render/vulkan: Upgrade stage span allocator

The old stage span allocator had two primary flaws:

1. If a shared buffer was used by one command buffer, the entire buffer
   would be held and any remaining regions unusable until that command
   buffer completed.

2. Allocated shared buffers would never be released.

Instead, have command buffers only hold the specific spans rather than
whole buffers, and release any buffers who have been unused across a
certain number of command buffer fetches.
This commit is contained in:
Kenny Levinsen 2024-07-01 01:13:30 +02:00
parent 67b88e46b0
commit cc30acfd4e
4 changed files with 165 additions and 99 deletions

View file

@ -247,7 +247,7 @@ struct wlr_vk_command_buffer {
// Textures to destroy after the command buffer completes // Textures to destroy after the command buffer completes
struct wl_list destroy_textures; // wlr_vk_texture.destroy_link struct wl_list destroy_textures; // wlr_vk_texture.destroy_link
// Staging shared buffers to release after the command buffer completes // Staging shared buffers to release after the command buffer completes
struct wl_list stage_buffers; // wlr_vk_shared_buffer.link struct wl_list stage_spans; // wlr_vk_stage_span.link
// Color transform to unref after the command buffer completes // Color transform to unref after the command buffer completes
struct wlr_color_transform *color_transform; struct wlr_color_transform *color_transform;
@ -313,6 +313,7 @@ struct wlr_vk_renderer {
struct wlr_vk_command_buffer *cb; struct wlr_vk_command_buffer *cb;
uint64_t last_timeline_point; uint64_t last_timeline_point;
struct wl_list buffers; // wlr_vk_shared_buffer.link struct wl_list buffers; // wlr_vk_shared_buffer.link
struct wl_list spans; // wlr_vk_stage_span.link
} stage; } stage;
struct { struct {
@ -387,9 +388,10 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend
// and used as staging buffer. The allocation is implicitly released when the // 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 // stage cb has finished execution. The start of the span will be a multiple
// of the given alignment. // of the given alignment.
struct wlr_vk_buffer_span vulkan_get_stage_span( struct wlr_vk_stage_span *vulkan_get_stage_span(
struct wlr_vk_renderer *renderer, VkDeviceSize size, struct wlr_vk_renderer *renderer, VkDeviceSize size,
VkDeviceSize alignment); VkDeviceSize alignment);
void vulkan_return_stage_span(struct wlr_vk_renderer *r, struct wlr_vk_stage_span *span);
// Tries to allocate a texture descriptor set. Will additionally // Tries to allocate a texture descriptor set. Will additionally
// return the pool it was allocated from when successful (for freeing it later). // return the pool it was allocated from when successful (for freeing it later).
@ -471,29 +473,32 @@ struct wlr_vk_descriptor_pool {
struct wl_list link; // wlr_vk_renderer.descriptor_pools struct wl_list link; // wlr_vk_renderer.descriptor_pools
}; };
struct wlr_vk_allocation { struct wlr_vk_stage_span {
struct wl_list link; // wlr_vk_renderer.stage.spans
// usage_link is a reference from the command buffer using the span.
// Separate from the main link to not mess up ordering.
struct wl_list usage_link; // wlr_vk_command_buffer.stage_spans
struct wlr_vk_shared_buffer *buffer;
VkDeviceSize start; VkDeviceSize start;
VkDeviceSize size; VkDeviceSize size;
bool free;
}; };
// List of suballocated staging buffers. // List of suballocated staging buffers.
// Used to upload to/read from device local images. // Used to upload to/read from device local images.
struct wlr_vk_shared_buffer { struct wlr_vk_shared_buffer {
struct wl_list link; // wlr_vk_renderer.stage.buffers or wlr_vk_command_buffer.stage_buffers struct wl_list link; // wlr_vk_renderer.stage.buffers
VkBuffer buffer; VkBuffer buffer;
VkDeviceMemory memory; VkDeviceMemory memory;
VkDeviceSize buf_size; VkDeviceSize buf_size;
void *cpu_mapping; void *cpu_mapping;
struct wl_array allocs; // struct wlr_vk_allocation
};
// Suballocated range on a buffer. VkDeviceSize active;
struct wlr_vk_buffer_span { uint64_t unused_counter;
struct wlr_vk_shared_buffer *buffer;
struct wlr_vk_allocation alloc;
}; };
// Lookup table for a color transform // Lookup table for a color transform
struct wlr_vk_color_transform { struct wlr_vk_color_transform {
struct wlr_addon addon; // owned by: wlr_vk_renderer struct wlr_addon addon; // owned by: wlr_vk_renderer

View file

@ -430,15 +430,6 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) {
free(render_wait); free(render_wait);
struct wlr_vk_shared_buffer *stage_buf, *stage_buf_tmp;
wl_list_for_each_safe(stage_buf, stage_buf_tmp, &renderer->stage.buffers, link) {
if (stage_buf->allocs.size == 0) {
continue;
}
wl_list_remove(&stage_buf->link);
wl_list_insert(&stage_cb->stage_buffers, &stage_buf->link);
}
if (!vulkan_sync_render_buffer(renderer, render_buffer, render_cb)) { if (!vulkan_sync_render_buffer(renderer, render_buffer, render_cb)) {
wlr_log(WLR_ERROR, "Failed to sync render buffer"); wlr_log(WLR_ERROR, "Failed to sync render buffer");
} }
@ -815,14 +806,14 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer,
size_t bytes_per_block = 4 * sizeof(float); size_t bytes_per_block = 4 * sizeof(float);
size_t size = lut_3d->dim_len * lut_3d->dim_len * lut_3d->dim_len * bytes_per_block; size_t size = lut_3d->dim_len * lut_3d->dim_len * lut_3d->dim_len * bytes_per_block;
struct wlr_vk_buffer_span span = vulkan_get_stage_span(renderer, struct wlr_vk_stage_span *span = vulkan_get_stage_span(renderer,
size, bytes_per_block); size, bytes_per_block);
if (!span.buffer || span.alloc.size != size) { if (span == NULL) {
wlr_log(WLR_ERROR, "Failed to retrieve staging buffer"); wlr_log(WLR_ERROR, "Failed to retrieve staging buffer");
goto fail_imageview; goto fail_imageview;
} }
char *map = (char*)span.buffer->cpu_mapping + span.alloc.start; char *map = (char*)span->buffer->cpu_mapping + span->start;
float *dst = (float*)map; float *dst = (float*)map;
size_t dim_len = lut_3d->dim_len; size_t dim_len = lut_3d->dim_len;
for (size_t b_index = 0; b_index < dim_len; b_index++) { for (size_t b_index = 0; b_index < dim_len; b_index++) {
@ -840,19 +831,21 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer,
} }
VkCommandBuffer cb = vulkan_record_stage_cb(renderer); VkCommandBuffer cb = vulkan_record_stage_cb(renderer);
wl_list_insert(&renderer->stage.cb->stage_spans, &span->usage_link);
vulkan_change_layout(cb, *image, vulkan_change_layout(cb, *image,
VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT); VK_ACCESS_TRANSFER_WRITE_BIT);
VkBufferImageCopy copy = { VkBufferImageCopy copy = {
.bufferOffset = span.alloc.start, .bufferOffset = span->start,
.imageExtent.width = lut_3d->dim_len, .imageExtent.width = lut_3d->dim_len,
.imageExtent.height = lut_3d->dim_len, .imageExtent.height = lut_3d->dim_len,
.imageExtent.depth = lut_3d->dim_len, .imageExtent.depth = lut_3d->dim_len,
.imageSubresource.layerCount = 1, .imageSubresource.layerCount = 1,
.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
}; };
vkCmdCopyBufferToImage(cb, span.buffer->buffer, *image, vkCmdCopyBufferToImage(cb, span->buffer->buffer, *image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy); VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy);
vulkan_change_layout(cb, *image, vulkan_change_layout(cb, *image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT,

View file

@ -39,7 +39,9 @@
// might still be a good idea. // might still be a good idea.
static const VkDeviceSize min_stage_size = 1024 * 1024; // 1MB static const VkDeviceSize min_stage_size = 1024 * 1024; // 1MB
static const VkDeviceSize special_stage_size = 16 * min_stage_size; // 16MB
static const VkDeviceSize max_stage_size = 256 * min_stage_size; // 256MB static const VkDeviceSize max_stage_size = 256 * min_stage_size; // 256MB
static const uint64_t max_stage_unused_frames = 1024;
static const size_t start_descriptor_pool_size = 256u; static const size_t start_descriptor_pool_size = 256u;
static bool default_debug = true; static bool default_debug = true;
@ -174,13 +176,21 @@ static void shared_buffer_destroy(struct wlr_vk_renderer *r,
if (!buffer) { if (!buffer) {
return; return;
} }
wlr_log(WLR_DEBUG, "Destroying vk staging buffer of size %" PRIu64, buffer->buf_size);
if (buffer->allocs.size > 0) { if (buffer->active > 0) {
wlr_log(WLR_ERROR, "shared_buffer_finish: %zu allocations left", wlr_log(WLR_ERROR, "shared_buffer_destroy: spans still in use");
buffer->allocs.size / sizeof(struct wlr_vk_allocation)); }
struct wlr_vk_stage_span *span, *span_tmp;
wl_list_for_each_safe(span, span_tmp, &r->stage.spans, link) {
if (span->buffer != buffer) {
continue;
}
wl_list_remove(&span->usage_link);
wl_list_remove(&span->link);
free(span);
} }
wl_array_release(&buffer->allocs);
if (buffer->cpu_mapping) { if (buffer->cpu_mapping) {
vkUnmapMemory(r->dev->dev, buffer->memory); vkUnmapMemory(r->dev->dev, buffer->memory);
buffer->cpu_mapping = NULL; buffer->cpu_mapping = NULL;
@ -196,44 +206,71 @@ static void shared_buffer_destroy(struct wlr_vk_renderer *r,
free(buffer); free(buffer);
} }
struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r, void vulkan_return_stage_span(struct wlr_vk_renderer *r, struct wlr_vk_stage_span *span) {
span->buffer->active -= span->size;
span->free = true;
// Merge next free span into this one, if one exists
if (span->link.next != &r->stage.spans) {
struct wlr_vk_stage_span *next = wl_container_of(span->link.next, next, link);
if (next->free && next->buffer == span->buffer) {
span->size += next->size;
wl_list_remove(&next->link);
free(next);
}
}
// Merge this free span into the previous one, if one exists
if (span->link.prev != &r->stage.spans) {
struct wlr_vk_stage_span *prev = wl_container_of(span->link.prev, prev, link);
if (prev->free && prev->buffer == span->buffer) {
prev->size += span->size;
wl_list_remove(&span->link);
free(span);
}
}
}
struct wlr_vk_stage_span *vulkan_get_stage_span(struct wlr_vk_renderer *r,
VkDeviceSize size, VkDeviceSize alignment) { VkDeviceSize size, VkDeviceSize alignment) {
// try to find free span // try to find free span
// simple greedy allocation algorithm - should be enough for this usecase // simple greedy allocation algorithm - should be enough for this usecase
// since all allocations are freed together after the frame // since all allocations are freed together after the frame
struct wlr_vk_shared_buffer *buf; struct wlr_vk_stage_span *span;
wl_list_for_each_reverse(buf, &r->stage.buffers, link) { wl_list_for_each_reverse(span, &r->stage.spans, link) {
VkDeviceSize start = 0u; if (!span->free || span->size < size) {
if (buf->allocs.size > 0) {
const struct wlr_vk_allocation *allocs = buf->allocs.data;
size_t allocs_len = buf->allocs.size / sizeof(struct wlr_vk_allocation);
const struct wlr_vk_allocation *last = &allocs[allocs_len - 1];
start = last->start + last->size;
}
assert(start <= buf->buf_size);
// ensure the proposed start is a multiple of alignment
start += alignment - 1 - ((start + alignment - 1) % alignment);
if (buf->buf_size - start < size) {
continue; continue;
} }
struct wlr_vk_allocation *a = wl_array_add(&buf->allocs, sizeof(*a)); if (size <= special_stage_size && span->buffer->buf_size > special_stage_size) {
if (a == NULL) { // Avoid accidentally holding on to big buffers
wlr_log_errno(WLR_ERROR, "Allocation failed"); continue;
goto error_alloc;
} }
*a = (struct wlr_vk_allocation){ span->free = false;
.start = start, span->buffer->active += size;
.size = size, if (span->size == size) {
}; // Perfect fit
return (struct wlr_vk_buffer_span) { return span;
.buffer = buf, }
.alloc = *a,
// Cleave the span
struct wlr_vk_stage_span *free_span = malloc(sizeof(*free_span));
if (free_span == NULL) {
span->free = true;
span->buffer->active -= size;
return NULL;
}
*free_span = (struct wlr_vk_stage_span){
.buffer = span->buffer,
.size = span->size - size,
.start = span->start + size,
.free = true,
}; };
wl_list_init(&free_span->usage_link);
wl_list_insert(&span->link, &free_span->link);
span->size = size;
return span;
} }
if (size > max_stage_size) { if (size > max_stage_size) {
@ -243,21 +280,28 @@ struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r,
goto error_alloc; goto error_alloc;
} }
// we didn't find a free buffer - create one // Pick the next bucket size. If the size is below our "special" threshold,
// size = clamp(max(size * 2, prev_size * 2), min_size, max_size) // double the last bucket size. Otherwise allocate the requested size
VkDeviceSize bsize = size * 2; // directly.
bsize = bsize < min_stage_size ? min_stage_size : bsize; VkDeviceSize bsize = min_stage_size;
if (!wl_list_empty(&r->stage.buffers)) { struct wlr_vk_shared_buffer *buf;
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;
}
if (bsize > max_stage_size) { if (size > special_stage_size) {
wlr_log(WLR_INFO, "vulkan stage buffers have reached max size"); // The size is too big for our buckets, alloate directly
bsize = max_stage_size; bsize = size;
} else {
bsize = min_stage_size;
// We start by picking the last bucket size * 2
wl_list_for_each_reverse(buf, &r->stage.buffers, link) {
if (buf->buf_size < special_stage_size && buf->buf_size * 2 > bsize) {
bsize = buf->buf_size * 2;
}
}
// If double the last bucket is not enough, keep doubling until we hit the
// size for dedicated allocations.
while (bsize < size && bsize < special_stage_size) {
bsize *= 2;
}
} }
// create buffer // create buffer
@ -266,6 +310,8 @@ struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r,
wlr_log_errno(WLR_ERROR, "Allocation failed"); wlr_log_errno(WLR_ERROR, "Allocation failed");
goto error_alloc; goto error_alloc;
} }
buf->buf_size = bsize;
wl_list_insert(&r->stage.buffers, &buf->link);
VkResult res; VkResult res;
VkBufferCreateInfo buf_info = { VkBufferCreateInfo buf_info = {
@ -315,33 +361,41 @@ struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r,
goto error; goto error;
} }
struct wlr_vk_allocation *a = wl_array_add(&buf->allocs, sizeof(*a)); span = malloc(sizeof(*span));
if (a == NULL) { struct wlr_vk_stage_span *free_span = malloc(sizeof(*free_span));
wlr_log_errno(WLR_ERROR, "Allocation failed"); if (span == NULL || free_span == NULL) {
free(span);
free(free_span);
goto error; goto error;
} }
*free_span = (struct wlr_vk_stage_span){
.buffer = buf,
.start = size,
.size = bsize - size,
.free = true,
};
wl_list_init(&free_span->usage_link);
wl_list_insert(&r->stage.spans, &free_span->link);
wlr_log(WLR_DEBUG, "Created new vk staging buffer of size %" PRIu64, bsize); *span = (struct wlr_vk_stage_span){
buf->buf_size = bsize; .buffer = buf,
wl_list_insert(&r->stage.buffers, &buf->link);
*a = (struct wlr_vk_allocation){
.start = 0, .start = 0,
.size = size, .size = size,
.free = false,
}; };
return (struct wlr_vk_buffer_span) { wl_list_init(&span->usage_link);
.buffer = buf, wl_list_insert(&r->stage.spans, &span->link);
.alloc = *a, buf->active = size;
};
wlr_log(WLR_DEBUG, "Created new vk staging buffer of size %" PRIu64, bsize);
return span;
error: error:
shared_buffer_destroy(r, buf); shared_buffer_destroy(r, buf);
error_alloc: error_alloc:
return (struct wlr_vk_buffer_span) { return NULL;
.buffer = NULL,
.alloc = (struct wlr_vk_allocation) {0, 0},
};
} }
VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer) { VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer) {
@ -429,7 +483,7 @@ static bool init_command_buffer(struct wlr_vk_command_buffer *cb,
.vk = vk_cb, .vk = vk_cb,
}; };
wl_list_init(&cb->destroy_textures); wl_list_init(&cb->destroy_textures);
wl_list_init(&cb->stage_buffers); wl_list_init(&cb->stage_spans);
return true; return true;
} }
@ -463,12 +517,11 @@ static void release_command_buffer_resources(struct wlr_vk_command_buffer *cb,
wlr_texture_destroy(&texture->wlr_texture); wlr_texture_destroy(&texture->wlr_texture);
} }
struct wlr_vk_shared_buffer *buf, *buf_tmp; struct wlr_vk_stage_span *span, *span_tmp;
wl_list_for_each_safe(buf, buf_tmp, &cb->stage_buffers, link) { wl_list_for_each_safe(span, span_tmp, &cb->stage_spans, usage_link) {
buf->allocs.size = 0; wl_list_remove(&span->usage_link);
wl_list_init(&span->usage_link);
wl_list_remove(&buf->link); vulkan_return_stage_span(renderer, span);
wl_list_insert(&renderer->stage.buffers, &buf->link);
} }
if (cb->color_transform) { if (cb->color_transform) {
@ -489,6 +542,18 @@ static struct wlr_vk_command_buffer *get_command_buffer(
return NULL; return NULL;
} }
struct wlr_vk_shared_buffer *buf, *buf_tmp;
wl_list_for_each_safe(buf, buf_tmp, &renderer->stage.buffers, link) {
if (buf->active > 0) {
buf->unused_counter = 0;
continue;
}
buf->unused_counter++;
if (buf->unused_counter > max_stage_unused_frames) {
shared_buffer_destroy(renderer, buf);
}
}
// Destroy textures for completed command buffers // Destroy textures for completed command buffers
for (size_t i = 0; i < VULKAN_COMMAND_BUFFERS_CAP; i++) { for (size_t i = 0; i < VULKAN_COMMAND_BUFFERS_CAP; i++) {
struct wlr_vk_command_buffer *cb = &renderer->command_buffers[i]; struct wlr_vk_command_buffer *cb = &renderer->command_buffers[i];
@ -2414,6 +2479,7 @@ struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev
wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl, WLR_BUFFER_CAP_DMABUF); wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl, WLR_BUFFER_CAP_DMABUF);
renderer->wlr_renderer.features.output_color_transform = true; renderer->wlr_renderer.features.output_color_transform = true;
wl_list_init(&renderer->stage.buffers); wl_list_init(&renderer->stage.buffers);
wl_list_init(&renderer->stage.spans);
wl_list_init(&renderer->foreign_textures); wl_list_init(&renderer->foreign_textures);
wl_list_init(&renderer->textures); wl_list_init(&renderer->textures);
wl_list_init(&renderer->descriptor_pools); wl_list_init(&renderer->descriptor_pools);

View file

@ -71,17 +71,17 @@ static bool write_pixels(struct wlr_vk_texture *texture,
} }
// get staging buffer // get staging buffer
struct wlr_vk_buffer_span span = vulkan_get_stage_span(renderer, bsize, format_info->bytes_per_block); struct wlr_vk_stage_span *span = vulkan_get_stage_span(renderer, bsize, format_info->bytes_per_block);
if (!span.buffer || span.alloc.size != bsize) { if (span == NULL) {
wlr_log(WLR_ERROR, "Failed to retrieve staging buffer"); wlr_log(WLR_ERROR, "Failed to retrieve staging buffer");
free(copies); free(copies);
return false; return false;
} }
char *map = (char*)span.buffer->cpu_mapping + span.alloc.start; char *map = (char*)span->buffer->cpu_mapping + span->start;
// upload data // upload data
uint32_t buf_off = span.alloc.start; uint32_t buf_off = span->start;
for (int i = 0; i < rects_len; i++) { for (int i = 0; i < rects_len; i++) {
pixman_box32_t rect = rects[i]; pixman_box32_t rect = rects[i];
uint32_t width = rect.x2 - rect.x1; uint32_t width = rect.x2 - rect.x1;
@ -130,6 +130,7 @@ static bool write_pixels(struct wlr_vk_texture *texture,
// will be executed before next frame // will be executed before next frame
VkCommandBuffer cb = vulkan_record_stage_cb(renderer); VkCommandBuffer cb = vulkan_record_stage_cb(renderer);
if (cb == VK_NULL_HANDLE) { if (cb == VK_NULL_HANDLE) {
vulkan_return_stage_span(renderer, span);
free(copies); free(copies);
return false; return false;
} }
@ -139,7 +140,7 @@ static bool write_pixels(struct wlr_vk_texture *texture,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT); VK_ACCESS_TRANSFER_WRITE_BIT);
vkCmdCopyBufferToImage(cb, span.buffer->buffer, texture->image, vkCmdCopyBufferToImage(cb, span->buffer->buffer, texture->image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (uint32_t)rects_len, copies); VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (uint32_t)rects_len, copies);
vulkan_change_layout(cb, texture->image, vulkan_change_layout(cb, texture->image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT,
@ -147,6 +148,7 @@ static bool write_pixels(struct wlr_vk_texture *texture,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT); VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT);
texture->last_used_cb = renderer->stage.cb; texture->last_used_cb = renderer->stage.cb;
wl_list_insert(&renderer->stage.cb->stage_spans, &span->usage_link);
free(copies); free(copies);