From 395c34ecab3decd98c61803d882d11295dd5c82d Mon Sep 17 00:00:00 2001 From: nmzik Date: Tue, 18 Aug 2026 02:02:03 +0200 Subject: [PATCH] renderer: recreate insufficient texture cache matches --- .../host_gpu/renderer/cache/textureCache.cpp | 8 +- .../host_gpu/renderer/cache/textureCache.h | 1 + tests/ShaderRecompilerComputeTests.cpp | 137 ++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index 9722e7125..c1888dac5 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -274,6 +274,11 @@ void TextureCache::DeleteImage(ImageId id) { } } +void TextureCache::FreeImage(ImageId id) { + ClearGpuModified(id); + DeleteImage(id); +} + void TextureCache::DeleteImages(std::span ids, std::optional native_source) { std::set> unique; @@ -1226,7 +1231,8 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) { if (exact_format && resolved.info.pixel_format != desc.info.pixel_format) { result = {}; } else if (resolved.info.resources < desc.info.resources) { - result = ExpandImage(desc.info, result); + FreeImage(result); + result = {}; } } if (!result) { diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.h b/src/graphics/host_gpu/renderer/cache/textureCache.h index 4c710da1b..815627ba5 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.h +++ b/src/graphics/host_gpu/renderer/cache/textureCache.h @@ -110,6 +110,7 @@ private: void RegisterImage(ImageId id); void UnregisterImage(ImageId id); void DeleteImage(ImageId id); + void FreeImage(ImageId id); void DeleteImages(std::span ids, std::optional native_source = {}); void RetainImage(CommandBuffer& command, ImageId id); void TouchImage(Image& image); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 9c27ce65f..eedcaad06 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -3455,6 +3455,45 @@ public: "TextureCache::FindTexture did not preserve sampled view " "identity, final refresh, and cache-only ownership"); + constexpr uint64_t resource_replacement_offset = 0x700000; + const std::array resource_replacement_data{ + 0x01020304u, 0x11121314u, 0x21222324u}; + std::memcpy(memory + resource_replacement_offset, + resource_replacement_data.data(), + sizeof(resource_replacement_data)); + auto underspecified = sampled; + underspecified.info.data = {base + resource_replacement_offset, + sizeof(resource_replacement_data)}; + underspecified.info.pixel_format = vk::Format::eR32Uint; + underspecified.info.guest_format = Prospero::BufferFormat::k32UInt; + underspecified.info.extent = {2, 1, 1}; + underspecified.info.resources = {1, 1}; + underspecified.info.pitch = 2; + underspecified.info.mip_layout[0] = {0, 8, 2, 1}; + underspecified.info.mip_layout[1] = {8, 4, 1, 1}; + underspecified.view_info.format = underspecified.info.pixel_format; + underspecified.view_info.level_count = 1; + const auto underspecified_id = texture_cache.FindImage(underspecified); + (void)texture_cache.FindTexture(underspecified_id, underspecified); + texture_cache.MarkGpuWritten(underspecified_id); + + auto complete = underspecified; + complete.info.resources = {2, 1}; + complete.view_info.level_count = 2; + const auto complete_id = texture_cache.FindImage(complete); + const auto complete_owner = + TextureCacheTestAccess::Owner(texture_cache, complete_id); + Require(name, "insufficient-resource replacement", + complete_id && complete_id != underspecified_id && + !TextureCacheTestAccess::Contains(texture_cache, + underspecified_id) && + complete_owner != nullptr && complete_owner->registered && + complete_owner->info.resources == complete.info.resources && + !complete_owner->IsGpuModified() && + complete_owner->IsDefinitelyCpuDirty(), + "an exact backing with insufficient resources was expanded " + "instead of being discarded and recreated"); + ImageInfo chain = sampled.info; chain.data = {0x10000, 0x8000}; chain.extent = {8, 8, 1}; @@ -4804,6 +4843,96 @@ public: vk::PipelineStageFlagBits::eTransfer, vk::AccessFlagBits::eTransferWrite); + constexpr uint64_t bridged_gpu_offset = 0x101000; + constexpr uint64_t bridged_cpu_offset = 0x105000; + constexpr uint64_t bridged_texel_size = 16; + constexpr uint64_t bridged_source_size = + bridged_cpu_offset - bridged_gpu_offset + bridged_texel_size; + constexpr uint32_t bridged_source_width = + bridged_source_size / bridged_texel_size; + constexpr uint32_t bridged_gpu_value = 0x10293847u; + constexpr uint32_t bridged_cpu_value = 0x56473829u; + constexpr uint32_t bridged_gpu_guest_value = 0xa5a5a5a5u; + std::array + bridged_cpu_values{}; + bridged_cpu_values.fill(bridged_cpu_value); + std::memcpy(memory + bridged_cpu_offset, bridged_cpu_values.data(), + sizeof(bridged_cpu_values)); + auto bridged_cpu_owner = resources.GetBufferCache().ObtainBuffer( + command, base + bridged_cpu_offset, bridged_texel_size, true, false); + Require(name, "bridged CPU owner allocation", + bridged_cpu_owner.owner != nullptr, + "bridged image source did not create its CPU-side cache owner"); + command.RetainResourceUntilFence(bridged_cpu_owner.owner); + resources.GetBufferCache().FillBuffer(base + bridged_cpu_offset, + bridged_texel_size, 0); + Require( + name, "bridged CPU write fault", + resources.HandleFault(PageFaultAccess::Write, + base + bridged_cpu_offset), + "bridged image source could not transfer its second owner to CPU"); + std::memcpy(memory + bridged_cpu_offset, bridged_cpu_values.data(), + sizeof(bridged_cpu_values)); + + std::array + bridged_gpu_guest_values{}; + bridged_gpu_guest_values.fill(bridged_gpu_guest_value); + std::memcpy(memory + bridged_gpu_offset, bridged_gpu_guest_values.data(), + sizeof(bridged_gpu_guest_values)); + + auto bridged_gpu_owner = resources.GetBufferCache().ObtainBuffer( + scheduler.Current(), base + bridged_gpu_offset, bridged_texel_size, + true, false); + Require(name, "bridged GPU owner allocation", + bridged_gpu_owner.owner != nullptr && + bridged_gpu_owner.owner != bridged_cpu_owner.owner, + "bridged image source did not retain two disjoint cache owners"); + scheduler.Current().RetainResourceUntilFence(bridged_gpu_owner.owner); + resources.GetBufferCache().FillBuffer( + base + bridged_gpu_offset, bridged_texel_size, bridged_gpu_value); + Require(name, "bridged ownership split", + resources.GetBufferCache().HasGpuDirtyBytes( + base + bridged_gpu_offset, bridged_texel_size) && + !resources.GetBufferCache().HasGpuDirtyBytes( + base + bridged_cpu_offset, bridged_texel_size) && + resources.GetBufferCache().IsRegionCpuModified( + base + bridged_cpu_offset, bridged_texel_size), + "bridged image source did not retain distinct GPU and CPU " + "ownership domains"); + + auto bridged_source_desc = MakeLinearDesc( + base + bridged_gpu_offset, bridged_source_size, + vk::Format::eR32G32B32A32Uint, + Prospero::BufferFormat::k32_32_32_32UInt, + Prospero::ImageType::kColor2D, {bridged_source_width, 1, 1}, 1, + bridged_texel_size, 1); + const auto bridged_source_image = + texture_cache.FindImage(bridged_source_desc); + (void)texture_cache.FindTexture(bridged_source_image, + bridged_source_desc); + auto bridged_source_readback = CreateHostBuffer( + name, bridged_source_size, vk::BufferUsageFlagBits::eTransferDst, + std::vector(bridged_source_size / sizeof(u32), 0)); + auto &bridged_source_native = + texture_cache.GetImage(bridged_source_image); + bridged_source_native.Transit(vk::ImageLayout::eTransferSrcOptimal, + vk::AccessFlagBits2::eTransferRead, {}, + scheduler.Current().Handle()); + vk::BufferImageCopy bridged_source_copy{}; + bridged_source_copy.bufferRowLength = bridged_source_width; + bridged_source_copy.imageSubresource.aspectMask = + vk::ImageAspectFlagBits::eColor; + bridged_source_copy.imageSubresource.layerCount = 1; + bridged_source_copy.imageExtent = {bridged_source_width, 1, 1}; + scheduler.Current().Handle().copyImageToBuffer( + bridged_source_native.backing.image, + vk::ImageLayout::eTransferSrcOptimal, bridged_source_readback.buffer, + 1, &bridged_source_copy); + HostReadBarrier(bridged_source_readback.buffer, + bridged_source_readback.size, + vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite); + constexpr uint64_t byte_mirror_page_offset = 0x24000; constexpr uint64_t byte_mirror_offset = byte_mirror_page_offset + 1; const std::array byte_mirror_guest{0xacu, 0x5au, 0xbdu, @@ -5075,6 +5204,13 @@ public: mixed_source_words[0x1000 / sizeof(u32)] == mixed_gpu_value, "mixed CPU/GPU source upload lost one " "ownership domain"); + const auto bridged_source_words = ReadBuffer( + name, bridged_source_readback, bridged_source_size / sizeof(u32)); + Require(name, "bridged-owner image content", + bridged_source_words.front() == bridged_gpu_value && + bridged_source_words.back() == bridged_cpu_value, + "image upload bridging separate cache owners lost a GPU or CPU " + "ownership domain"); Require(name, "BGRA16 content", ReadBuffer(name, bgra16_readback, 2) == std::vector{0x40004200u, 0x44003c00u}, @@ -5980,6 +6116,7 @@ public: DestroyBuffer(&layered_readback); DestroyBuffer(&bgra16_readback); DestroyBuffer(&mixed_source_readback); + DestroyBuffer(&bridged_source_readback); DestroyBuffer(&mip_formatted_readback); DestroyBuffer(&mip_prefix_readback); DestroyBuffer(&partial_cpu_refresh_readback);