aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/vulkan_common
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-30 05:16:34 -0400
committerkraibse <33869366+kraibse@users.noreply.github.com>2026-10-30 17:27:04 +0100
commit940d44ca25628288f1b5f64c25c022b3a4ae5ce2 (patch)
tree660cb9e8b59bc1fc074a891c77cc7225802fb8d8 /src/video_core/vulkan_common
parent97ce27ab35153884d2fd05c278433928e429cd91 (diff)
texture_cache: give images over unwritten sparse blocks no device memory
Mario Kart World creates nine terrain layer pools in sparse regions (four R8 16384x16384, four RG16 8192x8192, one R16 8192x8192: 2.9 GB of device memory). In 180 s of title and attract demo not one of their blocks is written and nothing renders into them, so they hold zeros; on the console only the few tiles the game maps have memory. nx2:gpu now publishes which 4 MiB blocks of each sparse region have been written (nx2.h, a leaf lock the texture cache may take). An image of 32 MiB or more lying in one sparse region with no written block is created as a sparse resident image with nothing bound: reads return zero, as an unmapped tile does. The first write into its range, or the first time the GPU renders into it (that range then counts as written), marks it remapped, so a backed image replaces it at its next use. Mario Kart World, 180 s: the collector deletes 0-80 images per 600 frames instead of thousands, frames over 100 ms per 600 presents drop from 1-9 to 0-1, peak working set 14.7 GB. One render target in a small sparse region gets replaced once (logged).
Diffstat (limited to 'src/video_core/vulkan_common')
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp29
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.h4
2 files changed, 23 insertions, 10 deletions
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 8bf692b00e..92f537aa3c 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -248,21 +248,15 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci, bool dedicat
}
if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY && ci.imageType != VK_IMAGE_TYPE_1D) {
// Too big to back at all, e.g. title 1's sparse virtual texture arrays (8192x8192,
- // 68 layers, ~23 GB): on the console only resident tiles have memory. Create a sparse
- // resident image with nothing bound, so views and copies still work; reads of it return
- // zero and writes are dropped, instead of aborting.
- VkImageCreateInfo sparse_ci = ci;
- sparse_ci.flags |= VK_IMAGE_CREATE_SPARSE_BINDING_BIT | VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT;
- VkImage sparse{};
- if (device.GetDispatchLoader().vkCreateImage(*device.GetLogical(), &sparse_ci, nullptr,
- &sparse) == VK_SUCCESS) {
+ // 68 layers, ~23 GB): on the console only resident tiles have memory. Use an image with
+ // nothing bound, so views and copies still work, instead of aborting.
+ if (vk::Image sparse = CreateUnbackedImage(ci)) {
LOG_WARNING(Render_Vulkan,
"Image {}x{}x{} ({} levels, {} layers, format {}) does not fit; using an "
"unbacked sparse image",
ci.extent.width, ci.extent.height, ci.extent.depth, ci.mipLevels,
ci.arrayLayers, static_cast<u32>(ci.format));
- return vk::Image(sparse, ci.usage, *device.GetLogical(), allocator, VK_NULL_HANDLE,
- device.GetDispatchLoader());
+ return sparse;
}
}
if (result != VK_SUCCESS) {
@@ -286,6 +280,21 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci, bool dedicat
device.GetDispatchLoader());
}
+vk::Image MemoryAllocator::CreateUnbackedImage(const VkImageCreateInfo &ci) const {
+ if (ci.imageType == VK_IMAGE_TYPE_1D) {
+ return vk::Image{};
+ }
+ VkImageCreateInfo sparse_ci = ci;
+ sparse_ci.flags |= VK_IMAGE_CREATE_SPARSE_BINDING_BIT | VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT;
+ VkImage sparse{};
+ if (device.GetDispatchLoader().vkCreateImage(*device.GetLogical(), &sparse_ci, nullptr,
+ &sparse) != VK_SUCCESS) {
+ return vk::Image{};
+ }
+ return vk::Image(sparse, ci.usage, *device.GetLogical(), allocator, VK_NULL_HANDLE,
+ device.GetDispatchLoader());
+}
+
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const {
// MESA will do memcpy() if not marked as host cached, so just force mark it for most buffers
auto const anv_flags = (usage == MemoryUsage::Stream
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
index fa7d4b41c3..99649b9aef 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.h
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -107,6 +107,10 @@ namespace Vulkan {
/// images would otherwise leave holes in the shared blocks).
vk::Image CreateImage(const VkImageCreateInfo &ci, bool dedicated = false) const;
+ /// A sparse resident image with nothing bound (reads return zero, writes are dropped), or
+ /// a null image when the device can't make one.
+ vk::Image CreateUnbackedImage(const VkImageCreateInfo &ci) const;
+
vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const;
vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage,