tests: build and run the render thread tests on Windows (MinGW)

render_thread_test is registered on MinGW (winpthreads provides the
fixture's pthreads and nanosleep; the core takes its Win32 path).
gl_render_thread_test gains the MinGW link path run_gl_scale_invariance.py
uses on test/gl-scale-invariance-windows (static SDL3, opengl32 and its
Win32 libraries, LTO, probe.exe), and a host with no window or GL 3.3 core
context (headless, Windows over SSH) exits 77 from a preflight run, reported
by CTest as a skip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Shokunin
2026-10-06 02:37:08 -07:00
parent 5860b85234
commit eabc8ed7f5
3 changed files with 56 additions and 11 deletions
+8 -4
View File
@@ -214,7 +214,9 @@ if(BUILD_TESTING)
# Render thread ([video] render_thread) on vs off: the values the
# guest reads back, native VRAM, the frame at S and the native-wide
# surface are identical for a scripted stream with readbacks, mask
# bits, uploads, ring wraps and per-primitive widescreen tags.
# bits, uploads, ring wraps and per-primitive widescreen tags. A host
# with no window/GL 3.3 context (headless, Windows over SSH) exits 77:
# reported as skipped.
add_test(NAME gl_render_thread_test
COMMAND "${Python3_EXECUTABLE}"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/run_gl_render_thread.py"
@@ -223,7 +225,8 @@ if(BUILD_TESTING)
--sdl-include "${_gl_scale_sdl_inc}"
--sdl-library "$<TARGET_FILE:SDL3::SDL3>"
--output "${CMAKE_CURRENT_BINARY_DIR}/gl-render-thread-results")
set_tests_properties(gl_render_thread_test PROPERTIES LABELS "gpu;opengl;hardware" TIMEOUT 300)
set_tests_properties(gl_render_thread_test PROPERTIES
LABELS "gpu;opengl;hardware" TIMEOUT 300 SKIP_RETURN_CODE 77)
unset(_gl_scale_sdl_inc)
else()
message(STATUS "gl_scale_invariance_test: not registered (needs macOS, Linux or MinGW, Python 3 and SDL3)")
@@ -1127,8 +1130,9 @@ if(BUILD_TESTING)
# Render thread core ([video] render_thread, docs/RENDER_THREAD.md):
# ordering across ring wraps, the frame bound, stale-frame detection and
# the drain + context hand-off sync point, against a fake executor.
# The fixture itself uses POSIX threads and nanosleep.
if(NOT WIN32)
# The fixture itself uses POSIX threads and nanosleep (MinGW has both;
# the core takes its Win32 path there).
if(NOT MSVC)
add_executable(render_thread_test tests/test_render_thread.c src/render_thread.c)
target_include_directories(render_thread_test PRIVATE include)
find_package(Threads REQUIRED)
+29 -5
View File
@@ -8,12 +8,16 @@ the stream (its own checks). A third run per scale with one frame in flight
(PSX_TEST_RT_FRAMES is not needed: the fixture takes the bound as argv) is not
required; the in-flight bound itself is covered by render_thread_test.
macOS/Linux: pass the SDL3 include directory and static library and a C
compiler, as for run_gl_scale_invariance.py. Evidence (commands, output) is
written to receipt.json under --output.
macOS/Linux/Windows (MinGW): pass the SDL3 include directory and static
library and a C compiler, as for run_gl_scale_invariance.py. Evidence
(commands, output) is written to receipt.json under --output. When the host
cannot create a hidden window with a GL 3.3 core context (headless, or Windows
over SSH) the fixture exits SKIP_EXIT and so does this script: CTest reports a
skip, not a pass.
"""
import argparse
import json
import os
import pathlib
import platform
import re
@@ -25,6 +29,11 @@ MAC_FRAMEWORKS = ["Cocoa", "OpenGL", "IOKit", "CoreVideo", "CoreAudio", "AudioTo
"Carbon", "ForceFeedback", "GameController", "Metal", "QuartzCore",
"CoreMedia", "AVFoundation", "Foundation", "CoreHaptics",
"UniformTypeIdentifiers"]
# The static SDL3's Win32 dependencies (as run_gl_texture_filter.py links them).
WIN_LIBS = ["opengl32", "kernel32", "user32", "gdi32", "winmm", "imm32", "ole32",
"oleaut32", "version", "uuid", "advapi32", "setupapi", "shell32", "dinput8"]
SKIP_EXIT = 77
WINDOWS = os.name == "nt" or platform.system().startswith(("MINGW", "MSYS", "CYGWIN"))
KEYS = ("rb", "digest", "hires", "wide")
@@ -78,6 +87,10 @@ def main():
("xr", src / "psx_openxr.c"), ("rth", src / "render_thread.c"),
("facade", src / "gpu_render.c")]
sections = [] if platform.system() == "Darwin" else ["-ffunction-sections", "-fdata-sections"]
# MinGW's PE linker reports undefined references from sections it later
# collects, so there LTO drops them first (as run_gl_scale_invariance.py).
if WINDOWS:
sections.append("-flto")
objs = []
for name, path in sources:
o = dest / (name + ".o")
@@ -87,11 +100,15 @@ def main():
print(r.stderr[-3000:])
return 2
objs.append(o)
link = [args.cc, *objs, args.sdl_library, "-o", dest / "probe"]
probe = dest / ("probe.exe" if WINDOWS else "probe")
link = [args.cc, *objs, args.sdl_library, "-o", probe]
if platform.system() == "Darwin":
for f in MAC_FRAMEWORKS:
link += ["-framework", f]
link += ["-liconv", "-lm", "-lpthread", "-Wl,-dead_strip"]
elif WINDOWS:
# -static: no libwinpthread/libgcc DLLs needed next to probe.exe.
link += ["-flto", "-static", *["-l" + x for x in WIN_LIBS], "-lm", "-Wl,--gc-sections"]
else:
link += ["-lGL", "-lm", "-ldl", "-lpthread", "-Wl,--gc-sections"]
r = run(link)
@@ -100,10 +117,17 @@ def main():
return 2
ok = True
first = True
for s in [int(v) for v in args.scales.split(",") if v]:
runs = {}
for threaded in (0, 1):
r = run([dest / "probe", s, threaded, args.frames])
r = run([probe, s, threaded, args.frames])
if first and r.returncode == SKIP_EXIT:
# No window/GL context on this host: skip. Only the first
# run may skip; after it, 77 is a failure like any other.
print("SKIP:", r.stderr.strip()[-600:])
return SKIP_EXIT
first = False
p = parse(r.stdout)
tail = r.stdout.strip().splitlines()[-6:]
print(f"scale {s} render_thread={threaded}: exit={r.returncode}", tail,
+19 -2
View File
@@ -210,7 +210,12 @@ int main(int argc, char **argv) {
int scale = argc > 1 ? atoi(argv[1]) : 1;
int threaded = argc > 2 && argv[2][0] == '1';
int frames = argc > 3 ? atoi(argv[3]) : 80;
if (SDL_Init(SDL_INIT_VIDEO) != 0) return 2;
/* No video device, window or GL context (headless, Windows over SSH):
* exit 77, which the harness reports as a CTest skip. */
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
fprintf(stderr, "SKIP no video (%s)\n", SDL_GetError());
return 77;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
@@ -219,7 +224,19 @@ int main(int argc, char **argv) {
#endif
SDL_Window *win = SDL_CreateWindow("Render thread hidden test", 0, 0, 320, 240,
SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
if (!win) return 2;
if (!win) { fprintf(stderr, "SKIP no window (%s)\n", SDL_GetError()); return 77; }
{ /* Probe with the renderer's attributes: only a probe tells "no
* context on this host" from "the renderer's init broke". */
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GLContext probe = SDL_GL_CreateContext(win);
if (!probe) {
fprintf(stderr, "SKIP no GL 3.3 core context (%s)\n", SDL_GetError());
return 77;
}
SDL_GL_MakeCurrent(win, NULL);
SDL_GL_DeleteContext(probe);
}
gr_set_backend(GR_BACKEND_OPENGL);
gr_init(vram);
gr_set_scale(scale);