From 068d7621dba9424c710214f727d274783169ee3b Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 4 Oct 2026 02:26:19 +0400 Subject: [PATCH] graphics: preserve stencil view layers through uploads and clears --- .../host_gpu/renderer/cache/textureCache.cpp | 52 ++++++++-- .../host_gpu/renderer/image/image.cpp | 4 +- src/graphics/host_gpu/renderer/image/image.h | 2 + tests/ShaderRecompilerComputeTests.cpp | 95 +++++++++++++++++++ 4 files changed, 142 insertions(+), 11 deletions(-) diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index e2a779d79..b51ab742c 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -1037,6 +1037,8 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off auto info = destination.info; if (image.depth_id) { info.data = image.info.data; + info.resources = {image.stencil_subresources.level_count, + image.stencil_subresources.layer_count}; info.guest_format = Prospero::BufferFormat::k8UInt; info.bytes_per_block = 1; if (info.IsTiled()) info.pitch = TileGetDepthPitch(info.extent.width, 1, 0); @@ -1051,6 +1053,11 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off auto copies = BuildDepthCopies(info, full_slice_size, image.depth_id ? vk::ImageAspectFlagBits::eStencil : vk::ImageAspectFlagBits::eDepth); + if (image.depth_id) { + for (auto& copy: copies) { + copy.imageSubresource.baseArrayLayer += image.stencil_subresources.base_layer; + } + } TileManager::Result linear {source.Handle(), source_offset, source.Size() - source_offset}; if (info.IsTiled()) { const auto tiles = BuildDepthTiles(info); @@ -1239,7 +1246,8 @@ ImageId TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) { } auto& record = m_slot_images[association]; TouchImage(record); - record.depth_id = depth_id; + record.depth_id = depth_id; + record.stencil_subresources = depth.stencil_subresources; return association; } @@ -1445,6 +1453,18 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) { TouchImage(image); image.MarkGpuModified(); image.usage.depth_target = true; + if (desc.info.HasStencil()) { + const auto layers = image.info.resources.layers; + EXIT_IF(layers == 0 || image.info.data.size % layers != 0 || + desc.info.data.address < image.info.data.address); + const auto slice_size = image.info.data.size / layers; + const auto offset = desc.info.data.address - image.info.data.address; + EXIT_IF(slice_size == 0 || offset % slice_size != 0 || offset / slice_size >= layers || + desc.info.resources.layers > layers - offset / slice_size); + image.stencil_subresources = {0, desc.info.resources.levels, + static_cast(offset / slice_size), + desc.info.resources.layers}; + } image.info.stencil = desc.info.stencil; image.info.metadata = desc.info.metadata; if (desc.info.HasMetadata()) { @@ -1491,9 +1511,11 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address if (command.IsInvalid() || !GuestRange {address, size}.Valid()) { EXIT("TextureCache: invalid image clear\n"); } - std::scoped_lock lock {m_lock}; - ImageId selected {}; - vk::ImageAspectFlags aspect {}; + std::scoped_lock lock {m_lock}; + ImageId selected {}; + ImageId stencil_id {}; + vk::ImageAspectFlags aspect {}; + ImageSubresourceRange subresources; for (const auto id: FindImagesInRegion(address, size, false)) { auto owner = m_slot_images.try_get(id); if (owner == nullptr) { @@ -1501,10 +1523,13 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address } vk::ImageAspectFlags candidate {}; ImageId candidate_id = id; + ImageSubresourceRange candidate_subresources { + 0, owner->info.resources.levels, 0, owner->info.TransferLayers()}; if (owner->depth_id && owner->info.data.address == address && owner->info.data.size == size) { - candidate = vk::ImageAspectFlagBits::eStencil; - candidate_id = owner->depth_id; + candidate = vk::ImageAspectFlagBits::eStencil; + candidate_id = owner->depth_id; + candidate_subresources = owner->stencil_subresources; owner = m_slot_images.try_get(candidate_id); if (owner == nullptr || owner->backing.image == nullptr || !owner->info.HasStencil()) { continue; @@ -1517,11 +1542,13 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address if (!candidate) { continue; } - if (selected && selected != candidate_id) { + if (selected && (selected != candidate_id || subresources != candidate_subresources)) { return false; } - selected = candidate_id; - aspect = candidate; + selected = candidate_id; + stencil_id = candidate == vk::ImageAspectFlagBits::eStencil ? id : ImageId {}; + aspect = candidate; + subresources = candidate_subresources; } if (!selected) { return false; @@ -1543,7 +1570,12 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address clear.depthStencil.stencil = stencil_clear; } ClearImage(command, selected, image.backing.format, - {aspect, 0, image.info.resources.levels, 0, image.info.TransferLayers()}, clear); + {aspect, subresources.base_level, subresources.level_count, + subresources.base_layer, subresources.layer_count}, clear); + if (stencil_id) { + TrackImage(stencil_id); + CommitGpuWrite(m_slot_images[stencil_id]); + } return true; } diff --git a/src/graphics/host_gpu/renderer/image/image.cpp b/src/graphics/host_gpu/renderer/image/image.cpp index 7f35de2e6..dfeeb11ad 100644 --- a/src/graphics/host_gpu/renderer/image/image.cpp +++ b/src/graphics/host_gpu/renderer/image/image.cpp @@ -673,7 +673,9 @@ Prospero::BufferFormat RenderTargetTransferFormat(uint32_t bytes_per_element) { } // namespace ImageOps Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& image_info) - : info(image_info), m_graphics(graphics), m_scheduler(scheduler) { + : info(image_info), + stencil_subresources {0, image_info.resources.levels, 0, image_info.resources.layers}, + m_graphics(graphics), m_scheduler(scheduler) { KYTY_PROFILER_FUNCTION(); ImageOps::Validate(info); m_cpu_dirty = diff --git a/src/graphics/host_gpu/renderer/image/image.h b/src/graphics/host_gpu/renderer/image/image.h index 27a42ceb9..5c216786c 100644 --- a/src/graphics/host_gpu/renderer/image/image.h +++ b/src/graphics/host_gpu/renderer/image/image.h @@ -148,6 +148,8 @@ public: uint64_t track_addr = 0; uint64_t track_addr_end = 0; ImageId depth_id {}; + // The current stencil plane's mapping into the depth image, also retained by its association. + ImageSubresourceRange stencil_subresources; uint64_t tick_accessed_last = 0; size_t lru_id = 0; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 706b0611c..a3ecf6616 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -6007,6 +6007,101 @@ public: "sampling"); DestroyBuffer(&layered_raw_d16_readback); + auto layered_stencil_desc = MakeLinearDesc( + base + 0x27d4000, 6 * 4 * sizeof(float), + vk::Format::eD32SfloatS8Uint, Prospero::BufferFormat::k32Float, + Prospero::ImageType::kColor2D, {4, 1, 1}, 6, 4, 1); + layered_stencil_desc.type = BindingType::DepthTarget; + // Four live stencil bytes per slice, with allocation padding. + constexpr uint32_t stencil_slice_size = 16; + layered_stencil_desc.info.stencil = {base + 0x27d6000, 6 * stencil_slice_size}; + layered_stencil_desc.view_info.aspect = + vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil; + layered_stencil_desc.view_info.usage = + vk::ImageUsageFlagBits::eDepthStencilAttachment; + const auto layered_stencil_id = texture_cache.FindImage(layered_stencil_desc); + auto &layered_stencil_native = texture_cache.GetImage(layered_stencil_id); + layered_stencil_native.Transit(vk::ImageLayout::eTransferDstOptimal, + vk::AccessFlagBits2::eTransferWrite, {}, scheduler.Current().Handle()); + const vk::ClearDepthStencilValue layered_stencil_clear{0.75f, 0x91}; + const vk::ImageSubresourceRange layered_stencil_range{ + layered_stencil_desc.view_info.aspect, 0, 1, 0, 6}; + scheduler.Current().Handle().clearDepthStencilImage( + layered_stencil_native.backing.image, + vk::ImageLayout::eTransferDstOptimal, &layered_stencil_clear, 1, + &layered_stencil_range); + texture_cache.MarkGpuWritten(layered_stencil_id); + std::array stencil_expected; + stencil_expected.fill(0x91919191u); + constexpr std::array, 4> stencil_cases{{ + {0, 1, 0x35353535u}, {0, 3, 0x57575757u}, + {2, 1, 0x79797979u}, {0, 1, 0xa5a5a5a5u}}}; + for (uint32_t pass = 0; pass < stencil_cases.size(); pass++) { + const auto [first, layers, stencil_word] = stencil_cases[pass]; + auto partial_stencil_desc = layered_stencil_desc; + partial_stencil_desc.info.data.address += first * 4 * sizeof(float); + partial_stencil_desc.info.data.size = layers * 4 * sizeof(float); + partial_stencil_desc.info.stencil.address += first * stencil_slice_size; + partial_stencil_desc.info.stencil.size = layers * stencil_slice_size; + partial_stencil_desc.info.resources.layers = layers; + partial_stencil_desc.info.mip_layout[0].size = + partial_stencil_desc.info.data.size; + partial_stencil_desc.view_info.type = layers == 1 + ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray; + partial_stencil_desc.view_info.layer_count = layers; + const auto stencil = partial_stencil_desc.info.stencil; + if (pass != 3) { + (void)resources.GetBufferCache().ObtainBuffer(stencil.address, stencil.size, true); + resources.GetBufferCache().FillBuffer( + stencil.address, stencil.size, stencil_word, false); + const auto partial_stencil_id = texture_cache.FindImage(partial_stencil_desc); + Require(name, "partial stencil retains depth owner", + partial_stencil_id == layered_stencil_id && + texture_cache.GetImage(partial_stencil_id).info.resources.layers == 6 && + partial_stencil_desc.view_info.base_layer == first && + texture_cache.FindDepthTarget(partial_stencil_id, + partial_stencil_desc) != nullptr, + "a partial stencil view replaced its six-layer depth owner"); + } else { + // The old prefix association must survive the owner's shifted binding. + Require(name, "partial stencil buffer clear", + texture_cache.ClearImageFromBuffer(scheduler.Current(), + stencil.address, stencil.size, stencil_word), + "the existing partial stencil association could not be cleared"); + const auto rebound = texture_cache.FindImage(partial_stencil_desc); + Require(name, "rebind cleared stencil association", + rebound == layered_stencil_id && + texture_cache.FindDepthTarget(rebound, partial_stencil_desc) != nullptr, + "the cleared stencil association lost its depth owner"); + } + std::fill_n(stencil_expected.begin() + first, layers, stencil_word); + auto readback = CreateHostBuffer(name, 30 * sizeof(uint32_t), + vk::BufferUsageFlagBits::eTransferDst, {}); + std::vector copies(2); + copies[0].imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, 0, 6}; + copies[1].bufferOffset = 24 * sizeof(float); + copies[1].imageSubresource = {vk::ImageAspectFlagBits::eStencil, 0, 0, 6}; + for (auto © : copies) copy.imageExtent = {4, 1, 1}; + texture_cache.GetImage(layered_stencil_id).Download( + copies, readback.buffer, 0, readback.size); + HostReadBarrier(readback.buffer, readback.size, + vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite); + scheduler.Finish(); + const auto words = ReadBuffer(name, readback, 30); + for (uint32_t layer = 0; layer < 6; layer++) { + for (uint32_t pixel = 0; pixel < 4; pixel++) { + Require(name, "partial stencil preserves depth", + words[layer * 4 + pixel] == std::bit_cast(0.75f), + "stencil upload changed the retained depth plane"); + } + Require(name, "partial stencil preserves other layers", + words[24 + layer] == stencil_expected[layer], + "stencil upload used the retained depth allocation's layer count"); + } + DestroyBuffer(&readback); + } + auto native_array_info = array_desc.info; native_array_info.data = {}; auto native_volume_info = volume_desc.info;