Skip to content

Instantly share code, notes, and snippets.

@ariankordi
Last active November 25, 2025 18:48
Show Gist options
  • Select an option

  • Save ariankordi/4959fb6a3cc9106ead308a86328cf6c3 to your computer and use it in GitHub Desktop.

Select an option

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…
// (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