render/vulkan: Better bucket size selection

The current buffer allocator allocates a buffer that is either the
minimum stage span, the requested size times two, or the largest current
allocation times two.

Imagine needing a miniscule allocation. We currently have 1M, 8M and 16M
buffers, but the 1M buffer is full or not available. The last rule would
cause us to request a 32M buffer, rather than a more appropriate 2M
buffer to fill the bucket sequence.

Make two adjustments to this logic:

1. Round the requested size up to the nearest power of two to avoid odd
   bucket sizes.

2. Look through the available buffers and find a hole in the bucket
   sequence to fill, which is made easy by the power of two rule above
   as we can just iterate until the buffer we are looking at is more
   than 2x our current target.

The buffer we create is inserted into the middle of the list of buffers
as needed to maintain the size order.
This commit is contained in:
Kenny Levinsen 2024-11-05 10:15:49 +01:00
parent 6dcab66c73
commit e180bedd61

View file

@ -197,6 +197,25 @@ static void shared_buffer_destroy(struct wlr_vk_renderer *r,
free(buffer);
}
static VkDeviceSize minimum_bucket(VkDeviceSize size) {
// Require at least 2x space in the bucket
size *= 2;
if (size < min_stage_size) {
return min_stage_size;
}
// Calculate the smallest containing power of two
size -= 1;
size |= size >> 1;
size |= size >> 2;
size |= size >> 4;
size |= size >> 8;
size |= size >> 16;
size |= size >> 32;
return size + 1;
}
struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r,
VkDeviceSize size, VkDeviceSize alignment) {
// try to find free span
@ -244,16 +263,22 @@ struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r,
goto error_alloc;
}
// we didn't find a free buffer - create one
// size = clamp(max(size * 2, prev_size * 2), min_size, max_size)
VkDeviceSize bsize = size * 2;
bsize = bsize < min_stage_size ? min_stage_size : bsize;
if (!wl_list_empty(&r->stage.buffers)) {
struct wl_list *last_link = r->stage.buffers.prev;
struct wlr_vk_shared_buffer *prev = wl_container_of(
last_link, prev, link);
VkDeviceSize last_size = 2 * prev->buf_size;
bsize = bsize < last_size ? last_size : bsize;
// We allocate buffers in buckets of increasing size, each twice the
// previous and always able to hold at least 2x the allocation request.
VkDeviceSize bsize = minimum_bucket(size);
struct wl_list *insertion_target = NULL;
wl_list_for_each_reverse(buf, &r->stage.buffers, link) {
if (buf->buf_size >= bsize) {
if (buf->buf_size > bsize * 2) {
// There have found a missing bucket size in the sequence.
insertion_target = &buf->link;
break;
}
bsize *= 2;
}
}
if (insertion_target == NULL) {
insertion_target = &r->stage.buffers;
}
if (bsize > max_stage_size) {
@ -325,7 +350,7 @@ struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r,
}
buf->buf_size = bsize;
wl_list_insert(&r->stage.buffers, &buf->link);
wl_list_insert(insertion_target, &buf->link);
*a = (struct wlr_vk_allocation){
.start = 0,