'+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
This commit is contained in:
parent
98809d99ec
commit
6094fc6ab3
4 changed files with 201 additions and 12 deletions
|
@ -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)
|
||||
|
|
|
@ -243,7 +243,7 @@ static void Headless_BE_RenderToTextureUpdate2d (qboolean destchanged)
|
|||
|
||||
rendererinfo_t headlessrenderer =
|
||||
{
|
||||
"Headless",
|
||||
"Headless (Null)",
|
||||
{"headless"},
|
||||
QR_HEADLESS,
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue