diff --git a/src/xenia/gpu/d3d12/d3d12_texture_cache.cc b/src/xenia/gpu/d3d12/d3d12_texture_cache.cc index 1b7c349a5..56985a99b 100644 --- a/src/xenia/gpu/d3d12/d3d12_texture_cache.cc +++ b/src/xenia/gpu/d3d12/d3d12_texture_cache.cc @@ -86,37 +86,24 @@ namespace shaders { static bool FormatSupportsMipGenerationUAV(DXGI_FORMAT format) { switch (format) { // RGBA formats - most common, widely supported for typed UAV. - case DXGI_FORMAT_R8G8B8A8_TYPELESS: case DXGI_FORMAT_R8G8B8A8_UNORM: - case DXGI_FORMAT_R8G8B8A8_SNORM: - case DXGI_FORMAT_R10G10B10A2_TYPELESS: case DXGI_FORMAT_R10G10B10A2_UNORM: - case DXGI_FORMAT_R16G16B16A16_TYPELESS: case DXGI_FORMAT_R16G16B16A16_UNORM: - case DXGI_FORMAT_R16G16B16A16_SNORM: case DXGI_FORMAT_R16G16B16A16_FLOAT: case DXGI_FORMAT_R32G32B32A32_FLOAT: // RG formats. - case DXGI_FORMAT_R8G8_TYPELESS: case DXGI_FORMAT_R8G8_UNORM: - case DXGI_FORMAT_R8G8_SNORM: - case DXGI_FORMAT_R16G16_TYPELESS: case DXGI_FORMAT_R16G16_UNORM: - case DXGI_FORMAT_R16G16_SNORM: case DXGI_FORMAT_R16G16_FLOAT: case DXGI_FORMAT_R32G32_FLOAT: // Single channel formats. - case DXGI_FORMAT_R8_TYPELESS: case DXGI_FORMAT_R8_UNORM: - case DXGI_FORMAT_R8_SNORM: - case DXGI_FORMAT_R16_TYPELESS: case DXGI_FORMAT_R16_UNORM: - case DXGI_FORMAT_R16_SNORM: case DXGI_FORMAT_R16_FLOAT: case DXGI_FORMAT_R32_FLOAT: return true; - // All other formats (block-compressed, packed, integer, etc.) are not - // supported for our float4 typed UAV store mip generation. + // All other formats (block-compressed, packed, integer, SNORM, etc.) are + // not supported for our float4 typed UAV store mip generation. default: return false; } @@ -1931,22 +1918,29 @@ bool D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, // Generate mip levels for scaled resolve textures via compute shader. if (level_last_for_mip_gen > 0) { + uint32_t texture_level_count = texture_key.mip_max_level + 1; if (is_3d) { command_processor_.PushDebugMarker( "Mip Generation 3D: %ux%ux%u levels 1-%u", width * texture_resolution_scale_x, height * texture_resolution_scale_y, depth, level_last_for_mip_gen); } else { - command_processor_.PushDebugMarker("Mip Generation: %ux%u levels 1-%u", + command_processor_.PushDebugMarker("Mip Generation: %ux%ux%u levels 1-%u", width * texture_resolution_scale_x, height * texture_resolution_scale_y, - level_last_for_mip_gen); + array_size, level_last_for_mip_gen); } ID3D12Resource* texture_resource = d3d12_texture.resource(); - DXGI_FORMAT texture_dxgi_format = GetDXGIResourceFormat(texture_key); + // Use the correct format based on texture signedness. + DXGI_FORMAT texture_dxgi_format = + texture_key.signed_separate + ? host_formats_[uint32_t(texture_key.format)].dxgi_format_signed + : GetDXGIUnormFormat(texture_key); // Check if the format supports typed UAV stores for mip generation. + // Note: SNORM formats don't support typed UAV stores, so signed textures + // will skip mip generation. if (!FormatSupportsMipGenerationUAV(texture_dxgi_format)) { XELOGW( "Skipping mip generation for scaled resolve texture: DXGI format %u " @@ -1966,6 +1960,7 @@ bool D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, command_list.D3DSetComputeRootSignature(mip_gen_root_signature_.Get()); // Generate each mip level by downsampling from the previous level. + // For texture arrays, process each slice separately. for (uint32_t level = 1; level <= level_last_for_mip_gen; ++level) { uint32_t src_width = std::max(scaled_width >> (level - 1), UINT32_C(1)); uint32_t src_height = std::max(scaled_height >> (level - 1), UINT32_C(1)); @@ -1974,38 +1969,40 @@ bool D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, uint32_t dst_height = std::max(scaled_height >> level, UINT32_C(1)); uint32_t dst_depth = std::max(depth >> level, UINT32_C(1)); - // Transition source mip (level - 1) to pixel shader resource for reading. - // Transition destination mip (level) to unordered access for writing. - // Note: For first iteration, source (level 0) is in COPY_DEST state from - // the buffer copy above. - D3D12_RESOURCE_BARRIER barriers[2]; - uint32_t barrier_count = 0; + // Transition source and destination mips for all array slices. + // Subresource index = mip + slice * mip_count. + std::vector barriers; + barriers.reserve(array_size * 2); + for (uint32_t slice = 0; slice < array_size; ++slice) { + uint32_t src_subresource = (level - 1) + slice * texture_level_count; + uint32_t dst_subresource = level + slice * texture_level_count; - // Source barrier - from COPY_DEST (level 0) or UAV (level > 0) to SRV. - // Subresource index = MipSlice for ArraySlice=0, PlaneSlice=0. - barriers[barrier_count].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barriers[barrier_count].Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barriers[barrier_count].Transition.pResource = texture_resource; - barriers[barrier_count].Transition.Subresource = level - 1; - barriers[barrier_count].Transition.StateBefore = - (level == 1) ? D3D12_RESOURCE_STATE_COPY_DEST - : D3D12_RESOURCE_STATE_UNORDERED_ACCESS; - barriers[barrier_count].Transition.StateAfter = - D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; - ++barrier_count; + // Source barrier - from COPY_DEST (level 0) or UAV (level > 0) to SRV. + D3D12_RESOURCE_BARRIER src_barrier; + src_barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + src_barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + src_barrier.Transition.pResource = texture_resource; + src_barrier.Transition.Subresource = src_subresource; + src_barrier.Transition.StateBefore = + (level == 1) ? D3D12_RESOURCE_STATE_COPY_DEST + : D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + src_barrier.Transition.StateAfter = + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + barriers.push_back(src_barrier); - // Destination barrier - from COPY_DEST to UAV. - barriers[barrier_count].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barriers[barrier_count].Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barriers[barrier_count].Transition.pResource = texture_resource; - barriers[barrier_count].Transition.Subresource = level; - barriers[barrier_count].Transition.StateBefore = - D3D12_RESOURCE_STATE_COPY_DEST; - barriers[barrier_count].Transition.StateAfter = - D3D12_RESOURCE_STATE_UNORDERED_ACCESS; - ++barrier_count; - - command_list.D3DResourceBarrier(barrier_count, barriers); + // Destination barrier - from COPY_DEST to UAV. + D3D12_RESOURCE_BARRIER dst_barrier; + dst_barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + dst_barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + dst_barrier.Transition.pResource = texture_resource; + dst_barrier.Transition.Subresource = dst_subresource; + dst_barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + dst_barrier.Transition.StateAfter = + D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + barriers.push_back(dst_barrier); + } + command_list.D3DResourceBarrier(uint32_t(barriers.size()), + barriers.data()); // Allocate descriptors for SRV and UAV. ui::d3d12::util::DescriptorCpuGpuHandlePair mip_gen_descriptors[2]; @@ -2019,6 +2016,7 @@ bool D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, } // Create SRV for source mip level. + // For 2D textures, use array view to process all slices in one dispatch. D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {}; srv_desc.Format = texture_dxgi_format; srv_desc.Shader4ComponentMapping = @@ -2029,11 +2027,13 @@ bool D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, srv_desc.Texture3D.MipLevels = 1; srv_desc.Texture3D.ResourceMinLODClamp = 0.0f; } else { - srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - srv_desc.Texture2D.MostDetailedMip = level - 1; - srv_desc.Texture2D.MipLevels = 1; - srv_desc.Texture2D.PlaneSlice = 0; - srv_desc.Texture2D.ResourceMinLODClamp = 0.0f; + srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; + srv_desc.Texture2DArray.MostDetailedMip = level - 1; + srv_desc.Texture2DArray.MipLevels = 1; + srv_desc.Texture2DArray.FirstArraySlice = 0; + srv_desc.Texture2DArray.ArraySize = array_size; + srv_desc.Texture2DArray.PlaneSlice = 0; + srv_desc.Texture2DArray.ResourceMinLODClamp = 0.0f; } device->CreateShaderResourceView(texture_resource, &srv_desc, mip_gen_descriptors[0].first); @@ -2047,9 +2047,11 @@ bool D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, uav_desc.Texture3D.FirstWSlice = 0; uav_desc.Texture3D.WSize = dst_depth; } else { - uav_desc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; - uav_desc.Texture2D.MipSlice = level; - uav_desc.Texture2D.PlaneSlice = 0; + uav_desc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; + uav_desc.Texture2DArray.MipSlice = level; + uav_desc.Texture2DArray.FirstArraySlice = 0; + uav_desc.Texture2DArray.ArraySize = array_size; + uav_desc.Texture2DArray.PlaneSlice = 0; } device->CreateUnorderedAccessView(texture_resource, nullptr, &uav_desc, mip_gen_descriptors[1].first); @@ -2076,7 +2078,7 @@ bool D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, 2, mip_gen_descriptors[1].second); // Dispatch compute shader. - // 2D: Thread group size is 8x8. + // 2D: Thread group size is 8x8, dispatch z = array_size for all slices. // 3D: Thread group size is 4x4x4. if (is_3d) { uint32_t group_count_x = (dst_width + 3) / 4; @@ -2086,7 +2088,7 @@ bool D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, } else { uint32_t group_count_x = (dst_width + 7) / 8; uint32_t group_count_y = (dst_height + 7) / 8; - command_list.D3DDispatch(group_count_x, group_count_y, 1); + command_list.D3DDispatch(group_count_x, group_count_y, array_size); } } @@ -2094,27 +2096,28 @@ bool D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, // texture's expected state after loading. // Level 0 to level_last_for_mip_gen - 1 are in NON_PIXEL_SHADER_RESOURCE. // Level level_last_for_mip_gen is in UNORDERED_ACCESS. - // Subresource index = MipSlice for ArraySlice=0, PlaneSlice=0. + // Subresource index = mip + slice * mip_count. std::vector final_barriers; - final_barriers.reserve(level_last_for_mip_gen + 1); - for (uint32_t level = 0; level < level_last_for_mip_gen; ++level) { + final_barriers.reserve((level_last_for_mip_gen + 1) * array_size); + for (uint32_t slice = 0; slice < array_size; ++slice) { + for (uint32_t level = 0; level < level_last_for_mip_gen; ++level) { + D3D12_RESOURCE_BARRIER barrier; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = texture_resource; + barrier.Transition.Subresource = level + slice * texture_level_count; + barrier.Transition.StateBefore = + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; + final_barriers.push_back(barrier); + } + // Last mip level is in UAV state. D3D12_RESOURCE_BARRIER barrier; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; barrier.Transition.pResource = texture_resource; - barrier.Transition.Subresource = level; - barrier.Transition.StateBefore = - D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; - final_barriers.push_back(barrier); - } - // Last mip level is in UAV state. - { - D3D12_RESOURCE_BARRIER barrier; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = texture_resource; - barrier.Transition.Subresource = level_last_for_mip_gen; + barrier.Transition.Subresource = + level_last_for_mip_gen + slice * texture_level_count; barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; final_barriers.push_back(barrier); diff --git a/src/xenia/gpu/shaders/bytecode/d3d12_5_1/mip_generate_cs.h b/src/xenia/gpu/shaders/bytecode/d3d12_5_1/mip_generate_cs.h index eb4582e76..b1414c0ea 100644 --- a/src/xenia/gpu/shaders/bytecode/d3d12_5_1/mip_generate_cs.h +++ b/src/xenia/gpu/shaders/bytecode/d3d12_5_1/mip_generate_cs.h @@ -18,32 +18,32 @@ cs_5_1 dcl_globalFlags refactoringAllowed | allResourcesBound dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0 dcl_sampler S0[0:0], mode_default, space=0 -dcl_resource_texture2d (float,float,float,float) T0[0:0], space=0 -dcl_uav_typed_texture2d (float,float,float,float) U0[0:0], space=0 -dcl_input vThreadID.xy -dcl_temps 1 +dcl_resource_texture2darray (float,float,float,float) T0[0:0], space=0 +dcl_uav_typed_texture2darray (float,float,float,float) U0[0:0], space=0 +dcl_input vThreadID.xyz +dcl_temps 2 dcl_thread_group 8, 8, 1 uge r0.xy, vThreadID.xyxx, CB0[0][0].zwzz or r0.x, r0.y, r0.x if_nz r0.x ret endif -utof r0.xy, vThreadID.xyxx -add r0.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) -utof r0.zw, CB0[0][0].zzzw -div r0.xy, r0.xyxx, r0.zwzz -sample_l r0.xyzw, r0.xyxx, T0[0].xyzw, S0[0], l(0.000000) -store_uav_typed U0[0].xyzw, vThreadID.xyyy, r0.xyzw +utof r0.xyz, vThreadID.xyzx +add r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) +utof r1.zw, CB0[0][0].zzzw +div r0.xy, r1.xyxx, r1.zwzz +sample_l r0.xyzw, r0.xyzx, T0[0].xyzw, S0[0], l(0.000000) +store_uav_typed U0[0].xyzw, vThreadID.xyzz, r0.xyzw ret // Approximately 0 instruction slots used #endif const BYTE mip_generate_cs[] = { - 68, 88, 66, 67, 105, 246, - 102, 79, 76, 251, 37, 172, - 110, 28, 51, 60, 201, 185, - 141, 14, 1, 0, 0, 0, + 68, 88, 66, 67, 59, 97, + 167, 140, 139, 19, 29, 32, + 0, 105, 17, 219, 209, 230, + 254, 36, 1, 0, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 44, 0, 0, 0, 60, 0, 0, 0, 76, 0, @@ -64,19 +64,19 @@ const BYTE mip_generate_cs[] = 0, 6, 70, 110, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 88, 24, + 0, 0, 0, 0, 88, 64, 0, 7, 70, 126, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, - 0, 0, 156, 24, 0, 7, + 0, 0, 156, 64, 0, 7, 70, 238, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, - 95, 0, 0, 2, 50, 0, + 95, 0, 0, 2, 114, 0, 2, 0, 104, 0, 0, 2, - 1, 0, 0, 0, 155, 0, + 2, 0, 0, 0, 155, 0, 0, 4, 8, 0, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, 80, 0, 0, 8, @@ -93,27 +93,27 @@ const BYTE mip_generate_cs[] = 10, 0, 16, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21, 0, 0, 1, 86, 0, - 0, 4, 50, 0, 16, 0, - 0, 0, 0, 0, 70, 0, + 0, 4, 114, 0, 16, 0, + 0, 0, 0, 0, 70, 2, 2, 0, 0, 0, 0, 10, - 50, 0, 16, 0, 0, 0, + 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 86, 0, 0, 7, 194, 0, - 16, 0, 0, 0, 0, 0, + 16, 0, 1, 0, 0, 0, 166, 142, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 7, 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, - 16, 0, 0, 0, 0, 0, - 230, 10, 16, 0, 0, 0, + 16, 0, 1, 0, 0, 0, + 230, 10, 16, 0, 1, 0, 0, 0, 72, 0, 0, 13, 242, 0, 16, 0, 0, 0, - 0, 0, 70, 0, 16, 0, + 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 126, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, @@ -122,7 +122,7 @@ const BYTE mip_generate_cs[] = 0, 0, 0, 0, 0, 0, 164, 0, 0, 7, 242, 224, 33, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 70, 5, + 0, 0, 0, 0, 70, 10, 2, 0, 70, 14, 16, 0, 0, 0, 0, 0, 62, 0, 0, 1 diff --git a/src/xenia/gpu/shaders/mip_generate.cs.hlsl b/src/xenia/gpu/shaders/mip_generate.cs.hlsl index c289a44c4..533aeeb67 100644 --- a/src/xenia/gpu/shaders/mip_generate.cs.hlsl +++ b/src/xenia/gpu/shaders/mip_generate.cs.hlsl @@ -9,15 +9,15 @@ // Mip generation compute shader for scaled resolve textures. // Generates a single mip level by downsampling from the previous level using -// bilinear filtering. +// bilinear filtering. Supports texture arrays via SV_DispatchThreadID.z. cbuffer XeMipGenerateConstants : register(b0) { uint2 xe_mip_gen_source_size; // Source mip dimensions uint2 xe_mip_gen_dest_size; // Destination mip dimensions }; -Texture2D xe_mip_gen_source : register(t0); -RWTexture2D xe_mip_gen_dest : register(u0); +Texture2DArray xe_mip_gen_source : register(t0); +RWTexture2DArray xe_mip_gen_dest : register(u0); SamplerState xe_sampler_linear_clamp : register(s0); [numthreads(8, 8, 1)] @@ -29,9 +29,12 @@ void main(uint3 xe_thread_id : SV_DispatchThreadID) { // Calculate UV coordinates for sampling the source texture. // Add 0.5 to sample at pixel center, then normalize to [0, 1] range. - float2 uv = (float2(xe_thread_id.xy) + 0.5f) / float2(xe_mip_gen_dest_size); + // xe_thread_id.z is the array slice index. + float3 uvw = float3((float2(xe_thread_id.xy) + 0.5f) / + float2(xe_mip_gen_dest_size), + xe_thread_id.z); // Sample with bilinear filtering and write to destination. - xe_mip_gen_dest[xe_thread_id.xy] = - xe_mip_gen_source.SampleLevel(xe_sampler_linear_clamp, uv, 0); + xe_mip_gen_dest[xe_thread_id] = + xe_mip_gen_source.SampleLevel(xe_sampler_linear_clamp, uvw, 0); }