render/vulkan: New staging buffer implementation

Implement a ring-buffer that uses timeline points to track and release
allocated spans, upgrading the buffer if it fills and shrinking it if it
has been 4x too large for many collections.
This commit is contained in:
Kenny Levinsen 2026-04-12 17:10:40 +02:00
parent e8c03e9ce9
commit 1ec740692f
4 changed files with 267 additions and 155 deletions

View file

@ -284,8 +284,6 @@ struct wlr_vk_command_buffer {
uint64_t timeline_point;
// Textures to destroy after the command buffer completes
struct wl_list destroy_textures; // wlr_vk_texture.destroy_link
// Staging shared buffers to release after the command buffer completes
struct wl_list stage_buffers; // wlr_vk_shared_buffer.link
// Color transform to unref after the command buffer completes
struct wlr_color_transform *color_transform;
@ -352,7 +350,7 @@ struct wlr_vk_renderer {
struct {
struct wlr_vk_command_buffer *cb;
uint64_t last_timeline_point;
struct wl_list buffers; // wlr_vk_shared_buffer.link
struct wl_list buffers; // wlr_vk_stage_buffer.link
} stage;
struct {
@ -453,14 +451,27 @@ struct wlr_vk_render_pass {
struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *renderer,
struct wlr_vk_render_buffer *buffer, const struct wlr_buffer_pass_options *options);
// Suballocates a buffer span with the given size that can be mapped
// and used as staging buffer. The allocation is implicitly released when the
// stage cb has finished execution. The start of the span will be a multiple
// of the given alignment.
// Suballocates a buffer span with the given size from the staging ring buffer
// that is mapped for CPU access. vulkan_stage_mark_submit must be called after
// allocations are made to mark the timeline point after which the allocations
// will be released. The start of the span will be a multiple of alignment.
struct wlr_vk_buffer_span vulkan_get_stage_span(
struct wlr_vk_renderer *renderer, VkDeviceSize size,
VkDeviceSize alignment);
// Returns unused bytes at the end of a buffer span back to the ring buffer.
// This allows the caller to allocate for worst-case consumption and return the
// unused remainder. This must not be called after vulkan_stage_mark_submit,
// and only works for the last made allocation.
void vulkan_return_stage_span(struct wlr_vk_buffer_span *span,
VkDeviceSize return_size);
// Records a watermark on all staging buffers with new allocations with the
// specified timeline point. Once the timeline point is passed, the span will
// be reclaimed by vulkan_stage_buffer_reclaim.
void vulkan_stage_mark_submit(struct wlr_vk_renderer *renderer,
uint64_t timeline_point);
// Tries to allocate a texture descriptor set. Will additionally
// return the pool it was allocated from when successful (for freeing it later).
struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds(
@ -544,29 +555,45 @@ struct wlr_vk_descriptor_pool {
struct wl_list link; // wlr_vk_renderer.descriptor_pools
};
struct wlr_vk_allocation {
VkDeviceSize start;
VkDeviceSize size;
struct wlr_vk_stage_watermark {
VkDeviceSize head;
uint64_t timeline_point;
};
// List of suballocated staging buffers.
// Used to upload to/read from device local images.
struct wlr_vk_shared_buffer {
struct wl_list link; // wlr_vk_renderer.stage.buffers or wlr_vk_command_buffer.stage_buffers
// Ring buffer for staging transfers
struct wlr_vk_stage_buffer {
struct wl_list link; // wlr_vk_renderer.stage.buffers
bool active;
VkBuffer buffer;
VkDeviceMemory memory;
VkDeviceSize buf_size;
void *cpu_mapping;
struct wl_array allocs; // struct wlr_vk_allocation
int64_t last_used_ms;
VkDeviceSize head;
VkDeviceSize tail;
struct wl_array watermarks; // struct wlr_vk_stage_watermark
VkDeviceSize peak_utilization;
int underutil_count;
};
// Suballocated range on a buffer.
// Suballocated range on a staging ring buffer.
struct wlr_vk_buffer_span {
struct wlr_vk_shared_buffer *buffer;
struct wlr_vk_allocation alloc;
struct wlr_vk_stage_buffer *buffer;
VkDeviceSize offset;
VkDeviceSize size;
};
// Suballocate a span of size bytes from a staging ring buffer, with the
// returned offset rounded up to the given alignment. Returns the byte offset
// of the allocation, or (VkDeviceSize)-1 if the buffer is too full to fit it.
VkDeviceSize vulkan_stage_buffer_alloc(struct wlr_vk_stage_buffer *buf,
VkDeviceSize size, VkDeviceSize alignment);
// Free all allocations covered by watermarks whose timeline point has been
// reached. Returns true if the buffer is now fully drained.
bool vulkan_stage_buffer_reclaim(struct wlr_vk_stage_buffer *buf,
uint64_t current_point);
// Prepared form for a color transform
struct wlr_vk_color_transform {