From b8ada7962a30f4ec96d73d2a848d2cbaff51f86f Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Thu, 18 Sep 2025 11:11:52 -0700 Subject: [PATCH 1/4] nv2a: Implement texture LOD bias Fixes #767 Tests: https://github.com/abaire/nxdk_pgraph_tests/blob/2aa0cb89543f3853a3830ba15a03c9f7dad58d69/src/tests/texture_lod_bias_tests.h#L1 HW results: https://abaire.github.io/nxdk_pgraph_tests_golden_results/results/Texture_LOD_Bias/index.html --- hw/xbox/nv2a/pgraph/gl/renderer.h | 1 + hw/xbox/nv2a/pgraph/gl/texture.c | 15 +++++++++++++++ hw/xbox/nv2a/pgraph/vk/texture.c | 12 +++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index 28277fcdf53..153fec4b490 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -80,6 +80,7 @@ typedef struct TextureBinding { bool border_color_set; GLenum gl_target; GLuint gl_texture; + uint32_t lod_bias; } TextureBinding; typedef struct ShaderModuleCacheKey { diff --git a/hw/xbox/nv2a/pgraph/gl/texture.c b/hw/xbox/nv2a/pgraph/gl/texture.c index 7622bf43691..b2268dface7 100644 --- a/hw/xbox/nv2a/pgraph/gl/texture.c +++ b/hw/xbox/nv2a/pgraph/gl/texture.c @@ -107,6 +107,15 @@ static bool check_texture_possibly_dirty(NV2AState *d, return possibly_dirty; } +static inline float convert_lod_bias(uint32_t lod_bias) +{ + int sign_extended_bias = lod_bias; + if (lod_bias & (1 << 12)) { + sign_extended_bias |= ~NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS; + } + return (float)sign_extended_bias / 256.f; +} + static void apply_texture_parameters(TextureBinding *binding, const BasicColorFormatInfo *f, unsigned int dimensionality, @@ -120,6 +129,8 @@ static void apply_texture_parameters(TextureBinding *binding, unsigned int addru = GET_MASK(address, NV_PGRAPH_TEXADDRESS0_ADDRU); unsigned int addrv = GET_MASK(address, NV_PGRAPH_TEXADDRESS0_ADDRV); unsigned int addrp = GET_MASK(address, NV_PGRAPH_TEXADDRESS0_ADDRP); + unsigned int lod_bias = + GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS); if (f->linear) { /* somtimes games try to set mipmap min filters on linear textures. @@ -146,6 +157,10 @@ static void apply_texture_parameters(TextureBinding *binding, pgraph_texture_mag_filter_gl_map[mag_filter]); binding->mag_filter = mag_filter; } + if (lod_bias != binding->lod_bias) { + binding->lod_bias = lod_bias; + glTexParameterf(binding->gl_target, GL_TEXTURE_LOD_BIAS, convert_lod_bias(lod_bias)); + } /* Texture wrapping */ assert(addru < ARRAY_SIZE(pgraph_texture_addr_gl_map)); diff --git a/hw/xbox/nv2a/pgraph/vk/texture.c b/hw/xbox/nv2a/pgraph/vk/texture.c index efaefaafab3..5ab8c4f27f8 100644 --- a/hw/xbox/nv2a/pgraph/vk/texture.c +++ b/hw/xbox/nv2a/pgraph/vk/texture.c @@ -51,6 +51,15 @@ static VkSamplerAddressMode lookup_texture_address_mode(int idx) return pgraph_texture_addr_vk_map[idx]; } +static inline float convert_lod_bias(uint32_t lod_bias) +{ + int sign_extended_bias = lod_bias; + if (lod_bias & (1 << 12)) { + sign_extended_bias |= ~NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS; + } + return (float)sign_extended_bias / 256.f; +} + // FIXME: Move to common // FIXME: We can shrink the size of this structure // FIXME: Use simple allocator @@ -1356,7 +1365,8 @@ static void create_texture(PGRAPHState *pg, int texture_idx) VK_SAMPLER_MIPMAP_MODE_LINEAR, .minLod = mipmap_en ? MIN(state.min_mipmap_level, state.levels - 1) : 0.0, .maxLod = mipmap_en ? MIN(state.max_mipmap_level, state.levels - 1) : 0.0, - .mipLodBias = 0.0, + .mipLodBias = convert_lod_bias( + GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS)), .pNext = sampler_next_struct, }; From 9c9f43ccd0e25061ffee3af4fad1429a799572d9 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Fri, 19 Sep 2025 15:45:51 -0700 Subject: [PATCH 2/4] SQUASHME: CR updates --- hw/xbox/nv2a/pgraph/gl/renderer.h | 2 +- hw/xbox/nv2a/pgraph/gl/texture.c | 14 +++----------- hw/xbox/nv2a/pgraph/texture.h | 9 +++++++++ hw/xbox/nv2a/pgraph/vk/texture.c | 18 +++++++----------- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index 153fec4b490..1e55ce80ca2 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -73,6 +73,7 @@ typedef struct TextureBinding { unsigned int scale; unsigned int min_filter; unsigned int mag_filter; + uint32_t lod_bias; unsigned int addru; unsigned int addrv; unsigned int addrp; @@ -80,7 +81,6 @@ typedef struct TextureBinding { bool border_color_set; GLenum gl_target; GLuint gl_texture; - uint32_t lod_bias; } TextureBinding; typedef struct ShaderModuleCacheKey { diff --git a/hw/xbox/nv2a/pgraph/gl/texture.c b/hw/xbox/nv2a/pgraph/gl/texture.c index b2268dface7..8be23fcdf4e 100644 --- a/hw/xbox/nv2a/pgraph/gl/texture.c +++ b/hw/xbox/nv2a/pgraph/gl/texture.c @@ -107,15 +107,6 @@ static bool check_texture_possibly_dirty(NV2AState *d, return possibly_dirty; } -static inline float convert_lod_bias(uint32_t lod_bias) -{ - int sign_extended_bias = lod_bias; - if (lod_bias & (1 << 12)) { - sign_extended_bias |= ~NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS; - } - return (float)sign_extended_bias / 256.f; -} - static void apply_texture_parameters(TextureBinding *binding, const BasicColorFormatInfo *f, unsigned int dimensionality, @@ -126,11 +117,11 @@ static void apply_texture_parameters(TextureBinding *binding, { unsigned int min_filter = GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIN); unsigned int mag_filter = GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MAG); + unsigned int lod_bias = + GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS); unsigned int addru = GET_MASK(address, NV_PGRAPH_TEXADDRESS0_ADDRU); unsigned int addrv = GET_MASK(address, NV_PGRAPH_TEXADDRESS0_ADDRV); unsigned int addrp = GET_MASK(address, NV_PGRAPH_TEXADDRESS0_ADDRP); - unsigned int lod_bias = - GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS); if (f->linear) { /* somtimes games try to set mipmap min filters on linear textures. @@ -742,6 +733,7 @@ static TextureBinding* generate_texture(const TextureShape s, ret->data_hash = 0; ret->min_filter = 0xFFFFFFFF; ret->mag_filter = 0xFFFFFFFF; + ret->lod_bias = 0xFFFFFFFF; ret->addru = 0xFFFFFFFF; ret->addrv = 0xFFFFFFFF; ret->addrp = 0xFFFFFFFF; diff --git a/hw/xbox/nv2a/pgraph/texture.h b/hw/xbox/nv2a/pgraph/texture.h index 4c9818ca3cc..675c992a271 100644 --- a/hw/xbox/nv2a/pgraph/texture.h +++ b/hw/xbox/nv2a/pgraph/texture.h @@ -64,4 +64,13 @@ hwaddr pgraph_get_texture_palette_phys_addr_length(PGRAPHState *pg, int texture_ TextureShape pgraph_get_texture_shape(PGRAPHState *pg, int texture_idx); size_t pgraph_get_texture_length(PGRAPHState *pg, TextureShape *shape); +static inline float convert_lod_bias(uint32_t lod_bias) +{ + int sign_extended_bias = lod_bias; + if (lod_bias & (1 << 12)) { + sign_extended_bias |= ~NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS; + } + return (float)sign_extended_bias / 256.f; +} + #endif diff --git a/hw/xbox/nv2a/pgraph/vk/texture.c b/hw/xbox/nv2a/pgraph/vk/texture.c index 5ab8c4f27f8..c35a5fee91f 100644 --- a/hw/xbox/nv2a/pgraph/vk/texture.c +++ b/hw/xbox/nv2a/pgraph/vk/texture.c @@ -51,15 +51,6 @@ static VkSamplerAddressMode lookup_texture_address_mode(int idx) return pgraph_texture_addr_vk_map[idx]; } -static inline float convert_lod_bias(uint32_t lod_bias) -{ - int sign_extended_bias = lod_bias; - if (lod_bias & (1 << 12)) { - sign_extended_bias |= ~NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS; - } - return (float)sign_extended_bias / 256.f; -} - // FIXME: Move to common // FIXME: We can shrink the size of this structure // FIXME: Use simple allocator @@ -1345,6 +1336,12 @@ static void create_texture(PGRAPHState *pg, int texture_idx) min_filter == NV_PGRAPH_TEXFILTER0_MIN_BOX_NEARESTLOD || min_filter == NV_PGRAPH_TEXFILTER0_MIN_TENT_NEARESTLOD; + float lod_bias = convert_lod_bias( + GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS)); + if (lod_bias > r->device_props.limits.maxSamplerLodBias) { + lod_bias = r->device_props.limits.maxSamplerLodBias; + } + VkSamplerCreateInfo sampler_create_info = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = vk_mag_filter, @@ -1365,8 +1362,7 @@ static void create_texture(PGRAPHState *pg, int texture_idx) VK_SAMPLER_MIPMAP_MODE_LINEAR, .minLod = mipmap_en ? MIN(state.min_mipmap_level, state.levels - 1) : 0.0, .maxLod = mipmap_en ? MIN(state.max_mipmap_level, state.levels - 1) : 0.0, - .mipLodBias = convert_lod_bias( - GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS)), + .mipLodBias = lod_bias, .pNext = sampler_next_struct, }; From ebbb5207a08d1f215ecb8f3033ade475c46d9451 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Fri, 19 Sep 2025 15:57:56 -0700 Subject: [PATCH 3/4] SQUASHME: Renamed convert_lod_bias for namespacing purposes and clarity --- hw/xbox/nv2a/pgraph/gl/texture.c | 3 ++- hw/xbox/nv2a/pgraph/texture.h | 2 +- hw/xbox/nv2a/pgraph/vk/texture.c | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/hw/xbox/nv2a/pgraph/gl/texture.c b/hw/xbox/nv2a/pgraph/gl/texture.c index 8be23fcdf4e..ead8af51345 100644 --- a/hw/xbox/nv2a/pgraph/gl/texture.c +++ b/hw/xbox/nv2a/pgraph/gl/texture.c @@ -150,7 +150,8 @@ static void apply_texture_parameters(TextureBinding *binding, } if (lod_bias != binding->lod_bias) { binding->lod_bias = lod_bias; - glTexParameterf(binding->gl_target, GL_TEXTURE_LOD_BIAS, convert_lod_bias(lod_bias)); + glTexParameterf(binding->gl_target, GL_TEXTURE_LOD_BIAS, + pgraph_convert_lod_bias_to_float(lod_bias)); } /* Texture wrapping */ diff --git a/hw/xbox/nv2a/pgraph/texture.h b/hw/xbox/nv2a/pgraph/texture.h index 675c992a271..343bcf0e3b2 100644 --- a/hw/xbox/nv2a/pgraph/texture.h +++ b/hw/xbox/nv2a/pgraph/texture.h @@ -64,7 +64,7 @@ hwaddr pgraph_get_texture_palette_phys_addr_length(PGRAPHState *pg, int texture_ TextureShape pgraph_get_texture_shape(PGRAPHState *pg, int texture_idx); size_t pgraph_get_texture_length(PGRAPHState *pg, TextureShape *shape); -static inline float convert_lod_bias(uint32_t lod_bias) +static inline float pgraph_convert_lod_bias_to_float(uint32_t lod_bias) { int sign_extended_bias = lod_bias; if (lod_bias & (1 << 12)) { diff --git a/hw/xbox/nv2a/pgraph/vk/texture.c b/hw/xbox/nv2a/pgraph/vk/texture.c index c35a5fee91f..ff3a54605ae 100644 --- a/hw/xbox/nv2a/pgraph/vk/texture.c +++ b/hw/xbox/nv2a/pgraph/vk/texture.c @@ -1336,7 +1336,7 @@ static void create_texture(PGRAPHState *pg, int texture_idx) min_filter == NV_PGRAPH_TEXFILTER0_MIN_BOX_NEARESTLOD || min_filter == NV_PGRAPH_TEXFILTER0_MIN_TENT_NEARESTLOD; - float lod_bias = convert_lod_bias( + float lod_bias = pgraph_convert_lod_bias_to_float( GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS)); if (lod_bias > r->device_props.limits.maxSamplerLodBias) { lod_bias = r->device_props.limits.maxSamplerLodBias; From ec5ed5efbac1ecb865dff005c86f82d8e2a89a45 Mon Sep 17 00:00:00 2001 From: Erik Abair Date: Fri, 19 Sep 2025 16:04:52 -0700 Subject: [PATCH 4/4] SQUASHME: Clamp negative LOD bias as well --- hw/xbox/nv2a/pgraph/vk/texture.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/xbox/nv2a/pgraph/vk/texture.c b/hw/xbox/nv2a/pgraph/vk/texture.c index ff3a54605ae..551ea7376c6 100644 --- a/hw/xbox/nv2a/pgraph/vk/texture.c +++ b/hw/xbox/nv2a/pgraph/vk/texture.c @@ -1340,6 +1340,8 @@ static void create_texture(PGRAPHState *pg, int texture_idx) GET_MASK(filter, NV_PGRAPH_TEXFILTER0_MIPMAP_LOD_BIAS)); if (lod_bias > r->device_props.limits.maxSamplerLodBias) { lod_bias = r->device_props.limits.maxSamplerLodBias; + } else if (lod_bias < -r->device_props.limits.maxSamplerLodBias) { + lod_bias = -r->device_props.limits.maxSamplerLodBias; } VkSamplerCreateInfo sampler_create_info = {