From 974115f924694f1ff5273aca06869837b9e07d16 Mon Sep 17 00:00:00 2001 From: Christian Gmeiner Date: Wed, 25 Jun 2025 12:26:49 +0200 Subject: [PATCH] etnaviv: nir: Add nir_texop_tex offset lowering Implement offset lowering by calculating implicit LOD using coordinate derivatives (ddx/ddy) and doing some deep floating point wizardry matching the binary blob behaviour. Adds helper functions for coordinate calculation and LOD clamping that will be reused by subsequent offset lowering passes. Passes dEQP-GLES3.functional.shaders.texture_functions.textureoffset.* without explicit bias on GC7000. Signed-off-by: Christian Gmeiner Reviewed-by: Lucas Stach Part-of: --- .../etnaviv/etnaviv_nir_lower_texture.c | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/gallium/drivers/etnaviv/etnaviv_nir_lower_texture.c b/src/gallium/drivers/etnaviv/etnaviv_nir_lower_texture.c index 6c3576199e7..ef3ceb065b3 100644 --- a/src/gallium/drivers/etnaviv/etnaviv_nir_lower_texture.c +++ b/src/gallium/drivers/etnaviv/etnaviv_nir_lower_texture.c @@ -21,6 +21,133 @@ lower_txs(nir_builder *b, nir_tex_instr *tex, UNUSED void *data) return true; } +static nir_def * +clamp_lod(nir_builder *b, nir_def *sampler, nir_def *lod) +{ + nir_def *params = nir_load_sampler_lod_parameters(b, 2, 32, sampler); + nir_def *min_lod = nir_channel(b, params, 0); + nir_def *max_lod = nir_channel(b, params, 1); + + return nir_fclamp(b, lod, min_lod, max_lod); +} + +static nir_def * +calculate_coord(nir_builder *b, nir_tex_instr *tex, nir_def *coord, nir_def *base_size_int, nir_def *lod, nir_def *offset) +{ + lod = nir_f2i32(b, lod); + + /* Calculate mipmap level dimensions by right-shifting base size by LOD */ + nir_def *mip_size = nir_ushr(b, base_size_int, lod); + + /* Ensure minimum size of 1 pixel - mipmaps can't be smaller than 1x1 */ + mip_size = nir_imax(b, mip_size, nir_imm_int(b, 1)); + + /* Convert mip size to float and calculate reciprocal to scale texel offsets into normalized coordinates */ + nir_def *mip_size_float = nir_i2f32(b, mip_size); + nir_def *inv_mip_size = nir_frcp(b, mip_size_float); + + offset = nir_i2f32(b, offset); + + if (tex->is_array) { + const unsigned array_index = tex->coord_components - 1; + + /* Split coordinate into spatial part and array layer */ + nir_def *spatial_coord = nir_trim_vector(b, coord, array_index); + nir_def *array_layer = nir_channel(b, coord, array_index); + + /* Apply offset only to spatial coordinates */ + nir_def *spatial_inv_level_size = nir_trim_vector(b, inv_mip_size, array_index); + spatial_coord = nir_fadd(b, spatial_coord, + nir_fmul(b, nir_trim_vector(b, offset, array_index), + spatial_inv_level_size)); + + /* Reconstruct full coordinate with original array layer */ + coord = nir_vec3(b, nir_channel(b, spatial_coord, 0), + nir_channel(b, spatial_coord, 1), + array_layer); + } else { + coord = nir_fadd(b, coord, nir_fmul(b, offset, inv_mip_size)); + } + + return coord; +} + +static bool +lower_tex_offset(nir_builder *b, nir_tex_instr *tex, UNUSED void *data) +{ + if (tex->op != nir_texop_tex) + return false; + + nir_def *offset = nir_steal_tex_src(tex, nir_tex_src_offset); + if (!offset) + return false; + + assert(b->shader->info.stage == MESA_SHADER_FRAGMENT); + + b->cursor = nir_before_instr(&tex->instr); + + int coord_index = nir_tex_instr_src_index(tex, nir_tex_src_coord); + nir_def *coord = tex->src[coord_index].src.ssa; + nir_def *sampler = nir_imm_int(b, tex->texture_index); + nir_def *lod = NULL; + + /* Load the base level texture size as int */ + nir_def *base_size_int = nir_load_texture_size_etna(b, 32, sampler); + base_size_int = nir_trim_vector(b, base_size_int, tex->coord_components); + + nir_def *base_size = nir_i2f32(b, base_size_int); + + /* Compute texture coordinate derivatives */ + nir_def *ddx = nir_ddx(b, coord); + nir_def *ddy = nir_ddy(b, coord); + + /* Scale derivatives by texture size */ + nir_def *scaled_ddx = nir_fmul(b, ddx, base_size); + nir_def *scaled_ddy = nir_fmul(b, ddy, base_size); + + /* Calculate LOD using scaled derivatives with fdot calls */ + nir_def *ddx_squared = nir_fdot(b, scaled_ddx, scaled_ddx); + nir_def *ddy_squared = nir_fdot(b, scaled_ddy, scaled_ddy); + + nir_def *max_derivative = nir_fmax(b, ddx_squared, ddy_squared); + + /* Hardware-specific LOD quantization using IEEE 754 float manipulation. + * By multiplying by 0.5 and adding 393216.0f (2^18 + 2^17), we force a + * specific exponent that traps the fractional LOD bits in the mantissa. + * This creates a float where the mantissa behaves like a 4.4 fixed-point + * value, matching the expected behaviour of Vivante GPU. + */ + nir_def *lod_raw = nir_flog2(b, max_derivative); + nir_def *lod_fixed_point = nir_ffma(b, lod_raw, nir_imm_float(b, 0.5f), + nir_imm_float(b, 393216.0f)); + + /* Extract 16-bit fractional part */ + nir_def *lod_masked = nir_iand_imm(b, lod_fixed_point, 0xFFFF); + + /* Handle sign extension for negative LODs */ + nir_def *sign_bit = nir_iand_imm(b, lod_masked, 0x8000); + nir_def *lod_or_magic = nir_ior_imm(b, lod_masked, 0xFFFF0000); + + /* Select sign-extended version for negative, original for positive */ + nir_def *lod_quantized = nir_bcsel(b, nir_ine_imm(b, sign_bit, 0), lod_or_magic, lod_masked); + + /* Convert back from fixed-point: scale by 1/32 and add 0.5 offset + * This reverses the fixed-point encoding to get final LOD value + */ + nir_def *lod_float = nir_u2f32(b, lod_quantized); + lod = nir_ffma(b, lod_float, nir_imm_float(b, 1.0f/32.0f), nir_imm_float(b, 0.5f)); + + /* floor and convert to int */ + lod = nir_ffloor(b, lod); + lod = clamp_lod(b, sampler, lod); + + coord = calculate_coord(b, tex, coord, base_size_int, lod, offset); + + nir_src_rewrite(&tex->src[coord_index].src, coord); + + return true; +} + static bool legalize_txf_lod(nir_builder *b, nir_tex_instr *tex, UNUSED void *data) { @@ -188,6 +315,9 @@ etna_nir_lower_texture(nir_shader *s, struct etna_shader_key *key, const struct NIR_PASS(progress, s, nir_shader_tex_pass, lower_txs, nir_metadata_control_flow, NULL); + NIR_PASS(progress, s, nir_shader_tex_pass, lower_tex_offset, + nir_metadata_control_flow, NULL); + NIR_PASS(progress, s, nir_shader_tex_pass, legalize_txf_lod, nir_metadata_control_flow, NULL);