From 6094fc6ab3585195f16bd53dcaadf2b45e9dd4a8 Mon Sep 17 00:00:00 2001
From: Spoike <acceptthis@users.sourceforge.net>
Date: Tue, 15 Sep 2020 08:54:10 +0000
Subject: [PATCH] '+set vid_renderer egl_headless' will now give a headless
 client that's able to take screenshots and video captures without creating
 any kind of window. Because we can.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5763 fc73d0e0-1445-4013-8a0c-d673dee63da5
---
 engine/client/renderer.c     |   6 ++
 engine/client/vid_headless.c |   2 +-
 engine/gl/gl_videgl.c        | 201 +++++++++++++++++++++++++++++++++--
 engine/gl/gl_videgl.h        |   4 +
 4 files changed, 201 insertions(+), 12 deletions(-)

diff --git a/engine/client/renderer.c b/engine/client/renderer.c
index 2cb036809..a92db6716 100644
--- a/engine/client/renderer.c
+++ b/engine/client/renderer.c
@@ -1225,6 +1225,9 @@ rendererinfo_t dedicatedrendererinfo = {
 #ifdef HEADLESSQUAKE
 	extern rendererinfo_t headlessrenderer;
 #endif
+#if defined(GLQUAKE) && defined(USE_EGL)
+	extern rendererinfo_t rendererinfo_headless_egl;
+#endif
 
 static struct
 {
@@ -1279,6 +1282,9 @@ static struct
 		//{NULL, &headlessvkrendererinfo},
 	#endif
 #endif
+#if defined(GLQUAKE) && defined(USE_EGL)
+	{NULL, &rendererinfo_headless_egl},
+#endif
 };
 
 qboolean R_RegisterRenderer(void *module, rendererinfo_t *ri)
diff --git a/engine/client/vid_headless.c b/engine/client/vid_headless.c
index 97aef1e17..ac6fc9ac9 100644
--- a/engine/client/vid_headless.c
+++ b/engine/client/vid_headless.c
@@ -243,7 +243,7 @@ static void Headless_BE_RenderToTextureUpdate2d	(qboolean destchanged)
 
 rendererinfo_t headlessrenderer =
 {
-	"Headless",
+	"Headless (Null)",
 	{"headless"},
 	QR_HEADLESS,
 
diff --git a/engine/gl/gl_videgl.c b/engine/gl/gl_videgl.c
index 87edf5ad2..a850a2f51 100644
--- a/engine/gl/gl_videgl.c
+++ b/engine/gl/gl_videgl.c
@@ -33,9 +33,11 @@ static EGLBoolean	(EGLAPIENTRY *qeglTerminate)(EGLDisplay dpy);
 static EGLBoolean	(EGLAPIENTRY *qeglGetConfigs)(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config);
 static EGLBoolean	(EGLAPIENTRY *qeglChooseConfig)(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);
 EGLBoolean	(EGLAPIENTRY *qeglGetConfigAttrib)(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value);
+static EGLBoolean	(EGLAPIENTRY *qeglBindAPI) (EGLenum api);
 
 static EGLSurface	(EGLAPIENTRY *qeglCreatePlatformWindowSurface)(EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list);
 static EGLSurface	(EGLAPIENTRY *qeglCreateWindowSurface)(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list);
+static EGLSurface	(EGLAPIENTRY *qeglCreatePbufferSurface) (EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list);
 static EGLBoolean	(EGLAPIENTRY *qeglDestroySurface)(EGLDisplay dpy, EGLSurface surface);
 static EGLBoolean	(EGLAPIENTRY *qeglQuerySurface)(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value);
 
@@ -59,6 +61,7 @@ static dllfunction_t qeglfuncs[] =
 	{(void*)&qeglChooseConfig, "eglChooseConfig"},
 	{(void*)&qeglGetConfigAttrib, "eglGetConfigAttrib"},
 
+	{(void*)&qeglCreatePbufferSurface, "eglCreatePbufferSurface"},
 	{(void*)&qeglCreateWindowSurface, "eglCreateWindowSurface"},
 	{(void*)&qeglDestroySurface, "eglDestroySurface"},
 	{(void*)&qeglQuerySurface, "eglQuerySurface"},
@@ -140,7 +143,7 @@ qboolean EGL_LoadLibrary(char *driver)
 		(most things are expected to statically link to their libs)
 		strictly speaking, EGL says that functions should work regardless of context.
 		(which of course makes portability a nightmare, especially on windows where static linking is basically impossible)
-		(android's EGL bugs out if you use eglGetProcAddress for core functions too)
+		(android's EGL bugs out if you use eglGetProcAddress for core functions too, note that EGL_KHR_get_all_proc_addresses fixes that.)
 	*/
 	Sys_Printf("Attempting to dlopen libGLESv2... ");
 	eslibrary = Sys_LoadLibrary("libGLESv2", NULL);
@@ -183,6 +186,10 @@ qboolean EGL_LoadLibrary(char *driver)
 	}
 	Sys_Printf("success\n");
 
+
+	//egl1.2
+	qeglBindAPI = EGL_Proc("eglBindAPI");
+
 	//these are from egl1.5
 	qeglGetPlatformDisplay		= EGL_Proc("eglGetPlatformDisplay");
 	qeglCreatePlatformWindowSurface	= EGL_Proc("eglCreatePlatformWindowSurface");
@@ -298,7 +305,7 @@ qboolean EGL_InitDisplay (rendererstate_t *info, int eglplat, void *ndpy, EGLNat
 	EGLint major=0, minor=0;
 	EGLint attrib[] =
 	{
-		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+		EGL_SURFACE_TYPE, (eglplat==EGL_PLATFORM_DEVICE_EXT)?EGL_PBUFFER_BIT:EGL_WINDOW_BIT,
 //		EGL_BUFFER_SIZE, info->bpp,
 //		EGL_SAMPLES, info->multisample,
 //		EGL_STENCIL_SIZE, 8,
@@ -370,19 +377,26 @@ qboolean EGL_InitDisplay (rendererstate_t *info, int eglplat, void *ndpy, EGLNat
 
 qboolean EGL_InitWindow (rendererstate_t *info, int eglplat, void *nwindow, EGLNativeWindowType windowid, EGLConfig cfg)
 {
-	EGLint contextattr[] =
-	{
-		EGL_CONTEXT_CLIENT_VERSION, 2,	//requires EGL 1.3
-		EGL_NONE, EGL_NONE
-	};
+	EGLint renderabletype;
 
-	if (qeglCreatePlatformWindowSurface)
+	if (eglplat == EGL_PLATFORM_DEVICE_EXT && qeglCreatePbufferSurface)
+	{
+		EGLint wndattrib[] =
+		{
+			EGL_WIDTH, vid.pixelwidth,
+			EGL_HEIGHT, vid.pixelheight,
+//			EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR,
+			EGL_NONE,EGL_NONE
+		};
+		eglsurf = qeglCreatePbufferSurface(egldpy, cfg, wndattrib);
+	}
+	else if (qeglCreatePlatformWindowSurface)
 	{
 		EGLAttrib wndattrib[] =
 		{
 //			EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR,
 
-			EGL_NONE
+			EGL_NONE,EGL_NONE
 		};
 		eglsurf = qeglCreatePlatformWindowSurface(egldpy, cfg, nwindow, info->srgb?wndattrib:NULL);
 	}
@@ -392,7 +406,7 @@ qboolean EGL_InitWindow (rendererstate_t *info, int eglplat, void *nwindow, EGLN
 		{
 //			EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR,
 
-			EGL_NONE
+			EGL_NONE,EGL_NONE
 		};
 		eglsurf = qeglCreateWindowSurface(egldpy, cfg, windowid, info->srgb?wndattrib:NULL);
 	}
@@ -406,7 +420,30 @@ qboolean EGL_InitWindow (rendererstate_t *info, int eglplat, void *nwindow, EGLN
 		return false;
 	}
 
-	eglctx = qeglCreateContext(egldpy, cfg, EGL_NO_SURFACE, contextattr);
+	qeglGetConfigAttrib(egldpy, cfg, EGL_RENDERABLE_TYPE, &renderabletype);
+	if ((renderabletype & EGL_OPENGL_BIT) && qeglBindAPI)
+	{
+		EGLint contextattr[] =
+		{
+//			EGL_CONTEXT_OPENGL_DEBUG, 1,
+//			EGL_CONTEXT_OPENGL_PROFILE_MASK,  EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
+//			EGL_CONTEXT_CLIENT_VERSION, 2,	//requires EGL 1.3
+//			EGL_TEXTURE_TARGET,EGL_NO_TEXTURE,	//just a rendertarget, not a texture.
+			EGL_NONE,EGL_NONE
+		};
+		qeglBindAPI(EGL_OPENGL_API);
+
+		eglctx = qeglCreateContext(egldpy, cfg, EGL_NO_SURFACE, contextattr);
+	}
+	else
+	{
+		EGLint contextattr[] =
+		{
+			EGL_CONTEXT_CLIENT_VERSION, 2,	//requires EGL 1.3
+			EGL_NONE, EGL_NONE
+		};
+		eglctx = qeglCreateContext(egldpy, cfg, EGL_NO_SURFACE, contextattr);
+	}
 	if (eglctx == EGL_NO_CONTEXT)
 	{
 		Con_Printf(CON_ERROR "EGL: no context!\n");
@@ -449,5 +486,147 @@ qboolean EGL_InitWindow (rendererstate_t *info, int eglplat, void *nwindow, EGLN
 	return true;
 }
 
+
+
+//code for headless/pbuffer rendering.
+//in terms of energy efficiency its better to use the true headless renderer which stubs out ALL rendering.
+//however that's not very useful for screenshots or demo captures, so we offer this route too.
+#include "glquake.h"
+#include "gl_draw.h"
+#include "shader.h"
+#include "EGL/eglext.h"
+static void EGLHeadless_SwapBuffers(void)
+{
+	if (R2D_Flush)
+		R2D_Flush();
+
+	EGL_SwapBuffers();
+}
+static qboolean EGLHeadless_Init (rendererstate_t *info, unsigned char *palette)
+{
+	EGLConfig cfg;
+	void *dpy = NULL;
+
+	if (!EGL_LoadLibrary(info->subrenderer))
+	{
+		Con_Printf("couldn't load EGL library\n");
+		return false;
+	}
+
+#ifdef EGL_EXT_device_base
+	if (*info->devicename)
+	{
+		EGLDeviceEXT devs[64];
+		EGLint count;
+		EGLint idx = atoi(info->devicename);
+		EGLBoolean (EGLAPIENTRY *qeglQueryDevicesEXT) (EGLint max_devices, EGLDeviceEXT *devices, EGLint *num_devices) = EGL_Proc("eglQueryDevicesEXT");
+		if (qeglQueryDevicesEXT)
+		{
+			qeglQueryDevicesEXT(countof(devs), devs, &count);
+			if (idx >= 0 && idx < count)
+				dpy = devs[idx];
+		}
+	}
+#endif
+
+	vid.pixelwidth = (info->width>0)?info->width:640;
+	vid.pixelheight = (info->height>0)?info->height:480;
+	vid.activeapp = true;
+
+	if (!EGL_InitDisplay(info, EGL_PLATFORM_DEVICE_EXT, dpy, (EGLNativeDisplayType)EGL_NO_DISPLAY, &cfg))
+	{
+		Con_Printf("couldn't find suitable EGL config\n");
+		return false;
+	}
+
+	if (!EGL_InitWindow(info, EGL_PLATFORM_DEVICE_EXT, EGL_NO_SURFACE, (EGLNativeWindowType)EGL_NO_SURFACE, cfg))
+	{
+		Con_Printf("couldn't initialise EGL context\n");
+		return false;
+	}
+
+	if (GL_Init(info, &EGL_Proc))
+		return true;
+	Con_Printf(CON_ERROR "Unable to initialise opengl-on-wayland.\n");
+	return false;
+}
+static void EGLHeadless_DeInit(void)
+{
+	EGL_Shutdown();
+	EGL_UnloadLibrary();
+	GL_ForgetPointers();
+}
+static qboolean EGLHeadless_ApplyGammaRamps(unsigned int gammarampsize, unsigned short *ramps)
+{
+	//not supported
+	return false;
+}
+static void EGLHeadless_SetCaption(const char *text)
+{
+}
+
+static int EGLHeadless_GetPriority(void)
+{
+	return -1;	//lowest priority, so its never auto-used..
+}
+
+rendererinfo_t rendererinfo_headless_egl =
+{
+	"Headless OpenGL (EGL)",
+	{
+		"egl_headless",
+	},
+	QR_OPENGL,
+
+	GLDraw_Init,
+	GLDraw_DeInit,
+
+	GL_UpdateFiltering,
+	GL_LoadTextureMips,
+	GL_DestroyTexture,
+
+	GLR_Init,
+	GLR_DeInit,
+	GLR_RenderView,
+
+	EGLHeadless_Init,
+	EGLHeadless_DeInit,
+	EGLHeadless_SwapBuffers,
+	EGLHeadless_ApplyGammaRamps,
+	NULL,
+	NULL,
+	NULL,
+	EGLHeadless_SetCaption,       //setcaption
+	GLVID_GetRGBInfo,
+
+
+	GLSCR_UpdateScreen,
+
+	GLBE_SelectMode,
+	GLBE_DrawMesh_List,
+	GLBE_DrawMesh_Single,
+	GLBE_SubmitBatch,
+	GLBE_GetTempBatch,
+	GLBE_DrawWorld,
+	GLBE_Init,
+	GLBE_GenBrushModelVBO,
+	GLBE_ClearVBO,
+	GLBE_UpdateLightmaps,
+	GLBE_SelectEntity,
+	GLBE_SelectDLight,
+	GLBE_Scissor,
+	GLBE_LightCullModel,
+
+	GLBE_VBO_Begin,
+	GLBE_VBO_Data,
+	GLBE_VBO_Finish,
+	GLBE_VBO_Destroy,
+
+	GLBE_RenderToTextureUpdate2d,
+
+	"",
+	EGLHeadless_GetPriority
+};
+
 #endif
 
diff --git a/engine/gl/gl_videgl.h b/engine/gl/gl_videgl.h
index 5327c921d..12c05f5d9 100644
--- a/engine/gl/gl_videgl.h
+++ b/engine/gl/gl_videgl.h
@@ -21,6 +21,10 @@
 #define EGL_PLATFORM_WIN32						0		//no meaningful value.
 #endif
 
+#ifndef EGL_PLATFORM_DEVICE_EXT
+#define EGL_PLATFORM_DEVICE_EXT					0x313F	//we're using it for headless/pbuffer. oh well.
+#endif
+
 void *EGL_Proc(char *f);
 void EGL_UnloadLibrary(void);
 qboolean EGL_LoadLibrary(char *driver);