mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
presentation: add opt-in FMV filter setting
This commit is contained in:
@@ -555,6 +555,13 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
|
||||
else throw std::runtime_error(fmt::format(
|
||||
"[video] texture_filtering must be \"nearest\" or \"bilinear\": {}", mode));
|
||||
}
|
||||
if (video.contains("fmv_filter")) {
|
||||
const auto mode = toml::find<std::string>(video, "fmv_filter");
|
||||
if (!video_fmv_filter_parse(mode, &rt.video_fmv_filter))
|
||||
throw std::runtime_error(fmt::format(
|
||||
"[video] fmv_filter must be \"nearest\", \"bilinear\", "
|
||||
"\"sharp\" or \"bicubic\": {}", mode));
|
||||
}
|
||||
if (video.contains("renderer")) {
|
||||
const auto mode = toml::find<std::string>(video, "renderer");
|
||||
if (mode == "software") rt.video_renderer = 0;
|
||||
@@ -2215,6 +2222,10 @@ UserSettings load_user_settings(const fs::path& path) {
|
||||
if (m == "nearest") { s.texture_filter = 0; s.has_texture_filter = true; }
|
||||
else if (m == "bilinear") { s.texture_filter = 1; s.has_texture_filter = true; }
|
||||
});
|
||||
if (v.contains("fmv_filter")) try_get([&]{
|
||||
const auto m = toml::find<std::string>(v, "fmv_filter");
|
||||
if (video_fmv_filter_parse(m, &s.fmv_filter)) s.has_fmv_filter = true;
|
||||
});
|
||||
if (v.contains("geometry_correction")) try_get([&]{
|
||||
s.geometry_correction = toml::find<bool>(v, "geometry_correction");
|
||||
s.has_geometry_correction = true;
|
||||
@@ -2522,6 +2533,8 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) {
|
||||
f << "antialiasing = " << (s.antialiasing ? "true" : "false") << "\n";
|
||||
if (s.has_texture_filter)
|
||||
f << "texture_filtering = \"" << (s.texture_filter ? "bilinear" : "nearest") << "\"\n";
|
||||
if (s.has_fmv_filter)
|
||||
f << "fmv_filter = \"" << video_fmv_filter_name(s.fmv_filter) << "\"\n";
|
||||
if (s.has_geometry_correction)
|
||||
f << "geometry_correction = "
|
||||
<< (s.geometry_correction ? "true" : "false") << "\n";
|
||||
|
||||
@@ -69,6 +69,33 @@ inline constexpr int DEFAULT_VIDEO_RENDERER = VIDEO_RENDERER_OPENGL;
|
||||
inline constexpr int PAD_BIND_MAX = 1000 + (1 << 21);
|
||||
inline bool pad_bind_value_ok(long long n) { return n >= 0 && n < PAD_BIND_MAX; }
|
||||
|
||||
// FMV present reconstruction, ordered least to most smoothing. The default
|
||||
// remains nearest so existing games keep their current FMV presentation unless
|
||||
// a package or user setting opts into filtering.
|
||||
inline constexpr int VIDEO_FMV_FILTER_NEAREST = 0;
|
||||
inline constexpr int VIDEO_FMV_FILTER_BILINEAR = 1;
|
||||
inline constexpr int VIDEO_FMV_FILTER_SHARP = 2;
|
||||
inline constexpr int VIDEO_FMV_FILTER_BICUBIC = 3;
|
||||
inline constexpr int VIDEO_FMV_FILTER_COUNT = 4;
|
||||
inline constexpr int VIDEO_FMV_FILTER_DEFAULT = VIDEO_FMV_FILTER_NEAREST;
|
||||
|
||||
// Canonical settings.toml / game.toml spelling for each value, and its parse.
|
||||
inline const char* video_fmv_filter_name(int v) {
|
||||
switch (v) {
|
||||
case VIDEO_FMV_FILTER_NEAREST: return "nearest";
|
||||
case VIDEO_FMV_FILTER_BILINEAR: return "bilinear";
|
||||
case VIDEO_FMV_FILTER_SHARP: return "sharp";
|
||||
default: return "bicubic";
|
||||
}
|
||||
}
|
||||
inline bool video_fmv_filter_parse(const std::string& s, int* out) {
|
||||
if (s == "nearest") { *out = VIDEO_FMV_FILTER_NEAREST; return true; }
|
||||
else if (s == "bilinear") { *out = VIDEO_FMV_FILTER_BILINEAR; return true; }
|
||||
else if (s == "sharp") { *out = VIDEO_FMV_FILTER_SHARP; return true; }
|
||||
else if (s == "bicubic") { *out = VIDEO_FMV_FILTER_BICUBIC; return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
struct WidescreenSignedBoundSite {
|
||||
uint32_t address = 0;
|
||||
uint32_t expected = 0; // guarded LUI instruction
|
||||
@@ -359,6 +386,18 @@ struct RuntimeConfig {
|
||||
// (smooths textures and 2D backgrounds). Stored as 0/1.
|
||||
int video_texture_filter = 0;
|
||||
|
||||
// fmv_filter: how the 24-bit FMV present reconstructs its low-res source
|
||||
// (a 320x192-class movie blown up to the window). Only consulted when
|
||||
// antialiasing is on — AA off means nearest, as it does everywhere else.
|
||||
// 0 nearest point-sampled; hard pixels, uneven pixel widths at a
|
||||
// non-integer scale
|
||||
// 1 bilinear plain GL_LINEAR; smoothest, but blurs the whole texel
|
||||
// 2 sharp sharp-bilinear; flat texel interiors, ramp confined to a
|
||||
// one-output-pixel band at the boundary
|
||||
// 3 bicubic Catmull-Rom; removes most of the staircase while holding
|
||||
// overall sharpness at the nearest level
|
||||
int video_fmv_filter = VIDEO_FMV_FILTER_DEFAULT;
|
||||
|
||||
// renderer: "software" | "opengl" (default) | "vulkan". Selects the
|
||||
// rasterizer/present backend. The software rasterizer remains the explicit
|
||||
// fallback. Stored as VIDEO_RENDERER_*.
|
||||
@@ -1124,6 +1163,9 @@ struct UserSettings {
|
||||
bool has_window_width = false; int window_width = 1280; // -> 1280x960
|
||||
bool has_antialiasing = false; bool antialiasing = true;
|
||||
bool has_texture_filter = false; int texture_filter = 0; // 0=nearest,1=bilinear
|
||||
// FMV present reconstruction (VIDEO_FMV_FILTER_*). Only consulted when
|
||||
// antialiasing is on. See RuntimeConfig::video_fmv_filter.
|
||||
bool has_fmv_filter = false; int fmv_filter = VIDEO_FMV_FILTER_DEFAULT;
|
||||
// Sub-pixel vertex precision / perspective-correct UVs (see RuntimeConfig).
|
||||
// Both default off — the faithful floor — and are player-selectable.
|
||||
bool has_geometry_correction = false; bool geometry_correction = false;
|
||||
|
||||
@@ -87,6 +87,11 @@ void gl_renderer_restage_vram_after_savestate(void);
|
||||
* digests / GPUREAD authority) while the OpenGL hr FBO keeps settings-scale
|
||||
* SSAA for present-only. Never enables glReadPixels; CPU stays current. */
|
||||
void gl_renderer_set_cpu_auth_dual(int on);
|
||||
|
||||
/* FMV present reconstruction, settings.toml [video] fmv_filter. Takes the
|
||||
* config enum VIDEO_FMV_FILTER_* (0 nearest, 1 bilinear, 2 sharp, 3 bicubic).
|
||||
* Only consulted while video antialiasing is on; AA off is always nearest. */
|
||||
void gl_renderer_set_fmv_filter(int cfg_value);
|
||||
int gl_renderer_cpu_auth_dual(void);
|
||||
|
||||
/* Post-savestate freeze probe: skip/swap/dirty-mark counters (GL present path).
|
||||
|
||||
@@ -146,6 +146,7 @@ typedef void (APIENTRY *PFN_glUniform1i)(GLint, GLint);
|
||||
typedef void (APIENTRY *PFN_glUniform1f)(GLint, GLfloat);
|
||||
typedef void (APIENTRY *PFN_glUniform2i)(GLint, GLint, GLint);
|
||||
typedef void (APIENTRY *PFN_glUniform4i)(GLint, GLint, GLint, GLint, GLint);
|
||||
typedef void (APIENTRY *PFN_glUniform2f)(GLint, GLfloat, GLfloat);
|
||||
typedef void (APIENTRY *PFN_glUniform4f)(GLint, GLfloat, GLfloat, GLfloat, GLfloat);
|
||||
typedef void (APIENTRY *PFN_glBlendColor)(GLfloat, GLfloat, GLfloat, GLfloat);
|
||||
typedef void (APIENTRY *PFN_glBlendFuncSeparate)(GLenum, GLenum, GLenum, GLenum);
|
||||
@@ -211,6 +212,7 @@ static PFN_glUniform1i p_glUniform1i;
|
||||
static PFN_glUniform1f p_glUniform1f;
|
||||
static PFN_glUniform2i p_glUniform2i;
|
||||
static PFN_glUniform4i p_glUniform4i;
|
||||
static PFN_glUniform2f p_glUniform2f;
|
||||
static PFN_glUniform4f p_glUniform4f;
|
||||
static PFN_glBlendColor p_glBlendColor;
|
||||
static PFN_glBlendFuncSeparate p_glBlendFuncSeparate;
|
||||
@@ -268,6 +270,7 @@ static int load_modern_gl(void) {
|
||||
LOAD(p_glGetUniformLocation, "glGetUniformLocation"); LOAD(p_glUniform1i, "glUniform1i");
|
||||
LOAD(p_glUniform1f, "glUniform1f");
|
||||
LOAD(p_glUniform2i, "glUniform2i"); LOAD(p_glUniform4i, "glUniform4i");
|
||||
LOAD(p_glUniform2f, "glUniform2f");
|
||||
LOAD(p_glUniform4f, "glUniform4f");
|
||||
LOAD(p_glBlendColor, "glBlendColor");
|
||||
LOAD(p_glBlendFuncSeparate, "glBlendFuncSeparate");
|
||||
@@ -322,6 +325,8 @@ static int s_osd_tw = 0, s_osd_th = 0;
|
||||
static GLuint s_present_prog = 0, s_present_vao = 0;
|
||||
static void gl_swap_with_osd(void);
|
||||
static GLint s_present_uTex = -1, s_present_uUvRect = -1;
|
||||
static GLint s_present_uTexSize = -1, s_present_uSharpScale = -1;
|
||||
static GLint s_present_uSharp = -1;
|
||||
static GLuint s_interp_prog = 0, s_interp_tex[3];
|
||||
static GLsync s_interp_fence[3];
|
||||
static GLsync s_interp_draw_fence = NULL;
|
||||
@@ -859,10 +864,74 @@ static const char *PRESENT_VS =
|
||||
" v_uv = vec2(mix(u_uv_rect.x,u_uv_rect.z,p.x),\n"
|
||||
" mix(u_uv_rect.y,u_uv_rect.w,1.0-p.y));\n"
|
||||
" gl_Position = vec4(p*2.0-1.0,0.0,1.0); }\n";
|
||||
/* Present sampling. u_sharp==0 is the historical behaviour: sample straight at
|
||||
* v_uv, so the texture's own filter (NEAREST or LINEAR) decides everything.
|
||||
*
|
||||
* u_sharp==1 selects SHARP-BILINEAR, for upscaling a low-res source (FMV) to a
|
||||
* much larger window. Plain GL_LINEAR blends across the whole texel and turns a
|
||||
* 320x192 movie to mush; plain GL_NEAREST keeps it crisp but blocky and makes
|
||||
* the non-integer scale factor beat (some source pixels land 3 window pixels
|
||||
* wide, their neighbours 4). Sharp-bilinear keeps each texel flat across its
|
||||
* interior and confines the linear ramp to a ONE-OUTPUT-PIXEL-wide band at the
|
||||
* texel boundary: crisp like nearest, but without the uneven pixel widths.
|
||||
*
|
||||
* u_sharp_scale is output pixels per texel. At <=1 (downscale) the band covers
|
||||
* the whole texel and this degrades to plain bilinear, which is what you want
|
||||
* there. The result is clamped to u_uv_rect so the half-texel edge inset the
|
||||
* caller applied still holds — that inset is what keeps LINEAR from bleeding
|
||||
* the border texel into the image (the old reason FMV was pinned to NEAREST). */
|
||||
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";
|
||||
"uniform vec4 u_uv_rect;\n"
|
||||
"uniform vec2 u_tex_size;\n"
|
||||
"uniform vec2 u_sharp_scale;\n"
|
||||
"uniform int u_sharp;\n"
|
||||
/* Catmull-Rom bicubic via 9 bilinear taps. Sharper than plain bilinear at
|
||||
* the same smoothness, with mild overshoot that reads as edge definition.
|
||||
* The present texture holds exactly the source rect and wraps CLAMP_TO_EDGE,
|
||||
* so the +/-2 texel footprint clamps to real edge pixels, never garbage. */
|
||||
"vec4 bicubic(vec2 uv){\n"
|
||||
" vec2 sp = uv * u_tex_size;\n"
|
||||
" vec2 t1 = floor(sp - 0.5) + 0.5;\n"
|
||||
" vec2 f = sp - t1;\n"
|
||||
" vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));\n"
|
||||
" vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);\n"
|
||||
" vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));\n"
|
||||
" vec2 w3 = f * f * (-0.5 + 0.5 * f);\n"
|
||||
" vec2 w12 = w1 + w2;\n"
|
||||
" vec2 t0 = (t1 - 1.0) / u_tex_size;\n"
|
||||
" vec2 t3 = (t1 + 2.0) / u_tex_size;\n"
|
||||
" vec2 t12 = (t1 + w2 / w12) / u_tex_size;\n"
|
||||
" vec4 r = vec4(0.0);\n"
|
||||
" r += texture(u_tex, vec2(t0.x , t0.y )) * (w0.x * w0.y );\n"
|
||||
" r += texture(u_tex, vec2(t12.x, t0.y )) * (w12.x * w0.y );\n"
|
||||
" r += texture(u_tex, vec2(t3.x , t0.y )) * (w3.x * w0.y );\n"
|
||||
" r += texture(u_tex, vec2(t0.x , t12.y)) * (w0.x * w12.y);\n"
|
||||
" r += texture(u_tex, vec2(t12.x, t12.y)) * (w12.x * w12.y);\n"
|
||||
" r += texture(u_tex, vec2(t3.x , t12.y)) * (w3.x * w12.y);\n"
|
||||
" r += texture(u_tex, vec2(t0.x , t3.y )) * (w0.x * w3.y );\n"
|
||||
" r += texture(u_tex, vec2(t12.x, t3.y )) * (w12.x * w3.y );\n"
|
||||
" r += texture(u_tex, vec2(t3.x , t3.y )) * (w3.x * w3.y );\n"
|
||||
" return r;\n"
|
||||
"}\n"
|
||||
"void main(){\n"
|
||||
" vec2 uv = v_uv;\n"
|
||||
" if (u_sharp == 2) { frag = bicubic(uv); return; }\n"
|
||||
" if (u_sharp == 1) {\n"
|
||||
" vec2 scale = max(u_sharp_scale, vec2(1.0));\n"
|
||||
" vec2 texel = uv * u_tex_size;\n"
|
||||
" vec2 tf = floor(texel);\n"
|
||||
" vec2 cd = (texel - tf) - 0.5;\n"
|
||||
" vec2 band = 0.5 - 0.5 / scale;\n"
|
||||
" vec2 f = (cd - clamp(cd, -band, band)) * scale + 0.5;\n"
|
||||
" uv = (tf + f) / u_tex_size;\n"
|
||||
" vec2 lo = min(u_uv_rect.xy, u_uv_rect.zw);\n"
|
||||
" vec2 hi = max(u_uv_rect.xy, u_uv_rect.zw);\n"
|
||||
" uv = clamp(uv, lo, hi);\n"
|
||||
" }\n"
|
||||
" frag = texture(u_tex, uv);\n"
|
||||
"}\n";
|
||||
static const char *INTERP_FS =
|
||||
"#version 330\n"
|
||||
"in vec2 v_uv; uniform sampler2D u_prev; uniform sampler2D u_curr;\n"
|
||||
@@ -2484,6 +2553,36 @@ static void upload_present_tex(const uint32_t *pixels, int w, int h, int linear)
|
||||
} else {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
static int s_fmv_filter_cfg = 0; /* VIDEO_FMV_FILTER_NEAREST */
|
||||
|
||||
void gl_renderer_set_fmv_filter(int cfg_value) {
|
||||
if (cfg_value >= 0 && cfg_value <= 3) s_fmv_filter_cfg = cfg_value;
|
||||
}
|
||||
|
||||
static int fmv_filter_mode(void) {
|
||||
return s_fmv_filter_cfg - 1;
|
||||
}
|
||||
|
||||
/* Select the present-program sampling mode. s_present_prog is shared by the CPU
|
||||
* present and both VRAM/FBO quad paths, and program uniforms persist, so every
|
||||
* user states its choice rather than inheriting the last one's. sharp=0 keeps
|
||||
* the historical straight sample. */
|
||||
static void present_set_sharp(int mode, int tex_w, int tex_h,
|
||||
int out_w, int out_h) {
|
||||
if (s_present_uSharp < 0) return;
|
||||
if (mode <= 0 || tex_w <= 0 || tex_h <= 0) {
|
||||
p_glUniform1i(s_present_uSharp, 0);
|
||||
return;
|
||||
}
|
||||
p_glUniform1i(s_present_uSharp, mode);
|
||||
if (s_present_uTexSize >= 0)
|
||||
p_glUniform2f(s_present_uTexSize, (float)tex_w, (float)tex_h);
|
||||
if (s_present_uSharpScale >= 0)
|
||||
p_glUniform2f(s_present_uSharpScale,
|
||||
(float)out_w / (float)tex_w,
|
||||
(float)out_h / (float)tex_h);
|
||||
}
|
||||
|
||||
/* Display aspect for the present letterbox. Default 4:3 (native). When a wide
|
||||
@@ -2750,6 +2849,12 @@ int gl_renderer_init_context(SDL_Window *win) {
|
||||
p_glGenVertexArrays(1, &s_present_vao);
|
||||
s_present_uTex = p_glGetUniformLocation(s_present_prog, "u_tex");
|
||||
s_present_uUvRect = p_glGetUniformLocation(s_present_prog, "u_uv_rect");
|
||||
s_present_uTexSize =
|
||||
p_glGetUniformLocation(s_present_prog, "u_tex_size");
|
||||
s_present_uSharpScale =
|
||||
p_glGetUniformLocation(s_present_prog, "u_sharp_scale");
|
||||
s_present_uSharp =
|
||||
p_glGetUniformLocation(s_present_prog, "u_sharp");
|
||||
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");
|
||||
@@ -2877,10 +2982,30 @@ void gl_renderer_present(const uint32_t *pixels, int src_w, int src_h, int linea
|
||||
lw = (lw * content_w) / src_w;
|
||||
if (lw < 1) lw = 1;
|
||||
}
|
||||
glViewport(lx, ly, lw, lh);
|
||||
/* This is the low-res source path (24-bit FMV, and the forced-CPU present
|
||||
* diagnostic): a 320x192-class image blown up to fill the window, so how it
|
||||
* is reconstructed is very visible. `linear` (the video AA setting) allows
|
||||
* filtered reconstruction when [video] fmv_filter opts into it:
|
||||
*
|
||||
* nearest hard pixels, uneven pixel widths at non-integer scale
|
||||
* bilinear plain GL_LINEAR — smoothest, but blurs the whole texel
|
||||
* sharp sharp-bilinear: flat texel interiors, ramp confined to a
|
||||
* one-output-pixel band at the boundary
|
||||
* bicubic Catmull-Rom
|
||||
*
|
||||
* Measured on this intro at 1280x960 (fraction of adjacent pixel pairs
|
||||
* differing by >=24 luma = visible staircase, vs mean |dx| = overall
|
||||
* sharpness): nearest 1.00%/1.028, sharp 0.87%/1.008, bicubic 0.34%/1.038,
|
||||
* bilinear 0.14%/0.930. Bicubic removes two thirds of the staircase while
|
||||
* holding gradient at the nearest level; bilinear removes the most but
|
||||
* costs 10% of it, which reads as blur. Still a taste call, hence the knob. */
|
||||
int filt_mode = linear ? fmv_filter_mode()
|
||||
: -1; /* AA off: nearest, no shader work */
|
||||
glViewport(lx, ly, lw, lh);
|
||||
p_glActiveTexture(PSXGL_TEXTURE0);
|
||||
upload_present_tex(pixels, src_w, src_h, linear);
|
||||
upload_present_tex(pixels, src_w, src_h, filt_mode >= 0 ? 1 : 0);
|
||||
p_glUseProgram(s_present_prog); p_glUniform1i(s_present_uTex, 0);
|
||||
present_set_sharp(filt_mode, src_w, src_h, lw, lh);
|
||||
if (crop) {
|
||||
/* Cropped present keeps left-aligned content; still inset so linear
|
||||
* AA does not blend the cut column with undefined border texels. */
|
||||
@@ -2899,7 +3024,7 @@ void gl_renderer_present(const uint32_t *pixels, int src_w, int src_h, int linea
|
||||
}
|
||||
p_glBindVertexArray(s_present_vao); glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
p_glBindVertexArray(0); p_glUseProgram(0);
|
||||
pres_record(GL_PRES_CPU, 0, 0, src_w, src_h, lx, ly, lw, lh);
|
||||
pres_record(GL_PRES_CPU, 0, 0, src_w, src_h, lx, ly, lw, lh);
|
||||
hold_capture_drawable();
|
||||
latency_ring_mark(LAT_SWAP_BEGIN);
|
||||
gl_swap_with_osd();
|
||||
@@ -3908,6 +4033,7 @@ static void gl_draw_osd_image(const uint32_t *px, int ow, int oh,
|
||||
glViewport(vx, wh - vy - dh, dw, dh);
|
||||
p_glUseProgram(s_present_prog);
|
||||
p_glUniform1i(s_present_uTex, 0);
|
||||
present_set_sharp(0, 0, 0, 0, 0); /* OSD is authored at output res */
|
||||
/* Host OSD bitmaps are top-down (row 0 = top), same as guest CPU
|
||||
* present with v_flip=1: uv (0,0)-(1,1). (0,1)-(1,0) was the hold-last
|
||||
* cancel for already-oriented captures and made toasts upside-down. */
|
||||
@@ -3976,6 +4102,9 @@ static void present_target_quad(GLuint tex, int tex_w, int tex_h,
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST);
|
||||
p_glUseProgram(s_present_prog);
|
||||
p_glUniform1i(s_present_uTex, 0);
|
||||
/* The rasterized path already renders at the internal scale, so it has no
|
||||
* low-res source to reconstruct — keep the plain sample. */
|
||||
present_set_sharp(0, 0, 0, 0, 0);
|
||||
/* Half-texel inset: with GL_LINEAR, corner-mapped UVs make the outermost
|
||||
* dest pixels blend the border texel with VRAM outside the content rect
|
||||
* (visible edge stripe with AA on). Center-mapped UVs keep edge samples
|
||||
|
||||
+41
-3
@@ -1106,6 +1106,23 @@ extern "C" EMSCRIPTEN_KEEPALIVE void psx_web_set_smooth_60fps(int enabled) {
|
||||
/* [video] options, resolved from the game config (defaults: native + AA). */
|
||||
static int g_video_scale = 1; /* internal-resolution SSAA factor */
|
||||
static bool g_video_aa = true; /* linear present filtering */
|
||||
/* FMV present reconstruction (VIDEO_FMV_FILTER_*), pushed to the GL renderer
|
||||
* once the config is resolved. Only consulted while g_video_aa is on. */
|
||||
static int g_video_fmv_filter = PSXRecompV4::VIDEO_FMV_FILTER_DEFAULT;
|
||||
|
||||
/* recomp-ui stores this 1-based so a zero-initialized (older) host reads as
|
||||
* "unset" rather than pinning nearest; the config enum is 0-based. Convert at
|
||||
* the boundary, and treat anything out of range as the default. */
|
||||
static inline int launcher_fmv_filter_to_cfg(int ls_value) {
|
||||
if (ls_value < 1 || ls_value > PSXRecompV4::VIDEO_FMV_FILTER_COUNT)
|
||||
return PSXRecompV4::VIDEO_FMV_FILTER_DEFAULT;
|
||||
return ls_value - 1;
|
||||
}
|
||||
static inline int cfg_fmv_filter_to_launcher(int cfg_value) {
|
||||
if (cfg_value < 0 || cfg_value >= PSXRecompV4::VIDEO_FMV_FILTER_COUNT)
|
||||
cfg_value = PSXRecompV4::VIDEO_FMV_FILTER_DEFAULT;
|
||||
return cfg_value + 1;
|
||||
}
|
||||
static int g_video_texfilter = 0; /* 0=nearest, 1=bilinear */
|
||||
/* Sub-pixel vertex precision + perspective-correct UVs (PGXP-style). Visual
|
||||
* only: the PS1-visible GTE SXY FIFO stays integer, so guest-side culling and
|
||||
@@ -6849,10 +6866,18 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
|
||||
/* OpenGL present: upload the active display rect and draw a full-screen
|
||||
* quad. Either SwapWindow vsync OR the wall-clock pacer owns timing,
|
||||
* never both. 24-bit (FMV) frames pin to native 4:3. */
|
||||
/* FMV: nearest present — linear filtering fringes the right edge of
|
||||
* low-res 24-bit scanouts into adjacent (often garbage) texels. */
|
||||
/* FMV follows the same AA setting as everything else. It used to be
|
||||
* pinned to nearest because plain GL_LINEAR fringed the right edge of
|
||||
* low-res 24-bit scanouts into adjacent (often garbage) texels — but
|
||||
* gl_renderer_present now half-texel-insets its UV rect (and crops the
|
||||
* depth24 margin via content_w), which is exactly that bleed, and it
|
||||
* reconstructs a filtered low-res source when [video] fmv_filter opts
|
||||
* into it instead of smearing whole texels. Measured: the
|
||||
* right-edge jump the old comment describes is 0 with filtering on.
|
||||
* Set video AA off to get nearest back. */
|
||||
gl_renderer_set_fmv_filter(g_video_fmv_filter);
|
||||
gl_renderer_present(sdl_pixel_buf, src_w, src_h,
|
||||
(g_video_aa && !depth24_frame) ? 1 : 0,
|
||||
g_video_aa ? 1 : 0,
|
||||
pin_43 ? 1 : 0, 0 /* full width */);
|
||||
netplay_note_present();
|
||||
} else {
|
||||
@@ -10661,6 +10686,7 @@ int main(int argc, char** argv) {
|
||||
g_video_scale = gc.runtime.video_supersampling;
|
||||
g_video_aa = gc.runtime.video_antialiasing;
|
||||
g_video_texfilter = gc.runtime.video_texture_filter;
|
||||
g_video_fmv_filter = gc.runtime.video_fmv_filter;
|
||||
g_video_geometry_correction =
|
||||
gc.runtime.video_geometry_correction ? 1 : 0;
|
||||
g_video_perspective_texturing =
|
||||
@@ -11039,6 +11065,7 @@ int main(int argc, char** argv) {
|
||||
if (us.has_window_width && us.window_width > 0) g_video_win_w_explicit = true;
|
||||
if (us.has_antialiasing) g_video_aa = us.antialiasing;
|
||||
if (us.has_texture_filter) g_video_texfilter = us.texture_filter;
|
||||
if (us.has_fmv_filter) g_video_fmv_filter = us.fmv_filter;
|
||||
if (us.has_geometry_correction)
|
||||
g_video_geometry_correction = us.geometry_correction ? 1 : 0;
|
||||
if (us.has_perspective_texturing)
|
||||
@@ -11482,6 +11509,7 @@ int main(int argc, char** argv) {
|
||||
seed.supersampling = g_video_scale; seed.has_supersampling = true;
|
||||
seed.antialiasing = g_video_aa; seed.has_antialiasing = true;
|
||||
seed.texture_filter = g_video_texfilter; seed.has_texture_filter = true;
|
||||
seed.fmv_filter = g_video_fmv_filter; seed.has_fmv_filter = true;
|
||||
/* Seeded (and marked present) so a launcher save round-trips the
|
||||
* player's hand-edited value instead of dropping the key. */
|
||||
seed.geometry_correction = (g_video_geometry_correction != 0);
|
||||
@@ -11669,6 +11697,7 @@ int main(int argc, char** argv) {
|
||||
ls.supersampling = seed.supersampling;
|
||||
ls.antialiasing = seed.antialiasing ? 1 : 0;
|
||||
ls.texture_filter = seed.texture_filter;
|
||||
ls.fmv_filter = cfg_fmv_filter_to_launcher(seed.fmv_filter);
|
||||
ls.geometry_correction = seed.geometry_correction ? 1 : 0;
|
||||
ls.perspective_texturing = seed.perspective_texturing ? 1 : 0;
|
||||
ls.screen_kind = seed.screen_kind;
|
||||
@@ -11861,6 +11890,8 @@ int main(int argc, char** argv) {
|
||||
* is the legacy fallback field for consoles without the cap and is
|
||||
* left unused here. */
|
||||
seed.texture_filter = ls.texture_filter ? 1 : 0; seed.has_texture_filter = true;
|
||||
seed.fmv_filter = launcher_fmv_filter_to_cfg(ls.fmv_filter);
|
||||
seed.has_fmv_filter = true;
|
||||
{
|
||||
const int n = std::min(PSX_MAX_PLAYERS, RECOMP_LAUNCHER_MAX_PLAYERS);
|
||||
const int un = std::min(n, PSXRecompV4::UserSettings::kMaxControllerPlayers);
|
||||
@@ -11906,6 +11937,8 @@ int main(int argc, char** argv) {
|
||||
seed.has_geometry_correction = true;
|
||||
seed.perspective_texturing = ls.perspective_texturing != 0;
|
||||
seed.has_perspective_texturing = true;
|
||||
seed.fmv_filter = launcher_fmv_filter_to_cfg(ls.fmv_filter);
|
||||
seed.has_fmv_filter = true;
|
||||
seed.screen_kind = ls.screen_kind; seed.has_screen_kind = true;
|
||||
seed.frame_interpolation = ls.frame_interp != 0; seed.has_frame_interpolation = true;
|
||||
seed.frame_interpolation_fps = ls.frame_interp_fps; seed.has_frame_interpolation_fps = true;
|
||||
@@ -12108,6 +12141,7 @@ int main(int argc, char** argv) {
|
||||
g_video_scale = seed.supersampling;
|
||||
g_video_aa = seed.antialiasing;
|
||||
g_video_texfilter = seed.texture_filter;
|
||||
g_video_fmv_filter = seed.fmv_filter;
|
||||
g_video_geometry_correction = seed.geometry_correction ? 1 : 0;
|
||||
g_video_perspective_texturing = seed.perspective_texturing ? 1 : 0;
|
||||
g_video_screen = seed.screen_kind;
|
||||
@@ -13538,6 +13572,7 @@ soft_return_lobby:
|
||||
ls.supersampling = g_video_scale;
|
||||
ls.antialiasing = g_video_aa ? 1 : 0;
|
||||
ls.texture_filter = g_video_texfilter;
|
||||
ls.fmv_filter = cfg_fmv_filter_to_launcher(g_video_fmv_filter);
|
||||
ls.geometry_correction = g_video_geometry_correction ? 1 : 0;
|
||||
ls.perspective_texturing = g_video_perspective_texturing ? 1 : 0;
|
||||
ls.screen_kind = g_video_screen;
|
||||
@@ -13790,6 +13825,8 @@ soft_return_lobby:
|
||||
us.has_antialiasing = true;
|
||||
us.texture_filter = ls.texture_filter;
|
||||
us.has_texture_filter = true;
|
||||
us.fmv_filter = launcher_fmv_filter_to_cfg(ls.fmv_filter);
|
||||
us.has_fmv_filter = true;
|
||||
us.geometry_correction = ls.geometry_correction != 0;
|
||||
us.has_geometry_correction = true;
|
||||
us.perspective_texturing = ls.perspective_texturing != 0;
|
||||
@@ -13845,6 +13882,7 @@ soft_return_lobby:
|
||||
g_video_scale = ls.supersampling;
|
||||
g_video_aa = ls.antialiasing;
|
||||
g_video_texfilter = ls.texture_filter;
|
||||
g_video_fmv_filter = launcher_fmv_filter_to_cfg(ls.fmv_filter);
|
||||
g_video_geometry_correction = ls.geometry_correction ? 1 : 0;
|
||||
g_video_perspective_texturing = ls.perspective_texturing ? 1 : 0;
|
||||
g_video_screen = ls.screen_kind;
|
||||
|
||||
Reference in New Issue
Block a user