From d8a441249818118666891826d7070790336d2188 Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 23 Sep 2026 12:57:44 +0400 Subject: [PATCH] Refresh stencil backing through the depth image Co-authored-by: Daniil Shushakov <4shushakov@gmail.com> --- .../host_gpu/renderer/cache/textureCache.cpp | 79 ++++++++--- .../host_gpu/renderer/cache/textureCache.h | 2 +- tests/ShaderRecompilerComputeTests.cpp | 128 +++++++++++++++++- 3 files changed, 185 insertions(+), 24 deletions(-) diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index 2a2c45832..7eb12a120 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -138,14 +138,15 @@ void NameImageBinding(GraphicContext& graphics, Image& image, vk::ImageView view } [[nodiscard]] std::vector BuildDepthCopies(const ImageInfo& info, - uint64_t slice_stride) { + uint64_t slice_stride, + vk::ImageAspectFlags aspect) { std::vector copies(info.resources.layers); for (uint32_t layer = 0; layer < info.resources.layers; ++layer) { auto& copy = copies[layer]; copy.bufferOffset = slice_stride * layer; copy.bufferRowLength = info.pitch; copy.bufferImageHeight = info.extent.height; - copy.imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, layer, 1}; + copy.imageSubresource = {aspect, 0, layer, 1}; copy.imageExtent = {info.extent.width, info.extent.height, 1}; } return copies; @@ -1031,16 +1032,17 @@ TextureCache::ImageDownload TextureCache::BuildDownload(const Image& image) cons } void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_offset) { - const auto& info = image.info; - const auto binding = UploadBinding(image); + auto& destination = image.depth_id ? m_slot_images[image.depth_id] : image; + const auto binding = image.depth_id ? BindingType::DepthTarget : UploadBinding(image); const auto upload = [&](std::vector& copies, TileManager::Result linear) { for (auto& copy: copies) { copy.bufferOffset += linear.offset; } - image.Upload(copies, linear.buffer, linear.offset, linear.size); + destination.Upload(copies, linear.buffer, linear.offset, linear.size); }; if (binding != BindingType::DepthTarget) { + const auto& info = image.info; auto transfer = BuildTextureTransfer(image, binding, TransferDirection::Upload); if (!transfer.valid) { EXIT("TextureCache: invalid texture upload: binding=%u addr=0x%016" PRIx64 @@ -1064,21 +1066,32 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off return; } - if (info.samples != 1 || image.backing.samples != 1 || + // The stencil plane has its own row pitch and shares the native image + // with the depth plane. + auto info = destination.info; + if (image.depth_id) { + info.data = image.info.data; + info.guest_format = Prospero::BufferFormat::k8UInt; + info.bytes_per_block = 1; + if (info.IsTiled()) info.pitch = TileGetDepthPitch(info.extent.width, 1, 0); + } + if (info.samples != 1 || destination.backing.samples != 1 || info.resources.layers == 0 || info.data.size % info.resources.layers != 0 || Prospero::NumBytesPerElement(info.guest_format) != info.bytes_per_block) { EXIT("TextureCache: invalid depth upload\n"); } const auto layers = info.resources.layers; const auto full_slice_size = info.data.size / layers; - auto copies = BuildDepthCopies(info, full_slice_size); + auto copies = BuildDepthCopies(info, full_slice_size, image.depth_id + ? vk::ImageAspectFlagBits::eStencil + : vk::ImageAspectFlagBits::eDepth); TileManager::Result linear {source.Handle(), source_offset, source.Size() - source_offset}; if (info.IsTiled()) { const auto tiles = BuildDepthTiles(info); linear = m_tiler.Detile(source.Handle(), source_offset, info.data.size, info.data.size, tiles); } - const auto transfer_bytes = DepthAspectTransferBytes(info.pixel_format); + const auto transfer_bytes = image.depth_id ? 1u : DepthAspectTransferBytes(info.pixel_format); if (transfer_bytes != info.bytes_per_block) { const uint64_t texels_per_slice = static_cast(info.pitch) * info.extent.height; EXIT_NOT_IMPLEMENTED(info.bytes_per_block != sizeof(uint16_t) || @@ -1202,8 +1215,16 @@ void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc, } void TextureCache::RefreshImage(ImageId id) { - TrackImage(id); auto& image = m_slot_images[id]; + if (image.depth_id && + (m_slot_images[image.depth_id].info.metadata.stencil_compressed || + m_slot_images[image.depth_id].info.samples != 1)) { + return; + } + if (image.info.HasStencil()) { + RefreshImage(AssociateStencil(id, image.info.stencil)); + } + TrackImage(id); if (image.IsMaybeCpuDirty()) { const auto hash = image.HashGuestEdges(); if (image.NeedsMaybeCpuHash()) { @@ -1225,7 +1246,7 @@ void TextureCache::RefreshImage(ImageId id) { InitializeImage(id); } -void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) { +ImageId TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) { if (!stencil.Valid()) { EXIT("TextureCache: invalid stencil association range\n"); } @@ -1251,6 +1272,7 @@ void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) { auto& record = m_slot_images[association]; TouchImage(record); record.depth_id = depth_id; + return association; } ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) { @@ -1444,17 +1466,15 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) { TouchImage(image); image.MarkGpuModified(); image.usage.depth_target = true; - RefreshImage(id); + image.info.stencil = desc.info.stencil; + image.info.metadata = desc.info.metadata; if (desc.info.HasMetadata()) { - image.info.metadata = desc.info.metadata; m_surface_metas.emplace(desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::HTile, .clear_mask = image.info.htile_clear_mask}); } + RefreshImage(id); CommitGpuWrite(image); - if (desc.info.HasStencil()) { - AssociateStencil(id, desc.info.stencil); - } const auto view = image.FindView(desc.view_info); NameImageBinding(m_graphics, image, view, desc.type, desc.view_info); return view; @@ -1468,11 +1488,16 @@ void TextureCache::MarkGpuWritten(ImageId id) { } TrackImage(id); CommitGpuWrite(image); + if (image.info.HasStencil()) { + const auto stencil_id = AssociateStencil(id, image.info.stencil); + TrackImage(stencil_id); + CommitGpuWrite(m_slot_images[stencil_id]); + } } void TextureCache::CommitGpuWrite(Image& image) { - if (image.depth_id || image.backing.image == nullptr) { - EXIT("TextureCache: stencil association cannot own image contents\n"); + if (!image.depth_id && image.backing.image == nullptr) { + EXIT("TextureCache: GPU writes require a native image or stencil association\n"); } image.ClearBufferModified(); if (image.IsCpuDirty()) { @@ -1556,9 +1581,10 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for range.layerCount == 0 || range.baseArrayLayer >= layers || range.layerCount > layers - range.baseArrayLayer || (range.aspectMask & aspects) != range.aspectMask); - const bool full_image = range.aspectMask == aspects && range.baseMipLevel == 0 && - range.levelCount == image.info.resources.levels && - range.baseArrayLayer == 0 && range.layerCount == layers; + const bool full_subresources = range.baseMipLevel == 0 && + range.levelCount == image.info.resources.levels && + range.baseArrayLayer == 0 && range.layerCount == layers; + const bool full_image = range.aspectMask == aspects && full_subresources; TrackImage(id); if (!full_image && (image.IsBufferModified() || image.IsCpuDirty())) { InitializeImage(id); @@ -1566,6 +1592,15 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for EXIT("TextureCache: image clear retained guest ownership\n"); } } + if (image.info.HasStencil() && (range.aspectMask & vk::ImageAspectFlagBits::eStencil)) { + const auto stencil_id = AssociateStencil(id, image.info.stencil); + if (!full_subresources) { + RefreshImage(stencil_id); + } else { + TrackImage(stencil_id); + CommitGpuWrite(m_slot_images[stencil_id]); + } + } command.EndRendering(); // Transfer clears use the backing format; aliased clears must encode through their view. if (format != image.backing.format || (image.info.IsVolume() && !full_image)) { @@ -1638,7 +1673,7 @@ void TextureCache::DownloadDepth(Image& image, Buffer& destination, uint64_t des EXIT_NOT_IMPLEMENTED(transfer_slice > UINT64_MAX / layers); const uint64_t transfer_size = transfer_slice * layers; EXIT_NOT_IMPLEMENTED(guest_slice > full_slice_size); - auto copies = BuildDepthCopies(info, full_slice_size); + auto copies = BuildDepthCopies(info, full_slice_size, vk::ImageAspectFlagBits::eDepth); if (transfer_bytes == info.bytes_per_block) { if (!info.IsTiled()) { for (auto& copy: copies) { @@ -1831,7 +1866,7 @@ void TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size) { std::scoped_lock lock {m_lock}; for (const auto id: FindImagesInRegion(address, size, true)) { auto& image = m_slot_images[id]; - if (image.depth_id || !image.Overlaps(address, size)) { + if (!image.Overlaps(address, size)) { continue; } if (image.IsGpuModified()) { diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.h b/src/graphics/host_gpu/renderer/cache/textureCache.h index e92dfd8f9..9f70abf91 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.h +++ b/src/graphics/host_gpu/renderer/cache/textureCache.h @@ -157,7 +157,7 @@ private: void RefreshCopySource(ImageId id); [[nodiscard]] bool CopyD16(Image& destination, Image& source); void CopyImage(ImageId destination, ImageId source); - void AssociateStencil(ImageId depth, GuestRange stencil); + [[nodiscard]] ImageId AssociateStencil(ImageId depth, GuestRange stencil); void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer); void ValidateImageDesc(const ImageDesc& desc) const; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index df382d81b..efe1a85c3 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -346,7 +346,7 @@ struct TextureCacheTestAccess { static void AssociateStencil(TextureCache &cache, ImageId depth, GuestRange stencil) { std::lock_guard lock(cache.m_lock); - cache.AssociateStencil(depth, stencil); + (void)cache.AssociateStencil(depth, stencil); } static void SetLinearReadback(TextureCache &cache, bool enabled) { @@ -12183,6 +12183,132 @@ public: !texture_cache.GetImage(depth_id).usage.storage, "stencil clear altered depth, lost its scalar value, or used a storage view"); DestroyBuffer(&stencil_readback); + + // Buffer writes to the independent tiled stencil plane must reach the + // combined native attachment, including values that are not a clear. + { + LibKernel::Memory::InstallGpuResources(&resources); + constexpr uint64_t plane_size = 0x10000; + constexpr uint64_t plane_address = base + 0x910000; + constexpr uint32_t texels = 6; + auto plane_depth = depth; + plane_depth.info.data = {base + 0x900000, plane_size}; + plane_depth.info.stencil = {plane_address, plane_size}; + plane_depth.info.extent = {3, 2, 1}; + plane_depth.info.pitch = TileGetDepthPitch(3, sizeof(uint32_t)); + plane_depth.info.tile_mode = Prospero::TileMode::kDepth; + plane_depth.info.mip_layout[0] = {0, plane_size, plane_depth.info.pitch, 2}; + const auto plane_depth_id = texture_cache.FindImage(plane_depth); + (void)texture_cache.FindDepthTarget(plane_depth_id, plane_depth); + TextureCacheTestAccess::ClearImage(texture_cache, scheduler.Current(), plane_depth_id, + {vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1}, + stencil_initial); + + TileBlockLayout stencil_block{}; + Require(name, "independent stencil tile pitch", + TileGetBlockLayout(TileBlockFamily::Depth64KB, 1, stencil_block) && + plane_depth.info.pitch == 128 && TileGetDepthPitch(3, 1) == 256, + "stencil must use its own one-byte tile layout"); + std::vector plane_words(plane_size / sizeof(uint32_t), 0); + auto *plane_bytes = reinterpret_cast(plane_words.data()); + std::array plane_offsets{}; + for (uint32_t i = 0; i < texels; ++i) { + Require(name, "stencil tile offset", + TileGetBlockOffset(stencil_block, i % 3, i / 3, 0, plane_offsets[i]), + "stencil texel offset is unavailable"); + plane_bytes[plane_offsets[i]] = static_cast(0x21 + i * 7); + } + auto plane_upload = CreateHostBuffer(name, plane_size, + vk::BufferUsageFlagBits::eTransferSrc, plane_words); + ShaderRecompiler::IR::Program plane_ir{}; + plane_ir.stage = ShaderType::Compute; + plane_ir.resource_tracking_complete = true; + auto &plane_resource = plane_ir.info.buffers.emplace_back(); + plane_resource.written = true; + plane_resource.formatted = true; + allocate_bindings(plane_ir); + ShaderRecompiler::IR::CompiledShaderInfo plane_program{}; + plane_program.stage = plane_ir.stage; + plane_program.info = std::move(plane_ir.info); + plane_program.bindings = std::move(plane_ir.bindings); + ShaderBufferResource plane_descriptor{}; + plane_descriptor.UpdateAddress48(plane_address); + plane_descriptor.fields[1] |= 4u << 16u; + plane_descriptor.fields[2] = plane_size / sizeof(uint32_t); + plane_descriptor.fields[3] = 0x00014204u; + ShaderRecompiler::IR::ResourceSnapshot plane_snapshot; + auto &plane_value = plane_snapshot.buffers.emplace_back(); + std::memcpy(plane_value.dwords.data(), plane_descriptor.fields, + sizeof(plane_descriptor.fields)); + plane_value.dword_count = 4; + const ShaderStageRuntime plane_runtime{&plane_program, &plane_snapshot}; + auto plane_bindings = executor.PrepareBindings(plane_runtime); + vk::MemoryBarrier2 plane_barrier{}; + plane_barrier.srcStageMask = plane_barrier.dstStageMask = + vk::PipelineStageFlagBits2::eAllCommands; + plane_barrier.srcAccessMask = plane_barrier.dstAccessMask = + vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite; + vk::DependencyInfo plane_dependency{}; + plane_dependency.memoryBarrierCount = 1; + plane_dependency.pMemoryBarriers = &plane_barrier; + const auto write_plane = [&] { + executor.FindBuffers(plane_bindings); + executor.RebindBuffers(plane_bindings); + const auto &plane_buffer = plane_bindings.buffers[0]; + const vk::BufferCopy plane_copy{0, plane_buffer.offset, plane_size}; + scheduler.Current().Handle().pipelineBarrier2(plane_dependency); + scheduler.Current().Handle().copyBuffer(plane_upload.buffer, plane_buffer.buffer, + 1, &plane_copy); + scheduler.Current().Handle().pipelineBarrier2(plane_dependency); + RenderExecutorTestAccess::ResetBindings(executor); + }; + write_plane(); + + auto plane_readback = CreateHostBuffer(name, texels * 4 + 8, + vk::BufferUsageFlagBits::eTransferDst, {}); + const std::array plane_copies{{ + {0, 0, 0, {vk::ImageAspectFlagBits::eDepth, 0, 0, 1}, {}, {3, 2, 1}}, + {texels * 4, 0, 0, {vk::ImageAspectFlagBits::eStencil, 0, 0, 1}, {}, {3, 2, 1}}}}; + const auto check_plane = [&](const char *phase, bool native_clear) { + (void)texture_cache.FindDepthTarget(plane_depth_id, plane_depth); + texture_cache.GetImage(plane_depth_id).Download( + plane_copies, plane_readback.buffer, 0, plane_readback.size); + scheduler.Current().Handle().pipelineBarrier2(stencil_dependency); + scheduler.Finish(); + const auto values = ReadBuffer(name, plane_readback, texels + 2); + const auto *bytes = reinterpret_cast(values.data() + texels); + for (uint32_t i = 0; i < texels; ++i) { + Require(name, phase, + values[i] == std::bit_cast(0.625f) && + bytes[i] == (native_clear ? 0x67 : plane_bytes[plane_offsets[i]]), + "stencil acquisition lost bytes or overwrote the independent depth plane"); + } + }; + check_plane("GPU stencil buffer write", false); + write_plane(); + vk::ClearValue stencil_again{}; + stencil_again.depthStencil.stencil = 0x67; + TextureCacheTestAccess::ClearImage(texture_cache, scheduler.Current(), plane_depth_id, + {vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1}, stencil_again); + check_plane("consumed stencil dirtiness", true); + check_plane("repeated clean stencil acquisition", true); + for (uint32_t i = 0; i < texels; ++i) { + plane_bytes[plane_offsets[i]] += 0x10; + } + std::memcpy(reinterpret_cast(plane_address), plane_words.data(), plane_size); + check_plane("CPU stencil plane write", false); + std::memcpy(reinterpret_cast(plane_address), plane_words.data(), plane_size); + TextureCacheTestAccess::ClearImage(texture_cache, scheduler.Current(), plane_depth_id, + {vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1}, stencil_again); + for (uint32_t i = 0; i < texels; ++i) { + plane_bytes[plane_offsets[i]] += 0x10; + } + std::memcpy(reinterpret_cast(plane_address), plane_words.data(), plane_size); + check_plane("CPU stencil write after native clear", false); + DestroyBuffer(&plane_readback); + DestroyBuffer(&plane_upload); + LibKernel::Memory::InstallGpuResources(nullptr); + } resources.UnmapMemory(base, allocation_size); scheduler.Finish(); RenderExecutorTestAccess::DestroyDescriptorPipelines(