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 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-10 23:02:15 -05:00
parent 7145c3f208
commit 73abd6577f
3 changed files with 19 additions and 2 deletions

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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;