Commit 22e5d56
gui: a window the driver refused opens with a software renderer shipped beside it on Windows (#109)
* gui: the OpenGL binding is carried as a copy that imports no opengl32.dll at load time
The published github.com/go-gl/gl links -lopengl32 and calls
wglGetProcAddress as an imported symbol, so the window binary names
opengl32.dll in its import table and the loader maps the system's copy
before a line of our code runs. Windows hands a library already mapped
under a name to every later request for that name, which means a software
renderer loaded by path afterwards can never be the opengl32.dll the
toolkit finds when it creates its context. Measured on 2026-09-17 with the
process's own module list, on a machine with a driver and on a guest with
none.
go.mod now replaces the module with a copy under third_party/go-gl-gl,
reduced to the two packages the window imports, with one change: the
Windows branch of GlowGetProcAddress looks wglGetProcAddress up at run
time after loading the library by name, and both cgo LDFLAGS lines naming
opengl32 are gone. PATCH.md beside the copy says what changed and why, and
carries the published version's module sum, because go.sum stops carrying
the sum of a replaced module. The CI module list is unchanged - go list
reports a replaced module under its own path - and the comment above it
says so.
Three guards. One downloads the pinned version and holds every file of
the copy to the published bytes plus exactly the patch, and the sum in
PATCH.md to the sum the toolchain computes. One builds the window binary
with cgo on Windows and reads its import table through debug/pe - no
opengl32.dll, with gdi32.dll as the canary that the table was read. That
one is proven by a probe rather than a mutation entry: broken by hand it
went red naming the import, resolved through the -lopengl32 the windowing
library links, and the run took 3 min 56 s of recompiling everything above
the binding, which the mutation runner's ceiling would cap. Measured before
the guard was written and worth stating: a guard reading linker flags would
have refused a binary that was already right, because the windowing library
links -lopengl32 too and the binary still imports nothing from it. The third
guard asks that the walk behind fourteen shape guards leaves a nested module
alone, which it now does by the toolchain's own rule - a directory with its
own go.mod is outside "./..." - rather than by a name on a list.
The registry entry for the module gains a note and THIRD-PARTY-NOTICES.md a
paragraph, since the copy is what ships. The blank line that had split the
window's module table in two since #103 is closed. sortedKeys in the
telemetry guard sorts, which its name has promised since it was written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* gui: a window the driver refused is tried again with the software renderer shipped beside it
On a machine whose graphics driver offers no OpenGL 2.1 the window binary
refused, since #108, with a sentence and exit code 1. It now tries once
more before refusing: the first process starts this program again with the
same arguments plus --software-gl, waits, and answers with that process's
exit code. The second process, before the toolkit exists, names the driver
in its environment and loads Mesa's llvmpipe from opengl/ beside the
executable - renderer first, loader second, both by absolute path - so the
toolkit's request for OpenGL by name finds it already mapped. Measured on
2026-09-17 on a machine with a driver: started with the flag and no
variable in the environment, the window is drawn by the two files beside
it and nothing else answers to that name, twice out of twice. Without the
files beside it the window opens on the driver and says which file was
missing. Without the flag nothing changes.
The seam gains the second attempt: OpenOrRefuse asks for one when the
window has no native window, and refuses with the reason only when there
was none. The decision lives in gui.SecondAttempt with every piece that
touches the world as a field, so a guard presses all nine states without
a window, a renderer or a process: the second process never starts a
third, nothing is tried where nothing ships, the arguments are handed on
whole, and the refusal says what became of the renderer - which file is
missing, that starting again failed, or that it was tried and did not
help. On Linux and macOS it says nothing, by the owner's decision, since
nothing ships there.
Said out loud, rule 6: the first process writes one line before it starts
the second, the second says what it draws with, and the About screen
carries the same sentence for as long as the window is open. The Host
gains SoftwareRendering for that, and a guard reads the screen both ways.
The flag is public and takes the renderer on any machine. The first
process hands the driver variable over as well, and the second sets it
for itself, because a person asking by hand has no first process.
Two guards learn the new file shapes with their reasons: the hardening
guard, for a load by a path under the executable's own directory, and the
telemetry guard, for the one spawn in this tree - this program, by the
path os.Executable answers. That guard also gains os in its list of low
level packages: os.StartProcess walked past it until now, found by asking
what else the tree could have used. The canary carries the case, and a
staleness half now covers the two file keyed registries. Its findings
name the file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* legal: the registry gains a third class, for what ships beside a binary rather than in it
A module is reported by the build and a font by the compiler that embeds
it. The software renderer in the Windows archive is neither: two files
the release workflow downloads and puts next to tfg-gui.exe, loaded at run
time, which nothing in a binary can report. So the registry gains a
Companion: what it is, which binary and which archive carry it, the files
in the order the program loads them with their sizes and sums, the licence
expression of everything compiled into them, and exactly where the bytes
come from - the project, its release, the archive and that archive's sum.
Everything in the entry was read from the files of the pinned versions.
The archive itself carries no licence file - one readme pointing at a web
page - so docs/license.rst and licenses/ of mesa-26.2.0.tar.xz, the build
list of pal1000/mesa-dist-win at its 26.2.0 tag, llvm/LICENSE.TXT at
llvmorg-22.1.8 and the DirectX-Headers licence at v1.619.5 were read
instead, and the renderer's own bytes were asked what they hold: Mesa
26.2.0, LLVM 22.1.8, the llvmpipe, d3d12, zink and softpipe drivers, and
no zlib or zstd, whatever stands in the build environment. The GPL marked
files in the Mesa tree are Linux kernel headers and two drivers' headers,
none compiled on Windows. The expression is MIT AND Apache-2.0 WITH
LLVM-exception AND BSL-1.0.
Four consumers. The notices gain a section naming the files, their sums,
the archive and its sum, and reproduce the two licence texts they did not
carry - the LLVM exception and the Boost licence - from the files they
came from. The window's About screen names it under a third heading, on
every system, because the notices say the same everywhere; the stored
picture of that screen is regenerated, one of twenty-six. The command
line's list never names it, since nothing ships beside that binary. The
bill of materials ships it beside the window as DEPENDS_ON, with the
archive's name and sum, and beside nothing else.
One guard for each consumer, and one holding the registry's file list to
gui.SoftwareFiles - the same files in the same order, or the archive would
be packed with something other than what the program looks for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* ci: the release puts the software renderer beside the window, signs it, and checks it on the page
Two files of Mesa's llvmpipe reach the Windows archive of the window
through a download the workflow makes. The release of pal1000/mesa-dist-win
to take them from, the SHA-256 of that release's archive and the two files
with their own sums stand in .github/mesa-dist-win, and nowhere else the
workflow can read. .github/scripts/fetch_software_renderer.sh reads that
file, downloads the archive from the project's own releases, checks its
sum BEFORE unpacking anything, takes exactly the two files out of x64/,
checks each file's sum, and refuses anything but two files under opengl/.
Run on this machine: 18 s, every sum agreeing with the registry. Run with a
wrong archive sum and with a wrong file sum: refused, exit 1, nothing
unpacked in the first case. Both workflows that build the window call it,
on Windows only, between the build and the packing - the one that builds
from a branch as well, by the owner's decision, so a build from a branch is
the build a guest without a driver can be handed.
The signing script used to put a subdirectory back EMPTY. It repacked from
os.listdir, which names a directory and none of its contents, and zipfile
writes a directory entry for a directory and nothing more - measured on an
archive shaped like the window's: opengl/ came out with nothing under it, a
valid archive, no error. Nothing shipped in a subdirectory until now, so
nothing had noticed. It walks every directory now, counts the repacked
files against what it unpacked, and signs the libraries beside the program
as well as the program, verifying each one's certificate against the pin.
The workflow that checks a published release reads the renderer's files
out of the registry, asks the window's Windows archive to carry them, and
asks every program and every library in every Windows archive who signed
it - on the bytes a person downloads. The release notes say the renderer is
in the archive, where, and when it is used.
Six guards: the pin agrees with the registry, the fetch script checks
before it unpacks and carries no copy of the pin, every window build
fetches between building and packing, the signing repacks whole and signs
the libraries, and the published archive is checked for the renderer. The
guard over links leaving this project learns that a workflow's download
from somebody else's release is not a place a person is sent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* git: a Python bytecode cache reached a commit, and none may again
The signing script was imported by a probe to exercise its repack, which
left a cache file beside it that the next commit swept up. Removed, and
__pycache__ ignored from here on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* guard: the window build's fetch of the renderer is asked for as a call, not as text anywhere in the file
The mutation runner answered the first version with the call commented
out: the text was still in the file, the guard was green, and the archive
would have shipped without the renderer. It asks for the call at the start
of a line now.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: the readme, the changelog and the security policy say the Windows window carries a software renderer
One paragraph each. The readme says what the folder next to the program is,
when the window uses it, what happens without it, and that nothing of the
kind ships for Linux or macOS. The changelog entry says the same for a
person deciding whether to update, with the version of Mesa and the fact
that both files are signed, named in the notices with the sums they were
reviewed at, and in the bill of materials. The security policy names the
one thing in the Windows archive this project did not write, how the
release workflow pins and checks it, and that the program loads it only by
absolute path under its own directory and only after the driver refused.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* gui: the flag on a system the renderer does not ship for says so, rather than naming the system as an error
Found in the review before the pull request: --software-gl on Linux or
macOS printed "could not be loaded: linux", the reason type's words in a
sentence meant for a file that did not load, and if the driver then refused
the refusal would have added that sentence too - on the systems where the
owner decided nothing is said about the renderer. A window asked for the
renderer now says one of three things about the loading, through one
function: that it draws with the renderer, that none ships for this system,
or what stood in the way. The refusal on such a system stays as it was.
The download address in the registry is registered with the guard over
addresses in shipped code: the release workflow fetches from it and the
bill of materials names it, the program never does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* ci: the dependency gate is told about the copy of the OpenGL binding
GitHub's dependency graph reported the directory behind go.mod's replace
directive as an added dependency with no licence, and the gate blocked -
correctly, for a name nobody had looked at. This one has been: the
licence is the copied module's own MIT, carried in the directory, and the
bytes are the published module plus one patch, which a guard holds by
downloading the pinned version and comparing. The exception names that
one directory. A second directory nobody looked at still blocks, and the
guard over the gate asks both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* gui: three things the first CI run found that no local subset reads
staticcheck on Linux called loadFailed unused, because the one file that
raises it is built on Windows alone - the type now lives in that file.
gosec and semgrep both flagged the one spawn in this tree, a command built
from variables. It is settled where they read it, with the reason: the
program is os.Executable and the arguments are this process's own plus one
flag. The guard on flag spelling below the surfaces found the flag's name
in the registry's note, which names it in words now.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* gui: eleven things an outside review of the pull request named, each measured before it was changed
The signing script interpolated a file's path into PowerShell text, and a
name with a quote in it ran as PowerShell - measured before the change on
a copy named "a'; throw 'injected'; #.dll": the throw ran. The path goes
through an environment variable now, and a guard refuses the old shape.
A file of the renderer that cannot be read was reported as missing, with
the advice for a missing file. The look beside the program tells the two
apart now, the refusal has a sentence for each, and the look is a function
with the question to the file system handed in, so a guard can answer it
with a permission error no test can arrange on every disk the same way.
Two allowances forgave more than they were written for: the hardening
guard let a registered file grow any number of computed loads, and the
telemetry guard dropped every spawn and every computed load in a
registered file. Each forgives one now, a second is a finding, and a
canary holds it. The link guard's exemption for a release download applied
to every file, SECURITY.md included - it belongs to the fetch script alone
now, and a canary puts the same address into a human facing file and asks
for the refusal. The release guards read active lines, with comments taken
out, so an operation commented out is an operation gone. The walk behind
the shape guards names the nested modules it skipped and the guard asks
for exactly one. Guards over tracked files fail rather than skip when the
file is missing - the notices, the workflows, the signing script.
Three sentences said more than was true: the flag on a system nothing
ships for claimed the window was drawn by the driver before the window
was shown, and the readme, the changelog, the security policy and the
release notes said the renderer was never touched on a machine with a
driver, which the flag makes untrue. Each says now exactly what happens.
Not changed, with the reason written down: the gles2 package of the
binding is not patched, because no shipped binary links it - measured
with go list on every release platform - and wrapping the restart's
errors in English would put words a person reads outside the text package,
which a guard refuses, while the sentence that shows them already says
what failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* gui: six things the second outside review of the pull request named, each measured before it was changed
The release notes, the changelog and the bill of materials said the
software renderer is used when the graphics driver offers no OpenGL 2.1.
The program tests something else - whether the first attempt left a
window - and the notes now say that, with the driver as the usual reason.
The guard over links to other projects forgave a release download by the
script's NAME, and any project's download in that script. The review
asked for the pinned address written into the guard, which the script
does not hold: it builds the address from the pin. The exemption is now
the script's path from the repository root plus the project the
companion registry names, so a companion added later is covered on the
day it arrives.
The guards over sign_release.py read text, so a required operation
inside a docstring or a string literal satisfied them - and one of the
texts, codesign.go, stands in the script four times, once as the
constant and three times in messages. The review asked for a reader
that drops every string literal, which would have turned the guards red
on the correct script: five of the ten things they look for are string
contents by nature, measured with Python's own tokenizer. The reader now
drops comments to the end of a line and docstrings whole, keeps short
literals, and what is code is asked for as a statement at the start of
a line.
Two allowances - a computed library load, a spawn - forgave one
operation of a KIND in a registered file. They now name the operation:
the call, and the function that answers its first argument, followed
back to its one binding in the enclosing function. The hardening guard
reads the same registry instead of a copy. The helpers and their canary
have a file of their own, because the telemetry guard had grown into the
band the test shape ceiling watches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* guard: the forgiveness canary carries the approved spawn twice, the shape the mutation runner found missing
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent 529dc75 commit 22e5d56
72 files changed
Lines changed: 49881 additions & 207 deletions
File tree
- .github
- scripts
- workflows
- internal
- guard
- testdata/screens
- gui
- text
- locale
- window
- legal
- third_party/go-gl-gl
- v2.1/gl
- KHR
- v3.1/gles2
- KHR
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
69 | | - | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
70 | 80 | | |
71 | 81 | | |
72 | 82 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
108 | 108 | | |
109 | 109 | | |
110 | 110 | | |
111 | | - | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
112 | 116 | | |
113 | 117 | | |
114 | | - | |
| 118 | + | |
| 119 | + | |
115 | 120 | | |
116 | 121 | | |
117 | 122 | | |
| |||
243 | 248 | | |
244 | 249 | | |
245 | 250 | | |
246 | | - | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
247 | 258 | | |
248 | | - | |
| 259 | + | |
249 | 260 | | |
250 | 261 | | |
251 | 262 | | |
252 | | - | |
| 263 | + | |
253 | 264 | | |
254 | | - | |
| 265 | + | |
255 | 266 | | |
256 | 267 | | |
257 | 268 | | |
| |||
333 | 344 | | |
334 | 345 | | |
335 | 346 | | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
336 | 365 | | |
337 | | - | |
| 366 | + | |
| 367 | + | |
338 | 368 | | |
339 | 369 | | |
340 | 370 | | |
341 | 371 | | |
342 | 372 | | |
343 | 373 | | |
344 | | - | |
| 374 | + | |
| 375 | + | |
345 | 376 | | |
346 | 377 | | |
347 | 378 | | |
348 | | - | |
349 | | - | |
350 | | - | |
351 | | - | |
352 | | - | |
353 | | - | |
354 | | - | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
355 | 393 | | |
356 | | - | |
357 | | - | |
| 394 | + | |
| 395 | + | |
358 | 396 | | |
359 | 397 | | |
360 | 398 | | |
361 | 399 | | |
362 | | - | |
| 400 | + | |
363 | 401 | | |
364 | 402 | | |
365 | 403 | | |
366 | | - | |
| 404 | + | |
367 | 405 | | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
368 | 414 | | |
369 | | - | |
| 415 | + | |
| 416 | + | |
370 | 417 | | |
371 | 418 | | |
372 | 419 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
142 | 153 | | |
143 | 154 | | |
144 | 155 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
204 | 204 | | |
205 | 205 | | |
206 | 206 | | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
207 | 223 | | |
208 | 224 | | |
209 | 225 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
314 | 314 | | |
315 | 315 | | |
316 | 316 | | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
317 | 331 | | |
318 | 332 | | |
319 | 333 | | |
| |||
445 | 459 | | |
446 | 460 | | |
447 | 461 | | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
448 | 470 | | |
449 | 471 | | |
450 | 472 | | |
| |||
0 commit comments