Last active
November 25, 2025 18:48
-
-
Save ariankordi/4959fb6a3cc9106ead308a86328cf6c3 to your computer and use it in GitHub Desktop.
raylib example for rendering without creating a window, still using the GPU on modern OpenGL. This uses OS-specific APIs (WGL on Windows, CGL on macOS, EGL on Linux/Android) to create the context. TBD: I'm planning on wrapping all of that into a reusable library to use outside of this example, but for the time being here's the standalone example…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // (soon to be) A header for initializing OpenGL headlessly without a window. | |
| // TODO: | |
| // - Find a way to report errors other than TraceLog from raylib, to remove dependency. | |
| // - Provide a "Finalize" function to clean up the context - requires state keeping | |
| // - Potentially delete and refactor "borrowed" code to be all original and public domain. | |
| // | |
| #include "raylib.h" // TraceLog, LOG_ERROR | |
| #include <stddef.h> // NULL | |
| // Snippets for initializing WGL, EGL, and CGL all derived from OsmAnd (GPLv3 license): | |
| // https://github.com/osmandapp/OsmAnd-core/blob/master/tools/src/EyePiece.cpp | |
| // Helpers are included for certain backends: | |
| // - CGL helpers from GLEW (BSD) | |
| // - WGL helpers from GLAD (public domain) and raylib (zlib license) | |
| // // ------------------------------------------- | |
| // // GLHeadless function declarations and defines | |
| // // ------------------------------------------- | |
| void* GLHeadless_GetProcAddress(const char* name); | |
| bool GLHeadless_Initialize(void); | |
| // Defines for which platform to implement. | |
| // Note: EGL is supported on all platforms; for use with ANGLE, etc. | |
| // By default, it is enabled for non-Windows/non-macOS (assuming Linux) | |
| // But, it can be defined explicitly and will override the others. Like so: | |
| //#define GLHeadless_IMPL_EGL 1 | |
| #ifdef WIN32 // Windows/WGL | |
| #define GLHeadless_IMPL_WGL 1 | |
| #else | |
| #define GLHeadless_IMPL_WGL 0 | |
| #endif // WIN32 | |
| #ifdef __APPLE__ // Apple/CGL | |
| #define GLHeadless_IMPL_CGL 1 | |
| #else | |
| #define GLHeadless_IMPL_CGL 0 | |
| #endif // __APPLE__ | |
| #ifndef GLHeadless_IMPL_EGL | |
| // By default, use EGL if not on Windows or macOS. | |
| #define GLHeadless_IMPL_EGL (!GLHeadless_IMPL_WGL && !GLHeadless_IMPL_CGL) | |
| #endif // GLHeadless_IMPL_EGL | |
| #if GLHeadless_IMPL_EGL | |
| //#pragma message("using GLHeadless EGL implementation") | |
| // // ------------------------------------------- | |
| // // GLHeadless implementation: EGL - X11/Wayland, Android, ANGLE | |
| // // ------------------------------------------- | |
| #include <GL/gl.h> | |
| #include <EGL/egl.h> | |
| #include <EGL/eglext.h> | |
| void* GLHeadless_GetProcAddress(const char* procname) | |
| { | |
| return (void*)eglGetProcAddress(procname); | |
| } | |
| bool GLHeadless_Initialize(void) | |
| { | |
| static const int width = 1, height = 1; | |
| static const int MAX_DEVICES = 10; | |
| EGLDeviceEXT eglDevs[MAX_DEVICES]; | |
| const PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = (PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT"); | |
| if (!eglQueryDevicesEXT) | |
| { | |
| TraceLog(LOG_ERROR, "Failed to find eglQueryDevicesEXT() routine."); | |
| return false; | |
| } | |
| EGLint numDevices; | |
| eglQueryDevicesEXT(MAX_DEVICES, eglDevs, &numDevices); | |
| if (numDevices > 0) | |
| TraceLog(LOG_INFO, "Detected %d GPUs.", numDevices); | |
| else | |
| { | |
| TraceLog(LOG_ERROR, "No suitable GPU found."); | |
| return false; | |
| } | |
| const PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); | |
| if (!eglGetPlatformDisplayEXT) | |
| { | |
| TraceLog(LOG_ERROR, "Failed to find eglGetPlatformDisplayEXT routine."); | |
| return false; | |
| } | |
| const EGLDisplay display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, eglDevs[0], 0); | |
| EGLint major, minor; | |
| eglInitialize(display, &major, &minor); | |
| const EGLint configAttribs[] = { | |
| EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, | |
| EGL_BLUE_SIZE, 8, | |
| EGL_GREEN_SIZE, 8, | |
| EGL_RED_SIZE, 8, | |
| EGL_DEPTH_SIZE, 24, | |
| EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, | |
| EGL_NONE | |
| }; | |
| EGLint numConfigs; | |
| EGLConfig eglCfg; | |
| eglChooseConfig(display, configAttribs, &eglCfg, 1, &numConfigs); | |
| const EGLint pbufferAttribs[] = { | |
| EGL_WIDTH, width, | |
| EGL_HEIGHT, height, | |
| EGL_NONE, // Terminator | |
| }; | |
| EGLSurface surface = eglCreatePbufferSurface(display, eglCfg, pbufferAttribs); | |
| eglBindAPI(EGL_OPENGL_API); | |
| EGLContext context = eglCreateContext(display, eglCfg, EGL_NO_CONTEXT, NULL); | |
| eglMakeCurrent(display, surface, surface, context); | |
| // Destroy: | |
| /* | |
| eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | |
| eglDestroyContext(display, context); | |
| eglDestroySurface(display, surface); | |
| eglTerminate(display); | |
| */ | |
| return true; | |
| } | |
| // GLHeadless_IMPL_EGL | |
| #elif GLHeadless_IMPL_WGL | |
| //#pragma message("using GLHeadless WGL implementation") | |
| // // ------------------------------------------- | |
| // // GLHeadless implementation: Windows (WGL) | |
| // // ------------------------------------------- | |
| // (from rcore_desktop_win32.c): | |
| // Move windows.h symbols to new names to avoid redefining the same names as raylib | |
| #define CloseWindow CloseWindowWin32 | |
| #define Rectangle RectangleWin32 | |
| #define ShowCursor ShowCursorWin32 | |
| #define DrawTextA DrawTextAWin32 | |
| #define DrawTextW DrawTextWin32 | |
| #define DrawTextExA DrawTextExAWin32 | |
| #define DrawTextExW DrawTextExWin32 | |
| #include <windows.h> | |
| #include <GL/gl.h> | |
| //#define WGL_WGLEXT_PROTOTYPES | |
| #include <GL/wglext.h> | |
| #include <string.h> // memset() the WNDCLASS | |
| #undef CloseWindow // raylib symbol collision | |
| #undef Rectangle // raylib symbol collision | |
| #undef ShowCursor // raylib symbol collision | |
| #undef LoadImage // raylib symbol collision | |
| #undef DrawText // raylib symbol collision | |
| #undef DrawTextA | |
| #undef DrawTextW | |
| #undef DrawTextEx // raylib symbol collision | |
| #undef DrawTextExA | |
| #undef DrawTextExW | |
| // Wrapper for wglGetProcAddress handling errors | |
| // and function pointers from opengl32.dll. | |
| // From rcore_desktop_win32.c (zlib license): | |
| // https://github.com/raysan5/raylib/blob/3d9129e3b47bdb83c47ec77ec01e769522623206/src/platforms/rcore_desktop_win32.c#L467-L488 | |
| void* GLHeadless_GetProcAddress(const char* procname) | |
| { | |
| void* proc = (void*)wglGetProcAddress(procname); | |
| static HMODULE glModule; // Global for opengl32.dll module. | |
| if ((proc == NULL) || | |
| // NOTE: Some GPU drivers could return following | |
| // invalid sentinel values instead of NULL. | |
| // (^ citation needed? ^) | |
| (proc == (void*)0x1) || | |
| (proc == (void*)0x2) || | |
| (proc == (void*)0x3) || | |
| (proc == (void*)-1)) | |
| { | |
| if (glModule == NULL) | |
| glModule = LoadLibraryA("opengl32.dll"); | |
| if (!glModule) | |
| return NULL; | |
| proc = (void*)GetProcAddress(glModule, procname); | |
| //if (proc == NULL) TRACELOG(LOG_ERROR, "GL: GetProcAddress() failed to get %s [%p], error=%u", procname, proc, GetLastError()); | |
| //else TRACELOG(LOG_INFO, "GL: Found entry point for %s [%p]", procname, proc); | |
| } | |
| return proc; | |
| } | |
| // Derived from glad_wgl_has_extension (public domain): | |
| // https://github.com/Dav1dde/glad/blob/27bed1181560211b55e39a9b132fef8c5846aae5/glad/generator/c/templates/wgl.c#L15-L46 | |
| WINAPI PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB; | |
| WINAPI PFNWGLGETEXTENSIONSSTRINGEXTPROC wglGetExtensionsStringEXT; | |
| static bool hasWglExt(const char* ext, const HDC hdc) { | |
| const char* terminator; | |
| const char* loc; | |
| static const char* extensions; // Global for WGL extensions string. | |
| if (extensions == NULL) | |
| { | |
| if (wglGetExtensionsStringEXT == NULL && wglGetExtensionsStringARB == NULL) | |
| return false; | |
| if (wglGetExtensionsStringARB == NULL || hdc == INVALID_HANDLE_VALUE) | |
| extensions = wglGetExtensionsStringEXT(); | |
| else | |
| extensions = wglGetExtensionsStringARB(hdc); | |
| } | |
| if (extensions == NULL || ext == NULL) | |
| return false; | |
| while (true) | |
| { | |
| loc = strstr(extensions, ext); | |
| if (loc == NULL) | |
| break; | |
| terminator = loc + strlen(ext); | |
| if ((loc == extensions || *(loc - 1) == ' ') && | |
| (*terminator == ' ' || *terminator == '\0')) | |
| { | |
| return true; | |
| } | |
| extensions = terminator; | |
| } | |
| return false; | |
| } | |
| WINAPI PFNWGLCREATEPBUFFERARBPROC wglCreatePbufferARB; | |
| WINAPI PFNWGLDESTROYPBUFFERARBPROC wglDestroyPbufferARB; | |
| WINAPI PFNWGLGETPBUFFERDCARBPROC wglGetPbufferDCARB; | |
| WINAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; | |
| WINAPI PFNWGLRELEASEPBUFFERDCARBPROC wglReleasePbufferDCARB; | |
| WINAPI PFNWGLCREATEPBUFFEREXTPROC wglCreatePbufferEXT; | |
| WINAPI PFNWGLDESTROYPBUFFEREXTPROC wglDestroyPbufferEXT; | |
| WINAPI PFNWGLGETPBUFFERDCEXTPROC wglGetPbufferDCEXT; | |
| WINAPI PFNWGLRELEASEPBUFFERDCEXTPROC wglReleasePbufferDCEXT; | |
| bool GLHeadless_Initialize(void) | |
| { | |
| static const int width = 1, height = 1; | |
| // On windows, to create a windowless OpenGL context, a window is needed. Nonsense, totally. | |
| const HMODULE hInstance = GetModuleHandle(NULL); | |
| WNDCLASS wndClass; | |
| memset(&wndClass, 0, sizeof(WNDCLASS)); | |
| wndClass.style = CS_OWNDC; | |
| wndClass.lpfnWndProc = DefWindowProc; | |
| wndClass.hInstance = hInstance; | |
| /*wndClass.lpszClassName = "headless wgl window you are not supposed to see this and if you do then please report to the application author"; | |
| if (!RegisterClass(&wndClass)) | |
| { | |
| TraceLog(LOG_ERROR, "Failed to register WNDCLASS for temporary window"); | |
| return false; | |
| }*/ | |
| wndClass.lpszClassName = "STATIC"; | |
| // Adjust window size | |
| RECT windowRect = { 0, 1, 0, 1 }; | |
| const DWORD windowStyle = WS_OVERLAPPEDWINDOW; | |
| const DWORD windowExtendedStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; | |
| if (!AdjustWindowRectEx(&windowRect, windowStyle, FALSE, windowExtendedStyle)) | |
| { | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "Failed to adjust temporary window size"); | |
| return false; | |
| } | |
| // Create temporary window | |
| const HWND hTempWindow = CreateWindowEx( | |
| windowExtendedStyle, | |
| wndClass.lpszClassName, | |
| wndClass.lpszClassName, | |
| // WS_EX_NOACTIVATE prevents activation when the window is shown. | |
| // WS_EX_TOOLWINDOW keeps it out of alt-tab. | |
| windowStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW, | |
| 0, 0, | |
| windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, | |
| NULL, | |
| NULL, | |
| hInstance, | |
| NULL); | |
| if (!hTempWindow) | |
| { | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "Failed to create temporary window"); | |
| return false; | |
| } | |
| // HDC for temporary window. | |
| const HDC hdc = GetDC(hTempWindow); | |
| if (!hdc) | |
| { | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "Failed to get temporary window DC"); | |
| return false; | |
| } | |
| // Get pixel format | |
| const PIXELFORMATDESCRIPTOR temporaryWindowPixelFormatDescriptior = | |
| { | |
| sizeof(PIXELFORMATDESCRIPTOR), | |
| 1, | |
| PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL, | |
| PFD_TYPE_RGBA, | |
| 32, | |
| 0, 0, 0, 0, 0, 0, | |
| 0, | |
| 0, | |
| 0, | |
| 0, 0, 0, 0, | |
| 16, | |
| 0, | |
| 0, | |
| PFD_MAIN_PLANE, | |
| 0, | |
| 0, 0, 0 | |
| }; | |
| const int tempWindowPixelFormat = ChoosePixelFormat(hdc, &temporaryWindowPixelFormatDescriptior); | |
| if (!tempWindowPixelFormat || !SetPixelFormat(hdc, tempWindowPixelFormat, &temporaryWindowPixelFormatDescriptior)) | |
| { | |
| ReleaseDC(hTempWindow, hdc); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "Failed to select proper pixel format for temporary window"); | |
| return false; | |
| } | |
| // Create temporary OpenGL context | |
| const HGLRC hTempGLRC = wglCreateContext(hdc); | |
| if (!hTempGLRC) | |
| { | |
| //glVerifyResult(output); | |
| ReleaseDC(hTempWindow, hdc); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "Failed to create temporary OpenGL context"); | |
| return false; | |
| } | |
| if (!wglMakeCurrent(hdc, hTempGLRC)) | |
| { | |
| //glVerifyResult(output); | |
| wglDeleteContext(hTempGLRC); | |
| ReleaseDC(hTempWindow, hdc); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "Failed to activate temporary OpenGL context"); | |
| return false; | |
| } | |
| // Load WGL extension functions | |
| wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC) | |
| wglGetProcAddress("wglGetExtensionsStringARB"); | |
| wglGetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC) | |
| wglGetProcAddress("wglGetExtensionsStringEXT"); | |
| wglCreatePbufferARB = (PFNWGLCREATEPBUFFERARBPROC) | |
| wglGetProcAddress("wglCreatePbufferARB"); | |
| wglDestroyPbufferARB = (PFNWGLDESTROYPBUFFERARBPROC) | |
| wglGetProcAddress("wglDestroyPbufferARB"); | |
| wglGetPbufferDCARB = (PFNWGLGETPBUFFERDCARBPROC) | |
| wglGetProcAddress("wglGetPbufferDCARB"); | |
| wglReleasePbufferDCARB = (PFNWGLRELEASEPBUFFERDCARBPROC) | |
| wglGetProcAddress("wglReleasePbufferDCARB"); | |
| wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) | |
| wglGetProcAddress("wglCreateContextAttribsARB"); | |
| wglCreatePbufferEXT = (PFNWGLCREATEPBUFFEREXTPROC) | |
| wglGetProcAddress("wglCreatePbufferEXT"); | |
| wglDestroyPbufferEXT = (PFNWGLDESTROYPBUFFEREXTPROC) | |
| wglGetProcAddress("wglDestroyPbufferEXT"); | |
| wglGetPbufferDCEXT = (PFNWGLGETPBUFFERDCEXTPROC) | |
| wglGetProcAddress("wglGetPbufferDCEXT"); | |
| wglReleasePbufferDCEXT = (PFNWGLRELEASEPBUFFERDCEXTPROC) | |
| wglGetProcAddress("wglReleasePbufferDCEXT"); | |
| // Create context. pbuffer will be created afterwards. | |
| //if (glewInit() != GLEW_NO_ERROR) | |
| /* | |
| if (!gladLoadGL((GLADloadproc)wglGetProcAddress)) | |
| { | |
| wglMakeCurrent(NULL, NULL); | |
| wglDeleteContext(hTempGLRC); | |
| ReleaseDC(hTempWindow, hTempWindowDC); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "Failed to initialize GLAD"); | |
| return false; | |
| } | |
| // Silence OpenGL errors here, it's inside GLEW, so it's not ours | |
| (void)glGetError(); | |
| */ | |
| // To find out what current system supports, use wglGetExtensionsString* | |
| /* | |
| char* wglExtensionsString = NULL; | |
| if (wglGetExtensionsStringEXT != NULL) | |
| { | |
| wglExtensionsString = wglGetExtensionsStringEXT(); | |
| //if (!glVerifyResult(output)) | |
| // return false; | |
| } | |
| else if (wglGetExtensionsStringARB != NULL) | |
| { | |
| wglExtensionsString = wglGetExtensionsStringARB(hTempWindowDC); | |
| //if (!glVerifyResult(output)) | |
| // return false; | |
| } | |
| else | |
| { | |
| wglMakeCurrent(NULL, NULL); | |
| wglDeleteContext(hTempGLRC); | |
| ReleaseDC(hTempWindow, hTempWindowDC); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "WGL_EXT_extensions_string or WGL_ARB_extensions_string has to be supported"); | |
| return false; | |
| } | |
| const char* wglExtensions = QString::fromLatin1(wglExtensionsString).split(QRegExp("\\s+"), QString::SkipEmptyParts); | |
| if (configuration.verbose) | |
| output << xT("WGL extensions: ") << QStringToStlString(wglExtensions.join(' ')) << std::endl; | |
| */ | |
| // Create pbuffer | |
| int pbufferContextAttribs[] = { 0 }; | |
| void* pBufferHandle = NULL; | |
| if (hasWglExt("WGL_ARB_pbuffer", hdc) && | |
| wglCreatePbufferARB != NULL) | |
| { | |
| const HPBUFFERARB hPBufferARB = wglCreatePbufferARB( | |
| hdc, | |
| tempWindowPixelFormat, | |
| width, height, | |
| pbufferContextAttribs); | |
| pBufferHandle = hPBufferARB; | |
| } | |
| else if (hasWglExt("WGL_EXT_pbuffer", hdc) && | |
| wglCreatePbufferEXT != NULL) | |
| { | |
| const HPBUFFEREXT hPBufferEXT = wglCreatePbufferEXT( | |
| hdc, | |
| tempWindowPixelFormat, | |
| width, height, | |
| pbufferContextAttribs); | |
| pBufferHandle = hPBufferEXT; | |
| } | |
| else | |
| { | |
| wglMakeCurrent(NULL, NULL); | |
| wglDeleteContext(hTempGLRC); | |
| ReleaseDC(hTempWindow, hdc); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "WGL_ARB_pbuffer or WGL_EXT_pbuffer has to be supported (wglCreatePbuffer*)"); | |
| return false; | |
| } | |
| if (!pBufferHandle) | |
| { | |
| wglMakeCurrent(NULL, NULL); | |
| wglDeleteContext(hTempGLRC); | |
| ReleaseDC(hTempWindow, hdc); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "Failed to create pbuffer"); | |
| return false; | |
| } | |
| // Create OpenGL context | |
| HDC hPBufferDC = NULL; | |
| if (hasWglExt("WGL_ARB_pbuffer", hdc) && | |
| wglGetPbufferDCARB != NULL && | |
| wglReleasePbufferDCARB != NULL) | |
| { | |
| hPBufferDC = wglGetPbufferDCARB((HPBUFFERARB)pBufferHandle); | |
| } | |
| else if (hasWglExt("WGL_EXT_pbuffer", hdc) && | |
| wglGetPbufferDCEXT != NULL && | |
| wglReleasePbufferDCEXT != NULL) | |
| { | |
| hPBufferDC = wglGetPbufferDCEXT((HPBUFFEREXT)pBufferHandle); | |
| } | |
| else | |
| { | |
| wglMakeCurrent(NULL, NULL); | |
| wglDeleteContext(hTempGLRC); | |
| ReleaseDC(hTempWindow, hdc); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "WGL_ARB_pbuffer or WGL_EXT_pbuffer has to be supported (wglGetPbufferDC*)"); | |
| return false; | |
| } | |
| if (!hPBufferDC) | |
| { | |
| if (hasWglExt("WGL_ARB_pbuffer", hdc) && | |
| wglDestroyPbufferARB != NULL) | |
| { | |
| wglDestroyPbufferARB((HPBUFFERARB)pBufferHandle); | |
| } | |
| else if (hasWglExt("WGL_EXT_pbuffer", hdc) && | |
| wglDestroyPbufferEXT != NULL) | |
| { | |
| wglDestroyPbufferEXT((HPBUFFEREXT)pBufferHandle); | |
| } | |
| wglMakeCurrent(NULL, NULL); | |
| wglDeleteContext(hTempGLRC); | |
| ReleaseDC(hTempWindow, hdc); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "Failed to get pbuffer DC"); | |
| return false; | |
| } | |
| // Create windowless OpenGL context | |
| if (!hasWglExt("WGL_ARB_create_context", hdc) || | |
| !hasWglExt("WGL_ARB_create_context_profile", hdc) || | |
| wglCreateContextAttribsARB == NULL) | |
| { | |
| if (hasWglExt("WGL_ARB_pbuffer", hdc) && | |
| wglDestroyPbufferARB != NULL) | |
| { | |
| wglReleasePbufferDCARB((HPBUFFERARB)pBufferHandle, hPBufferDC); | |
| wglDestroyPbufferARB((HPBUFFERARB)pBufferHandle); | |
| } | |
| else if (hasWglExt("WGL_EXT_pbuffer", hdc) && | |
| wglDestroyPbufferEXT != NULL) | |
| { | |
| wglReleasePbufferDCEXT((HPBUFFEREXT)pBufferHandle, hPBufferDC); | |
| wglDestroyPbufferEXT((HPBUFFEREXT)pBufferHandle); | |
| } | |
| wglMakeCurrent(NULL, NULL); | |
| wglDeleteContext(hTempGLRC); | |
| ReleaseDC(hTempWindow, hdc); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "WGL_ARB_create_context, WGL_ARB_create_context_profile and WGL_ARB_make_current_read are required to perform windowless rendering"); | |
| return false; | |
| } | |
| const HGLRC hPBufferContext = wglCreateContextAttribsARB(hPBufferDC, NULL, pbufferContextAttribs); | |
| if (!hPBufferContext) | |
| { | |
| //glVerifyResult(output); | |
| if (hasWglExt("WGL_ARB_pbuffer", hdc) && | |
| wglDestroyPbufferARB != NULL) | |
| { | |
| wglReleasePbufferDCARB((HPBUFFERARB)pBufferHandle, hPBufferDC); | |
| wglDestroyPbufferARB((HPBUFFERARB)pBufferHandle); | |
| } | |
| else if (hasWglExt("WGL_EXT_pbuffer", hdc) && | |
| wglDestroyPbufferEXT != NULL) | |
| { | |
| wglReleasePbufferDCEXT((HPBUFFEREXT)pBufferHandle, hPBufferDC); | |
| wglDestroyPbufferEXT((HPBUFFEREXT)pBufferHandle); | |
| } | |
| wglMakeCurrent(NULL, NULL); | |
| wglDeleteContext(hTempGLRC); | |
| ReleaseDC(hTempWindow, hdc); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| TraceLog(LOG_ERROR, "Failed to create windowless OpenGL context"); | |
| return false; | |
| } | |
| // After windowless OpenGL context was create, window can be killed | |
| wglMakeCurrent(NULL, NULL); | |
| wglDeleteContext(hTempGLRC); | |
| ReleaseDC(hTempWindow, hdc); | |
| DestroyWindow(hTempWindow); | |
| //UnregisterClass(wndClass.lpszClassName, hInstance); | |
| if (!wglMakeCurrent(hPBufferDC, hPBufferContext)) | |
| { | |
| //glVerifyResult(output); | |
| wglDeleteContext(hPBufferContext); | |
| if (hasWglExt("WGL_ARB_pbuffer", hdc) && | |
| wglDestroyPbufferARB != NULL) | |
| { | |
| wglReleasePbufferDCARB((HPBUFFERARB)pBufferHandle, hPBufferDC); | |
| wglDestroyPbufferARB((HPBUFFERARB)pBufferHandle); | |
| } | |
| else if (hasWglExt("WGL_EXT_pbuffer", hdc) && | |
| wglDestroyPbufferEXT != NULL) | |
| { | |
| wglReleasePbufferDCEXT((HPBUFFEREXT)pBufferHandle, hPBufferDC); | |
| wglDestroyPbufferEXT((HPBUFFEREXT)pBufferHandle); | |
| } | |
| TraceLog(LOG_ERROR, "Failed to activate windowless/framebufferless OpenGL context"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // GLHeadless_IMPL_WGL | |
| #elif GLHeadless_IMPL_CGL | |
| //#pragma message("using GLHeadless CGL implementation") | |
| // // ------------------------------------------- | |
| // // GLHeadless implementation: macOS (CGL) | |
| // // ------------------------------------------- | |
| // Silence Apple's "deprecated" warnings. OpenGL is still very relevant. | |
| #define GL_SILENCE_DEPRECATION | |
| // OpenGL headers in macOS. | |
| #include <OpenGL/gl3.h> | |
| #include <OpenGL/gl3ext.h> | |
| #include <OpenGL/OpenGL.h> | |
| // Dynamic library | |
| #include <dlfcn.h> | |
| // Derived from NSGLGetProcAddress in GLEW (modified BSD license): | |
| // https://github.com/nigels-com/glew/blob/ba71151c6af7435127c54b890808fec2a4aad41f/auto/src/glew_head.c#L85-L101 | |
| void* GLHeadless_GetProcAddress(const char* name) | |
| { | |
| static void* image = NULL; // Global for dlopen image. | |
| void* addr; | |
| if (image == NULL) | |
| image = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_LAZY); | |
| if (!image) | |
| return NULL; | |
| addr = dlsym(image, name); | |
| return addr; | |
| } | |
| bool GLHeadless_Initialize(void) | |
| { | |
| // Modes other than core profile were removed from OsmAnd snippet. | |
| static const int width = 1, height = 1; | |
| // First select pixel format using attributes | |
| CGLPixelFormatObj pixelFormat; | |
| GLint matchingPixelFormats; | |
| const CGLPixelFormatAttribute pixelFormatAttrins[] = { | |
| kCGLPFAAccelerated, | |
| kCGLPFAColorSize, (CGLPixelFormatAttribute)24, | |
| kCGLPFAAlphaSize, (CGLPixelFormatAttribute)8, | |
| kCGLPFADepthSize, (CGLPixelFormatAttribute)24, | |
| kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)kCGLOGLPVersion_GL3_Core, | |
| (CGLPixelFormatAttribute)0 | |
| }; | |
| CGLError error = CGLChoosePixelFormat(pixelFormatAttrins, &pixelFormat, &matchingPixelFormats); | |
| if (error != kCGLNoError) | |
| { | |
| TraceLog(LOG_ERROR, "Failed to find proper pixel format: %s", CGLErrorString(error)); | |
| return false; | |
| } | |
| // Create context | |
| CGLContextObj windowlessContext; | |
| error = CGLCreateContext(pixelFormat, NULL, &windowlessContext); | |
| if (error != kCGLNoError) | |
| { | |
| CGLDestroyPixelFormat(pixelFormat); | |
| TraceLog(LOG_ERROR, "Failed to create windowless context: %s", CGLErrorString(error)); | |
| return false; | |
| } | |
| CGLDestroyPixelFormat(pixelFormat); | |
| // Activate context | |
| error = CGLSetCurrentContext(windowlessContext); | |
| if (error != kCGLNoError) | |
| { | |
| CGLDestroyContext(windowlessContext); | |
| TraceLog(LOG_ERROR, "Failed to activate windowless context: %s", CGLErrorString(error)); | |
| return false; | |
| } | |
| GLuint framebuffer; | |
| GLuint framebufferColorTexture; | |
| GLuint framebufferDepthRenderbuffer; | |
| GLenum glError; | |
| // Create color texture | |
| glGenTextures(1, &framebufferColorTexture); | |
| glBindTexture(GL_TEXTURE_2D, framebufferColorTexture); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); | |
| if ((glError = glGetError())) | |
| { | |
| glDeleteTextures(1, &framebufferColorTexture); | |
| CGLSetCurrentContext(NULL); | |
| CGLDestroyContext(windowlessContext); | |
| TraceLog(LOG_ERROR, "Failed to create color texture for framebuffer: %d", glError); | |
| return false; | |
| } | |
| // Create depth renderbuffer | |
| glGenRenderbuffers(1, &framebufferDepthRenderbuffer); | |
| glBindRenderbuffer(GL_RENDERBUFFER, framebufferDepthRenderbuffer); | |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height); | |
| if ((glError = glGetError())) | |
| { | |
| glDeleteTextures(1, &framebufferColorTexture); | |
| glDeleteRenderbuffers(1, &framebufferDepthRenderbuffer); | |
| CGLSetCurrentContext(NULL); | |
| CGLDestroyContext(windowlessContext); | |
| TraceLog(LOG_ERROR, "Failed to create color texture for framebuffer: %d", glError); | |
| return false; | |
| } | |
| // Create framebuffer | |
| glGenFramebuffers(1, &framebuffer); | |
| glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); | |
| glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, framebufferColorTexture, 0); | |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, framebufferDepthRenderbuffer); | |
| if ((glError = glGetError())) | |
| { | |
| glDeleteTextures(1, &framebufferColorTexture); | |
| glDeleteRenderbuffers(1, &framebufferDepthRenderbuffer); | |
| glDeleteFramebuffers(1, &framebuffer); | |
| CGLSetCurrentContext(NULL); | |
| CGLDestroyContext(windowlessContext); | |
| TraceLog(LOG_ERROR, "Failed to create framebuffer: %d", glError); | |
| return false; | |
| } | |
| // Set the list of draw buffers. | |
| GLenum drawBuffers[] = { GL_COLOR_ATTACHMENT0 }; | |
| glDrawBuffers(1, drawBuffers); | |
| if ((glError = glGetError())) | |
| { | |
| glDeleteTextures(1, &framebufferColorTexture); | |
| glDeleteRenderbuffers(1, &framebufferDepthRenderbuffer); | |
| glDeleteFramebuffers(1, &framebuffer); | |
| CGLSetCurrentContext(NULL); | |
| CGLDestroyContext(windowlessContext); | |
| TraceLog(LOG_ERROR, "Failed to set draw buffer: %d", glError); | |
| return false; | |
| } | |
| // Always check that our framebuffer is ok | |
| if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) | |
| { | |
| glDeleteTextures(1, &framebufferColorTexture); | |
| glDeleteRenderbuffers(1, &framebufferDepthRenderbuffer); | |
| glDeleteFramebuffers(1, &framebuffer); | |
| CGLSetCurrentContext(NULL); | |
| CGLDestroyContext(windowlessContext); | |
| TraceLog(LOG_ERROR, "Framebuffer incomplete: %d", glError); | |
| return false; | |
| } | |
| // Destroy: | |
| /* | |
| CGLSetCurrentContext(NULL); | |
| /CGLDestroyContext(windowlessContext); | |
| */ | |
| return true; | |
| } | |
| //GLHeadless_IMPL_CGL | |
| #endif // GLHeadless_IMPL_* | |
| // // ------------------------------------------- | |
| // // End GLHeadless implementations | |
| // // Begin raylib usage example: | |
| // // ------------------------------------------- | |
| #include "rlgl.h" | |
| #include <time.h> // For getting a timestamp on the cube. | |
| #include <stdlib.h> // NULL, resolving RL_ALLOC/FREE() | |
| // Uses RenderDoc to capture the frame. | |
| // This only works on Linux, and only worked if I used | |
| // LD_PRELOAD to librenderdoc.so, for whatever reason. | |
| // Then, the rdc appears in /tmp/RenderDoc. | |
| //#define DEBUG_RENDERDOC | |
| // Draws within a window for 5 secs. instead of headlessly. | |
| //#define INIT_WINDOW | |
| // RenderDoc imports and globals. | |
| #ifdef DEBUG_RENDERDOC | |
| #include <renderdoc_app.h> | |
| #include <dlfcn.h> | |
| RENDERDOC_API_1_1_2* gRdocApi = NULL; | |
| pRENDERDOC_GetAPI RENDERDOC_GetAPI; | |
| #endif // DEBUG_RENDERDOC | |
| // Copy-pasted functions from rcore.c: | |
| // Takes a screenshot of the specified texture - modified from TakeScreenshot in rcore.c | |
| void TakeScreenshotTexture(const char* path, const Texture* tex) | |
| { | |
| unsigned char* imgData = rlReadTexturePixels(tex->id, tex->width, tex->height, tex->format); | |
| Image image = { imgData, tex->width, tex->height, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; | |
| ExportImage(image, path); // WARNING: Module required: rtextures | |
| RL_FREE(imgData); | |
| if (FileExists(path)) TraceLog(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully", path); | |
| else TraceLog(LOG_WARNING, "SYSTEM: [%s] Screenshot could not be saved", path); | |
| } | |
| #ifndef INIT_WINDOW | |
| #define EndTextureMode EndTextureModeNoViewport | |
| // Modified from EndTextureMode in rcore.c | |
| void EndTextureModeNoViewport(void) | |
| { | |
| rlDrawRenderBatchActive(); // Update and draw internal render batch | |
| rlDisableFramebuffer(); // Disable render target (fbo) | |
| // Go back to the modelview state from BeginDrawing since we are back to the default FBO | |
| rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix | |
| rlLoadIdentity(); // Reset current matrix (modelview) | |
| } | |
| #endif // INIT_WINDOW | |
| // Sets the camera just like BeginMode3D, but also flips | |
| // the projection upside down for reading the pixels out. | |
| // Modified from BeginMode3D in rcore.c | |
| void BeginMode3DUpsideDown(Camera camera) | |
| { | |
| BeginMode3D(camera); | |
| // Apply to the projection matrix. | |
| rlMatrixMode(RL_PROJECTION); | |
| // Define an identity matrix with Y axis flipped. | |
| static const Matrix Y_FLIP = { | |
| 1, 0, 0, 0, | |
| 0, -1, 0, 0, | |
| 0, 0, 1, 0, | |
| 0, 0, 0, 1 | |
| }; | |
| // Multiply the projection matrix (use as float16 as-is) | |
| rlMultMatrixf(&Y_FLIP.m0); | |
| // Reverse the culling from back to front. | |
| // NOTE: This makes the following assumptions: | |
| // - The shapes are always using backface culling. | |
| // - This needs to be set back when drawing normally. | |
| // (We cannot get what the cull mode is.) | |
| rlSetCullFace(RL_CULL_FACE_FRONT); | |
| // This should also work: | |
| // glFrontFace(GL_CCW); | |
| // However, the function needs to be defined | |
| // and then tested with GL 1.1 and GL 3.3 both | |
| } | |
| // Definitions needed to get fonts working: | |
| extern bool isGpuReady; | |
| extern void LoadFontDefault(void); | |
| // // ------------------------------------------- | |
| // // Example that draws a cube with text drawn on it, | |
| // // exported to a screenshot headlessly. | |
| // // ------------------------------------------- | |
| int main(void) | |
| { | |
| #ifdef DEBUG_RENDERDOC | |
| #ifdef _WIN32 | |
| #pragma warning("The function for initializing RenderDoc needs to be adapted to work on Windows.") | |
| #endif | |
| void* pModule; | |
| if ((pModule = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD))) | |
| { | |
| pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(pModule, "RENDERDOC_GetAPI"); | |
| int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_4_1, (void**)&gRdocApi); | |
| TraceLog(LOG_INFO, "RenderDoc API loaded"); | |
| } | |
| else | |
| TraceLog(LOG_ERROR, "dlopen(librenderdoc.so) failed. (pModule = %p)", pModule); | |
| #endif // DEBUG_RENDERDOC | |
| #ifdef INIT_WINDOW | |
| // Initialize window. | |
| InitWindow(800, 600, "Spinning cube with render texture"); | |
| SetTargetFPS(60); | |
| #else | |
| // Manual initialization headlessly. | |
| bool result = GLHeadless_Initialize(); | |
| if (!result) | |
| { | |
| TraceLog(LOG_ERROR, "GLHeadless_Initialize() failed."); | |
| return 1; | |
| } | |
| rlLoadExtensions((void*)GLHeadless_GetProcAddress); // BEFORE rlglInit | |
| rlglInit(1, 1); // Init with tiny screen buffer. | |
| // Load default font: | |
| isGpuReady = true; | |
| LoadFontDefault(); | |
| #endif // INIT_WINDOW | |
| #ifdef DEBUG_RENDERDOC | |
| if (gRdocApi != NULL) | |
| gRdocApi->StartFrameCapture(NULL, NULL); | |
| #endif | |
| // Create render texture for the cube to draw text on. | |
| RenderTexture2D rtCube = LoadRenderTexture(128, 128); | |
| // Build a cube model and apply our render texture to its material. | |
| Model cube = LoadModelFromMesh(GenMeshCube(2.0f, 2.0f, 2.0f)); | |
| // Override the default white texture: | |
| cube.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = rtCube.texture; | |
| // Setup camera. | |
| Camera3D camera = { 0 }; | |
| camera.position = (Vector3) { 4.0f, 3.0f, 4.0f }; | |
| camera.target = (Vector3) { 0.0f, 0.0f, 0.0f }; | |
| camera.up = (Vector3) { 0.0f, 1.0f, 0.0f }; | |
| camera.fovy = 45.0f; | |
| camera.projection = CAMERA_PERSPECTIVE; | |
| // Setup rotation and time parameters. | |
| float rotationAngle = 0.0f; | |
| #ifndef INIT_WINDOW | |
| // Create render texture to draw the scene to. | |
| RenderTexture2D renderTarget = LoadRenderTexture(512, 512); | |
| #else | |
| double startTime = GetTime(); | |
| // Loop for five seconds. | |
| while (!WindowShouldClose() && (GetTime() - startTime < 5.0)) | |
| { | |
| #endif | |
| // Update date/time string. | |
| time_t now = time(NULL); | |
| struct tm* t = localtime(&now); | |
| char dateTime[64]; | |
| // Put a newline between the date and time. | |
| strftime(dateTime, sizeof(dateTime), "%Y-%m-%d\n %H:%M:%S", t); | |
| // Draw that text into the render texture. | |
| BeginTextureMode(rtCube); | |
| // Set the texture's color. Since the texture is | |
| // fully wrapped around the cube with no blending... | |
| ClearBackground(RED); // This is effectively the color of the cube. | |
| DrawText(dateTime, 10, 10, 24, BLACK); | |
| EndTextureMode(); | |
| // Draw into the screen framebuffer. | |
| #ifdef INIT_WINDOW | |
| BeginDrawing(); | |
| #else | |
| BeginTextureMode(renderTarget); | |
| #endif | |
| ClearBackground(BLUE); | |
| #ifdef INIT_WINDOW | |
| BeginMode3D(camera); | |
| #else | |
| BeginMode3DUpsideDown(camera); | |
| #endif | |
| rlRotatef(rotationAngle, 0.0f, 1.0f, 0.0f); // Rotate around Y axis | |
| DrawModel(cube, | |
| (Vector3) { 0, 0, 0 }, 1, // Position, Scale | |
| WHITE // Fully apply the texture's color. | |
| ); | |
| DrawCubeWiresV((Vector3) { 0, 0, 0 }, (Vector3) { 2, 2, 2 }, MAROON); | |
| EndMode3D(); | |
| #ifdef INIT_WINDOW | |
| EndDrawing(); | |
| #else | |
| EndTextureMode(); | |
| #endif | |
| rotationAngle += 30.0f * GetFrameTime(); // Rotate 30 degrees/s. | |
| #ifdef INIT_WINDOW | |
| } | |
| #endif | |
| // Take a screenshot of the final frame. | |
| #ifdef INIT_WINDOW | |
| TakeScreenshot("screenshots/cube.png"); | |
| #else | |
| //TakeScreenshotTexture("screenshots/cube-texture.png", &rtCube.texture); | |
| TakeScreenshotTexture("screenshots/cube.png", &renderTarget.texture); | |
| UnloadRenderTexture(renderTarget); | |
| #endif | |
| #ifdef DEBUG_RENDERDOC | |
| if (gRdocApi != NULL) | |
| gRdocApi->EndFrameCapture(NULL, NULL); | |
| #endif | |
| // Cleanup. Unload the render texture, model, and window. | |
| UnloadRenderTexture(rtCube); | |
| UnloadModel(cube); | |
| CloseWindow(); | |
| // TODO: HeadlessGL needs a function to deinitialize. | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment