A drop in opengl32.dll for Windows that renders everything on the CPU,
with fatmap underneath and GLSL JIT compiled to AVX2 / AVX-512.
Windows | x64 + x86 (32 bit) | GL 1.1 .. 3.3, core + compatibility | MIT licensed
Try it / What works / Performance / Building / fatmap
Doom 3 BFG Edition, unmodified, rendering through fatgl on the CPU.
No relinking, no driver, no GPU. Put fatgl's opengl32.dll next to an
application's .exe and Windows loads it instead of the system one: the
application renders through fatmap's multithreaded, SIMD software
pipeline. Fixed function GL 1.x, immediate mode and display lists, GLSL
1.10 to 3.30, FBOs, UBOs, VAOs, MSAA: it runs Doom 3 BFG Edition with
its shaders, shadow volumes, render to texture and Bink videos.
Handy for headless CI, virtual machines and remote desktops without a usable GL driver, old games on new machines, and as a readable reference implementation when a GPU driver disagrees with you.
- Build it (see Building), or take
opengl32.dllfrom a build. - Copy
opengl32.dllnext to the application's.exe(a 32 bit application needs the 32 bit DLL). - Run the application.
fatgl.logappears next to the.exe, andGL_RENDERERreads "fatgl on fatmap ...". Press F10 for the performance overlay.
fatgl exports the same 336 OpenGL 1.1 functions and WGL functions as the
system DLL (gdi32's ChoosePixelFormat / SetPixelFormat / SwapBuffers
route into it), and everything newer comes through wglGetProcAddress
(GL 1.2 .. 3.3 core plus the compatibility profile functions such as
glMultiTexCoord*, and the ARB / EXT / KHR aliases of promoted functions),
as GL loaders (GLEW, glad, GLFW) expect.
| WGL | pixel formats (RGBA8, depth 24, stencil 8, double / single buffered, 4x / 8x MSAA), contexts, WGL_ARB_create_context(_profile), WGL_ARB_pixel_format, WGL_EXT_swap_control, presentation with GDI |
| GL 1.x fixed function | immediate mode (every primitive, points and lines with glPointSize / glLineWidth), client vertex arrays, display lists, matrix stacks, lighting (8 lights, materials, color material), multitexture (GL_ARB_multitexture: two fixed function texture stages, e.g. texture x lightmap), fog (linear / exp / exp2), 2D textures (RGB(A), BGR(A), luminance, alpha; mipmaps by fatmap), S3TC (DXT1 / 3 / 5) and RGTC compressed textures, every pixel format / type of GL 3.3 (packed, half float, integer), pixel buffer objects, texenv, depth / alpha / stencil test, culling, scissor, polygon offset, glPolygonMode (fill / line / point), blending, glReadPixels, glCopyTex(Sub)Image*, glGetTexImage |
| GL 2.0 .. 3.3 | GLSL compiled at run time (glslang inside the DLL, GLSL to SPIR-V, run by fatmap's SPIR-V backend), programs, loose uniforms, uniform blocks (glBindBufferBase / glUniformBlockBinding), samplers on 16 texture units, every texture target but buffer / multisample (1D, 2D, 3D, cube maps, rectangle, 1D / 2D arrays; cube faces and layers as render targets), texture swizzles, buffer objects (map, copy), vertex array objects (any attribute type, normalized, integer, instance divisors), glDrawArrays / glDrawElements with instancing and base vertex, gl_VertexID / gl_InstanceID, glGetStringi, framebuffer objects (texture and renderbuffer attachments, depth only passes, glBlitFramebuffer), renderbuffers, depth textures, all blend factors and equations (glBlendFuncSeparate, glBlendEquationSeparate, glBlendColor), separate stencil state, points (gl_PointSize with GL_PROGRAM_POINT_SIZE, gl_PointCoord) and every line mode, primitive restart, sampler objects, occlusion / primitive / timer queries, sync objects, glClearBuffer*, indexed and 64 bit queries |
| legacy GLSL | GLSL 1.10 .. 1.30 and compatibility profiles: rewritten to 3.30 before glslang (attribute / varying, texture2D & co., gl_FragColor / gl_FragData, ftransform); the fixed function state as GLSL sees it (gl_Vertex, gl_Normal, gl_Color, gl_MultiTexCoord0, gl_ModelViewProjectionMatrix and friends, gl_NormalMatrix, gl_LightSource[], gl_FrontMaterial, gl_LightModel, gl_TexCoord[], ...), fed from client arrays, immediate mode or the current values |
| not yet | cube map arrays, mipmap levels given by the application (fatmap builds them from level 0), BPTC (BC7) textures, geometry shaders, transform feedback, more than one color output, multisample render targets (single sampled), logic ops, float / integer render targets (stored as 8 bit unorm), shadow samplers, fog coordinates, more than two fixed function texture stages, texture coordinate generation, two sided lighting, wide smooth lines |
Shaders: GLSL goes through glslang to SPIR-V, and fatmap compiles the SPIR-V to x86 machine code (a JIT for x86-64 and x86-32, AVX2 / AVX-512, 16 pixels or vertices per pass). Programs the JIT does not cover, and CPUs without AVX2, run on fatmap's SPIR-V interpreter. Both produce the same bits (fatmap 0.9.0 and later).
Tested with Doom 3 BFG Edition (32 bit, GLSL shaders throughout): menus, Bink videos, in game rendering, shadow volumes, render to texture.
Colors are kept the way GL keeps them: straight (not premultiplied) in
textures and framebuffers, rows bottom up, so render to texture, blending
and glReadPixels need no conversions.
Unimplemented entry points exist (so loaders and applications link) and
report GL_INVALID_OPERATION. fatgl writes fatgl.log next to the
executable: the DLL that was loaded (so you can tell it is fatgl), the
contexts the application created, every unimplemented call it made (once
each) and shader compile / link errors with their source. GL_RENDERER
reads "fatgl on fatmap ...".
Environment variables:
FATGL_LOG=0 / FATGL_LOG=<file> |
no fatgl.log / write it elsewhere |
FATGL_VERBOSE=1 |
also print unimplemented calls on stderr |
FATGL_THREADS=<n> |
render threads (default: every core) |
FATGL_MSAA=0 / 4 / 8 |
window anti-aliasing off / 4x / 8x, whatever the pixel format asks (start of F8) |
FATGL_SHADERS=jit / avx2 / interp |
shaders on the JIT, the JIT at AVX2, the interpreter (start of F7) |
FATGL_TEXTURES=fast |
trilinear filtering as bilinear from the nearest mip level (start of F6) |
FATGL_PRECISE_MATH=1 |
shader math within 1 ulp instead of GPU-like precision (slower) |
FATGL_DUMP_SPIRV=<dir> |
write every linked program's SPIR-V |
FATGL_LOAD_SPIRV=<dir> |
use such files instead (e.g. after spirv-opt) |
FM_JIT=0 |
fatmap: run shaders on the interpreter instead of the JIT |
Keys while a window of the application has the focus (swallowed): F10 the performance overlay, F8 cycles MSAA (the game's, off, 4x, 8x), F7 the shader execution, F6 the texture quality. The modes show for two seconds.
tools/bench: Doom 3 BFG style light interactions (1280 x 720, 4 additive
light passes, 5 trilinear textures each). The same GLSL and scene run on
Mesa llvmpipe through bench_interaction.py. Frame times on a Ryzen 9
9950X3D:
| 1 thread | 32 threads | |
|---|---|---|
| fatgl, x64 | 145 ms | 8.3 ms |
| fatgl, x86 (32 bit) | 197 ms | 11.9 ms |
| Mesa llvmpipe 25.0 (LLVM 19, Linux x64) | 133 ms | 11.0 ms |
The shaders run through fatmap's SPIR-V JIT (AVX-512 here). One thread is pinned to one CCD; 32 threads are unpinned. On pure math shaders (Seascape) llvmpipe is still about 2x ahead: see fatmap's README.
- OpenGL 2.1 / 3.3 (core and compatibility profiles): GLSL through glslang (vendored with a Meson build) to SPIR-V, run by fatmap's SPIR-V backend; buffer objects, VAOs, FBOs, UBOs, points / lines, legacy GLSL (done). Next: multiple render targets, shadow samplers, more render target formats.
- Not planned: OpenGL 4.x (tessellation and double precision shaders are outside fatmap's pipeline); 3.2's geometry shaders come later, if at all.
Meson + Ninja, Windows, MinGW-w64 or Visual Studio (64 bit or 32 bit):
meson setup build
ninja -C build
build\gears.exe # fatgl's opengl32.dll sits next to it
build\cube33.exe # OpenGL 3.3 core profile: VAO, VBO / EBO, GLSL 330, UBO, texture
build\fbo33.exe # render to texture: framebuffer object, renderbuffer, blit
build\blend.exe # every blend equation, constant color, straight alpha textures
build\prims.exe # points and lines: fixed function and GLSL point sprites
build\legacy.exe # GLSL 1.10 reading the fixed function state, next to the fixed pipeline32 bit (most games of the GL 1.x era are 32 bit processes, which cannot
load a 64 bit DLL): from a Visual Studio x86 prompt (vcvarsall.bat x86),
meson setup build-x86 -Dfatmap=source then ninja -C build-x86. Visual
Studio builds link the C runtime statically: the DLL needs only GDI32,
USER32 and KERNEL32. meson test runs tests/gl_test.c through the
built DLL (a hidden window).
fatmap comes from its release package (subprojects/fatmap.wrap, v0.9.1),
or with -Dfatmap=source from a fatmap checkout at
subprojects/fatmap-src (a junction / symlink) when developing both.
glslang (GLSL to SPIR-V) comes from
subprojects/glslang.wrap with fatgl's own Meson build of it. The examples
link against the system opengl32 import library on purpose: Windows
loads the DLL next to the executable, which is fatgl's.
tools/scene: a Doom 3 BFG style frame without the game (BFG's interaction
shaders, a depth prepass, per light stencil shadow volumes with color writes
masked and an alpha masked additive light pass, a destination alpha blend
light, a render to texture monitor, a screen copy heat haze).
python tools/scene/run.py [--build build-dev] renders it in every mode
(JIT, interpreter, AVX2, SSE2, one thread: the same bits; 4x / 8x MSAA and
fast textures: close to them), checks the image hashes in
tools/scene/golden.txt (--update after an intended change), prints the
frame times and writes the images to <build>/scene_out. meson test runs
it.
examples/*.exe --shot file.bmp renders 30 frames, saves the last one and
quits.
tools/gen_gl.py regenerates the export list, the wglGetProcAddress
table and the stubs after adding GL functions.
MIT. The Khronos headers in include/ are MIT licensed (Khronos Group).
