Files
psxrecomp/tools/measure_render_quality.py
T

70 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""Measure a running debug build's cadence, redraws and execution phases.
Use the same scene/settings for comparisons. Phase samples measure host wall
time (including waits), not the percentage of guest instructions compiled.
This tool neither changes settings nor supplies input to the game.
"""
import argparse
import json
import math
from pathlib import Path
import time
from debug_client import query
def measure(host, port, seconds, counter_prefix):
def ask(cmd, **values):
result = query(host, port, dict(cmd=cmd, **values), timeout=30)
if not result.get('ok'):
raise RuntimeError(f'{cmd}: {result.get("err", "request failed")}')
return result
def sample():
return {cmd: ask(cmd) for cmd in ('get_registers', 'mod_counters', 'render_pass_stats')}
def frame_count(sample):
return next((entry['count'] for entry in sample['mod_counters']['counters']
if entry['name'] == counter_prefix + '.frames'), None)
before = sample()
start = time.monotonic()
time.sleep(seconds)
after = sample()
elapsed = time.monotonic() - start
guest_hz = (after['get_registers']['frame'] - before['get_registers']['frame']) / elapsed
redraw_hz = (after['render_pass_stats']['passes'] - before['render_pass_stats']['passes']) / elapsed
native_before, native_after = frame_count(before), frame_count(after)
return dict(
elapsed_seconds=elapsed, guest_vblanks_per_second=guest_hz,
extra_geometry_renders_per_second=redraw_hz,
original_draws_per_second=(native_after-native_before)/elapsed
if native_after is not None and native_before is not None else None,
game_phase=ask('turbo_loads'), video=ask('video_info'),
execution_phase_samples=ask('phase_profile', window=min(60, math.ceil(seconds))),
timing=ask('frame_perf'), presenter=ask('gl_interp'), before=before, after=after,
method='Host wall-time phase samples and event-counter deltas; neither instruction coverage nor unique displayed FPS.')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--host', default='127.0.0.1')
parser.add_argument('--port', type=int, default=4370)
parser.add_argument('--seconds', type=float, default=10)
parser.add_argument('--counter-prefix', default='', help='For example medievil.fr or medievil2.fr')
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
if not math.isfinite(args.seconds) or not 1 <= args.seconds <= 60:
parser.error('--seconds must be between 1 and 60')
result = measure(args.host, args.port, args.seconds, args.counter_prefix)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(result, indent=2)+'\n', encoding='utf-8')
print(f'Guest VBlanks: {result["guest_vblanks_per_second"]:.2f}/s; '
f'extra geometry renders: {result["extra_geometry_renders_per_second"]:.2f}/s')
print(f'Evidence: {args.output}')
if __name__ == '__main__':
main()