render/vulkan: use stage buffers for read_pixels

No need to roll our own cache machinery here, stage buffers
already do it (and more) for us. Should improve performance
when multiple screens with a different resolution are captured.

Closes: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3525
This commit is contained in:
Simon Ser 2022-11-25 16:41:05 +01:00
parent 3ed69b4946
commit 7328cad84b
2 changed files with 37 additions and 90 deletions

View file

@ -207,14 +207,6 @@ struct wlr_vk_renderer {
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
} stage; } stage;
struct {
bool initialized;
uint32_t drm_format;
uint32_t width, height;
VkImage dst_image;
VkDeviceMemory dst_img_memory;
} read_pixels_cache;
}; };
// Creates a vulkan renderer for the given device. // Creates a vulkan renderer for the given device.

View file

@ -1181,11 +1181,6 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) {
vkDestroySampler(dev->dev, renderer->sampler, NULL); vkDestroySampler(dev->dev, renderer->sampler, NULL);
vkDestroyCommandPool(dev->dev, renderer->command_pool, NULL); vkDestroyCommandPool(dev->dev, renderer->command_pool, NULL);
if (renderer->read_pixels_cache.initialized) {
vkFreeMemory(dev->dev, renderer->read_pixels_cache.dst_img_memory, NULL);
vkDestroyImage(dev->dev, renderer->read_pixels_cache.dst_image, NULL);
}
struct wlr_vk_instance *ini = dev->instance; struct wlr_vk_instance *ini = dev->instance;
vulkan_device_destroy(dev); vulkan_device_destroy(dev);
vulkan_instance_destroy(ini); vulkan_instance_destroy(ini);
@ -1196,6 +1191,7 @@ static bool vulkan_read_pixels(struct wlr_renderer *wlr_renderer,
uint32_t drm_format, uint32_t stride, uint32_t drm_format, uint32_t stride,
uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y, uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y,
uint32_t dst_x, uint32_t dst_y, void *data) { uint32_t dst_x, uint32_t dst_y, void *data) {
VkResult res;
struct wlr_vk_renderer *vk_renderer = vulkan_get_renderer(wlr_renderer); struct wlr_vk_renderer *vk_renderer = vulkan_get_renderer(wlr_renderer);
VkDevice dev = vk_renderer->dev->dev; VkDevice dev = vk_renderer->dev->dev;
VkImage src_image = vk_renderer->current_render_buffer->image; VkImage src_image = vk_renderer->current_render_buffer->image;
@ -1227,78 +1223,39 @@ static bool vulkan_read_pixels(struct wlr_renderer *wlr_renderer,
return false; return false;
} }
VkResult res; VkImageCreateInfo image_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = dst_format,
.extent.width = width,
.extent.height = height,
.extent.depth = 1,
.arrayLayers = 1,
.mipLevels = 1,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT
};
VkImage dst_image; VkImage dst_image;
VkDeviceMemory dst_img_memory; res = vkCreateImage(dev, &image_create_info, NULL, &dst_image);
bool use_cached = vk_renderer->read_pixels_cache.initialized && if (res != VK_SUCCESS) {
vk_renderer->read_pixels_cache.drm_format == drm_format && wlr_vk_error("vkCreateImage", res);
vk_renderer->read_pixels_cache.width == width && return false;
vk_renderer->read_pixels_cache.height == height; }
if (use_cached) { VkMemoryRequirements mem_reqs;
dst_image = vk_renderer->read_pixels_cache.dst_image; vkGetImageMemoryRequirements(dev, dst_image, &mem_reqs);
dst_img_memory = vk_renderer->read_pixels_cache.dst_img_memory;
} else {
VkImageCreateInfo image_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = dst_format,
.extent.width = width,
.extent.height = height,
.extent.depth = 1,
.arrayLayers = 1,
.mipLevels = 1,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT
};
res = vkCreateImage(dev, &image_create_info, NULL, &dst_image);
if (res != VK_SUCCESS) {
wlr_vk_error("vkCreateImage", res);
return false;
}
VkMemoryRequirements mem_reqs; struct wlr_vk_buffer_span buffer_span = vulkan_get_stage_span(vk_renderer, mem_reqs.size);
vkGetImageMemoryRequirements(dev, dst_image, &mem_reqs); if (buffer_span.buffer == NULL) {
goto error;
}
int mem_type = vulkan_find_mem_type(vk_renderer->dev, res = vkBindImageMemory(dev, dst_image, buffer_span.buffer->memory, buffer_span.alloc.start);
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | if (res != VK_SUCCESS) {
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | wlr_vk_error("vkBindImageMemory", res);
VK_MEMORY_PROPERTY_HOST_CACHED_BIT, goto error;
mem_reqs.memoryTypeBits);
if (mem_type < 0) {
wlr_log(WLR_ERROR, "vulkan_read_pixels: could not find adequate memory type");
goto destroy_image;
}
VkMemoryAllocateInfo mem_alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
};
mem_alloc_info.allocationSize = mem_reqs.size;
mem_alloc_info.memoryTypeIndex = mem_type;
res = vkAllocateMemory(dev, &mem_alloc_info, NULL, &dst_img_memory);
if (res != VK_SUCCESS) {
wlr_vk_error("vkAllocateMemory", res);
goto destroy_image;
}
res = vkBindImageMemory(dev, dst_image, dst_img_memory, 0);
if (res != VK_SUCCESS) {
wlr_vk_error("vkBindImageMemory", res);
goto free_memory;
}
if (vk_renderer->read_pixels_cache.initialized) {
vkFreeMemory(dev, vk_renderer->read_pixels_cache.dst_img_memory, NULL);
vkDestroyImage(dev, vk_renderer->read_pixels_cache.dst_image, NULL);
}
vk_renderer->read_pixels_cache.initialized = true;
vk_renderer->read_pixels_cache.drm_format = drm_format;
vk_renderer->read_pixels_cache.dst_image = dst_image;
vk_renderer->read_pixels_cache.dst_img_memory = dst_img_memory;
vk_renderer->read_pixels_cache.width = width;
vk_renderer->read_pixels_cache.height = height;
} }
VkCommandBuffer cb = vulkan_record_stage_cb(vk_renderer); VkCommandBuffer cb = vulkan_record_stage_cb(vk_renderer);
@ -1376,7 +1333,7 @@ static bool vulkan_read_pixels(struct wlr_renderer *wlr_renderer,
VK_ACCESS_MEMORY_READ_BIT); VK_ACCESS_MEMORY_READ_BIT);
if (!vulkan_submit_stage_wait(vk_renderer)) { if (!vulkan_submit_stage_wait(vk_renderer)) {
return false; goto error;
} }
VkImageSubresource img_sub_res = { VkImageSubresource img_sub_res = {
@ -1388,10 +1345,10 @@ static bool vulkan_read_pixels(struct wlr_renderer *wlr_renderer,
vkGetImageSubresourceLayout(dev, dst_image, &img_sub_res, &img_sub_layout); vkGetImageSubresourceLayout(dev, dst_image, &img_sub_res, &img_sub_layout);
void *v; void *v;
res = vkMapMemory(dev, dst_img_memory, 0, VK_WHOLE_SIZE, 0, &v); res = vkMapMemory(dev, buffer_span.buffer->memory, 0, VK_WHOLE_SIZE, 0, &v);
if (res != VK_SUCCESS) { if (res != VK_SUCCESS) {
wlr_vk_error("vkMapMemory", res); wlr_vk_error("vkMapMemory", res);
return false; goto error;
} }
const char *d = (const char *)v + img_sub_layout.offset; const char *d = (const char *)v + img_sub_layout.offset;
@ -1406,14 +1363,12 @@ static bool vulkan_read_pixels(struct wlr_renderer *wlr_renderer,
} }
} }
vkUnmapMemory(dev, dst_img_memory); vkUnmapMemory(dev, buffer_span.buffer->memory);
// Don't need to free anything else, since memory and image are cached
return true;
free_memory:
vkFreeMemory(dev, dst_img_memory, NULL);
destroy_image:
vkDestroyImage(dev, dst_image, NULL); vkDestroyImage(dev, dst_image, NULL);
return true;
error:
vkDestroyImage(dev, dst_image, NULL);
return false; return false;
} }