diff --git a/.builds/alpine.yml b/.builds/alpine.yml index affd85411..f8d75d8b1 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -35,6 +35,9 @@ tasks: cd wlroots ninja -C build sudo ninja -C build install + - test: | + cd wlroots + meson test -C build --verbose - build-features-disabled: | cd wlroots meson setup build --reconfigure -Dauto_features=disabled diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index fae04ab31..84e04eda7 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -37,6 +37,10 @@ tasks: - clang: | cd wlroots/build-clang ninja + - test: | + cd wlroots/build-gcc + meson test --verbose + meson test --benchmark --verbose - smoke-test: | cd wlroots/build-gcc/tinywl sudo modprobe vkms diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index a99e9c911..3bfe37489 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -32,6 +32,9 @@ tasks: meson setup build --fatal-meson-warnings -Dauto_features=enabled -Dallocators=gbm ninja -C build sudo ninja -C build install + - test: | + cd wlroots + meson test -C build --verbose - tinywl: | cd wlroots/tinywl make diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index faa535d44..daa8ba9bf 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -475,6 +476,62 @@ static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm, atomic_add(atom, id, props->crtc_h, dst_box->height); } +static void set_color_encoding_and_range(struct atomic *atom, + struct wlr_drm_backend *drm, struct wlr_drm_plane *plane, + enum wlr_color_encoding encoding, enum wlr_color_range range) { + uint32_t id = plane->id; + const struct wlr_drm_plane_props *props = &plane->props; + + uint32_t color_encoding; + switch (encoding) { + case WLR_COLOR_ENCODING_NONE: + case WLR_COLOR_ENCODING_BT601: + color_encoding = WLR_DRM_COLOR_YCBCR_BT601; + break; + case WLR_COLOR_ENCODING_BT709: + color_encoding = WLR_DRM_COLOR_YCBCR_BT709; + break; + case WLR_COLOR_ENCODING_BT2020: + color_encoding = WLR_DRM_COLOR_YCBCR_BT2020; + break; + default: + wlr_log(WLR_DEBUG, "Unsupported color encoding %d", encoding); + atom->failed = true; + return; + } + + if (props->color_encoding) { + atomic_add(atom, id, props->color_encoding, color_encoding); + } else { + wlr_log(WLR_DEBUG, "Plane %"PRIu32" is missing the COLOR_ENCODING property", + id); + atom->failed = true; + return; + } + + uint32_t color_range; + switch (range) { + case WLR_COLOR_RANGE_NONE: + case WLR_COLOR_RANGE_LIMITED: + color_range = WLR_DRM_COLOR_YCBCR_LIMITED_RANGE; + break; + case WLR_COLOR_RANGE_FULL: + color_range = WLR_DRM_COLOR_YCBCR_FULL_RANGE; + break; + default: + assert(0); // Unreachable + } + + if (props->color_range) { + atomic_add(atom, id, props->color_range, color_range); + } else { + wlr_log(WLR_DEBUG, "Plane %"PRIu32" is missing the COLOR_RANGE property", + id); + atom->failed = true; + return; + } +} + static bool supports_cursor_hotspots(const struct wlr_drm_plane *plane) { return plane->props.hotspot_x && plane->props.hotspot_y; } @@ -528,6 +585,10 @@ static void atomic_connector_add(struct atomic *atom, set_plane_props(atom, drm, crtc->primary, state->primary_fb, crtc->id, &state->primary_viewport.dst_box, &state->primary_viewport.src_box); + if (state->base->committed & WLR_OUTPUT_STATE_COLOR_REPRESENTATION) { + set_color_encoding_and_range(atom, drm, crtc->primary, + state->base->color_encoding, state->base->color_range); + } if (crtc->primary->props.fb_damage_clips != 0) { atomic_add(atom, crtc->primary->id, crtc->primary->props.fb_damage_clips, state->fb_damage_clips); diff --git a/backend/drm/backend.c b/backend/drm/backend.c index 5a545b192..c2806152b 100644 --- a/backend/drm/backend.c +++ b/backend/drm/backend.c @@ -95,7 +95,7 @@ static const struct wlr_backend_impl backend_impl = { .commit = backend_commit, }; -bool wlr_backend_is_drm(struct wlr_backend *b) { +bool wlr_backend_is_drm(const struct wlr_backend *b) { return b->impl == &backend_impl; } diff --git a/backend/drm/drm.c b/backend/drm/drm.c index bd29872fb..d2f75f71f 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -43,7 +43,8 @@ static const uint32_t COMMIT_OUTPUT_STATE = WLR_OUTPUT_STATE_WAIT_TIMELINE | WLR_OUTPUT_STATE_SIGNAL_TIMELINE | WLR_OUTPUT_STATE_COLOR_TRANSFORM | - WLR_OUTPUT_STATE_IMAGE_DESCRIPTION; + WLR_OUTPUT_STATE_IMAGE_DESCRIPTION | + WLR_OUTPUT_STATE_COLOR_REPRESENTATION; static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BACKEND_OPTIONAL | COMMIT_OUTPUT_STATE; @@ -1320,7 +1321,7 @@ static const struct wlr_output_impl output_impl = { .get_primary_formats = drm_connector_get_primary_formats, }; -bool wlr_output_is_drm(struct wlr_output *output) { +bool wlr_output_is_drm(const struct wlr_output *output) { return output->impl == &output_impl; } diff --git a/backend/drm/fb.c b/backend/drm/fb.c index 575f32d91..da736d20e 100644 --- a/backend/drm/fb.c +++ b/backend/drm/fb.c @@ -144,7 +144,13 @@ static struct wlr_drm_fb *drm_fb_create(struct wlr_drm_backend *drm, struct wlr_buffer *buf, const struct wlr_drm_format_set *formats) { struct wlr_dmabuf_attributes attribs; if (!wlr_buffer_get_dmabuf(buf, &attribs)) { - wlr_log(WLR_DEBUG, "Failed to get DMA-BUF from buffer"); + struct wlr_shm_attributes shm; + if (wlr_buffer_get_shm(buf, &shm)) { + wlr_log(WLR_DEBUG, "Failed to get DMA-BUF from shm buffer"); + } else { + wlr_log(WLR_DEBUG, "Failed to get DMA-BUF from buffer"); + } + return NULL; } diff --git a/backend/drm/properties.c b/backend/drm/properties.c index 314023954..4c6bdcb36 100644 --- a/backend/drm/properties.c +++ b/backend/drm/properties.c @@ -50,6 +50,8 @@ static const struct prop_info crtc_info[] = { static const struct prop_info plane_info[] = { #define INDEX(name) (offsetof(struct wlr_drm_plane_props, name) / sizeof(uint32_t)) + { "COLOR_ENCODING", INDEX(color_encoding) }, + { "COLOR_RANGE", INDEX(color_range) }, { "CRTC_H", INDEX(crtc_h) }, { "CRTC_ID", INDEX(crtc_id) }, { "CRTC_W", INDEX(crtc_w) }, diff --git a/backend/drm/util.c b/backend/drm/util.c index 1e0307dcb..57e68f33f 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -135,13 +135,13 @@ static bool is_taken(size_t n, const uint32_t arr[static n], uint32_t key) { */ struct match_state { const size_t num_conns; - const uint32_t *restrict conns; + const uint32_t *conns; const size_t num_crtcs; size_t score; size_t replaced; - uint32_t *restrict res; - uint32_t *restrict best; - const uint32_t *restrict orig; + uint32_t *res; + uint32_t *best; + const uint32_t *orig; bool exit_early; }; @@ -236,9 +236,9 @@ static bool match_connectors_with_crtcs_(struct match_state *st, } void match_connectors_with_crtcs(size_t num_conns, - const uint32_t conns[static restrict num_conns], - size_t num_crtcs, const uint32_t prev_crtcs[static restrict num_crtcs], - uint32_t new_crtcs[static restrict num_crtcs]) { + const uint32_t conns[static num_conns], + size_t num_crtcs, const uint32_t prev_crtcs[static num_crtcs], + uint32_t new_crtcs[static num_crtcs]) { uint32_t solution[num_crtcs]; for (size_t i = 0; i < num_crtcs; ++i) { solution[i] = UNMATCHED; diff --git a/backend/headless/backend.c b/backend/headless/backend.c index d03f520b8..0f21b53f4 100644 --- a/backend/headless/backend.c +++ b/backend/headless/backend.c @@ -81,6 +81,6 @@ struct wlr_backend *wlr_headless_backend_create(struct wl_event_loop *loop) { return &backend->backend; } -bool wlr_backend_is_headless(struct wlr_backend *backend) { +bool wlr_backend_is_headless(const struct wlr_backend *backend) { return backend->impl == &backend_impl; } diff --git a/backend/headless/output.c b/backend/headless/output.c index a4cdb17c3..0464e0d1b 100644 --- a/backend/headless/output.c +++ b/backend/headless/output.c @@ -106,7 +106,7 @@ static const struct wlr_output_impl output_impl = { .move_cursor = output_move_cursor, }; -bool wlr_output_is_headless(struct wlr_output *wlr_output) { +bool wlr_output_is_headless(const struct wlr_output *wlr_output) { return wlr_output->impl == &output_impl; } diff --git a/backend/libinput/backend.c b/backend/libinput/backend.c index 121731b31..909292864 100644 --- a/backend/libinput/backend.c +++ b/backend/libinput/backend.c @@ -15,6 +15,12 @@ static struct wlr_libinput_backend *get_libinput_backend_from_backend( return backend; } +struct libinput *wlr_backend_get_libinput(struct wlr_backend *wlr_backend) { + struct wlr_libinput_backend *backend = + get_libinput_backend_from_backend(wlr_backend); + return backend->libinput_context; +} + static int libinput_open_restricted(const char *path, int flags, void *_backend) { struct wlr_libinput_backend *backend = _backend; @@ -87,13 +93,6 @@ static bool backend_start(struct wlr_backend *wlr_backend) { get_libinput_backend_from_backend(wlr_backend); wlr_log(WLR_DEBUG, "Starting libinput backend"); - backend->libinput_context = libinput_udev_create_context(&libinput_impl, - backend, backend->session->udev); - if (!backend->libinput_context) { - wlr_log(WLR_ERROR, "Failed to create libinput context"); - return false; - } - if (libinput_udev_assign_seat(backend->libinput_context, backend->session->seat) != 0) { wlr_log(WLR_ERROR, "Failed to assign libinput seat"); @@ -155,7 +154,7 @@ static const struct wlr_backend_impl backend_impl = { .destroy = backend_destroy, }; -bool wlr_backend_is_libinput(struct wlr_backend *b) { +bool wlr_backend_is_libinput(const struct wlr_backend *b) { return b->impl == &backend_impl; } @@ -187,12 +186,20 @@ struct wlr_backend *wlr_libinput_backend_create(struct wlr_session *session) { wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); return NULL; } + wlr_backend_init(&backend->backend, &backend_impl); - wl_list_init(&backend->devices); - backend->session = session; + backend->libinput_context = libinput_udev_create_context(&libinput_impl, + backend, backend->session->udev); + if (!backend->libinput_context) { + wlr_log(WLR_ERROR, "Failed to create libinput context"); + wlr_backend_finish(&backend->backend); + free(backend); + return NULL; + } + backend->session_signal.notify = session_signal; wl_signal_add(&session->events.active, &backend->session_signal); diff --git a/backend/multi/backend.c b/backend/multi/backend.c index 3d8fb96f5..3a9aca7e6 100644 --- a/backend/multi/backend.c +++ b/backend/multi/backend.c @@ -173,7 +173,7 @@ struct wlr_backend *wlr_multi_backend_create(struct wl_event_loop *loop) { return &backend->backend; } -bool wlr_backend_is_multi(struct wlr_backend *b) { +bool wlr_backend_is_multi(const struct wlr_backend *b) { return b->impl == &backend_impl; } diff --git a/backend/session/session.c b/backend/session/session.c index 868774399..1e587f793 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -395,8 +395,8 @@ bool wlr_session_change_vt(struct wlr_session *session, unsigned vt) { /* Tests if 'path' is KMS compatible by trying to open it. Returns the opened * device on success. */ -struct wlr_device *session_open_if_kms(struct wlr_session *restrict session, - const char *restrict path) { +struct wlr_device *session_open_if_kms(struct wlr_session *session, + const char *path) { if (!path) { return NULL; } diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index 14a783b67..0d294b543 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -577,7 +577,7 @@ static const struct wlr_backend_impl backend_impl = { .get_drm_fd = backend_get_drm_fd, }; -bool wlr_backend_is_wl(struct wlr_backend *b) { +bool wlr_backend_is_wl(const struct wlr_backend *b) { return b->impl == &backend_impl; } diff --git a/backend/wayland/output.c b/backend/wayland/output.c index fb4d1f914..c3442f411 100644 --- a/backend/wayland/output.c +++ b/backend/wayland/output.c @@ -1027,7 +1027,7 @@ static const struct wlr_output_impl output_impl = { .get_primary_formats = output_get_formats, }; -bool wlr_output_is_wl(struct wlr_output *wlr_output) { +bool wlr_output_is_wl(const struct wlr_output *wlr_output) { return wlr_output->impl == &output_impl; } diff --git a/backend/x11/backend.c b/backend/x11/backend.c index 4dc4bf16d..ab56e758e 100644 --- a/backend/x11/backend.c +++ b/backend/x11/backend.c @@ -219,7 +219,7 @@ static const struct wlr_backend_impl backend_impl = { .get_drm_fd = backend_get_drm_fd, }; -bool wlr_backend_is_x11(struct wlr_backend *backend) { +bool wlr_backend_is_x11(const struct wlr_backend *backend) { return backend->impl == &backend_impl; } @@ -575,8 +575,7 @@ struct wlr_backend *wlr_x11_backend_create(struct wl_event_loop *loop, } int fd = xcb_get_file_descriptor(x11->xcb); - uint32_t events = WL_EVENT_READABLE | WL_EVENT_ERROR | WL_EVENT_HANGUP; - x11->event_source = wl_event_loop_add_fd(loop, fd, events, x11_event, x11); + x11->event_source = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, x11_event, x11); if (!x11->event_source) { wlr_log(WLR_ERROR, "Could not create event source"); goto error_display; diff --git a/backend/x11/output.c b/backend/x11/output.c index 51a9c1441..40e601f0b 100644 --- a/backend/x11/output.c +++ b/backend/x11/output.c @@ -711,7 +711,7 @@ void handle_x11_configure_notify(struct wlr_x11_output *output, wlr_output_state_finish(&state); } -bool wlr_output_is_x11(struct wlr_output *wlr_output) { +bool wlr_output_is_x11(const struct wlr_output *wlr_output) { return wlr_output->impl == &output_impl; } diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h index c02d655ba..20e0a5bff 100644 --- a/include/backend/drm/properties.h +++ b/include/backend/drm/properties.h @@ -65,6 +65,22 @@ struct wlr_drm_plane_props { uint32_t hotspot_x; uint32_t hotspot_y; uint32_t in_fence_fd; + + uint32_t color_encoding; // Not guaranteed to exist + uint32_t color_range; // Not guaranteed to exist +}; + +// Equivalent to wlr_drm_color_encoding defined in the kernel (but not exported) +enum wlr_drm_color_encoding { + WLR_DRM_COLOR_YCBCR_BT601, + WLR_DRM_COLOR_YCBCR_BT709, + WLR_DRM_COLOR_YCBCR_BT2020, +}; + +// Equivalent to wlr_drm_color_range defined in the kernel (but not exported) +enum wlr_drm_color_range { + WLR_DRM_COLOR_YCBCR_FULL_RANGE, + WLR_DRM_COLOR_YCBCR_LIMITED_RANGE, }; bool get_drm_connector_props(int fd, uint32_t id, diff --git a/include/backend/drm/util.h b/include/backend/drm/util.h index 9ba5f435e..a7d36638d 100644 --- a/include/backend/drm/util.h +++ b/include/backend/drm/util.h @@ -34,8 +34,8 @@ void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, * new_crtcs is populated with the new connector indices. */ void match_connectors_with_crtcs(size_t num_conns, - const uint32_t conns[static restrict num_conns], - size_t num_crtcs, const uint32_t prev_crtcs[static restrict num_crtcs], - uint32_t new_crtcs[static restrict num_crtcs]); + const uint32_t conns[static num_conns], + size_t num_crtcs, const uint32_t prev_crtcs[static num_crtcs], + uint32_t new_crtcs[static num_crtcs]); #endif diff --git a/include/backend/session/session.h b/include/backend/session/session.h index 0275f69fa..f3ff9f5bd 100644 --- a/include/backend/session/session.h +++ b/include/backend/session/session.h @@ -14,7 +14,7 @@ bool libseat_change_vt(struct wlr_session *base, unsigned vt); void session_init(struct wlr_session *session); -struct wlr_device *session_open_if_kms(struct wlr_session *restrict session, - const char *restrict path); +struct wlr_device *session_open_if_kms(struct wlr_session *session, + const char *path); #endif diff --git a/include/render/drm_syncobj_merger.h b/include/render/drm_syncobj_merger.h index c55c87314..397c5ce80 100644 --- a/include/render/drm_syncobj_merger.h +++ b/include/render/drm_syncobj_merger.h @@ -3,6 +3,8 @@ #include +struct wlr_buffer; + /** * Accumulate timeline points, to have a destination timeline point be * signalled when all inputs are @@ -41,4 +43,22 @@ bool wlr_drm_syncobj_merger_add(struct wlr_drm_syncobj_merger *merger, struct wlr_drm_syncobj_timeline *dst_timeline, uint64_t dst_point, struct wl_event_loop *loop); +/** + * Add a new sync file to wait for. + * + * Ownership of fd is transferred to the merger. + */ +bool wlr_drm_syncobj_merger_add_sync_file(struct wlr_drm_syncobj_merger *merger, + int fd); + +/** + * Add a new DMA-BUF release to wait for. + * + * Waits for write access. + * If the platform does not support DMA-BUF<->sync file interop, the supplied + * event_loop is used to schedule a wait. + */ +bool wlr_drm_syncobj_merger_add_dmabuf(struct wlr_drm_syncobj_merger *merger, + struct wlr_buffer *buffer, struct wl_event_loop *event_loop); + #endif diff --git a/include/render/egl.h b/include/render/egl.h index 103ab57df..208362858 100644 --- a/include/render/egl.h +++ b/include/render/egl.h @@ -16,6 +16,7 @@ struct wlr_egl { bool EXT_image_dma_buf_import_modifiers; bool IMG_context_priority; bool EXT_create_context_robustness; + bool KHR_context_flush_control; // Device extensions bool EXT_device_drm; diff --git a/include/render/gles2.h b/include/render/gles2.h index 6b852dcb7..226592b5b 100644 --- a/include/render/gles2.h +++ b/include/render/gles2.h @@ -119,8 +119,8 @@ struct wlr_gles2_texture { GLenum target; - // If this texture is imported from a buffer, the texture is does not own - // these states. These cannot be destroyed along with the texture in this + // If this texture is imported from a buffer, the texture does not own + // these states. They cannot be destroyed along with the texture in this // case. GLuint tex; GLuint fbo; diff --git a/include/render/vulkan.h b/include/render/vulkan.h index f96619f2c..c00b1122b 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -56,6 +56,10 @@ struct wlr_vk_device { PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR; PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR; PFN_vkQueueSubmit2KHR vkQueueSubmit2KHR; + PFN_vkBindImageMemory2KHR vkBindImageMemory2KHR; + PFN_vkCreateSamplerYcbcrConversionKHR vkCreateSamplerYcbcrConversionKHR; + PFN_vkDestroySamplerYcbcrConversionKHR vkDestroySamplerYcbcrConversionKHR; + PFN_vkGetImageMemoryRequirements2KHR vkGetImageMemoryRequirements2KHR; } api; uint32_t format_prop_count; @@ -63,6 +67,9 @@ struct wlr_vk_device { struct wlr_drm_format_set dmabuf_render_formats; struct wlr_drm_format_set dmabuf_texture_formats; struct wlr_drm_format_set shm_texture_formats; + + float timestamp_period; + uint32_t timestamp_valid_bits; }; // Tries to find the VkPhysicalDevice for the given drm fd. @@ -205,6 +212,7 @@ struct wlr_vk_render_format_setup { bool use_blending_buffer; bool use_srgb; VkRenderPass render_pass; + VkRenderPass render_pass_clear; VkPipeline output_pipe_identity; VkPipeline output_pipe_srgb; @@ -277,8 +285,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; @@ -345,7 +351,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 { @@ -406,7 +412,13 @@ VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer); // Submits the current stage command buffer and waits until it has // finished execution. -bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer); +bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer, int wait_sync_file_fd); + +struct wlr_vk_render_timer { + struct wlr_render_timer base; + struct wlr_vk_renderer *renderer; + VkQueryPool query_pool; +}; struct wlr_vk_render_pass_texture { struct wlr_vk_texture *texture; @@ -432,20 +444,28 @@ struct wlr_vk_render_pass { struct wlr_drm_syncobj_timeline *signal_timeline; uint64_t signal_point; + struct wlr_vk_render_timer *timer; + struct wl_array textures; // struct wlr_vk_render_pass_texture }; 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); +// 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( @@ -472,6 +492,8 @@ uint64_t vulkan_end_command_buffer(struct wlr_vk_command_buffer *cb, void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb); bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, struct wlr_vk_renderer *renderer); +VkSemaphore vulkan_command_buffer_wait_sync_file(struct wlr_vk_renderer *renderer, + struct wlr_vk_command_buffer *render_cb, size_t sem_index, int sync_file_fd); bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer, struct wlr_vk_render_pass *pass); @@ -484,7 +506,8 @@ bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, VkFormat src_format, VkImage src_image, uint32_t drm_format, uint32_t stride, 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, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point); // State (e.g. image texture) associated with a surface. struct wlr_vk_texture { @@ -526,29 +549,43 @@ 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 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 + int empty_gc_cnt; }; -// 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. +void 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 { diff --git a/include/util/rect_union.h b/include/util/rect_union.h index 2d74f94d5..297c64d43 100644 --- a/include/util/rect_union.h +++ b/include/util/rect_union.h @@ -58,13 +58,15 @@ void rect_union_finish(struct rect_union *r); * * Amortized time: O(1) */ -void rect_union_add(struct rect_union *r, pixman_box32_t box); +void rect_union_add(struct rect_union *r, const pixman_box32_t *box); /** * Compute an exact cover of the rectangles added so far, and return * a pointer to a pixman_region32_t giving that cover. The pointer will - * remain valid until the next time *r is modified. If there was an allocation - * failure, this function may return a single-rectangle bounding box instead. + * remain valid until the next time *r is modified. + * + * An internal complexity limit is enforced by rect_union. If exceeded, this + * function will instead return a single-rectangle bounding box. * * This may be called multiple times and interleaved with rect_union_add(). * diff --git a/include/wlr/backend/drm.h b/include/wlr/backend/drm.h index 3ca6390ab..233bbcb74 100644 --- a/include/wlr/backend/drm.h +++ b/include/wlr/backend/drm.h @@ -39,8 +39,8 @@ struct wlr_drm_lease { struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session, struct wlr_device *dev, struct wlr_backend *parent); -bool wlr_backend_is_drm(struct wlr_backend *backend); -bool wlr_output_is_drm(struct wlr_output *output); +bool wlr_backend_is_drm(const struct wlr_backend *backend); +bool wlr_output_is_drm(const struct wlr_output *output); /** * Get the parent DRM backend, if any. diff --git a/include/wlr/backend/headless.h b/include/wlr/backend/headless.h index 126f1ff21..5487c6085 100644 --- a/include/wlr/backend/headless.h +++ b/include/wlr/backend/headless.h @@ -25,7 +25,7 @@ struct wlr_backend *wlr_headless_backend_create(struct wl_event_loop *loop); struct wlr_output *wlr_headless_add_output(struct wlr_backend *backend, unsigned int width, unsigned int height); -bool wlr_backend_is_headless(struct wlr_backend *backend); -bool wlr_output_is_headless(struct wlr_output *output); +bool wlr_backend_is_headless(const struct wlr_backend *backend); +bool wlr_output_is_headless(const struct wlr_output *output); #endif diff --git a/include/wlr/backend/libinput.h b/include/wlr/backend/libinput.h index 2a4e1996e..18b8e48f3 100644 --- a/include/wlr/backend/libinput.h +++ b/include/wlr/backend/libinput.h @@ -28,8 +28,12 @@ struct libinput_device *wlr_libinput_get_device_handle( */ struct libinput_tablet_tool *wlr_libinput_get_tablet_tool_handle( struct wlr_tablet_tool *wlr_tablet_tool); +/** + * Gets the underlying struct libinput for direct access to the libinput context. + */ +struct libinput *wlr_backend_get_libinput(struct wlr_backend *wlr_backend); -bool wlr_backend_is_libinput(struct wlr_backend *backend); +bool wlr_backend_is_libinput(const struct wlr_backend *backend); bool wlr_input_device_is_libinput(struct wlr_input_device *device); #endif diff --git a/include/wlr/backend/multi.h b/include/wlr/backend/multi.h index c4322d98b..cc3e41b4e 100644 --- a/include/wlr/backend/multi.h +++ b/include/wlr/backend/multi.h @@ -26,7 +26,7 @@ bool wlr_multi_backend_add(struct wlr_backend *multi, void wlr_multi_backend_remove(struct wlr_backend *multi, struct wlr_backend *backend); -bool wlr_backend_is_multi(struct wlr_backend *backend); +bool wlr_backend_is_multi(const struct wlr_backend *backend); bool wlr_multi_is_empty(struct wlr_backend *backend); void wlr_multi_for_each_backend(struct wlr_backend *backend, diff --git a/include/wlr/backend/wayland.h b/include/wlr/backend/wayland.h index c732de68c..7a41b3d09 100644 --- a/include/wlr/backend/wayland.h +++ b/include/wlr/backend/wayland.h @@ -46,7 +46,7 @@ struct wlr_output *wlr_wl_output_create_from_surface(struct wlr_backend *backend /** * Check whether the provided backend is a Wayland backend. */ -bool wlr_backend_is_wl(struct wlr_backend *backend); +bool wlr_backend_is_wl(const struct wlr_backend *backend); /** * Check whether the provided input device is a Wayland input device. @@ -56,7 +56,7 @@ bool wlr_input_device_is_wl(struct wlr_input_device *device); /** * Check whether the provided output device is a Wayland output device. */ -bool wlr_output_is_wl(struct wlr_output *output); +bool wlr_output_is_wl(const struct wlr_output *output); /** * Sets the title of a struct wlr_output which is a Wayland toplevel. diff --git a/include/wlr/backend/x11.h b/include/wlr/backend/x11.h index 1791041ce..8e8bc9870 100644 --- a/include/wlr/backend/x11.h +++ b/include/wlr/backend/x11.h @@ -31,7 +31,7 @@ struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend); /** * Check whether this backend is an X11 backend. */ -bool wlr_backend_is_x11(struct wlr_backend *backend); +bool wlr_backend_is_x11(const struct wlr_backend *backend); /** * Check whether this input device is an X11 input device. @@ -41,7 +41,7 @@ bool wlr_input_device_is_x11(struct wlr_input_device *device); /** * Check whether this output device is an X11 output device. */ -bool wlr_output_is_x11(struct wlr_output *output); +bool wlr_output_is_x11(const struct wlr_output *output); /** * Sets the title of a struct wlr_output which is an X11 window. diff --git a/include/wlr/config.h.in b/include/wlr/config.h.in index ef186343e..8c9f7bbde 100644 --- a/include/wlr/config.h.in +++ b/include/wlr/config.h.in @@ -10,7 +10,7 @@ #mesondefine WLR_HAS_DRM_BACKEND /** * Whether the libinput backend is compile-time enabled. Equivalent to the - * pkg-config "have_libinput_backend" vartiable. + * pkg-config "have_libinput_backend" variable. * * Required for . */ diff --git a/include/wlr/render/drm_syncobj.h b/include/wlr/render/drm_syncobj.h index fabe23d1c..c7dd3b34c 100644 --- a/include/wlr/render/drm_syncobj.h +++ b/include/wlr/render/drm_syncobj.h @@ -97,7 +97,11 @@ bool wlr_drm_syncobj_timeline_signal(struct wlr_drm_syncobj_timeline *timeline, /** * Asynchronously wait for a timeline point. * - * See wlr_drm_syncobj_timeline_check() for a definition of flags. + * Flags can be: + * + * - 0 to wait for the point to be signalled + * - DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE to only wait for a fence to + * materialize * * A callback must be provided that will be invoked when the waiter has finished. */ diff --git a/include/wlr/render/gles2.h b/include/wlr/render/gles2.h index 454e7eb0e..87a57fa54 100644 --- a/include/wlr/render/gles2.h +++ b/include/wlr/render/gles2.h @@ -42,9 +42,9 @@ struct wlr_gles2_texture_attribs { bool has_alpha; }; -bool wlr_renderer_is_gles2(struct wlr_renderer *wlr_renderer); -bool wlr_render_timer_is_gles2(struct wlr_render_timer *timer); -bool wlr_texture_is_gles2(struct wlr_texture *texture); +bool wlr_renderer_is_gles2(const struct wlr_renderer *wlr_renderer); +bool wlr_render_timer_is_gles2(const struct wlr_render_timer *timer); +bool wlr_texture_is_gles2(const struct wlr_texture *texture); void wlr_gles2_texture_get_attribs(struct wlr_texture *texture, struct wlr_gles2_texture_attribs *attribs); diff --git a/include/wlr/render/pixman.h b/include/wlr/render/pixman.h index 206dd78c6..2951f5e9e 100644 --- a/include/wlr/render/pixman.h +++ b/include/wlr/render/pixman.h @@ -14,8 +14,8 @@ struct wlr_renderer *wlr_pixman_renderer_create(void); -bool wlr_renderer_is_pixman(struct wlr_renderer *wlr_renderer); -bool wlr_texture_is_pixman(struct wlr_texture *texture); +bool wlr_renderer_is_pixman(const struct wlr_renderer *wlr_renderer); +bool wlr_texture_is_pixman(const struct wlr_texture *texture); pixman_image_t *wlr_pixman_renderer_get_buffer_image( struct wlr_renderer *wlr_renderer, struct wlr_buffer *wlr_buffer); diff --git a/include/wlr/render/vulkan.h b/include/wlr/render/vulkan.h index 50f8c558d..170665dd0 100644 --- a/include/wlr/render/vulkan.h +++ b/include/wlr/render/vulkan.h @@ -25,8 +25,8 @@ VkPhysicalDevice wlr_vk_renderer_get_physical_device(struct wlr_renderer *render VkDevice wlr_vk_renderer_get_device(struct wlr_renderer *renderer); uint32_t wlr_vk_renderer_get_queue_family(struct wlr_renderer *renderer); -bool wlr_renderer_is_vk(struct wlr_renderer *wlr_renderer); -bool wlr_texture_is_vk(struct wlr_texture *texture); +bool wlr_renderer_is_vk(const struct wlr_renderer *wlr_renderer); +bool wlr_texture_is_vk(const struct wlr_texture *texture); void wlr_vk_texture_get_image_attribs(struct wlr_texture *texture, struct wlr_vk_image_attribs *attribs); diff --git a/include/wlr/render/wlr_texture.h b/include/wlr/render/wlr_texture.h index 1e352c6e6..b7dcb143b 100644 --- a/include/wlr/render/wlr_texture.h +++ b/include/wlr/render/wlr_texture.h @@ -37,6 +37,8 @@ struct wlr_texture_read_pixels_options { uint32_t dst_x, dst_y; /** Source box of the texture to read from. If empty, the full texture is assumed. */ const struct wlr_box src_box; + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; }; bool wlr_texture_read_pixels(struct wlr_texture *texture, diff --git a/include/wlr/types/wlr_ext_background_effect_v1.h b/include/wlr/types/wlr_ext_background_effect_v1.h new file mode 100644 index 000000000..72b0dbc8e --- /dev/null +++ b/include/wlr/types/wlr_ext_background_effect_v1.h @@ -0,0 +1,50 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_EXT_BACKGROUND_EFFECT_V1_H +#define WLR_TYPES_WLR_EXT_BACKGROUND_EFFECT_V1_H + +#include +#include +#include + +struct wlr_surface; + +struct wlr_ext_background_effect_surface_v1_state { + pixman_region32_t blur_region; +}; + +struct wlr_ext_background_effect_manager_v1 { + struct wl_global *global; + uint32_t capabilities; // bitmask of enum ext_background_effect_manager_v1_capability + + struct { + struct wl_signal destroy; + } events; + + void *data; + + struct { + struct wl_list resources; // wl_resource_get_link() + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_ext_background_effect_manager_v1 *wlr_ext_background_effect_manager_v1_create( + struct wl_display *display, uint32_t version, uint32_t capabilities); + +/** + * Get the committed background effect state for a surface. + * + * Returns NULL if the client has not attached a background effect object to + * the surface. + */ +const struct wlr_ext_background_effect_surface_v1_state * +wlr_ext_background_effect_v1_get_surface_state(struct wlr_surface *surface); + +#endif diff --git a/include/wlr/types/wlr_ext_image_capture_source_v1.h b/include/wlr/types/wlr_ext_image_capture_source_v1.h index 6cbbacf76..0a93c49f6 100644 --- a/include/wlr/types/wlr_ext_image_capture_source_v1.h +++ b/include/wlr/types/wlr_ext_image_capture_source_v1.h @@ -43,6 +43,8 @@ struct wlr_ext_image_capture_source_v1 { struct wl_signal frame; // struct wlr_ext_image_capture_source_v1_frame_event struct wl_signal destroy; } events; + + void *data; }; /** @@ -91,7 +93,7 @@ struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 { struct { struct wl_signal destroy; - struct wl_signal new_request; // struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request + struct wl_signal capture_request; // struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_event } events; struct { @@ -99,7 +101,7 @@ struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 { } WLR_PRIVATE; }; -struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request { +struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_event { struct wlr_ext_foreign_toplevel_handle_v1 *toplevel_handle; struct wl_client *client; @@ -124,7 +126,7 @@ struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 * wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(struct wl_display *display, uint32_t version); bool wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept( - struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request, + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_event *request, struct wlr_ext_image_capture_source_v1 *source); struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_with_scene_node( diff --git a/include/wlr/types/wlr_linux_drm_syncobj_v1.h b/include/wlr/types/wlr_linux_drm_syncobj_v1.h index 7fd55ec2e..dfcbc4d43 100644 --- a/include/wlr/types/wlr_linux_drm_syncobj_v1.h +++ b/include/wlr/types/wlr_linux_drm_syncobj_v1.h @@ -74,4 +74,21 @@ bool wlr_linux_drm_syncobj_v1_state_add_release_point( struct wlr_drm_syncobj_timeline *release_timeline, uint64_t release_point, struct wl_event_loop *event_loop); +/** + * Register the DMA-BUF release of a buffer for buffer usage. + * Non-dmabuf buffers are considered to be immediately available (no wait). + * + * This function may be called multiple times for the same commit. The client's + * release point will be signalled when all registered points are signalled, and + * a new buffer has been committed. + * + * Because the platform may not support DMA-BUF fence merges, a wl_event_loop + * must be supplied to schedule a wait internally, if needed + * + * Waits for write access + */ +bool wlr_linux_drm_syncobj_v1_state_add_release_from_implicit_sync( + struct wlr_linux_drm_syncobj_surface_v1_state *state, + struct wlr_buffer *buffer, struct wl_event_loop *event_loop); + #endif diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 4485b3694..c8e44b0e6 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -77,6 +77,7 @@ enum wlr_output_state_field { WLR_OUTPUT_STATE_SIGNAL_TIMELINE = 1 << 11, WLR_OUTPUT_STATE_COLOR_TRANSFORM = 1 << 12, WLR_OUTPUT_STATE_IMAGE_DESCRIPTION = 1 << 13, + WLR_OUTPUT_STATE_COLOR_REPRESENTATION = 1 << 14, }; enum wlr_output_state_mode_type { @@ -142,6 +143,10 @@ struct wlr_output_state { * regular page-flip at the next wlr_output.frame event. */ bool tearing_page_flip; + // Set if (committed & WLR_OUTPUT_STATE_COLOR_REPRESENTATION) + enum wlr_color_encoding color_encoding; + enum wlr_color_range color_range; + enum wlr_output_state_mode_type mode_type; struct wlr_output_mode *mode; struct { @@ -205,6 +210,8 @@ struct wlr_output { enum wl_output_transform transform; enum wlr_output_adaptive_sync_status adaptive_sync_status; uint32_t render_format; + enum wlr_color_encoding color_encoding; + enum wlr_color_range color_range; const struct wlr_output_image_description *image_description; // Indicates whether making changes to adaptive sync status is supported. @@ -625,6 +632,15 @@ void wlr_output_state_set_color_transform(struct wlr_output_state *state, bool wlr_output_state_set_image_description(struct wlr_output_state *state, const struct wlr_output_image_description *image_desc); +/** + * Set the color encoding and range of the primary scanout buffer. + * + * Pass WLR_COLOR_ENCODING_NONE / WLR_COLOR_RANGE_NONE to reset to defaults. + */ +void wlr_output_state_set_color_encoding_and_range( + struct wlr_output_state *state, + enum wlr_color_encoding encoding, enum wlr_color_range range); + /** * Copies the output state from src to dst. It is safe to then * wlr_output_state_finish() src and have dst still be valid. diff --git a/include/wlr/types/wlr_pointer_warp_v1.h b/include/wlr/types/wlr_pointer_warp_v1.h new file mode 100644 index 000000000..466ca3290 --- /dev/null +++ b/include/wlr/types/wlr_pointer_warp_v1.h @@ -0,0 +1,38 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_POINTER_WARP_V1_H +#define WLR_TYPES_WLR_POINTER_WARP_V1_H + +#include + +struct wlr_pointer_warp_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + struct wl_signal warp; // struct wlr_pointer_warp_v1_event_warp + } events; + + void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_pointer_warp_v1_event_warp { + struct wlr_surface *surface; + struct wlr_seat_client *seat_client; + double x, y; + uint32_t serial; +}; + +struct wlr_pointer_warp_v1 *wlr_pointer_warp_v1_create(struct wl_display *display, + uint32_t version); +#endif diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 6c627b1f9..f6f97cfea 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -171,8 +171,6 @@ struct wlr_scene_buffer { struct { struct wl_signal outputs_update; // struct wlr_scene_outputs_update_event - struct wl_signal output_enter; // struct wlr_scene_output - struct wl_signal output_leave; // struct wlr_scene_output struct wl_signal output_sample; // struct wlr_scene_output_sample_event struct wl_signal frame_done; // struct wlr_scene_frame_done_event } events; diff --git a/include/wlr/types/wlr_xdg_decoration_v1.h b/include/wlr/types/wlr_xdg_decoration_v1.h index 78f84eb39..29b10d72a 100644 --- a/include/wlr/types/wlr_xdg_decoration_v1.h +++ b/include/wlr/types/wlr_xdg_decoration_v1.h @@ -66,7 +66,8 @@ struct wlr_xdg_toplevel_decoration_v1 { }; struct wlr_xdg_decoration_manager_v1 * - wlr_xdg_decoration_manager_v1_create(struct wl_display *display); + wlr_xdg_decoration_manager_v1_create(struct wl_display *display, + uint32_t version); uint32_t wlr_xdg_toplevel_decoration_v1_set_mode( struct wlr_xdg_toplevel_decoration_v1 *decoration, diff --git a/include/wlr/types/wlr_xdg_foreign_v1.h b/include/wlr/types/wlr_xdg_foreign_v1.h index 975828c2c..dd20700cd 100644 --- a/include/wlr/types/wlr_xdg_foreign_v1.h +++ b/include/wlr/types/wlr_xdg_foreign_v1.h @@ -56,7 +56,6 @@ struct wlr_xdg_imported_v1 { }; struct wlr_xdg_imported_child_v1 { - struct wlr_xdg_imported_v1 *imported; struct wlr_xdg_toplevel *toplevel; struct wl_list link; // wlr_xdg_imported_v1.children diff --git a/include/wlr/types/wlr_xdg_foreign_v2.h b/include/wlr/types/wlr_xdg_foreign_v2.h index 0af0be52e..f2b2a9230 100644 --- a/include/wlr/types/wlr_xdg_foreign_v2.h +++ b/include/wlr/types/wlr_xdg_foreign_v2.h @@ -56,7 +56,6 @@ struct wlr_xdg_imported_v2 { }; struct wlr_xdg_imported_child_v2 { - struct wlr_xdg_imported_v2 *imported; struct wlr_xdg_toplevel *toplevel; struct wl_list link; // wlr_xdg_imported_v2.children diff --git a/include/wlr/util/box.h b/include/wlr/util/box.h index 64dcbc5cf..f6809e0c3 100644 --- a/include/wlr/util/box.h +++ b/include/wlr/util/box.h @@ -107,6 +107,13 @@ void wlr_fbox_transform(struct wlr_fbox *dest, const struct wlr_fbox *box, #ifdef WLR_USE_UNSTABLE +/** + * Checks whether two boxes intersect. + * + * Returns false if either box is empty. + */ +bool wlr_box_intersects(const struct wlr_box *a, const struct wlr_box *b); + /** * Returns true if the two boxes are equal, false otherwise. */ diff --git a/include/xwayland/selection.h b/include/xwayland/selection.h index 308310232..2f333f8e0 100644 --- a/include/xwayland/selection.h +++ b/include/xwayland/selection.h @@ -82,9 +82,9 @@ void xwm_handle_selection_notify(struct wlr_xwm *xwm, xcb_selection_notify_event_t *event); int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, xcb_xfixes_selection_notify_event_t *event); -bool data_source_is_xwayland(struct wlr_data_source *wlr_source); +bool data_source_is_xwayland(const struct wlr_data_source *wlr_source); bool primary_selection_source_is_xwayland( - struct wlr_primary_selection_source *wlr_source); + const struct wlr_primary_selection_source *wlr_source); void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag); diff --git a/meson.build b/meson.build index b232905d6..90200d4e3 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wlroots', 'c', - version: '0.20.1', + version: '0.21.0-dev', license: 'MIT', meson_version: '>=1.3', default_options: [ @@ -116,7 +116,7 @@ xkbcommon = dependency('xkbcommon', ], ) pixman = dependency('pixman-1', - version: '>=0.43.0', + version: '>=0.46.0', fallback: 'pixman', default_options: ['werror=false'], ) @@ -178,6 +178,10 @@ if get_option('examples') subdir('tinywl') endif +if get_option('tests') + subdir('test') +endif + pkgconfig = import('pkgconfig') pkgconfig.generate( lib_wlr, diff --git a/meson.options b/meson.options index 5863764aa..d8a8ca940 100644 --- a/meson.options +++ b/meson.options @@ -7,5 +7,6 @@ option('backends', type: 'array', choices: ['auto', 'drm', 'libinput', 'x11'], v option('allocators', type: 'array', choices: ['auto', 'gbm', 'udmabuf'], value: ['auto'], description: 'Select built-in allocators') option('session', type: 'feature', value: 'auto', description: 'Enable session support') +option('tests', type: 'boolean', value: true, description: 'Build tests and benchmarks') option('color-management', type: 'feature', value: 'auto', description: 'Enable support for color management') option('libliftoff', type: 'feature', value: 'auto', description: 'Enable support for libliftoff') diff --git a/protocol/meson.build b/protocol/meson.build index 30aeb68c6..5830272b3 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -30,6 +30,7 @@ protocols = { 'content-type-v1': wl_protocol_dir / 'staging/content-type/content-type-v1.xml', 'cursor-shape-v1': wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', + 'ext-background-effect-v1': wl_protocol_dir / 'staging/ext-background-effect/ext-background-effect-v1.xml', 'ext-foreign-toplevel-list-v1': wl_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml', 'ext-idle-notify-v1': wl_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', 'ext-image-capture-source-v1': wl_protocol_dir / 'staging/ext-image-capture-source/ext-image-capture-source-v1.xml', @@ -39,6 +40,7 @@ protocols = { 'ext-workspace-v1': wl_protocol_dir / 'staging/ext-workspace/ext-workspace-v1.xml', 'fractional-scale-v1': wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', 'linux-drm-syncobj-v1': wl_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml', + 'pointer-warp-v1': wl_protocol_dir / 'staging/pointer-warp/pointer-warp-v1.xml', 'security-context-v1': wl_protocol_dir / 'staging/security-context/security-context-v1.xml', 'single-pixel-buffer-v1': wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', 'xdg-activation-v1': wl_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml', diff --git a/protocol/virtual-keyboard-unstable-v1.xml b/protocol/virtual-keyboard-unstable-v1.xml index 5095c91b8..a888e5f87 100644 --- a/protocol/virtual-keyboard-unstable-v1.xml +++ b/protocol/virtual-keyboard-unstable-v1.xml @@ -39,16 +39,15 @@ Provide a file descriptor to the compositor which can be memory-mapped to provide a keyboard mapping description. - - Format carries a value from the keymap_format enumeration. - + + diff --git a/render/dmabuf_linux.c b/render/dmabuf_linux.c index 53fb909bc..b28f22beb 100644 --- a/render/dmabuf_linux.c +++ b/render/dmabuf_linux.c @@ -56,7 +56,8 @@ bool dmabuf_import_sync_file(int dmabuf_fd, uint32_t flags, int sync_file_fd) { .fd = sync_file_fd, }; if (drmIoctl(dmabuf_fd, DMA_BUF_IOCTL_IMPORT_SYNC_FILE, &data) != 0) { - wlr_log_errno(WLR_ERROR, "drmIoctl(IMPORT_SYNC_FILE) failed"); + enum wlr_log_importance importance = errno == ENOTTY ? WLR_DEBUG : WLR_ERROR; + wlr_log_errno(importance, "drmIoctl(IMPORT_SYNC_FILE) failed"); return false; } return true; @@ -68,7 +69,8 @@ int dmabuf_export_sync_file(int dmabuf_fd, uint32_t flags) { .fd = -1, }; if (drmIoctl(dmabuf_fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &data) != 0) { - wlr_log_errno(WLR_ERROR, "drmIoctl(EXPORT_SYNC_FILE) failed"); + enum wlr_log_importance importance = errno == ENOTTY ? WLR_DEBUG : WLR_ERROR; + wlr_log_errno(importance, "drmIoctl(EXPORT_SYNC_FILE) failed"); return -1; } return data.fd; diff --git a/render/drm_syncobj.c b/render/drm_syncobj.c index 2abe2cff6..15f71c536 100644 --- a/render/drm_syncobj.c +++ b/render/drm_syncobj.c @@ -222,14 +222,8 @@ bool wlr_drm_syncobj_timeline_waiter_init(struct wlr_drm_syncobj_timeline_waiter return false; } - struct drm_syncobj_eventfd syncobj_eventfd = { - .handle = timeline->handle, - .flags = flags, - .point = point, - .fd = ev_fd, - }; - if (drmIoctl(timeline->drm_fd, DRM_IOCTL_SYNCOBJ_EVENTFD, &syncobj_eventfd) != 0) { - wlr_log_errno(WLR_ERROR, "DRM_IOCTL_SYNCOBJ_EVENTFD failed"); + if (drmSyncobjEventfd(timeline->drm_fd, timeline->handle, point, ev_fd, flags) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjEventfd() failed"); close(ev_fd); return false; } diff --git a/render/drm_syncobj_merger.c b/render/drm_syncobj_merger.c index d50d28c28..66657b2e1 100644 --- a/render/drm_syncobj_merger.c +++ b/render/drm_syncobj_merger.c @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include "render/dmabuf.h" #include "render/drm_syncobj_merger.h" #include "config.h" @@ -79,14 +81,7 @@ void wlr_drm_syncobj_merger_unref(struct wlr_drm_syncobj_merger *merger) { static bool merger_add_exportable(struct wlr_drm_syncobj_merger *merger, struct wlr_drm_syncobj_timeline *src_timeline, uint64_t src_point) { int new_sync = wlr_drm_syncobj_timeline_export_sync_file(src_timeline, src_point); - if (merger->sync_fd != -1) { - int fd2 = new_sync; - new_sync = sync_file_merge(merger->sync_fd, fd2); - close(fd2); - close(merger->sync_fd); - } - merger->sync_fd = new_sync; - return true; + return wlr_drm_syncobj_merger_add_sync_file(merger, new_sync); } struct export_waiter { @@ -123,6 +118,7 @@ bool wlr_drm_syncobj_merger_add(struct wlr_drm_syncobj_merger *merger, } if (!wlr_drm_syncobj_timeline_waiter_init(&add->waiter, src_timeline, src_point, flags, loop, export_waiter_handle_ready)) { + free(add); return false; } add->merger = merger; @@ -131,3 +127,71 @@ bool wlr_drm_syncobj_merger_add(struct wlr_drm_syncobj_merger *merger, merger->n_ref++; return true; } + +bool wlr_drm_syncobj_merger_add_sync_file(struct wlr_drm_syncobj_merger *merger, + int fd) { + int new_sync = fd; + if (merger->sync_fd != -1) { + new_sync = sync_file_merge(merger->sync_fd, fd); + close(fd); + if (new_sync < 0) { + return false; + } + close(merger->sync_fd); + } + merger->sync_fd = new_sync; + return merger->sync_fd != -1; +} + +struct poll_waiter { + struct wl_event_source *event_source; + struct wlr_drm_syncobj_merger *merger; +}; + +static int poll_waiter_handle_done(int fd, uint32_t mask, void *data) { + struct poll_waiter *waiter = data; + wlr_drm_syncobj_merger_unref(waiter->merger); + wl_event_source_remove(waiter->event_source); + free(waiter); + return 0; +} + +bool wlr_drm_syncobj_merger_add_dmabuf(struct wlr_drm_syncobj_merger *merger, + struct wlr_buffer *buffer, struct wl_event_loop *event_loop) { + struct wlr_dmabuf_attributes dmabuf_attributes; + if (!wlr_buffer_get_dmabuf(buffer, &dmabuf_attributes)) { + return true; + } + + bool res = true; + for (int i = 0; i < dmabuf_attributes.n_planes; ++i) { + int sync_fd = dmabuf_export_sync_file(dmabuf_attributes.fd[i], DMA_BUF_SYNC_WRITE); + if (sync_fd == -1) { + res = false; + break; + } + if (!wlr_drm_syncobj_merger_add_sync_file(merger, sync_fd)) { + return false; + } + } + + if (res) { + return true; + } + + for (int i = 0; i < dmabuf_attributes.n_planes; ++i) { + struct poll_waiter *waiter = calloc(1, sizeof(*waiter)); + if (waiter == NULL) { + return false; + } + waiter->merger = wlr_drm_syncobj_merger_ref(merger); + waiter->event_source = wl_event_loop_add_fd(event_loop, + dmabuf_attributes.fd[i], WL_EVENT_WRITABLE, poll_waiter_handle_done, waiter); + if (waiter->event_source == NULL) { + wlr_drm_syncobj_merger_unref(waiter->merger); + free(waiter); + return false; + } + } + return true; +} diff --git a/render/egl.c b/render/egl.c index 8673acabf..9e02cb6df 100644 --- a/render/egl.c +++ b/render/egl.c @@ -295,6 +295,8 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display, egl->exts.EXT_create_context_robustness = check_egl_ext(display_exts_str, "EGL_EXT_create_context_robustness"); + egl->exts.KHR_context_flush_control = + check_egl_ext(display_exts_str, "EGL_KHR_context_flush_control"); const char *device_exts_str = NULL, *driver_name = NULL; if (egl->exts.EXT_device_query) { @@ -409,7 +411,7 @@ static bool egl_init(struct wlr_egl *egl, EGLenum platform, } size_t atti = 0; - EGLint attribs[7]; + EGLint attribs[32]; attribs[atti++] = EGL_CONTEXT_CLIENT_VERSION; attribs[atti++] = 2; @@ -429,6 +431,11 @@ static bool egl_init(struct wlr_egl *egl, EGLenum platform, attribs[atti++] = EGL_LOSE_CONTEXT_ON_RESET_EXT; } + if (egl->exts.KHR_context_flush_control) { + attribs[atti++] = EGL_CONTEXT_RELEASE_BEHAVIOR_KHR; + attribs[atti++] = EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR; + } + attribs[atti++] = EGL_NONE; assert(atti <= sizeof(attribs)/sizeof(attribs[0])); diff --git a/render/gles2/pass.c b/render/gles2/pass.c index a70ea1320..477f1750d 100644 --- a/render/gles2/pass.c +++ b/render/gles2/pass.c @@ -201,8 +201,6 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, src_fbox.width /= options->texture->width; src_fbox.height /= options->texture->height; - push_gles2_debug(renderer); - if (options->wait_timeline != NULL) { int sync_file_fd = wlr_drm_syncobj_timeline_export_sync_file(options->wait_timeline, options->wait_point); @@ -223,6 +221,8 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, } } + push_gles2_debug(renderer); + setup_blending(!texture->has_alpha && alpha == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode); diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index e362daee8..7f01b8acd 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -29,7 +29,7 @@ static const struct wlr_renderer_impl renderer_impl; static const struct wlr_render_timer_impl render_timer_impl; -bool wlr_renderer_is_gles2(struct wlr_renderer *wlr_renderer) { +bool wlr_renderer_is_gles2(const struct wlr_renderer *wlr_renderer) { return wlr_renderer->impl == &renderer_impl; } @@ -40,7 +40,7 @@ struct wlr_gles2_renderer *gles2_get_renderer( return renderer; } -bool wlr_render_timer_is_gles2(struct wlr_render_timer *timer) { +bool wlr_render_timer_is_gles2(const struct wlr_render_timer *timer) { return timer->impl == &render_timer_impl; } diff --git a/render/gles2/shaders/quad.frag b/render/gles2/shaders/quad.frag index 97d3a31c1..d48aefdbe 100644 --- a/render/gles2/shaders/quad.frag +++ b/render/gles2/shaders/quad.frag @@ -4,7 +4,6 @@ precision highp float; precision mediump float; #endif -varying vec4 v_color; varying vec2 v_texcoord; uniform vec4 color; diff --git a/render/gles2/texture.c b/render/gles2/texture.c index 9a967ebdb..575a11fca 100644 --- a/render/gles2/texture.c +++ b/render/gles2/texture.c @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -16,7 +18,7 @@ static const struct wlr_texture_impl texture_impl; -bool wlr_texture_is_gles2(struct wlr_texture *wlr_texture) { +bool wlr_texture_is_gles2(const struct wlr_texture *wlr_texture) { return wlr_texture->impl == &texture_impl; } @@ -201,6 +203,27 @@ static bool gles2_texture_read_pixels(struct wlr_texture *wlr_texture, return false; } + if (options->wait_timeline != NULL) { + int sync_file_fd = + wlr_drm_syncobj_timeline_export_sync_file(options->wait_timeline, options->wait_point); + if (sync_file_fd < 0) { + return false; + } + + struct wlr_gles2_renderer *renderer = texture->renderer; + EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, sync_file_fd); + close(sync_file_fd); + if (sync == EGL_NO_SYNC_KHR) { + return false; + } + + bool ok = wlr_egl_wait_sync(renderer->egl, sync); + wlr_egl_destroy_sync(renderer->egl, sync); + if (!ok) { + return false; + } + } + // Make sure any pending drawing is finished before we try to read it glFinish(); diff --git a/render/pixman/meson.build b/render/pixman/meson.build index dea96b096..ea9a396ef 100644 --- a/render/pixman/meson.build +++ b/render/pixman/meson.build @@ -1,7 +1,3 @@ -pixman = dependency('pixman-1', version: '>=0.46.0') - -wlr_deps += pixman - wlr_files += files( 'pass.c', 'pixel_format.c', diff --git a/render/pixman/renderer.c b/render/pixman/renderer.c index 4631a33ab..25c558a9d 100644 --- a/render/pixman/renderer.c +++ b/render/pixman/renderer.c @@ -12,7 +12,7 @@ static const struct wlr_renderer_impl renderer_impl; -bool wlr_renderer_is_pixman(struct wlr_renderer *wlr_renderer) { +bool wlr_renderer_is_pixman(const struct wlr_renderer *wlr_renderer) { return wlr_renderer->impl == &renderer_impl; } @@ -69,7 +69,7 @@ static struct wlr_pixman_buffer *get_buffer( static const struct wlr_texture_impl texture_impl; -bool wlr_texture_is_pixman(struct wlr_texture *texture) { +bool wlr_texture_is_pixman(const struct wlr_texture *texture) { return texture->impl == &texture_impl; } diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 503e37c07..38be3320b 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -2,7 +2,9 @@ #include #include #include +#include #include +#include #include #include @@ -38,17 +40,6 @@ static void bind_pipeline(struct wlr_vk_render_pass *pass, VkPipeline pipeline) pass->bound_pipeline = pipeline; } -static void get_clip_region(struct wlr_vk_render_pass *pass, - const pixman_region32_t *in, pixman_region32_t *out) { - if (in != NULL) { - pixman_region32_init(out); - pixman_region32_copy(out, in); - } else { - struct wlr_buffer *buffer = pass->render_buffer->wlr_buffer; - pixman_region32_init_rect(out, 0, 0, buffer->width, buffer->height); - } -} - static void convert_pixman_box_to_vk_rect(const pixman_box32_t *box, VkRect2D *rect) { *rect = (VkRect2D){ .offset = { .x = box->x1, .y = box->y1 }, @@ -99,53 +90,6 @@ static void render_pass_destroy(struct wlr_vk_render_pass *pass) { free(pass); } -static VkSemaphore render_pass_wait_sync_file(struct wlr_vk_render_pass *pass, - size_t sem_index, int sync_file_fd) { - struct wlr_vk_renderer *renderer = pass->renderer; - struct wlr_vk_command_buffer *render_cb = pass->command_buffer; - VkResult res; - - VkSemaphore *wait_semaphores = render_cb->wait_semaphores.data; - size_t wait_semaphores_len = render_cb->wait_semaphores.size / sizeof(wait_semaphores[0]); - - VkSemaphore *sem_ptr; - if (sem_index >= wait_semaphores_len) { - sem_ptr = wl_array_add(&render_cb->wait_semaphores, sizeof(*sem_ptr)); - if (sem_ptr == NULL) { - return VK_NULL_HANDLE; - } - *sem_ptr = VK_NULL_HANDLE; - } else { - sem_ptr = &wait_semaphores[sem_index]; - } - - if (*sem_ptr == VK_NULL_HANDLE) { - VkSemaphoreCreateInfo semaphore_info = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - }; - res = vkCreateSemaphore(renderer->dev->dev, &semaphore_info, NULL, sem_ptr); - if (res != VK_SUCCESS) { - wlr_vk_error("vkCreateSemaphore", res); - return VK_NULL_HANDLE; - } - } - - VkImportSemaphoreFdInfoKHR import_info = { - .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, - .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, - .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT, - .semaphore = *sem_ptr, - .fd = sync_file_fd, - }; - res = renderer->dev->api.vkImportSemaphoreFdKHR(renderer->dev->dev, &import_info); - if (res != VK_SUCCESS) { - wlr_vk_error("vkImportSemaphoreFdKHR", res); - return VK_NULL_HANDLE; - } - - return *sem_ptr; -} - static bool render_pass_wait_render_buffer(struct wlr_vk_render_pass *pass, VkSemaphoreSubmitInfoKHR *render_wait, uint32_t *render_wait_len_ptr) { int sync_file_fds[WLR_DMABUF_MAX_PLANES]; @@ -162,7 +106,8 @@ static bool render_pass_wait_render_buffer(struct wlr_vk_render_pass *pass, continue; } - VkSemaphore sem = render_pass_wait_sync_file(pass, *render_wait_len_ptr, sync_file_fds[i]); + VkSemaphore sem = vulkan_command_buffer_wait_sync_file(pass->renderer, + pass->command_buffer, *render_wait_len_ptr, sync_file_fds[i]); if (sem == VK_NULL_HANDLE) { close(sync_file_fds[i]); continue; @@ -248,11 +193,13 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { int width = pass->render_buffer->wlr_buffer->width; int height = pass->render_buffer->wlr_buffer->height; - float final_matrix[9] = { - width, 0, -1, - 0, height, -1, - 0, 0, 0, - }; + struct wlr_box output_box = { 0, 0, width, height }; + float proj[9], final_matrix[9]; + wlr_matrix_identity(proj); + wlr_matrix_project_box(final_matrix, &output_box, + WL_OUTPUT_TRANSFORM_NORMAL, proj); + wlr_matrix_multiply(final_matrix, pass->projection, final_matrix); + struct wlr_vk_vert_pcr_data vert_pcr_data = { .uv_off = { 0, 0 }, .uv_size = { 1, 1 }, @@ -331,16 +278,38 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { int clip_rects_len; const pixman_box32_t *clip_rects = pixman_region32_rectangles( clip, &clip_rects_len); - for (int i = 0; i < clip_rects_len; i++) { - VkRect2D rect; - convert_pixman_box_to_vk_rect(&clip_rects[i], &rect); - vkCmdSetScissor(render_cb->vk, 0, 1, &rect); - vkCmdDraw(render_cb->vk, 4, 1, 0, 0); + + if (clip_rects_len > 0) { + const VkDeviceSize instance_size = 4 * sizeof(float); + struct wlr_vk_buffer_span span = vulkan_get_stage_span(renderer, + clip_rects_len * instance_size, 16); + if (!span.buffer) { + pass->failed = true; + goto error; + } + + float *instance_data = (float *)((char *)span.buffer->cpu_mapping + span.offset); + for (int i = 0; i < clip_rects_len; i++) { + const pixman_box32_t *b = &clip_rects[i]; + instance_data[i * 4 + 0] = (float)b->x1 / width; + instance_data[i * 4 + 1] = (float)b->y1 / height; + instance_data[i * 4 + 2] = (float)(b->x2 - b->x1) / width; + instance_data[i * 4 + 3] = (float)(b->y2 - b->y1) / height; + } + + VkDeviceSize vb_offset = span.offset; + vkCmdBindVertexBuffers(render_cb->vk, 0, 1, &span.buffer->buffer, &vb_offset); + vkCmdDraw(render_cb->vk, 4, clip_rects_len, 0, 0); } } vkCmdEndRenderPass(render_cb->vk); + if (pass->timer != NULL) { + vkCmdWriteTimestamp(render_cb->vk, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + pass->timer->query_pool, 1); + } + size_t pass_textures_len = pass->textures.size / sizeof(struct wlr_vk_render_pass_texture); size_t render_wait_cap = (1 + pass_textures_len) * WLR_DMABUF_MAX_PLANES; render_wait = calloc(render_wait_cap, sizeof(*render_wait)); @@ -431,7 +400,8 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { continue; } - VkSemaphore sem = render_pass_wait_sync_file(pass, render_wait_len, sync_file_fds[i]); + VkSemaphore sem = vulkan_command_buffer_wait_sync_file(renderer, render_cb, + render_wait_len, sync_file_fds[i]); if (sem == VK_NULL_HANDLE) { close(sync_file_fds[i]); continue; @@ -459,33 +429,32 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { } if (pass->two_pass) { - // The render pass changes the blend image layout from - // color attachment to read only, so on each frame, before - // the render pass starts, we change it back - VkImageLayout blend_src_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - if (!render_buffer->two_pass.blend_transitioned) { - blend_src_layout = VK_IMAGE_LAYOUT_UNDEFINED; + // On the first frame the clear render pass transitions the blend + // image from undefined and we just mark it transitioned. On every + // frame after, the previous frame left it read-only, so we change + // it back to a color attachment before the render pass starts + if (render_buffer->two_pass.blend_transitioned) { + VkImageMemoryBarrier blend_acq_barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = render_buffer->two_pass.blend_image, + .oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1, + .levelCount = 1, + }, + }; + vkCmdPipelineBarrier(stage_cb->vk, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, 0, NULL, 0, NULL, 1, &blend_acq_barrier); + } else { render_buffer->two_pass.blend_transitioned = true; } - - VkImageMemoryBarrier blend_acq_barrier = { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = render_buffer->two_pass.blend_image, - .oldLayout = blend_src_layout, - .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, - .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1, - .levelCount = 1, - }, - }; - vkCmdPipelineBarrier(stage_cb->vk, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - 0, 0, NULL, 0, NULL, 1, &blend_acq_barrier); } // acquire render buffer before rendering @@ -635,14 +604,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { 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); - } + vulkan_stage_mark_submit(renderer, render_timeline_point); if (!vulkan_sync_render_pass_release(renderer, pass)) { wlr_log(WLR_ERROR, "Failed to sync render buffer"); @@ -667,18 +629,11 @@ error: } static void render_pass_mark_box_updated(struct wlr_vk_render_pass *pass, - const struct wlr_box *box) { + const pixman_box32_t *box) { if (!pass->two_pass) { return; } - - pixman_box32_t pixman_box = { - .x1 = box->x, - .x2 = box->x + box->width, - .y1 = box->y, - .y2 = box->y + box->height, - }; - rect_union_add(&pass->updated_region, pixman_box); + rect_union_add(&pass->updated_region, box); } static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, @@ -698,29 +653,26 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, options->color.a, // no conversion for alpha }; + struct wlr_box box; + wlr_render_rect_options_get_box(options, pass->render_buffer->wlr_buffer, &box); + pixman_region32_t clip; - get_clip_region(pass, options->clip, &clip); + if (options->clip) { + pixman_region32_init(&clip); + pixman_region32_intersect_rect(&clip, options->clip, + box.x, box.y, box.width, box.height); + } else { + pixman_region32_init_rect(&clip, + box.x, box.y, box.width, box.height); + } int clip_rects_len; const pixman_box32_t *clip_rects = pixman_region32_rectangles(&clip, &clip_rects_len); - // Record regions possibly updated for use in second subpass - for (int i = 0; i < clip_rects_len; i++) { - struct wlr_box clip_box = { - .x = clip_rects[i].x1, - .y = clip_rects[i].y1, - .width = clip_rects[i].x2 - clip_rects[i].x1, - .height = clip_rects[i].y2 - clip_rects[i].y1, - }; - struct wlr_box intersection; - if (!wlr_box_intersection(&intersection, &options->box, &clip_box)) { - continue; - } - render_pass_mark_box_updated(pass, &intersection); + if (clip_rects_len == 0) { + pixman_region32_fini(&clip); + return; } - struct wlr_box box; - wlr_render_rect_options_get_box(options, pass->render_buffer->wlr_buffer, &box); - switch (options->blend_mode) { case WLR_RENDER_BLEND_MODE_PREMULTIPLIED:; float proj[9], matrix[9]; @@ -739,6 +691,23 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, break; } + const VkDeviceSize instance_size = 4 * sizeof(float); + struct wlr_vk_buffer_span span = vulkan_get_stage_span(pass->renderer, + clip_rects_len * instance_size, 16); + if (!span.buffer) { + pass->failed = true; + break; + } + float *instance_data = (float *)((char *)span.buffer->cpu_mapping + span.offset); + for (int i = 0; i < clip_rects_len; i++) { + const pixman_box32_t *rect = &clip_rects[i]; + render_pass_mark_box_updated(pass, rect); + instance_data[i * 4 + 0] = (float)(rect->x1 - box.x) / box.width; + instance_data[i * 4 + 1] = (float)(rect->y1 - box.y) / box.height; + instance_data[i * 4 + 2] = (float)(rect->x2 - rect->x1) / box.width; + instance_data[i * 4 + 3] = (float)(rect->y2 - rect->y1) / box.height; + } + struct wlr_vk_vert_pcr_data vert_pcr_data = { .uv_off = { 0, 0 }, .uv_size = { 1, 1 }, @@ -752,12 +721,9 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), sizeof(float) * 4, linear_color); - for (int i = 0; i < clip_rects_len; i++) { - VkRect2D rect; - convert_pixman_box_to_vk_rect(&clip_rects[i], &rect); - vkCmdSetScissor(cb, 0, 1, &rect); - vkCmdDraw(cb, 4, 1, 0, 0); - } + VkDeviceSize vb_offset = span.offset; + vkCmdBindVertexBuffers(cb, 0, 1, &span.buffer->buffer, &vb_offset); + vkCmdDraw(cb, 4, clip_rects_len, 0, 0); break; case WLR_RENDER_BLEND_MODE_NONE:; VkClearAttachment clear_att = { @@ -774,7 +740,9 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, .layerCount = 1, }; for (int i = 0; i < clip_rects_len; i++) { - convert_pixman_box_to_vk_rect(&clip_rects[i], &clear_rect.rect); + const pixman_box32_t *rect = &clip_rects[i]; + render_pass_mark_box_updated(pass, rect); + convert_pixman_box_to_vk_rect(rect, &clear_rect.rect); vkCmdClearAttachments(cb, 1, &clear_att, 1, &clear_rect); } break; @@ -816,6 +784,31 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, wlr_matrix_project_box(matrix, &dst_box, options->transform, proj); wlr_matrix_multiply(matrix, pass->projection, matrix); + pixman_region32_t clip; + if (options->clip) { + pixman_region32_init(&clip); + pixman_region32_intersect_rect(&clip, options->clip, + dst_box.x, dst_box.y, dst_box.width, dst_box.height); + } else { + pixman_region32_init_rect(&clip, + dst_box.x, dst_box.y, dst_box.width, dst_box.height); + } + int clip_rects_len; + const pixman_box32_t *clip_rects = pixman_region32_rectangles(&clip, &clip_rects_len); + if (clip_rects_len == 0) { + pixman_region32_fini(&clip); + return; + } + + const VkDeviceSize instance_size = 4 * sizeof(float); + struct wlr_vk_buffer_span span = vulkan_get_stage_span(renderer, + clip_rects_len * instance_size, 16); + if (!span.buffer) { + pixman_region32_fini(&clip); + pass->failed = true; + return; + } + struct wlr_vk_vert_pcr_data vert_pcr_data = { .uv_off = { src_box.x / options->texture->width, @@ -886,6 +879,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, WLR_RENDER_BLEND_MODE_NONE : options->blend_mode, }); if (!pipe) { + pixman_region32_fini(&clip); pass->failed = true; return; } @@ -893,6 +887,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, struct wlr_vk_texture_view *view = vulkan_texture_get_or_create_view(texture, pipe->layout, srgb_image_view); if (!view) { + pixman_region32_fini(&clip); pass->failed = true; return; } @@ -930,34 +925,35 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), sizeof(frag_pcr_data), &frag_pcr_data); - pixman_region32_t clip; - get_clip_region(pass, options->clip, &clip); - - int clip_rects_len; - const pixman_box32_t *clip_rects = pixman_region32_rectangles(&clip, &clip_rects_len); + float *instance_data = (float *)((char *)span.buffer->cpu_mapping + span.offset); for (int i = 0; i < clip_rects_len; i++) { - VkRect2D rect; - convert_pixman_box_to_vk_rect(&clip_rects[i], &rect); - vkCmdSetScissor(cb, 0, 1, &rect); - vkCmdDraw(cb, 4, 1, 0, 0); + const pixman_box32_t *rect = &clip_rects[i]; + render_pass_mark_box_updated(pass, rect); - struct wlr_box clip_box = { - .x = clip_rects[i].x1, - .y = clip_rects[i].y1, - .width = clip_rects[i].x2 - clip_rects[i].x1, - .height = clip_rects[i].y2 - clip_rects[i].y1, + struct wlr_fbox norm = { + .x = (double)(rect->x1 - dst_box.x) / dst_box.width, + .y = (double)(rect->y1 - dst_box.y) / dst_box.height, + .width = (double)(rect->x2 - rect->x1) / dst_box.width, + .height = (double)(rect->y2 - rect->y1) / dst_box.height, }; - struct wlr_box intersection; - if (!wlr_box_intersection(&intersection, &dst_box, &clip_box)) { - continue; + + if (options->transform != WL_OUTPUT_TRANSFORM_NORMAL) { + wlr_fbox_transform(&norm, &norm, options->transform, 1.0, 1.0); } - render_pass_mark_box_updated(pass, &intersection); + + instance_data[i * 4 + 0] = (float)norm.x; + instance_data[i * 4 + 1] = (float)norm.y; + instance_data[i * 4 + 2] = (float)norm.width; + instance_data[i * 4 + 3] = (float)norm.height; } + pixman_region32_fini(&clip); + + VkDeviceSize vb_offset = span.offset; + vkCmdBindVertexBuffers(cb, 0, 1, &span.buffer->buffer, &vb_offset); + vkCmdDraw(cb, 4, clip_rects_len, 0, 0); texture->last_used_cb = pass->command_buffer; - pixman_region32_fini(&clip); - if (texture->dmabuf_imported || (options != NULL && options->wait_timeline != NULL)) { struct wlr_vk_render_pass_texture *pass_texture = wl_array_add(&pass->textures, sizeof(*pass_texture)); @@ -1039,7 +1035,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, res = vkCreateImage(dev, &img_info, NULL, image); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImage failed", res); - return NULL; + return false; } VkMemoryRequirements mem_reqs = {0}; @@ -1096,13 +1092,13 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, size_t size = dim_len * dim_len * dim_len * bytes_per_block; struct wlr_vk_buffer_span span = vulkan_get_stage_span(renderer, size, bytes_per_block); - if (!span.buffer || span.alloc.size != size) { + if (!span.buffer || span.size != size) { wlr_log(WLR_ERROR, "Failed to retrieve staging buffer"); goto fail_imageview; } float sample_range = 1.0f / (dim_len - 1); - char *map = (char *)span.buffer->cpu_mapping + span.alloc.start; + char *map = (char *)span.buffer->cpu_mapping + span.offset; float *dst = (float *)map; for (size_t b_index = 0; b_index < dim_len; b_index++) { for (size_t g_index = 0; g_index < dim_len; g_index++) { @@ -1132,7 +1128,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); VkBufferImageCopy copy = { - .bufferOffset = span.alloc.start, + .bufferOffset = span.offset, .imageExtent.width = dim_len, .imageExtent.height = dim_len, .imageExtent.depth = dim_len, @@ -1302,7 +1298,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend struct wlr_vk_command_buffer *cb = vulkan_acquire_command_buffer(renderer); if (cb == NULL) { - free(pass); + render_pass_destroy(pass); return NULL; } @@ -1313,7 +1309,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend if (res != VK_SUCCESS) { wlr_vk_error("vkBeginCommandBuffer", res); vulkan_reset_command_buffer(cb); - free(pass); + render_pass_destroy(pass); return NULL; } @@ -1325,15 +1321,27 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT); } + struct wlr_vk_render_timer *timer = NULL; + if (options != NULL && options->timer != NULL) { + timer = wl_container_of(options->timer, timer, base); + vkCmdResetQueryPool(cb->vk, timer->query_pool, 0, 2); + vkCmdWriteTimestamp(cb->vk, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + timer->query_pool, 0); + } + int width = buffer->wlr_buffer->width; int height = buffer->wlr_buffer->height; VkRect2D rect = { .extent = { width, height } }; + bool blend_first_use = pass->two_pass && !buffer->two_pass.blend_transitioned; + VkClearValue clear_value = {0}; VkRenderPassBeginInfo rp_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderArea = rect, - .clearValueCount = 0, - .renderPass = render_setup->render_pass, + .clearValueCount = blend_first_use ? 1 : 0, + .pClearValues = blend_first_use ? &clear_value : NULL, + .renderPass = blend_first_use ? + render_setup->render_pass_clear : render_setup->render_pass, .framebuffer = buffer_out->framebuffer, }; vkCmdBeginRenderPass(cb->vk, &rp_info, VK_SUBPASS_CONTENTS_INLINE); @@ -1343,6 +1351,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend .height = height, .maxDepth = 1, }); + vkCmdSetScissor(cb->vk, 0, 1, &rect); // matrix_projection() assumes a GL coordinate system so we need // to pass WL_OUTPUT_TRANSFORM_FLIPPED_180 to adjust it for vulkan. @@ -1353,5 +1362,6 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend pass->render_buffer_out = buffer_out; pass->render_setup = render_setup; pass->command_buffer = cb; + pass->timer = timer; return pass; } diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index da17a4703..44f4d39d8 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -8,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -26,11 +26,9 @@ #include "render/vulkan/shaders/texture.frag.h" #include "render/vulkan/shaders/quad.frag.h" #include "render/vulkan/shaders/output.frag.h" -#include "types/wlr_buffer.h" -#include "util/time.h" +#include "util/array.h" // TODO: -// - simplify stage allocation, don't track allocations but use ringbuffer-like // - use a pipeline cache (not sure when to save though, after every pipeline // creation?) // - create pipelines as derivatives of each other @@ -46,7 +44,7 @@ static bool default_debug = true; static const struct wlr_renderer_impl renderer_impl; -bool wlr_renderer_is_vk(struct wlr_renderer *wlr_renderer) { +bool wlr_renderer_is_vk(const struct wlr_renderer *wlr_renderer) { return wlr_renderer->impl == &renderer_impl; } @@ -171,6 +169,7 @@ static void destroy_render_format_setup(struct wlr_vk_renderer *renderer, VkDevice dev = renderer->dev->dev; vkDestroyRenderPass(dev, setup->render_pass, NULL); + vkDestroyRenderPass(dev, setup->render_pass_clear, NULL); vkDestroyPipeline(dev, setup->output_pipe_identity, NULL); vkDestroyPipeline(dev, setup->output_pipe_srgb, NULL); vkDestroyPipeline(dev, setup->output_pipe_pq, NULL); @@ -187,18 +186,13 @@ static void destroy_render_format_setup(struct wlr_vk_renderer *renderer, free(setup); } -static void shared_buffer_destroy(struct wlr_vk_renderer *r, - struct wlr_vk_shared_buffer *buffer) { +static void stage_buffer_destroy(struct wlr_vk_renderer *r, + struct wlr_vk_stage_buffer *buffer) { if (!buffer) { return; } - if (buffer->allocs.size > 0) { - wlr_log(WLR_ERROR, "shared_buffer_finish: %zu allocations left", - buffer->allocs.size / sizeof(struct wlr_vk_allocation)); - } - - wl_array_release(&buffer->allocs); + wl_array_release(&buffer->watermarks); if (buffer->cpu_mapping) { vkUnmapMemory(r->dev->dev, buffer->memory); buffer->cpu_mapping = NULL; @@ -214,75 +208,12 @@ static void shared_buffer_destroy(struct wlr_vk_renderer *r, free(buffer); } -struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r, - VkDeviceSize size, VkDeviceSize alignment) { - // try to find free span - // simple greedy allocation algorithm - should be enough for this usecase - // since all allocations are freed together after the frame - struct wlr_vk_shared_buffer *buf; - wl_list_for_each_reverse(buf, &r->stage.buffers, link) { - VkDeviceSize start = 0u; - 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; - } - - struct wlr_vk_allocation *a = wl_array_add(&buf->allocs, sizeof(*a)); - if (a == NULL) { - wlr_log_errno(WLR_ERROR, "Allocation failed"); - goto error_alloc; - } - - *a = (struct wlr_vk_allocation){ - .start = start, - .size = size, - }; - return (struct wlr_vk_buffer_span) { - .buffer = buf, - .alloc = *a, - }; - } - - if (size > max_stage_size) { - wlr_log(WLR_ERROR, "cannot vulkan stage buffer: " - "requested size (%zu bytes) exceeds maximum (%zu bytes)", - (size_t)size, (size_t)max_stage_size); - 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; - } - - if (bsize > max_stage_size) { - wlr_log(WLR_INFO, "vulkan stage buffers have reached max size"); - bsize = max_stage_size; - } - - // create buffer - buf = calloc(1, sizeof(*buf)); +static struct wlr_vk_stage_buffer *stage_buffer_create( + struct wlr_vk_renderer *r, VkDeviceSize bsize) { + struct wlr_vk_stage_buffer *buf = calloc(1, sizeof(*buf)); if (!buf) { wlr_log_errno(WLR_ERROR, "Allocation failed"); - goto error_alloc; + return NULL; } wl_list_init(&buf->link); @@ -292,7 +223,8 @@ struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r, .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = bsize, .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; res = vkCreateBuffer(r->dev->dev, &buf_info, NULL, &buf->buffer); @@ -319,7 +251,7 @@ struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r, }; res = vkAllocateMemory(r->dev->dev, &mem_info, NULL, &buf->memory); if (res != VK_SUCCESS) { - wlr_vk_error("vkAllocatorMemory", res); + wlr_vk_error("vkAllocateMemory", res); goto error; } @@ -335,34 +267,162 @@ struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r, goto error; } - struct wlr_vk_allocation *a = wl_array_add(&buf->allocs, sizeof(*a)); - if (a == NULL) { - wlr_log_errno(WLR_ERROR, "Allocation failed"); + buf->buf_size = bsize; + return buf; + +error: + stage_buffer_destroy(r, buf); + return NULL; +} + +void vulkan_stage_buffer_reclaim(struct wlr_vk_stage_buffer *buf, + uint64_t current_point) { + + size_t completed = 0; + struct wlr_vk_stage_watermark *mark; + wl_array_for_each(mark, &buf->watermarks) { + if (mark->timeline_point > current_point) { + break; + } + buf->tail = mark->head; + completed++; + } + + if (completed > 0) { + completed *= sizeof(struct wlr_vk_stage_watermark); + if (completed == buf->watermarks.size) { + buf->watermarks.size = 0; + } else { + array_remove_at(&buf->watermarks, 0, completed); + } + } +} + +VkDeviceSize vulkan_stage_buffer_alloc(struct wlr_vk_stage_buffer *buf, + VkDeviceSize size, VkDeviceSize alignment) { + VkDeviceSize head = buf->head; + + // Round up to the next multiple of alignment + VkDeviceSize rem = head % alignment; + if (rem != 0) { + head += alignment - rem; + } + + VkDeviceSize end = head >= buf->tail ? buf->buf_size : buf->tail; + if (head + size < end) { + // Regular allocation head till end of available space + buf->head = head + size; + return head; + } else if (size < buf->tail && head >= buf->tail) { + // First allocation after wrap-around + buf->head = size; + return 0; + } + + return (VkDeviceSize)-1; +} + +struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r, + VkDeviceSize size, VkDeviceSize alignment) { + if (size >= max_stage_size) { + wlr_log(WLR_ERROR, "cannot allocate stage buffer: " + "requested size (%zu bytes) exceeds maximum (%zu bytes)", + (size_t)size, (size_t)max_stage_size-1); goto error; } - buf->buf_size = bsize; - wl_list_insert(&r->stage.buffers, &buf->link); + VkDeviceSize max_buf_size = min_stage_size / 2; + struct wlr_vk_stage_buffer *buf; + wl_list_for_each(buf, &r->stage.buffers, link) { + VkDeviceSize offset = vulkan_stage_buffer_alloc(buf, size, alignment); + if (offset != (VkDeviceSize)-1) { + return (struct wlr_vk_buffer_span) { + .buffer = buf, + .offset = offset, + .size = size, + }; + } + if (buf->buf_size > max_buf_size) { + max_buf_size = buf->buf_size; + } + } + + VkDeviceSize bsize = max_buf_size * 2; + while (size * 2 > bsize) { + bsize *= 2; + } + if (bsize > max_stage_size) { + wlr_log(WLR_INFO, "vulkan stage buffer has reached max size"); + bsize = max_stage_size; + } + + struct wlr_vk_stage_buffer *new_buf = stage_buffer_create(r, bsize); + if (new_buf == NULL) { + goto error; + } + + wl_list_insert(r->stage.buffers.prev, &new_buf->link); + + VkDeviceSize offset = vulkan_stage_buffer_alloc(new_buf, size, alignment); + assert(offset != (VkDeviceSize)-1); - *a = (struct wlr_vk_allocation){ - .start = 0, - .size = size, - }; return (struct wlr_vk_buffer_span) { - .buffer = buf, - .alloc = *a, + .buffer = new_buf, + .offset = offset, + .size = size, }; error: - shared_buffer_destroy(r, buf); - -error_alloc: return (struct wlr_vk_buffer_span) { .buffer = NULL, - .alloc = (struct wlr_vk_allocation) {0, 0}, + .offset = 0, + .size = 0, }; } +void vulkan_stage_mark_submit(struct wlr_vk_renderer *renderer, + uint64_t timeline_point) { + struct wlr_vk_stage_buffer *buf; + wl_list_for_each(buf, &renderer->stage.buffers, link) { + if (buf->head == buf->tail) { + continue; + } + + struct wlr_vk_stage_watermark *mark = wl_array_add( + &buf->watermarks, sizeof(*mark)); + if (mark == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + continue; + } + + *mark = (struct wlr_vk_stage_watermark){ + .head = buf->head, + .timeline_point = timeline_point, + }; + } +} + +static void stage_buffer_gc(struct wlr_vk_renderer *renderer, uint64_t current_point) { + struct wlr_vk_stage_buffer *buf, *buf_tmp; + wl_list_for_each_safe(buf, buf_tmp, &renderer->stage.buffers, link) { + if (buf->head != buf->tail) { + buf->empty_gc_cnt = 0; + vulkan_stage_buffer_reclaim(buf, current_point); + continue; + } + if (buf->buf_size <= min_stage_size) { + // We will not deallocate the first buffer + continue; + } + + buf->empty_gc_cnt++; + if (buf->empty_gc_cnt >= 1000) { + // This buffer hasn't been used for a while, so let's deallocate it + stage_buffer_destroy(renderer, buf); + } + } +} + VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer) { if (renderer->stage.cb == NULL) { renderer->stage.cb = vulkan_acquire_command_buffer(renderer); @@ -379,7 +439,52 @@ VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer) { return renderer->stage.cb->vk; } -bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer) { +VkSemaphore vulkan_command_buffer_wait_sync_file(struct wlr_vk_renderer *renderer, + struct wlr_vk_command_buffer *render_cb, size_t sem_index, int sync_file_fd) { + VkResult res; + + VkSemaphore *wait_semaphores = render_cb->wait_semaphores.data; + size_t wait_semaphores_len = render_cb->wait_semaphores.size / sizeof(wait_semaphores[0]); + + VkSemaphore *sem_ptr; + if (sem_index >= wait_semaphores_len) { + sem_ptr = wl_array_add(&render_cb->wait_semaphores, sizeof(*sem_ptr)); + if (sem_ptr == NULL) { + return VK_NULL_HANDLE; + } + *sem_ptr = VK_NULL_HANDLE; + } else { + sem_ptr = &wait_semaphores[sem_index]; + } + + if (*sem_ptr == VK_NULL_HANDLE) { + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + res = vkCreateSemaphore(renderer->dev->dev, &semaphore_info, NULL, sem_ptr); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateSemaphore", res); + return VK_NULL_HANDLE; + } + } + + VkImportSemaphoreFdInfoKHR import_info = { + .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT, + .semaphore = *sem_ptr, + .fd = sync_file_fd, + }; + res = renderer->dev->api.vkImportSemaphoreFdKHR(renderer->dev->dev, &import_info); + if (res != VK_SUCCESS) { + wlr_vk_error("vkImportSemaphoreFdKHR", res); + return VK_NULL_HANDLE; + } + + return *sem_ptr; +} + +bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer, int wait_sync_file_fd) { if (renderer->stage.cb == NULL) { return false; } @@ -392,6 +497,8 @@ bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer) { return false; } + VkSemaphore wait_semaphore; + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; VkTimelineSemaphoreSubmitInfoKHR timeline_submit_info = { .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR, .signalSemaphoreValueCount = 1, @@ -405,16 +512,32 @@ bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer) { .signalSemaphoreCount = 1, .pSignalSemaphores = &renderer->timeline_semaphore, }; + + if (wait_sync_file_fd != -1) { + wait_semaphore = vulkan_command_buffer_wait_sync_file(renderer, cb, 0, wait_sync_file_fd); + if (wait_semaphore == VK_NULL_HANDLE) { + return false; + } + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &wait_semaphore; + submit_info.pWaitDstStageMask = &wait_stage; + } + + vulkan_stage_mark_submit(renderer, timeline_point); + VkResult res = vkQueueSubmit(renderer->dev->queue, 1, &submit_info, VK_NULL_HANDLE); if (res != VK_SUCCESS) { wlr_vk_error("vkQueueSubmit", res); return false; } - // NOTE: don't release stage allocations here since they may still be - // used for reading. Will be done next frame. + if (!vulkan_wait_command_buffer(cb, renderer)) { + return false; + } - return vulkan_wait_command_buffer(cb, renderer); + // We did a blocking wait so this is now the current point + stage_buffer_gc(renderer, timeline_point); + return true; } struct wlr_vk_format_props *vulkan_format_props_from_drm( @@ -448,7 +571,6 @@ static bool init_command_buffer(struct wlr_vk_command_buffer *cb, .vk = vk_cb, }; wl_list_init(&cb->destroy_textures); - wl_list_init(&cb->stage_buffers); return true; } @@ -474,7 +596,7 @@ bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, } static void release_command_buffer_resources(struct wlr_vk_command_buffer *cb, - struct wlr_vk_renderer *renderer, int64_t now) { + struct wlr_vk_renderer *renderer) { struct wlr_vk_texture *texture, *texture_tmp; wl_list_for_each_safe(texture, texture_tmp, &cb->destroy_textures, destroy_link) { wl_list_remove(&texture->destroy_link); @@ -482,15 +604,6 @@ static void release_command_buffer_resources(struct wlr_vk_command_buffer *cb, wlr_texture_destroy(&texture->wlr_texture); } - struct wlr_vk_shared_buffer *buf, *buf_tmp; - wl_list_for_each_safe(buf, buf_tmp, &cb->stage_buffers, link) { - buf->allocs.size = 0; - buf->last_used_ms = now; - - wl_list_remove(&buf->link); - wl_list_insert(&renderer->stage.buffers, &buf->link); - } - if (cb->color_transform) { wlr_color_transform_unref(cb->color_transform); cb->color_transform = NULL; @@ -509,22 +622,14 @@ static struct wlr_vk_command_buffer *get_command_buffer( return NULL; } - - // Garbage collect any buffers that have remained unused for too long - int64_t now = get_current_time_msec(); - struct wlr_vk_shared_buffer *buf, *buf_tmp; - wl_list_for_each_safe(buf, buf_tmp, &renderer->stage.buffers, link) { - if (buf->allocs.size == 0 && buf->last_used_ms + 10000 < now) { - shared_buffer_destroy(renderer, buf); - } - } + stage_buffer_gc(renderer, current_point); // Destroy textures for completed command buffers for (size_t i = 0; i < VULKAN_COMMAND_BUFFERS_CAP; i++) { struct wlr_vk_command_buffer *cb = &renderer->command_buffers[i]; if (cb->vk != VK_NULL_HANDLE && !cb->recording && cb->timeline_point <= current_point) { - release_command_buffer_resources(cb, renderer, now); + release_command_buffer_resources(cb, renderer); } } @@ -1127,7 +1232,7 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) { if (cb->vk == VK_NULL_HANDLE) { continue; } - release_command_buffer_resources(cb, renderer, 0); + release_command_buffer_resources(cb, renderer); if (cb->binary_semaphore != VK_NULL_HANDLE) { vkDestroySemaphore(renderer->dev->dev, cb->binary_semaphore, NULL); } @@ -1139,9 +1244,9 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) { } // stage.cb automatically freed with command pool - struct wlr_vk_shared_buffer *buf, *tmp_buf; + struct wlr_vk_stage_buffer *buf, *tmp_buf; wl_list_for_each_safe(buf, tmp_buf, &renderer->stage.buffers, link) { - shared_buffer_destroy(renderer, buf); + stage_buffer_destroy(renderer, buf); } struct wlr_vk_texture *tex, *tex_tmp; @@ -1188,7 +1293,7 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) { vkDestroyPipelineLayout(dev->dev, pipeline_layout->vk, NULL); vkDestroyDescriptorSetLayout(dev->dev, pipeline_layout->ds, NULL); vkDestroySampler(dev->dev, pipeline_layout->sampler, NULL); - vkDestroySamplerYcbcrConversion(dev->dev, pipeline_layout->ycbcr.conversion, NULL); + renderer->dev->api.vkDestroySamplerYcbcrConversionKHR(dev->dev, pipeline_layout->ycbcr.conversion, NULL); free(pipeline_layout); } @@ -1218,7 +1323,8 @@ bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, VkFormat src_format, VkImage src_image, uint32_t drm_format, uint32_t stride, 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, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point) { VkDevice dev = vk_renderer->dev->dev; const struct wlr_pixel_format_info *pixel_format_info = drm_get_pixel_format_info(drm_format); @@ -1404,7 +1510,17 @@ bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_MEMORY_READ_BIT); - if (!vulkan_submit_stage_wait(vk_renderer)) { + int wait_sync_file_fd = -1; + if (wait_timeline != NULL) { + wait_sync_file_fd = wlr_drm_syncobj_timeline_export_sync_file(wait_timeline, wait_point); + if (wait_sync_file_fd < 0) { + wlr_log(WLR_ERROR, "Failed to export wait timeline point as sync_file"); + return false; + } + } + + if (!vulkan_submit_stage_wait(vk_renderer, wait_sync_file_fd)) { + close(wait_sync_file_fd); return false; } @@ -1485,6 +1601,80 @@ static struct wlr_render_pass *vulkan_begin_buffer_pass(struct wlr_renderer *wlr return &render_pass->base; } +static const struct wlr_render_timer_impl render_timer_impl; + +static struct wlr_render_timer *vulkan_render_timer_create( + struct wlr_renderer *wlr_renderer) { + struct wlr_vk_renderer *renderer = vulkan_get_renderer(wlr_renderer); + if (renderer->dev->timestamp_valid_bits == 0) { + wlr_log(WLR_ERROR, "Failed to create render timer: " + "timestamp queries not supported by queue family"); + return NULL; + } + + struct wlr_vk_render_timer *timer = calloc(1, sizeof(*timer)); + if (!timer) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + VkQueryPoolCreateInfo pool_info = { + .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, + .queryType = VK_QUERY_TYPE_TIMESTAMP, + .queryCount = 2, + }; + VkResult res = vkCreateQueryPool(renderer->dev->dev, &pool_info, + NULL, &timer->query_pool); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateQueryPool", res); + free(timer); + return NULL; + } + + timer->base.impl = &render_timer_impl; + timer->renderer = renderer; + return &timer->base; +} + +static int vulkan_render_timer_get_duration_ns( + struct wlr_render_timer *wlr_timer) { + struct wlr_vk_render_timer *timer = + wl_container_of(wlr_timer, timer, base); + struct wlr_vk_renderer *renderer = timer->renderer; + + // Layout: [ timestamp1, avail1, timestamp2, avail2 ] + uint64_t data[4] = {0}; + VkResult res = vkGetQueryPoolResults(renderer->dev->dev, + timer->query_pool, 0, 2, sizeof(data), data, + 2 * sizeof(uint64_t), + VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT); + if (res == VK_NOT_READY || data[1] == 0 || data[3] == 0) { + wlr_log(WLR_ERROR, "Failed to get render duration: " + "timestamp query results not yet ready"); + return -1; + } + if (res != VK_SUCCESS) { + wlr_vk_error("vkGetQueryPoolResults", res); + return -1; + } + + uint64_t ticks = data[2] - data[0]; + return (int)(ticks * renderer->dev->timestamp_period); +} + +static void vulkan_render_timer_destroy( + struct wlr_render_timer *wlr_timer) { + struct wlr_vk_render_timer *timer = + wl_container_of(wlr_timer, timer, base); + vkDestroyQueryPool(timer->renderer->dev->dev, timer->query_pool, NULL); + free(timer); +} + +static const struct wlr_render_timer_impl render_timer_impl = { + .get_duration_ns = vulkan_render_timer_get_duration_ns, + .destroy = vulkan_render_timer_destroy, +}; + static const struct wlr_renderer_impl renderer_impl = { .get_texture_formats = vulkan_get_texture_formats, .get_render_formats = vulkan_get_render_formats, @@ -1492,6 +1682,7 @@ static const struct wlr_renderer_impl renderer_impl = { .get_drm_fd = vulkan_get_drm_fd, .texture_from_buffer = vulkan_texture_from_buffer, .begin_buffer_pass = vulkan_begin_buffer_pass, + .render_timer_create = vulkan_render_timer_create, }; // Initializes the VkDescriptorSetLayout and VkPipelineLayout needed @@ -1692,6 +1883,25 @@ static bool pipeline_key_equals(const struct wlr_vk_pipeline_key *a, return true; } +static const VkVertexInputBindingDescription instance_vert_binding = { + .binding = 0, + .stride = sizeof(float) * 4, + .inputRate = VK_VERTEX_INPUT_RATE_INSTANCE, +}; +static const VkVertexInputAttributeDescription instance_vert_attr = { + .location = 0, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = 0, +}; +static const VkPipelineVertexInputStateCreateInfo instance_vert_input = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &instance_vert_binding, + .vertexAttributeDescriptionCount = 1, + .pVertexAttributeDescriptions = &instance_vert_attr, +}; + // Initializes the pipeline for rendering textures and using the given // VkRenderPass and VkPipelineLayout. struct wlr_vk_pipeline *setup_get_or_create_pipeline( @@ -1823,10 +2033,6 @@ struct wlr_vk_pipeline *setup_get_or_create_pipeline( .dynamicStateCount = sizeof(dyn_states) / sizeof(dyn_states[0]), }; - VkPipelineVertexInputStateCreateInfo vertex = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - }; - VkGraphicsPipelineCreateInfo pinfo = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .layout = pipeline_layout->vk, @@ -1841,7 +2047,7 @@ struct wlr_vk_pipeline *setup_get_or_create_pipeline( .pMultisampleState = &multisample, .pViewportState = &viewport, .pDynamicState = &dynamic, - .pVertexInputState = &vertex, + .pVertexInputState = &instance_vert_input, }; VkPipelineCache cache = VK_NULL_HANDLE; @@ -1940,10 +2146,6 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, .dynamicStateCount = sizeof(dyn_states) / sizeof(dyn_states[0]), }; - VkPipelineVertexInputStateCreateInfo vertex = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - }; - VkGraphicsPipelineCreateInfo pinfo = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = NULL, @@ -1958,7 +2160,7 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, .pMultisampleState = &multisample, .pViewportState = &viewport, .pDynamicState = &dynamic, - .pVertexInputState = &vertex, + .pVertexInputState = &instance_vert_input, }; VkPipelineCache cache = VK_NULL_HANDLE; @@ -2049,10 +2251,10 @@ struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout( .yChromaOffset = VK_CHROMA_LOCATION_MIDPOINT, .chromaFilter = VK_FILTER_LINEAR, }; - res = vkCreateSamplerYcbcrConversion(renderer->dev->dev, + res = renderer->dev->api.vkCreateSamplerYcbcrConversionKHR(renderer->dev->dev, &conversion_create_info, NULL, &pipeline_layout->ycbcr.conversion); if (res != VK_SUCCESS) { - wlr_vk_error("vkCreateSamplerYcbcrConversion", res); + wlr_vk_error("vkCreateSamplerYcbcrConversionKHR", res); free(pipeline_layout); return NULL; } @@ -2384,6 +2586,14 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( goto error; } + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + res = vkCreateRenderPass(dev, &rp_info, NULL, &setup->render_pass_clear); + if (res != VK_SUCCESS) { + wlr_vk_error("Failed to create 2-step clear render pass", res); + goto error; + } + // this is only well defined if render pass has a 2nd subpass if (!init_blend_to_output_pipeline( renderer, setup->render_pass, renderer->output_pipe_layout, diff --git a/render/vulkan/shaders/common.vert b/render/vulkan/shaders/common.vert index f1579790d..82ea9658c 100644 --- a/render/vulkan/shaders/common.vert +++ b/render/vulkan/shaders/common.vert @@ -8,11 +8,14 @@ layout(push_constant, row_major) uniform UBO { vec2 uv_size; } data; +layout(location = 0) in vec4 inst_rect; + layout(location = 0) out vec2 uv; void main() { vec2 pos = vec2(float((gl_VertexIndex + 1) & 2) * 0.5f, float(gl_VertexIndex & 2) * 0.5f); + pos = inst_rect.xy + pos * inst_rect.zw; uv = data.uv_offset + pos * data.uv_size; gl_Position = data.proj * vec4(pos, 0.0, 1.0); } diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index 57db97a56..deff70f11 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -15,7 +15,7 @@ static const struct wlr_texture_impl texture_impl; -bool wlr_texture_is_vk(struct wlr_texture *wlr_texture) { +bool wlr_texture_is_vk(const struct wlr_texture *wlr_texture) { return wlr_texture->impl == &texture_impl; } @@ -72,16 +72,16 @@ static bool write_pixels(struct wlr_vk_texture *texture, // get staging buffer struct wlr_vk_buffer_span span = vulkan_get_stage_span(renderer, bsize, format_info->bytes_per_block); - if (!span.buffer || span.alloc.size != bsize) { + if (!span.buffer || span.size != bsize) { wlr_log(WLR_ERROR, "Failed to retrieve staging buffer"); free(copies); return false; } - char *map = (char*)span.buffer->cpu_mapping + span.alloc.start; + char *map = (char*)span.buffer->cpu_mapping + span.offset; // upload data - uint32_t buf_off = span.alloc.start; + uint32_t buf_off = span.offset; for (int i = 0; i < rects_len; i++) { pixman_box32_t rect = rects[i]; uint32_t width = rect.x2 - rect.x1; @@ -238,7 +238,8 @@ static bool vulkan_texture_read_pixels(struct wlr_texture *wlr_texture, void *p = wlr_texture_read_pixel_options_get_data(options); return vulkan_read_pixels(texture->renderer, texture->format->vk, texture->image, - options->format, options->stride, src.width, src.height, src.x, src.y, 0, 0, p); + options->format, options->stride, src.width, src.height, src.x, src.y, 0, 0, p, + options->wait_timeline, options->wait_point); } static uint32_t vulkan_texture_preferred_read_format(struct wlr_texture *wlr_texture) { @@ -329,6 +330,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text view->ds_pool = vulkan_alloc_texture_ds(texture->renderer, pipeline_layout->ds, &view->ds); if (!view->ds_pool) { + vkDestroyImageView(dev, view->image_view, NULL); free(view); wlr_log(WLR_ERROR, "failed to allocate descriptor"); return NULL; @@ -653,7 +655,7 @@ VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, }; - vkGetImageMemoryRequirements2(dev, &memri, &memr); + renderer->dev->api.vkGetImageMemoryRequirements2KHR(dev, &memri, &memr); int mem = vulkan_find_mem_type(renderer->dev, 0, memr.memoryRequirements.memoryTypeBits & fdp.memoryTypeBits); if (mem < 0) { @@ -712,7 +714,7 @@ VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, } } - res = vkBindImageMemory2(dev, mem_count, bindi); + res = renderer->dev->api.vkBindImageMemory2KHR(dev, mem_count, bindi); if (res != VK_SUCCESS) { wlr_vk_error("vkBindMemory failed", res); goto error_image; diff --git a/render/vulkan/vulkan.c b/render/vulkan/vulkan.c index 0c0445d80..520a678b1 100644 --- a/render/vulkan/vulkan.c +++ b/render/vulkan/vulkan.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -81,21 +82,6 @@ static VKAPI_ATTR VkBool32 debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT } struct wlr_vk_instance *vulkan_instance_create(bool debug) { - // we require vulkan 1.1 - PFN_vkEnumerateInstanceVersion pfEnumInstanceVersion = - (PFN_vkEnumerateInstanceVersion) - vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceVersion"); - if (!pfEnumInstanceVersion) { - wlr_log(WLR_ERROR, "wlroots requires vulkan 1.1 which is not available"); - return NULL; - } - - uint32_t ini_version; - if (pfEnumInstanceVersion(&ini_version) != VK_SUCCESS || - ini_version < VK_API_VERSION_1_1) { - wlr_log(WLR_ERROR, "wlroots requires vulkan 1.1 which is not available"); - return NULL; - } uint32_t avail_extc = 0; VkResult res; @@ -125,7 +111,18 @@ struct wlr_vk_instance *vulkan_instance_create(bool debug) { } size_t extensions_len = 0; - const char *extensions[1] = {0}; + const char *extensions[8] = {0}; + extensions[extensions_len++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; + extensions[extensions_len++] = VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME; + extensions[extensions_len++] = VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME; + + for (size_t i = 0; i < extensions_len; i++) { + if (!check_extension(avail_ext_props, avail_extc, extensions[i])) { + wlr_log(WLR_ERROR, "vulkan: required instance extension %s not found", + extensions[i]); + goto error; + } + } bool debug_utils_found = false; if (debug && check_extension(avail_ext_props, avail_extc, @@ -140,7 +137,7 @@ struct wlr_vk_instance *vulkan_instance_create(bool debug) { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pEngineName = "wlroots", .engineVersion = WLR_VERSION_NUM, - .apiVersion = VK_API_VERSION_1_1, + .apiVersion = VK_API_VERSION_1_0, }; VkInstanceCreateInfo instance_info = { @@ -256,6 +253,14 @@ static void log_phdev(const VkPhysicalDeviceProperties *props) { wlr_log(WLR_INFO, " Driver version: %u.%u.%u", dv_major, dv_minor, dv_patch); } +struct wlr_vk_phdev_info { + VkPhysicalDeviceType type; + char name[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE]; + + bool has_drm, has_render; + dev_t primary_devid, render_devid; +}; + VkPhysicalDevice vulkan_find_drm_phdev(struct wlr_vk_instance *ini, int drm_fd) { VkResult res; uint32_t num_phdevs; @@ -273,29 +278,14 @@ VkPhysicalDevice vulkan_find_drm_phdev(struct wlr_vk_instance *ini, int drm_fd) return VK_NULL_HANDLE; } - struct stat drm_stat = {0}; - if (drm_fd >= 0 && fstat(drm_fd, &drm_stat) != 0) { - wlr_log_errno(WLR_ERROR, "fstat failed"); - return VK_NULL_HANDLE; - } - + struct wlr_vk_phdev_info devices[1 + num_phdevs]; for (uint32_t i = 0; i < num_phdevs; ++i) { VkPhysicalDevice phdev = phdevs[i]; - // check whether device supports vulkan 1.1, needed for - // vkGetPhysicalDeviceProperties2 VkPhysicalDeviceProperties phdev_props; vkGetPhysicalDeviceProperties(phdev, &phdev_props); - log_phdev(&phdev_props); - if (phdev_props.apiVersion < VK_API_VERSION_1_1) { - // NOTE: we could additionally check whether the - // VkPhysicalDeviceProperties2KHR extension is supported but - // implementations not supporting 1.1 are unlikely in future - continue; - } - // check for extensions uint32_t avail_extc = 0; res = vkEnumerateDeviceExtensionProperties(phdev, NULL, @@ -344,25 +334,81 @@ VkPhysicalDevice vulkan_find_drm_phdev(struct wlr_vk_instance *ini, int drm_fd) wlr_log(WLR_INFO, " Driver name: %s (%s)", driver_props.driverName, driver_props.driverInfo); } + struct wlr_vk_phdev_info info = { + .type = phdev_props.deviceType, + .has_drm = has_drm_props, + }; + memcpy(info.name, phdev_props.deviceName, sizeof(phdev_props.deviceName)); + if (has_drm_props) { + info.has_render = drm_props.hasRender; + info.primary_devid = makedev(drm_props.primaryMajor, drm_props.primaryMinor); + info.render_devid = makedev(drm_props.renderMajor, drm_props.renderMinor); + } + devices[i] = info; + } + + // Find a Vulkan device matching the DRM device's dev_t + struct stat drm_stat = {0}; + if (drm_fd >= 0 && fstat(drm_fd, &drm_stat) != 0) { + wlr_log_errno(WLR_ERROR, "fstat failed"); + return VK_NULL_HANDLE; + } + + for (size_t i = 0; i < num_phdevs; ++i) { + VkPhysicalDevice phdev = phdevs[i]; + const struct wlr_vk_phdev_info *info = &devices[i]; + bool found; if (drm_fd >= 0) { - if (!has_drm_props) { + if (!info->has_drm) { wlr_log(WLR_DEBUG, " Ignoring physical device \"%s\": " - "VK_EXT_physical_device_drm not supported", - phdev_props.deviceName); + "VK_EXT_physical_device_drm not supported", info->name); continue; } - dev_t primary_devid = makedev(drm_props.primaryMajor, drm_props.primaryMinor); - dev_t render_devid = makedev(drm_props.renderMajor, drm_props.renderMinor); - found = primary_devid == drm_stat.st_rdev || render_devid == drm_stat.st_rdev; + found = info->primary_devid == drm_stat.st_rdev || info->render_devid == drm_stat.st_rdev; } else { - found = phdev_props.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU; + found = info->type == VK_PHYSICAL_DEVICE_TYPE_CPU; } if (found) { - wlr_log(WLR_INFO, "Found matching Vulkan physical device: %s", - phdev_props.deviceName); + wlr_log(WLR_INFO, "Found matching Vulkan physical device: %s", info->name); + return phdev; + } + } + + // If the DRM device we're interested in uses the platform bus, is + // KMS-capable, and has no render node associated, search for a sibling + // render-capable platform bus device. The platform bus is a good enough + // hint that these two devices should be compatible. + drmDevice *drm_dev = NULL; + if (drmGetDevice2(drm_fd, 0, &drm_dev) != 0) { + wlr_log(WLR_ERROR, "drmGetDevice2() failed"); + return VK_NULL_HANDLE; + } + bool is_platform_lone_kms = drm_dev->bustype == DRM_BUS_PLATFORM && + !(drm_dev->available_nodes & (1 << DRM_NODE_RENDER)) && drmIsKMS(drm_fd); + drmFreeDevice(&drm_dev); + if (!is_platform_lone_kms) { + return VK_NULL_HANDLE; + } + + for (size_t i = 0; i < num_phdevs; ++i) { + VkPhysicalDevice phdev = phdevs[i]; + const struct wlr_vk_phdev_info *info = &devices[i]; + + if (!info->has_render) { + continue; + } + + drmDevice *drm_dev = NULL; + if (drmGetDeviceFromDevId(info->render_devid, 0, &drm_dev) != 0) { + wlr_log(WLR_ERROR, "drmGetDeviceFromDevId() failed"); + return VK_NULL_HANDLE; + } + + if (drm_dev->bustype == DRM_BUS_PLATFORM) { + wlr_log(WLR_INFO, "Found Vulkan physical device on platform bus: %s", info->name); return phdev; } } @@ -474,6 +520,12 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, extensions[extensions_len++] = VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME; extensions[extensions_len++] = VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME; // or vulkan 1.2 extensions[extensions_len++] = VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME; // or vulkan 1.3 + extensions[extensions_len++] = VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME; // or vulkan 1.1 + extensions[extensions_len++] = VK_KHR_BIND_MEMORY_2_EXTENSION_NAME; // or vulkan 1.1 + extensions[extensions_len++] = VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME; // or vulkan 1.1 + extensions[extensions_len++] = VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME; // or vulkan 1.1 + extensions[extensions_len++] = VK_KHR_MAINTENANCE_1_EXTENSION_NAME; // or vulkan 1.1 + extensions[extensions_len++] = VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME; // or vulkan 1.1 for (size_t i = 0; i < extensions_len; i++) { if (!check_extension(avail_ext_props, avail_extc, extensions[i])) { @@ -496,10 +548,15 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, graphics_found = queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT; if (graphics_found) { dev->queue_family = i; + dev->timestamp_valid_bits = queue_props[i].timestampValidBits; break; } } assert(graphics_found); + + VkPhysicalDeviceProperties phdev_props; + vkGetPhysicalDeviceProperties(phdev, &phdev_props); + dev->timestamp_period = phdev_props.limits.timestampPeriod; } bool exportable_semaphore = false, importable_semaphore = false; @@ -630,6 +687,10 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, load_device_proc(dev, "vkGetSemaphoreCounterValueKHR", &dev->api.vkGetSemaphoreCounterValueKHR); load_device_proc(dev, "vkQueueSubmit2KHR", &dev->api.vkQueueSubmit2KHR); + load_device_proc(dev, "vkBindImageMemory2KHR", &dev->api.vkBindImageMemory2KHR); + load_device_proc(dev, "vkCreateSamplerYcbcrConversionKHR", &dev->api.vkCreateSamplerYcbcrConversionKHR); + load_device_proc(dev, "vkDestroySamplerYcbcrConversionKHR", &dev->api.vkDestroySamplerYcbcrConversionKHR); + load_device_proc(dev, "vkGetImageMemoryRequirements2KHR", &dev->api.vkGetImageMemoryRequirements2KHR); if (has_external_semaphore_fd) { load_device_proc(dev, "vkGetSemaphoreFdKHR", &dev->api.vkGetSemaphoreFdKHR); diff --git a/test/bench_scene.c b/test/bench_scene.c new file mode 100644 index 000000000..6d6b270f0 --- /dev/null +++ b/test/bench_scene.c @@ -0,0 +1,154 @@ +#include +#include +#include + +struct tree_spec { + // Parameters for the tree we'll construct + int depth; + int branching; + int rect_size; + int spread; + + // Stats around the tree we built + int tree_count; + int rect_count; + int max_x; + int max_y; +}; + +static int max(int a, int b) { + return a > b ? a : b; +} + +static double timespec_diff_msec(struct timespec *start, struct timespec *end) { + return (double)(end->tv_sec - start->tv_sec) * 1e3 + + (double)(end->tv_nsec - start->tv_nsec) / 1e6; +} + +static bool build_tree(struct wlr_scene_tree *parent, struct tree_spec *spec, + int depth, int x, int y) { + + if (depth == spec->depth) { + float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + struct wlr_scene_rect *rect = + wlr_scene_rect_create(parent, spec->rect_size, spec->rect_size, color); + if (rect == NULL) { + fprintf(stderr, "wlr_scene_rect_create failed\n"); + return false; + } + wlr_scene_node_set_position(&rect->node, x, y); + spec->max_x = max(spec->max_x, x + spec->rect_size); + spec->max_y = max(spec->max_y, y + spec->rect_size); + spec->rect_count++; + return true; + } + + for (int i = 0; i < spec->branching; i++) { + struct wlr_scene_tree *child = wlr_scene_tree_create(parent); + if (child == NULL) { + fprintf(stderr, "wlr_scene_tree_create failed\n"); + return false; + } + spec->tree_count++; + int offset = i * spec->spread; + wlr_scene_node_set_position(&child->node, offset, offset); + if (!build_tree(child, spec, depth + 1, x + offset, y + offset)) { + return false; + } + } + return true; +} + +static bool bench_create_tree(struct wlr_scene *scene, struct tree_spec *spec) { + struct timespec start, end; + clock_gettime(CLOCK_MONOTONIC, &start); + if (!build_tree(&scene->tree, spec, 0, 0, 0)) { + fprintf(stderr, "build_tree failed\n"); + return false; + } + clock_gettime(CLOCK_MONOTONIC, &end); + + printf("Built tree with %d tree nodes, %d rect nodes\n\n", + spec->tree_count, spec->rect_count); + + double elapsed = timespec_diff_msec(&start, &end); + int nodes = spec->tree_count + spec->rect_count; + printf("create test tree: %d nodes, %.3f ms, %.0f nodes/ms\n", + nodes, elapsed, nodes / elapsed); + return true; +} + +static void bench_scene_node_at(struct wlr_scene *scene, struct tree_spec *spec) { + struct timespec start, end; + int iters = 10000; + int hits = 0; + + clock_gettime(CLOCK_MONOTONIC, &start); + for (int i = 0; i < iters; i++) { + // Spread lookups across the tree extent + double lx = (double)(i * 97 % spec->max_x); + double ly = (double)(i * 53 % spec->max_y); + double nx, ny; + struct wlr_scene_node *node = + wlr_scene_node_at(&scene->tree.node, lx, ly, &nx, &ny); + if (node != NULL) { + hits++; + } + } + clock_gettime(CLOCK_MONOTONIC, &end); + + double elapsed = timespec_diff_msec(&start, &end); + int nodes = (spec->tree_count + spec->rect_count) * iters; + printf("wlr_scene_node_at: %d iters, %.3f ms, %.0f nodes/ms (hits: %d/%d)\n", + iters, elapsed, nodes / elapsed, hits, iters); +} + +static void noop_iterator(struct wlr_scene_buffer *buffer, + int sx, int sy, void *user_data) { + (void)buffer; + (void)sx; + (void)sy; + int *cnt = user_data; + (*cnt)++; +} + +static void bench_scene_node_for_each_buffer(struct wlr_scene *scene, struct tree_spec *spec) { + struct timespec start, end; + int iters = 10000; + int hits = 0; + + clock_gettime(CLOCK_MONOTONIC, &start); + for (int i = 0; i < iters; i++) { + wlr_scene_node_for_each_buffer(&scene->tree.node, + noop_iterator, &hits); + } + clock_gettime(CLOCK_MONOTONIC, &end); + + double elapsed = timespec_diff_msec(&start, &end); + int nodes = (spec->tree_count + spec->rect_count) * iters; + printf("wlr_scene_node_for_each_buffer: %d iters, %.3f ms, %.0f nodes/ms (hits: %d/%d)\n", + iters, elapsed, nodes / elapsed, hits, iters); +} + +int main(void) { + struct wlr_scene *scene = wlr_scene_create(); + if (scene == NULL) { + fprintf(stderr, "wlr_scene_create failed\n"); + return 99; + } + + struct tree_spec spec = { + .depth = 5, + .branching = 5, + .rect_size = 10, + .spread = 100, + }; + if (!bench_create_tree(scene, &spec)) { + return 99; + } + bench_scene_node_at(scene, &spec); + bench_scene_node_for_each_buffer(scene, &spec); + + wlr_scene_node_destroy(&scene->tree.node); + return 0; +} diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 000000000..9c622e3ef --- /dev/null +++ b/test/meson.build @@ -0,0 +1,32 @@ +# Used to test internal symbols +lib_wlr_internal = static_library( + versioned_name + '-internal', + objects: lib_wlr.extract_all_objects(recursive: false), + dependencies: wlr_deps, + include_directories: [wlr_inc], + install: false, +) + +test( + 'box', + executable('test-box', 'test_box.c', dependencies: wlroots), +) + +if features.get('vulkan-renderer') + test( + 'vulkan_stage_buffer', + executable( + 'test-vulkan-stage-buffer', + 'test_vulkan_stage_buffer.c', + link_with: lib_wlr_internal, + dependencies: wlr_deps, + include_directories: wlr_inc, + ), + ) +endif + +benchmark( + 'scene', + executable('bench-scene', 'bench_scene.c', dependencies: wlroots), + timeout: 30, +) diff --git a/test/test_box.c b/test/test_box.c new file mode 100644 index 000000000..fcd95a8e2 --- /dev/null +++ b/test/test_box.c @@ -0,0 +1,149 @@ +#include +#include +#include + +static void test_box_empty(void) { + // NULL is empty + assert(wlr_box_empty(NULL)); + + // Zero width/height + struct wlr_box box = { .x = 0, .y = 0, .width = 0, .height = 10 }; + assert(wlr_box_empty(&box)); + box = (struct wlr_box){ .x = 0, .y = 0, .width = 10, .height = 0 }; + assert(wlr_box_empty(&box)); + + // Negative width/height + box = (struct wlr_box){ .x = 0, .y = 0, .width = -1, .height = 10 }; + assert(wlr_box_empty(&box)); + box = (struct wlr_box){ .x = 0, .y = 0, .width = 10, .height = -1 }; + assert(wlr_box_empty(&box)); + + // Valid box + box = (struct wlr_box){ .x = 0, .y = 0, .width = 10, .height = 10 }; + assert(!wlr_box_empty(&box)); +} + +static void test_box_intersection(void) { + struct wlr_box dest; + + // Overlapping + struct wlr_box a = { .x = 0, .y = 0, .width = 100, .height = 100 }; + struct wlr_box b = { .x = 50, .y = 50, .width = 100, .height = 100 }; + assert(wlr_box_intersection(&dest, &a, &b)); + assert(dest.x == 50 && dest.y == 50 && + dest.width == 50 && dest.height == 50); + + // Non-overlapping + b = (struct wlr_box){ .x = 200, .y = 200, .width = 50, .height = 50 }; + assert(!wlr_box_intersection(&dest, &a, &b)); + assert(dest.width == 0 && dest.height == 0); + + // Touching edges + b = (struct wlr_box){ .x = 100, .y = 0, .width = 50, .height = 50 }; + assert(!wlr_box_intersection(&dest, &a, &b)); + + // Self-intersection + assert(wlr_box_intersection(&dest, &a, &a)); + assert(dest.x == a.x && dest.y == a.y && + dest.width == a.width && dest.height == a.height); + + // Empty input + struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 }; + assert(!wlr_box_intersection(&dest, &a, &empty)); + + // NULL input + assert(!wlr_box_intersection(&dest, &a, NULL)); + assert(!wlr_box_intersection(&dest, NULL, &a)); +} + +static void test_box_intersects_box(void) { + // Overlapping + struct wlr_box a = { .x = 0, .y = 0, .width = 100, .height = 100 }; + struct wlr_box b = { .x = 50, .y = 50, .width = 100, .height = 100 }; + assert(wlr_box_intersects(&a, &b)); + + // Non-overlapping + b = (struct wlr_box){ .x = 200, .y = 200, .width = 50, .height = 50 }; + assert(!wlr_box_intersects(&a, &b)); + + // Touching edges + b = (struct wlr_box){ .x = 100, .y = 0, .width = 50, .height = 50 }; + assert(!wlr_box_intersects(&a, &b)); + + // Self-intersection + assert(wlr_box_intersects(&a, &a)); + + // Empty input + struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 }; + assert(!wlr_box_intersects(&a, &empty)); + + // NULL input + assert(!wlr_box_intersects(&a, NULL)); + assert(!wlr_box_intersects(NULL, &a)); +} + +static void test_box_contains_point(void) { + struct wlr_box box = { .x = 10, .y = 20, .width = 100, .height = 50 }; + + // Interior point + assert(wlr_box_contains_point(&box, 50, 40)); + + // Inclusive lower bound + assert(wlr_box_contains_point(&box, 10, 20)); + + // Exclusive upper bound + assert(!wlr_box_contains_point(&box, 110, 70)); + assert(!wlr_box_contains_point(&box, 110, 40)); + assert(!wlr_box_contains_point(&box, 50, 70)); + + // Outside + assert(!wlr_box_contains_point(&box, 5, 40)); + assert(!wlr_box_contains_point(&box, 50, 15)); + + // Empty box + struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 }; + assert(!wlr_box_contains_point(&empty, 0, 0)); + + // NULL + assert(!wlr_box_contains_point(NULL, 0, 0)); +} + +static void test_box_contains_box(void) { + struct wlr_box outer = { .x = 0, .y = 0, .width = 100, .height = 100 }; + + // Fully contained + struct wlr_box inner = { .x = 10, .y = 10, .width = 50, .height = 50 }; + assert(wlr_box_contains_box(&outer, &inner)); + + // Self-containment + assert(wlr_box_contains_box(&outer, &outer)); + + // Partial overlap — not contained + struct wlr_box partial = { .x = 50, .y = 50, .width = 100, .height = 100 }; + assert(!wlr_box_contains_box(&outer, &partial)); + + // Empty inner + struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 }; + assert(!wlr_box_contains_box(&outer, &empty)); + + // Empty outer + assert(!wlr_box_contains_box(&empty, &inner)); + + // NULL + assert(!wlr_box_contains_box(&outer, NULL)); + assert(!wlr_box_contains_box(NULL, &outer)); +} + +int main(void) { +#ifdef NDEBUG + fprintf(stderr, "NDEBUG must be disabled for tests\n"); + return 1; +#endif + + test_box_empty(); + test_box_intersection(); + test_box_intersects_box(); + test_box_contains_point(); + test_box_contains_box(); + return 0; +} diff --git a/test/test_vulkan_stage_buffer.c b/test/test_vulkan_stage_buffer.c new file mode 100644 index 000000000..74f230a44 --- /dev/null +++ b/test/test_vulkan_stage_buffer.c @@ -0,0 +1,200 @@ +#include +#include +#include + +#include "render/vulkan.h" + +#define BUF_SIZE 1024 +#define ALLOC_FAIL ((VkDeviceSize)-1) + +static void stage_buffer_init(struct wlr_vk_stage_buffer *buf) { + *buf = (struct wlr_vk_stage_buffer){ + .buf_size = BUF_SIZE, + }; + wl_array_init(&buf->watermarks); +} + +static void stage_buffer_finish(struct wlr_vk_stage_buffer *buf) { + wl_array_release(&buf->watermarks); +} + +static void push_watermark(struct wlr_vk_stage_buffer *buf, + uint64_t timeline_point) { + struct wlr_vk_stage_watermark *mark = wl_array_add( + &buf->watermarks, sizeof(*mark)); + assert(mark != NULL); + *mark = (struct wlr_vk_stage_watermark){ + .head = buf->head, + .timeline_point = timeline_point, + }; +} + +static size_t watermark_count(const struct wlr_vk_stage_buffer *buf) { + return buf->watermarks.size / sizeof(struct wlr_vk_stage_watermark); +} + +static void test_alloc_simple(void) { + struct wlr_vk_stage_buffer buf; + stage_buffer_init(&buf); + + assert(vulkan_stage_buffer_alloc(&buf, 100, 1) == 0); + assert(buf.head == 100); + assert(vulkan_stage_buffer_alloc(&buf, 200, 1) == 100); + assert(buf.head == 300); + assert(buf.tail == 0); + + stage_buffer_finish(&buf); +} + +static void test_alloc_alignment(void) { + struct wlr_vk_stage_buffer buf; + stage_buffer_init(&buf); + + assert(vulkan_stage_buffer_alloc(&buf, 7, 1) == 0); + assert(buf.head == 7); + + assert(vulkan_stage_buffer_alloc(&buf, 4, 16) == 16); + assert(buf.head == 20); + + assert(vulkan_stage_buffer_alloc(&buf, 8, 8) == 24); + assert(buf.head == 32); + + stage_buffer_finish(&buf); +} + +static void test_alloc_limit(void) { + struct wlr_vk_stage_buffer buf; + stage_buffer_init(&buf); + + // We do not allow allocations that would cause head to equal tail + assert(vulkan_stage_buffer_alloc(&buf, BUF_SIZE, 1) == ALLOC_FAIL); + assert(buf.head == 0); + + assert(vulkan_stage_buffer_alloc(&buf, BUF_SIZE-1, 1) == 0); + assert(buf.head == BUF_SIZE-1); + + stage_buffer_finish(&buf); +} + +static void test_alloc_wrap(void) { + struct wlr_vk_stage_buffer buf; + stage_buffer_init(&buf); + + // Fill the first 924 bytes + assert(vulkan_stage_buffer_alloc(&buf, BUF_SIZE - 100, 1) == 0); + push_watermark(&buf, 1); + + // Fill the end of the buffer + assert(vulkan_stage_buffer_alloc(&buf, 50, 1) == 924); + push_watermark(&buf, 2); + + // First, check that we don't wrap prematurely + assert(vulkan_stage_buffer_alloc(&buf, 50, 1) == ALLOC_FAIL); + assert(vulkan_stage_buffer_alloc(&buf, 100, 1) == ALLOC_FAIL); + + // Free the beginning of the buffer and try to wrap again + vulkan_stage_buffer_reclaim(&buf, 1); + assert(vulkan_stage_buffer_alloc(&buf, 50, 1) == 0); + assert(buf.tail == 924); + assert(buf.head == 50); + + // Check that freeing from the end of the buffer still works + vulkan_stage_buffer_reclaim(&buf, 2); + assert(buf.tail == 974); + assert(buf.head == 50); + + // Check that allocations still work + assert(vulkan_stage_buffer_alloc(&buf, 100, 1) == 50); + assert(buf.tail == 974); + assert(buf.head == 150); + + stage_buffer_finish(&buf); +} + +static void test_reclaim_empty(void) { + struct wlr_vk_stage_buffer buf; + stage_buffer_init(&buf); + + // Fresh buffer with no watermarks and head == tail == 0 is drained. + vulkan_stage_buffer_reclaim(&buf, 0); + assert(buf.head == buf.tail); + assert(buf.tail == 0); + + stage_buffer_finish(&buf); +} + +static void test_reclaim_pending_not_completed(void) { + struct wlr_vk_stage_buffer buf; + stage_buffer_init(&buf); + + assert(vulkan_stage_buffer_alloc(&buf, 100, 1) == 0); + push_watermark(&buf, 1); + + // current point hasn't reached the watermark yet. + vulkan_stage_buffer_reclaim(&buf, 0); + assert(buf.head != buf.tail); + assert(buf.tail == 0); + assert(watermark_count(&buf) == 1); + + stage_buffer_finish(&buf); +} + +static void test_reclaim_partial(void) { + struct wlr_vk_stage_buffer buf; + stage_buffer_init(&buf); + + assert(vulkan_stage_buffer_alloc(&buf, 100, 1) == 0); + push_watermark(&buf, 1); + assert(vulkan_stage_buffer_alloc(&buf, 100, 1) == 100); + push_watermark(&buf, 2); + + // Only the first watermark is reached. + vulkan_stage_buffer_reclaim(&buf, 1); + assert(buf.head != buf.tail); + assert(buf.tail == 100); + assert(watermark_count(&buf) == 1); + + const struct wlr_vk_stage_watermark *remaining = buf.watermarks.data; + assert(remaining[0].head == 200); + assert(remaining[0].timeline_point == 2); + + stage_buffer_finish(&buf); +} + +static void test_reclaim_all(void) { + struct wlr_vk_stage_buffer buf; + stage_buffer_init(&buf); + + assert(vulkan_stage_buffer_alloc(&buf, 100, 1) == 0); + push_watermark(&buf, 1); + assert(vulkan_stage_buffer_alloc(&buf, 100, 1) == 100); + push_watermark(&buf, 2); + assert(vulkan_stage_buffer_alloc(&buf, 100, 1) == 200); + push_watermark(&buf, 3); + + vulkan_stage_buffer_reclaim(&buf, 100); + assert(buf.head == buf.tail); + assert(buf.tail == 300); + assert(watermark_count(&buf) == 0); + + stage_buffer_finish(&buf); +} + +int main(void) { +#ifdef NDEBUG + fprintf(stderr, "NDEBUG must be disabled for tests\n"); + return 1; +#endif + + test_alloc_simple(); + test_alloc_alignment(); + test_alloc_limit(); + test_alloc_wrap(); + + test_reclaim_empty(); + test_reclaim_pending_not_completed(); + test_reclaim_partial(); + test_reclaim_all(); + + return 0; +} diff --git a/tinywl/Makefile b/tinywl/Makefile index 2efe4a436..0f1c4cb13 100644 --- a/tinywl/Makefile +++ b/tinywl/Makefile @@ -1,6 +1,6 @@ PKG_CONFIG?=pkg-config -PKGS="wlroots-0.20" wayland-server xkbcommon +PKGS="wlroots-0.21" wayland-server xkbcommon CFLAGS_PKG_CONFIG!=$(PKG_CONFIG) --cflags $(PKGS) CFLAGS+=$(CFLAGS_PKG_CONFIG) LIBS!=$(PKG_CONFIG) --libs $(PKGS) diff --git a/types/ext_image_capture_source_v1/foreign_toplevel.c b/types/ext_image_capture_source_v1/foreign_toplevel.c index b1110d155..4aa084df6 100644 --- a/types/ext_image_capture_source_v1/foreign_toplevel.c +++ b/types/ext_image_capture_source_v1/foreign_toplevel.c @@ -17,7 +17,7 @@ foreign_toplevel_manager_from_resource(struct wl_resource *resource) { } bool wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept( - struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request, + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_event *request, struct wlr_ext_image_capture_source_v1 *source) { return wlr_ext_image_capture_source_v1_create_resource(source, request->client, request->new_id); } @@ -34,13 +34,13 @@ static void foreign_toplevel_manager_handle_create_source(struct wl_client *clie return; } - struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request request = { + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_event request = { .toplevel_handle = toplevel_handle, .client = client, .new_id = new_id, }; - wl_signal_emit_mutable(&manager->events.new_request, &request); + wl_signal_emit_mutable(&manager->events.capture_request, &request); } static void foreign_toplevel_manager_handle_destroy(struct wl_client *client, @@ -71,7 +71,7 @@ static void foreign_toplevel_manager_handle_display_destroy(struct wl_listener * wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, NULL); assert(wl_list_empty(&manager->events.destroy.listener_list)); - assert(wl_list_empty(&manager->events.new_request.listener_list)); + assert(wl_list_empty(&manager->events.capture_request.listener_list)); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -97,7 +97,7 @@ wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(struct wl_displa } wl_signal_init(&manager->events.destroy); - wl_signal_init(&manager->events.new_request); + wl_signal_init(&manager->events.capture_request); manager->display_destroy.notify = foreign_toplevel_manager_handle_display_destroy; wl_display_add_destroy_listener(display, &manager->display_destroy); diff --git a/types/meson.build b/types/meson.build index 26958f048..0513b8fc8 100644 --- a/types/meson.build +++ b/types/meson.build @@ -48,6 +48,7 @@ wlr_files += files( 'wlr_data_control_v1.c', 'wlr_drm.c', 'wlr_export_dmabuf_v1.c', + 'wlr_ext_background_effect_v1.c', 'wlr_ext_data_control_v1.c', 'wlr_ext_foreign_toplevel_list_v1.c', 'wlr_ext_image_copy_capture_v1.c', @@ -73,6 +74,7 @@ wlr_files += files( 'wlr_output_swapchain_manager.c', 'wlr_pointer_constraints_v1.c', 'wlr_pointer_gestures_v1.c', + 'wlr_pointer_warp_v1.c', 'wlr_pointer.c', 'wlr_presentation_time.c', 'wlr_primary_selection_v1.c', diff --git a/types/output/cursor.c b/types/output/cursor.c index 5e93b0b2e..4a823dab1 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -159,9 +159,8 @@ static void output_cursor_update_visible(struct wlr_output_cursor *cursor) { struct wlr_box cursor_box; output_cursor_get_box(cursor, &cursor_box); - struct wlr_box intersection; cursor->visible = - wlr_box_intersection(&intersection, &output_box, &cursor_box); + wlr_box_intersects(&output_box, &cursor_box); } static bool output_pick_cursor_format(struct wlr_output *output, diff --git a/types/output/output.c b/types/output/output.c index 3715950ce..46da1f425 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -233,6 +233,11 @@ static void output_apply_state(struct wlr_output *output, output->transform = state->transform; } + if (state->committed & WLR_OUTPUT_STATE_COLOR_REPRESENTATION) { + output->color_encoding = state->color_encoding; + output->color_range = state->color_range; + } + if (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { if (state->image_description != NULL) { output->image_description_value = *state->image_description; @@ -580,6 +585,11 @@ static uint32_t output_compare_state(struct wlr_output *output, output->color_transform == state->color_transform) { fields |= WLR_OUTPUT_STATE_COLOR_TRANSFORM; } + if ((state->committed & WLR_OUTPUT_STATE_COLOR_REPRESENTATION) && + output->color_encoding == state->color_encoding && + output->color_range == state->color_range) { + fields |= WLR_OUTPUT_STATE_COLOR_REPRESENTATION; + } return fields; } @@ -615,7 +625,7 @@ static bool output_basic_test(struct wlr_output *output, }; struct wlr_box dst_box; output_state_get_buffer_dst_box(state, &dst_box); - if (!wlr_box_intersection(&output_box, &output_box, &dst_box)) { + if (!wlr_box_intersects(&output_box, &dst_box)) { wlr_log(WLR_ERROR, "Primary buffer is entirely off-screen or 0-sized"); return false; } @@ -632,6 +642,10 @@ static bool output_basic_test(struct wlr_output *output, wlr_log(WLR_DEBUG, "Tried to set signal timeline without a buffer"); return false; } + if (state->committed & WLR_OUTPUT_STATE_COLOR_REPRESENTATION) { + wlr_log(WLR_DEBUG, "Tried to set color representation without a buffer"); + return false; + } } if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { diff --git a/types/output/state.c b/types/output/state.c index f2c655e21..5f44c18e8 100644 --- a/types/output/state.c +++ b/types/output/state.c @@ -141,6 +141,14 @@ bool wlr_output_state_set_image_description(struct wlr_output_state *state, return true; } +void wlr_output_state_set_color_encoding_and_range( + struct wlr_output_state *state, + enum wlr_color_encoding encoding, enum wlr_color_range range) { + state->committed |= WLR_OUTPUT_STATE_COLOR_REPRESENTATION; + state->color_encoding = encoding; + state->color_range = range; +} + bool wlr_output_state_copy(struct wlr_output_state *dst, const struct wlr_output_state *src) { struct wlr_output_state copy = *src; diff --git a/types/scene/surface.c b/types/scene/surface.c index 89c54a9ed..33b229955 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -161,6 +161,14 @@ static void handle_scene_buffer_output_sample( wl_container_of(listener, surface, output_sample); const struct wlr_scene_output_sample_event *event = data; struct wlr_output *output = event->output->output; + + struct wlr_linux_drm_syncobj_surface_v1_state *syncobj_surface_state = + wlr_linux_drm_syncobj_v1_get_surface_state(surface->surface); + if (syncobj_surface_state != NULL && event->release_timeline != NULL) { + wlr_linux_drm_syncobj_v1_state_add_release_point(syncobj_surface_state, + event->release_timeline, event->release_point, output->event_loop); + } + if (get_surface_frame_pacing_output(surface->surface) != output) { return; } @@ -170,13 +178,6 @@ static void handle_scene_buffer_output_sample( } else { wlr_presentation_surface_textured_on_output(surface->surface, output); } - - struct wlr_linux_drm_syncobj_surface_v1_state *syncobj_surface_state = - wlr_linux_drm_syncobj_v1_get_surface_state(surface->surface); - if (syncobj_surface_state != NULL && event->release_timeline != NULL) { - wlr_linux_drm_syncobj_v1_state_add_release_point(syncobj_surface_state, - event->release_timeline, event->release_point, output->event_loop); - } } static void handle_scene_buffer_frame_done( diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 54c09bbfe..7e4a81004 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -113,24 +113,11 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); - uint64_t active = scene_buffer->active_outputs; - if (active) { - struct wlr_scene_output *scene_output; - wl_list_for_each(scene_output, &scene->outputs, link) { - if (active & (1ull << scene_output->index)) { - wl_signal_emit_mutable(&scene_buffer->events.output_leave, - scene_output); - } - } - } - scene_buffer_set_buffer(scene_buffer, NULL); scene_buffer_set_texture(scene_buffer, NULL); pixman_region32_fini(&scene_buffer->opaque_region); wlr_drm_syncobj_timeline_unref(scene_buffer->wait_timeline); - assert(wl_list_empty(&scene_buffer->events.output_leave.listener_list)); - assert(wl_list_empty(&scene_buffer->events.output_enter.listener_list)); assert(wl_list_empty(&scene_buffer->events.outputs_update.listener_list)); assert(wl_list_empty(&scene_buffer->events.output_sample.listener_list)); assert(wl_list_empty(&scene_buffer->events.frame_done.listener_list)); @@ -238,7 +225,7 @@ static bool _scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box struct wlr_box node_box = { .x = lx, .y = ly }; scene_node_get_size(node, &node_box.width, &node_box.height); - if (wlr_box_intersection(&node_box, &node_box, box) && + if (wlr_box_intersects(&node_box, box) && iterator(node, lx, ly, user_data)) { return true; } @@ -476,33 +463,12 @@ static void update_node_update_outputs(struct wlr_scene_node *node, } } - if (old_primary_output != scene_buffer->primary_output) { - scene_buffer->prev_feedback_options = - (struct wlr_linux_dmabuf_feedback_v1_init_options){0}; - } - - uint64_t old_active = scene_buffer->active_outputs; - scene_buffer->active_outputs = active_outputs; - - struct wlr_scene_output *scene_output; - wl_list_for_each(scene_output, outputs, link) { - uint64_t mask = 1ull << scene_output->index; - bool intersects = active_outputs & mask; - bool intersects_before = old_active & mask; - - if (intersects && !intersects_before) { - wl_signal_emit_mutable(&scene_buffer->events.output_enter, scene_output); - } else if (!intersects && intersects_before) { - wl_signal_emit_mutable(&scene_buffer->events.output_leave, scene_output); - } - } - // if there are active outputs on this node, we should always have a primary // output - assert(!scene_buffer->active_outputs || scene_buffer->primary_output); + assert(!active_outputs || scene_buffer->primary_output); // Skip output update event if nothing was updated - if (old_active == active_outputs && + if (scene_buffer->active_outputs == active_outputs && (!force || ((1ull << force->index) & ~active_outputs)) && old_primary_output == scene_buffer->primary_output) { return; @@ -515,6 +481,7 @@ static void update_node_update_outputs(struct wlr_scene_node *node, }; size_t i = 0; + struct wlr_scene_output *scene_output; wl_list_for_each(scene_output, outputs, link) { if (~active_outputs & (1ull << scene_output->index)) { continue; @@ -524,6 +491,7 @@ static void update_node_update_outputs(struct wlr_scene_node *node, outputs_array[i++] = scene_output; } + scene_buffer->active_outputs = active_outputs; wl_signal_emit_mutable(&scene_buffer->events.outputs_update, &event); } @@ -869,8 +837,6 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, scene_node_init(&scene_buffer->node, WLR_SCENE_NODE_BUFFER, parent); wl_signal_init(&scene_buffer->events.outputs_update); - wl_signal_init(&scene_buffer->events.output_enter); - wl_signal_init(&scene_buffer->events.output_leave); wl_signal_init(&scene_buffer->events.output_sample); wl_signal_init(&scene_buffer->events.frame_done); @@ -2098,15 +2064,6 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( return SCANOUT_INELIGIBLE; } - bool is_color_repr_none = buffer->color_encoding == WLR_COLOR_ENCODING_NONE && - buffer->color_range == WLR_COLOR_RANGE_NONE; - bool is_color_repr_identity_full = buffer->color_encoding == WLR_COLOR_ENCODING_IDENTITY && - buffer->color_range == WLR_COLOR_RANGE_FULL; - - if (!(is_color_repr_none || is_color_repr_identity_full)) { - return SCANOUT_INELIGIBLE; - } - // We want to ensure optimal buffer selection, but as direct-scanout can be enabled and disabled // on a frame-by-frame basis, we wait for a few frames to send the new format recommendations. // Maybe we should only send feedback in this case if tests fail. @@ -2149,11 +2106,22 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( wlr_output_state_set_wait_timeline(&pending, buffer->wait_timeline, buffer->wait_point); } - if (scene_output->out_timeline) { + if (scene_output->out_timeline) { scene_output->out_point++; wlr_output_state_set_signal_timeline(&pending, scene_output->out_timeline, scene_output->out_point); } + if (buffer->color_encoding == WLR_COLOR_ENCODING_IDENTITY && + buffer->color_range == WLR_COLOR_RANGE_FULL) { + // IDENTITY+FULL (used for RGB formats) is equivalent to no color + // representation being set at all. + wlr_output_state_set_color_encoding_and_range(&pending, + WLR_COLOR_ENCODING_NONE, WLR_COLOR_RANGE_NONE); + } else { + wlr_output_state_set_color_encoding_and_range(&pending, + buffer->color_encoding, buffer->color_range); + } + if (!wlr_output_test_state(scene_output->output, &pending)) { wlr_output_state_finish(&pending); return SCANOUT_CANDIDATE; @@ -2696,8 +2664,7 @@ static void scene_output_for_each_scene_buffer(const struct wlr_box *output_box, struct wlr_box node_box = { .x = lx, .y = ly }; scene_node_get_size(node, &node_box.width, &node_box.height); - struct wlr_box intersection; - if (wlr_box_intersection(&intersection, output_box, &node_box)) { + if (wlr_box_intersects(output_box, &node_box)) { struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); user_iterator(scene_buffer, lx, ly, user_data); diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index aa924e36d..27f9d1829 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -20,6 +20,7 @@ struct wlr_color_management_output_v1 { struct wlr_color_manager_v1 *manager; struct wl_list link; + struct wl_listener output_commit; struct wl_listener output_destroy; }; @@ -283,11 +284,21 @@ static void cm_output_destroy(struct wlr_color_management_output_v1 *cm_output) return; } wl_resource_set_user_data(cm_output->resource, NULL); // make inert + wl_list_remove(&cm_output->output_commit.link); wl_list_remove(&cm_output->output_destroy.link); wl_list_remove(&cm_output->link); free(cm_output); } +static void cm_output_handle_output_commit(struct wl_listener *listener, void *data) { + struct wlr_color_management_output_v1 *cm_output = wl_container_of(listener, cm_output, output_commit); + const struct wlr_output_event_commit *event = data; + + if (event->state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + wp_color_management_output_v1_send_image_description_changed(cm_output->resource); + } +} + static void cm_output_handle_output_destroy(struct wl_listener *listener, void *data) { struct wlr_color_management_output_v1 *cm_output = wl_container_of(listener, cm_output, output_destroy); cm_output_destroy(cm_output); @@ -742,6 +753,9 @@ static void manager_handle_get_output(struct wl_client *client, cm_output->manager = manager; cm_output->output = output; + cm_output->output_commit.notify = cm_output_handle_output_commit; + wl_signal_add(&output->events.commit, &cm_output->output_commit); + cm_output->output_destroy.notify = cm_output_handle_output_destroy; wl_signal_add(&output->events.destroy, &cm_output->output_destroy); diff --git a/types/wlr_ext_background_effect_v1.c b/types/wlr_ext_background_effect_v1.c new file mode 100644 index 000000000..3bf214397 --- /dev/null +++ b/types/wlr_ext_background_effect_v1.c @@ -0,0 +1,241 @@ +#include +#include +#include +#include +#include +#include "ext-background-effect-v1-protocol.h" + +#define BACKGROUND_EFFECT_VERSION 1 + +struct wlr_ext_background_effect_surface_v1 { + struct wl_resource *resource; + struct wlr_surface *surface; + struct wlr_addon addon; + struct wlr_surface_synced synced; + struct wlr_ext_background_effect_surface_v1_state pending, current; +}; + +static const struct ext_background_effect_surface_v1_interface surface_impl; +static const struct ext_background_effect_manager_v1_interface manager_impl; + +static struct wlr_ext_background_effect_surface_v1 *surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_background_effect_surface_v1_interface, &surface_impl)); + + return wl_resource_get_user_data(resource); +} + +static void surface_destroy(struct wlr_ext_background_effect_surface_v1 *surface) { + if (surface == NULL) { + return; + } + + wlr_surface_synced_finish(&surface->synced); + wlr_addon_finish(&surface->addon); + wl_resource_set_user_data(surface->resource, NULL); + free(surface); +} + +static void surface_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_background_effect_surface_v1 *surface = surface_from_resource(resource); + surface_destroy(surface); +} + +static void surface_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void surface_handle_set_blur_region(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *region_resource) { + struct wlr_ext_background_effect_surface_v1 *surface = surface_from_resource(resource); + + if (surface == NULL) { + wl_resource_post_error(resource, + EXT_BACKGROUND_EFFECT_SURFACE_V1_ERROR_SURFACE_DESTROYED, + "The wl_surface object has been destroyed"); + return; + } + + if (region_resource != NULL) { + const pixman_region32_t *region = wlr_region_from_resource(region_resource); + pixman_region32_copy(&surface->pending.blur_region, region); + } else { + pixman_region32_clear(&surface->pending.blur_region); + } +} + +static const struct ext_background_effect_surface_v1_interface surface_impl = { + .destroy = surface_handle_destroy, + .set_blur_region = surface_handle_set_blur_region, +}; + +static void surface_synced_init_state(void *_state) { + struct wlr_ext_background_effect_surface_v1_state *state = _state; + pixman_region32_init(&state->blur_region); +} + +static void surface_synced_finish_state(void *_state) { + struct wlr_ext_background_effect_surface_v1_state *state = _state; + pixman_region32_fini(&state->blur_region); +} + +static void surface_synced_move_state(void *_dst, void *_src) { + struct wlr_ext_background_effect_surface_v1_state *dst = _dst; + struct wlr_ext_background_effect_surface_v1_state *src = _src; + + pixman_region32_copy(&dst->blur_region, &src->blur_region); +} + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_ext_background_effect_surface_v1_state), + .init_state = surface_synced_init_state, + .finish_state = surface_synced_finish_state, + .move_state = surface_synced_move_state, +}; + +static void surface_addon_destroy(struct wlr_addon *addon) { + struct wlr_ext_background_effect_surface_v1 *surface = wl_container_of(addon, surface, addon); + + surface_destroy(surface); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "ext_background_effect_surface_v1", + .destroy = surface_addon_destroy, +}; + +static struct wlr_ext_background_effect_surface_v1 *surface_from_wlr_surface( + struct wlr_surface *wlr_surface) { + struct wlr_addon *addon = wlr_addon_find(&wlr_surface->addons, NULL, &surface_addon_impl); + if (addon == NULL) { + return NULL; + } + + struct wlr_ext_background_effect_surface_v1 *surface = + wl_container_of(addon, surface, addon); + + return surface; +} + +static void manager_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void manager_handle_get_background_effect(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, struct wl_resource *surface_resource) { + struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); + + if (surface_from_wlr_surface(wlr_surface) != NULL) { + wl_resource_post_error(manager_resource, + EXT_BACKGROUND_EFFECT_MANAGER_V1_ERROR_BACKGROUND_EFFECT_EXISTS, + "The wl_surface object already has a ext_background_effect_surface_v1 object"); + return; + } + + struct wlr_ext_background_effect_surface_v1 *surface = calloc(1, sizeof(*surface)); + if (surface == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + if (!wlr_surface_synced_init(&surface->synced, wlr_surface, &surface_synced_impl, + &surface->pending, &surface->current)) { + free(surface); + wl_resource_post_no_memory(manager_resource); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + surface->resource = + wl_resource_create(client, &ext_background_effect_surface_v1_interface, version, id); + + if (surface->resource == NULL) { + wlr_surface_synced_finish(&surface->synced); + free(surface); + wl_resource_post_no_memory(manager_resource); + return; + } + + wl_resource_set_implementation(surface->resource, &surface_impl, surface, + surface_handle_resource_destroy); + + surface->surface = wlr_surface; + wlr_addon_init(&surface->addon, &wlr_surface->addons, NULL, &surface_addon_impl); +} + +static const struct ext_background_effect_manager_v1_interface manager_impl = { + .destroy = manager_handle_destroy, + .get_background_effect = manager_handle_get_background_effect, +}; + +static void manager_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void manager_bind(struct wl_client *wl_client, void *data, uint32_t version, uint32_t id) { + struct wlr_ext_background_effect_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(wl_client, + &ext_background_effect_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, + manager_handle_resource_destroy); + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + ext_background_effect_manager_v1_send_capabilities(resource, manager->capabilities); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_background_effect_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_global_destroy(manager->global); + wl_list_remove(&manager->display_destroy.link); + free(manager); +} + +struct wlr_ext_background_effect_manager_v1 *wlr_ext_background_effect_manager_v1_create( + struct wl_display *display, uint32_t version, uint32_t capabilities) { + assert(version <= BACKGROUND_EFFECT_VERSION); + + struct wlr_ext_background_effect_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, &ext_background_effect_manager_v1_interface, version, + manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->capabilities = capabilities; + + wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->resources); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +const struct wlr_ext_background_effect_surface_v1_state * +wlr_ext_background_effect_v1_get_surface_state(struct wlr_surface *wlr_surface) { + struct wlr_ext_background_effect_surface_v1 *surface = + surface_from_wlr_surface(wlr_surface); + + if (surface == NULL) { + return NULL; + } + + return &surface->current; +} diff --git a/types/wlr_layer_shell_v1.c b/types/wlr_layer_shell_v1.c index 2d8384981..72d3c4923 100644 --- a/types/wlr_layer_shell_v1.c +++ b/types/wlr_layer_shell_v1.c @@ -394,6 +394,7 @@ static void layer_surface_role_commit(struct wlr_surface *wlr_surface) { layer_surface_reset(surface); assert(!surface->initialized); + assert(!surface->initial_commit); surface->initial_commit = false; } else { surface->initial_commit = !surface->initialized; diff --git a/types/wlr_linux_dmabuf_v1.c b/types/wlr_linux_dmabuf_v1.c index 3165e8805..0b2e908fb 100644 --- a/types/wlr_linux_dmabuf_v1.c +++ b/types/wlr_linux_dmabuf_v1.c @@ -901,9 +901,9 @@ static bool set_default_feedback(struct wlr_linux_dmabuf_v1 *linux_dmabuf, if (device->available_nodes & (1 << DRM_NODE_RENDER)) { const char *name = device->nodes[DRM_NODE_RENDER]; main_device_fd = open(name, O_RDWR | O_CLOEXEC); - drmFreeDevice(&device); if (main_device_fd < 0) { wlr_log_errno(WLR_ERROR, "Failed to open DRM device %s", name); + drmFreeDevice(&device); goto error_compiled; } } else { @@ -913,8 +913,8 @@ static bool set_default_feedback(struct wlr_linux_dmabuf_v1 *linux_dmabuf, assert(device->available_nodes & (1 << DRM_NODE_PRIMARY)); wlr_log(WLR_DEBUG, "DRM device %s has no render node, " "skipping DMA-BUF import checks", device->nodes[DRM_NODE_PRIMARY]); - drmFreeDevice(&device); } + drmFreeDevice(&device); size_t tranches_len = feedback->tranches.size / sizeof(struct wlr_linux_dmabuf_feedback_v1_tranche); diff --git a/types/wlr_linux_drm_syncobj_v1.c b/types/wlr_linux_drm_syncobj_v1.c index 2e09b9e2e..6d9188777 100644 --- a/types/wlr_linux_drm_syncobj_v1.c +++ b/types/wlr_linux_drm_syncobj_v1.c @@ -12,6 +12,7 @@ #include #include "config.h" #include "linux-drm-syncobj-v1-protocol.h" +#include "render/dmabuf.h" #include "render/drm_syncobj_merger.h" #define LINUX_DRM_SYNCOBJ_V1_VERSION 1 @@ -541,3 +542,12 @@ bool wlr_linux_drm_syncobj_v1_state_add_release_point( return wlr_drm_syncobj_merger_add(state->release_merger, release_timeline, release_point, event_loop); } + +bool wlr_linux_drm_syncobj_v1_state_add_release_from_implicit_sync( + struct wlr_linux_drm_syncobj_surface_v1_state *state, + struct wlr_buffer *buffer, struct wl_event_loop *event_loop) { + if (state->release_merger == NULL) { + return true; + } + return wlr_drm_syncobj_merger_add_dmabuf(state->release_merger, buffer, event_loop); +} diff --git a/types/wlr_output_layout.c b/types/wlr_output_layout.c index ef2751179..7226809e7 100644 --- a/types/wlr_output_layout.c +++ b/types/wlr_output_layout.c @@ -260,14 +260,12 @@ bool wlr_output_layout_contains_point(struct wlr_output_layout *layout, bool wlr_output_layout_intersects(struct wlr_output_layout *layout, struct wlr_output *reference, const struct wlr_box *target_lbox) { - struct wlr_box out_box; - if (reference == NULL) { struct wlr_output_layout_output *l_output; wl_list_for_each(l_output, &layout->outputs, link) { struct wlr_box output_box; output_layout_output_get_box(l_output, &output_box); - if (wlr_box_intersection(&out_box, &output_box, target_lbox)) { + if (wlr_box_intersects(&output_box, target_lbox)) { return true; } } @@ -281,7 +279,7 @@ bool wlr_output_layout_intersects(struct wlr_output_layout *layout, struct wlr_box output_box; output_layout_output_get_box(l_output, &output_box); - return wlr_box_intersection(&out_box, &output_box, target_lbox); + return wlr_box_intersects(&output_box, target_lbox); } } diff --git a/types/wlr_pointer_warp_v1.c b/types/wlr_pointer_warp_v1.c new file mode 100644 index 000000000..fc7393e7a --- /dev/null +++ b/types/wlr_pointer_warp_v1.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include "pointer-warp-v1-protocol.h" + +#define POINTER_WARP_VERSION 1 + +static const struct wp_pointer_warp_v1_interface pointer_warp_impl; + +static struct wlr_pointer_warp_v1 *pointer_warp_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_pointer_warp_v1_interface, + &pointer_warp_impl)); + return wl_resource_get_user_data(resource); +} + +static void resource_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void pointer_warp_handle_warp_pointer(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *surface_resource, + struct wl_resource *pointer_resource, wl_fixed_t sx, wl_fixed_t sy, + uint32_t serial) { + struct wlr_pointer_warp_v1 *pointer_warp = + pointer_warp_from_resource(resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_pointer_resource(pointer_resource); + if (seat_client == NULL) { + return; + } + + struct wlr_pointer_warp_v1_event_warp event = { + .surface = surface, + .seat_client = seat_client, + .x = wl_fixed_to_double(sx), + .y = wl_fixed_to_double(sy), + .serial = serial, + }; + + wl_signal_emit_mutable(&pointer_warp->events.warp, &event); +} + +static const struct wp_pointer_warp_v1_interface pointer_warp_impl = { + .destroy = resource_destroy, + .warp_pointer = pointer_warp_handle_warp_pointer, +}; + +static void pointer_warp_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_pointer_warp_v1 *pointer_warp = data; + struct wl_resource *resource = wl_resource_create(client, + &wp_pointer_warp_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &pointer_warp_impl, pointer_warp, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_pointer_warp_v1 *pointer_warp = + wl_container_of(listener, pointer_warp, display_destroy); + + wl_signal_emit_mutable(&pointer_warp->events.destroy, NULL); + + assert(wl_list_empty(&pointer_warp->events.destroy.listener_list)); + assert(wl_list_empty(&pointer_warp->events.warp.listener_list)); + + wl_list_remove(&pointer_warp->display_destroy.link); + wl_global_destroy(pointer_warp->global); + free(pointer_warp); +} + +struct wlr_pointer_warp_v1 *wlr_pointer_warp_v1_create(struct wl_display *display, + uint32_t version) { + assert(version <= POINTER_WARP_VERSION); + + struct wlr_pointer_warp_v1 *pointer_warp = calloc(1, sizeof(*pointer_warp)); + if (pointer_warp == NULL) { + return NULL; + } + + pointer_warp->global = wl_global_create(display, &wp_pointer_warp_v1_interface, + version, pointer_warp, pointer_warp_bind); + if (pointer_warp->global == NULL) { + free(pointer_warp); + return NULL; + } + + wl_signal_init(&pointer_warp->events.destroy); + wl_signal_init(&pointer_warp->events.warp); + + pointer_warp->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &pointer_warp->display_destroy); + + return pointer_warp; +} diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c index 9916a604c..30287b79e 100644 --- a/types/wlr_screencopy_v1.c +++ b/types/wlr_screencopy_v1.c @@ -512,6 +512,16 @@ static void capture_output(struct wl_client *wl_client, return; } + struct wlr_screencopy_frame_v1 *existing; + wl_list_for_each(existing, &client->manager->frames, link) { + if (existing->client == client && existing->output == output) { + wl_resource_set_user_data(frame->resource, NULL); + zwlr_screencopy_frame_v1_send_failed(frame->resource); + free(frame); + return; + } + } + frame->client = client; client->ref++; diff --git a/types/wlr_virtual_keyboard_v1.c b/types/wlr_virtual_keyboard_v1.c index 465b97dd3..3701f4d53 100644 --- a/types/wlr_virtual_keyboard_v1.c +++ b/types/wlr_virtual_keyboard_v1.c @@ -44,6 +44,19 @@ static void virtual_keyboard_keymap(struct wl_client *client, return; } + if (!wl_keyboard_keymap_format_is_valid(format, 1)) { + wl_resource_post_error(resource, + ZWP_VIRTUAL_KEYBOARD_V1_ERROR_INVALID_KEYMAP_FORMAT, + "Invalid keymap format"); + return; + } + + if (format == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { + keyboard->has_keymap = true; + close(fd); + return; + } + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!context) { goto context_fail; diff --git a/types/wlr_xdg_decoration_v1.c b/types/wlr_xdg_decoration_v1.c index 4194942a2..7c9b88888 100644 --- a/types/wlr_xdg_decoration_v1.c +++ b/types/wlr_xdg_decoration_v1.c @@ -5,7 +5,7 @@ #include #include "xdg-decoration-unstable-v1-protocol.h" -#define DECORATION_MANAGER_VERSION 1 +#define DECORATION_MANAGER_VERSION 2 static const struct zxdg_toplevel_decoration_v1_interface toplevel_decoration_impl; @@ -171,8 +171,9 @@ static void decoration_manager_handle_get_toplevel_decoration( decoration_manager_from_resource(manager_resource); struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource); + uint32_t version = wl_resource_get_version(manager_resource); - if (wlr_surface_has_buffer(toplevel->base->surface)) { + if (version == 1 && wlr_surface_has_buffer(toplevel->base->surface)) { wl_resource_post_error(manager_resource, ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER, "xdg_toplevel_decoration must not have a buffer at creation"); @@ -204,7 +205,6 @@ static void decoration_manager_handle_get_toplevel_decoration( return; } - uint32_t version = wl_resource_get_version(manager_resource); decoration->resource = wl_resource_create(client, &zxdg_toplevel_decoration_v1_interface, version, id); if (decoration->resource == NULL) { @@ -271,7 +271,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { } struct wlr_xdg_decoration_manager_v1 * - wlr_xdg_decoration_manager_v1_create(struct wl_display *display) { + wlr_xdg_decoration_manager_v1_create(struct wl_display *display, + uint32_t version) { + assert(version <= DECORATION_MANAGER_VERSION); struct wlr_xdg_decoration_manager_v1 *manager = calloc(1, sizeof(*manager)); if (manager == NULL) { return NULL; diff --git a/types/xdg_shell/wlr_xdg_surface.c b/types/xdg_shell/wlr_xdg_surface.c index 8a252d2f8..7b9c99150 100644 --- a/types/xdg_shell/wlr_xdg_surface.c +++ b/types/xdg_shell/wlr_xdg_surface.c @@ -320,6 +320,7 @@ static void xdg_surface_role_commit(struct wlr_surface *wlr_surface) { reset_xdg_surface_role_object(surface); reset_xdg_surface(surface); + assert(!surface->initialized); assert(!surface->initial_commit); surface->initial_commit = false; } else { diff --git a/util/box.c b/util/box.c index 489c5ad2e..62d405488 100644 --- a/util/box.c +++ b/util/box.c @@ -102,6 +102,15 @@ bool wlr_box_contains_box(const struct wlr_box *bigger, const struct wlr_box *sm smaller->y + smaller->height <= bigger->y + bigger->height; } +bool wlr_box_intersects(const struct wlr_box *a, const struct wlr_box *b) { + if (wlr_box_empty(a) || wlr_box_empty(b)) { + return false; + } + + return a->x < b->x + b->width && b->x < a->x + a->width && + a->y < b->y + b->height && b->y < a->y + a->height; +} + void wlr_box_transform(struct wlr_box *dest, const struct wlr_box *box, enum wl_output_transform transform, int width, int height) { struct wlr_box src = {0}; diff --git a/util/rect_union.c b/util/rect_union.c index 8cd26d761..f113fe43e 100644 --- a/util/rect_union.c +++ b/util/rect_union.c @@ -1,15 +1,15 @@ #include #include "util/rect_union.h" -static void box_union(pixman_box32_t *dst, pixman_box32_t box) { - dst->x1 = dst->x1 < box.x1 ? dst->x1 : box.x1; - dst->y1 = dst->y1 < box.y1 ? dst->y1 : box.y1; - dst->x2 = dst->x2 > box.x2 ? dst->x2 : box.x2; - dst->y2 = dst->y2 > box.y2 ? dst->y2 : box.y2; +static void box_union(pixman_box32_t *dst, const pixman_box32_t *box) { + dst->x1 = dst->x1 < box->x1 ? dst->x1 : box->x1; + dst->y1 = dst->y1 < box->y1 ? dst->y1 : box->y1; + dst->x2 = dst->x2 > box->x2 ? dst->x2 : box->x2; + dst->y2 = dst->y2 > box->y2 ? dst->y2 : box->y2; } -static bool box_empty_or_invalid(pixman_box32_t box) { - return box.x1 >= box.x2 || box.y1 >= box.y2; +static bool box_empty_or_invalid(const pixman_box32_t *box) { + return box->x1 >= box->x2 || box->y1 >= box->y2; } void rect_union_init(struct rect_union *ru) { @@ -37,20 +37,28 @@ static void handle_alloc_failure(struct rect_union *ru) { wl_array_init(&ru->unsorted); } -void rect_union_add(struct rect_union *ru, pixman_box32_t box) { +void rect_union_add(struct rect_union *ru, const pixman_box32_t *box) { if (box_empty_or_invalid(box)) { return; } box_union(&ru->bounding_box, box); - if (!ru->alloc_failure) { - pixman_box32_t *entry = wl_array_add(&ru->unsorted, sizeof(*entry)); - if (entry) { - *entry = box; - } else { - handle_alloc_failure(ru); - } + if (ru->alloc_failure) { + return; + } + + int nrects = (int)(ru->unsorted.size / sizeof(pixman_box32_t)); + if (nrects >= 1024) { + handle_alloc_failure(ru); + return; + } + + pixman_box32_t *entry = wl_array_add(&ru->unsorted, sizeof(*entry)); + if (entry) { + *entry = *box; + } else { + handle_alloc_failure(ru); } } @@ -81,7 +89,7 @@ const pixman_region32_t *rect_union_evaluate(struct rect_union *ru) { return &ru->region; bounding_box: pixman_region32_fini(&ru->region); - if (box_empty_or_invalid(ru->bounding_box)) { + if (box_empty_or_invalid(&ru->bounding_box)) { pixman_region32_init(&ru->region); } else { pixman_region32_init_with_extents(&ru->region, &ru->bounding_box); diff --git a/xwayland/selection/dnd.c b/xwayland/selection/dnd.c index 31e588baf..f80a55d4d 100644 --- a/xwayland/selection/dnd.c +++ b/xwayland/selection/dnd.c @@ -83,7 +83,12 @@ static void xwm_dnd_send_enter(struct wlr_xwm *xwm) { // data and must be retrieved with the DND_TYPE_LIST property data.data32[1] |= 1; - xcb_atom_t targets[n]; + xcb_atom_t *targets = malloc(n * sizeof(targets[0])); + if (targets == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + size_t i = 0; char **mime_type_ptr; wl_array_for_each(mime_type_ptr, mime_types) { @@ -99,6 +104,8 @@ static void xwm_dnd_send_enter(struct wlr_xwm *xwm) { XCB_ATOM_ATOM, 32, // format n, targets); + + free(targets); } xwm_dnd_send_event(xwm, xwm->atoms[DND_ENTER], &data); diff --git a/xwayland/selection/incoming.c b/xwayland/selection/incoming.c index 85d7775cd..2e512e7db 100644 --- a/xwayland/selection/incoming.c +++ b/xwayland/selection/incoming.c @@ -103,7 +103,7 @@ static int write_selection_property_to_wl_client(int fd, uint32_t mask, void *data) { struct wlr_xwm_selection_transfer *transfer = data; - char *property = xcb_get_property_value(transfer->property_reply); + const char *property = xcb_get_property_value(transfer->property_reply); int remainder = xcb_get_property_value_length(transfer->property_reply) - transfer->property_start; @@ -249,7 +249,7 @@ struct x11_data_source { static const struct wlr_data_source_impl data_source_impl; bool data_source_is_xwayland( - struct wlr_data_source *wlr_source) { + const struct wlr_data_source *wlr_source) { return wlr_source->impl == &data_source_impl; } @@ -292,7 +292,7 @@ static const struct wlr_primary_selection_source_impl primary_selection_source_impl; bool primary_selection_source_is_xwayland( - struct wlr_primary_selection_source *wlr_source) { + const struct wlr_primary_selection_source *wlr_source) { return wlr_source->impl == &primary_selection_source_impl; } @@ -343,8 +343,9 @@ static bool source_get_targets(struct wlr_xwm_selection *selection, return false; } - xcb_atom_t *value = xcb_get_property_value(reply); - for (uint32_t i = 0; i < reply->value_len; i++) { + const xcb_atom_t *value = xcb_get_property_value(reply); + uint32_t value_len = xcb_get_property_value_length(reply) / sizeof(value); + for (uint32_t i = 0; i < value_len; i++) { char *mime_type = NULL; if (value[i] == xwm->atoms[UTF8_STRING]) { diff --git a/xwayland/selection/outgoing.c b/xwayland/selection/outgoing.c index 6216abbb4..a58c92e3e 100644 --- a/xwayland/selection/outgoing.c +++ b/xwayland/selection/outgoing.c @@ -336,7 +336,11 @@ static void xwm_selection_send_targets(struct wlr_xwm_selection *selection, } size_t n = 2 + mime_types->size / sizeof(char *); - xcb_atom_t targets[n]; + xcb_atom_t *targets = malloc(n * sizeof(targets[0])); + if (targets == NULL) { + wlr_log(WLR_ERROR, "Allocation failure"); + return; + } targets[0] = xwm->atoms[TIMESTAMP]; targets[1] = xwm->atoms[TARGETS]; @@ -356,6 +360,8 @@ static void xwm_selection_send_targets(struct wlr_xwm_selection *selection, 32, // format n, targets); + free(targets); + xwm_selection_send_notify(selection->xwm, req, true); } diff --git a/xwayland/xwm.c b/xwayland/xwm.c index e44a11743..8cee2961e 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -630,6 +630,7 @@ static void xwayland_surface_destroy(struct wlr_xwayland_surface *xsurface) { wl_list_remove(&child->parent_link); wl_list_init(&child->parent_link); child->parent = NULL; + wl_signal_emit_mutable(&child->events.set_parent, NULL); } wl_list_remove(&xsurface->unpaired_link); @@ -659,7 +660,7 @@ static void read_surface_class(struct wlr_xwm *xwm, } size_t len = xcb_get_property_value_length(reply); - char *class = xcb_get_property_value(reply); + const char *class = xcb_get_property_value(reply); // Unpack two sequentially stored strings: instance, class size_t instance_len = strnlen(class, len); @@ -667,6 +668,7 @@ static void read_surface_class(struct wlr_xwm *xwm, if (len > 0 && instance_len < len) { surface->instance = strndup(class, instance_len); class += instance_len + 1; + len -= instance_len + 1; } else { surface->instance = NULL; } @@ -690,7 +692,7 @@ static void read_surface_startup_id(struct wlr_xwm *xwm, } size_t len = xcb_get_property_value_length(reply); - char *startup_id = xcb_get_property_value(reply); + const char *startup_id = xcb_get_property_value(reply); free(xsurface->startup_id); if (len > 0) { @@ -719,7 +721,7 @@ static void read_surface_opacity(struct wlr_xwm *xwm, return; } - uint32_t *val = xcb_get_property_value(reply); + const uint32_t *val = xcb_get_property_value(reply); xsurface->opacity = (double)*val / UINT32_MAX; wl_signal_emit_mutable(&xsurface->events.set_opacity, NULL); } @@ -734,7 +736,7 @@ static void read_surface_role(struct wlr_xwm *xwm, } size_t len = xcb_get_property_value_length(reply); - char *role = xcb_get_property_value(reply); + const char *role = xcb_get_property_value(reply); free(xsurface->role); if (len > 0) { @@ -806,8 +808,12 @@ static void read_surface_parent(struct wlr_xwm *xwm, } struct wlr_xwayland_surface *found_parent = NULL; - xcb_window_t *xid = xcb_get_property_value(reply); - if (reply->type != XCB_ATOM_NONE && xid != NULL) { + if (reply->type != XCB_ATOM_NONE) { + if (xcb_get_property_value_length(reply) != sizeof(xcb_window_t)) { + wlr_log(WLR_DEBUG, "Invalid WM_TRANSIENT_FOR property length"); + return; + } + const xcb_window_t *xid = xcb_get_property_value(reply); found_parent = lookup_surface(xwm, *xid); if (!has_parent(found_parent, xsurface)) { xsurface->parent = found_parent; @@ -837,9 +843,9 @@ static void read_surface_window_type(struct wlr_xwm *xwm, return; } - xcb_atom_t *atoms = xcb_get_property_value(reply); - size_t atoms_len = reply->value_len; - size_t atoms_size = sizeof(xcb_atom_t) * atoms_len; + const xcb_atom_t *atoms = xcb_get_property_value(reply); + size_t atoms_len = xcb_get_property_value_length(reply) / sizeof(atoms[0]); + size_t atoms_size = sizeof(atoms[0]) * atoms_len; free(xsurface->window_type); if (atoms_len > 0) { @@ -864,9 +870,9 @@ static void read_surface_protocols(struct wlr_xwm *xwm, return; } - xcb_atom_t *atoms = xcb_get_property_value(reply); - size_t atoms_len = reply->value_len; - size_t atoms_size = sizeof(xcb_atom_t) * atoms_len; + const xcb_atom_t *atoms = xcb_get_property_value(reply); + size_t atoms_len = xcb_get_property_value_length(reply) / sizeof(atoms[0]); + size_t atoms_size = sizeof(atoms[0]) * atoms_len; free(xsurface->protocols); if (atoms_len > 0) { @@ -893,7 +899,7 @@ static void read_surface_hints(struct wlr_xwm *xwm, } free(xsurface->hints); - if (reply->value_len > 0) { + if (xcb_get_property_value_length(reply) > 0) { xsurface->hints = calloc(1, sizeof(*xsurface->hints)); if (xsurface->hints == NULL) { return; @@ -923,7 +929,7 @@ static void read_surface_normal_hints(struct wlr_xwm *xwm, free(xsurface->size_hints); xsurface->size_hints = NULL; - if (reply->value_len == 0) { + if (xcb_get_property_value_length(reply) == 0) { return; } @@ -970,18 +976,19 @@ static void read_surface_normal_hints(struct wlr_xwm *xwm, static void read_surface_motif_hints(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, xcb_get_property_reply_t *reply) { - if (reply->value_len == 0) { + if (xcb_get_property_value_length(reply) == 0) { xsurface->decorations = 0; wl_signal_emit_mutable(&xsurface->events.set_decorations, NULL); return; } - if (reply->value_len < 5) { + const uint32_t *motif_hints = xcb_get_property_value(reply); + int motif_hints_len = xcb_get_property_value_length(reply) / sizeof(motif_hints[0]); + if (motif_hints_len < 5) { wlr_log(WLR_DEBUG, "Invalid MOTIF_WM_HINTS property type"); return; } - uint32_t *motif_hints = xcb_get_property_value(reply); if (motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) { xsurface->decorations = WLR_XWAYLAND_SURFACE_DECORATIONS_ALL; uint32_t decorations = motif_hints[MWM_HINTS_DECORATIONS_FIELD]; @@ -1029,31 +1036,32 @@ static void read_surface_net_wm_state(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, xcb_get_property_reply_t *reply) { xsurface->fullscreen = 0; - xcb_atom_t *atom = xcb_get_property_value(reply); - for (uint32_t i = 0; i < reply->value_len; i++) { - if (atom[i] == xwm->atoms[NET_WM_STATE_MODAL]) { + const xcb_atom_t *atoms = xcb_get_property_value(reply); + uint32_t atoms_len = xcb_get_property_value_length(reply) / sizeof(atoms[0]); + for (uint32_t i = 0; i < atoms_len; i++) { + if (atoms[i] == xwm->atoms[NET_WM_STATE_MODAL]) { xsurface->modal = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_FULLSCREEN]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_FULLSCREEN]) { xsurface->fullscreen = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_MAXIMIZED_VERT]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_MAXIMIZED_VERT]) { xsurface->maximized_vert = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_MAXIMIZED_HORZ]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_MAXIMIZED_HORZ]) { xsurface->maximized_horz = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_HIDDEN]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_HIDDEN]) { xsurface->minimized = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_STICKY]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_STICKY]) { xsurface->sticky = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_SHADED]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_SHADED]) { xsurface->shaded = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_SKIP_TASKBAR]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_SKIP_TASKBAR]) { xsurface->skip_taskbar = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_SKIP_PAGER]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_SKIP_PAGER]) { xsurface->skip_pager = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_ABOVE]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_ABOVE]) { xsurface->above = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_BELOW]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_BELOW]) { xsurface->below = true; - } else if (atom[i] == xwm->atoms[NET_WM_STATE_DEMANDS_ATTENTION]) { + } else if (atoms[i] == xwm->atoms[NET_WM_STATE_DEMANDS_ATTENTION]) { xsurface->demands_attention = true; } } @@ -1459,15 +1467,31 @@ static void xwm_handle_surface_id_message(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); if (xsurface == NULL) { wlr_log(WLR_DEBUG, - "client message WL_SURFACE_ID but no new window %u ?", + "Received client message WL_SURFACE_ID but no X11 window %u", ev->window); return; } - /* Check if we got notified after wayland surface create event */ + if (xsurface->surface != NULL) { + wlr_log(WLR_DEBUG, "Received multiple client messages WL_SURFACE_ID " + "for an already-associated X11 window %u", ev->window); + return; + } + uint32_t id = ev->data.data32[0]; + + // Because the X11 and Wayland connections are separate sockets, the + // WL_SURFACE_ID and wl_compositor.create_surface messages may be received + // in any order. struct wl_resource *resource = wl_client_get_object(xwm->xwayland->server->client, id); if (resource) { + if (wl_resource_get_interface(resource) != &wl_surface_interface) { + wlr_log(WLR_DEBUG, "Received client message WL_SURFACE_ID " + "for X11 window %u but Wayland object is not a wl_surface", + ev->window); + return; + } + struct wlr_surface *surface = wlr_surface_from_resource(resource); xwayland_surface_associate(xwm, xsurface, surface); } else { @@ -2771,6 +2795,22 @@ bool xwm_atoms_contains(struct wlr_xwm *xwm, xcb_atom_t *atoms, } void wlr_xwayland_surface_ping(struct wlr_xwayland_surface *surface) { + if (surface->pinging) { + return; + } + + // don't ping if client not supports + bool supports_ping = false; + for(size_t i = 0; i < surface->protocols_len; i++) { + if (surface->protocols[i] == surface->xwm->atoms[NET_WM_PING]) { + supports_ping = true; + break; + } + } + if (!supports_ping) { + return; + } + xcb_client_message_data_t data = { 0 }; data.data32[0] = surface->xwm->atoms[NET_WM_PING]; data.data32[1] = XCB_CURRENT_TIME;