mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 19:07:47 -04:00
4a2f5a67d2
With the render thread running, the backend's facade table records every state change, primitive, fill, copy, CPU->VRAM upload (payload copied) and native-wide call, and the present, into the command ring; the render thread replays them through the unchanged glb_* functions. With it off gl_backend_get returns the existing table and nothing new runs. Sync points: every other GL entry point reached on the emulation thread (readbacks, savestate restage, render passes, OpenXR, settings, diagnostics) drains the ring and takes the context (GL_RT_SYNC); the emulation thread then runs the rest of that frame directly, exactly as without the thread, and hands the context back at the frame boundary when the frame may run asynchronously again (no depth24 display, dual-raster, frame interpolation, render pass, netplay or OpenXR). Guest-visible VRAM stays gpu.c's array: while the render thread owns the context, uploads stage from a private copy that receives each payload in command order. Guest state the backend reads while drawing (per-primitive widescreen tags, the native-wide fast-path latch) is captured at record time; host overlays travel with the present; a present that a newer recorded frame already supersedes is skipped. The emulation thread answers projective/wide-present/draw-area queries from mirrors of the recorded calls. The per-frame display-ring readback is queued (fbo_peek_deferred) instead of being a sync point. The GL fixtures link render_thread.c and stub the two facade hooks; the scale fixture also gains the gpu_ws_background_requires_full_composite stub it was already missing (it failed to link on master). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
4.5 KiB
Python
98 lines
4.5 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"))
|
|
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())
|