mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
8b003a591d
Review (#599): fn and ctx were two separate stores read on the render thread, so an unregister could pair a live fn with a NULL ctx. They are now one immutable record behind an acquire/release atomic pointer. The GL fixture runners compile a fixed source list; gpu_gl_renderer.c now calls psx_host_overlay_draw, so every run_gl_*.py links host_overlay.c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
105 lines
5.0 KiB
Python
105 lines
5.0 KiB
Python
"""Run source-owned readback checks with a hidden real OpenGL context."""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
|
|
def probe_result_ok(code, stdout, stderr, expect_unbounded=False):
|
|
lines = stdout.rstrip().splitlines()
|
|
summary = re.fullmatch(r"checks=(\d+) failures=(\d+)", lines[-1]) if lines else None
|
|
if not summary or int(summary[1]) == 0:
|
|
return False
|
|
failures = [line for line in stderr.splitlines() if line.startswith("FAIL ")]
|
|
if expect_unbounded:
|
|
return (code == 1 and int(summary[2]) == 1 and
|
|
failures == ["FAIL single-pixel bounded transfer"])
|
|
return code == 0 and int(summary[2]) == 0 and not failures
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--compiler-bin", required=True)
|
|
parser.add_argument("--sdl-root", required=True)
|
|
parser.add_argument("--sdl-library", help="Explicit SDL3 library (e.g. a system DLL import library)")
|
|
parser.add_argument("--output", required=True)
|
|
parser.add_argument("--gl-source")
|
|
parser.add_argument("--fixture", type=pathlib.Path)
|
|
parser.add_argument("--expect-unbounded", action="store_true")
|
|
args = parser.parse_args()
|
|
framework = pathlib.Path(__file__).resolve().parents[2]
|
|
compiler = pathlib.Path(args.compiler_bin).resolve()
|
|
for name in ("gcc.exe", "g++.exe"):
|
|
if not (compiler / name).is_file():
|
|
parser.error(f"--compiler-bin must contain {name}: {compiler}")
|
|
sdl = pathlib.Path(args.sdl_root).resolve()
|
|
if not (sdl / "sdl3-src/include").is_dir():
|
|
parser.error(f"--sdl-root must contain the sdl3-src/include directory: {sdl}")
|
|
sdl_library = pathlib.Path(args.sdl_library).resolve() if args.sdl_library else sdl / "sdl3-build/libSDL3.a"
|
|
if not sdl_library.is_file():
|
|
parser.error(f"SDL3 library does not exist: {sdl_library}")
|
|
output = pathlib.Path(args.output).resolve()
|
|
output.mkdir(parents=True, exist_ok=True)
|
|
dest = pathlib.Path(tempfile.mkdtemp(prefix="readback-", dir=output))
|
|
print("Evidence directory:", dest)
|
|
env = os.environ.copy()
|
|
env["PATH"] = str(compiler) + os.pathsep + env.get("PATH", "")
|
|
if args.gl_source:
|
|
shutil.copyfile(args.gl_source, dest / "gpu_gl_renderer.c")
|
|
includes = ["-I", str(dest), "-I", str(framework / "runtime/include"),
|
|
"-I", str(framework / "runtime/src"), "-I", str(sdl / "sdl3-src/include")]
|
|
receipt = []
|
|
|
|
def run(command):
|
|
command = [str(value) for value in command]
|
|
result = subprocess.run(command, cwd=dest, capture_output=True,
|
|
text=True, encoding="utf-8", errors="replace", env=env)
|
|
receipt.append({"cmd": command, "exit": result.returncode,
|
|
"stdout": result.stdout, "stderr": result.stderr})
|
|
(dest / "receipt.json").write_text(json.dumps(receipt, indent=2), encoding="utf-8")
|
|
print(result.stdout[-600:], result.stderr[-2000:])
|
|
return result
|
|
|
|
fixture = args.fixture or framework / "runtime/tests/test_gl_readback_region.c"
|
|
# The renderer's render-pass paths call the journal and VRAM policy in
|
|
# render_pass_plan.c (self-contained: no other runtime symbols).
|
|
sources = [("probe", fixture), ("sw", framework / "runtime/src/gpu_sw_renderer.c"),
|
|
("rp", framework / "runtime/src/render_pass_plan.c"),
|
|
("xr", framework / "runtime/src/psx_openxr.c")]
|
|
if (framework / "runtime/src/render_thread.c").exists():
|
|
sources.append(("rth", framework / "runtime/src/render_thread.c"))
|
|
if (framework / "runtime/src/present_thread.c").exists():
|
|
sources.append(("pth", framework / "runtime/src/present_thread.c"))
|
|
if (framework / "runtime/src/frame_gen.c").exists():
|
|
sources.append(("fg", framework / "runtime/src/frame_gen.c"))
|
|
# The renderer calls the in-game overlay hook (host_overlay.c) at present.
|
|
if (framework / "runtime/src" / "host_overlay.c").exists():
|
|
sources.append(("hov", framework / "runtime/src" / "host_overlay.c"))
|
|
for name, source in sources:
|
|
if run([compiler / "gcc.exe", "-std=c11", "-O2", "-flto", "-DPSX_SDL3=1",
|
|
"-DPSX_NO_DEBUG_TOOLS=1", *includes, "-c", source,
|
|
"-o", dest / (name + ".o")]).returncode:
|
|
return 2
|
|
libraries = ["m", "kernel32", "user32", "gdi32", "winmm", "imm32", "ole32",
|
|
"oleaut32", "version", "uuid", "advapi32", "setupapi", "shell32",
|
|
"dinput8", "opengl32"]
|
|
if run([compiler / "g++.exe", "-O2", "-flto", *[dest / (name + ".o") for name, _ in sources],
|
|
sdl_library, *["-l" + name for name in libraries],
|
|
"-o", dest / "probe.exe"]).returncode:
|
|
return 2
|
|
for scale in (1, 4):
|
|
result = run([dest / "probe.exe", scale])
|
|
if not probe_result_ok(result.returncode, result.stdout, result.stderr,
|
|
args.expect_unbounded):
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|