opengl: add motion-adaptive interpolation blend

This commit is contained in:
Matthew Stanley
2026-07-28 13:39:05 -07:00
parent 224147bb07
commit 3c67a52f95
4 changed files with 96 additions and 31 deletions
+2 -1
View File
@@ -25,7 +25,8 @@ void gl_renderer_set_swap_interval(int interval);
/* Presentation-only frame interpolation. High-refresh sub-presents blend the
* two most recent stable display images; guest simulation timing is unchanged. */
void gl_renderer_set_interpolation(int enabled, double host_hz, double target_hz);
void gl_renderer_set_interpolation(int enabled, double host_hz, double target_hz,
int blend_mode);
void gl_renderer_set_interpolation_suspended(int suspended);
void gl_renderer_interpolation_diag(int *enabled, int *suspended,
int *history_frames,
+11
View File
@@ -76,6 +76,17 @@ int psx_mod_set_native_vblank_rate(uint32_t frames_per_second);
* A value of zero follows the measured host-display refresh rate.
*/
int psx_mod_set_frame_interpolation(uint32_t frames_per_second);
/*
* Choose how the OpenGL presenter combines completed frames. Linear is the
* legacy full-frame crossfade. Motion-adaptive retains interpolation for
* small temporal changes but switches large changes cleanly to reduce the
* double-image trails produced by moving objects.
*/
enum {
PSX_MOD_FRAME_INTERPOLATION_LINEAR = 0,
PSX_MOD_FRAME_INTERPOLATION_MOTION_ADAPTIVE = 1
};
int psx_mod_set_frame_interpolation_blend(uint32_t blend_mode);
int psx_mod_set_auto_skip_fmv(int enabled);
/*
+46 -27
View File
@@ -315,10 +315,12 @@ static GLint s_present_uTex = -1, s_present_uUvRect = -1;
static GLuint s_interp_prog = 0, s_interp_tex[3];
static GLsync s_interp_fence[3];
static GLsync s_interp_draw_fence = NULL;
static GLint s_interp_uPrev = -1, s_interp_uCurr = -1;
static GLint s_interp_uAlpha = -1, s_interp_uUvRect = -1;
static int s_interp_enabled = 0, s_interp_valid = 0;
static int s_interp_suspended = 0;
static GLint s_interp_uPrev = -1, s_interp_uCurr = -1;
static GLint s_interp_uAlpha = -1, s_interp_uUvRect = -1;
static GLint s_interp_uBlendMode = -1;
static int s_interp_enabled = 0, s_interp_valid = 0;
static int s_interp_suspended = 0;
static int s_interp_blend_mode = 0;
static int s_interp_prev = 0, s_interp_cur = 0;
static int s_interp_w = 0, s_interp_h = 0, s_interp_linear = 0;
static int s_interp_force_4_3 = 0, s_interp_source_path = -1;
@@ -740,11 +742,21 @@ static const char *PRESENT_FS =
"#version 330\n"
"in vec2 v_uv; uniform sampler2D u_tex; out vec4 frag;\n"
"void main(){ frag = texture(u_tex, v_uv); }\n";
static const char *INTERP_FS =
"#version 330\n"
"in vec2 v_uv; uniform sampler2D u_prev; uniform sampler2D u_curr;\n"
"uniform float u_alpha; out vec4 frag;\n"
"void main(){ frag=mix(texture(u_prev,v_uv),texture(u_curr,v_uv),u_alpha); }\n";
static const char *INTERP_FS =
"#version 330\n"
"in vec2 v_uv; uniform sampler2D u_prev; uniform sampler2D u_curr;\n"
"uniform float u_alpha; uniform int u_blend_mode; out vec4 frag;\n"
"void main(){\n"
" vec4 prev=texture(u_prev,v_uv), curr=texture(u_curr,v_uv);\n"
" float alpha=u_alpha;\n"
" if(u_blend_mode==1){\n"
" vec3 d=abs(prev.rgb-curr.rgb);\n"
" float change=max(max(d.r,d.g),d.b);\n"
" float safe_blend=1.0-smoothstep(0.08,0.20,change);\n"
" alpha=mix(step(0.5,u_alpha),u_alpha,safe_blend);\n"
" }\n"
" frag=mix(prev,curr,alpha);\n"
"}\n";
/* Geometry: position in VRAM pixels (draw offset already applied by gpu.c),
* color rgb in 0..1, color a = mask bit (0/1). The clip transform is in
@@ -2461,9 +2473,11 @@ int gl_renderer_init_context(SDL_Window *win) {
s_present_uTex = p_glGetUniformLocation(s_present_prog, "u_tex");
s_present_uUvRect = p_glGetUniformLocation(s_present_prog, "u_uv_rect");
s_interp_uPrev = p_glGetUniformLocation(s_interp_prog, "u_prev");
s_interp_uCurr = p_glGetUniformLocation(s_interp_prog, "u_curr");
s_interp_uAlpha = p_glGetUniformLocation(s_interp_prog, "u_alpha");
s_interp_uUvRect = p_glGetUniformLocation(s_interp_prog, "u_uv_rect");
s_interp_uCurr = p_glGetUniformLocation(s_interp_prog, "u_curr");
s_interp_uAlpha = p_glGetUniformLocation(s_interp_prog, "u_alpha");
s_interp_uUvRect = p_glGetUniformLocation(s_interp_prog, "u_uv_rect");
s_interp_uBlendMode =
p_glGetUniformLocation(s_interp_prog, "u_blend_mode");
glGenTextures(3, s_interp_tex);
for (int i = 0; i < 3; i++) {
glBindTexture(GL_TEXTURE_2D, s_interp_tex[i]);
@@ -3239,7 +3253,8 @@ static void interp_reset_history(void) {
if (s_interp_mutex) SDL_UnlockMutex(s_interp_mutex);
}
void gl_renderer_set_interpolation(int enabled, double host_hz, double target_hz) {
void gl_renderer_set_interpolation(int enabled, double host_hz, double target_hz,
int blend_mode) {
double effective_hz = target_hz < 0.0
? -1.0
: (target_hz >= 60.0 ? target_hz : host_hz);
@@ -3272,15 +3287,18 @@ void gl_renderer_set_interpolation(int enabled, double host_hz, double target_hz
if (s_interp_mutex) SDL_LockMutex(s_interp_mutex);
if (active != s_interp_enabled) interp_reset_history_unlocked();
s_interp_enabled = active;
s_interp_host_hz = host_hz;
s_interp_target_hz = active ? effective_hz : 0.0;
s_interp_host_hz = host_hz;
s_interp_target_hz = active ? effective_hz : 0.0;
s_interp_blend_mode = blend_mode == 1 ? 1 : 0;
if (s_interp_mutex) SDL_UnlockMutex(s_interp_mutex);
if (active && effective_hz < 0.0)
fprintf(stdout, "psxrecomp: GL frame interpolation enabled: uncapped "
"target on %.1f Hz display\n", host_hz);
"target on %.1f Hz display (%s blend)\n", host_hz,
s_interp_blend_mode ? "motion-adaptive" : "linear");
else if (active)
fprintf(stdout, "psxrecomp: GL frame interpolation enabled: %.1f FPS "
"target on %.1f Hz display\n", effective_hz, host_hz);
"target on %.1f Hz display (%s blend)\n", effective_hz, host_hz,
s_interp_blend_mode ? "motion-adaptive" : "linear");
else
fprintf(stdout, "psxrecomp: GL frame interpolation disabled (host %.1f Hz)\n", host_hz);
}
@@ -3397,8 +3415,9 @@ static void interp_draw_quad(float alpha, int lx, int ly, int lw, int lh) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, s_interp_linear ? GL_LINEAR : GL_NEAREST);
p_glUseProgram(s_interp_prog);
p_glUniform1i(s_interp_uPrev, 0);
p_glUniform1i(s_interp_uCurr, 1);
p_glUniform1f(s_interp_uAlpha, alpha);
p_glUniform1i(s_interp_uCurr, 1);
p_glUniform1f(s_interp_uAlpha, alpha);
p_glUniform1i(s_interp_uBlendMode, s_interp_blend_mode);
p_glUniform4f(s_interp_uUvRect, 0.f, 0.f, 1.f, 1.f);
p_glBindVertexArray(s_interp_thread_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
@@ -3442,15 +3461,15 @@ static int interp_present(void) {
return 1;
}
static int interp_thread_main(void *opaque) {
(void)opaque;
if (SDL_GL_MakeCurrent(s_win, s_interp_ctx) != 0) return -1;
SDL_GL_SetSwapInterval(0); /* host-period scheduler owns cadence */
static int interp_thread_main(void *opaque) {
(void)opaque;
if (SDL_GL_MakeCurrent(s_win, s_interp_ctx) != 0) return -1;
SDL_GL_SetSwapInterval(0); /* host-period scheduler owns cadence */
p_glGenVertexArrays(1, &s_interp_thread_vao);
uint64_t freq = SDL_GetPerformanceFrequency();
uint64_t deadline = SDL_GetPerformanceCounter();
uint64_t diag_start = deadline, diag_swaps = 0, diag_captures = 0;
uint64_t freq = SDL_GetPerformanceFrequency();
uint64_t deadline = SDL_GetPerformanceCounter();
uint64_t diag_start = deadline, diag_swaps = 0, diag_captures = 0;
while (SDL_AtomicGet(&s_interp_thread_run)) {
SDL_LockMutex(s_interp_mutex);
double hz = s_interp_target_hz;
+37 -3
View File
@@ -540,6 +540,10 @@ static int g_low_latency_input = 1;
static int g_video_vsync = 1;
static int g_frame_interpolation = 0;
static int g_frame_interpolation_fps = 0;
static int g_frame_interpolation_blend =
PSX_MOD_FRAME_INTERPOLATION_LINEAR;
static int g_frame_interpolation_blend_default =
PSX_MOD_FRAME_INTERPOLATION_LINEAR;
static int g_mod_controller_mode_override[2] = { -1, -1 };
static_assert((int)PSX_MOD_CONTROLLER_HYBRID ==
(int)PSXRecompV4::PAD_MODE_HYBRID);
@@ -702,6 +706,22 @@ extern "C" int psx_mod_set_frame_interpolation(
return 1;
}
extern "C" int psx_mod_set_frame_interpolation_blend(
uint32_t blend_mode) {
if (blend_mode != PSX_MOD_FRAME_INTERPOLATION_LINEAR &&
blend_mode != PSX_MOD_FRAME_INTERPOLATION_MOTION_ADAPTIVE) {
std::fprintf(stderr,
"psxrecomp: mod rejected invalid frame-interpolation blend %u\n",
(unsigned)blend_mode);
return 0;
}
g_frame_interpolation_blend = (int)blend_mode;
std::fprintf(stdout, "psxrecomp: frame-interpolation blend = %s\n",
blend_mode == PSX_MOD_FRAME_INTERPOLATION_MOTION_ADAPTIVE
? "motion-adaptive clarity" : "linear crossfade");
return 1;
}
extern "C" int psx_mod_set_auto_skip_fmv(int enabled) {
if (enabled != 0 && enabled != 1) {
std::fprintf(stderr,
@@ -5616,7 +5636,8 @@ int main(int argc, char** argv) {
/* Latency knobs: env overrides win over config (for A/B measurement).
* PSX_LOW_LATENCY_INPUT=0/1 ; PSX_VSYNC=1(vsync)/0(immediate)/-1(adaptive);
* PSX_FRAME_INTERPOLATION=0/1; PSX_FRAME_INTERPOLATION_FPS=0|90+. */
* PSX_FRAME_INTERPOLATION=0/1; PSX_FRAME_INTERPOLATION_FPS=0|60+;
* PSX_FRAME_INTERPOLATION_BLEND=linear|adaptive. */
if (const char *e = std::getenv("PSX_LOW_LATENCY_INPUT")) g_low_latency_input = atoi(e) ? 1 : 0;
if (const char *e = std::getenv("PSX_VSYNC")) g_video_vsync = atoi(e);
if (const char *e = std::getenv("PSX_SMOOTH_60FPS"))
@@ -5625,7 +5646,18 @@ int main(int argc, char** argv) {
g_frame_interpolation = atoi(e) ? 1 : 0;
if (const char *e = std::getenv("PSX_FRAME_INTERPOLATION_FPS")) {
int fps = atoi(e);
if (fps == 0 || fps >= 90) g_frame_interpolation_fps = fps;
if (fps == 0 || fps >= 60) g_frame_interpolation_fps = fps;
}
if (const char *e = std::getenv("PSX_FRAME_INTERPOLATION_BLEND")) {
if (strcmp(e, "adaptive") == 0 || strcmp(e, "clarity") == 0 ||
strcmp(e, "1") == 0) {
g_frame_interpolation_blend_default =
PSX_MOD_FRAME_INTERPOLATION_MOTION_ADAPTIVE;
} else if (strcmp(e, "linear") == 0 || strcmp(e, "0") == 0) {
g_frame_interpolation_blend_default =
PSX_MOD_FRAME_INTERPOLATION_LINEAR;
}
g_frame_interpolation_blend = g_frame_interpolation_blend_default;
}
/* Apply writable-state isolation before game-options and launcher setup,
@@ -6186,6 +6218,7 @@ int main(int argc, char** argv) {
* leave its prior mode latched across a soft return to the launcher. */
g_mod_controller_mode_override[0] = -1;
g_mod_controller_mode_override[1] = -1;
g_frame_interpolation_blend = g_frame_interpolation_blend_default;
mod_runtime_activate_plugins();
if (g_mod_controller_mode_override[0] >= 0)
p1_mode = g_mod_controller_mode_override[0];
@@ -6580,7 +6613,8 @@ session_reboot:
* undersized and the wide readback overflows it. */
g_video_scale = gr_scale();
gl_renderer_set_interpolation(g_frame_interpolation, g_host_refresh_hz,
(double)g_frame_interpolation_fps);
(double)g_frame_interpolation_fps,
g_frame_interpolation_blend);
}
/* Vulkan backend: create the instance/device/swapchain on the
* SDL_WINDOW_VULKAN window. On failure, fall back to software (vkb_init