aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkraibse <33869366+kraibse@users.noreply.github.com>2026-10-30 17:14:52 +0100
committerkraibse <33869366+kraibse@users.noreply.github.com>2026-10-30 17:27:04 +0100
commit6f5e137ec4eb83bbff9ea15f1ad1a8407cfc0cba (patch)
treee28995b98d07026c622573410f38f2665aa585f9
parentd4ee7fcd5129e7ee32ff38a5aef9b449d5a756de (diff)
texture_cache: log unbacked layers over the image's memory cap
-rw-r--r--src/video_core/nx2.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp22
-rw-r--r--src/video_core/texture_cache/texture_cache.h1
3 files changed, 24 insertions, 1 deletions
diff --git a/src/video_core/nx2.h b/src/video_core/nx2.h
index 1a3bae93ec..4b0d3d5f62 100644
--- a/src/video_core/nx2.h
+++ b/src/video_core/nx2.h
@@ -96,7 +96,7 @@ inline void Nx2BackSparseWrite(u64 gpu_address, u64 size) {
// Which blocks of each sparse region have been written (got memory), for the texture cache: an
// image over blocks nobody wrote is all zeros and needs no device memory. Its own lock, taken last:
-// the texture cache asks with its lock held, and nx2:gpu updates it holding none of the GPU's.
+// the texture cache asks with its lock held, and nx2:gpu updates it holding only its region mutex.
struct Nx2SparseRegionState {
u64 size;
u64 block_size;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index b77f9af5f8..9204ade9eb 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -2620,6 +2620,20 @@ std::vector<u32> Image::BindLayers(std::span<const u32> layers) {
const VkDeviceSize cap = SparseLayersCap(device);
std::vector<VkSparseImageMemoryBind> image_binds;
std::vector<VkSparseMemoryBind> opaque_binds;
+ const bool tail_was_bound = s.tail_memory != VK_NULL_HANDLE;
+ std::vector<u32> fresh; // Layers given memory by this call
+ // A failed bind leaves the layers as they were, so the next call tries them again.
+ const auto roll_back = [&] {
+ for (const u32 layer : fresh) {
+ s.allocator->FreeSparseMemory(s.layer_memory[layer]);
+ s.layer_memory[layer] = VK_NULL_HANDLE;
+ s.bound_bytes -= s.layer_bytes;
+ }
+ if (s.tail_memory != VK_NULL_HANDLE && !tail_was_bound) {
+ s.allocator->FreeSparseMemory(s.tail_memory);
+ s.tail_memory = VK_NULL_HANDLE;
+ }
+ };
for (const u32 layer : layers) {
if (s.layer_memory[layer] != VK_NULL_HANDLE) {
bound.push_back(layer);
@@ -2647,6 +2661,7 @@ std::vector<u32> Image::BindLayers(std::span<const u32> layers) {
}
s.layer_memory[layer] = allocation;
s.bound_bytes += s.layer_bytes;
+ fresh.push_back(layer);
for (u32 level = 0; level < tail_lod; ++level) {
const u32 width = (std::max)(s.width >> level, 1U);
const u32 height = (std::max)(s.height >> level, 1U);
@@ -2693,6 +2708,12 @@ std::vector<u32> Image::BindLayers(std::span<const u32> layers) {
.memoryOffset = offset,
.flags = 0,
});
+ } else {
+ LOG_WARNING(Render_Vulkan, "Sparse layered image {:#x}: out of device memory for the "
+ "mip tail",
+ gpu_addr);
+ roll_back();
+ return {};
}
}
if (image_binds.empty() && opaque_binds.empty()) {
@@ -2737,6 +2758,7 @@ std::vector<u32> Image::BindLayers(std::span<const u32> layers) {
if (result != VK_SUCCESS) {
LOG_ERROR(Render_Vulkan, "Sparse layered image {:#x}: binding failed ({})", gpu_addr,
static_cast<int>(result));
+ roll_back();
return {};
}
fence.Wait();
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 3de82d2eb4..63abffed0f 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -2999,6 +2999,7 @@ void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool
if (True(image.flags & ImageFlagBits::SparseLayers)) {
std::vector<u32> layers(static_cast<size_t>(image.info.resources.layers));
std::iota(layers.begin(), layers.end(), 0U);
+ // Layers over the image's memory cap stay unbacked (logged once).
image.BindLayers(layers);
}
}