From ee2fb9a94b93bbe20fc27b02977b476bfb3406c7 Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Fri, 3 Apr 2026 01:52:38 -0400 Subject: [PATCH] Restore signed texture channel handling in fragment shaders Re-applies the fix from 0f623fe906 that was lost in the sloppy-stuff mega-commit. The RSIGNED/GSIGNED/BSIGNED/ASIGNED bits in TEXFILTER0 tell the hardware to interpret each sampled texture channel as a signed value (-1..1). Without this, bump-mapped and environment-mapped surfaces render with incorrect channel values, causing visual artifacts in games that use G8B8/R6G5B5 bump map textures with the signed filter flags set. Changes: - texture.h: add PgraphTextureSignedChannel enum and pgraph_get_texture_signed_component_mask_from_filter() - psh.h: add tex_signed[4] to PshState - psh.c: populate tex_signed from filter register, add apply_signed_texture_remap(), update BUMPENVMAP/BUMPENVMAP_LUM cases to check per-channel signed flags, apply remap after sampling - gl/texture.c: remove NV2A_UNIMPLEMENTED stubs for SIGNED flags (handled in shader now, same as VK path) Co-Authored-By: Claude Sonnet 4.6 --- hw/xbox/nv2a/pgraph/gl/texture.c | 6 --- hw/xbox/nv2a/pgraph/glsl/psh.c | 64 ++++++++++++++++++++++++++------ hw/xbox/nv2a/pgraph/glsl/psh.h | 1 + hw/xbox/nv2a/pgraph/texture.h | 18 +++++++++ 4 files changed, 72 insertions(+), 17 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/gl/texture.c b/hw/xbox/nv2a/pgraph/gl/texture.c index b71d04f121..c2ebc7f3f1 100644 --- a/hw/xbox/nv2a/pgraph/gl/texture.c +++ b/hw/xbox/nv2a/pgraph/gl/texture.c @@ -532,12 +532,6 @@ void pgraph_gl_bind_textures(NV2AState *d) 1 << (GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_TEXCTL0_0 + i*4), NV_PGRAPH_TEXCTL0_0_MAX_ANISOTROPY)); - /* Check for unsupported features */ - if (filter & NV_PGRAPH_TEXFILTER0_ASIGNED) NV2A_UNIMPLEMENTED("NV_PGRAPH_TEXFILTER0_ASIGNED"); - if (filter & NV_PGRAPH_TEXFILTER0_RSIGNED) NV2A_UNIMPLEMENTED("NV_PGRAPH_TEXFILTER0_RSIGNED"); - if (filter & NV_PGRAPH_TEXFILTER0_GSIGNED) NV2A_UNIMPLEMENTED("NV_PGRAPH_TEXFILTER0_GSIGNED"); - if (filter & NV_PGRAPH_TEXFILTER0_BSIGNED) NV2A_UNIMPLEMENTED("NV_PGRAPH_TEXFILTER0_BSIGNED"); - TextureShape state = pgraph_get_texture_shape(pg, i); hwaddr texture_vram_offset, palette_vram_offset; size_t length, palette_length; diff --git a/hw/xbox/nv2a/pgraph/glsl/psh.c b/hw/xbox/nv2a/pgraph/glsl/psh.c index ec18562849..b5079d597b 100644 --- a/hw/xbox/nv2a/pgraph/glsl/psh.c +++ b/hw/xbox/nv2a/pgraph/glsl/psh.c @@ -29,6 +29,7 @@ #include "qemu/osdep.h" #include "hw/xbox/nv2a/debug.h" #include "hw/xbox/nv2a/pgraph/pgraph.h" +#include "hw/xbox/nv2a/pgraph/texture.h" #include "psh.h" DEF_UNIFORM_INFO_ARR(PshUniform, PSH_UNIFORM_DECL_X) @@ -186,6 +187,8 @@ void pgraph_glsl_set_psh_state(PGRAPHState *pg, PshState *state) state->shadow_map[i] = f.depth; uint32_t filter = pgraph_reg_r(pg, NV_PGRAPH_TEXFILTER0 + i * 4); + state->tex_signed[i] = + pgraph_get_texture_signed_component_mask_from_filter(filter); unsigned int min_filter = GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIN); enum ConvolutionFilter kernel = CONVOLUTION_FILTER_DISABLED; /* FIXME: We do not distinguish between min and mag when @@ -793,6 +796,26 @@ static void define_colorkey_comparator(MString *preflight) // clang-format on } +static bool texture_channels_are_signed(const struct PixelShader *ps, int tex, + uint8_t mask) +{ + return (ps->state->tex_signed[tex] & mask) == mask; +} + +static void apply_signed_texture_remap(const struct PixelShader *ps, + MString *vars, int tex) +{ + uint8_t mask = ps->state->tex_signed[tex]; + if (mask & PGRAPH_TEXTURE_SIGNED_R) + mstring_append_fmt(vars, "t%d.r = sign3(t%d.r);\n", tex, tex); + if (mask & PGRAPH_TEXTURE_SIGNED_G) + mstring_append_fmt(vars, "t%d.g = sign3(t%d.g);\n", tex, tex); + if (mask & PGRAPH_TEXTURE_SIGNED_B) + mstring_append_fmt(vars, "t%d.b = sign3(t%d.b);\n", tex, tex); + if (mask & PGRAPH_TEXTURE_SIGNED_A) + mstring_append_fmt(vars, "t%d.a = sign3(t%d.a);\n", tex, tex); +} + static MString* psh_convert(struct PixelShader *ps) { MString *preflight = mstring_new(); @@ -1164,8 +1187,11 @@ static MString* psh_convert(struct PixelShader *ps) case PS_TEXTUREMODES_BUMPENVMAP: assert(i >= 1); - if (ps->state->snorm_tex[ps->input_tex[i]]) { - /* Input color channels already signed (FIXME: May not always want signed textures in this case) */ + if (ps->state->snorm_tex[ps->input_tex[i]] || + texture_channels_are_signed( + ps, ps->input_tex[i], + PGRAPH_TEXTURE_SIGNED_G | PGRAPH_TEXTURE_SIGNED_B)) { + /* Input color channels already signed */ mstring_append_fmt(vars, "vec2 dsdt%d = t%d.bg;\n", i, ps->input_tex[i]); } else { @@ -1190,15 +1216,27 @@ static MString* psh_convert(struct PixelShader *ps) case PS_TEXTUREMODES_BUMPENVMAP_LUM: assert(i >= 1); - if (ps->state->snorm_tex[ps->input_tex[i]]) { - /* Input color channels already signed (FIXME: May not always want signed textures in this case) */ - mstring_append_fmt(vars, "vec3 dsdtl%d = vec3(t%d.bg, sign3_to_0_to_1(t%d.r));\n", - i, ps->input_tex[i], ps->input_tex[i]); - } else { - /* Convert to signed (FIXME: loss of accuracy due to filtering/interpolation) */ - mstring_append_fmt(vars, "vec3 dsdtl%d = vec3(sign3(t%d.b), sign3(t%d.g), t%d.r);\n", - i, ps->input_tex[i], ps->input_tex[i], ps->input_tex[i]); - } + { + bool bg_signed = + ps->state->snorm_tex[ps->input_tex[i]] || + texture_channels_are_signed( + ps, ps->input_tex[i], + PGRAPH_TEXTURE_SIGNED_G | PGRAPH_TEXTURE_SIGNED_B); + bool r_signed = + ps->state->snorm_tex[ps->input_tex[i]] || + texture_channels_are_signed( + ps, ps->input_tex[i], PGRAPH_TEXTURE_SIGNED_R); + g_autofree gchar *b_expr = g_strdup_printf( + bg_signed ? "t%d.b" : "sign3(t%d.b)", ps->input_tex[i]); + g_autofree gchar *g_expr = g_strdup_printf( + bg_signed ? "t%d.g" : "sign3(t%d.g)", ps->input_tex[i]); + g_autofree gchar *r_expr = g_strdup_printf( + r_signed ? "sign3_to_0_to_1(t%d.r)" : "t%d.r", + ps->input_tex[i]); + mstring_append_fmt(vars, + "vec3 dsdtl%d = vec3(%s, %s, %s);\n", i, + b_expr, g_expr, r_expr); + } mstring_append_fmt(vars, "dsdtl%d.st = bumpMat[%d] * dsdtl%d.st;\n", i, i, i); @@ -1394,6 +1432,10 @@ static MString* psh_convert(struct PixelShader *ps) mstring_append(vars, "}\n"); } + if (!ps->state->shadow_map[i] && !ps->state->tex_x8y24[i]) { + apply_signed_texture_remap(ps, vars, i); + } + if (ps->state->rect_tex[i]) { mstring_append_fmt(preflight, "vec2 norm%d(vec2 coord) {\n" diff --git a/hw/xbox/nv2a/pgraph/glsl/psh.h b/hw/xbox/nv2a/pgraph/glsl/psh.h index ea3045ab32..e18cac86c5 100644 --- a/hw/xbox/nv2a/pgraph/glsl/psh.h +++ b/hw/xbox/nv2a/pgraph/glsl/psh.h @@ -47,6 +47,7 @@ typedef struct PshState { bool point_sprite; bool rect_tex[4]; bool snorm_tex[4]; + uint8_t tex_signed[4]; bool compare_mode[4][4]; bool alphakill[4]; int colorkey_mode[4]; diff --git a/hw/xbox/nv2a/pgraph/texture.h b/hw/xbox/nv2a/pgraph/texture.h index 343bcf0e3b..bbe933ec9f 100644 --- a/hw/xbox/nv2a/pgraph/texture.h +++ b/hw/xbox/nv2a/pgraph/texture.h @@ -50,8 +50,26 @@ typedef struct BasicColorFormatInfo { bool depth; } BasicColorFormatInfo; +enum PgraphTextureSignedChannel { + PGRAPH_TEXTURE_SIGNED_R = 1 << 0, + PGRAPH_TEXTURE_SIGNED_G = 1 << 1, + PGRAPH_TEXTURE_SIGNED_B = 1 << 2, + PGRAPH_TEXTURE_SIGNED_A = 1 << 3, +}; + extern const BasicColorFormatInfo kelvin_color_format_info_map[66]; +static inline uint8_t +pgraph_get_texture_signed_component_mask_from_filter(uint32_t filter) +{ + uint8_t mask = 0; + if (filter & NV_PGRAPH_TEXFILTER0_RSIGNED) mask |= PGRAPH_TEXTURE_SIGNED_R; + if (filter & NV_PGRAPH_TEXFILTER0_GSIGNED) mask |= PGRAPH_TEXTURE_SIGNED_G; + if (filter & NV_PGRAPH_TEXFILTER0_BSIGNED) mask |= PGRAPH_TEXTURE_SIGNED_B; + if (filter & NV_PGRAPH_TEXFILTER0_ASIGNED) mask |= PGRAPH_TEXTURE_SIGNED_A; + return mask; +} + uint8_t *pgraph_convert_texture_data(const TextureShape s, const uint8_t *data, const uint8_t *palette_data, unsigned int width, unsigned int height,