From 90c2600c44e8fb847d15ee3039148a7c752fe915 Mon Sep 17 00:00:00 2001 From: raghav314 Date: Sun, 25 Oct 2026 07:03:50 +0530 Subject: [PATCH 1/3] fix(agc): return a depth plane copy's memory to the device pool Co-Authored-By: Claude Opus 5.5 --- .../Graphics/src/DepthSurface.cpp | 6 +-- .../tests/execution/DepthPlaneGuestLayers.cpp | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 7273cb82e3..ff8359d789 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -1170,7 +1170,6 @@ struct DepthPlaneSlice { class DepthPlaneCopy { public: DepthPlaneCopy(const Context& context, VkExtent2D extent, VkFormat format, std::uint32_t layers) : context(context), extent(extent), format(format), layers(layers), guestLayers(layers), surfaceLayers(layers) { - this->context.bufferPool.reset(); VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; info.flags = layers % 6u == 0 && extent.width == extent.height ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u; info.imageType = VK_IMAGE_TYPE_2D; @@ -1185,15 +1184,16 @@ public: info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; try { Check(context.Function("vkCreateImage")(context.device, &info, nullptr, &image), "vkCreateImage depth plane copy"); + owned = std::make_shared(context, image, VK_NULL_HANDLE); VkMemoryRequirements requirements{}; context.Function("vkGetImageMemoryRequirements")(context.device, image, &requirements); VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; allocation.allocationSize = requirements.size; allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory depth plane copy"); + owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory depth plane copy"); + memory = owned->memory; Check(context.Function("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory depth plane copy"); imageBytes = requirements.size; - owned = std::make_shared(context, image, memory); } catch (...) { release(); throw; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp index 4145b544b5..8ba0776098 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp @@ -46,6 +46,14 @@ PFN_vkCmdCopyImageToBuffer realCopyImageToBuffer = nullptr; PFN_vkCmdCopyBufferToImage realCopyBufferToImage = nullptr; std::atomic imageToBufferCopies{0}; std::atomic bufferToImageCopies{0}; +std::atomic lastFilledImage{VK_NULL_HANDLE}; +PFN_vkAllocateMemory realAllocateMemory = nullptr; +std::atomic memoryAllocations{0}; + +VKAPI_ATTR VkResult VKAPI_CALL CountAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* info, const VkAllocationCallbacks* allocator, VkDeviceMemory* memory) { + memoryAllocations.fetch_add(1); + return realAllocateMemory(device, info, allocator, memory); +} VKAPI_ATTR void VKAPI_CALL CountImageToBuffer(VkCommandBuffer commands, VkImage image, VkImageLayout layout, VkBuffer buffer, std::uint32_t count, const VkBufferImageCopy* regions) { imageToBufferCopies.fetch_add(1); @@ -54,6 +62,7 @@ VKAPI_ATTR void VKAPI_CALL CountImageToBuffer(VkCommandBuffer commands, VkImage VKAPI_ATTR void VKAPI_CALL CountBufferToImage(VkCommandBuffer commands, VkBuffer buffer, VkImage image, VkImageLayout layout, std::uint32_t count, const VkBufferImageCopy* regions) { bufferToImageCopies.fetch_add(1); + lastFilledImage = image; realCopyBufferToImage(commands, buffer, image, layout, count, regions); } @@ -64,6 +73,10 @@ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL CountingDeviceProc(VkDevice device, con realCopyImageToBuffer = reinterpret_cast(function); return reinterpret_cast(&CountImageToBuffer); } + if (std::strcmp(name, "vkAllocateMemory") == 0) { + realAllocateMemory = reinterpret_cast(function); + return reinterpret_cast(&CountAllocateMemory); + } if (std::strcmp(name, "vkCmdCopyBufferToImage") == 0) { realCopyBufferToImage = reinterpret_cast(function); return reinterpret_cast(&CountBufferToImage); @@ -322,6 +335,30 @@ void RecordedUploads(const Context& context, const WatchedBlock& block) { recorder.Sync(); } +void PooledPlaneCopies(const Context& context, const WatchedBlock& first, const WatchedBlock& second) { + ClearDepthSurfaces(context.device); + FillLayer(first.Data() + LayerBytes, 0.25f); + FillLayer(second.Data() + LayerBytes, 0.75f); + DepthSurfaceView(context, DepthTarget{first.Address(), 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0}); + DepthSurfaceView(context, DepthTarget{second.Address(), 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0}); + const auto sample = [&](const WatchedBlock& block, int round) { + lastFilledImage = VK_NULL_HANDLE; + bufferToImageCopies = 0; + Require(DepthSurfaceTexture(context, words, ArrayOver(block.Address()), components) != nullptr, "the 2D array over a depth surface was not sampled through its depth planes in round " + std::to_string(round)); + Require(DepthPlaneCopyCount() == 1, "the depth plane copies outgrew a budget that holds one in round " + std::to_string(round) + ", " + std::to_string(DepthPlaneCopyCount()) + " copies"); + Require(lastFilledImage.load() != VK_NULL_HANDLE && bufferToImageCopies.load() == 1, "a plane copy made again over a full budget was not filled from its surface once in round " + std::to_string(round) + ", " + std::to_string(bufferToImageCopies.load()) + " fills"); + }; + sample(first, 0); + sample(second, 0); + memoryAllocations = 0; + for (int round = 1; round <= 3; ++round) { + sample(first, round); + sample(second, round); + } + Require(memoryAllocations.load() == 0, "plane copies evicted and made again over a full budget allocated device memory " + std::to_string(memoryAllocations.load()) + " times instead of taking the evicted copies' memory back"); + ClearDepthSurfaces(context.device); +} + void RunCubeGather(const Context& base, const WatchedBlock& block) { TextureDetiler detiler(base); auto context = base; @@ -396,6 +433,11 @@ void RunCubeTiledSlice(const Context& base, const WatchedBlock& block) { int main() { try { +#ifdef _WIN32 + _putenv_s("APS5_DEPTH_PLANE_COPY_MIB", "1"); +#else + setenv("APS5_DEPTH_PLANE_COPY_MIB", "1", 1); +#endif if (!AgcDriver::GuestMemory::WriteWatched()) { std::puts("skipped, guest memory has no write watch"); return Skipped; @@ -416,6 +458,10 @@ int main() { Require(AgcDriver::GuestMemory::Watched(block.Address(), BlockBytes), "depth plane guest layers: the guest block is not write-watched"); Run(context, block); RecordedUploads(context, block); + { + WatchedBlock second; + PooledPlaneCopies(context, block, second); + } RunCubeGather(context, block); RunCubeTiledSlice(context, block); } From ce9c6cbd6ab65fa81354cc9ed275d9181a3ca053 Mon Sep 17 00:00:00 2001 From: raghav314 Date: Sun, 25 Oct 2026 08:13:29 +0530 Subject: [PATCH 2/3] fix(agc): share one depth plane copy across a surface's layer ranges Co-Authored-By: Claude Opus 5.5 --- .../Graphics/include/Texture.hpp | 2 +- .../Graphics/src/DepthSurface.cpp | 27 ++++++++------- .../libSceAgcDriver/Graphics/src/Texture.cpp | 4 +-- .../tests/execution/DepthPlaneGuestLayers.cpp | 34 +++++++++++++++++++ 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index 0c6aec2abf..0f96e69358 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -64,7 +64,7 @@ public: // compute pass writing it and the next pass sampling it share one image and copy nothing. // CanCopyFrom says whether the two descriptors address the same surface compatibly. Texture(const Context& context, const std::shared_ptr& source, const GuestTextureResource& descriptor, VkComponentMapping components); - Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D, std::uint32_t baseLayer = 0, std::shared_ptr owner = {}, VkImageUsageFlags usage = 0); + Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D, std::uint32_t baseLayer = 0, std::shared_ptr owner = {}, VkImageUsageFlags usage = 0, std::uint32_t layerCount = VK_REMAINING_ARRAY_LAYERS); struct DepthCopyTag {}; Texture(const Context& context, const std::shared_ptr& source, const GuestTextureResource& descriptor, VkComponentMapping components, DepthCopyTag); struct SrgbExpandTag {}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index ff8359d789..1cd1db00cf 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -1204,19 +1204,21 @@ public: DepthPlaneCopy& operator=(const DepthPlaneCopy&) = delete; std::shared_ptr Refresh(std::span slices, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType viewType, std::uint32_t baseLayer) { - Require(slices.size() == layers, "depth plane copy slices do not match its layers"); + const auto requested = static_cast(slices.size()); + Require(requested != 0 && requested <= layers, "depth plane copy slices exceed its layers"); + Require(baseLayer < requested, "a depth plane copy view starts past the slices it was given"); const auto geometry = DescribeSurface(resource); const bool guestFill = std::any_of(slices.begin(), slices.end(), [](const auto& slice) { return slice.image == VK_NULL_HANDLE; }); - if (guestFill && (geometry.layers != layers || geometry.sliceLinearBytes < sliceBytes() || geometry.mips.empty())) { + if (guestFill && (geometry.layers != requested || geometry.sliceLinearBytes < sliceBytes() || geometry.mips.empty())) { char text[320]; std::snprintf(text, sizeof(text), "AGC graphics: depth plane copy geometry does not match its layers (copy %ux%u, %u layers, %llu bytes a slice; texture %ux%u guest format %u, dimension %d, tile mode %u, %u layers, %llu bytes a slice, %zu mips)", extent.width, extent.height, layers, static_cast(sliceBytes()), resource.width, resource.height, resource.format, static_cast(resource.dimension), resource.tileMode, geometry.layers, static_cast(geometry.sliceLinearBytes), geometry.mips.size()); throw std::runtime_error(text); } - const auto stagingBytes = std::max(static_cast(geometry.sliceLinearBytes), static_cast(sliceBytes())) * layers; + const auto stagingBytes = std::max(static_cast(geometry.sliceLinearBytes), static_cast(sliceBytes())) * requested; std::vector> uploads; std::vector uploadLayers; std::vector copiedLayers; - for (std::uint32_t layer = 0; layer < layers; ++layer) { + for (std::uint32_t layer = 0; layer < requested; ++layer) { auto& held = guestLayers[layer]; auto& surface = surfaceLayers[layer]; if (slices[layer].image != VK_NULL_HANDLE) { @@ -1242,10 +1244,11 @@ public: if (staging == nullptr || stagingBytes > staging->Size()) staging = std::make_shared(context, stagingBytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); record(slices, resource, geometry, copiedLayers, uploads, uploadLayers, staging); } - const std::array key{static_cast(components.r), static_cast(components.g), static_cast(components.b), static_cast(components.a), static_cast(viewType), baseLayer}; + const auto viewLayers = viewType == VK_IMAGE_VIEW_TYPE_2D ? 1u : requested - baseLayer; + const std::array key{static_cast(components.r), static_cast(components.g), static_cast(components.b), static_cast(components.a), static_cast(viewType), baseLayer, viewLayers}; auto& texture = textures[key]; if (texture == nullptr) { - texture = std::make_shared(context, image, format, aspect(), components, viewType, baseLayer, owned); + texture = std::make_shared(context, image, format, aspect(), components, viewType, baseLayer, owned, 0, viewLayers); texture->MarkRefreshedPerUse(); } return texture; @@ -1272,7 +1275,7 @@ private: VkDeviceMemory memory = VK_NULL_HANDLE; std::shared_ptr owned; std::shared_ptr staging; - std::map, std::shared_ptr> textures; + std::map, std::shared_ptr> textures; std::vector guestLayers; std::vector surfaceLayers; bool filled = false; @@ -1362,8 +1365,8 @@ private: } }; -std::map, std::unique_ptr>& planeCopies() { - static auto* copies = new std::map, std::unique_ptr>(); +std::map, std::unique_ptr>& planeCopies() { + static auto* copies = new std::map, std::unique_ptr>(); return *copies; } @@ -1372,7 +1375,7 @@ std::uint64_t& planeCopyClock() { return clock; } -void evictPlaneCopies(const std::tuple& keep) { +void evictPlaneCopies(const std::tuple& keep) { static const VkDeviceSize budget = [] { const char* text = std::getenv("APS5_DEPTH_PLANE_COPY_MIB"); return static_cast(text != nullptr ? std::strtoull(text, nullptr, 10) : 256ull) << 20u; @@ -2401,9 +2404,9 @@ std::shared_ptr DepthSurfaceTexture(const Context& context, std::span copyKey{context.device, base.address, base.extent.width, base.extent.height, imageFormat, layers}; + const std::tuple copyKey{context.device, base.address, base.extent.width, base.extent.height, imageFormat}; auto& copy = copies[copyKey]; - if (copy == nullptr) copy = std::make_unique(context, base.extent, imageFormat, layers); + if (copy == nullptr || copy->layers < layers) copy = std::make_unique(context, base.extent, imageFormat, layers); copy->lastUse = ++planeCopyClock(); auto texture = copy->Refresh(slices, resource, components, cube || resource.dimension == TextureDimension::k2DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D, layered && !cube ? resource.baseArray : 0u); evictPlaneCopies(copyKey); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index d8394b6c5e..ce17d01c95 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -1066,7 +1066,7 @@ Texture::Texture(const Context& context, const std::shared_ptr& } } -Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType, std::uint32_t baseLayer, std::shared_ptr owner, VkImageUsageFlags usage) : context(context), owned(std::move(owner)) { +Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType, std::uint32_t baseLayer, std::shared_ptr owner, VkImageUsageFlags usage, std::uint32_t layerCount) : context(context), owned(std::move(owner)) { Require(owned == nullptr || (owned->image == depthImage && owned->context.device == context.device), "a depth sampled view must retain its own device and image"); layout = VK_IMAGE_LAYOUT_GENERAL; VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; @@ -1078,7 +1078,7 @@ Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthForma viewInfo.format = depthFormat; viewFormat = depthFormat; viewInfo.components = components; - viewInfo.subresourceRange = {aspect, 0, 1, baseLayer, viewType == VK_IMAGE_VIEW_TYPE_2D ? 1u : VK_REMAINING_ARRAY_LAYERS}; + viewInfo.subresourceRange = {aspect, 0, 1, baseLayer, viewType == VK_IMAGE_VIEW_TYPE_2D ? 1u : layerCount}; Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView depth plane"); viewRange = {viewInfo.viewType, viewInfo.subresourceRange.levelCount, viewInfo.subresourceRange.layerCount}; if (viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) return; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp index 8ba0776098..0bf3802b8f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp @@ -359,6 +359,39 @@ void PooledPlaneCopies(const Context& context, const WatchedBlock& first, const ClearDepthSurfaces(context.device); } +void SharedLayerRanges(const Context& context, const WatchedBlock& block) { + ClearDepthSurfaces(context.device); + auto resource = ArrayOver(block.Address()); + resource.depthOrLastArray = 3; + const auto geometry = DescribeSurface(resource); + for (std::uint32_t layer = 0; layer < 4; ++layer) { + DepthSurfaceView(context, DepthTarget{block.Address() + geometry.GuestLayerOffset(layer), 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0}); + } + DepthSurfaceView(context, DepthTarget{block.Address() + 16u * LayerBytes, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0}); + const auto sample = [&](std::uint32_t lastArray, std::uint32_t baseArray, std::uint32_t fromSlices, std::uint32_t intoCopy) { + auto range = resource; + range.depthOrLastArray = lastArray; + range.baseArray = baseArray; + imageToBufferCopies = 0; + bufferToImageCopies = 0; + const auto texture = DepthSurfaceTexture(context, words, range, components); + const auto when = "layers " + std::to_string(baseArray) + "-" + std::to_string(lastArray); + Require(texture != nullptr, "the 2D array over depth surfaces was not sampled through its depth planes for " + when); + if (imageToBufferCopies.load() != fromSlices || bufferToImageCopies.load() != intoCopy) { + throw std::runtime_error("sampling " + when + " of the depth surfaces recorded " + std::to_string(imageToBufferCopies.load()) + " depth surface copies and " + std::to_string(bufferToImageCopies.load()) + " plane copy fills, expected " + std::to_string(fromSlices) + " and " + std::to_string(intoCopy)); + } + const auto view = texture->SampledViewRange(false); + Require(view.layers == lastArray + 1u - baseArray, "the view sampling " + when + " covers " + std::to_string(view.layers) + " layers, not the texture's own"); + return texture; + }; + sample(3, 0, 4, 1); + sample(2, 0, 0, 0); + sample(1, 0, 0, 0); + sample(2, 1, 0, 0); + sample(3, 0, 0, 0); + ClearDepthSurfaces(context.device); +} + void RunCubeGather(const Context& base, const WatchedBlock& block) { TextureDetiler detiler(base); auto context = base; @@ -462,6 +495,7 @@ int main() { WatchedBlock second; PooledPlaneCopies(context, block, second); } + SharedLayerRanges(context, block); RunCubeGather(context, block); RunCubeTiledSlice(context, block); } From ab7834aaa26b6229f2d1df4c4d2932add5d074a4 Mon Sep 17 00:00:00 2001 From: raghav314 Date: Sun, 25 Oct 2026 20:21:02 +0530 Subject: [PATCH 3/3] test(agc): check plane copy memory reuse only where the pool keeps such a block Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/DepthPlaneGuestLayers.cpp | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp index 0bf3802b8f..532d98883f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" @@ -49,9 +50,13 @@ std::atomic bufferToImageCopies{0}; std::atomic lastFilledImage{VK_NULL_HANDLE}; PFN_vkAllocateMemory realAllocateMemory = nullptr; std::atomic memoryAllocations{0}; +std::atomic lastAllocationBytes{0}; +std::atomic lastAllocationType{0}; VKAPI_ATTR VkResult VKAPI_CALL CountAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* info, const VkAllocationCallbacks* allocator, VkDeviceMemory* memory) { memoryAllocations.fetch_add(1); + lastAllocationBytes = info->allocationSize; + lastAllocationType = info->memoryTypeIndex; return realAllocateMemory(device, info, allocator, memory); } @@ -350,12 +355,25 @@ void PooledPlaneCopies(const Context& context, const WatchedBlock& first, const }; sample(first, 0); sample(second, 0); + const auto keepsCopies = [&] { + if (context.bufferPool == nullptr) return false; + const VkMemoryAllocateInfo probe{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, nullptr, lastAllocationBytes.load(), lastAllocationType.load()}; + const auto memory = context.bufferPool->TakeImageMemory(probe, "vkAllocateMemory pool probe"); + const auto before = context.bufferPool->RetainedImages(); + context.bufferPool->PutImageMemory(memory, probe.allocationSize, probe.memoryTypeIndex); + const bool kept = context.bufferPool->RetainedImages() > before; + return kept; + }(); memoryAllocations = 0; for (int round = 1; round <= 3; ++round) { sample(first, round); sample(second, round); } - Require(memoryAllocations.load() == 0, "plane copies evicted and made again over a full budget allocated device memory " + std::to_string(memoryAllocations.load()) + " times instead of taking the evicted copies' memory back"); + if (!keepsCopies) { + std::printf("note: the device memory pool does not keep a block of a plane copy's size and memory type here (not device-tiered, or its heap over budget); the reuse of evicted copies' memory is not checked\n"); + } else { + Require(memoryAllocations.load() == 0, "plane copies evicted and made again over a full budget allocated device memory " + std::to_string(memoryAllocations.load()) + " times instead of taking the evicted copies' memory back"); + } ClearDepthSurfaces(context.device); }