[GPU] Apply gradient exponent bias during fetch that's per-axis

The fetch constant carries independent signed exponent biases in [-16, 15] for the horizontal and vertical LOD gradients. Scale the H and V gradients by exp2(lod + bias_h) and exp2(lod + bias_v) respectively in the computed LOD sample path. getCompTexLOD keeps returning the raw queried LOD, treating the adjustment like the fetch-constant LOD bias, which is also not folded into it. Zero, the common case, is a no-op.

On the cube implicit-LOD path, where no explicit gradients exist to scale, the greater of the two biases is added to the LOD bias instead - exact when both are equal, erring towards a blurrier mip otherwise.

Co-Authored-By: Herman S. <429230+has207@users.noreply.github.com>
This commit is contained in:
bomabomabomaboma
2026-08-01 01:09:21 +00:00
committed by Radosław Gliński
parent 090cecd1b8
commit 0f2980de44
4 changed files with 63 additions and 39 deletions

View File

@@ -114,7 +114,7 @@ class DxbcShaderTranslator : public ShaderTranslator {
// If anything in this is structure is changed in a way not compatible with
// the previous layout, invalidate the pipeline storages by increasing this
// version number (0xYYYYMMDD)!
static constexpr uint32_t kVersion = 0x20260802;
static constexpr uint32_t kVersion = 0x20260803;
enum class DepthStencilMode : uint32_t {
kNoModifiers,

View File

@@ -1628,13 +1628,12 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
}
assert_not_zero(grad_component_count);
uint32_t grad_mask = (1 << grad_component_count) - 1;
// Convert the bias to a gradient scale.
// Convert the bias to a gradient scale, and merge the per-axis
// gradient exponent biases into it. Zero (common case) is a no-op.
// getCompTexLOD keeps returning the raw queried LOD, treating the
// adjustment like the fetch constant LOD bias, which is also not
// folded into it.
a_.OpExp(lod_dest, lod_src);
// FIXME(Triang3l): Gradient exponent adjustment is currently not done
// in getCompTexLOD, so don't do it here too.
#if 0
// Extract gradient exponent biases from the fetch constant and merge
// them with the LOD bias.
a_.OpIBFE(dxbc::Dest::R(grad_h_lod_temp, 0b0011), dxbc::Src::LU(5),
dxbc::Src::LU(22, 27, 0, 0),
RequestTextureFetchConstantWord(tfetch_index, 4));
@@ -1645,7 +1644,6 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
dxbc::Src::R(grad_h_lod_temp, dxbc::Src::kYYYY));
a_.OpMul(lod_dest, lod_src,
dxbc::Src::R(grad_h_lod_temp, dxbc::Src::kXXXX));
#endif
// Obtain the gradients and apply biases to them.
// For 1D textures, always use automatic gradients. For wide 1D
// textures, coordinates have been remapped to 2D, and register
@@ -1657,16 +1655,9 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
// Register gradients are already in the cube space for cube maps.
a_.OpMul(dxbc::Dest::R(grad_h_lod_temp, grad_mask),
dxbc::Src::R(system_temp_grad_h_lod_), lod_src);
// FIXME(Triang3l): Gradient exponent adjustment is currently not
// done in getCompTexLOD, so don't do it here too.
#if 0
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
dxbc::Src::R(system_temp_grad_v_vfetch_address_),
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
#else
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
dxbc::Src::R(system_temp_grad_v_vfetch_address_), lod_src);
#endif
// TODO(Triang3l): Are cube map register gradients unnormalized if
// the coordinates themselves are unnormalized?
if (instr.attributes.unnormalized_coordinates &&
@@ -1705,16 +1696,9 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
dxbc::Src::R(grad_h_lod_temp), lod_src);
a_.OpDerivRTYCoarse(dxbc::Dest::R(grad_v_temp, grad_mask),
dxbc::Src::R(coord_and_sampler_temp));
// FIXME(Triang3l): Gradient exponent adjustment is currently not
// done in getCompTexLOD, so don't do it here too.
#if 0
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
dxbc::Src::R(grad_v_temp),
dxbc::Src::R(grad_v_temp, dxbc::Src::kWWWW));
#else
a_.OpMul(dxbc::Dest::R(grad_v_temp, grad_mask),
dxbc::Src::R(grad_v_temp), lod_src);
#endif
}
if (coordinate_dimension == xenos::FetchOpDimension::k1D) {
// Pad the gradients to 2D because 1D textures are fetched as 2D

View File

@@ -34,7 +34,7 @@ class SpirvShaderTranslator : public ShaderTranslator {
// TODO(Triang3l): Change to 0xYYYYMMDD once it's out of the rapid
// prototyping stage (easier to do small granular updates with an
// incremental counter).
static constexpr uint32_t kVersion = 16;
static constexpr uint32_t kVersion = 17;
enum class DepthStencilMode : uint32_t {
kNoModifiers,

View File

@@ -1792,18 +1792,58 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
!instr.attributes.use_register_gradients &&
instr.dimension == xenos::FetchOpDimension::kCube;
if (use_lod_bias) {
// The per-axis gradient exponent biases can't be applied to the
// host's implicit gradients, so we approximate them by adding the
// greater of the two to the LOD bias. Scaling both gradients by 2^n
// shifts the computed LOD by n, so this is exact whenever both biases
// are equal, and biases a blurrier mip rather than a shimmering one.
spv::Id grad_exp_adjust_max = builder_->createBinBuiltinCall(
type_int_, ext_inst_glsl_std_450_, GLSLstd450SMax,
builder_->createTriOp(spv::OpBitFieldSExtract, type_int_,
fetch_constant_word_4_signed,
builder_->makeUintConstant(22),
builder_->makeUintConstant(5)),
builder_->createTriOp(spv::OpBitFieldSExtract, type_int_,
fetch_constant_word_4_signed,
builder_->makeUintConstant(27),
builder_->makeUintConstant(5)));
lod = builder_->createNoContractionBinOp(
spv::OpFAdd, type_float_, lod,
builder_->createUnaryOp(spv::OpConvertSToF, type_float_,
grad_exp_adjust_max));
}
// Calculate the gradients for sampling the texture if needed.
// 2D vectors for k1D (because 1D images are emulated as 2D arrays),
// k2D.
// 3D vectors for k3DOrStacked, kCube.
spv::Id gradients_h = spv::NoResult, gradients_v = spv::NoResult;
if (use_computed_lod && !use_lod_bias) {
// TODO(Triang3l): Gradient exponent adjustment is currently not done
// in getCompTexLOD, so not doing it here too for now. Apply the
// gradient exponent biases from the word 4 of the fetch constant in
// the future when it's handled in getCompTexLOD somehow.
spv::Id lod_gradient_scale = builder_->createUnaryBuiltinCall(
type_float_, ext_inst_glsl_std_450_, GLSLstd450Exp2, lod);
// Per-axis gradient exponent biases (LodBiasH/V) from word 4: h in
// bits 22:26, v in bits 27:31. Applied here in the sample path like
// the fetch-constant LOD bias (getCompTexLOD returns the raw queried
// LOD, so neither bias is folded into it). Zero (the common case) is
// a no-op.
spv::Id grad_exp_adjust_h = builder_->createUnaryOp(
spv::OpConvertSToF, type_float_,
builder_->createTriOp(spv::OpBitFieldSExtract, type_int_,
fetch_constant_word_4_signed,
builder_->makeUintConstant(22),
builder_->makeUintConstant(5)));
spv::Id grad_exp_adjust_v = builder_->createUnaryOp(
spv::OpConvertSToF, type_float_,
builder_->createTriOp(spv::OpBitFieldSExtract, type_int_,
fetch_constant_word_4_signed,
builder_->makeUintConstant(27),
builder_->makeUintConstant(5)));
spv::Id lod_gradient_scale_h = builder_->createUnaryBuiltinCall(
type_float_, ext_inst_glsl_std_450_, GLSLstd450Exp2,
builder_->createNoContractionBinOp(spv::OpFAdd, type_float_, lod,
grad_exp_adjust_h));
spv::Id lod_gradient_scale_v = builder_->createUnaryBuiltinCall(
type_float_, ext_inst_glsl_std_450_, GLSLstd450Exp2,
builder_->createNoContractionBinOp(spv::OpFAdd, type_float_, lod,
grad_exp_adjust_v));
switch (coordinate_dimension) {
case xenos::FetchOpDimension::k1D: {
spv::Id gradient_h_x, gradient_v_x;
@@ -1828,13 +1868,13 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
gradient_v_y = builder_->createUnaryOp(
spv::OpDPdyCoarse, type_float_, coordinates[1]);
gradient_h_x = builder_->createNoContractionBinOp(
spv::OpFMul, type_float_, gradient_h_x, lod_gradient_scale);
spv::OpFMul, type_float_, gradient_h_x, lod_gradient_scale_h);
gradient_v_x = builder_->createNoContractionBinOp(
spv::OpFMul, type_float_, gradient_v_x, lod_gradient_scale);
spv::OpFMul, type_float_, gradient_v_x, lod_gradient_scale_v);
gradient_h_y = builder_->createNoContractionBinOp(
spv::OpFMul, type_float_, gradient_h_y, lod_gradient_scale);
spv::OpFMul, type_float_, gradient_h_y, lod_gradient_scale_h);
gradient_v_y = builder_->createNoContractionBinOp(
spv::OpFMul, type_float_, gradient_v_y, lod_gradient_scale);
spv::OpFMul, type_float_, gradient_v_y, lod_gradient_scale_v);
// 1D textures are sampled as 2D arrays - need 2-component
// gradients.
id_vector_temp_.clear();
@@ -1895,10 +1935,10 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
}
gradients_h = builder_->createNoContractionBinOp(
spv::OpVectorTimesScalar, type_float2_, gradients_h,
lod_gradient_scale);
lod_gradient_scale_h);
gradients_v = builder_->createNoContractionBinOp(
spv::OpVectorTimesScalar, type_float2_, gradients_v,
lod_gradient_scale);
lod_gradient_scale_v);
} break;
case xenos::FetchOpDimension::k3DOrStacked: {
if (instr.attributes.use_register_gradients) {
@@ -1942,10 +1982,10 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
}
gradients_h = builder_->createNoContractionBinOp(
spv::OpVectorTimesScalar, type_float3_, gradients_h,
lod_gradient_scale);
lod_gradient_scale_h);
gradients_v = builder_->createNoContractionBinOp(
spv::OpVectorTimesScalar, type_float3_, gradients_v,
lod_gradient_scale);
lod_gradient_scale_v);
} break;
case xenos::FetchOpDimension::kCube: {
// Only register gradients reach here (auto-LOD uses implicit LOD
@@ -1959,10 +1999,10 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
spv::NoPrecision);
gradients_h = builder_->createNoContractionBinOp(
spv::OpVectorTimesScalar, type_float3_, gradients_h,
lod_gradient_scale);
lod_gradient_scale_h);
gradients_v = builder_->createNoContractionBinOp(
spv::OpVectorTimesScalar, type_float3_, gradients_v,
lod_gradient_scale);
lod_gradient_scale_v);
} break;
}
}