From 73abd6577f76cbbc4ac869aed2e83ee6a462f085 Mon Sep 17 00:00:00 2001 From: chrg6 Date: Tue, 10 Nov 2026 23:02:15 -0500 Subject: [PATCH] Try a smaller allocation before giving up on a texture upload when the video memory is used up. On an integrated Radeon 890M the ASTC scratch buffer failed to allocate and then a staging buffer ended the run. The scratch asks for 32 MiB whatever the upload needs, and the staging pool grows only in 64 MiB blocks of one memory type. When the device reports it is out of memory, the scratch is now tried again at the size the upload needs, and a staging buffer that does not fit the pool is tried again outside it with the same mapping, where a smaller block or another host-visible type may still have room. The menu's own upload flushes what it wrote, since such a buffer may lie in memory that is not coherent. Built and run through the memory probe (96 GPU checks pass); the out-of-memory paths themselves were not reached here. Co-Authored-By: Claude Fable 5.2 --- runtime/ui/menu_art.cpp | 5 +++++ runtime/vk_astc.c | 10 ++++++++-- runtime/vk_memory.c | 6 ++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/runtime/ui/menu_art.cpp b/runtime/ui/menu_art.cpp index 3a623ca0..ad7f31f8 100644 --- a/runtime/ui/menu_art.cpp +++ b/runtime/ui/menu_art.cpp @@ -134,6 +134,11 @@ static int upload_rgba(Art &a, const uint8_t *rgba, uint32_t w, uint32_t h, uint } memcpy(map, chain, (size_t)bytes); free(chain); + // The batch flushes its own staging buffers. This one is caller-owned and can be noncoherent. + if (vmaFlushAllocation(c->vma, sa, 0, bytes) != VK_SUCCESS) { + vmaDestroyBuffer(c->vma, sb, sa); + return art_free(a), 0; + } VkCommandBuffer cb = vk_upload_begin(); VkImageMemoryBarrier2 b = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2}; b.srcStageMask = VK_PIPELINE_STAGE_2_NONE; diff --git a/runtime/vk_astc.c b/runtime/vk_astc.c index daa4e5b9..04bda92b 100644 --- a/runtime/vk_astc.c +++ b/runtime/vk_astc.c @@ -483,13 +483,19 @@ static VkBuffer scratch_get(VkCtx *c, VkCommandBuffer cb, VkDeviceSize need, int VmaAllocationCreateInfo aci = {.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE}; VkBuffer b; VmaAllocation a; - if (vmaCreateBuffer(c->vma, &bi, &aci, &b, &a, NULL) != VK_SUCCESS) { + VkResult r = vmaCreateBuffer(c->vma, &bi, &aci, &b, &a, NULL); + if (r == VK_ERROR_OUT_OF_DEVICE_MEMORY && bi.size > need) { + // The usual scratch leaves room for later textures. This upload can still fit without the spare bytes. + bi.size = need; + r = vmaCreateBuffer(c->vma, &bi, &aci, &b, &a, NULL); + } + if (r != VK_SUCCESS) { plat_mutex_unlock(&g_lock); return VK_NULL_HANDLE; } // the old one may still be read by its command buffer: freed once the batches recorded so far completed if (S.buf && S.dev == c->device) vk_defer_buffer(S.buf, S.alloc); - S.buf = b, S.alloc = a, S.size = size, S.dev = c->device; + S.buf = b, S.alloc = a, S.size = bi.size, S.dev = c->device; n_scratch_new++; vk_texstat_time(VK_TS_SCRATCH, t0); } else { diff --git a/runtime/vk_memory.c b/runtime/vk_memory.c index 1c17fd59..5574d9c6 100644 --- a/runtime/vk_memory.c +++ b/runtime/vk_memory.c @@ -3679,6 +3679,12 @@ void *vk_staging(VkDeviceSize size, VkBuffer *buf, VmaAllocation *alloc) { VkBuffer b; VmaAllocation a; VkResult r = vmaCreateBuffer(c->vma, &bi, &aci, &b, &a, &info); + if (r == VK_ERROR_OUT_OF_DEVICE_MEMORY && aci.pool) { + // The staging pool can grow only in 64 MiB blocks of its one memory type. A smaller allocation may + // still fit in a default block or another host-visible type. Keep the same mapping requirements. + aci.pool = NULL; + r = vmaCreateBuffer(c->vma, &bi, &aci, &b, &a, &info); + } if (r != VK_SUCCESS) rt_fatal("vk: staging buffer of %llu bytes failed (%d)", (unsigned long long)size, (int)r); vk_texstat_add(VK_TS_STG_NEW, 1, bi.size); *buf = b;