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_ANISO=<n> |
anisotropic filtering with at most n samples (default 16: what the application asks for, BFG 8x; 0: off, faster on surfaces seen at an angle; F6 turns it off too) |
FATGL_PIPELINE=0 |
no frame pipelining: SwapBuffers renders and presents the frame before it returns (by default frame N renders while the game prepares N + 1 and shows one SwapBuffers later, like a GPU driver queue) |
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.
Whole Doom 3 BFG frames (tools/bfgscene, baked from the game's assets:
depth pass, stencil shadow volumes, light interactions, ambient stages;
1280 x 720), best of several runs. tools/bfgscene/llvmpipe.py replays the
same scene files on llvmpipe (the images agree within about one level on
average). Without anisotropic filtering (FATGL_ANISO=0, llvmpipe.py --no-aniso):
| scene | fatgl 1 thread | llvmpipe 1 thread | fatgl 32 threads | llvmpipe 32 threads |
|---|---|---|---|---|
| Underground Garage | 117 ms | 367 ms | 10.3 ms | 45.9 ms |
| Alpha Labs 1 | 47 ms | 138 ms | 4.8 ms | 25.6 ms |
| Hell | 109 ms | 286 ms | 7.2 ms | 26.4 ms |
(fatgl 0.7.0 with fatmap 0.10.0: 181 / 73 / 130 ms and 15.3 / 7.9 / 9.1 ms. With 4x MSAA on 32 threads: 25 / 8 / 10 ms, was 105 / 43 / 18 ms.)
With the 8x anisotropic filtering BFG asks for (both renderers):
| scene | fatgl 1 thread | llvmpipe 1 thread | fatgl 32 threads | llvmpipe 32 threads |
|---|---|---|---|---|
| Underground Garage | 184 ms | 411 ms | 12.9 ms | 43.8 ms |
| Alpha Labs 1 | 88 ms | 145 ms | 7.4 ms | 27.0 ms |
| Hell | 241 ms | 345 ms | 13.8 ms | 26.8 ms |
The shadow volumes are most of the difference: fatmap skips the batches and tiles whose stored depth already decides them (hierarchical z per 8 x 8 pixels, also for stencil draws and the depth bounds test BFG uses), and fatgl caches converted vertex streams of static buffers across frames.
- 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.12.0),
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.
tools/bfgscene: real Doom 3 BFG frames for tuning, baked from the game's
own assets (needs an install; no game run). bake.py reads the
.resources files (the binary map, models, materials, images, the game's
GLSL) and does the frontend's work for one camera: portal visibility, lights
and their scissors, interactions, prelight and entity shadow volumes, material
expressions. It records the GL work the way BFG's backend issues it (depth
fill, per light stencil shadows and interactions, ambient stages); bfgscene
replays that command stream. tools/bfgscene/scenes.txt lists the frames
(Underground Garage, Alpha Labs 1, Hell); python tools/bfgscene/run.py [--game <dir>] [--sections] bakes them once into <build>/bfgscenes (git
ignored: they hold id's textures), then times each in every mode, checks that
the exact modes agree and compares against the local golden.txt (--update).
bfgscene <file> --show opens a window (the F6 to F10 keys work). Profiling:
bfgscene <file> 5 out.ppm --prof prints fatmap's per frame statistics
(fragments in / shaded, hierarchical z rejects, phase times, worker
utilization); --sample s.txt records instruction pointers of the timed frames
and python tools/bfgscene/profile.py s.txt [--fn <function>] lists the hot
functions (and a function's hot instructions). --swap also times the frames
back to back with SwapBuffers between them (throughput: frame pipelining
overlaps them; the per frame times above wait with glFinish). python tools/bfgscene/llvmpipe.py [--no-aniso] replays the scenes on Mesa's llvmpipe
in a Linux container (Docker; bfgscene builds for Linux with EGL). Not baked yet: GUIs,
particles, animated models, fog / blend lights, post process.
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).
