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 <noreply@anthropic.com>
This commit is contained in:
izzy2lost
2026-04-08 02:34:59 -04:00
co-authored by Claude Sonnet 4.6
parent d726dbc780
commit ee2fb9a94b
4 changed files with 72 additions and 17 deletions
-6
View File
@@ -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;
+53 -11
View File
@@ -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"
+1
View File
@@ -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];
+18
View File
@@ -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,