From 8486e97a06cbbf68da310f16bf18f011d511c528 Mon Sep 17 00:00:00 2001 From: goldislead <69987043+goldislead@users.noreply.github.com> Date: Thu, 30 Jul 2026 00:38:48 -0700 Subject: [PATCH] [GPU] Sample locked mip unnormalized fetches in the mip's grid Fixes visibility popping in 555308B6 and 5553080B. Unnormalized texture coordinates address texels of the mip level being sampled, not always the base level. The titles in question lock the fetch constant to one mip and address that mip's grid. The denominator was still the base level size, so each reduction after the first read garbage. So the locked size is now used for 2D fetches (point filter, clamp-to-edge) with MipMinLevel == MipMaxLevel. Everything else keeps the base level denominator until a shared effective LOD model exists. --- src/xenia/gpu/dxbc_shader_translator_fetch.cc | 31 +++++++++++++ .../gpu/spirv_shader_translator_fetch.cc | 46 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/xenia/gpu/dxbc_shader_translator_fetch.cc b/src/xenia/gpu/dxbc_shader_translator_fetch.cc index 478a87185..3f8e5f5f8 100644 --- a/src/xenia/gpu/dxbc_shader_translator_fetch.cc +++ b/src/xenia/gpu/dxbc_shader_translator_fetch.cc @@ -927,6 +927,37 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( a_.OpIAdd( dxbc::Dest::R(size_and_is_3d_temp, size_needed_components & 0b0111), dxbc::Src::R(size_and_is_3d_temp), dxbc::Src::LU(1)); + // HZB reducers in 555308B6 and 5553080B lock the sampler to one mip + // and address it with unnormalized coordinates. Those coordinates are + // in the locked mip's grid, but the denominator below was always the + // base level size, so each reduction after the first read garbage. + // Limit this to 2D unnormalized fetches with a locked mip. This changes + // only the denominator. + bool selected_mip_grid_possible = + instr.opcode == FetchOpcode::kTextureFetch && + instr.dimension == xenos::FetchOpDimension::k2D && + instr.attributes.unnormalized_coordinates; + if (selected_mip_grid_possible) { + uint32_t selected_mip_temp = PushSystemTemp(); + // Word 4 has MipMinLevel in bits 2:5 and MipMaxLevel in bits 6:9. + a_.OpUBFE(dxbc::Dest::R(selected_mip_temp, 0b0011), + dxbc::Src::LU(4, 4, 0, 0), dxbc::Src::LU(2, 6, 0, 0), + RequestTextureFetchConstantWord(tfetch_index, 4)); + a_.OpIEq(dxbc::Dest::R(selected_mip_temp, 0b0100), + dxbc::Src::R(selected_mip_temp, dxbc::Src::kXXXX), + dxbc::Src::R(selected_mip_temp, dxbc::Src::kYYYY)); + // max(size >> mip, 1) for non-pow2 textures. Scaling stays unchanged. + a_.OpUShR(dxbc::Dest::R(selected_mip_temp, 0b1010), + dxbc::Src::R(size_and_is_3d_temp, 0b01000000), + dxbc::Src::R(selected_mip_temp, dxbc::Src::kXXXX)); + a_.OpUMax(dxbc::Dest::R(selected_mip_temp, 0b1010), + dxbc::Src::R(selected_mip_temp), dxbc::Src::LU(1)); + a_.OpMovC(dxbc::Dest::R(size_and_is_3d_temp, 0b0011), + dxbc::Src::R(selected_mip_temp, dxbc::Src::kZZZZ), + dxbc::Src::R(selected_mip_temp, 0b00001101), + dxbc::Src::R(size_and_is_3d_temp)); + PopSystemTemp(); + } // Convert the size to float for multiplication/division. a_.OpUToF( dxbc::Dest::R(size_and_is_3d_temp, size_needed_components & 0b0111), diff --git a/src/xenia/gpu/spirv_shader_translator_fetch.cc b/src/xenia/gpu/spirv_shader_translator_fetch.cc index 5aa54c5b6..03d3c665c 100644 --- a/src/xenia/gpu/spirv_shader_translator_fetch.cc +++ b/src/xenia/gpu/spirv_shader_translator_fetch.cc @@ -1015,6 +1015,40 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction( } } break; } + // HZB reducers in 555308B6 and 5553080B lock the sampler to one mip + // and address it with unnormalized coordinates. Those coordinates are + // in the locked mip's grid, but the denominator below was always the + // base level size, so each reduction after the first read garbage. + // Limit this to 2D unnormalized fetches with a locked mip. This changes + // only the denominator. + spv::Id selected_mip_level = spv::NoResult; + spv::Id selected_mip_locked = spv::NoResult; + bool selected_mip_grid_possible = + instr.opcode == ucode::FetchOpcode::kTextureFetch && + instr.dimension == xenos::FetchOpDimension::k2D && + instr.attributes.unnormalized_coordinates; + if (selected_mip_grid_possible) { + // Word 4 has MipMinLevel in bits 2:5 and MipMaxLevel in bits 6:9. + id_vector_temp_.clear(); + id_vector_temp_.push_back(const_int_0_); + id_vector_temp_.push_back(builder_->makeIntConstant( + int((fetch_constant_word_0_index + 4) >> 2))); + id_vector_temp_.push_back(builder_->makeIntConstant( + int((fetch_constant_word_0_index + 4) & 3))); + spv::Id fetch_constant_word_4_mips = + builder_->createLoad(builder_->createAccessChain( + spv::StorageClassUniform, + uniform_fetch_constants_, id_vector_temp_), + spv::NoPrecision); + selected_mip_level = builder_->createTriOp( + spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_4_mips, + builder_->makeUintConstant(2), builder_->makeUintConstant(4)); + spv::Id mip_max_level = builder_->createTriOp( + spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_4_mips, + builder_->makeUintConstant(6), builder_->makeUintConstant(4)); + selected_mip_locked = builder_->createBinOp( + spv::OpIEqual, type_bool_, selected_mip_level, mip_max_level); + } { uint32_t size_remaining_components = size_needed_components; uint32_t size_component_index; @@ -1026,6 +1060,18 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction( size_component_ref = builder_->createBinOp(spv::OpIAdd, type_uint_, size_component_ref, builder_->makeUintConstant(1)); + if (selected_mip_locked != spv::NoResult) { + // max(size >> mip, 1) for non-pow2 textures. Scaling stays + // unchanged. + spv::Id selected_mip_size = builder_->createBinBuiltinCall( + type_uint_, ext_inst_glsl_std_450_, GLSLstd450UMax, + builder_->createBinOp(spv::OpShiftRightLogical, type_uint_, + size_component_ref, selected_mip_level), + builder_->makeUintConstant(1)); + size_component_ref = builder_->createTriOp( + spv::OpSelect, type_uint_, selected_mip_locked, + selected_mip_size, size_component_ref); + } // Convert the size to float for multiplication or division. size_component_ref = builder_->createUnaryOp( spv::OpConvertUToF, type_float_, size_component_ref);