From 26824edcfa4d5f50910d293100385e49c71d78d4 Mon Sep 17 00:00:00 2001 From: Raffaele Date: Fri, 19 Dec 2025 23:47:32 +0100 Subject: [PATCH] [D3D12] Fix 4D5307DF fur textures - Fixes issue https://github.com/xenia-canary/game-compatibility/issues/6 - Implements a copy texture logic --- src/xenia/gpu/d3d12/d3d12_texture_cache.cc | 214 +++++++++++++++++---- src/xenia/gpu/d3d12/d3d12_texture_cache.h | 15 +- 2 files changed, 191 insertions(+), 38 deletions(-) diff --git a/src/xenia/gpu/d3d12/d3d12_texture_cache.cc b/src/xenia/gpu/d3d12/d3d12_texture_cache.cc index 41fc005bc..0a9d7e608 100644 --- a/src/xenia/gpu/d3d12/d3d12_texture_cache.cc +++ b/src/xenia/gpu/d3d12/d3d12_texture_cache.cc @@ -582,21 +582,42 @@ void D3D12TextureCache::WriteActiveTextureBindfulSRV( const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index); if (binding && AreDimensionsCompatible(host_shader_binding.dimension, binding->key.dimension)) { + bool force_special_view = + (host_shader_binding.dimension == xenos::FetchOpDimension::k2D && + binding->key.dimension == xenos::DataDimension::k3D); + const D3D12TextureBinding& d3d12_binding = d3d12_texture_bindings_[fetch_constant_index]; if (host_shader_binding.is_signed) { // Not supporting signed compressed textures - hopefully DXN and DXT5A are // not used as signed. if (texture_util::IsAnySignSigned(binding->swizzled_signs)) { - descriptor_index = d3d12_binding.descriptor_index_signed; texture = IsSignedVersionSeparateForFormat(binding->key) ? binding->texture_signed : binding->texture; + + if (force_special_view && texture) { + // Request the 2D view of the 3D texture on demand + descriptor_index = FindOrCreateTextureDescriptor( + *static_cast(texture), + xenos::DataDimension::k2DOrStacked, true, binding->host_swizzle); + } else { + descriptor_index = d3d12_binding.descriptor_index_signed; + } } } else { if (texture_util::IsAnySignNotSigned(binding->swizzled_signs)) { - descriptor_index = d3d12_binding.descriptor_index; texture = binding->texture; + + if (force_special_view && texture) { + // Request the 2D view of the 3D texture on demand + descriptor_index = FindOrCreateTextureDescriptor( + *static_cast(texture), + xenos::DataDimension::k2DOrStacked, false, binding->host_swizzle); + } else { + descriptor_index = d3d12_binding.descriptor_index; + texture = binding->texture; + } } } } @@ -646,12 +667,44 @@ uint32_t D3D12TextureCache::GetActiveTextureBindlessSRVIndex( const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index); if (binding && AreDimensionsCompatible(host_shader_binding.dimension, binding->key.dimension)) { + // 3D Texture on 2D Request + bool force_special_view = + (host_shader_binding.dimension == xenos::FetchOpDimension::k2D && + binding->key.dimension == xenos::DataDimension::k3D); + const D3D12TextureBinding& d3d12_binding = d3d12_texture_bindings_[fetch_constant_index]; - descriptor_index = host_shader_binding.is_signed - ? d3d12_binding.descriptor_index_signed - : d3d12_binding.descriptor_index; + + // Helper lambda to get standard index + uint32_t standard_index = host_shader_binding.is_signed + ? d3d12_binding.descriptor_index_signed + : d3d12_binding.descriptor_index; + + if (force_special_view) { + // Determine which texture object to use + Texture* texture = nullptr; + if (host_shader_binding.is_signed) { + texture = IsSignedVersionSeparateForFormat(binding->key) + ? binding->texture_signed + : binding->texture; + if (texture) { + descriptor_index = FindOrCreateTextureDescriptor( + *static_cast(texture), + xenos::DataDimension::k2DOrStacked, true, binding->host_swizzle); + } + } else { + texture = binding->texture; + if (texture) { + descriptor_index = FindOrCreateTextureDescriptor( + *static_cast(texture), + xenos::DataDimension::k2DOrStacked, false, binding->host_swizzle); + } + } + } else { + descriptor_index = standard_index; + } } + if (descriptor_index == UINT32_MAX) { switch (host_shader_binding.dimension) { case xenos::FetchOpDimension::k3DOrStacked: @@ -1756,36 +1809,117 @@ void D3D12TextureCache::UpdateTextureBindingsImpl( if (binding->texture && texture_util::IsAnySignNotSigned(binding->swizzled_signs)) { d3d12_binding.descriptor_index = FindOrCreateTextureDescriptor( - *static_cast(binding->texture), false, - binding->host_swizzle); + *static_cast(binding->texture), + binding->key.dimension, false, binding->host_swizzle); } if (binding->texture_signed && texture_util::IsAnySignSigned(binding->swizzled_signs)) { d3d12_binding.descriptor_index_signed = FindOrCreateTextureDescriptor( - *static_cast(binding->texture_signed), true, - binding->host_swizzle); + *static_cast(binding->texture_signed), + binding->key.dimension, true, binding->host_swizzle); } } else { D3D12Texture* texture = static_cast(binding->texture); if (texture) { if (texture_util::IsAnySignNotSigned(binding->swizzled_signs)) { d3d12_binding.descriptor_index = FindOrCreateTextureDescriptor( - *texture, false, binding->host_swizzle); + *texture, binding->key.dimension, false, binding->host_swizzle); } if (texture_util::IsAnySignSigned(binding->swizzled_signs)) { d3d12_binding.descriptor_index_signed = FindOrCreateTextureDescriptor( - *texture, true, binding->host_swizzle); + *texture, binding->key.dimension, true, binding->host_swizzle); } } } } } +ID3D12Resource* D3D12TextureCache::D3D12Texture::GetOrCreate3DAs2DResource( + D3D12_RESOURCE_STATES end_state) { + auto& d3d12_cache = static_cast(texture_cache()); + + // 1. If it already exists, just transition and return it. + if (resource_3d_as_2d_) { + if (resource_3d_as_2d_state_ != end_state) { + d3d12_cache.command_processor_.PushTransitionBarrier( + resource_3d_as_2d_.Get(), resource_3d_as_2d_state_, end_state); + resource_3d_as_2d_state_ = end_state; + } + return resource_3d_as_2d_.Get(); + } + + // 2. Create the 2D Alias Resource. + D3D12_RESOURCE_DESC source_desc = resource_->GetDesc(); + + D3D12_RESOURCE_DESC desc = source_desc; + desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + + const ui::d3d12::D3D12Provider& provider = + d3d12_cache.command_processor_.GetD3D12Provider(); + ID3D12Device* device = provider.GetDevice(); + + if (FAILED(device->CreateCommittedResource( + &ui::d3d12::util::kHeapPropertiesDefault, + provider.GetHeapFlagCreateNotZeroed(), &desc, + D3D12_RESOURCE_STATE_COPY_DEST, nullptr, + IID_PPV_ARGS(&resource_3d_as_2d_)))) { + XELOGE("D3D12Texture: Failed to create 3D-as-2D alias resource"); + return nullptr; + } + resource_3d_as_2d_state_ = D3D12_RESOURCE_STATE_COPY_DEST; + + // 3. Perform the Copy (Slice 0 of 3D -> 2D). + // Transition MAIN 3D resource to COPY_SOURCE. + D3D12_RESOURCE_STATES old_main_state = + SetResourceState(D3D12_RESOURCE_STATE_COPY_SOURCE); + d3d12_cache.command_processor_.PushTransitionBarrier( + resource(), old_main_state, D3D12_RESOURCE_STATE_COPY_SOURCE); + + auto& command_list = d3d12_cache.command_processor_.GetDeferredCommandList(); + + D3D12_TEXTURE_COPY_LOCATION dest_loc = {}; + dest_loc.pResource = resource_3d_as_2d_.Get(); + dest_loc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dest_loc.SubresourceIndex = 0; + + D3D12_TEXTURE_COPY_LOCATION src_loc = {}; + src_loc.pResource = resource(); + src_loc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + src_loc.SubresourceIndex = 0; + + // Define the copy box to select ONLY Slice 0 + D3D12_BOX src_box; + src_box.left = 0; + src_box.top = 0; + src_box.front = 0; + src_box.right = static_cast(source_desc.Width); + src_box.bottom = source_desc.Height; + src_box.back = 1; // CRITICAL: Only copy 1 depth slice! + + command_list.D3DCopyTextureRegion(&dest_loc, 0, 0, 0, &src_loc, &src_box); + + // 4. Restore States. + d3d12_cache.command_processor_.PushTransitionBarrier( + resource(), D3D12_RESOURCE_STATE_COPY_SOURCE, old_main_state); + SetResourceState(old_main_state); + + d3d12_cache.command_processor_.PushTransitionBarrier( + resource_3d_as_2d_.Get(), D3D12_RESOURCE_STATE_COPY_DEST, end_state); + resource_3d_as_2d_state_ = end_state; + + return resource_3d_as_2d_.Get(); +} + uint32_t D3D12TextureCache::FindOrCreateTextureDescriptor( - D3D12Texture& texture, bool is_signed, uint32_t host_swizzle) { + D3D12Texture& texture, xenos::DataDimension dimension, bool is_signed, + uint32_t host_swizzle) { D3D12Texture::SRVDescriptorKey descriptor_key; + descriptor_key.key = 0; descriptor_key.is_signed = uint32_t(is_signed); descriptor_key.host_swizzle = host_swizzle; + descriptor_key.dimension = uint32_t(dimension); // Try to find an existing descriptor. uint32_t existing_descriptor_index = @@ -1797,7 +1931,7 @@ uint32_t D3D12TextureCache::FindOrCreateTextureDescriptor( TextureKey texture_key = texture.key(); // Create a new bindless or cached descriptor if supported. - D3D12_SHADER_RESOURCE_VIEW_DESC desc; + D3D12_SHADER_RESOURCE_VIEW_DESC desc = {}; // Zero out struct if (IsSignedVersionSeparateForFormat(texture_key) && texture_key.signed_separate != uint32_t(is_signed)) { @@ -1819,34 +1953,44 @@ uint32_t D3D12TextureCache::FindOrCreateTextureDescriptor( } uint32_t mip_levels = texture_key.mip_max_level + 1; - switch (texture_key.dimension) { - case xenos::DataDimension::k1D: - case xenos::DataDimension::k2DOrStacked: - desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; + ID3D12Resource* resource_for_view = texture.resource(); + + if (dimension == xenos::DataDimension::k3D) { + desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; + desc.Texture3D.MostDetailedMip = 0; + desc.Texture3D.MipLevels = mip_levels; + desc.Texture3D.ResourceMinLODClamp = 0.0f; + } else if (dimension == xenos::DataDimension::kCube) { + desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; + desc.TextureCube.MostDetailedMip = 0; + desc.TextureCube.MipLevels = mip_levels; + desc.TextureCube.ResourceMinLODClamp = 0.0f; + } else { + desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; + if (texture_key.dimension == xenos::DataDimension::k3D) { + resource_for_view = texture.GetOrCreate3DAs2DResource( + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + if (!resource_for_view) { + return UINT32_MAX; + } + + // Configure SRV for the new 2D resource + desc.Texture2DArray.MostDetailedMip = 0; + desc.Texture2DArray.MipLevels = 1; + desc.Texture2DArray.FirstArraySlice = 0; + desc.Texture2DArray.ArraySize = 1; + desc.Texture2DArray.PlaneSlice = 0; + desc.Texture2DArray.ResourceMinLODClamp = 0.0f; + } else { + // Standard behavior desc.Texture2DArray.MostDetailedMip = 0; desc.Texture2DArray.MipLevels = mip_levels; desc.Texture2DArray.FirstArraySlice = 0; desc.Texture2DArray.ArraySize = texture_key.GetDepthOrArraySize(); desc.Texture2DArray.PlaneSlice = 0; desc.Texture2DArray.ResourceMinLODClamp = 0.0f; - break; - case xenos::DataDimension::k3D: - desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; - desc.Texture3D.MostDetailedMip = 0; - desc.Texture3D.MipLevels = mip_levels; - desc.Texture3D.ResourceMinLODClamp = 0.0f; - break; - case xenos::DataDimension::kCube: - desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; - desc.TextureCube.MostDetailedMip = 0; - desc.TextureCube.MipLevels = mip_levels; - desc.TextureCube.ResourceMinLODClamp = 0.0f; - break; - default: - assert_unhandled_case(texture_key.dimension); - return UINT32_MAX; + } } - desc.Shader4ComponentMapping = host_swizzle | D3D12_SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES; @@ -1894,7 +2038,7 @@ uint32_t D3D12TextureCache::FindOrCreateTextureDescriptor( } } device->CreateShaderResourceView( - texture.resource(), &desc, + resource_for_view, &desc, GetTextureDescriptorCPUHandle(descriptor_index)); texture.AddSRVDescriptorIndex(descriptor_key, descriptor_index); return descriptor_index; diff --git a/src/xenia/gpu/d3d12/d3d12_texture_cache.h b/src/xenia/gpu/d3d12/d3d12_texture_cache.h index e0ff3c60a..c7a125cce 100644 --- a/src/xenia/gpu/d3d12/d3d12_texture_cache.h +++ b/src/xenia/gpu/d3d12/d3d12_texture_cache.h @@ -551,6 +551,7 @@ class D3D12TextureCache final : public TextureCache { struct { uint32_t is_signed : 1; uint32_t host_swizzle : 12; + uint32_t dimension : 2; }; SRVDescriptorKey() : key(0) { static_assert_size(*this, sizeof(key)); } @@ -568,6 +569,8 @@ class D3D12TextureCache final : public TextureCache { } }; + ID3D12Resource* GetOrCreate3DAs2DResource(D3D12_RESOURCE_STATES end_state); + explicit D3D12Texture(D3D12TextureCache& texture_cache, const TextureKey& key, ID3D12Resource* resource, D3D12_RESOURCE_STATES resource_state); @@ -595,6 +598,10 @@ class D3D12TextureCache final : public TextureCache { Microsoft::WRL::ComPtr resource_; D3D12_RESOURCE_STATES resource_state_; + Microsoft::WRL::ComPtr resource_3d_as_2d_; + D3D12_RESOURCE_STATES resource_3d_as_2d_state_ = + D3D12_RESOURCE_STATE_COMMON; + // For bindful - indices in the non-shader-visible descriptor cache for // copying to the shader-visible heap (much faster than recreating, which, // according to profiling, was often a bottleneck in many games). @@ -717,7 +724,8 @@ class D3D12TextureCache final : public TextureCache { case xenos::FetchOpDimension::k1D: case xenos::FetchOpDimension::k2D: return resource_dimension == xenos::DataDimension::k1D || - resource_dimension == xenos::DataDimension::k2DOrStacked; + resource_dimension == xenos::DataDimension::k2DOrStacked || + resource_dimension == xenos::DataDimension::k3D; case xenos::FetchOpDimension::k3DOrStacked: return resource_dimension == xenos::DataDimension::k3D; case xenos::FetchOpDimension::kCube: @@ -730,8 +738,9 @@ class D3D12TextureCache final : public TextureCache { // Returns the index of an existing of a newly created non-shader-visible // cached (for bindful) or a shader-visible global (for bindless) descriptor, // or UINT32_MAX if failed to create. - uint32_t FindOrCreateTextureDescriptor(D3D12Texture& texture, bool is_signed, - uint32_t host_swizzle); + uint32_t FindOrCreateTextureDescriptor(D3D12Texture& texture, + xenos::DataDimension dimension, + bool is_signed, uint32_t host_swizzle); void ReleaseTextureDescriptor(uint32_t descriptor_index); D3D12_CPU_DESCRIPTOR_HANDLE GetTextureDescriptorCPUHandle( uint32_t descriptor_index) const;