From 7cd47947b07de30b649fb4224418a659890eab73 Mon Sep 17 00:00:00 2001 From: goldislead <69987043+goldislead@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:40:16 -0700 Subject: [PATCH] [GPU] Select promoted tfetch1D layouts at runtime This keeps 545407D4's 2D use working while fixing wide 1D bindings in 555308CE. Co-authored-by: Herman S. <429230+has207@users.noreply.github.com> --- src/xenia/gpu/dxbc_shader_translator_fetch.cc | 48 ++++++++++++++--- .../gpu/spirv_shader_translator_fetch.cc | 52 ++++++++++++++++--- src/xenia/gpu/texture_cache.cc | 1 + src/xenia/gpu/xenos.h | 3 ++ 4 files changed, 90 insertions(+), 14 deletions(-) diff --git a/src/xenia/gpu/dxbc_shader_translator_fetch.cc b/src/xenia/gpu/dxbc_shader_translator_fetch.cc index e4babadd6..5d63cd1ac 100644 --- a/src/xenia/gpu/dxbc_shader_translator_fetch.cc +++ b/src/xenia/gpu/dxbc_shader_translator_fetch.cc @@ -885,7 +885,10 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( size_needed_components |= 0b0001; break; case xenos::FetchOpDimension::k2D: - if (instr.attributes.unnormalized_coordinates) { + // A tfetch1D promoted by its source swizzle may still use a 1D fetch + // constant. Its size interpretation is selected below at runtime. + if (instr.dimension == xenos::FetchOpDimension::k1D || + instr.attributes.unnormalized_coordinates) { size_needed_components |= 0b0011; } break; @@ -937,6 +940,26 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( a_.OpUBFE(dxbc::Dest::R(size_and_is_3d_temp, size_needed_components), dxbc::Src::LU(13, 13, 0, 0), dxbc::Src::LU(0, 13, 0, 0), RequestTextureFetchConstantWord(tfetch_index, 2)); + if (instr.dimension == xenos::FetchOpDimension::k1D) { + assert_true((size_needed_components & 0b0011) == 0b0011); + size_1d_width_minus_1_temp = PushSystemTemp(); + a_.OpUBFE(dxbc::Dest::R(size_1d_width_minus_1_temp, 0b0001), + dxbc::Src::LU(xenos::kTexture1DMaxWidthLog2), + dxbc::Src::LU(0), + RequestTextureFetchConstantWord(tfetch_index, 2)); + a_.OpMov(dxbc::Dest::R(size_1d_width_minus_1_temp, 0b0010), + dxbc::Src::LU(0)); + a_.OpUBFE(dxbc::Dest::R(size_and_is_3d_temp, 0b1000), + dxbc::Src::LU(2), dxbc::Src::LU(9), + RequestTextureFetchConstantWord(tfetch_index, 5)); + a_.OpIEq(dxbc::Dest::R(size_and_is_3d_temp, 0b1000), + dxbc::Src::R(size_and_is_3d_temp, dxbc::Src::kWWWW), + dxbc::Src::LU(uint32_t(xenos::DataDimension::k1D))); + a_.OpMovC(dxbc::Dest::R(size_and_is_3d_temp, 0b0011), + dxbc::Src::R(size_and_is_3d_temp, dxbc::Src::kWWWW), + dxbc::Src::R(size_1d_width_minus_1_temp), + dxbc::Src::R(size_and_is_3d_temp)); + } break; case xenos::FetchOpDimension::k3DOrStacked: // tfetch3D is used for both stacked and 3D - first, check if 3D. @@ -1262,7 +1285,7 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( } } } - switch (coordinate_dimension) { + switch (instr.dimension) { case xenos::FetchOpDimension::k1D: { // Check if the fetch constant's actual dimension is k1D (word 5, bits // 9-10). If not, skip wide 1D handling as size bits differ per @@ -1315,6 +1338,9 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( dxbc::Src::LF(float(xenos::kTexture2DCubeMaxWidthHeight))); a_.OpRoundPI(dxbc::Dest::R(coord_and_sampler_temp, 0b0100), dxbc::Src::R(coord_and_sampler_temp, dxbc::Src::kZZZZ)); + a_.OpMin(dxbc::Dest::R(coord_and_sampler_temp, 0b0100), + dxbc::Src::R(coord_and_sampler_temp, dxbc::Src::kZZZZ), + dxbc::Src::LF(float(xenos::kTexture1DWideMaxRows))); // coord.y = (row_index + 0.5) / num_rows - sample at the center of // the row, not its edge. At the edge, linear filtering would blend // 50/50 with the previous row (texels 8192 apart), and even point @@ -1333,15 +1359,23 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( a_.OpElse(); { // Normal 1D texture - pad to 2D array coordinates. - a_.OpMov(dxbc::Dest::R(coord_and_sampler_temp, 0b0110), - dxbc::Src::LF(0.0f)); + a_.OpMov( + dxbc::Dest::R(coord_and_sampler_temp, + coordinate_dimension == xenos::FetchOpDimension::k1D + ? 0b0110 + : 0b0100), + dxbc::Src::LF(0.0f)); } a_.OpEndIf(); a_.OpElse(); { - // Non-1D texture bound to 1D fetch - just pad coordinates. - a_.OpMov(dxbc::Dest::R(coord_and_sampler_temp, 0b0110), - dxbc::Src::LF(0.0f)); + // Keep Y when the source swizzle promoted the fetch to 2D. + a_.OpMov( + dxbc::Dest::R(coord_and_sampler_temp, + coordinate_dimension == xenos::FetchOpDimension::k1D + ? 0b0110 + : 0b0100), + dxbc::Src::LF(0.0f)); } a_.OpEndIf(); } break; diff --git a/src/xenia/gpu/spirv_shader_translator_fetch.cc b/src/xenia/gpu/spirv_shader_translator_fetch.cc index 299a90854..0167c19a5 100644 --- a/src/xenia/gpu/spirv_shader_translator_fetch.cc +++ b/src/xenia/gpu/spirv_shader_translator_fetch.cc @@ -903,7 +903,10 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction( size_needed_components |= 0b0001; break; case xenos::FetchOpDimension::k2D: - if (instr.attributes.unnormalized_coordinates) { + // A tfetch1D promoted by its source swizzle may still use a 1D fetch + // constant. Its size interpretation is selected below at runtime. + if (instr.dimension == xenos::FetchOpDimension::k1D || + instr.attributes.unnormalized_coordinates) { size_needed_components |= 0b0011; } break; @@ -1002,6 +1005,38 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction( spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, width_height_bit_count, width_height_bit_count); } + if (instr.dimension == xenos::FetchOpDimension::k1D) { + assert_true((size_needed_components & 0b11) == 0b11); + 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 + 5) >> 2))); + id_vector_temp_.push_back(builder_->makeIntConstant( + int((fetch_constant_word_0_index + 5) & 3))); + spv::Id fetch_constant_word_5 = builder_->createLoad( + builder_->createAccessChain(spv::StorageClassUniform, + uniform_fetch_constants_, + id_vector_temp_), + spv::NoPrecision); + spv::Id data_is_1d = builder_->createBinOp( + spv::OpIEqual, type_bool_, + builder_->createTriOp(spv::OpBitFieldUExtract, type_uint_, + fetch_constant_word_5, + builder_->makeUintConstant(9), + builder_->makeUintConstant(2)), + builder_->makeUintConstant( + static_cast(xenos::DataDimension::k1D))); + spv::Id width_1d_minus_1 = builder_->createTriOp( + spv::OpBitFieldUExtract, type_uint_, fetch_constant_word_2, + const_uint_0_, + builder_->makeUintConstant(xenos::kTexture1DMaxWidthLog2)); + size[0] = + builder_->createTriOp(spv::OpSelect, type_uint_, data_is_1d, + width_1d_minus_1, size[0]); + size[1] = builder_->createTriOp(spv::OpSelect, type_uint_, + data_is_1d, const_uint_0_, size[1]); + size_1d_width_minus_1_uint = width_1d_minus_1; + } assert_zero(size_needed_components & 0b100); } break; case xenos::FetchOpDimension::k3DOrStacked: { @@ -1341,12 +1376,15 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction( spv::Id row_width_float = builder_->makeFloatConstant( float(xenos::kTexture2DCubeMaxWidthHeight)); - // num_rows = ceil(original_width / row_width) - spv::Id num_rows = builder_->createUnaryBuiltinCall( - type_float_, ext_inst_glsl_std_450_, GLSLstd450Ceil, - builder_->createNoContractionBinOp(spv::OpFDiv, type_float_, - original_width_float, - row_width_float)); + // Keep this in sync with the texture cache's materialized row cap. + spv::Id num_rows = builder_->createBinBuiltinCall( + type_float_, ext_inst_glsl_std_450_, GLSLstd450NMin, + builder_->createUnaryBuiltinCall( + type_float_, ext_inst_glsl_std_450_, GLSLstd450Ceil, + builder_->createNoContractionBinOp(spv::OpFDiv, type_float_, + original_width_float, + row_width_float)), + builder_->makeFloatConstant(float(xenos::kTexture1DWideMaxRows))); // linear_x = coord * original_width (denormalize to texel space) spv::Id linear_x = builder_->createNoContractionBinOp( diff --git a/src/xenia/gpu/texture_cache.cc b/src/xenia/gpu/texture_cache.cc index 534d5a0de..f809c0dcf 100644 --- a/src/xenia/gpu/texture_cache.cc +++ b/src/xenia/gpu/texture_cache.cc @@ -1013,6 +1013,7 @@ void TextureCache::BindingInfoFromFetchConstant( uint32_t total_width = width_minus_1 + 1; uint32_t row_width = xenos::kTexture2DCubeMaxWidthHeight; uint32_t num_rows = (total_width + row_width - 1) / row_width; + num_rows = std::min(num_rows, xenos::kTexture1DWideMaxRows); width_minus_1 = row_width - 1; height_minus_1 = num_rows - 1; // Disable mipmaps for wide 1D textures. The shader's coordinate remapping diff --git a/src/xenia/gpu/xenos.h b/src/xenia/gpu/xenos.h index d93dc6fc6..306a7732e 100644 --- a/src/xenia/gpu/xenos.h +++ b/src/xenia/gpu/xenos.h @@ -1185,6 +1185,9 @@ constexpr uint32_t kTextureSubresourceAlignmentBytes = // Texture fetch constant size field widths. constexpr uint32_t kTexture1DMaxWidthLog2 = 24; constexpr uint32_t kTexture1DMaxWidth = 1 << kTexture1DMaxWidthLog2; +// Limit the number of rows materialized when wide 1D textures are mapped to +// 2D. Some games use very large widths with much less data behind them. +constexpr uint32_t kTexture1DWideMaxRows = 32; constexpr uint32_t kTexture2DCubeMaxWidthHeightLog2 = 13; constexpr uint32_t kTexture2DCubeMaxWidthHeight = 1 << kTexture2DCubeMaxWidthHeightLog2;