// Copyright (C)2019-2023, 2025-2026 D. R. Commander
//
// This library is free software and may be redistributed and/or modified under
// the terms of the wxWindows Library License, Version 3.1 or (at your option)
// any later version.  The full license is in the LICENSE.txt file included
// with this distribution.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// wxWindows Library License for more details.

#include "backend.h"
#include "ContextHashEGL.h"
#include "PbufferHashEGL.h"
#include "EGLError.h"
#include "BufferState.h"
#include "PixmapHash.h"
#include "glxvisual.h"
#include "threadlocal.h"
#include <X11/Xmd.h>
#include <GL/glxproto.h>

#ifndef X_GLXCreateContextAttribsARB
#define X_GLXCreateContextAttribsARB  34
#endif


namespace backend {

#define CATCH_EGL(minorCode) \
	catch(EGLError &e) \
	{ \
		int glxError = e.getGLXError(); \
		bool isX11Error = e.isX11Error(); \
		if(glxError >= 0) \
		{ \
			if(fconfig.verbose) \
				vglout.print("[VGL] ERROR: in %s--\n[VGL]    %s\n", GET_METHOD(e), \
					e.what()); \
			faker::sendGLXError(dpy, minorCode, glxError, isX11Error); \
		} \
		else throw; \
	} \


VGL_THREAD_LOCAL(CurrentContextEGL, GLXContext, None)
VGL_THREAD_LOCAL(CurrentDrawableEGL, GLXDrawable, None)
VGL_THREAD_LOCAL(CurrentReadDrawableEGL, GLXDrawable, None)


static FakePbuffer *getCurrentFakePbuffer(EGLint readdraw)
{
	FakePbuffer *pb = PBHASHEGL.find(readdraw == EGL_READ ?
		getCurrentReadDrawableEGL() : getCurrentDrawableEGL());
	if(pb)
	{
		GLint fbo = -1;
		_glGetIntegerv(readdraw == EGL_READ ?
			GL_READ_FRAMEBUFFER_BINDING : GL_DRAW_FRAMEBUFFER_BINDING, &fbo);
		if(fbo == (GLint)pb->getFBO())
			return pb;
	}
	return NULL;
}


static GLuint getDefaultFBO(EGLint readdraw)
{
	FakePbuffer *pb = PBHASHEGL.find(readdraw == EGL_READ ?
		getCurrentReadDrawableEGL() : getCurrentDrawableEGL());
	if(pb) return pb->getFBO();
	return 0;
}


static GLenum getDefaultFBOAttachment(FakePbuffer *pb, GLenum attachment)
{
	switch(attachment)
	{
		case GL_FRONT_LEFT:
			return GL_COLOR_ATTACHMENT0;
		case GL_FRONT_RIGHT:
			return GL_COLOR_ATTACHMENT2;
		case GL_BACK_LEFT:
			return GL_COLOR_ATTACHMENT1;
		case GL_BACK_RIGHT:
			return GL_COLOR_ATTACHMENT3;
		case GL_DEPTH:
		{
			VGLFBConfig config = pb->getFBConfig();
			if(config->attr.stencilSize && config->attr.depthSize)
				return GL_DEPTH_STENCIL_ATTACHMENT;
			else
				return GL_DEPTH_ATTACHMENT;
		}
		case GL_STENCIL:
		{
			VGLFBConfig config = pb->getFBConfig();
			if(config->attr.stencilSize && config->attr.depthSize)
				return GL_DEPTH_STENCIL_ATTACHMENT;
			else
				return GL_STENCIL_ATTACHMENT;
		}
	}
	return attachment;
}


void bindFramebuffer(GLenum target, GLuint framebuffer, bool ext)
{
	const GLenum *oldDrawBufs = NULL;  GLsizei nDrawBufs = 0;
	GLenum oldReadBuf = GL_NONE;
	FakePbuffer *drawpb = NULL, *readpb = NULL;

	if(fconfig.egl)
	{
		if(framebuffer == 0)
		{
			if(target == GL_DRAW_FRAMEBUFFER || target == GL_FRAMEBUFFER)
			{
				drawpb = PBHASHEGL.find(getCurrentDrawableEGL());
				if(drawpb)
				{
					oldDrawBufs =
						CTXHASHEGL.getDrawBuffers(_eglGetCurrentContext(), nDrawBufs);
					framebuffer = drawpb->getFBO();
					CTXHASHEGL.setDrawFBO(_eglGetCurrentContext(), 0);
				}
			}
			if(target == GL_READ_FRAMEBUFFER || target == GL_FRAMEBUFFER)
			{
				readpb = PBHASHEGL.find(getCurrentReadDrawableEGL());
				if(readpb)
				{
					oldReadBuf = CTXHASHEGL.getReadBuffer(_eglGetCurrentContext());
					framebuffer = readpb->getFBO();
					CTXHASHEGL.setReadFBO(_eglGetCurrentContext(), 0);
				}
			}
		}
		else
		{
			if(target == GL_DRAW_FRAMEBUFFER || target == GL_FRAMEBUFFER)
				CTXHASHEGL.setDrawFBO(_eglGetCurrentContext(), framebuffer);
			if(target == GL_READ_FRAMEBUFFER || target == GL_FRAMEBUFFER)
				CTXHASHEGL.setReadFBO(_eglGetCurrentContext(), framebuffer);
		}
	}
	if(ext) _glBindFramebufferEXT(target, framebuffer);
	else _glBindFramebuffer(target, framebuffer);
	if(fconfig.egl)
	{
		if(oldDrawBufs)
		{
			if(nDrawBufs == 1)
				drawpb->setDrawBuffer(oldDrawBufs[0], false);
			else if(nDrawBufs > 0)
				drawpb->setDrawBuffers(nDrawBufs, oldDrawBufs, false);
			delete [] oldDrawBufs;
		}
		if(oldReadBuf) readpb->setReadBuffer(oldReadBuf, false);
	}
}


void blitNamedFramebuffer(GLuint readFramebuffer, GLuint drawFramebuffer,
	GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
	GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)
{
	if(fconfig.egl)
	{
		if(readFramebuffer == 0)
			readFramebuffer = getDefaultFBO(EGL_READ);
		if(drawFramebuffer == 0)
			drawFramebuffer = getDefaultFBO(EGL_DRAW);
	}
	_glBlitNamedFramebuffer(readFramebuffer, drawFramebuffer, srcX0, srcY0,
		srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}


GLenum checkNamedFramebufferStatus(GLuint framebuffer, GLenum target, bool ext)
{
	if(fconfig.egl && framebuffer == 0)
		framebuffer =
			getDefaultFBO(target == GL_READ_FRAMEBUFFER ? EGL_READ : EGL_DRAW);
	if(ext) return _glCheckNamedFramebufferStatusEXT(framebuffer, target);
	else return _glCheckNamedFramebufferStatus(framebuffer, target);
}


void clearNamedFramebufferfi(GLuint framebuffer, GLenum buffer,
	GLint drawbuffer, GLfloat depth, GLint stencil)
{
	if(fconfig.egl && framebuffer == 0)
		framebuffer = getDefaultFBO(EGL_DRAW);
	_glClearNamedFramebufferfi(framebuffer, buffer, drawbuffer, depth, stencil);
}


void clearNamedFramebufferfv(GLuint framebuffer, GLenum buffer,
	GLint drawbuffer, const GLfloat *value)
{
	if(fconfig.egl && framebuffer == 0)
		framebuffer = getDefaultFBO(EGL_DRAW);
	_glClearNamedFramebufferfv(framebuffer, buffer, drawbuffer, value);
}


void clearNamedFramebufferiv(GLuint framebuffer, GLenum buffer,
	GLint drawbuffer, const GLint *value)
{
	if(fconfig.egl && framebuffer == 0)
		framebuffer = getDefaultFBO(EGL_DRAW);
	_glClearNamedFramebufferiv(framebuffer, buffer, drawbuffer, value);
}


void clearNamedFramebufferuiv(GLuint framebuffer, GLenum buffer,
	GLint drawbuffer, const GLuint *value)
{
	if(fconfig.egl && framebuffer == 0)
		framebuffer = getDefaultFBO(EGL_DRAW);
	_glClearNamedFramebufferuiv(framebuffer, buffer, drawbuffer, value);
}


GLXContext createContext(Display *dpy, VGLFBConfig config, GLXContext share,
	Bool direct, const int *glxAttribs)
{
	if(fconfig.egl)
	{
		if(!direct) return NULL;

		int eglAttribs[MAX_ATTRIBS + 1], egli = 0;
		for(int i = 0; i < MAX_ATTRIBS + 1; i++) eglAttribs[i] = EGL_NONE;
		bool majorVerSpecified = false, forwardCompatSpecified = false;
		int majorVer = -1;

		if(glxAttribs && glxAttribs[0] != None)
		{
			for(int glxi = 0; glxAttribs[glxi] && egli < MAX_ATTRIBS; glxi += 2)
			{
				switch(glxAttribs[glxi])
				{
					case GLX_CONTEXT_MAJOR_VERSION_ARB:
						eglAttribs[egli++] = EGL_CONTEXT_MAJOR_VERSION;
						eglAttribs[egli++] = majorVer = glxAttribs[glxi + 1];
						majorVerSpecified = true;
						break;
					case GLX_CONTEXT_MINOR_VERSION_ARB:
						eglAttribs[egli++] = EGL_CONTEXT_MINOR_VERSION;
						eglAttribs[egli++] = glxAttribs[glxi + 1];
						break;
					case GLX_CONTEXT_FLAGS_ARB:
					{
						int flags = glxAttribs[glxi + 1];
						if(flags & GLX_CONTEXT_DEBUG_BIT_ARB)
						{
							eglAttribs[egli++] = EGL_CONTEXT_OPENGL_DEBUG;
							eglAttribs[egli++] = EGL_TRUE;
						}
						if(flags & GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB)
						{
							eglAttribs[egli++] = EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE;
							eglAttribs[egli++] = EGL_TRUE;
							forwardCompatSpecified = true;
						}
						if(flags & GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB)
						{
							// For future expansion
							eglAttribs[egli++] = EGL_CONTEXT_OPENGL_ROBUST_ACCESS;
							eglAttribs[egli++] = EGL_TRUE;
						}
						if(flags & ~(GLX_CONTEXT_DEBUG_BIT_ARB |
							GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB))
						{
							faker::sendGLXError(dpy, X_GLXCreateContextAttribsARB, BadValue,
								true);
							return NULL;
						}
						break;
					}
					case GLX_CONTEXT_PROFILE_MASK_ARB:
						// The mask bits are the same for GLX_ARB_create_context and EGL.
						eglAttribs[egli++] = EGL_CONTEXT_OPENGL_PROFILE_MASK;
						eglAttribs[egli++] = glxAttribs[glxi + 1];
						if(glxAttribs[glxi + 1] != GLX_CONTEXT_CORE_PROFILE_BIT_ARB
							&& glxAttribs[glxi + 1]
								!= GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB)
						{
							faker::sendGLXError(dpy, X_GLXCreateContextAttribsARB,
								GLXBadProfileARB, false);
							return NULL;
						}
						break;
					case GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB:
						switch(glxAttribs[glxi + 1])
						{
							case GLX_NO_RESET_NOTIFICATION_ARB:
								eglAttribs[egli++] =
									EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY;
								eglAttribs[egli++] = EGL_NO_RESET_NOTIFICATION;
								break;
							case GLX_LOSE_CONTEXT_ON_RESET_ARB:
								eglAttribs[egli++] =
									EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY;
								eglAttribs[egli++] = EGL_LOSE_CONTEXT_ON_RESET;
								break;
						}
						break;
					default:
						if(glxAttribs[glxi] == GLX_RENDER_TYPE
							&& glxAttribs[glxi + 1] == GLX_COLOR_INDEX_TYPE)
						{
							faker::sendGLXError(dpy, X_GLXCreateContextAttribsARB, BadMatch,
								true);
							return NULL;
						}
						else if(glxAttribs[glxi] != GLX_RENDER_TYPE
							|| glxAttribs[glxi + 1] != GLX_RGBA_TYPE)
						{
							faker::sendGLXError(dpy, X_GLXCreateContextAttribsARB, BadValue,
								true);
							return NULL;
						}
				}
			}
		}

		CARD16 minorCode = egli ? X_GLXCreateContextAttribsARB :
			X_GLXCreateNewContext;
		if(majorVerSpecified && forwardCompatSpecified && majorVer < 3)
		{
			faker::sendGLXError(dpy, minorCode, BadMatch, true);
			return NULL;
		}
		try
		{
			if(!VALID_CONFIG(config))
			{
				faker::sendGLXError(dpy, minorCode, GLXBadFBConfig, false);
				return NULL;
			}
			RBOCONTEXT.createContext(RBOContext::REFCOUNT_CONTEXT);
			if(!share)
				share = (GLXContext)RBOCONTEXT.getContext();
			if(!_eglBindAPI(EGL_OPENGL_API))
				THROW("Could not enable OpenGL API");
			GLXContext ctx = (GLXContext)_eglCreateContext(EDPY, (EGLConfig)0,
				(EGLContext)share, egli ? eglAttribs : NULL);
			EGLint eglError = _eglGetError();
			// Some implementations of eglCreateContext() return NULL but do not set
			// the EGL error if certain invalid OpenGL versions are passed to the
			// function.  This is why we can't have nice things.
			if(!ctx && eglError == EGL_SUCCESS && majorVerSpecified)
				eglError = EGL_BAD_MATCH;
			if(!ctx && eglError != EGL_SUCCESS)
				throw(EGLError("eglCreateContext()", __LINE__, eglError));
			if(ctx) CTXHASHEGL.add(ctx, config);
			return ctx;
		}
		CATCH_EGL(minorCode)
		return 0;
	}
	else
	{
		if(glxAttribs && glxAttribs[0] != None)
			return _glXCreateContextAttribsARB(DPY3D, GLXFBC(config), share, direct,
				glxAttribs);
		else
			return _glXCreateNewContext(DPY3D, GLXFBC(config), GLX_RGBA_TYPE, share,
				direct);
	}
}


GLXPbuffer createPbuffer(Display *dpy, VGLFBConfig config,
	const int *glxAttribs)
{
	if(fconfig.egl)
	{
		try
		{
			FakePbuffer *pb = new FakePbuffer(dpy, config, glxAttribs);
			GLXDrawable id = pb->getID();
			if(id) PBHASHEGL.add(id, pb);
			return id;
		}
		CATCH_EGL(X_GLXCreatePbuffer)
		return 0;
	}
	else return _glXCreatePbuffer(DPY3D, GLXFBC(config), glxAttribs);
}


void deleteFramebuffers(GLsizei n, const GLuint *framebuffers, bool ext)
{
	if(fconfig.egl)
	{
		if(n > 0 && framebuffers)
		{
			GLint drawFBO = -1, readFBO = -1;
			_glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFBO);
			_glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readFBO);
			for(GLsizei i = 0; i < n; i++)
			{
				if((GLint)framebuffers[i] == drawFBO)
					bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
				if((GLint)framebuffers[i] == readFBO)
					bindFramebuffer(GL_READ_FRAMEBUFFER, 0);
			}
		}
	}
	if(ext) _glDeleteFramebuffersEXT(n, framebuffers);
	else _glDeleteFramebuffers(n, framebuffers);
}


void destroyContext(Display *dpy, GLXContext ctx)
{
	if(fconfig.egl)
	{
		try
		{
			if(!ctx) return;
			VGLFBConfig config = CTXHASHEGL.findConfig(ctx);
			CTXHASHEGL.remove(ctx);
			RBOCONTEXT.destroyContext(RBOContext::REFCOUNT_CONTEXT);
			if(!_eglBindAPI(EGL_OPENGL_API))
				THROW("Could not enable OpenGL API");
			if(!_eglDestroyContext(EDPY, (EGLContext)ctx))
				THROW_EGL("eglDestroyContext()");
			if(!config)
				faker::sendGLXError(dpy, X_GLXDestroyContext, GLXBadContext, false);
		}
		CATCH_EGL(X_GLXDestroyContext)
	}
	else _glXDestroyContext(DPY3D, ctx);
}


void destroyPbuffer(Display *dpy, GLXPbuffer pbuf)
{
	if(fconfig.egl)
	{
		try
		{
			PBHASHEGL.remove(pbuf);
		}
		CATCH_EGL(X_GLXDestroyPbuffer)
	}
	else _glXDestroyPbuffer(DPY3D, pbuf);
}


void drawBuffer(GLenum mode)
{
	if(fconfig.egl)
	{
		FakePbuffer *pb = getCurrentFakePbuffer(EGL_DRAW);
		if(pb)
		{
			pb->setDrawBuffer(mode, false);
			return;
		}
	}
	_glDrawBuffer(mode);
}


void drawBuffers(GLsizei n, const GLenum *bufs)
{
	if(fconfig.egl)
	{
		FakePbuffer *pb = getCurrentFakePbuffer(EGL_DRAW);
		if(pb)
		{
			pb->setDrawBuffers(n, bufs, false);
			return;
		}
	}
	_glDrawBuffers(n, bufs);
}


GLXContext getCurrentContext(void)
{
	if(fconfig.egl)
		return getCurrentContextEGL();
	else
		return _glXGetCurrentContext();
}


Display *getCurrentDisplay(void)
{
	if(fconfig.egl)
	{
		FakePbuffer *pb = PBHASHEGL.find(getCurrentDrawableEGL());
		return pb ? pb->getDisplay() : NULL;
	}
	else return _glXGetCurrentDisplay();
}


GLXDrawable getCurrentDrawable(void)
{
	if(fconfig.egl)
		return getCurrentDrawableEGL();
	else
		return _glXGetCurrentDrawable();
}


GLXDrawable getCurrentReadDrawable(void)
{
	if(fconfig.egl)
		return getCurrentReadDrawableEGL();
	else
		return _glXGetCurrentReadDrawable();
}


int getFBConfigAttrib(Display *dpy, VGLFBConfig config, int attribute,
	int *value)
{
	if(fconfig.egl)
	{
		if(!value) return GLX_BAD_VALUE;

		switch(attribute)
		{
			case GLX_FBCONFIG_ID:
				*value = config->id;
				return Success;
			case GLX_BUFFER_SIZE:
				*value = config->attr.redSize + config->attr.greenSize +
					config->attr.blueSize + config->attr.alphaSize;
				return Success;
			case GLX_LEVEL:
				*value = 0;
				return Success;
			case GLX_DOUBLEBUFFER:
				*value = config->attr.doubleBuffer;
				return Success;
			case GLX_STEREO:
				*value = config->attr.stereo;
				return Success;
			case GLX_AUX_BUFFERS:
				*value = 0;
				return Success;
			case GLX_RED_SIZE:
				*value = config->attr.redSize;
				return Success;
			case GLX_GREEN_SIZE:
				*value = config->attr.greenSize;
				return Success;
			case GLX_BLUE_SIZE:
				*value = config->attr.blueSize;
				return Success;
			case GLX_ALPHA_SIZE:
				*value = config->attr.alphaSize;
				return Success;
			case GLX_DEPTH_SIZE:
				*value = config->attr.depthSize;
				return Success;
			case GLX_STENCIL_SIZE:
				*value = config->attr.stencilSize;
				return Success;
			case GLX_ACCUM_RED_SIZE:
			case GLX_ACCUM_GREEN_SIZE:
			case GLX_ACCUM_BLUE_SIZE:
			case GLX_ACCUM_ALPHA_SIZE:
				*value = 0;
				return Success;
			case GLX_RENDER_TYPE:
				*value = GLX_RGBA_BIT;
				return Success;
			case GLX_DRAWABLE_TYPE:
				*value = GLX_PBUFFER_BIT |
					(config->visualID ? GLX_WINDOW_BIT | GLX_PIXMAP_BIT : 0);
				return Success;
			case GLX_X_RENDERABLE:
				*value = !!config->visualID;
				return Success;
			case GLX_VISUAL_ID:
				*value = config->visualID;
				return Success;
			case GLX_X_VISUAL_TYPE:
				*value = config->c_class == TrueColor ?
					GLX_TRUE_COLOR : GLX_DIRECT_COLOR;
				return Success;
			case GLX_CONFIG_CAVEAT:
			case GLX_TRANSPARENT_TYPE:
				*value = GLX_NONE;
				return Success;
			case GLX_TRANSPARENT_INDEX_VALUE:
			case GLX_TRANSPARENT_RED_VALUE:
			case GLX_TRANSPARENT_GREEN_VALUE:
			case GLX_TRANSPARENT_BLUE_VALUE:
			case GLX_TRANSPARENT_ALPHA_VALUE:
				*value = 0;
				return Success;
			case GLX_MAX_PBUFFER_WIDTH:
			{
				*value = config->maxPBWidth;
				return EGL_TRUE;
			}
			case GLX_MAX_PBUFFER_HEIGHT:
			{
				*value = config->maxPBHeight;
				return EGL_TRUE;
			}
			case GLX_MAX_PBUFFER_PIXELS:
			{
				*value = config->maxPBWidth * config->maxPBHeight;
				return EGL_TRUE;
			}
			case GLX_SAMPLE_BUFFERS:
				*value = !!config->attr.samples;
				return Success;
			case GLX_SAMPLES:
				*value = config->attr.samples;
				return Success;
			case GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT:
				*value = !!(config->attr.redSize + config->attr.greenSize +
					config->attr.blueSize == 24);
				return Success;
			default:
				return GLX_BAD_ATTRIBUTE;
		}
	}  // fconfig.egl
	else return _glXGetFBConfigAttrib(DPY3D, GLXFBC(config), attribute, value);
}


void getFramebufferAttachmentParameteriv(GLenum target, GLenum attachment,
	GLenum pname, GLint *params, bool ext)
{
	bool isDefault = false;

	if(fconfig.egl && params)
	{
		if((attachment >= GL_FRONT_LEFT && attachment <= GL_BACK_RIGHT)
			|| (attachment >= GL_DEPTH && attachment <= GL_STENCIL))
		{
			FakePbuffer *pb;
			if(((target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER)
					&& (pb = getCurrentFakePbuffer(EGL_DRAW)) != NULL)
				|| (target == GL_READ_FRAMEBUFFER
					&& (pb = getCurrentFakePbuffer(EGL_READ)) != NULL))
			{
				isDefault = true;
				attachment = getDefaultFBOAttachment(pb, attachment);
			}
		}
	}
	if(ext)
		_glGetFramebufferAttachmentParameterivEXT(target, attachment, pname,
			params);
	else
		_glGetFramebufferAttachmentParameteriv(target, attachment, pname, params);
	if(fconfig.egl && params)
	{
		if(isDefault && *params == GL_RENDERBUFFER)
			*params = GL_FRAMEBUFFER_DEFAULT;
	}
}


void getFramebufferParameteriv(GLenum target, GLenum pname, GLint *params)
{
	if(fconfig.egl && params)
	{
		FakePbuffer *pb;
		if(((target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER)
				&& (pb = getCurrentFakePbuffer(EGL_DRAW)) != NULL)
			|| (target == GL_READ_FRAMEBUFFER
				&& (pb = getCurrentFakePbuffer(EGL_READ)) != NULL))
		{
			if(pname == GL_DOUBLEBUFFER)
			{
				*params = pb->getFBConfig()->attr.doubleBuffer;
				return;
			}
			else if(pname == GL_STEREO)
			{
				*params = pb->getFBConfig()->attr.stereo;
				return;
			}
		}
	}
	_glGetFramebufferParameteriv(target, pname, params);
}


void getFramebufferParameterivEXT(GLuint framebuffer, GLenum pname,
	GLint *params)
{
	if(fconfig.egl && params && framebuffer == 0
		&& (pname == GL_DRAW_BUFFER || pname == GL_READ_BUFFER
			|| (pname >= GL_DRAW_BUFFER0 && pname <= GL_DRAW_BUFFER15)))
	{
		FakePbuffer *pb;
		if(pname == GL_DRAW_BUFFER)
		{
			if((pb = PBHASHEGL.find(getCurrentDrawableEGL())) != NULL)
			{
				*params = pb->getDrawBuffer(0);
				return;
			}
		}
		else if(pname >= GL_DRAW_BUFFER0 && pname <= GL_DRAW_BUFFER15)
		{
			if((pb = PBHASHEGL.find(getCurrentDrawableEGL())) != NULL)
			{
				int index = pname - GL_DRAW_BUFFER0;
				*params = pb->getDrawBuffer(index);
				return;
			}
		}
		else if(pname == GL_READ_BUFFER)
		{
			if((pb = PBHASHEGL.find(getCurrentReadDrawableEGL())) != NULL)
			{
				*params = pb->getReadBuffer();
				return;
			}
		}
	}
	_glGetFramebufferParameterivEXT(framebuffer, pname, params);
}


void getIntegerv(GLenum pname, GLint *params)
{
	if(fconfig.egl)
	{
		if(!_eglBindAPI(EGL_OPENGL_API))
			THROW("Could not enable OpenGL API");
		EGLContext ctx = _eglGetCurrentContext();
		VGLFBConfig config = CTXHASHEGL.findConfig(ctx);

		if(!params || !config)
		{
			_glGetIntegerv(pname, params);
			return;
		}
		else if(pname == GL_DOUBLEBUFFER)
		{
			FakePbuffer *pb = getCurrentFakePbuffer(EGL_DRAW);
			if(pb)
			{
				*params = config->attr.doubleBuffer;
				return;
			}
		}
		else if(pname == GL_DRAW_BUFFER)
		{
			FakePbuffer *pb = getCurrentFakePbuffer(EGL_DRAW);
			GLenum drawBuf = CTXHASHEGL.getDrawBuffer(ctx, 0);
			if(pb)
			{
				*params = drawBuf;
				return;
			}
		}
		else if(pname >= GL_DRAW_BUFFER0 && pname <= GL_DRAW_BUFFER15)
		{
			FakePbuffer *pb = getCurrentFakePbuffer(EGL_DRAW);
			int index = pname - GL_DRAW_BUFFER0;
			GLenum drawBuf = CTXHASHEGL.getDrawBuffer(ctx, index);
			if(pb)
			{
				*params = drawBuf;
				return;
			}
		}
		else if(pname == GL_DRAW_FRAMEBUFFER_BINDING)
		{
			*params = CTXHASHEGL.getDrawFBO(ctx);
			return;
		}
		else if(pname == GL_MAX_DRAW_BUFFERS)
		{
			_glGetIntegerv(GL_MAX_DRAW_BUFFERS, params);
			if(*params > 16) *params = 16;
			return;
		}
		else if(pname == GL_READ_BUFFER)
		{
			FakePbuffer *pb = getCurrentFakePbuffer(EGL_READ);
			GLenum readBuf = CTXHASHEGL.getReadBuffer(ctx);
			if(pb)
			{
				*params = readBuf;
				return;
			}
		}
		else if(pname == GL_READ_FRAMEBUFFER_BINDING)
		{
			*params = CTXHASHEGL.getReadFBO(ctx);
			return;
		}
		else if(pname == GL_STEREO)
		{
			FakePbuffer *pb = getCurrentFakePbuffer(EGL_DRAW);
			if(pb)
			{
				*params = config->attr.stereo;
				return;
			}
		}
	}
	_glGetIntegerv(pname, params);
}


void getNamedFramebufferAttachmentParameteriv(GLuint framebuffer,
	GLenum attachment, GLenum pname, GLint *params, bool ext)
{
	bool isDefault = false;

	if(fconfig.egl && params)
	{
		if((attachment >= GL_FRONT_LEFT && attachment <= GL_BACK_RIGHT)
			|| (attachment >= GL_DEPTH && attachment <= GL_STENCIL))
		{
			FakePbuffer *pb;
			if(framebuffer == 0
				&& (pb = PBHASHEGL.find(getCurrentDrawableEGL())) != NULL)
			{
				framebuffer = pb->getFBO();
				isDefault = true;
				attachment = getDefaultFBOAttachment(pb, attachment);
			}
		}
	}
	if(ext)
		_glGetNamedFramebufferAttachmentParameterivEXT(framebuffer, attachment,
			pname, params);
	else
		_glGetNamedFramebufferAttachmentParameteriv(framebuffer, attachment, pname,
			params);
	if(fconfig.egl && params)
	{
		if(isDefault && *params == GL_RENDERBUFFER)
			*params = GL_FRAMEBUFFER_DEFAULT;
	}
}


void getNamedFramebufferParameteriv(GLuint framebuffer, GLenum pname,
	GLint *param, bool ext)
{
	if(fconfig.egl && param)
	{
		FakePbuffer *pb;
		if(framebuffer == 0
			&& (pb = PBHASHEGL.find(getCurrentDrawableEGL())) != NULL)
		{
			if(pname == GL_DOUBLEBUFFER)
			{
				*param = pb->getFBConfig()->attr.doubleBuffer;
				return;
			}
			else if(pname == GL_STEREO)
			{
				*param = pb->getFBConfig()->attr.stereo;
				return;
			}
			else framebuffer = pb->getFBO();
		}
	}
	if(ext) _glGetNamedFramebufferParameterivEXT(framebuffer, pname, param);
	else _glGetNamedFramebufferParameteriv(framebuffer, pname, param);
}


void invalidateNamedFramebufferData(GLuint framebuffer, GLsizei numAttachments,
	const GLenum *attachments)
{
	GLenum newAttachments[MAX_ATTRIBS];
	if(fconfig.egl && framebuffer == 0 && numAttachments > 0 && attachments)
	{
		FakePbuffer *pb;
		if((pb = PBHASHEGL.find(getCurrentDrawableEGL())) != NULL)
		{
			framebuffer = pb->getFBO();
			numAttachments = min(numAttachments, MAX_ATTRIBS);
			for(int i = 0; i < numAttachments; i++)
			{
				if(attachments[i] == GL_COLOR)
					newAttachments[i] = pb->getFBConfig()->attr.doubleBuffer ?
						GL_BACK_LEFT : GL_FRONT_LEFT;
				if((attachments[i] >= GL_FRONT_LEFT && attachments[i] <= GL_BACK_RIGHT)
					|| (attachments[i] >= GL_DEPTH && attachments[i] <= GL_STENCIL))
					newAttachments[i] = getDefaultFBOAttachment(pb, attachments[i]);
				else newAttachments[i] = attachments[i];
			}
			attachments = newAttachments;
		}
	}
	_glInvalidateNamedFramebufferData(framebuffer, numAttachments, attachments);
}


void invalidateNamedFramebufferSubData(GLuint framebuffer,
	GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y,
	GLsizei width, GLsizei height)
{
	GLenum newAttachments[MAX_ATTRIBS];
	if(fconfig.egl && framebuffer == 0 && numAttachments > 0 && attachments)
	{
		FakePbuffer *pb;
		if((pb = PBHASHEGL.find(getCurrentDrawableEGL())) != NULL)
		{
			framebuffer = pb->getFBO();
			numAttachments = min(numAttachments, MAX_ATTRIBS);
			for(int i = 0; i < numAttachments; i++)
			{
				if(attachments[i] == GL_COLOR)
					newAttachments[i] = pb->getFBConfig()->attr.doubleBuffer ?
						GL_BACK_LEFT : GL_FRONT_LEFT;
				if((attachments[i] >= GL_FRONT_LEFT && attachments[i] <= GL_BACK_RIGHT)
					|| (attachments[i] >= GL_DEPTH && attachments[i] <= GL_STENCIL))
					newAttachments[i] = getDefaultFBOAttachment(pb, attachments[i]);
				else newAttachments[i] = attachments[i];
			}
			attachments = newAttachments;
		}
	}
	_glInvalidateNamedFramebufferSubData(framebuffer, numAttachments,
		attachments, x, y, width, height);
}


Bool isDirect(GLXContext ctx)
{
	if(fconfig.egl)
		return True;
	else
		return _glXIsDirect(DPY3D, ctx);
}


Bool makeCurrent(Display *dpy, GLXDrawable draw, GLXDrawable read,
	GLXContext ctx)
{
	if(fconfig.egl)
	{
		try
		{
			if(!_eglBindAPI(EGL_OPENGL_API))
				THROW("Could not enable OpenGL API");
			EGLBoolean ret = (Bool)_eglMakeCurrent(EDPY, EGL_NO_SURFACE,
				EGL_NO_SURFACE, (EGLContext)ctx);
			if(!ret) THROW_EGL("eglMakeCurrent()");
			setCurrentContextEGL(ctx);
			setCurrentDrawableEGL(draw);
			setCurrentReadDrawableEGL(read);
			if(!ctx)
			{
				_eglReleaseThread();
				return True;
			}

			FakePbuffer *drawpb = NULL, *readpb = NULL;
			drawpb = PBHASHEGL.find(draw);
			readpb = (read == draw ? drawpb : PBHASHEGL.find(read));
			GLint drawFBO = -1, readFBO = -1;
			_glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFBO);
			_glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readFBO);

			if(drawpb)
				drawpb->createBuffer(false, true,
					(CTXHASHEGL.getDrawFBO(ctx) == 0 || drawFBO == 0),
					(CTXHASHEGL.getReadFBO(ctx) == 0 || readFBO == 0));
			if(readpb && readpb != drawpb)
				readpb->createBuffer(false, true,
					(CTXHASHEGL.getDrawFBO(ctx) == 0 || drawFBO == 0),
					(CTXHASHEGL.getReadFBO(ctx) == 0 || readFBO == 0));

			bool boundNewDrawFBO = false, boundNewReadFBO = false;
			if(drawpb && (CTXHASHEGL.getDrawFBO(ctx) == 0 || drawFBO == 0))
			{
				_glBindFramebuffer(GL_DRAW_FRAMEBUFFER, drawpb->getFBO());
				boundNewDrawFBO = true;
			}
			if(readpb && (CTXHASHEGL.getReadFBO(ctx) == 0 || readFBO == 0))
			{
				_glBindFramebuffer(GL_READ_FRAMEBUFFER, readpb->getFBO());
				boundNewReadFBO = true;
			}

			VGLFBConfig config = CTXHASHEGL.findConfig(ctx);
			if(drawpb)
			{
				if(drawFBO == 0 && config)
				{
					drawpb->setDrawBuffer(config->attr.doubleBuffer ?
						GL_BACK : GL_FRONT, false);
					_glViewport(0, 0, drawpb->getWidth(), drawpb->getHeight());
				}
				else if(boundNewDrawFBO)
				{
					const GLenum *oldDrawBufs;  GLsizei nDrawBufs = 0;
					oldDrawBufs = CTXHASHEGL.getDrawBuffers(ctx, nDrawBufs);
					if(oldDrawBufs && nDrawBufs > 0)
					{
						if(nDrawBufs == 1)
							drawpb->setDrawBuffer(oldDrawBufs[0], false);
						else
							drawpb->setDrawBuffers(nDrawBufs, oldDrawBufs, false);
						delete [] oldDrawBufs;
					}
				}
			}
			if(readpb)
			{
				if(readFBO == 0 && config)
					readpb->setReadBuffer(config->attr.doubleBuffer ?
						GL_BACK : GL_FRONT, false);
				else if(boundNewReadFBO)
				{
					GLenum oldReadBuf = CTXHASHEGL.getReadBuffer(ctx);
					if(oldReadBuf) readpb->setReadBuffer(oldReadBuf, false);
				}
			}

			return ret;
		}
		CATCH_EGL(X_GLXMakeContextCurrent)
		return 0;
	}
	else return _glXMakeContextCurrent(DPY3D, draw, read, ctx);
}


void namedFramebufferDrawBuffer(GLuint framebuffer, GLenum buf, bool ext)
{
	if(fconfig.egl)
	{
		FakePbuffer *pb;
		if(framebuffer == 0
			&& (pb = PBHASHEGL.find(getCurrentDrawableEGL())) != NULL)
		{
			pb->setDrawBuffer(buf, true);
			return;
		}
	}
	if(ext) _glFramebufferDrawBufferEXT(framebuffer, buf);
	else _glNamedFramebufferDrawBuffer(framebuffer, buf);
}


void namedFramebufferDrawBuffers(GLuint framebuffer, GLsizei n,
	const GLenum *bufs, bool ext)
{
	if(fconfig.egl)
	{
		FakePbuffer *pb;
		if(framebuffer == 0
			&& (pb = PBHASHEGL.find(getCurrentDrawableEGL())) != NULL)
		{
			pb->setDrawBuffers(n, bufs, true);
			return;
		}
	}
	if(ext) _glFramebufferDrawBuffersEXT(framebuffer, n, bufs);
	else _glNamedFramebufferDrawBuffers(framebuffer, n, bufs);
}


void namedFramebufferReadBuffer(GLuint framebuffer, GLenum mode, bool ext)
{
	if(fconfig.egl)
	{
		FakePbuffer *pb;
		if(framebuffer == 0
			&& (pb = PBHASHEGL.find(getCurrentReadDrawableEGL())) != NULL)
		{
			pb->setReadBuffer(mode, true);
			return;
		}
	}
	if(ext) _glFramebufferReadBufferEXT(framebuffer, mode);
	else _glNamedFramebufferReadBuffer(framebuffer, mode);
}


int queryContext(Display *dpy, GLXContext ctx, int attribute, int *value)
{
	if(fconfig.egl)
	{
		int retval = Success;
		VGLFBConfig config;

		if(!ctx || !(config = CTXHASHEGL.findConfig(ctx)))
		{
			faker::sendGLXError(dpy, X_GLXQueryContext, GLXBadContext, false);
			return GLX_BAD_CONTEXT;
		}
		switch(attribute)
		{
			case GLX_FBCONFIG_ID:
				*value = config->id;
				retval = Success;
				break;
			case GLX_RENDER_TYPE:
				*value = GLX_RGBA_TYPE;
				retval = Success;
				break;
			case GLX_SCREEN:
				*value = config->screen;
				retval = Success;
				break;
			default:
				retval = GLX_BAD_ATTRIBUTE;
		}

		return retval;
	}
	else
	{
		int retval = _glXQueryContext(DPY3D, ctx, attribute, value);
		if(fconfig.amdgpuHack && ctx && attribute == GLX_RENDER_TYPE && value
			&& *value == 0)
			*value = GLX_RGBA_TYPE;
		return retval;
	}
}


void queryDrawable(Display *dpy, GLXDrawable draw, int attribute,
	unsigned int *value)
{
	if(fconfig.egl)
	{
		FakePbuffer *pb = NULL;

		if(!value) return;

		if(!draw || (pb = PBHASHEGL.find(draw)) == NULL)
		{
			faker::sendGLXError(dpy, X_GLXGetDrawableAttributes, GLXBadDrawable,
				false);
			return;
		}

		switch(attribute)
		{
			case GLX_WIDTH:
				*value = pb->getWidth();
				return;
			case GLX_HEIGHT:
				*value = pb->getHeight();
				return;
			case GLX_PRESERVED_CONTENTS:
				*value = 1;
				return;
			case GLX_LARGEST_PBUFFER:
				*value = 0;
				return;
			case GLX_FBCONFIG_ID:
				*value = pb->getFBConfig() ? pb->getFBConfig()->id : 0;
				return;
			default:
				return;
		}
	}
	else _glXQueryDrawable(DPY3D, draw, attribute, value);
}


Bool queryExtension(Display *dpy, int *majorOpcode, int *eventBase,
	int *errorBase)
{
	if(fconfig.egl)
	{
		// If the 2D X server has a GLX extension, then we hijack its major opcode
		// and error base.
		Bool retval = _XQueryExtension(dpy, "GLX", majorOpcode, eventBase,
			errorBase);
		// Otherwise, there is no sane way for the EGL back end to operate, mostly
		// because of XCB.  However, we don't throw a fatal error here, because the
		// 3D application is well within its rights to ask whether the GLX
		// extension is present and then not use it.  (It could, for instance, use
		// EGL/X11 instead.)
		if(!retval)
		{
			static bool alreadyWarned = false;
			if(!alreadyWarned)
			{
				if(fconfig.verbose)
					vglout.print("[VGL] WARNING: The EGL back end requires a 2D X server with a GLX extension.\n");
				alreadyWarned = true;
			}
		}
		return retval;
	}
	// When using the GLX back end, all GLX errors will come from the 3D X
	// server.
	else
		return _XQueryExtension(DPY3D, "GLX", majorOpcode, eventBase, errorBase);
}


void readBuffer(GLenum mode)
{
	if(fconfig.egl)
	{
		FakePbuffer *pb = getCurrentFakePbuffer(EGL_READ);
		if(pb)
		{
			pb->setReadBuffer(mode, false);
			return;
		}
	}
	_glReadBuffer(mode);
}


void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
	GLenum type, void *data)
{
	if(fconfig.egl)
	{
		bool fallthrough = true;
		VGLFBConfig config = CTXHASHEGL.findConfig(_eglGetCurrentContext());
		FakePbuffer *readpb = getCurrentFakePbuffer(EGL_READ);

		if(config && config->attr.samples > 1 && readpb)
		{
			GLuint fbo = 0, rbo = 0;
			_glGenFramebuffers(1, &fbo);
			if(fbo)
			{
				BufferState bs(BS_DRAWFBO | BS_READFBO | BS_DRAWBUFS | BS_READBUF);
				_glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

				_glGenRenderbuffers(1, &rbo);
				if(rbo)
				{
					BufferState bsRBO(BS_RBO);
					_glBindRenderbuffer(GL_RENDERBUFFER, rbo);

					GLenum internalFormat = GL_RGB8;
					if(config->attr.redSize > 8) internalFormat = GL_RGB10_A2;
					else if(config->attr.alphaSize) internalFormat = GL_RGBA8;
					_glRenderbufferStorage(GL_RENDERBUFFER, internalFormat,
						readpb->getWidth(), readpb->getHeight());
					_glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
						GL_RENDERBUFFER, rbo);

					GLenum status = _glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
					if(status == GL_FRAMEBUFFER_COMPLETE)
					{
						_glBlitFramebuffer(0, 0, readpb->getWidth(), readpb->getHeight(),
							0, 0, readpb->getWidth(), readpb->getHeight(),
							GL_COLOR_BUFFER_BIT, GL_NEAREST);
						_glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bs.getOldReadFBO());
						_glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
						_glReadPixels(x, y, width, height, format, type, data);
						fallthrough = false;
					}
					_glDeleteRenderbuffers(1, &rbo);
				}
				_glDeleteFramebuffers(1, &fbo);
			}
		}
		if(!fallthrough) return;
	}
	_glReadPixels(x, y, width, height, format, type, data);
}


void swapBuffers(Display *dpy, GLXDrawable drawable)
{
	if(fconfig.egl)
	{
		try
		{
			if(PMHASH.find(dpy, drawable)) return;

			FakePbuffer *pb = NULL;

			if(drawable && (pb = PBHASHEGL.find(drawable)) != NULL)
				pb->swap();
			else
				faker::sendGLXError(dpy, X_GLXSwapBuffers, GLXBadDrawable, false);
		}
		CATCH_EGL(X_GLXSwapBuffers)
	}
	else _glXSwapBuffers(DPY3D, drawable);
}

}  // namespace
