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);