From d119505289d540f61ae3d4ba6168f1145625277a Mon Sep 17 00:00:00 2001 From: goldislead <69987043+goldislead@users.noreply.github.com> Date: Tue, 30 Jun 2026 20:40:19 -0700 Subject: [PATCH] [GPU] Texture integer scaling fetches; 8_8_8_8_GAMMA resolve Integer num_format fetches now get their scale CPU-side instead of trying to guess in the shader. This is authoritative; no cvar. Both translators now use texture_integer_scale_bits after signs/gamma and before exponent bias. This alone fixes black screens and a bunch of rendering bugs across at least several dozen titles. This new information lives in the updated FormatInfo table, including fixed component widths. Resolve also has two new fixes. 8_8_8_8_GAMMA EDRAM sources can now decode through the PWL curve while still in linear space, before MSAA resolve or format conversion, then re-encoded for gamma destinations. This is also now default enabled via gamma_decode_pwl_resolve and has improved blowout in at least 4 titles, with no obvious regressions thus far. Full resolve can also now pack fixed destinations according to copy_dest_number. --- .../gpu/d3d12/d3d12_command_processor.cc | 7 + src/xenia/gpu/d3d12/d3d12_texture_cache.h | 8 +- src/xenia/gpu/draw_util.cc | 54 ++++- src/xenia/gpu/draw_util.h | 5 + src/xenia/gpu/dxbc_shader_translator.cc | 3 + src/xenia/gpu/dxbc_shader_translator.h | 11 +- src/xenia/gpu/dxbc_shader_translator_fetch.cc | 34 ++++ src/xenia/gpu/shaders/pixel_formats.xesli | 186 +++++++++++------- src/xenia/gpu/shaders/resolve.xesli | 180 ++++++++++++++++- .../gpu/shaders/resolve_full_16bpp.xesli | 3 +- .../gpu/shaders/resolve_full_32bpp.xesli | 3 +- .../gpu/shaders/resolve_full_64bpp.xesli | 3 +- src/xenia/gpu/shaders/resolve_full_8bpp.xesli | 4 +- src/xenia/gpu/shaders/xenos_draw.hlsli | 2 + src/xenia/gpu/spirv_shader_translator.cc | 7 + src/xenia/gpu/spirv_shader_translator.h | 10 +- .../gpu/spirv_shader_translator_fetch.cc | 76 +++++++ src/xenia/gpu/texture_cache.cc | 51 ++++- src/xenia/gpu/texture_cache.h | 13 ++ src/xenia/gpu/texture_info.h | 2 + src/xenia/gpu/texture_info_formats.cc | 8 +- src/xenia/gpu/texture_info_formats.inl | 141 +++++++------ .../gpu/vulkan/vulkan_command_processor.cc | 6 + src/xenia/gpu/vulkan/vulkan_texture_cache.h | 8 +- 24 files changed, 669 insertions(+), 156 deletions(-) diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index a68f77cd7..5fbe5b9bc 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -3992,6 +3992,13 @@ XE_NOINLINE void D3D12CommandProcessor::UpdateSystemConstantValues_Impl( texture_signs_uint = (texture_signs_uint & ~texture_signs_mask) | texture_signs_shifted; // cache misses here, we're accessing the texture bindings out of order + uint32_t texture_integer_scale_bits = + texture_cache_->GetActiveIntegerScaleBits(texture_index); + update_dirty_uint32_cmp( + system_constants_.texture_integer_scale_bits[texture_index], + texture_integer_scale_bits); + system_constants_.texture_integer_scale_bits[texture_index] = + texture_integer_scale_bits; textures_resolution_scaled |= uint32_t(texture_cache_->IsActiveTextureResolutionScaled(texture_index)) << texture_index; diff --git a/src/xenia/gpu/d3d12/d3d12_texture_cache.h b/src/xenia/gpu/d3d12/d3d12_texture_cache.h index 703d09247..804047a2e 100644 --- a/src/xenia/gpu/d3d12/d3d12_texture_cache.h +++ b/src/xenia/gpu/d3d12/d3d12_texture_cache.h @@ -172,10 +172,10 @@ class D3D12TextureCache final : public TextureCache { LoadShaderIndex load_shader_signed; // Do NOT add integer DXGI formats to this - they are not filterable, can - // only be read with Load, not Sample! If any game is seen using num_format - // 1 for fixed-point formats (for floating-point, it's normally set to 1 - // though), add a constant buffer containing multipliers for the - // textures and multiplication to the tfetch implementation. + // only be read with Load, not Sample! Games that fetch fixed-point formats + // are handled after sampling by scaling the normalized host value back to + // the guest integer range (see GetIntegerScaleBits). Keep these as + // sampled float/normalized views. // Whether the DXGI format, if not uncompressing the texture, consists of // blocks, thus copy regions must be aligned to block size (assuming it's diff --git a/src/xenia/gpu/draw_util.cc b/src/xenia/gpu/draw_util.cc index df5e110d9..5bafb7a43 100644 --- a/src/xenia/gpu/draw_util.cc +++ b/src/xenia/gpu/draw_util.cc @@ -28,6 +28,29 @@ DEFINE_bool( "is necessary for certain games to display the scene graphics).", "GPU"); +DEFINE_bool( + resolve_check_number_format, false, + "Require the destination number format to match before using fast color " + "resolves.\n" + "Fast resolves copy the exact EDRAM bits. If a title resolves unsigned " + "color data to a signed or integer destination, enabling this forces full " + "resolves in the shader so the destination gets repacked instead.\n" + "This can fix some garbage shading stemming from format mismatches, but " + "it's disabled by default because it can worsen performance in some games " + "that realistically don't need it.", + "GPU"); + +DEFINE_bool( + gamma_decode_pwl_resolve, true, + "During 8_8_8_8_GAMMA MSAA color resolves, average the samples in linear " + "space instead of averaging the encoded PWL gamma values directly.\n" + "This is separate from gamma_render_target_as_unorm16. It only applies " + "when a full shader resolve reads an 8_8_8_8_GAMMA EDRAM color source. " + "Compatible 8_8_8_8 destinations are written back as PWL gamma.\n" + "Leave enabled for games that otherwise look overexposed after gamma " + "MSAA resolves. Disable only if it causes a title-specific regression.", + "GPU"); + namespace xe { namespace gpu { namespace draw_util { @@ -1269,6 +1292,8 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory, color_edram_info.format = uint32_t(color_info.color_format); color_edram_info.format_is_64bpp = is_64bpp; color_edram_info.fill_half_pixel_offset = uint32_t(fill_half_pixel_offset); + color_edram_info.decode_pwl_gamma = + cvars::gamma_decode_pwl_resolve ? 1u : 0u; if ((fixed_rg16_truncated_to_minus_1_to_1 && color_info.color_format == xenos::ColorRenderTargetFormat::k_16_16) || (fixed_rgba16_truncated_to_minus_1_to_1 && @@ -1317,6 +1342,27 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory, return true; } XE_MSVC_OPTIMIZE_REVERT() + +// Raw resolve is only safe when the destination would read the same bits the +// active EDRAM view already stores. Canonical fixed colors are unsigned +// fractions and float colors are floats; signed/integer destinations need full +// resolve so copy_dest_number can actually repack them. +static constexpr bool ColorResolveNumberFormatMatches( + xenos::ColorFormat color_format, xenos::SurfaceNumberFormat num_format) { + switch (color_format) { + case xenos::ColorFormat::k_16_FLOAT: + case xenos::ColorFormat::k_16_16_FLOAT: + case xenos::ColorFormat::k_16_16_16_16_FLOAT: + case xenos::ColorFormat::k_32_FLOAT: + case xenos::ColorFormat::k_32_32_FLOAT: + case xenos::ColorFormat::k_32_32_32_32_FLOAT: + return num_format == xenos::SurfaceNumberFormat::kFloat; + default: + return num_format == + xenos::SurfaceNumberFormat::kUnsignedRepeatingFraction; + } +} + ResolveCopyShaderIndex ResolveInfo::GetCopyShader( uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y, ResolveCopyShaderConstants& constants_out, uint32_t& group_count_x_out, @@ -1325,12 +1371,18 @@ ResolveCopyShaderIndex ResolveInfo::GetCopyShader( bool is_depth = IsCopyingDepth(); ResolveEdramInfo edram_info = is_depth ? depth_edram_info : color_edram_info; bool source_is_64bpp = !is_depth && color_edram_info.format_is_64bpp != 0; + // Fast color resolve is a raw copy. If copy_dest_number asks for a different + // fixed interpretation, full resolve has to do the repack. if (is_depth || (!copy_dest_info.copy_dest_exp_bias && xenos::IsSingleCopySampleSelected( copy_dest_coordinate_info.copy_sample_select) && xenos::IsColorResolveFormatBitwiseEquivalent( xenos::ColorRenderTargetFormat(color_edram_info.format), - xenos::ColorFormat(copy_dest_info.copy_dest_format)))) { + xenos::ColorFormat(copy_dest_info.copy_dest_format)) && + (!cvars::resolve_check_number_format || + ColorResolveNumberFormatMatches( + xenos::ColorFormat(copy_dest_info.copy_dest_format), + copy_dest_info.copy_dest_number)))) { if (edram_info.msaa_samples >= xenos::MsaaSamples::k4X) { shader = source_is_64bpp ? ResolveCopyShaderIndex::kFast64bpp4xMSAA : ResolveCopyShaderIndex::kFast32bpp4xMSAA; diff --git a/src/xenia/gpu/draw_util.h b/src/xenia/gpu/draw_util.h index bd546592d..cf2301f6a 100644 --- a/src/xenia/gpu/draw_util.h +++ b/src/xenia/gpu/draw_util.h @@ -514,6 +514,11 @@ union ResolveEdramInfo { // of the resolve region with the contents of the first surely covered // column / row with resolution scaling. uint32_t fill_half_pixel_offset : 1; + // Flag from gamma_decode_pwl_resolve in resolve shader. Some games appear + // overexposed unless full 8_8_8_8_GAMMA resolves decode PWL gamma to + // linear before MSAA averaging / conversion, then write gamma bytes again + // for gamma dests. Off keeps the old byte averaging. + uint32_t decode_pwl_gamma : 1; }; ResolveEdramInfo() : packed(0) { static_assert_size(*this, sizeof(packed)); } }; diff --git a/src/xenia/gpu/dxbc_shader_translator.cc b/src/xenia/gpu/dxbc_shader_translator.cc index 4d01159e9..f00bd3fed 100644 --- a/src/xenia/gpu/dxbc_shader_translator.cc +++ b/src/xenia/gpu/dxbc_shader_translator.cc @@ -2178,6 +2178,9 @@ constexpr DxbcShaderTranslator::SystemConstantRdef {"xe_edram_blend_constant", ShaderRdefTypeIndex::kFloat4, sizeof(float) * 4}, + + {"xe_texture_integer_scale_bits", ShaderRdefTypeIndex::kUint4Array8, + sizeof(uint32_t) * 32}, }; void DxbcShaderTranslator::WriteResourceDefinition() { diff --git a/src/xenia/gpu/dxbc_shader_translator.h b/src/xenia/gpu/dxbc_shader_translator.h index f71473b43..5349d7297 100644 --- a/src/xenia/gpu/dxbc_shader_translator.h +++ b/src/xenia/gpu/dxbc_shader_translator.h @@ -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 = 0x20251216; + static constexpr uint32_t kVersion = 0x20260703; enum class DepthStencilMode : uint32_t { kNoModifiers, @@ -401,6 +401,13 @@ class DxbcShaderTranslator : public ShaderTranslator { // The constant blend factor for the respective modes. float edram_blend_constant[4]; + // Integer num_format on fixed textures. Each dword packs the scale needed + // to turn normalized host samples back into guest integer values. + // bits 0:3 = component_bits - 1 + // bit 4 = signed + // Zero means no scale. + uint32_t texture_integer_scale_bits[32]; + private: friend class DxbcShaderTranslator; @@ -454,6 +461,8 @@ class DxbcShaderTranslator : public ShaderTranslator { kEdramBlendConstant, + kTextureIntegerScaleBits, + kCount, }; static_assert( diff --git a/src/xenia/gpu/dxbc_shader_translator_fetch.cc b/src/xenia/gpu/dxbc_shader_translator_fetch.cc index 493b22a14..c4cbd78bb 100644 --- a/src/xenia/gpu/dxbc_shader_translator_fetch.cc +++ b/src/xenia/gpu/dxbc_shader_translator_fetch.cc @@ -2058,6 +2058,40 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( a_.OpBreak(); a_.OpEndSwitch(); } + // num_format is applied after signedness. A fixed-point format's host + // view returns normalized values, so for an integer num_format restore + // the guest integer range here. + uint32_t integer_scale_bits_temp = PushSystemTemp(); + uint32_t integer_scale_temp = PushSystemTemp(); + dxbc::Dest integer_scale_bits_dest(dxbc::Dest::R( + integer_scale_bits_temp, used_result_nonzero_components)); + dxbc::Src integer_scale_bits_src(dxbc::Src::R(integer_scale_bits_temp)); + dxbc::Dest integer_scale_dest( + dxbc::Dest::R(integer_scale_temp, used_result_nonzero_components)); + dxbc::Src integer_scale_src(dxbc::Src::R(integer_scale_temp)); + dxbc::Src integer_scale_bits_packed = LoadSystemConstant( + SystemConstants::Index::kTextureIntegerScaleBits, + offsetof(SystemConstants, texture_integer_scale_bits) + + sizeof(uint32_t) * tfetch_index, + dxbc::Src::kXXXX); + // Uniform early out. Zero means leave the sample alone. Only integer + // num_format on fixed textures has scale bits. + a_.OpIf(true, integer_scale_bits_packed); + a_.OpUBFE(integer_scale_bits_dest, dxbc::Src::LU(5), + dxbc::Src::LU(0, 5, 10, 15), integer_scale_bits_packed); + a_.OpAnd(integer_scale_dest, integer_scale_bits_src, dxbc::Src::LU(0xF)); + a_.OpIAdd(integer_scale_dest, integer_scale_src, dxbc::Src::LU(1)); + a_.OpUShR(integer_scale_bits_dest, integer_scale_bits_src, + dxbc::Src::LU(4)); + a_.OpIAdd(integer_scale_dest, integer_scale_src, -integer_scale_bits_src); + a_.OpIShL(integer_scale_dest, dxbc::Src::LU(1), integer_scale_src); + a_.OpIAdd(integer_scale_dest, integer_scale_src, dxbc::Src::LI(-1)); + a_.OpUToF(integer_scale_dest, integer_scale_src); + a_.OpMul( + dxbc::Dest::R(system_temp_result_, used_result_nonzero_components), + dxbc::Src::R(system_temp_result_), integer_scale_src); + a_.OpEndIf(); + PopSystemTemp(2); } if (signs_temp != UINT32_MAX) { PopSystemTemp(); diff --git a/src/xenia/gpu/shaders/pixel_formats.xesli b/src/xenia/gpu/shaders/pixel_formats.xesli index 3f6350848..90938c710 100644 --- a/src/xenia/gpu/shaders/pixel_formats.xesli +++ b/src/xenia/gpu/shaders/pixel_formats.xesli @@ -93,57 +93,103 @@ #define kXenosDepthRenderTargetFormat_D24S8 0u #define kXenosDepthRenderTargetFormat_D24FS8 1u +#define kXenosSurfaceNumberFormat_UnsignedRepeatingFraction 0u +#define kXenosSurfaceNumberFormat_SignedRepeatingFraction 1u +#define kXenosSurfaceNumberFormat_UnsignedInteger 2u +#define kXenosSurfaceNumberFormat_SignedInteger 3u +#define kXenosSurfaceNumberFormat_Float 7u + // ColorFormat packing, according to the Direct3D 11.3 functional specification. -uint XePackR5G5B5A1UNorm(float4_xe f) { - uint4_xe n = uint4_xe(saturate_xe(f) * float2_xe(31.0f, 1.0f).xxxy + 0.5f); +uint XePackFixed(float value, uint bits, uint num_format) { + uint unsigned_max = (1u << bits) - 1u; + uint packed = 0u; + if (num_format == kXenosSurfaceNumberFormat_SignedRepeatingFraction) { + // Signed fraction uses the positive endpoint, then keeps the destination + // bit width. + float positive_max = float((1u << (bits - 1u)) - 1u); + packed = uint(int(min(max(value, -1.0f), 1.0f) * positive_max + + (value >= 0.0f ? 0.5f : -0.5f))); + } else if (num_format == kXenosSurfaceNumberFormat_UnsignedInteger) { + packed = uint(min(max(value, 0.0f), float(unsigned_max)) + 0.5f); + } else if (num_format == kXenosSurfaceNumberFormat_SignedInteger) { + int signed_min = -int(1u << (bits - 1u)); + int signed_max = int((1u << (bits - 1u)) - 1u); + packed = uint(int(min(max(value, float(signed_min)), float(signed_max)) + + (value >= 0.0f ? 0.5f : -0.5f))); + } else { + // Unsigned fraction, float-on-fixed, or anything unexpected: keep the + // usual unsigned-fraction pack. + packed = uint(saturate_xe(value) * float(unsigned_max) + 0.5f); + } + return packed & unsigned_max; +} + +uint4_xe XePackFixed4(float4_xe f, uint4_xe bits, uint num_format) { + return uint4_xe(XePackFixed(f.x, bits.x, num_format), + XePackFixed(f.y, bits.y, num_format), + XePackFixed(f.z, bits.z, num_format), + XePackFixed(f.w, bits.w, num_format)); +} + +uint3_xe XePackFixed3(float3_xe f, uint3_xe bits, uint num_format) { + return uint3_xe(XePackFixed(f.x, bits.x, num_format), + XePackFixed(f.y, bits.y, num_format), + XePackFixed(f.z, bits.z, num_format)); +} + +uint2_xe XePackFixed2(float2_xe f, uint2_xe bits, uint num_format) { + return uint2_xe(XePackFixed(f.x, bits.x, num_format), + XePackFixed(f.y, bits.y, num_format)); +} + +uint XePackR5G5B5A1(float4_xe f, uint num_format) { + uint4_xe n = XePackFixed4(f, uint2_xe(5u, 1u).xxxy, num_format); return n.r | (n.g << 5) | (n.b << 10) | (n.a << 15); } -uint XePackR5G6B5UNorm(float3_xe f) { - uint3_xe n = uint3_xe(saturate_xe(f) * float3_xe(31.0f, 63.0f, 31.0f) + 0.5f); +uint XePackR5G6B5(float3_xe f, uint num_format) { + uint3_xe n = XePackFixed3(f, uint3_xe(5u, 6u, 5u), num_format); return n.r | (n.g << 5) | (n.b << 11); } -uint XePackR5G5B6UNorm(float3_xe f) { - uint3_xe n = uint3_xe(saturate_xe(f) * float3_xe(31.0f, 31.0f, 63.0f) + 0.5f); +uint XePackR5G5B6(float3_xe f, uint num_format) { + uint3_xe n = XePackFixed3(f, uint3_xe(5u, 5u, 6u), num_format); return n.r | (n.g << 5) | (n.b << 10); } -uint XePackR8G8B8A8UNorm(float4_xe f) { - uint4_xe n = uint4_xe(saturate_xe(f) * 255.0f + 0.5f); +uint XePackR8G8B8A8(float4_xe f, uint num_format) { + uint4_xe n = XePackFixed4(f, uint_x4_xe(8u), num_format); return n.r | (n.g << 8) | (n.b << 16) | (n.a << 24); } -uint XePackR10G10B10A2UNorm(float4_xe f) { - uint4_xe n = uint4_xe(saturate_xe(f) * float2_xe(1023.0f, 3.0f).xxxy + 0.5f); +uint XePackR10G10B10A2(float4_xe f, uint num_format) { + uint4_xe n = XePackFixed4(f, uint2_xe(10u, 2u).xxxy, num_format); return n.r | (n.g << 10) | (n.b << 20) | (n.a << 30); } -uint XePackR4G4B4A4UNorm(float4_xe f) { - uint4_xe n = uint4_xe(saturate_xe(f) * 15.0f + 0.5f); +uint XePackR4G4B4A4(float4_xe f, uint num_format) { + uint4_xe n = XePackFixed4(f, uint_x4_xe(4u), num_format); return n.r | (n.g << 4) | (n.b << 8) | (n.a << 12); } -uint XePackR11G11B10UNorm(float3_xe f) { - uint3_xe n = - uint3_xe(saturate_xe(f) * float3_xe(2047.0f, 2047.0f, 1023.0f) + 0.5); +uint XePackR11G11B10(float3_xe f, uint num_format) { + uint3_xe n = XePackFixed3(f, uint3_xe(11u, 11u, 10u), num_format); return n.r | (n.g << 11) | (n.b << 22); } -uint XePackR10G11B11UNorm(float3_xe f) { - uint3_xe n = - uint3_xe(saturate_xe(f) * float3_xe(1023.0f, 2047.0f, 2047.0f) + 0.5f); +uint XePackR10G11B11(float3_xe f, uint num_format) { + uint3_xe n = XePackFixed3(f, uint3_xe(10u, 11u, 11u), num_format); return n.r | (n.g << 10) | (n.b << 21); } -uint XePackR16G16UNorm(float2_xe f) { - uint2_xe n = uint2_xe(saturate_xe(f) * 65535.0f + 0.5f); +uint XePackR16G16(float2_xe f, uint num_format) { + uint2_xe n = XePackFixed2(f, uint_x2_xe(16u), num_format); return n.r | (n.g << 16); } -uint2_xe XePackR16G16B16A16UNorm(float4_xe f) { - uint4_xe n = uint4_xe(saturate_xe(f) * 65535.0f + 0.5f); +uint2_xe XePackR16G16B16A16(float4_xe f, uint num_format) { + uint4_xe n = XePackFixed4(f, uint_x4_xe(16u), num_format); return n.rb | (n.ga << 16); } @@ -162,40 +208,43 @@ uint2_xe XePackR16G16B16A16Edram(float4_xe f) { uint2_xe XePack16bpp4PixelsInUInt2(float4_xe pixel_0, float4_xe pixel_1, float4_xe pixel_2, float4_xe pixel_3, - uint format) { + uint format, uint num_format) { uint2_xe packed; switch (format) { case kXenosFormat_1_5_5_5: - packed.x = XePackR5G5B5A1UNorm(pixel_0) | - (XePackR5G5B5A1UNorm(pixel_1) << 16u); - packed.y = XePackR5G5B5A1UNorm(pixel_2) | - (XePackR5G5B5A1UNorm(pixel_3) << 16u); + packed.x = XePackR5G5B5A1(pixel_0, num_format) | + (XePackR5G5B5A1(pixel_1, num_format) << 16u); + packed.y = XePackR5G5B5A1(pixel_2, num_format) | + (XePackR5G5B5A1(pixel_3, num_format) << 16u); break; case kXenosFormat_5_6_5: - packed.x = XePackR5G6B5UNorm(pixel_0.rgb) | - (XePackR5G6B5UNorm(pixel_1.rgb) << 16u); - packed.y = XePackR5G6B5UNorm(pixel_2.rgb) | - (XePackR5G6B5UNorm(pixel_3.rgb) << 16u); + packed.x = XePackR5G6B5(pixel_0.rgb, num_format) | + (XePackR5G6B5(pixel_1.rgb, num_format) << 16u); + packed.y = XePackR5G6B5(pixel_2.rgb, num_format) | + (XePackR5G6B5(pixel_3.rgb, num_format) << 16u); break; case kXenosFormat_6_5_5: - packed.x = XePackR5G5B6UNorm(pixel_0.rgb) | - (XePackR5G5B6UNorm(pixel_1.rgb) << 16u); - packed.y = XePackR5G5B6UNorm(pixel_2.rgb) | - (XePackR5G5B6UNorm(pixel_3.rgb) << 16u); + packed.x = XePackR5G5B6(pixel_0.rgb, num_format) | + (XePackR5G5B6(pixel_1.rgb, num_format) << 16u); + packed.y = XePackR5G5B6(pixel_2.rgb, num_format) | + (XePackR5G5B6(pixel_3.rgb, num_format) << 16u); break; case kXenosFormat_8_8: - packed.x = XePackR8G8B8A8UNorm(float4_xe(pixel_0.rg, pixel_1.rg)); - packed.y = XePackR8G8B8A8UNorm(float4_xe(pixel_2.rg, pixel_3.rg)); + packed.x = + XePackR8G8B8A8(float4_xe(pixel_0.rg, pixel_1.rg), num_format); + packed.y = + XePackR8G8B8A8(float4_xe(pixel_2.rg, pixel_3.rg), num_format); break; case kXenosFormat_4_4_4_4: - packed.x = XePackR4G4B4A4UNorm(pixel_0) | - (XePackR4G4B4A4UNorm(pixel_1) << 16u); - packed.y = XePackR4G4B4A4UNorm(pixel_2) | - (XePackR4G4B4A4UNorm(pixel_3) << 16u); + packed.x = XePackR4G4B4A4(pixel_0, num_format) | + (XePackR4G4B4A4(pixel_1, num_format) << 16u); + packed.y = XePackR4G4B4A4(pixel_2, num_format) | + (XePackR4G4B4A4(pixel_3, num_format) << 16u); break; case kXenosFormat_16: - packed = XePackR16G16B16A16UNorm(float4_xe(pixel_0.r, pixel_1.r, - pixel_2.r, pixel_3.r)); + packed = XePackR16G16B16A16(float4_xe(pixel_0.r, pixel_1.r, pixel_2.r, + pixel_3.r), + num_format); break; default: // Treat as something (16_FLOAT). @@ -208,45 +257,45 @@ uint2_xe XePack16bpp4PixelsInUInt2(float4_xe pixel_0, float4_xe pixel_1, uint4_xe XePack32bpp4Pixels(float4_xe pixel_0, float4_xe pixel_1, float4_xe pixel_2, float4_xe pixel_3, - uint format) { + uint format, uint num_format) { uint4_xe packed; switch (format) { case kXenosFormat_8_8_8_8: // TODO(Triang3l): Investigate 8_8_8_8_A. case kXenosFormat_8_8_8_8_A: case kXenosFormat_8_8_8_8_AS_16_16_16_16: - packed.x = XePackR8G8B8A8UNorm(pixel_0); - packed.y = XePackR8G8B8A8UNorm(pixel_1); - packed.z = XePackR8G8B8A8UNorm(pixel_2); - packed.w = XePackR8G8B8A8UNorm(pixel_3); + packed.x = XePackR8G8B8A8(pixel_0, num_format); + packed.y = XePackR8G8B8A8(pixel_1, num_format); + packed.z = XePackR8G8B8A8(pixel_2, num_format); + packed.w = XePackR8G8B8A8(pixel_3, num_format); break; case kXenosFormat_2_10_10_10: case kXenosFormat_2_10_10_10_AS_16_16_16_16: - packed.x = XePackR10G10B10A2UNorm(pixel_0); - packed.y = XePackR10G10B10A2UNorm(pixel_1); - packed.z = XePackR10G10B10A2UNorm(pixel_2); - packed.w = XePackR10G10B10A2UNorm(pixel_3); + packed.x = XePackR10G10B10A2(pixel_0, num_format); + packed.y = XePackR10G10B10A2(pixel_1, num_format); + packed.z = XePackR10G10B10A2(pixel_2, num_format); + packed.w = XePackR10G10B10A2(pixel_3, num_format); break; case kXenosFormat_10_11_11: case kXenosFormat_10_11_11_AS_16_16_16_16: - packed.x = XePackR11G11B10UNorm(pixel_0.rgb); - packed.y = XePackR11G11B10UNorm(pixel_1.rgb); - packed.z = XePackR11G11B10UNorm(pixel_2.rgb); - packed.w = XePackR11G11B10UNorm(pixel_3.rgb); + packed.x = XePackR11G11B10(pixel_0.rgb, num_format); + packed.y = XePackR11G11B10(pixel_1.rgb, num_format); + packed.z = XePackR11G11B10(pixel_2.rgb, num_format); + packed.w = XePackR11G11B10(pixel_3.rgb, num_format); break; case kXenosFormat_11_11_10: case kXenosFormat_11_11_10_AS_16_16_16_16: - packed.x = XePackR10G11B11UNorm(pixel_0.rgb); - packed.y = XePackR10G11B11UNorm(pixel_1.rgb); - packed.z = XePackR10G11B11UNorm(pixel_2.rgb); - packed.w = XePackR10G11B11UNorm(pixel_3.rgb); + packed.x = XePackR10G11B11(pixel_0.rgb, num_format); + packed.y = XePackR10G11B11(pixel_1.rgb, num_format); + packed.z = XePackR10G11B11(pixel_2.rgb, num_format); + packed.w = XePackR10G11B11(pixel_3.rgb, num_format); break; case kXenosFormat_16_16_EDRAM: case kXenosFormat_16_16: - packed.x = XePackR16G16UNorm(pixel_0.rg); - packed.y = XePackR16G16UNorm(pixel_1.rg); - packed.z = XePackR16G16UNorm(pixel_2.rg); - packed.w = XePackR16G16UNorm(pixel_3.rg); + packed.x = XePackR16G16(pixel_0.rg, num_format); + packed.y = XePackR16G16(pixel_1.rg, num_format); + packed.z = XePackR16G16(pixel_2.rg, num_format); + packed.w = XePackR16G16(pixel_3.rg, num_format); break; case kXenosFormat_16_16_FLOAT: packed.x = pack_half_2x16_xe(float2_xe(pixel_0.r, pixel_0.g)); @@ -267,15 +316,16 @@ uint4_xe XePack32bpp4Pixels(float4_xe pixel_0, float4_xe pixel_1, void XePack64bpp4Pixels(float4_xe pixel_0, float4_xe pixel_1, float4_xe pixel_2, float4_xe pixel_3, uint format, + uint num_format, out_param_xe(uint4_xe, packed_01), out_param_xe(uint4_xe, packed_23)) { switch (format) { case kXenosFormat_16_16_16_16_EDRAM: case kXenosFormat_16_16_16_16: - packed_01.xy = XePackR16G16B16A16UNorm(pixel_0); - packed_01.zw = XePackR16G16B16A16UNorm(pixel_1); - packed_23.xy = XePackR16G16B16A16UNorm(pixel_2); - packed_23.zw = XePackR16G16B16A16UNorm(pixel_3); + packed_01.xy = XePackR16G16B16A16(pixel_0, num_format); + packed_01.zw = XePackR16G16B16A16(pixel_1, num_format); + packed_23.xy = XePackR16G16B16A16(pixel_2, num_format); + packed_23.zw = XePackR16G16B16A16(pixel_3, num_format); break; case kXenosFormat_16_16_16_16_FLOAT: packed_01.x = pack_half_2x16_xe(float2_xe(pixel_0.r, pixel_0.g)); diff --git a/src/xenia/gpu/shaders/resolve.xesli b/src/xenia/gpu/shaders/resolve.xesli index f91cfa9cd..d4b812c79 100644 --- a/src/xenia/gpu/shaders/resolve.xesli +++ b/src/xenia/gpu/shaders/resolve.xesli @@ -70,6 +70,7 @@ struct XeResolveInfo { uint edram_base_tiles; uint edram_format; uint edram_format_ints_log2; + bool decode_pwl_gamma; uint2_xe resolution_scale; uint2_xe half_pixel_offset_fill_source; uint2_xe edram_offset_scaled; @@ -81,6 +82,7 @@ struct XeResolveInfo { bool dest_is_array; uint dest_slice; uint dest_format; + uint dest_num_format; float dest_exp_bias_factor; bool dest_swap; uint dest_row_pitch_macro_tiles; @@ -101,6 +103,7 @@ XeResolveInfo XeResolveGetInfo(param_push_consts_xe) { resolve_info.edram_base_tiles = (edram_info >> 13u) & ((1u << 11u) - 1u); resolve_info.edram_format = (edram_info >> 24u) & ((1u << 4u) - 1u); resolve_info.edram_format_ints_log2 = (edram_info >> 28u) & 1u; + resolve_info.decode_pwl_gamma = (edram_info & (1u << 30u)) != 0u; #ifdef XE_RESOLVE_RESOLUTION_SCALED resolve_info.resolution_scale = (uint_x2_xe(coordinate_info) >> uint2_xe(16u, 19u)) & 7u; @@ -132,6 +135,7 @@ XeResolveInfo XeResolveGetInfo(param_push_consts_xe) { resolve_info.dest_is_array = (dest_info & (1u << 3u)) != 0u; resolve_info.dest_slice = (dest_info >> 4u) & ((1u << 3u) - 1u); resolve_info.dest_format = (dest_info >> 7u) & ((1u << 6u) - 1u); + resolve_info.dest_num_format = (dest_info >> 13u) & ((1u << 3u) - 1u); resolve_info.dest_exp_bias_factor = int_bits_to_float_xe( (int(dest_info) << (32 - (16 + 6)) >> (32 - 6) << 23) + float_bits_to_int_xe(1.0f)); @@ -580,6 +584,127 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { } } + // Gamma RTs store linear 10 bit color through an 8 bit PWL curve. + // Host sRGB is not a substitute. Some titles appear overexposed if full + // 8_8_8_8_GAMMA resolves average the encoded bytes directly (5345085D, + // 45410934), so we decode before MSAA averaging and encode again only + // for compatible 8_8_8_8 destinations. + float XePWLGammaToLinear(float gamma) { + gamma = saturate_xe(gamma); + float scale; + float offset; + if (gamma >= 96.0f / 255.0f) { + if (gamma >= 192.0f / 255.0f) { + scale = 8.0f / 1024.0f; + offset = -1024.0f; + } else { + scale = 4.0f / 1024.0f; + offset = -256.0f; + } + } else { + if (gamma >= 64.0f / 255.0f) { + scale = 2.0f / 1024.0f; + offset = -64.0f; + } else { + scale = 1.0f / 1024.0f; + offset = 0.0f; + } + } + float linear_value = gamma * (255.0f * 1024.0f) * scale + offset; + linear_value += trunc(linear_value * scale); + return linear_value * (1.0f / 1023.0f); + } + + float3_xe XePWLGammaToLinear3(float3_xe gamma) { + return float3_xe(XePWLGammaToLinear(gamma.x), + XePWLGammaToLinear(gamma.y), + XePWLGammaToLinear(gamma.z)); + } + + float4_xe XePWLGammaToLinear4(float4_xe gamma) { + return float4_xe(XePWLGammaToLinear(gamma.x), + XePWLGammaToLinear(gamma.y), + XePWLGammaToLinear(gamma.z), + XePWLGammaToLinear(gamma.w)); + } + + float XeLinearToPWLGamma(float linear_value) { + linear_value = saturate_xe(linear_value); + float scale; + float offset; + if (linear_value >= 128.0f / 1023.0f) { + if (linear_value >= 512.0f / 1023.0f) { + scale = 1023.0f / 8.0f; + offset = 128.0f / 255.0f; + } else { + scale = 1023.0f / 4.0f; + offset = 64.0f / 255.0f; + } + } else { + if (linear_value >= 64.0f / 1023.0f) { + scale = 1023.0f / 2.0f; + offset = 32.0f / 255.0f; + } else { + scale = 1023.0f; + offset = 0.0f; + } + } + return trunc(linear_value * scale) * (1.0f / 255.0f) + offset; + } + + float3_xe XeLinearToPWLGamma3(float3_xe linear_value) { + return float3_xe(XeLinearToPWLGamma(linear_value.x), + XeLinearToPWLGamma(linear_value.y), + XeLinearToPWLGamma(linear_value.z)); + } + + bool XeResolveSourceUsesPWLGamma(XeResolveInfo resolve_info) { + return resolve_info.decode_pwl_gamma && + resolve_info.edram_format == + kXenosColorRenderTargetFormat_8_8_8_8_GAMMA; + } + + bool XeResolveDestStoresPWLGamma(XeResolveInfo resolve_info) { + // Resolve constants don't carry a destination gamma bit. Treat an + // unsigned 8_8_8_8 destination as PWL gamma storage. + bool dest_number_is_unorm = + resolve_info.dest_num_format == + kXenosSurfaceNumberFormat_UnsignedRepeatingFraction; + bool dest_is_8888 = + resolve_info.dest_format == kXenosFormat_8_8_8_8 || + resolve_info.dest_format == kXenosFormat_8_8_8_8_A || + resolve_info.dest_format == + kXenosFormat_8_8_8_8_AS_16_16_16_16; + return dest_number_is_unorm && dest_is_8888; + } + + void XeResolvePWLGammaToLinearRGB(inout_param_xe(float4_xe, pixel)) { + pixel.rgb = XePWLGammaToLinear3(pixel.rgb); + } + + void XeResolveLinearToPWLGammaRGB(inout_param_xe(float4_xe, pixel)) { + pixel.rgb = XeLinearToPWLGamma3(pixel.rgb); + } + + void XeResolveDecodePWLGammaSource( + XeResolveInfo resolve_info, inout_param_xe(float4_xe, pixel)) { + // Source gamma is RGB only. 8_8_8_8_GAMMA still stores alpha as ordinary + // fixed data, so alpha needs to stay with normal resolve. + dont_flatten_xe if (XeResolveSourceUsesPWLGamma(resolve_info)) { + XeResolvePWLGammaToLinearRGB(pixel); + } + } + + void XeResolveEncodePWLGammaDest( + XeResolveInfo resolve_info, inout_param_xe(float4_xe, pixel)) { + // Only re-encode when the source was PWL gamma and the destination is the + // 8_8_8_8 UNORM storage we treat as the same PWL byte stream. + dont_flatten_xe if (XeResolveSourceUsesPWLGamma(resolve_info) && + XeResolveDestStoresPWLGamma(resolve_info)) { + XeResolveLinearToPWLGammaRGB(pixel); + } + } + void XeResolveLoad2RGBAColors( param_byte_buffer_xe(xe_resolve_edram) param_next_after_byte_buffer_xe XeResolveInfo resolve_info, uint address_bytes, @@ -590,11 +715,12 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { address_bytes, pixel_stride_bytes, resolve_info.edram_format_ints_log2, resolve_info.edram_format, pixel_0, pixel_1); + XeResolveDecodePWLGammaSource(resolve_info, pixel_0); + XeResolveDecodePWLGammaSource(resolve_info, pixel_1); float exp_bias = resolve_info.dest_exp_bias_factor; dont_flatten_xe if (resolve_info.sample_select >= kXenosCopySampleSelect_01) { uint tile_row_stride_bytes = 4u * 80u * resolve_info.resolution_scale.x; - // TODO(Triang3l): Gamma-correct resolve for 8_8_8_8_GAMMA. exp_bias *= 0.5f; float4_xe msaa_resolve_pixel_0, msaa_resolve_pixel_1; XeResolveLoad2RGBAUnswappedPixelSamplesFromRaw( @@ -602,6 +728,8 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { address_bytes + tile_row_stride_bytes, pixel_stride_bytes, resolve_info.edram_format_ints_log2, resolve_info.edram_format, msaa_resolve_pixel_0, msaa_resolve_pixel_1); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_0); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_1); pixel_0 += msaa_resolve_pixel_0; pixel_1 += msaa_resolve_pixel_1; dont_flatten_xe @@ -614,6 +742,8 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { address_bytes + sample_stride_bytes, pixel_stride_bytes, resolve_info.edram_format_ints_log2, resolve_info.edram_format, msaa_resolve_pixel_0, msaa_resolve_pixel_1); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_0); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_1); pixel_0 += msaa_resolve_pixel_0; pixel_1 += msaa_resolve_pixel_1; XeResolveLoad2RGBAUnswappedPixelSamplesFromRaw( @@ -623,12 +753,16 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { pixel_stride_bytes, resolve_info.edram_format_ints_log2, resolve_info.edram_format, msaa_resolve_pixel_0, msaa_resolve_pixel_1); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_0); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_1); pixel_0 += msaa_resolve_pixel_0; pixel_1 += msaa_resolve_pixel_1; } } pixel_0 *= exp_bias; pixel_1 *= exp_bias; + XeResolveEncodePWLGammaDest(resolve_info, pixel_0); + XeResolveEncodePWLGammaDest(resolve_info, pixel_1); dont_flatten_xe if (resolve_info.dest_swap) { pixel_0 = pixel_0.bgra; pixel_1 = pixel_1.bgra; @@ -646,11 +780,14 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { address_bytes, pixel_stride_bytes, resolve_info.edram_format_ints_log2, resolve_info.edram_format, pixel_0, pixel_1, pixel_2, pixel_3); + XeResolveDecodePWLGammaSource(resolve_info, pixel_0); + XeResolveDecodePWLGammaSource(resolve_info, pixel_1); + XeResolveDecodePWLGammaSource(resolve_info, pixel_2); + XeResolveDecodePWLGammaSource(resolve_info, pixel_3); float exp_bias = resolve_info.dest_exp_bias_factor; dont_flatten_xe if (resolve_info.sample_select >= kXenosCopySampleSelect_01) { uint tile_row_stride_bytes = 4u * 80u * resolve_info.resolution_scale.x; - // TODO(Triang3l): Gamma-correct resolve for 8_8_8_8_GAMMA. exp_bias *= 0.5f; float4_xe msaa_resolve_pixel_0; float4_xe msaa_resolve_pixel_1; @@ -662,6 +799,10 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { resolve_info.edram_format_ints_log2, resolve_info.edram_format, msaa_resolve_pixel_0, msaa_resolve_pixel_1, msaa_resolve_pixel_2, msaa_resolve_pixel_3); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_0); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_1); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_2); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_3); pixel_0 += msaa_resolve_pixel_0; pixel_1 += msaa_resolve_pixel_1; pixel_2 += msaa_resolve_pixel_2; @@ -677,6 +818,10 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { resolve_info.edram_format_ints_log2, resolve_info.edram_format, msaa_resolve_pixel_0, msaa_resolve_pixel_1, msaa_resolve_pixel_2, msaa_resolve_pixel_3); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_0); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_1); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_2); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_3); pixel_0 += msaa_resolve_pixel_0; pixel_1 += msaa_resolve_pixel_1; pixel_2 += msaa_resolve_pixel_2; @@ -688,6 +833,10 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { pixel_stride_bytes, resolve_info.edram_format_ints_log2, resolve_info.edram_format, msaa_resolve_pixel_0, msaa_resolve_pixel_1, msaa_resolve_pixel_2, msaa_resolve_pixel_3); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_0); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_1); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_2); + XeResolveDecodePWLGammaSource(resolve_info, msaa_resolve_pixel_3); pixel_0 += msaa_resolve_pixel_0; pixel_1 += msaa_resolve_pixel_1; pixel_2 += msaa_resolve_pixel_2; @@ -698,6 +847,10 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { pixel_1 *= exp_bias; pixel_2 *= exp_bias; pixel_3 *= exp_bias; + XeResolveEncodePWLGammaDest(resolve_info, pixel_0); + XeResolveEncodePWLGammaDest(resolve_info, pixel_1); + XeResolveEncodePWLGammaDest(resolve_info, pixel_2); + XeResolveEncodePWLGammaDest(resolve_info, pixel_3); dont_flatten_xe if (resolve_info.dest_swap) { pixel_0 = pixel_0.bgra; pixel_1 = pixel_1.bgra; @@ -721,11 +874,14 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { address_bytes, pixel_stride_bytes, resolve_info.edram_format_ints_log2, resolve_info.edram_format, resolve_info.dest_swap, pixels_0123, pixels_4567); + dont_flatten_xe if (XeResolveSourceUsesPWLGamma(resolve_info)) { + pixels_0123 = XePWLGammaToLinear4(pixels_0123); + pixels_4567 = XePWLGammaToLinear4(pixels_4567); + } float exp_bias = resolve_info.dest_exp_bias_factor; dont_flatten_xe if (resolve_info.sample_select >= kXenosCopySampleSelect_01) { uint tile_row_stride_bytes = 4u * 80u * resolve_info.resolution_scale.x; - // TODO(Triang3l): Gamma-correct resolve for 8_8_8_8_GAMMA. exp_bias *= 0.5f; float4_xe msaa_resolve_pixels_0123, msaa_resolve_pixels_4567; XeResolveLoad8RedPixelSamplesFromRaw( @@ -734,6 +890,12 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { resolve_info.edram_format_ints_log2, resolve_info.edram_format, resolve_info.dest_swap, msaa_resolve_pixels_0123, msaa_resolve_pixels_4567); + dont_flatten_xe if (XeResolveSourceUsesPWLGamma(resolve_info)) { + msaa_resolve_pixels_0123 = + XePWLGammaToLinear4(msaa_resolve_pixels_0123); + msaa_resolve_pixels_4567 = + XePWLGammaToLinear4(msaa_resolve_pixels_4567); + } pixels_0123 += msaa_resolve_pixels_0123; pixels_4567 += msaa_resolve_pixels_4567; dont_flatten_xe @@ -747,6 +909,12 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { resolve_info.edram_format_ints_log2, resolve_info.edram_format, resolve_info.dest_swap, msaa_resolve_pixels_0123, msaa_resolve_pixels_4567); + dont_flatten_xe if (XeResolveSourceUsesPWLGamma(resolve_info)) { + msaa_resolve_pixels_0123 = + XePWLGammaToLinear4(msaa_resolve_pixels_0123); + msaa_resolve_pixels_4567 = + XePWLGammaToLinear4(msaa_resolve_pixels_4567); + } pixels_0123 += msaa_resolve_pixels_0123; pixels_4567 += msaa_resolve_pixels_4567; XeResolveLoad8RedPixelSamplesFromRaw( @@ -756,6 +924,12 @@ uint XeResolveEdramPixelStrideBytes(XeResolveInfo resolve_info) { pixel_stride_bytes, resolve_info.edram_format_ints_log2, resolve_info.edram_format, resolve_info.dest_swap, msaa_resolve_pixels_0123, msaa_resolve_pixels_4567); + dont_flatten_xe if (XeResolveSourceUsesPWLGamma(resolve_info)) { + msaa_resolve_pixels_0123 = + XePWLGammaToLinear4(msaa_resolve_pixels_0123); + msaa_resolve_pixels_4567 = + XePWLGammaToLinear4(msaa_resolve_pixels_4567); + } pixels_0123 += msaa_resolve_pixels_0123; pixels_4567 += msaa_resolve_pixels_4567; } diff --git a/src/xenia/gpu/shaders/resolve_full_16bpp.xesli b/src/xenia/gpu/shaders/resolve_full_16bpp.xesli index b5bc1af4f..b31cfe229 100644 --- a/src/xenia/gpu/shaders/resolve_full_16bpp.xesli +++ b/src/xenia/gpu/shaders/resolve_full_16bpp.xesli @@ -42,7 +42,8 @@ entry_inputs_end_code_begin_compute_xe resolve_info, pixel_index), pixel_0, pixel_1, pixel_2, pixel_3); uint2_xe packed = XePack16bpp4PixelsInUInt2( - pixel_0, pixel_1, pixel_2, pixel_3, resolve_info.dest_format); + pixel_0, pixel_1, pixel_2, pixel_3, resolve_info.dest_format, + resolve_info.dest_num_format); dont_flatten_xe if (pixel_index.x == 0u && resolve_info.half_pixel_offset_fill_source.x != 0u) { diff --git a/src/xenia/gpu/shaders/resolve_full_32bpp.xesli b/src/xenia/gpu/shaders/resolve_full_32bpp.xesli index ae3e18fc3..428255ab4 100644 --- a/src/xenia/gpu/shaders/resolve_full_32bpp.xesli +++ b/src/xenia/gpu/shaders/resolve_full_32bpp.xesli @@ -44,7 +44,8 @@ entry_inputs_end_code_begin_compute_xe resolve_info, pixel_index), pixel_0, pixel_1, pixel_2, pixel_3); uint4_xe packed = XePack32bpp4Pixels(pixel_0, pixel_1, pixel_2, pixel_3, - resolve_info.dest_format); + resolve_info.dest_format, + resolve_info.dest_num_format); dont_flatten_xe if (pixel_index.x == 0u && resolve_info.half_pixel_offset_fill_source.x != 0u) { diff --git a/src/xenia/gpu/shaders/resolve_full_64bpp.xesli b/src/xenia/gpu/shaders/resolve_full_64bpp.xesli index 63a0fd4e3..fb0ce8440 100644 --- a/src/xenia/gpu/shaders/resolve_full_64bpp.xesli +++ b/src/xenia/gpu/shaders/resolve_full_64bpp.xesli @@ -43,7 +43,8 @@ entry_inputs_end_code_begin_compute_xe pixel_0, pixel_1, pixel_2, pixel_3); uint4_xe packed_01, packed_23; XePack64bpp4Pixels(pixel_0, pixel_1, pixel_2, pixel_3, - resolve_info.dest_format, packed_01, packed_23); + resolve_info.dest_format, + resolve_info.dest_num_format, packed_01, packed_23); dont_flatten_xe if (pixel_index.x == 0u && resolve_info.half_pixel_offset_fill_source.x != 0u) { diff --git a/src/xenia/gpu/shaders/resolve_full_8bpp.xesli b/src/xenia/gpu/shaders/resolve_full_8bpp.xesli index 29224d963..f2d1fcd6b 100644 --- a/src/xenia/gpu/shaders/resolve_full_8bpp.xesli +++ b/src/xenia/gpu/shaders/resolve_full_8bpp.xesli @@ -56,7 +56,7 @@ entry_inputs_end_code_begin_compute_xe // TODO(Triang3l): Investigate formats 8_A and 8_B. byte_buffer_align8_store8_xe( xe_resolve_dest, XeResolveDestPixelAddress(resolve_info, pixel_index, 0u), - uint2_xe(XePackR8G8B8A8UNorm(pixels_0123), - XePackR8G8B8A8UNorm(pixels_4567))); + uint2_xe(XePackR8G8B8A8(pixels_0123, resolve_info.dest_num_format), + XePackR8G8B8A8(pixels_4567, resolve_info.dest_num_format))); } entry_code_end_compute_xe diff --git a/src/xenia/gpu/shaders/xenos_draw.hlsli b/src/xenia/gpu/shaders/xenos_draw.hlsli index a866ad460..69e6f3c18 100644 --- a/src/xenia/gpu/shaders/xenos_draw.hlsli +++ b/src/xenia/gpu/shaders/xenos_draw.hlsli @@ -49,6 +49,8 @@ cbuffer xe_system_cbuffer : register(b0) { uint4 xe_edram_rt_blend_factors_ops; float4 xe_edram_blend_constant; + + uint4 xe_texture_integer_scale_bits[8]; }; struct XeHSControlPointInputIndexed { diff --git a/src/xenia/gpu/spirv_shader_translator.cc b/src/xenia/gpu/spirv_shader_translator.cc index 1171e365a..27e897772 100644 --- a/src/xenia/gpu/spirv_shader_translator.cc +++ b/src/xenia/gpu/spirv_shader_translator.cc @@ -252,6 +252,10 @@ void SpirvShaderTranslator::StartTranslation() { type_uint4_, builder_->makeUintConstant(4), sizeof(uint32_t) * 4); builder_->addDecoration(type_uint4_array_4, spv::DecorationArrayStride, sizeof(uint32_t) * 4); + spv::Id type_uint4_array_8 = builder_->makeArrayType( + type_uint4_, builder_->makeUintConstant(8), sizeof(uint32_t) * 4); + builder_->addDecoration(type_uint4_array_8, spv::DecorationArrayStride, + sizeof(uint32_t) * 4); spv::Id type_float4_array_6 = builder_->makeArrayType( type_float4_, builder_->makeUintConstant(6), sizeof(float) * 4); builder_->addDecoration(type_float4_array_6, spv::DecorationArrayStride, @@ -317,6 +321,9 @@ void SpirvShaderTranslator::StartTranslation() { type_float4_array_4}, {"edram_blend_constant", offsetof(SystemConstants, edram_blend_constant), type_float4_}, + {"texture_integer_scale_bits", + offsetof(SystemConstants, texture_integer_scale_bits), + type_uint4_array_8}, }; id_vector_temp_.clear(); id_vector_temp_.reserve(xe::countof(system_constants)); diff --git a/src/xenia/gpu/spirv_shader_translator.h b/src/xenia/gpu/spirv_shader_translator.h index f3214bdd0..8168eb8c1 100644 --- a/src/xenia/gpu/spirv_shader_translator.h +++ b/src/xenia/gpu/spirv_shader_translator.h @@ -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 = 8; + static constexpr uint32_t kVersion = 9; enum class DepthStencilMode : uint32_t { kNoModifiers, @@ -275,6 +275,13 @@ class SpirvShaderTranslator : public ShaderTranslator { // The constant blend factor for the respective modes. float edram_blend_constant[4]; + + // Integer num_format on fixed textures. Each dword packs the scale needed + // to turn normalized host samples back into guest integer values. + // bits 0:3 = component_bits - 1 + // bit 4 = signed. + // Zero means no scale. + uint32_t texture_integer_scale_bits[32]; }; // Separate constant buffer for user clip planes @@ -905,6 +912,7 @@ class SpirvShaderTranslator : public ShaderTranslator { kSystemConstantEdramRTKeepMask, kSystemConstantEdramRTClamp, kSystemConstantEdramBlendConstant, + kSystemConstantTextureIntegerScaleBits, }; spv::Id uniform_system_constants_; spv::Id uniform_clip_plane_constants_; diff --git a/src/xenia/gpu/spirv_shader_translator_fetch.cc b/src/xenia/gpu/spirv_shader_translator_fetch.cc index e0f519275..7cd5d475b 100644 --- a/src/xenia/gpu/spirv_shader_translator_fetch.cc +++ b/src/xenia/gpu/spirv_shader_translator_fetch.cc @@ -2308,6 +2308,82 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction( } } + // num_format is applied after signs/gamma. Fixed textures sample as + // normalized host values, so integer num_format scales them back to + // guest integer units here. + id_vector_temp_.clear(); + id_vector_temp_.push_back( + builder_->makeIntConstant(kSystemConstantTextureIntegerScaleBits)); + id_vector_temp_.push_back( + builder_->makeIntConstant(int32_t(fetch_constant_index >> 2))); + id_vector_temp_.push_back( + builder_->makeIntConstant(int32_t(fetch_constant_index & 3))); + spv::Id integer_scale_bits_packed = builder_->createLoad( + builder_->createAccessChain(spv::StorageClassUniform, + uniform_system_constants_, + id_vector_temp_), + spv::NoPrecision); + { + // Uniform early out. Zero means leave the sample alone. Only integer + // num_format on fixed textures has scale bits. + spv::Id integer_scale_active = builder_->createBinOp( + spv::OpINotEqual, type_bool_, integer_scale_bits_packed, + builder_->makeUintConstant(0)); + SpirvBuilder::IfBuilder if_integer_scale( + integer_scale_active, spv::SelectionControlMaskNone, *builder_); + spv::Id scaled_result[4] = {}; + { + spv::Id const_uint_1 = builder_->makeUintConstant(1); + uint32_t result_remaining_components = + used_result_nonzero_components; + uint32_t result_component_index; + while (xe::bit_scan_forward(result_remaining_components, + &result_component_index)) { + result_remaining_components &= + ~(UINT32_C(1) << result_component_index); + spv::Id scale_bits = builder_->createTriOp( + spv::OpBitFieldUExtract, type_uint_, + integer_scale_bits_packed, + builder_->makeUintConstant(result_component_index * 5), + builder_->makeUintConstant(5)); + spv::Id scale_shift = builder_->createBinOp( + spv::OpIAdd, type_uint_, + builder_->createBinOp(spv::OpBitwiseAnd, type_uint_, + scale_bits, + builder_->makeUintConstant(0xF)), + const_uint_1); + scale_shift = builder_->createBinOp( + spv::OpISub, type_uint_, scale_shift, + builder_->createTriOp(spv::OpBitFieldUExtract, type_uint_, + scale_bits, + builder_->makeUintConstant(4), + builder_->makeUintConstant(1))); + spv::Id scale_uint = builder_->createBinOp( + spv::OpISub, type_uint_, + builder_->createBinOp(spv::OpShiftLeftLogical, type_uint_, + const_uint_1, scale_shift), + const_uint_1); + scaled_result[result_component_index] = + builder_->createNoContractionBinOp( + spv::OpFMul, type_float_, result[result_component_index], + builder_->createUnaryOp(spv::OpConvertUToF, type_float_, + scale_uint)); + } + } + if_integer_scale.makeEndIf(); + // Keep the original result when the scale branch is skipped. + uint32_t result_remaining_components = used_result_nonzero_components; + uint32_t result_component_index; + while (xe::bit_scan_forward(result_remaining_components, + &result_component_index)) { + result_remaining_components &= + ~(UINT32_C(1) << result_component_index); + result[result_component_index] = if_integer_scale.createMergePhi( + scaled_result[result_component_index], + result[result_component_index]); + } + } + // Apply the exponent bias from the bits 13:18 of the fetch constant // word 3. spv::Id result_exponent_bias = builder_->createBinBuiltinCall( diff --git a/src/xenia/gpu/texture_cache.cc b/src/xenia/gpu/texture_cache.cc index 5e012c9a6..35b9199de 100644 --- a/src/xenia/gpu/texture_cache.cc +++ b/src/xenia/gpu/texture_cache.cc @@ -340,6 +340,7 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) { TextureBinding& binding = texture_bindings_[index]; xenos::xe_gpu_texture_fetch_t fetch = regs.GetTextureFetch(index); TextureKey old_key = binding.key; + uint32_t old_integer_scale_bits = binding.integer_scale_bits; uint8_t old_swizzled_signs = binding.swizzled_signs; BindingInfoFromFetchConstant(fetch, binding.key, &binding.swizzled_signs); texture_bindings_in_sync_ |= index_bit; @@ -353,6 +354,9 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) { uint32_t old_host_swizzle = binding.host_swizzle; binding.host_swizzle = GuestToHostSwizzle(fetch.swizzle, GetHostFormatSwizzle(binding.key)); + binding.integer_scale_bits = + GetIntegerScaleBits(fetch.format, fetch.num_format, + binding.host_swizzle, binding.swizzled_signs); // Check if need to load the unsigned and the signed versions of the texture // (if the format is emulated with different host bit representations for @@ -366,7 +370,8 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) { texture_util::IsAnySignNotSigned(binding.swizzled_signs); bool any_sign_is_signed = texture_util::IsAnySignSigned(binding.swizzled_signs); - if (key_changed || binding.host_swizzle != old_host_swizzle || + if (key_changed || binding.integer_scale_bits != old_integer_scale_bits || + binding.host_swizzle != old_host_swizzle || any_sign_is_not_signed != any_sign_was_not_signed || any_sign_is_signed != any_sign_was_signed) { bindings_changed |= index_bit; @@ -670,6 +675,50 @@ TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) { texture->LogAction("Created"); return texture; } + +// Packs the integer scale the fetch shader reads from the system constant to +// undo the host sampler's normalization - the guest wants e.g. [0, 255], not +// [0, 1]. 5 bits per output component: bits 0:3 = width - 1, bit 4 = signed. +// The scale lands after swizzling, so each output lane walks the host swizzle +// back to its source component's width; constant (0/1) lanes, gamma, and +// non-fixed formats have nothing to rescale and stay 0. +uint32_t TextureCache::GetIntegerScaleBits(xenos::TextureFormat guest_format, + uint32_t num_format, + uint32_t host_swizzle, + uint8_t swizzled_signs) { + // num_format 0 is the normalized/fractional fetch - nothing to rescale. + const FormatInfo& format_info = *FormatInfo::Get(guest_format); + uint32_t scale_bits = 0; + + if (!num_format || !format_info.fixed) { + return 0; + } + + for (uint32_t i = 0; i < 4; ++i) { + uint32_t source_component = (host_swizzle >> (i * 3)) & 0b111; + if (source_component >= xenos::XE_GPU_TEXTURE_SWIZZLE_0) { + continue; + } + + xenos::TextureSign sign = + xenos::TextureSign((swizzled_signs >> (i * 2)) & 0b11); + + uint8_t width = format_info.component_bits[source_component]; + if (!width || width > 16 || sign == xenos::TextureSign::kGamma) { + continue; + } + + uint32_t component_scale = uint32_t(width - 1); + if (sign == xenos::TextureSign::kSigned) { + component_scale |= UINT32_C(1) << 4; + } + + scale_bits |= component_scale << (i * 5); + } + + return scale_bits; +} + void TextureCache::LoadTexturesData(Texture** textures, uint32_t n_textures) { assert_true(n_textures <= 64); if (n_textures < 2) { diff --git a/src/xenia/gpu/texture_cache.h b/src/xenia/gpu/texture_cache.h index c1e6ca5c7..7bf40b5b3 100644 --- a/src/xenia/gpu/texture_cache.h +++ b/src/xenia/gpu/texture_cache.h @@ -137,6 +137,11 @@ class TextureCache { GetValidTextureBinding(fetch_constant_index); return binding ? binding->swizzled_signs : kSwizzledSignsUnsigned; } + uint32_t GetActiveIntegerScaleBits(uint32_t fetch_constant_index) const { + const TextureBinding* binding = + GetValidTextureBinding(fetch_constant_index); + return binding ? binding->integer_scale_bits : 0; + } bool IsActiveTextureResolutionScaled(uint32_t fetch_constant_index) const { const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index); @@ -500,6 +505,8 @@ class TextureCache { struct TextureBinding { TextureKey key; + // Packed integer scale, 5 bits per component. + uint32_t integer_scale_bits; // Destination swizzle merged with guest to host format swizzle. uint32_t host_swizzle; // Packed TextureSign values, 2 bit per each component, with guest-side @@ -577,6 +584,12 @@ class TextureCache { assert_true(load_shader_index < kLoadShaderCount); return load_shader_info_[load_shader_index]; } + // Integer num_format on fixed textures. Returns the packed scale used by the + // shader to restore guest integer units from normalized host samples. + static uint32_t GetIntegerScaleBits(xenos::TextureFormat guest_format, + uint32_t num_format, + uint32_t host_swizzle, + uint8_t swizzled_signs); bool LoadTextureData(Texture& texture); void LoadTexturesData(Texture** textures, uint32_t n_textures); // Writes the texture data (for base, mips or both - but not neither) from the diff --git a/src/xenia/gpu/texture_info.h b/src/xenia/gpu/texture_info.h index 567e2cc48..cbdf94fc1 100644 --- a/src/xenia/gpu/texture_info.h +++ b/src/xenia/gpu/texture_info.h @@ -195,6 +195,8 @@ struct FormatInfo { const uint32_t block_width; const uint32_t block_height; const uint32_t bits_per_pixel; + const uint8_t component_bits[4]; + const bool fixed; uint32_t bytes_per_block() const { return block_width * block_height * bits_per_pixel / 8; diff --git a/src/xenia/gpu/texture_info_formats.cc b/src/xenia/gpu/texture_info_formats.cc index 99f4516c3..4bea89b7f 100644 --- a/src/xenia/gpu/texture_info_formats.cc +++ b/src/xenia/gpu/texture_info_formats.cc @@ -16,8 +16,8 @@ namespace gpu { using namespace xe::gpu::xenos; -#define FORMAT_INFO(texture_format, format, block_width, block_height, bits_per_pixel) \ - {xenos::TextureFormat::texture_format, FormatType::format, block_width, block_height, bits_per_pixel} +#define FORMAT_INFO(texture_format, format, block_width, block_height, bits_per_pixel, component_bits_0, component_bits_1, component_bits_2, component_bits_3, fixed) \ + {xenos::TextureFormat::texture_format, FormatType::format, block_width, block_height, bits_per_pixel, {component_bits_0, component_bits_1, component_bits_2, component_bits_3}, fixed} const FormatInfo* FormatInfo::Get(uint32_t gpu_format) { static constexpr FormatInfo format_infos[64] = { #include "texture_info_formats.inl" @@ -43,7 +43,7 @@ constexpr unsigned char GetShift(unsigned pow) { this means we can use a boolean table that also acts as a sparse indexer ( popcnt preceding bits to get index) and shift and mask a 32 bit word to get the shift */ unsigned char FormatInfo::GetWidthShift(uint32_t gpu_format) { - #define FORMAT_INFO(texture_format, format, block_width, block_height, bits_per_pixel) GetShift(block_width) + #define FORMAT_INFO(texture_format, format, block_width, block_height, bits_per_pixel, component_bits_0, component_bits_1, component_bits_2, component_bits_3, fixed) GetShift(block_width) alignas(XE_HOST_CACHE_LINE_SIZE) constexpr unsigned char wshift_table[64] = { #include "texture_info_formats.inl" @@ -53,7 +53,7 @@ unsigned char FormatInfo::GetWidthShift(uint32_t gpu_format) { return wshift_table[gpu_format]; } unsigned char FormatInfo::GetHeightShift(uint32_t gpu_format) { -#define FORMAT_INFO(texture_format, format, block_width, block_height, bits_per_pixel) GetShift(block_height) +#define FORMAT_INFO(texture_format, format, block_width, block_height, bits_per_pixel, component_bits_0, component_bits_1, component_bits_2, component_bits_3, fixed) GetShift(block_height) alignas(XE_HOST_CACHE_LINE_SIZE) constexpr unsigned char hshift_table[64] = { #include "texture_info_formats.inl" diff --git a/src/xenia/gpu/texture_info_formats.inl b/src/xenia/gpu/texture_info_formats.inl index c47cdf9db..455ba4239 100644 --- a/src/xenia/gpu/texture_info_formats.inl +++ b/src/xenia/gpu/texture_info_formats.inl @@ -1,64 +1,77 @@ -FORMAT_INFO(k_1_REVERSE, kUncompressed, 1, 1, 1), - FORMAT_INFO(k_1, kUncompressed, 1, 1, 1), - FORMAT_INFO(k_8, kResolvable, 1, 1, 8), - FORMAT_INFO(k_1_5_5_5, kResolvable, 1, 1, 16), - FORMAT_INFO(k_5_6_5, kResolvable, 1, 1, 16), - FORMAT_INFO(k_6_5_5, kResolvable, 1, 1, 16), - FORMAT_INFO(k_8_8_8_8, kResolvable, 1, 1, 32), - FORMAT_INFO(k_2_10_10_10, kResolvable, 1, 1, 32), - FORMAT_INFO(k_8_A, kResolvable, 1, 1, 8), - FORMAT_INFO(k_8_B, kResolvable, 1, 1, 8), - FORMAT_INFO(k_8_8, kResolvable, 1, 1, 16), - FORMAT_INFO(k_Cr_Y1_Cb_Y0_REP, kCompressed, 2, 1, 16), - FORMAT_INFO(k_Y1_Cr_Y0_Cb_REP, kCompressed, 2, 1, 16), - FORMAT_INFO(k_16_16_EDRAM, kUncompressed, 1, 1, 32), - FORMAT_INFO(k_8_8_8_8_A, kResolvable, 1, 1, 32), - FORMAT_INFO(k_4_4_4_4, kResolvable, 1, 1, 16), - FORMAT_INFO(k_10_11_11, kResolvable, 1, 1, 32), - FORMAT_INFO(k_11_11_10, kResolvable, 1, 1, 32), - FORMAT_INFO(k_DXT1, kCompressed, 4, 4, 4), - FORMAT_INFO(k_DXT2_3, kCompressed, 4, 4, 8), - FORMAT_INFO(k_DXT4_5, kCompressed, 4, 4, 8), - FORMAT_INFO(k_16_16_16_16_EDRAM, kUncompressed, 1, 1, 64), - FORMAT_INFO(k_24_8, kUncompressed, 1, 1, 32), - FORMAT_INFO(k_24_8_FLOAT, kUncompressed, 1, 1, 32), - FORMAT_INFO(k_16, kResolvable, 1, 1, 16), - FORMAT_INFO(k_16_16, kResolvable, 1, 1, 32), - FORMAT_INFO(k_16_16_16_16, kResolvable, 1, 1, 64), - FORMAT_INFO(k_16_EXPAND, kUncompressed, 1, 1, 16), - FORMAT_INFO(k_16_16_EXPAND, kUncompressed, 1, 1, 32), - FORMAT_INFO(k_16_16_16_16_EXPAND, kUncompressed, 1, 1, 64), - FORMAT_INFO(k_16_FLOAT, kResolvable, 1, 1, 16), - FORMAT_INFO(k_16_16_FLOAT, kResolvable, 1, 1, 32), - FORMAT_INFO(k_16_16_16_16_FLOAT, kResolvable, 1, 1, 64), - FORMAT_INFO(k_32, kUncompressed, 1, 1, 32), - FORMAT_INFO(k_32_32, kUncompressed, 1, 1, 64), - FORMAT_INFO(k_32_32_32_32, kUncompressed, 1, 1, 128), - FORMAT_INFO(k_32_FLOAT, kResolvable, 1, 1, 32), - FORMAT_INFO(k_32_32_FLOAT, kResolvable, 1, 1, 64), - FORMAT_INFO(k_32_32_32_32_FLOAT, kResolvable, 1, 1, 128), - FORMAT_INFO(k_32_AS_8, kCompressed, 4, 1, 8), - FORMAT_INFO(k_32_AS_8_8, kCompressed, 2, 1, 16), - FORMAT_INFO(k_16_MPEG, kUncompressed, 1, 1, 16), - FORMAT_INFO(k_16_16_MPEG, kUncompressed, 1, 1, 32), - FORMAT_INFO(k_8_INTERLACED, kUncompressed, 1, 1, 8), - FORMAT_INFO(k_32_AS_8_INTERLACED, kCompressed, 4, 1, 8), - FORMAT_INFO(k_32_AS_8_8_INTERLACED, kCompressed, 1, 1, 16), - FORMAT_INFO(k_16_INTERLACED, kUncompressed, 1, 1, 16), - FORMAT_INFO(k_16_MPEG_INTERLACED, kUncompressed, 1, 1, 16), - FORMAT_INFO(k_16_16_MPEG_INTERLACED, kUncompressed, 1, 1, 32), - FORMAT_INFO(k_DXN, kCompressed, 4, 4, 8), - FORMAT_INFO(k_8_8_8_8_AS_16_16_16_16, kResolvable, 1, 1, 32), - FORMAT_INFO(k_DXT1_AS_16_16_16_16, kCompressed, 4, 4, 4), - FORMAT_INFO(k_DXT2_3_AS_16_16_16_16, kCompressed, 4, 4, 8), - FORMAT_INFO(k_DXT4_5_AS_16_16_16_16, kCompressed, 4, 4, 8), - FORMAT_INFO(k_2_10_10_10_AS_16_16_16_16, kResolvable, 1, 1, 32), - FORMAT_INFO(k_10_11_11_AS_16_16_16_16, kResolvable, 1, 1, 32), - FORMAT_INFO(k_11_11_10_AS_16_16_16_16, kResolvable, 1, 1, 32), - FORMAT_INFO(k_32_32_32_FLOAT, kUncompressed, 1, 1, 96), - FORMAT_INFO(k_DXT3A, kCompressed, 4, 4, 4), - FORMAT_INFO(k_DXT5A, kCompressed, 4, 4, 4), - FORMAT_INFO(k_CTX1, kCompressed, 4, 4, 4), - FORMAT_INFO(k_DXT3A_AS_1_1_1_1, kCompressed, 4, 4, 4), - FORMAT_INFO(k_8_8_8_8_GAMMA_EDRAM, kUncompressed, 1, 1, 32), - FORMAT_INFO(k_2_10_10_10_FLOAT_EDRAM, kUncompressed, 1, 1, 32), +FORMAT_INFO(k_1_REVERSE, kUncompressed, 1, 1, 1, 0, 0, 0, 0, false), + FORMAT_INFO(k_1, kUncompressed, 1, 1, 1, 0, 0, 0, 0, false), + FORMAT_INFO(k_8, kResolvable, 1, 1, 8, 8, 0, 0, 0, true), + FORMAT_INFO(k_1_5_5_5, kResolvable, 1, 1, 16, 5, 5, 5, 1, true), + FORMAT_INFO(k_5_6_5, kResolvable, 1, 1, 16, 5, 6, 5, 0, true), + FORMAT_INFO(k_6_5_5, kResolvable, 1, 1, 16, 5, 5, 6, 0, true), + FORMAT_INFO(k_8_8_8_8, kResolvable, 1, 1, 32, 8, 8, 8, 8, true), + FORMAT_INFO(k_2_10_10_10, kResolvable, 1, 1, 32, 10, 10, 10, 2, true), + FORMAT_INFO(k_8_A, kResolvable, 1, 1, 8, 8, 0, 0, 0, true), + FORMAT_INFO(k_8_B, kResolvable, 1, 1, 8, 8, 0, 0, 0, true), + FORMAT_INFO(k_8_8, kResolvable, 1, 1, 16, 8, 8, 0, 0, true), + FORMAT_INFO(k_Cr_Y1_Cb_Y0_REP, kCompressed, 2, 1, 16, 0, 0, 0, 0, false), + FORMAT_INFO(k_Y1_Cr_Y0_Cb_REP, kCompressed, 2, 1, 16, 0, 0, 0, 0, false), + FORMAT_INFO(k_16_16_EDRAM, kUncompressed, 1, 1, 32, 16, 16, 0, 0, true), + FORMAT_INFO(k_8_8_8_8_A, kResolvable, 1, 1, 32, 8, 8, 8, 8, true), + FORMAT_INFO(k_4_4_4_4, kResolvable, 1, 1, 16, 4, 4, 4, 4, true), + FORMAT_INFO(k_10_11_11, kResolvable, 1, 1, 32, 11, 11, 10, 0, true), + FORMAT_INFO(k_11_11_10, kResolvable, 1, 1, 32, 10, 11, 11, 0, true), + FORMAT_INFO(k_DXT1, kCompressed, 4, 4, 4, 0, 0, 0, 0, false), + FORMAT_INFO(k_DXT2_3, kCompressed, 4, 4, 8, 0, 0, 0, 0, false), + FORMAT_INFO(k_DXT4_5, kCompressed, 4, 4, 8, 0, 0, 0, 0, false), + FORMAT_INFO(k_16_16_16_16_EDRAM, kUncompressed, 1, 1, 64, 16, 16, 16, 16, + true), + FORMAT_INFO(k_24_8, kUncompressed, 1, 1, 32, 0, 0, 0, 0, false), + FORMAT_INFO(k_24_8_FLOAT, kUncompressed, 1, 1, 32, 0, 0, 0, 0, false), + FORMAT_INFO(k_16, kResolvable, 1, 1, 16, 16, 0, 0, 0, true), + FORMAT_INFO(k_16_16, kResolvable, 1, 1, 32, 16, 16, 0, 0, true), + FORMAT_INFO(k_16_16_16_16, kResolvable, 1, 1, 64, 16, 16, 16, 16, true), + FORMAT_INFO(k_16_EXPAND, kUncompressed, 1, 1, 16, 0, 0, 0, 0, false), + FORMAT_INFO(k_16_16_EXPAND, kUncompressed, 1, 1, 32, 0, 0, 0, 0, false), + FORMAT_INFO(k_16_16_16_16_EXPAND, kUncompressed, 1, 1, 64, 0, 0, 0, 0, + false), + FORMAT_INFO(k_16_FLOAT, kResolvable, 1, 1, 16, 0, 0, 0, 0, false), + FORMAT_INFO(k_16_16_FLOAT, kResolvable, 1, 1, 32, 0, 0, 0, 0, false), + FORMAT_INFO(k_16_16_16_16_FLOAT, kResolvable, 1, 1, 64, 0, 0, 0, 0, false), + FORMAT_INFO(k_32, kUncompressed, 1, 1, 32, 0, 0, 0, 0, false), + FORMAT_INFO(k_32_32, kUncompressed, 1, 1, 64, 0, 0, 0, 0, false), + FORMAT_INFO(k_32_32_32_32, kUncompressed, 1, 1, 128, 0, 0, 0, 0, false), + FORMAT_INFO(k_32_FLOAT, kResolvable, 1, 1, 32, 0, 0, 0, 0, false), + FORMAT_INFO(k_32_32_FLOAT, kResolvable, 1, 1, 64, 0, 0, 0, 0, false), + FORMAT_INFO(k_32_32_32_32_FLOAT, kResolvable, 1, 1, 128, 0, 0, 0, 0, false), + FORMAT_INFO(k_32_AS_8, kCompressed, 4, 1, 8, 0, 0, 0, 0, false), + FORMAT_INFO(k_32_AS_8_8, kCompressed, 2, 1, 16, 0, 0, 0, 0, false), + FORMAT_INFO(k_16_MPEG, kUncompressed, 1, 1, 16, 0, 0, 0, 0, false), + FORMAT_INFO(k_16_16_MPEG, kUncompressed, 1, 1, 32, 0, 0, 0, 0, false), + FORMAT_INFO(k_8_INTERLACED, kUncompressed, 1, 1, 8, 0, 0, 0, 0, false), + FORMAT_INFO(k_32_AS_8_INTERLACED, kCompressed, 4, 1, 8, 0, 0, 0, 0, false), + FORMAT_INFO(k_32_AS_8_8_INTERLACED, kCompressed, 1, 1, 16, 0, 0, 0, 0, + false), + FORMAT_INFO(k_16_INTERLACED, kUncompressed, 1, 1, 16, 0, 0, 0, 0, false), + FORMAT_INFO(k_16_MPEG_INTERLACED, kUncompressed, 1, 1, 16, 0, 0, 0, 0, + false), + FORMAT_INFO(k_16_16_MPEG_INTERLACED, kUncompressed, 1, 1, 32, 0, 0, 0, 0, + false), + FORMAT_INFO(k_DXN, kCompressed, 4, 4, 8, 8, 8, 0, 0, true), + FORMAT_INFO(k_8_8_8_8_AS_16_16_16_16, kResolvable, 1, 1, 32, 8, 8, 8, 8, + true), + FORMAT_INFO(k_DXT1_AS_16_16_16_16, kCompressed, 4, 4, 4, 0, 0, 0, 0, false), + FORMAT_INFO(k_DXT2_3_AS_16_16_16_16, kCompressed, 4, 4, 8, 0, 0, 0, 0, + false), + FORMAT_INFO(k_DXT4_5_AS_16_16_16_16, kCompressed, 4, 4, 8, 0, 0, 0, 0, + false), + FORMAT_INFO(k_2_10_10_10_AS_16_16_16_16, kResolvable, 1, 1, 32, 10, 10, 10, + 2, true), + FORMAT_INFO(k_10_11_11_AS_16_16_16_16, kResolvable, 1, 1, 32, 11, 11, 10, 0, + true), + FORMAT_INFO(k_11_11_10_AS_16_16_16_16, kResolvable, 1, 1, 32, 10, 11, 11, 0, + true), + FORMAT_INFO(k_32_32_32_FLOAT, kUncompressed, 1, 1, 96, 0, 0, 0, 0, false), + FORMAT_INFO(k_DXT3A, kCompressed, 4, 4, 4, 8, 0, 0, 0, true), + FORMAT_INFO(k_DXT5A, kCompressed, 4, 4, 4, 8, 0, 0, 0, true), + FORMAT_INFO(k_CTX1, kCompressed, 4, 4, 4, 8, 8, 0, 0, true), + FORMAT_INFO(k_DXT3A_AS_1_1_1_1, kCompressed, 4, 4, 4, 0, 0, 0, 0, false), + FORMAT_INFO(k_8_8_8_8_GAMMA_EDRAM, kUncompressed, 1, 1, 32, 8, 8, 8, 8, + true), + FORMAT_INFO(k_2_10_10_10_FLOAT_EDRAM, kUncompressed, 1, 1, 32, 0, 0, 0, 0, + false), diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index ce46a01ab..13928eb74 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -4705,6 +4705,12 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( (texture_signs_uint & texture_signs_mask) != texture_signs_shifted; texture_signs_uint = (texture_signs_uint & ~texture_signs_mask) | texture_signs_shifted; + uint32_t texture_integer_scale_bits = + texture_cache_->GetActiveIntegerScaleBits(texture_index); + dirty |= system_constants_.texture_integer_scale_bits[texture_index] != + texture_integer_scale_bits; + system_constants_.texture_integer_scale_bits[texture_index] = + texture_integer_scale_bits; } } diff --git a/src/xenia/gpu/vulkan/vulkan_texture_cache.h b/src/xenia/gpu/vulkan/vulkan_texture_cache.h index ea77d8c1e..7fb7dad9e 100644 --- a/src/xenia/gpu/vulkan/vulkan_texture_cache.h +++ b/src/xenia/gpu/vulkan/vulkan_texture_cache.h @@ -219,10 +219,10 @@ class VulkanTextureCache final : public TextureCache { struct HostFormat { LoadShaderIndex load_shader; // Do NOT add integer formats to this - they are not filterable, can only be - // read with ImageFetch, not ImageSample! If any game is seen using - // num_format 1 for fixed-point formats (for floating-point, it's normally - // set to 1 though), add a constant buffer containing multipliers for the - // textures and multiplication to the tfetch implementation. + // read with ImageFetch, not ImageSample! Games that fetch fixed-point + // formats are handled after sampling by scaling the normalized host value + // back to the guest integer range (see GetIntegerScaleBits). Keep these as + // sampled float/normalized views. VkFormat format; // Whether the format is block-compressed on the host (the host block size // matches the guest format block size in this case), and isn't decompressed