From aafe014d0c94eab1cac1c9af14dc054c58df9177 Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 22 Oct 2026 18:56:26 +0200 Subject: [PATCH] shader: read raw D16 depth bits through kDepth k16UInt image loads Co-authored-by: Artyom Zabroda --- CMakeLists.txt | 2 + .../renderer/pipeline/descriptors.cpp | 6 +- .../backend/spirv/spirvEmitterImage.cpp | 21 ++- src/graphics/shader/recompiler/ir/ShaderIR.h | 6 + .../ir/passes/ResourceMaterialization.cpp | 9 +- tests/ShaderRecompilerComputeTests.cpp | 158 ++++++++++++++++++ 6 files changed, 192 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d0bebc68..b1b00492c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -678,6 +678,8 @@ if(BUILD_TESTING) COMMAND $ --gpu-command-lane-only) add_test(NAME wait_until_safe_for_rendering COMMAND $ --wait-until-safe-for-rendering-only) + add_test(NAME depth_bits + COMMAND $ --depth-bits-only) add_test(NAME pm4_context_state COMMAND $ --context-state-only) add_test(NAME gpu_tiler diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index a530f1c5f..aed048652 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -356,7 +356,7 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& } view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled; view.mapping = - storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid + storage || resource.conversion_format != Prospero::BufferFormat::kInvalid ? vk::ComponentMapping {} : TextureGetComponentMapping(descriptor.DstSelXYZW(), surface_format.host_to_storage); switch (resource.dimension) { @@ -515,7 +515,9 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR } const auto samples = multisampled ? 1u << last_level : 1u; const auto depth = static_cast(descriptor.Depth()) + 1u; - const auto format = descriptor.Format(); + const auto format = ShaderRecompiler::IR::ReadsDepthBits(resource) + ? Prospero::BufferFormat::k16UNorm + : descriptor.Format(); const auto surface_format = TextureGetSurfaceFormatInfo(format); const bool shader_conversion = surface_format.conversion_format != Prospero::BufferFormat::kInvalid; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index 967feb938..66196c3cf 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -389,6 +389,7 @@ Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state, const auto format = image.conversion_format; if (format == Prospero::BufferFormat::kInvalid) return {}; const auto info = Format::GetFormatInfo(format); + if (IR::ReadsDepthBits(image)) return info; const bool raw_signed = IR::RawSignedStorageFormat(image, format) != Prospero::BufferFormat::kInvalid; EXIT_IF((Prospero::RemapTextureFormat(format) == format && !raw_signed) || info.component_count == 0u || info.component_count > 4u); @@ -424,6 +425,7 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint const auto numeric_class = ctx.state.program.info.images[mem.resource].numeric_class; const auto scalar_type = ImageScalarType(ctx.state, numeric_class); + const bool integer = info.packed_bitfield || info.type == Format::ComponentType::Uint; uint32_t packed = 0; if (info.packed_bitfield) { packed = ctx.state.builder.AllocateId(); @@ -441,8 +443,16 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint ctx.state.builder.AddFunction(spv::OpCompositeExtract, scalar_type, components[component], texel, component); // UNorm backing preserves the guest's filtering; scaling precedes swizzle constants. + const auto unorm_max = static_cast((1u << info.component_bits[component]) - 1u); components[component] = Binary(ctx.state, spv::OpFMul, scalar_type, components[component], - ConstantF32Value(ctx.state, 255.0f)); + ConstantF32Value(ctx.state, unorm_max)); + if (integer) { + // Depth bits: round back to the stored UNorm code. + components[component] = + Unary(ctx.state, spv::OpConvertFToU, TypeU32(ctx.state), + Binary(ctx.state, spv::OpFAdd, scalar_type, components[component], + ConstantF32Value(ctx.state, 0.5f))); + } } } for (uint32_t component = info.component_count; component < 4u; component++) { @@ -454,20 +464,19 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint for (uint32_t component = 0; component < 4u; component++) { const auto selector = (swizzle >> (component * 3u)) & 7u; if (selector == 1u) { - selected[component] = info.packed_bitfield + selected[component] = integer ? ConstantU32(ctx.state, Format::FormattedConstantBits(info, Format::FormattedSourceKind::One)) : ConstantF32Value(ctx.state, 1.0f); } else if (selector >= 4u) { selected[component] = components[selector - 4u]; } else { - selected[component] = info.packed_bitfield ? ConstantU32(ctx.state, 0u) - : ZeroF32(ctx.state); + selected[component] = integer ? ConstantU32(ctx.state, 0u) : ZeroF32(ctx.state); } } const auto result = ctx.state.builder.AllocateId(); ctx.state.builder.AddFunction(spv::OpCompositeConstruct, - ImageVectorType(ctx.state, numeric_class, 4), result, selected[0], - selected[1], selected[2], selected[3]); + integer ? TypeU32Vector(ctx.state, 4) : TypeF32Vector(ctx.state, 4), + result, selected[0], selected[1], selected[2], selected[3]); return info.type == Format::ComponentType::Unorm ? Unary(ctx.state, spv::OpBitcast, TypeF32Vector(ctx.state, 4), result) : result; } diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index e49fbc840..696f9f812 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -188,6 +188,12 @@ RawSignedStorageFormat(const ImageResource& image, Prospero::BufferFormat format return Prospero::BufferFormat::kInvalid; } +// A raw load of a kDepth k16UInt T# reads a D16 surface's codes. Vulkan cannot view D16 as +// R16Uint, so the host binds it as UNorm and the shader converts back. +[[nodiscard]] constexpr bool ReadsDepthBits(const ImageResource& image) { + return image.conversion_format == Prospero::BufferFormat::k16UInt; +} + struct SamplerResource { static constexpr uint32_t NoIndirectSampler = UINT32_MAX; diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index c07eca966..7b7156bad 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -932,7 +932,12 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna return SpecializationFail("FMASK requires a direct image load"); } } - image.conversion_format = ImageConversionFormat(format); + // See ReadsDepthBits; raw loads only, since filtering would blend the codes. + const bool depth_bits = + !storage && base.unfiltered_access_only && format == Prospero::BufferFormat::k16UInt && + static_cast((descriptor.dwords[3] >> 20u) & 0x1fu) == + Prospero::TileMode::kDepth; + image.conversion_format = depth_bits ? format : ImageConversionFormat(format); if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) { image.shader_swizzle = DescriptorImageSwizzle(descriptor); } @@ -940,7 +945,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna const bool raw_sint_storage = raw_sint_format != Prospero::BufferFormat::kInvalid; if (raw_sint_format == Prospero::BufferFormat::k16UInt) image.conversion_format = format; image.numeric_class = Prospero::SampledTextureNumericClass( - Prospero::RemapTextureFormat(format)); + depth_bits ? Prospero::BufferFormat::k16UNorm : Prospero::RemapTextureFormat(format)); if (!storage && format == Prospero::BufferFormat::k11_11_10UNorm) { if (image.indirect_root != ImageResource::NoIndirectImage) { return SpecializationFail("packed UNorm sampling requires a direct image"); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 8f8a11045..1650bcb0e 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -12478,6 +12478,123 @@ public: std::printf("[gpu] %-32s ok\n", name); } + // Cyberpunk 2077 copies its HTile D16 depth target through a kDepth k16UInt image load. + void CheckDepthBitsCopy() { + constexpr const char *name = "DepthBitsCopy"; + constexpr uintptr_t base = 0x0000000205000000ull; + constexpr uint64_t allocation_size = 0x30000; + constexpr uint64_t htile_address = base + 0x10000; + constexpr uint64_t copy_address = base + 0x20000; + constexpr u32 depth_code = 0x2000u; + std::vector copy{EncodeMimg0(0x00, 0x1), EncodeMimg1(4, 0, 2), + EncodeMimg0(0x08, 0x1), EncodeMimg1(4, 0, 0)}; + AppendEnd(©); + ShaderMapUserData(reinterpret_cast(copy.data()), + {.type = Prospero::ShaderBinaryType::kCs, + .code_size_bytes = static_cast(copy.size() * sizeof(u32))}); + EnsureRuntimeContext(); + int64_t direct_offset = -1; + Require(name, "direct allocation", + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), + allocation_size, 0x10000, 0, &direct_offset) == 0, + "depth allocation failed"); + void *mapped = reinterpret_cast(base); + Require(name, "direct mapping", + Libs::LibKernel::Memory::KernelMapDirectMemory( + &mapped, allocation_size, 0x3, 0x10, direct_offset, 0x10000) == 0 && + mapped == reinterpret_cast(base), + "depth mapping failed"); + std::memset(mapped, 0, allocation_size); + RenderContext context(m_runtime_context); + context.InitializeGpu(nullptr); + LibKernel::Memory::InstallGpuResources(&context); + context.GetGpu().SendCommandSync([&] { + CommandProcessor processor(context, 0); + processor.BufferInit(); + auto &scheduler = context.GetCommandScheduler(); + auto &textures = context.GetTextureCache(); + auto &shaders = processor.GetShCtx(); + context.MapMemory(base, allocation_size); + ImageDesc depth{}; + depth.type = BindingType::DepthTarget; + depth.info.data = {base, 0x10000}; + depth.info.pixel_format = vk::Format::eD16Unorm; + depth.info.guest_format = Prospero::BufferFormat::k16UNorm; + depth.info.type = Prospero::ImageType::kColor2D; + depth.info.extent = {256, 128, 1}; + depth.info.resources = {1, 1}; + depth.info.pitch = 256; + depth.info.bytes_per_block = 2; + depth.info.samples = 1; + depth.info.tile_mode = Prospero::TileMode::kDepth; + depth.info.UpdateSize(); + depth.info.metadata.kind = ImageMetadataKind::Htile; + depth.info.metadata.range = {htile_address, 0x8000}; + depth.info.htile_initial_clear = false; + depth.view_info.format = vk::Format::eD16Unorm; + depth.view_info.type = vk::ImageViewType::e2D; + depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth; + depth.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment; + const auto depth_id = textures.FindImage(depth); + Require(name, "HTile depth target", textures.FindDepthTarget(depth_id, depth) != nullptr, + "the D16 depth target was not created"); + vk::ClearValue clear{}; + clear.depthStencil.depth = static_cast(depth_code) / 65535.0f; + TextureCacheTestAccess::ClearImage(textures, scheduler.Current(), depth_id, + {vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1}, clear); + + // The captured T# with this depth target's size and HTile; the copy writes 2x2 texels. + const ShaderTextureResource depth_bits{{static_cast(base >> 8u), + (static_cast(Prospero::BufferFormat::k16UInt) << 20u) | (3u << 30u), + 0x3fu | (127u << 14u), + DstSel(4, 0, 0, 1) | (static_cast(Prospero::TileMode::kDepth) << 20u) | + (static_cast(Prospero::ImageType::kColor2D) << 28u), + 0, 0x00700000u, 0x00280000u | (static_cast(htile_address >> 8u) << 24u), + static_cast(htile_address >> 16u)}}; + const ShaderTextureResource target{{static_cast(copy_address >> 8u), + (static_cast(Prospero::BufferFormat::k16UInt) << 20u) | (1u << 30u), 1u << 14u, + DstSel(4, 5, 6, 7) | (static_cast(Prospero::TileMode::kLinear) << 20u) | + (static_cast(Prospero::ImageType::kColor2D) << 28u), + 0, 0x00700000u, 0, 0}}; + shaders.SetCsShader({.data_addr = reinterpret_cast(copy.data()), + .num_thread_x = 2, .num_thread_y = 2, .num_thread_z = 1, + .wave_size = 64, .user_sgpr = 16, .tidig_comp_cnt = 1}); + for (u32 i = 0; i < 8; i++) { + shaders.SetCsUserSgpr(i, target.fields[i], HW::UserSgprType::Unknown); + shaders.SetCsUserSgpr(8u + i, depth_bits.fields[i], HW::UserSgprType::Unknown); + } + processor.DispatchDirect(1, 1, 1, 0x41u); + + ShaderRecompiler::IR::DescriptorValue value{}; + value.dword_count = 8; + std::copy_n(target.fields, 8, value.dwords.begin()); + ShaderRecompiler::IR::ImageResource resource{}; + resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Storage; + resource.numeric_class = Prospero::TextureNumericClass::Uint; + resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; + resource.written = true; + const auto binding = + RenderExecutorTestAccess::ResolveTexture(context.GetRenderExecutor(), resource, value); + Require(name, "raw depth bits", + ReadCachedTexel(name, context, binding.image_id, {}, {2, 2, 1}) == + std::vector(2, depth_code | depth_code << 16u), + "the copy did not read the stored D16 codes"); + Require(name, "depth target retained", TextureCacheTestAccess::Contains(textures, depth_id), + "reading depth bits replaced the HTile depth target"); + context.UnmapMemory(base, allocation_size); + scheduler.Finish(); + }); + context.ShutdownGpu(); + LibKernel::Memory::InstallGpuResources(nullptr); + Require(name, "unmap", Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0, + "depth mapping release failed"); + Require(name, "release", + Libs::LibKernel::Memory::KernelReleaseDirectMemory(direct_offset, allocation_size) == 0, + "depth allocation release failed"); + std::printf("[gpu] %-32s ok\n", name); + } + void CheckSampledDccClear() { constexpr const char *name = "SampledDccClear"; constexpr uintptr_t base = 0x0000000205000000ull; @@ -37773,6 +37890,39 @@ template TestCase ImageSamplePacked8() { return test; } +// A kDepth k16UInt image load returns the stored D16 codes and integer swizzle constants. +TestCase ImageLoadDepthBits() { + using O = ShaderOpcode; + TestCase test; + test.name = "ImageLoadDepthBits"; + constexpr std::array depth{0x2000u, 0xc000u, 0xffffu, 0x0001u}; + for (u32 texel = 0; texel < depth.size(); ++texel) { + AppendVMovU32(&test.code, 20, texel % 2u); + AppendVMovU32(&test.code, 21, texel / 2u); + test.code.push_back(EncodeMimg0(0x00, 0xf)); + test.code.push_back(EncodeMimg1(0, 20)); + for (u32 component = 0; component < 4; ++component) { + AppendStoreVgpr(&test.code, component, texel * 4u + component); + } + test.expected.insert(test.expected.end(), {depth[texel], 0u, 0u, 1u}); + } + AppendEnd(&test.code); + test.image_width = test.image_height = 2; + test.sampled_image_rgba = {depth[0] | depth[1] << 16u, depth[2] | depth[3] << 16u}; + test.sampled_image_format = vk::Format::eR16Unorm; + test.sampled_image_dwords_per_pixel = 1; + test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k16UInt); + test.user_data[1] |= 1u << 30u; + test.user_data[2] = 1u << 14u; + test.user_data[3] |= static_cast(Prospero::TileMode::kDepth) << 20u; + test.user_data[50] = test.expected.size() * sizeof(u32); + test.user_data[51] = 3u << 28u; + test.has_user_data = true; + test.image_descriptor_swizzle = DstSel(4, 0, 0, 1); + test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + return test; +} + TestCase ImageLoadR128IgnoresAdjacentMaskSgprs() { using O = ShaderOpcode; @@ -43679,6 +43829,7 @@ std::vector MakeCases() { AddCase(ImageSamplePacked8); AddCase(ImageSamplePacked8); AddCase(ImageSamplePacked8); + AddCase(ImageLoadDepthBits); AddCase(ImageLoadR128IgnoresAdjacentMaskSgprs); AddCase(ImageLoad1DUsesScalarCoordinate); AddCase(ImageGather2DInstructionWith1DDescriptor); @@ -49719,6 +49870,12 @@ int main(int argc, char **argv) { RunCase(&vulkan, ImageSamplePacked8()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--depth-bits-only") == 0) { + VulkanHarness vulkan; + vulkan.CheckDepthBitsCopy(); + RunCase(&vulkan, ImageLoadDepthBits()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--packed-texture-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, ImageLoadSintUsesSignedSampledImage()); @@ -50610,6 +50767,7 @@ int main(int argc, char **argv) { vulkan.CheckRenderExecutorColorVolumeDiscovery(); vulkan.CheckRenderExecutorColorMetadataClear(); vulkan.CheckSampledDccClear(); + vulkan.CheckDepthBitsCopy(); vulkan.CheckRenderExecutorColorStandardTileDiscovery(); vulkan.CheckRenderExecutorColorDepthTileDiscovery(); vulkan.CheckRenderExecutorStencilBindingDiscovery();