mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-18 23:09:29 -05:00
The two parts of mihawk-99/PS5_RetroArch#62 (Marco Tundo) that 0131 did not already cover. Frame generation's plan writes the presents per game frame into video_shader_subframes, which retroarch.cfg keeps. Once frame generation was turned off, in its menus or in ES-DE's, the 2 it left there held every game frame for two refreshes: half speed on a 60 Hz output. retroarch.c now sets one present per game frame whenever frame generation is off, and logs it when it replaces a larger number. Turning frame generation on or off in the menus reinitializes the video output, as a Target Framerate change already did, so the plan applies at once (src/ps5_framegen_menu.c). A Shader Sub-Frames value set by hand no longer survives a core load while frame generation is off; sub-frame shaders have not been tested on this console. Evidence: tests/test_framegen_plan.py runs the plan as the script inserts it. Two new cases start from a 2 left in retroarch.cfg with frame generation off, on a 60 Hz output and on a VRR one; both fail with 0132's edit left out. Gates: format PASS; unit PASS (197 tests); build PASS; integration PASS; evidence PASS (131 captures). The build gate ran with PS5_REUSE_STAGED_CORES naming the 33 shipped cores: core stamps hash absolute paths, so in a worktree every core looks changed, and the staged cores are main's. Console run: with this series' deploy. Co-authored-by: Marco Tundo <marco@bymar.co>
84 lines
4.1 KiB
Python
84 lines
4.1 KiB
Python
"""Run frame generation's plan (patches 0121, 0131 and 0132) as the port inserts it, for each kind of output."""
|
|
import importlib.util
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
class FramegenPlan(unittest.TestCase):
|
|
def test_the_plan_follows_the_flips_the_output_shows(self):
|
|
spec = importlib.util.spec_from_file_location('patches', ROOT / 'tools/apply-port-patches.py')
|
|
patches = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(patches)
|
|
# The plan as 0121 inserts it, with every later retroarch.c edit applied in order, as main() does.
|
|
edits = [e for e in patches.EDITS if e[0] == 'retroarch.c']
|
|
start = next(i for i, e in enumerate(edits) if e[3] == 'Output Rate Auto: the presents per game frame')
|
|
text = edits[start][2]
|
|
for _, anchor, replacement, marker in edits[start + 1:]:
|
|
if marker not in text and anchor in text:
|
|
text = text.replace(anchor, replacement, 1)
|
|
plan = text[:text.index(' unsigned shader_subframes')]
|
|
source = r'''
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdbool.h>
|
|
#define RARCH_LOG(...) ((void)0)
|
|
typedef struct {
|
|
struct { bool video_framegen, video_framegen_auto; } bools;
|
|
struct { unsigned video_framegen_target, video_shader_subframes; } uints;
|
|
struct { float video_refresh_rate; } floats;
|
|
} settings_t;
|
|
unsigned vulkan_ps5_display_refresh_x1000, vulkan_ps5_flip_rate_x1000, ps5_fg_dup;
|
|
float ps5_fg_core_fps, ps5_fg_target_fps;
|
|
/* The presents per game frame, frame generation on or off with what an earlier run left in retroarch.cfg, for a mode
|
|
* and the flips timed in it; refresh: what RetroArch then paces on. */
|
|
static unsigned plan_after(bool framegen, unsigned left, unsigned mode, unsigned flips, unsigned target,
|
|
double input_fps, float *refresh)
|
|
{
|
|
settings_t s = {{framegen, true}, {target, left}, {mode / 1000.0f}};
|
|
settings_t *settings = &s;
|
|
float video_refresh_rate = settings->floats.video_refresh_rate;
|
|
vulkan_ps5_display_refresh_x1000 = mode;
|
|
vulkan_ps5_flip_rate_x1000 = flips;
|
|
''' + plan + r'''
|
|
(void)video_refresh_rate;
|
|
*refresh = settings->floats.video_refresh_rate;
|
|
return settings->uints.video_shader_subframes;
|
|
}
|
|
static unsigned plan(unsigned mode, unsigned flips, unsigned target, double input_fps, float *refresh)
|
|
{
|
|
return plan_after(true, 1, mode, flips, target, input_fps, refresh);
|
|
}
|
|
int main(void)
|
|
{
|
|
float refresh;
|
|
/* An output without 120 Hz: no picture held for two refreshes, paced at 59.94 */
|
|
assert(plan(59940, 59940, 120, 58.944, &refresh) == 1 && refresh < 60.0f);
|
|
assert(plan(59940, 59940, 120, 60.0, &refresh) == 1 && refresh < 60.0f);
|
|
assert(plan(59940, 59940, 60, 60.0, &refresh) == 1);
|
|
/* ...and a 30 fps game still gets a frame between its frames, at the display's 60 */
|
|
assert(plan(59940, 59940, 120, 30.0, &refresh) == 2 && fabsf(ps5_fg_target_fps - 59.94f) < 0.01f);
|
|
/* VRR: 120 flips a second in the 59.94 Hz mode */
|
|
assert(plan(59940, 119880, 120, 60.0, &refresh) == 2 && refresh > 119.0f && ps5_fg_dup == 1);
|
|
assert(plan(59940, 119880, 60, 60.0, &refresh) == 2 && ps5_fg_dup == 2);
|
|
/* Flips not timed: the mode is believed */
|
|
assert(plan(59940, 0, 120, 60.0, &refresh) == 1 && refresh < 60.0f);
|
|
/* The 119.88 Hz mode itself */
|
|
assert(plan(119880, 0, 120, 60.0, &refresh) == 2 && ps5_fg_dup == 1);
|
|
assert(plan(119880, 0, 60, 60.0, &refresh) == 2 && ps5_fg_dup == 2);
|
|
/* Frame generation turned off: the 2 it left is one present per game frame again, on either output */
|
|
assert(plan_after(false, 2, 59940, 59940, 120, 60.0, &refresh) == 1);
|
|
assert(plan_after(false, 2, 59940, 119880, 120, 60.0, &refresh) == 1);
|
|
return 0;
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / 'plan.c'
|
|
path.write_text(source)
|
|
exe = Path(tmp) / 'plan'
|
|
subprocess.run(['cc', '-std=gnu99', str(path), '-o', str(exe), '-lm'], check=True)
|
|
subprocess.run([str(exe)], check=True)
|