[Vulkan] Sample auto-LOD cube fetches with implicit LOD + bias

Explicit gradients of the direction reconstructed from the guest S/T/face coordinates pick the wrong mip on Vulkan, so sample cube fetches that use computed LOD without register gradients with implicit LOD and the accumulated LOD bias (fetch constant + register LOD + instruction bias) as the Bias image operand instead. This also makes tfetchCube consistent with getCompTexLOD, which already queries the implicit LOD. Register-gradient cube fetches keep explicit gradients in cube space, and other dimensions keep explicit gradients, matching the DXBC path. Gradient setup is skipped entirely on the implicit-LOD path.

Co-authored-by: Herman S. <429230+has207@users.noreply.github.com>
This commit is contained in:
bomabomabomaboma
2026-08-01 00:58:34 +00:00
committed by Radosław Gliński
parent 658cdbb5de
commit 3254ac20f8
3 changed files with 26 additions and 31 deletions

View File

@@ -354,6 +354,7 @@ namespace spv {
// Backward compatibility for ImageOperands
#define ImageOperandsGradMask ImageOperandsMask::Grad
#define ImageOperandsLodMask ImageOperandsMask::Lod
#define ImageOperandsBiasMask ImageOperandsMask::Bias
#define ImageOperandsMaskNone ImageOperandsMask::MaskNone
// Backward compatibility for StorageClass

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 = 14;
static constexpr uint32_t kVersion = 15;
enum class DepthStencilMode : uint32_t {
kNoModifiers,

View File

@@ -1785,12 +1785,19 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
builder_->makeFloatConstant(instr.attributes.lod_bias));
}
// Cube auto-LOD without register gradients uses implicit LOD + bias to
// work around wrong-mip explicit cube gradients on Vulkan. Other dims
// keep explicit gradients.
bool use_lod_bias = use_computed_lod &&
!instr.attributes.use_register_gradients &&
instr.dimension == xenos::FetchOpDimension::kCube;
// 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) {
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
@@ -1941,31 +1948,15 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
lod_gradient_scale);
} break;
case xenos::FetchOpDimension::kCube: {
if (instr.attributes.use_register_gradients) {
// Register gradients are already in the cube space for cube
// maps.
// TODO(Triang3l): Are cube map register gradients unnormalized
// if the coordinates themselves are unnormalized?
gradients_h = builder_->createLoad(var_main_tfetch_gradients_h_,
spv::NoPrecision);
gradients_v = builder_->createLoad(var_main_tfetch_gradients_v_,
spv::NoPrecision);
} else {
id_vector_temp_.clear();
for (uint32_t i = 0; i < 3; ++i) {
id_vector_temp_.push_back(coordinates[i]);
}
spv::Id gradient_coordinate_vector =
builder_->createCompositeConstruct(type_float3_,
id_vector_temp_);
builder_->addCapability(spv::CapabilityDerivativeControl);
gradients_h =
builder_->createUnaryOp(spv::OpDPdxCoarse, type_float3_,
gradient_coordinate_vector);
gradients_v =
builder_->createUnaryOp(spv::OpDPdyCoarse, type_float3_,
gradient_coordinate_vector);
}
// Only register gradients reach here (auto-LOD uses implicit LOD
// + bias, handled at the gradient block guard above). Register
// gradients are already in the cube space for cube maps.
// TODO(Triang3l): Are cube map register gradients unnormalized
// if the coordinates themselves are unnormalized?
gradients_h = builder_->createLoad(var_main_tfetch_gradients_h_,
spv::NoPrecision);
gradients_v = builder_->createLoad(var_main_tfetch_gradients_v_,
spv::NoPrecision);
gradients_h = builder_->createNoContractionBinOp(
spv::OpVectorTimesScalar, type_float3_, gradients_h,
lod_gradient_scale);
@@ -1978,10 +1969,13 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
// Sample the texture.
spv::ImageOperandsMask image_operands_mask =
use_computed_lod ? spv::ImageOperandsGradMask
: spv::ImageOperandsLodMask;
use_lod_bias ? spv::ImageOperandsBiasMask
: (use_computed_lod ? spv::ImageOperandsGradMask
: spv::ImageOperandsLodMask);
spv::Id sample_result_unsigned, sample_result_signed;
if (!use_computed_lod) {
if (use_lod_bias) {
texture_parameters.bias = lod;
} else if (!use_computed_lod) {
texture_parameters.lod = lod;
}
if (instr.dimension == xenos::FetchOpDimension::k3DOrStacked) {
@@ -2232,7 +2226,7 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
sample_result_signed = if_data_is_3d.createMergePhi(
sample_result_signed_3d, sample_result_signed_stacked);
} else {
if (use_computed_lod) {
if (use_computed_lod && !use_lod_bias) {
texture_parameters.gradX = gradients_h;
texture_parameters.gradY = gradients_v;
}