diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 2434b00d5..3afa3104a 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -1,6 +1,7 @@ #ifndef RENDER_VULKAN_H #define RENDER_VULKAN_H +#include #include #include #include @@ -270,6 +271,12 @@ struct wlr_vk_renderer { // Pool of command buffers struct wlr_vk_command_buffer command_buffers[VULKAN_COMMAND_BUFFERS_CAP]; + struct { + pthread_t thread; + int worker_fd, control_fd; + struct wl_event_source *event_source; + } upload; + struct { struct wlr_vk_command_buffer *cb; uint64_t last_timeline_point; @@ -301,6 +308,17 @@ struct wlr_vk_texture_view { struct wlr_vk_descriptor_pool *ds_pool; }; +struct wlr_vk_upload_task { + struct wlr_buffer *buffer; + VkDeviceMemory memory; + uint64_t timeline_point; + char *dst; + const char *src; + uint32_t src_stride, dst_size; + pixman_region32_t region; + const struct wlr_pixel_format_info *format_info; +}; + struct wlr_vk_pipeline *setup_get_or_create_pipeline( struct wlr_vk_render_format_setup *setup, const struct wlr_vk_pipeline_key *key); @@ -312,7 +330,8 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view( const struct wlr_vk_pipeline_layout *layout); // Creates a vulkan renderer for the given device. -struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev); +struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev, + struct wl_event_loop *loop); // stage utility - for uploading/retrieving data // Gets an command buffer in recording state which is guaranteed to be @@ -382,6 +401,9 @@ bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y, void *data); +bool vulkan_init_upload_worker(struct wlr_vk_renderer *renderer, + struct wl_event_loop *loop); + // State (e.g. image texture) associated with a surface. struct wlr_vk_texture { struct wlr_texture wlr_texture; diff --git a/include/wlr/render/vulkan.h b/include/wlr/render/vulkan.h index 50f8c558d..3c4eac77b 100644 --- a/include/wlr/render/vulkan.h +++ b/include/wlr/render/vulkan.h @@ -18,7 +18,8 @@ struct wlr_vk_image_attribs { VkFormat format; }; -struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd(int drm_fd); +struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd( + struct wl_event_loop *loop, int drm_fd); VkInstance wlr_vk_renderer_get_instance(struct wlr_renderer *renderer); VkPhysicalDevice wlr_vk_renderer_get_physical_device(struct wlr_renderer *renderer); diff --git a/render/vulkan/meson.build b/render/vulkan/meson.build index 36a822f9a..d4c81367f 100644 --- a/render/vulkan/meson.build +++ b/render/vulkan/meson.build @@ -46,6 +46,7 @@ wlr_files += files( ) wlr_deps += dep_vulkan +wlr_deps += dependency('threads') features += { 'vulkan-renderer': true } subdir('shaders') diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 0b7f23e70..e4646e7f6 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -369,6 +369,7 @@ bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer) { return false; } + // TODO VkTimelineSemaphoreSubmitInfoKHR timeline_submit_info = { .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR, .signalSemaphoreValueCount = 1, @@ -2153,7 +2154,8 @@ error: return NULL; } -struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev) { +struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev, + struct wl_event_loop *loop) { struct wlr_vk_renderer *renderer; VkResult res; if (!(renderer = calloc(1, sizeof(*renderer)))) { @@ -2210,6 +2212,10 @@ struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev goto error; } + if (!vulkan_init_upload_worker(renderer, loop)) { + goto error; + } + return &renderer->wlr_renderer; error: @@ -2217,7 +2223,8 @@ error: return NULL; } -struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd(int drm_fd) { +struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd(struct wl_event_loop *loop, + int drm_fd) { wlr_log(WLR_INFO, "The vulkan renderer is only experimental and " "not expected to be ready for daily use"); wlr_log(WLR_INFO, "Run with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation " @@ -2252,7 +2259,7 @@ struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd(int drm_fd) { return NULL; } - return vulkan_renderer_create_for_device(dev); + return vulkan_renderer_create_for_device(dev, loop); } VkInstance wlr_vk_renderer_get_instance(struct wlr_renderer *renderer) { diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index b26397989..8b984e58a 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -2,9 +2,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -70,13 +72,150 @@ static void copy_pixels(char *vmap, const char *vdata, uint32_t tex_width, assert((uint32_t)(map - vmap) == size); } +static bool read_upload_task(struct wlr_vk_upload_task *task, int fd) { + while (true) { + errno = 0; + ssize_t n = read(fd, task, sizeof(*task)); + if (errno == EINTR) { + continue; + } + if (n == sizeof(*task)) { + return true; + } else if (n < 0) { + wlr_log_errno(WLR_ERROR, "read() failed"); + } else if (n > 0) { + wlr_log(WLR_ERROR, "Unexpected partial read"); + } + return false; + } +} + +static bool write_upload_task(const struct wlr_vk_upload_task *task, int fd) { + while (true) { + errno = 0; + ssize_t n = write(fd, task, sizeof(*task)); + if (errno == EINTR) { + continue; + } + if (n == sizeof(*task)) { + return true; + } else if (n < 0) { + wlr_log_errno(WLR_ERROR, "write() failed"); + } else if (n > 0) { + wlr_log(WLR_ERROR, "Unexpected partial write"); + } + return false; + } +} + +static void process_upload_task(struct wlr_vk_renderer *renderer, + struct wlr_vk_upload_task *task) { + copy_pixels(task->dst, task->src, task->buffer->width, task->src_stride, + task->dst_size, &task->region, task->format_info); + + VkSemaphoreSignalInfoKHR signal_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO_KHR, + .semaphore = renderer->upload_timeline_semaphore, + .value = task->timeline_point, + }; + VkResult res = renderer->dev->api.vkSignalSemaphoreKHR(renderer->dev->dev, &signal_info); + if (res != VK_SUCCESS) { + wlr_vk_error("vkMapMemory", res); + } +} + +static void *run_uploads(void *data) { + struct wlr_vk_renderer *renderer = data; + + while (true) { + struct wlr_vk_upload_task task = {0}; + if (!read_upload_task(&task, renderer->upload.worker_fd)) { + break; + } + process_upload_task(renderer, &task); + if (!write_upload_task(&task, renderer->upload.worker_fd)) { + break; + } + } + + close(renderer->upload.worker_fd); + return NULL; +} + +static void handle_upload_task_complete(struct wlr_vk_renderer *renderer, + struct wlr_vk_upload_task *task) { + wlr_buffer_end_data_ptr_access(task->buffer); + wlr_buffer_unlock(task->buffer); + pixman_region32_fini(&task->region); +} + +static int handle_upload_fd_event(int fd, uint32_t mask, void *data) { + struct wlr_vk_renderer *renderer = data; + + if (mask & WL_EVENT_ERROR) { + wlr_log(WLR_ERROR, "Upload worker FD error"); + return 0; + } + if (mask & WL_EVENT_HANGUP) { + return 0; + } + + if (mask & WL_EVENT_READABLE) { + struct wlr_vk_upload_task task = {0}; + if (!read_upload_task(&task, fd)) { + return 0; + } + handle_upload_task_complete(renderer, &task); + } + + return 0; +} + +bool vulkan_init_upload_worker(struct wlr_vk_renderer *renderer, + struct wl_event_loop *loop) { + int sockets[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) { + wlr_log_errno(WLR_ERROR, "pipe() failed"); + return false; + } + renderer->upload.worker_fd = sockets[0]; + renderer->upload.control_fd = sockets[1]; + + renderer->upload.event_source = wl_event_loop_add_fd(loop, + renderer->upload.control_fd, WL_EVENT_READABLE, + handle_upload_fd_event, renderer); + if (renderer->upload.event_source == NULL) { + wlr_log(WLR_ERROR, "wl_event_loop_add_fd() failed"); + goto error_fds; + } + + // Block all signals in the new thread: let the main thread handle these + sigset_t saved_sigset, new_sigset; + sigfillset(&new_sigset); + pthread_sigmask(SIG_BLOCK, &new_sigset, &saved_sigset); + int ret = pthread_create(&renderer->upload.thread, NULL, run_uploads, renderer); + pthread_sigmask(SIG_SETMASK, &saved_sigset, NULL); + if (ret != 0) { + wlr_log_errno(WLR_ERROR, "pthread_create() failed"); + goto error_event_source; + } + + return true; + +error_event_source: + wl_event_source_remove(renderer->upload.event_source); +error_fds: + close(renderer->upload.worker_fd); + close(renderer->upload.control_fd); + return false; +} + // Will transition the texture to shaderReadOnlyOptimal layout for reading // from fragment shader later on -static bool write_pixels(struct wlr_vk_texture *texture, +static bool start_upload(struct wlr_vk_texture *texture, struct wlr_buffer *buffer, uint32_t stride, const pixman_region32_t *region, const void *vdata, VkImageLayout old_layout, VkPipelineStageFlags src_stage, VkAccessFlags src_access) { - VkResult res; struct wlr_vk_renderer *renderer = texture->renderer; const struct wlr_pixel_format_info *format_info = drm_get_pixel_format_info(texture->format->drm); @@ -144,19 +283,20 @@ static bool write_pixels(struct wlr_vk_texture *texture, buf_off += height * packed_stride; } - char *dst = (char *)span.buffer->map + span.alloc.start; - copy_pixels(dst, vdata, texture->wlr_texture.width, - stride, bsize, region, format_info); - - VkSemaphoreSignalInfoKHR signal_info = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO_KHR, - .semaphore = renderer->upload_timeline_semaphore, - .value = timeline_point, + struct wlr_vk_upload_task task = { + .buffer = wlr_buffer_lock(buffer), + .memory = span.buffer->memory, + .timeline_point = timeline_point, + .dst = (char *)span.buffer->map + span.alloc.start, + .src = vdata, + .src_stride = stride, + .dst_size = bsize, + .format_info = format_info, }; - res = renderer->dev->api.vkSignalSemaphoreKHR(renderer->dev->dev, &signal_info); - if (res != VK_SUCCESS) { + pixman_region32_init(&task.region); + pixman_region32_copy(&task.region, region); + if (!write_upload_task(&task, renderer->upload.control_fd)) { free(copies); - wlr_vk_error("vkMapMemory", res); return false; } @@ -199,19 +339,21 @@ static bool vulkan_texture_update_from_buffer(struct wlr_texture *wlr_texture, return false; } - bool ok = true; - if (format != texture->format->drm) { - ok = false; - goto out; + goto error; } - ok = write_pixels(texture, stride, damage, data, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT); + if (!start_upload(texture, buffer, stride, damage, data, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT)) { + goto error; + } -out: + return true; + +error: wlr_buffer_end_data_ptr_access(buffer); - return ok; + return false; } void vulkan_texture_destroy(struct wlr_vk_texture *texture) { @@ -407,7 +549,8 @@ static void texture_set_format(struct wlr_vk_texture *texture, } static struct wlr_texture *vulkan_texture_from_pixels( - struct wlr_vk_renderer *renderer, uint32_t drm_fmt, uint32_t stride, + struct wlr_vk_renderer *renderer, struct wlr_buffer *buffer, + uint32_t drm_fmt, uint32_t stride, uint32_t width, uint32_t height, const void *data) { VkResult res; VkDevice dev = renderer->dev->dev; @@ -480,7 +623,8 @@ static struct wlr_texture *vulkan_texture_from_pixels( pixman_region32_t region; pixman_region32_init_rect(®ion, 0, 0, width, height); - if (!write_pixels(texture, stride, ®ion, data, VK_IMAGE_LAYOUT_UNDEFINED, + if (!start_upload(texture, buffer, stride, ®ion, data, + VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0)) { goto error; } @@ -816,8 +960,10 @@ struct wlr_texture *vulkan_texture_from_buffer(struct wlr_renderer *wlr_renderer } else if (wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { struct wlr_texture *tex = vulkan_texture_from_pixels(renderer, - format, stride, buffer->width, buffer->height, data); - wlr_buffer_end_data_ptr_access(buffer); + buffer, format, stride, buffer->width, buffer->height, data); + if (tex == NULL) { + wlr_buffer_end_data_ptr_access(buffer); + } return tex; } else { return NULL; diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c index 89f81abd8..aad0ec760 100644 --- a/render/wlr_renderer.c +++ b/render/wlr_renderer.c @@ -267,7 +267,7 @@ static struct wlr_renderer *renderer_autocreate(struct wlr_backend *backend, int log_creation_failure(is_auto, "Cannot create Vulkan renderer: no DRM FD available"); } else { #if WLR_HAS_VULKAN_RENDERER - renderer = wlr_vk_renderer_create_with_drm_fd(drm_fd); + renderer = wlr_vk_renderer_create_with_drm_fd(loop, drm_fd); #else wlr_log(WLR_ERROR, "Cannot create Vulkan renderer: disabled at compile-time"); #endif