mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
opengl: add motion-adaptive interpolation blend
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
/*
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user