diff --git a/include/render/egl.h b/include/render/egl.h index 0765cce..103ab57 100644 --- a/include/render/egl.h +++ b/include/render/egl.h @@ -38,6 +38,10 @@ struct wlr_egl { PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT; PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT; PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT; + PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR; + PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID; + PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR; } procs; bool has_modifiers; @@ -105,4 +109,12 @@ bool wlr_egl_make_current(struct wlr_egl *egl, struct wlr_egl_context *save_cont bool wlr_egl_unset_current(struct wlr_egl *egl); +EGLSyncKHR wlr_egl_create_sync(struct wlr_egl *egl, int fence_fd); + +void wlr_egl_destroy_sync(struct wlr_egl *egl, EGLSyncKHR sync); + +int wlr_egl_dup_fence_fd(struct wlr_egl *egl, EGLSyncKHR sync); + +bool wlr_egl_wait_sync(struct wlr_egl *egl, EGLSyncKHR sync); + #endif diff --git a/include/render/fx_renderer/matrix.h b/include/render/fx_renderer/matrix.h deleted file mode 100644 index c3bae42..0000000 --- a/include/render/fx_renderer/matrix.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _MATRIX_H -#define _MATRIX_H - -#include - -/** - * Writes a 2D orthographic projection matrix to mat of (width, height) with a - * specified wl_output_transform. - * - * Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied. - */ -void matrix_projection(float mat[static 9], int width, int height, - enum wl_output_transform transform); - -#endif diff --git a/include/scenefx/render/pass.h b/include/scenefx/render/pass.h index bd00b77..136caf4 100644 --- a/include/scenefx/render/pass.h +++ b/include/scenefx/render/pass.h @@ -18,6 +18,8 @@ struct fx_gles_render_pass { float projection_matrix[9]; struct wlr_egl_context prev_ctx; struct fx_render_timer *timer; + struct wlr_drm_syncobj_timeline *signal_timeline; + uint64_t signal_point; }; struct fx_buffer_pass_options { diff --git a/include/scenefx/types/wlr_scene.h b/include/scenefx/types/wlr_scene.h index b4df998..c79e920 100644 --- a/include/scenefx/types/wlr_scene.h +++ b/include/scenefx/types/wlr_scene.h @@ -80,9 +80,9 @@ struct wlr_scene_node { struct wlr_addon_set addons; - // private state - - pixman_region32_t visible; + struct { + pixman_region32_t visible; + } WLR_PRIVATE; }; enum wlr_scene_debug_damage_option { @@ -106,17 +106,20 @@ struct wlr_scene { // May be NULL struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1; + struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1; - // private state - - struct wl_listener linux_dmabuf_v1_destroy; + struct { + struct wl_listener linux_dmabuf_v1_destroy; + struct wl_listener gamma_control_manager_v1_destroy; + struct wl_listener gamma_control_manager_v1_set_gamma; - enum wlr_scene_debug_damage_option debug_damage_option; - bool direct_scanout; - bool calculate_visibility; - bool highlight_transparent_region; + enum wlr_scene_debug_damage_option debug_damage_option; + bool direct_scanout; + bool calculate_visibility; + bool highlight_transparent_region; - struct blur_data blur_data; + struct blur_data blur_data; + } WLR_PRIVATE; }; /** A scene-graph node displaying a single surface. */ @@ -124,19 +127,19 @@ struct wlr_scene_surface { struct wlr_scene_buffer *buffer; struct wlr_surface *surface; - // private state - - struct wlr_box clip; - - struct wlr_addon addon; - - struct wl_listener outputs_update; - struct wl_listener output_enter; - struct wl_listener output_leave; - struct wl_listener output_sample; - struct wl_listener frame_done; - struct wl_listener surface_destroy; - struct wl_listener surface_commit; + struct { + struct wlr_box clip; + + struct wlr_addon addon; + + struct wl_listener outputs_update; + struct wl_listener output_enter; + struct wl_listener output_leave; + struct wl_listener output_sample; + struct wl_listener frame_done; + struct wl_listener surface_destroy; + struct wl_listener surface_commit; + } WLR_PRIVATE; }; /** A scene-graph node displaying a solid-colored rectangle */ @@ -221,18 +224,27 @@ struct wlr_scene_buffer { enum wl_output_transform transform; pixman_region32_t opaque_region; - // private state - - uint64_t active_outputs; - struct wlr_texture *texture; - struct wlr_linux_dmabuf_feedback_v1_init_options prev_feedback_options; - - bool own_buffer; - int buffer_width, buffer_height; - bool buffer_is_opaque; - - struct wl_listener buffer_release; - struct wl_listener renderer_destroy; + struct { + uint64_t active_outputs; + struct wlr_texture *texture; + struct wlr_linux_dmabuf_feedback_v1_init_options prev_feedback_options; + + bool own_buffer; + int buffer_width, buffer_height; + bool buffer_is_opaque; + + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; + + struct wl_listener buffer_release; + struct wl_listener renderer_destroy; + + // True if the underlying buffer is a wlr_single_pixel_buffer_v1 + bool is_single_pixel_buffer; + // If is_single_pixel_buffer is set, contains the color of the buffer + // as {R, G, B, A} where the max value of each component is UINT32_MAX + uint32_t single_pixel_buffer_color[4]; + } WLR_PRIVATE; }; /** A viewport for an output in the scene-graph */ @@ -250,20 +262,34 @@ struct wlr_scene_output { struct wl_signal destroy; } events; - // private state + struct { + pixman_region32_t pending_commit_damage; + + uint8_t index; - pixman_region32_t pending_commit_damage; + /** + * When scanout is applicable, we increment this every time a frame is rendered until + * DMABUF_FEEDBACK_DEBOUNCE_FRAMES is hit to debounce the scanout dmabuf feedback. Likewise, + * when scanout is no longer applicable, we decrement this until zero is hit to debounce + * composition dmabuf feedback. + */ + uint8_t dmabuf_feedback_debounce; + bool prev_scanout; - uint8_t index; - bool prev_scanout; + bool gamma_lut_changed; + struct wlr_gamma_control_v1 *gamma_lut; - struct wl_listener output_commit; - struct wl_listener output_damage; - struct wl_listener output_needs_frame; + struct wl_listener output_commit; + struct wl_listener output_damage; + struct wl_listener output_needs_frame; - struct wl_list damage_highlight_regions; + struct wl_list damage_highlight_regions; - struct wl_array render_list; + struct wl_array render_list; + + struct wlr_drm_syncobj_timeline *in_timeline; + uint64_t in_point; + } WLR_PRIVATE; }; struct wlr_scene_timer { @@ -276,12 +302,12 @@ struct wlr_scene_layer_surface_v1 { struct wlr_scene_tree *tree; struct wlr_layer_surface_v1 *layer_surface; - // private state - - struct wl_listener tree_destroy; - struct wl_listener layer_surface_destroy; - struct wl_listener layer_surface_map; - struct wl_listener layer_surface_unmap; + struct { + struct wl_listener tree_destroy; + struct wl_listener layer_surface_destroy; + struct wl_listener layer_surface_map; + struct wl_listener layer_surface_unmap; + } WLR_PRIVATE; }; /** @@ -346,6 +372,9 @@ struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node, /** * Create a new scene-graph. + * + * The graph is also a struct wlr_scene_node. Associated resources can be + * destroyed through wlr_scene_node_destroy(). */ struct wlr_scene *wlr_scene_create(void); @@ -361,6 +390,15 @@ void wlr_scene_set_blur_data(struct wlr_scene *scene, struct blur_data blur_data void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene, struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1); +/** + * Handles gamma_control_v1 for all outputs in the scene. + * + * Asserts that a struct wlr_gamma_control_manager_v1 hasn't already been set + * for the scene. + */ +void wlr_scene_set_gamma_control_manager_v1(struct wlr_scene *scene, + struct wlr_gamma_control_manager_v1 *gamma_control); + /** * Add a node displaying nothing but its children. @@ -370,11 +408,29 @@ struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent); /** * Add a node displaying a single surface to the scene-graph. * - * The child sub-surfaces are ignored. + * The child sub-surfaces are ignored. See wlr_scene_subsurface_tree_create() + * + * Note that this helper does multiple things on behalf of the compositor. Some + * of these include protocol implementations where compositors just need to enable + * the protocols: + * - wp_viewporter + * - wp_presentation_time + * - wp_fractional_scale_v1 + * - wp_alpha_modifier_v1 + * - wp_linux_drm_syncobj_v1 + * - zwp_linux_dmabuf_v1 presentation feedback with wlr_scene_set_linux_dmabuf_v1() + * + * This helper will also transparently: + * - Send preferred buffer scale¹ + * - Send preferred buffer transform¹ + * - Restack xwayland surfaces. See wlr_xwayland_surface_restack()² + * - Send output enter/leave events. * - * wlr_surface_send_enter() and wlr_surface_send_leave() will be called - * automatically based on the position of the surface and outputs in - * the scene. + * ¹ Note that scale and transform sent to the surface will be based on the output + * which has the largest visible surface area. Intelligent visibility calculations + * influence this. + * ² xwayland stacking order is undefined when the xwayland surfaces do not + * intersect. */ struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_tree *parent, struct wlr_surface *surface); @@ -412,6 +468,8 @@ struct wlr_scene_surface *wlr_scene_surface_try_from_buffer( /** * Add a node displaying a solid-colored rectangle to the scene-graph. + * + * The color argument must be a premultiplied color value. */ struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent, int width, int height, const float color[static 4]); @@ -440,6 +498,8 @@ void wlr_scene_rect_set_clipped_region(struct wlr_scene_rect *rect, /** * Change the color of an existing rectangle node. + * + * The color argument must be a premultiplied color value. */ void wlr_scene_rect_set_color(struct wlr_scene_rect *rect, const float color[static 4]); @@ -550,6 +610,28 @@ void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer, struct wlr_buffer *buffer, const pixman_region32_t *region); +/** + * Options for wlr_scene_buffer_set_buffer_with_options(). + */ +struct wlr_scene_buffer_set_buffer_options { + // The damage region is in buffer-local coordinates. If the region is NULL, + // the whole buffer node will be damaged. + const pixman_region32_t *damage; + + // Wait for a timeline synchronization point before reading from the buffer. + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; +}; + +/** + * Sets the buffer's backing buffer. + * + * If the buffer is NULL, the buffer node will not be displayed. If options is + * NULL, empty options are used. + */ +void wlr_scene_buffer_set_buffer_with_options(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer, const struct wlr_scene_buffer_set_buffer_options *options); + /** * Sets the buffer's opaque region. This is an optimization hint used to * determine if buffers which reside under this one need to be rendered or not. @@ -654,6 +736,12 @@ struct wlr_scene_output_state_options { struct wlr_swapchain *swapchain; }; +/** + * Returns true if scene wants to render a new frame. False, if no new frame + * is needed and an output commit can be skipped for the current frame. + */ +bool wlr_scene_output_needs_frame(struct wlr_scene_output *scene_output); + /** * Render and commit an output. */ diff --git a/include/types/wlr_buffer.h b/include/types/wlr_buffer.h deleted file mode 100644 index 016cae8..0000000 --- a/include/types/wlr_buffer.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef TYPES_WLR_BUFFER -#define TYPES_WLR_BUFFER - -#include - -/** - * Check whether a buffer is fully opaque. - * - * When true is returned, the buffer is guaranteed to be fully opaque, but the - * reverse is not true: false may be returned in cases where the buffer is fully - * opaque. - */ -bool buffer_is_opaque(struct wlr_buffer *buffer); - -#endif diff --git a/include/util/matrix.h b/include/util/matrix.h new file mode 100644 index 0000000..c45c99a --- /dev/null +++ b/include/util/matrix.h @@ -0,0 +1,49 @@ +#ifndef UTIL_MATRIX_H +#define UTIL_MATRIX_H + +#include + +struct wlr_box; + +/** Writes the identity matrix into mat */ +void wlr_matrix_identity(float mat[static 9]); + +/** mat ← a × b */ +void wlr_matrix_multiply(float mat[static 9], const float a[static 9], + const float b[static 9]); + +/** Writes a 2D translation matrix to mat of magnitude (x, y) */ +void wlr_matrix_translate(float mat[static 9], float x, float y); + +/** Writes a 2D scale matrix to mat of magnitude (x, y) */ +void wlr_matrix_scale(float mat[static 9], float x, float y); + +/** Writes a transformation matrix which applies the specified + * wl_output_transform to mat */ +void wlr_matrix_transform(float mat[static 9], + enum wl_output_transform transform); + +/** Shortcut for the various matrix operations involved in projecting the + * specified wlr_box onto a given orthographic projection with a given + * rotation. The result is written to mat, which can be applied to each + * coordinate of the box to get a new coordinate from [-1,1]. */ +void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box, + enum wl_output_transform transform, const float projection[static 9]); + +/** + * Writes a 2D orthographic projection matrix to mat of (width, height) with a + * specified wl_output_transform. + * + * Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied. + */ +void matrix_projection(float mat[static 9], int width, int height, + enum wl_output_transform transform); + +/** + * Compute the inverse of a matrix. + * + * The matrix needs to be inversible. + */ +void matrix_invert(float out[static 9], float m[static 9]); + +#endif diff --git a/include/util/time.h b/include/util/time.h index 3f76aa4..dd164bf 100644 --- a/include/util/time.h +++ b/include/util/time.h @@ -4,6 +4,8 @@ #include #include +static const long NSEC_PER_SEC = 1000000000; + /** * Get the current time, in milliseconds. */ diff --git a/meson.build b/meson.build index 0f4867f..77383ba 100644 --- a/meson.build +++ b/meson.build @@ -3,11 +3,12 @@ project( 'c', version: '0.3.0', license: 'MIT', - meson_version: '>=0.59.0', + meson_version: '>=1.3', default_options: [ - 'c_std=' + (meson.version().version_compare('>=1.3.0') ? 'c23,c11' : 'c11'), + 'c_std=' + (meson.version().version_compare('>=1.4.0') ? 'c23,c11' : 'c11'), 'warning_level=2', 'werror=true', + 'wrap_mode=nodownload', ], ) @@ -22,6 +23,7 @@ big_endian = target_machine.endian() == 'big' add_project_arguments([ '-D_POSIX_C_SOURCE=200809L', '-DWLR_USE_UNSTABLE', + '-DWLR_PRIVATE=', '-DWLR_LITTLE_ENDIAN=@0@'.format(little_endian.to_int()), '-DWLR_BIG_ENDIAN=@0@'.format(big_endian.to_int()), ], language: 'c') @@ -48,31 +50,10 @@ add_project_arguments(cc.get_supported_arguments([ '-Wno-unused-parameter', ]), language: 'c') -# Compute the relative path used by compiler invocations. -source_root = meson.current_source_dir().split('/') -build_root = meson.global_build_root().split('/') -relative_dir_parts = [] -i = 0 -in_prefix = true -foreach p : build_root - if i >= source_root.length() or not in_prefix or p != source_root[i] - in_prefix = false - relative_dir_parts += '..' - endif - i += 1 -endforeach -i = 0 -in_prefix = true -foreach p : source_root - if i >= build_root.length() or not in_prefix or build_root[i] != p - in_prefix = false - relative_dir_parts += p - endif - i += 1 -endforeach -relative_dir = join_paths(relative_dir_parts) + '/' +fs = import('fs') # Strip relative path prefixes from the code if possible, otherwise hide them. +relative_dir = fs.relative_to(meson.current_source_dir(), meson.global_build_root()) + '/' if cc.has_argument('-fmacro-prefix-map=/prefix/to/hide=') add_project_arguments( '-fmacro-prefix-map=@0@='.format(relative_dir), @@ -85,16 +66,21 @@ else ) endif -wayland_project_options = ['tests=false', 'documentation=false'] +wayland_kwargs = { + 'version': '>=1.23.1', + 'fallback': 'wayland', + 'default_options': [ + 'tests=false', + 'documentation=false', + ], +} wayland_server = dependency('wayland-server', - version: '>=1.23', - fallback: 'wayland', - default_options: wayland_project_options, + kwargs: wayland_kwargs, ) wlroots_options = [ 'examples=false' ] -wlroots_version = ['>=0.18.1', '<0.19.0'] -wlroots = dependency('wlroots-0.18', +wlroots_version = ['>=0.19.0', '<0.20.0'] +wlroots = dependency('wlroots-0.19', version: wlroots_version, default_options: wlroots_options, required: false, @@ -119,7 +105,7 @@ drm = dependency('libdrm', ) xkbcommon = dependency('xkbcommon') pixman = dependency('pixman-1', - version: '>=0.42.0', + version: '>=0.43.0', fallback: 'pixman', default_options: ['werror=false'], ) diff --git a/protocol/meson.build b/protocol/meson.build index 659f3b2..f65e2f6 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,11 +1,14 @@ wayland_protos = dependency('wayland-protocols', - version: '>=1.35', + version: '>=1.41', fallback: 'wayland-protocols', default_options: ['tests=false'], ) wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') -wayland_scanner_dep = dependency('wayland-scanner', native: true) +wayland_scanner_dep = dependency('wayland-scanner', + kwargs: wayland_kwargs, + native: true, +) wayland_scanner = find_program( wayland_scanner_dep.get_variable('wayland_scanner'), native: true, diff --git a/render/egl.c b/render/egl.c index d58b512..75cef15 100644 --- a/render/egl.c +++ b/render/egl.c @@ -312,17 +312,6 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { return false; } - if (check_egl_ext(device_exts_str, "EGL_MESA_device_software")) { - if (env_parse_bool("WLR_RENDERER_ALLOW_SOFTWARE")) { - wlr_log(WLR_INFO, "Using software rendering"); - } else { - wlr_log(WLR_ERROR, "Software rendering detected, please use " - "the WLR_RENDERER_ALLOW_SOFTWARE environment variable " - "to proceed"); - return false; - } - } - #ifdef EGL_DRIVER_NAME_EXT if (check_egl_ext(device_exts_str, "EGL_EXT_device_persistent_id")) { driver_name = egl->procs.eglQueryDeviceStringEXT(egl->device, @@ -334,6 +323,20 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { check_egl_ext(device_exts_str, "EGL_EXT_device_drm"); egl->exts.EXT_device_drm_render_node = check_egl_ext(device_exts_str, "EGL_EXT_device_drm_render_node"); + + // The only way a non-DRM device is selected is when the user + // explicitly picks software rendering + if (check_egl_ext(device_exts_str, "EGL_MESA_device_software") && + egl->exts.EXT_device_drm) { + if (env_parse_bool("WLR_RENDERER_ALLOW_SOFTWARE")) { + wlr_log(WLR_INFO, "Using software rendering"); + } else { + wlr_log(WLR_ERROR, "Software rendering detected, please use " + "the WLR_RENDERER_ALLOW_SOFTWARE environment variable " + "to proceed"); + return false; + } + } } if (!check_egl_ext(display_exts_str, "EGL_KHR_no_config_context") && @@ -348,6 +351,18 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { return false; } + if (check_egl_ext(display_exts_str, "EGL_KHR_fence_sync") && + check_egl_ext(display_exts_str, "EGL_ANDROID_native_fence_sync")) { + load_egl_proc(&egl->procs.eglCreateSyncKHR, "eglCreateSyncKHR"); + load_egl_proc(&egl->procs.eglDestroySyncKHR, "eglDestroySyncKHR"); + load_egl_proc(&egl->procs.eglDupNativeFenceFDANDROID, + "eglDupNativeFenceFDANDROID"); + } + + if (check_egl_ext(display_exts_str, "EGL_KHR_wait_sync")) { + load_egl_proc(&egl->procs.eglWaitSyncKHR, "eglWaitSyncKHR"); + } + egl->exts.IMG_context_priority = check_egl_ext(display_exts_str, "EGL_IMG_context_priority"); @@ -377,7 +392,7 @@ static bool egl_init(struct wlr_egl *egl, EGLenum platform, } display_attribs[display_attribs_len++] = EGL_NONE; - assert(display_attribs_len < sizeof(display_attribs) / sizeof(display_attribs[0])); + assert(display_attribs_len <= sizeof(display_attribs) / sizeof(display_attribs[0])); EGLDisplay display = egl->procs.eglGetPlatformDisplayEXT(platform, remote_display, display_attribs); @@ -480,32 +495,55 @@ static EGLDeviceEXT get_egl_device_from_drm_fd(struct wlr_egl *egl, if (!egl->procs.eglQueryDevicesEXT(nb_devices, devices, &nb_devices)) { wlr_log(WLR_ERROR, "Failed to query EGL devices"); + free(devices); return EGL_NO_DEVICE_EXT; } - drmDevice *device = NULL; - int ret = drmGetDevice(drm_fd, &device); - if (ret < 0) { - wlr_log(WLR_ERROR, "Failed to get DRM device: %s", strerror(-ret)); - return EGL_NO_DEVICE_EXT; + drmDevice *selected_drm_device = NULL; + if (drm_fd >= 0) { + int ret = drmGetDevice(drm_fd, &selected_drm_device); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get DRM device: %s", strerror(-ret)); + free(devices); + return EGL_NO_DEVICE_EXT; + } } EGLDeviceEXT egl_device = NULL; for (int i = 0; i < nb_devices; i++) { - const char *egl_device_name = egl->procs.eglQueryDeviceStringEXT( - devices[i], EGL_DRM_DEVICE_FILE_EXT); - if (egl_device_name == NULL) { + const char *device_exts_str = egl->procs.eglQueryDeviceStringEXT(devices[i], EGL_EXTENSIONS); + if (device_exts_str == NULL) { + wlr_log(WLR_ERROR, "eglQueryDeviceStringEXT(EGL_EXTENSIONS) failed"); continue; } - if (device_has_name(device, egl_device_name)) { - wlr_log(WLR_DEBUG, "Using EGL device %s", egl_device_name); + const char *egl_device_name = NULL; + if (check_egl_ext(device_exts_str, "EGL_EXT_device_drm")) { + egl_device_name = egl->procs.eglQueryDeviceStringEXT(devices[i], EGL_DRM_DEVICE_FILE_EXT); + if (egl_device_name == NULL) { + wlr_log(WLR_ERROR, "eglQueryDeviceStringEXT(EGL_DRM_DEVICE_FILE_EXT) failed"); + continue; + } + } + + bool is_software = check_egl_ext(device_exts_str, "EGL_MESA_device_software"); + + bool found; + if (selected_drm_device != NULL) { + found = egl_device_name != NULL && device_has_name(selected_drm_device, egl_device_name); + } else { + found = is_software; + } + if (found) { + if (egl_device_name != NULL) { + wlr_log(WLR_DEBUG, "Using EGL device %s", egl_device_name); + } egl_device = devices[i]; break; } } - drmFreeDevice(&device); + drmFreeDevice(&selected_drm_device); free(devices); return egl_device; @@ -558,7 +596,7 @@ struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) { wlr_log(WLR_DEBUG, "EXT_platform_device not supported"); } - if (egl->exts.KHR_platform_gbm) { + if (egl->exts.KHR_platform_gbm && drm_fd >= 0) { int gbm_fd = open_render_node(drm_fd); if (gbm_fd < 0) { wlr_log(WLR_ERROR, "Failed to open DRM render node"); @@ -786,7 +824,7 @@ EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl, attribs[atti++] = EGL_TRUE; attribs[atti++] = EGL_NONE; - assert(atti < sizeof(attribs)/sizeof(attribs[0])); + assert(atti <= sizeof(attribs)/sizeof(attribs[0])); EGLImageKHR image = egl->procs.eglCreateImageKHR(egl->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); @@ -1030,3 +1068,65 @@ int wlr_egl_dup_drm_fd(struct wlr_egl *egl) { } return fd; } + +EGLSyncKHR wlr_egl_create_sync(struct wlr_egl *egl, int fence_fd) { + if (!egl->procs.eglCreateSyncKHR) { + return EGL_NO_SYNC_KHR; + } + + EGLint attribs[3] = { EGL_NONE }; + int dup_fd = -1; + if (fence_fd >= 0) { + dup_fd = fcntl(fence_fd, F_DUPFD_CLOEXEC, 0); + if (dup_fd < 0) { + wlr_log_errno(WLR_ERROR, "dup failed"); + return EGL_NO_SYNC_KHR; + } + + attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID; + attribs[1] = dup_fd; + attribs[2] = EGL_NONE; + } + + EGLSyncKHR sync = egl->procs.eglCreateSyncKHR(egl->display, + EGL_SYNC_NATIVE_FENCE_ANDROID, attribs); + if (sync == EGL_NO_SYNC_KHR) { + wlr_log(WLR_ERROR, "eglCreateSyncKHR failed"); + if (dup_fd >= 0) { + close(dup_fd); + } + } + return sync; +} + +void wlr_egl_destroy_sync(struct wlr_egl *egl, EGLSyncKHR sync) { + if (sync == EGL_NO_SYNC_KHR) { + return; + } + assert(egl->procs.eglDestroySyncKHR); + if (egl->procs.eglDestroySyncKHR(egl->display, sync) != EGL_TRUE) { + wlr_log(WLR_ERROR, "eglDestroySyncKHR failed"); + } +} + +int wlr_egl_dup_fence_fd(struct wlr_egl *egl, EGLSyncKHR sync) { + if (!egl->procs.eglDupNativeFenceFDANDROID) { + return -1; + } + + int fd = egl->procs.eglDupNativeFenceFDANDROID(egl->display, sync); + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + wlr_log(WLR_ERROR, "eglDupNativeFenceFDANDROID failed"); + return -1; + } + + return fd; +} + +bool wlr_egl_wait_sync(struct wlr_egl *egl, EGLSyncKHR sync) { + if (egl->procs.eglWaitSyncKHR(egl->display, sync, 0) != EGL_TRUE) { + wlr_log(WLR_ERROR, "eglWaitSyncKHR failed"); + return false; + } + return true; +} diff --git a/render/fx_renderer/fx_pass.c b/render/fx_renderer/fx_pass.c index 03cda80..2ac76db 100644 --- a/render/fx_renderer/fx_pass.c +++ b/render/fx_renderer/fx_pass.c @@ -3,21 +3,22 @@ #include #include #include +#include #include -#include +#include #include #include #include #include "render/egl.h" #include "render/fx_renderer/fx_renderer.h" -#include "render/fx_renderer/matrix.h" #include "render/fx_renderer/shaders.h" #include "render/pass.h" #include "scenefx/render/fx_renderer/fx_renderer.h" #include "scenefx/render/fx_renderer/fx_effect_framebuffers.h" #include "scenefx/types/fx/blur_data.h" #include "scenefx/types/fx/corner_location.h" +#include "util/matrix.h" #define MAX_QUADS 86 // 4kb @@ -67,6 +68,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { struct fx_gles_render_pass *pass = get_render_pass(wlr_pass); struct fx_renderer *renderer = pass->buffer->renderer; struct fx_render_timer *timer = pass->timer; + bool ok = false; push_fx_debug(renderer); @@ -82,12 +84,36 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { clock_gettime(CLOCK_MONOTONIC, &timer->cpu_end); } - glFlush(); + if (pass->signal_timeline != NULL) { + EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, -1); + if (sync == EGL_NO_SYNC_KHR) { + goto out; + } + + int sync_file_fd = wlr_egl_dup_fence_fd(renderer->egl, sync); + wlr_egl_destroy_sync(renderer->egl, sync); + if (sync_file_fd < 0) { + goto out; + } + + ok = wlr_drm_syncobj_timeline_import_sync_file(pass->signal_timeline, pass->signal_point, sync_file_fd); + close(sync_file_fd); + if (!ok) { + goto out; + } + } else { + glFlush(); + } + + ok = true; + +out: glBindFramebuffer(GL_FRAMEBUFFER, 0); pop_fx_debug(renderer); wlr_egl_restore_context(&pass->prev_ctx); + wlr_drm_syncobj_timeline_unref(pass->signal_timeline); wlr_buffer_unlock(pass->buffer->buffer); struct fx_effect_framebuffers *fbos = fx_effect_framebuffers_try_get(pass->output); if (fbos) { @@ -95,7 +121,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { } free(pass); - return true; + return ok; } static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, @@ -292,6 +318,27 @@ void fx_render_pass_add_texture(struct fx_gles_render_pass *pass, src_fbox.height /= options->texture->height; push_fx_debug(renderer); + + 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; + } + + EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, sync_file_fd); + close(sync_file_fd); + if (sync == EGL_NO_SYNC_KHR) { + return; + } + + bool ok = wlr_egl_wait_sync(renderer->egl, sync); + wlr_egl_destroy_sync(renderer->egl, sync); + if (!ok) { + return; + } + } + bool has_alpha = texture->has_alpha || alpha < 1.0 || fx_options->corner_radius > 0 @@ -1036,7 +1083,8 @@ static const char *reset_status_str(GLenum status) { } static struct fx_gles_render_pass *begin_buffer_pass(struct fx_framebuffer *buffer, - struct wlr_egl_context *prev_ctx, struct fx_render_timer *timer) { + struct wlr_egl_context *prev_ctx, struct fx_render_timer *timer, + struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point) { struct fx_renderer *renderer = buffer->renderer; struct wlr_buffer *wlr_buffer = buffer->buffer; @@ -1064,6 +1112,10 @@ static struct fx_gles_render_pass *begin_buffer_pass(struct fx_framebuffer *buff pass->buffer = buffer; pass->timer = timer; pass->prev_ctx = *prev_ctx; + if (signal_timeline != NULL) { + pass->signal_timeline = wlr_drm_syncobj_timeline_ref(signal_timeline); + pass->signal_point = signal_point; + } matrix_projection(pass->projection_matrix, wlr_buffer->width, wlr_buffer->height, WL_OUTPUT_TRANSFORM_FLIPPED_180); @@ -1128,7 +1180,8 @@ struct fx_gles_render_pass *fx_renderer_begin_buffer_pass( } } - struct fx_gles_render_pass *pass = begin_buffer_pass(buffer, &prev_ctx, timer); + struct fx_gles_render_pass *pass = begin_buffer_pass(buffer, + &prev_ctx, timer, options->signal_timeline, options->signal_point); if (!pass) { return NULL; } diff --git a/render/fx_renderer/fx_renderer.c b/render/fx_renderer/fx_renderer.c index d3a4ef9..ff42020 100644 --- a/render/fx_renderer/fx_renderer.c +++ b/render/fx_renderer/fx_renderer.c @@ -8,15 +8,14 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include +#include #include "render/egl.h" #include "render/fx_renderer/shaders.h" @@ -523,6 +522,13 @@ struct wlr_renderer *fx_renderer_create_egl(struct wlr_egl *egl) { get_fx_shm_formats(renderer, &renderer->shm_texture_formats); + int drm_fd = wlr_renderer_get_drm_fd(&renderer->wlr_renderer); + uint64_t cap_syncobj_timeline; + if (drm_fd >= 0 && drmGetCap(drm_fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap_syncobj_timeline) == 0) { + renderer->wlr_renderer.features.timeline = egl->procs.eglDupNativeFenceFDANDROID && + egl->procs.eglWaitSyncKHR && cap_syncobj_timeline != 0; + } + return &renderer->wlr_renderer; error: diff --git a/render/fx_renderer/gles2/shaders/gradient.frag b/render/fx_renderer/gles2/shaders/gradient.frag index f5522ac..407e273 100644 --- a/render/fx_renderer/gles2/shaders/gradient.frag +++ b/render/fx_renderer/gles2/shaders/gradient.frag @@ -30,12 +30,12 @@ vec4 gradient(vec4 colors[LEN], int count, vec2 size, vec2 grad_box, vec2 origin } float smooth_fac = 1.0/float(count - 1); - int ind = int(step/smooth_fac); - float at = float(ind)*smooth_fac; + int ind = int(step/smooth_fac); + float at = float(ind)*smooth_fac; - vec4 color = colors[ind]; - if(ind > 0) color = mix(colors[ind - 1], color, smoothstep(at - smooth_fac, at, step)); - if(ind <= count - 1) color = mix(color, colors[ind + 1], smoothstep(at, at + smooth_fac, step)); + vec4 color = colors[ind]; + if(ind > 0) color = mix(colors[ind - 1], color, smoothstep(at - smooth_fac, at, step)); + if(ind <= count - 1) color = mix(color, colors[ind + 1], smoothstep(at, at + smooth_fac, step)); return color; } diff --git a/render/fx_renderer/matrix.c b/render/fx_renderer/matrix.c deleted file mode 100644 index 8f0fe15..0000000 --- a/render/fx_renderer/matrix.c +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include -#include - -#include "render/fx_renderer/matrix.h" - -static const float transforms[][9] = { - [WL_OUTPUT_TRANSFORM_NORMAL] = { - 1.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - }, - [WL_OUTPUT_TRANSFORM_90] = { - 0.0f, 1.0f, 0.0f, - -1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - }, - [WL_OUTPUT_TRANSFORM_180] = { - -1.0f, 0.0f, 0.0f, - 0.0f, -1.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - }, - [WL_OUTPUT_TRANSFORM_270] = { - 0.0f, -1.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - }, - [WL_OUTPUT_TRANSFORM_FLIPPED] = { - -1.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - }, - [WL_OUTPUT_TRANSFORM_FLIPPED_90] = { - 0.0f, 1.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - }, - [WL_OUTPUT_TRANSFORM_FLIPPED_180] = { - 1.0f, 0.0f, 0.0f, - 0.0f, -1.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - }, - [WL_OUTPUT_TRANSFORM_FLIPPED_270] = { - 0.0f, -1.0f, 0.0f, - -1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - }, -}; - -void matrix_projection(float mat[static 9], int width, int height, - enum wl_output_transform transform) { - memset(mat, 0, sizeof(*mat) * 9); - - const float *t = transforms[transform]; - float x = 2.0f / width; - float y = 2.0f / height; - - // Rotation + reflection - mat[0] = x * t[0]; - mat[1] = x * t[1]; - mat[3] = y * -t[3]; - mat[4] = y * -t[4]; - - // Translation - mat[2] = -copysign(1.0f, mat[0] + mat[1]); - mat[5] = -copysign(1.0f, mat[3] + mat[4]); - - // Identity - mat[8] = 1.0f; -} diff --git a/render/fx_renderer/meson.build b/render/fx_renderer/meson.build index 4618272..1d584bd 100644 --- a/render/fx_renderer/meson.build +++ b/render/fx_renderer/meson.build @@ -6,7 +6,6 @@ elif 'auto' in renderers and get_option('auto_features').disabled() endif scenefx_files += files( - 'matrix.c', 'util.c', 'shaders.c', 'pixel_format.c', @@ -36,6 +35,8 @@ if 'gles2' in renderers or 'auto' in renderers gbm = dependency('gbm', required: 'gles2' in renderers) glesv2 = dependency('glesv2', required: 'gles2' in renderers) + glslang = find_program('glslang', 'glslangValidator', native: true, required: false, disabler: true) + if egl.found() and gbm.found() and glesv2.found() scenefx_deps += [egl, gbm, glesv2] endif diff --git a/render/fx_renderer/util.c b/render/fx_renderer/util.c index d4fcec3..dd044e0 100644 --- a/render/fx_renderer/util.c +++ b/render/fx_renderer/util.c @@ -5,14 +5,7 @@ #include #include "render/fx_renderer/util.h" - -static uint32_t backend_get_buffer_caps(struct wlr_backend *backend) { - if (!backend->impl->get_buffer_caps) { - return 0; - } - - return backend->impl->get_buffer_caps(backend); -} +#include "util/env.h" static int open_drm_render_node(void) { uint32_t flags = 0; @@ -66,6 +59,12 @@ bool open_preferred_drm_fd(struct wlr_backend *backend, int *drm_fd_ptr, return true; } + if (env_parse_bool("WLR_RENDERER_FORCE_SOFTWARE")) { + *drm_fd_ptr = -1; + *own_drm_fd = false; + return true; + } + // Allow the user to override the render node const char *render_name = getenv("WLR_RENDER_DRM_DEVICE"); if (render_name != NULL) { @@ -97,8 +96,7 @@ bool open_preferred_drm_fd(struct wlr_backend *backend, int *drm_fd_ptr, // If the backend hasn't picked a DRM FD, but accepts DMA-BUFs, pick an // arbitrary render node - uint32_t backend_caps = backend_get_buffer_caps(backend); - if (backend_caps & WLR_BUFFER_CAP_DMABUF) { + if (backend->buffer_caps & WLR_BUFFER_CAP_DMABUF) { int drm_fd = open_drm_render_node(); if (drm_fd < 0) { return false; diff --git a/tinywl/.gitignore b/tinywl/.gitignore index 2b5bb6f..5de73fa 100644 --- a/tinywl/.gitignore +++ b/tinywl/.gitignore @@ -1,3 +1,3 @@ tinywl -*-protocol.c +tinywl.o *-protocol.h diff --git a/tinywl/Makefile b/tinywl/Makefile index 475e740..fb5f315 100644 --- a/tinywl/Makefile +++ b/tinywl/Makefile @@ -1,10 +1,13 @@ PKG_CONFIG?=pkg-config -WAYLAND_PROTOCOLS=$(shell $(PKG_CONFIG) --variable=pkgdatadir wayland-protocols) -WAYLAND_SCANNER=$(shell $(PKG_CONFIG) --variable=wayland_scanner wayland-scanner) +WAYLAND_PROTOCOLS!=$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols +WAYLAND_SCANNER!=$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner -PKGS="scenefx-0.2" "wlroots-0.18" wayland-server xkbcommon -CFLAGS+=$(shell $(PKG_CONFIG) --cflags $(PKGS)) -LIBS=$(shell $(PKG_CONFIG) --libs $(PKGS)) +PKGS="scenefx-0.2" "wlroots-0.19" wayland-server xkbcommon +CFLAGS_PKG_CONFIG!=$(PKG_CONFIG) --cflags $(PKGS) +CFLAGS+=$(CFLAGS_PKG_CONFIG) +LIBS!=$(PKG_CONFIG) --libs $(PKGS) + +all: tinywl # wayland-scanner is a tool which generates C headers and rigging for Wayland # protocols, which are specified in XML. wlroots requires you to rig these up @@ -14,12 +17,11 @@ xdg-shell-protocol.h: $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ tinywl.o: tinywl.c xdg-shell-protocol.h - $(CC) -g -Werror $(CFLAGS) -I. -DWLR_USE_UNSTABLE -o $@ -c $< + $(CC) -c $< -g -Werror $(CFLAGS) -I. -DWLR_USE_UNSTABLE -o $@ tinywl: tinywl.o - $(CC) $< -g -Werror $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ + $(CC) $^ $> -g -Werror $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ clean: rm -f tinywl tinywl.o xdg-shell-protocol.h -.DEFAULT_GOAL=tinywl -.PHONY: clean +.PHONY: all clean diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c index 69d6789..61eb4ca 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -128,7 +128,7 @@ struct tinywl_keyboard { struct wl_listener destroy; }; -static void focus_toplevel(struct tinywl_toplevel *toplevel, struct wlr_surface *surface) { +static void focus_toplevel(struct tinywl_toplevel *toplevel) { /* Note: this function only deals with keyboard focus. */ if (toplevel == NULL) { return; @@ -136,6 +136,7 @@ static void focus_toplevel(struct tinywl_toplevel *toplevel, struct wlr_surface struct tinywl_server *server = toplevel->server; struct wlr_seat *seat = server->seat; struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; + struct wlr_surface *surface = toplevel->xdg_toplevel->base->surface; if (prev_surface == surface) { /* Don't re-focus an already focused surface. */ return; @@ -165,7 +166,7 @@ static void focus_toplevel(struct tinywl_toplevel *toplevel, struct wlr_surface * clients without additional work on your part. */ if (keyboard != NULL) { - wlr_seat_keyboard_notify_enter(seat, toplevel->xdg_toplevel->base->surface, + wlr_seat_keyboard_notify_enter(seat, surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); } } @@ -207,7 +208,7 @@ static bool handle_keybinding(struct tinywl_server *server, xkb_keysym_t sym) { } struct tinywl_toplevel *next_toplevel = wl_container_of(server->toplevels.prev, next_toplevel, link); - focus_toplevel(next_toplevel, next_toplevel->xdg_toplevel->base->surface); + focus_toplevel(next_toplevel); break; default: return false; @@ -396,7 +397,7 @@ static void reset_cursor_mode(struct tinywl_server *server) { server->grabbed_toplevel = NULL; } -static void process_cursor_move(struct tinywl_server *server, uint32_t time) { +static void process_cursor_move(struct tinywl_server *server) { /* Move the grabbed toplevel to the new position. */ struct tinywl_toplevel *toplevel = server->grabbed_toplevel; wlr_scene_node_set_position(&toplevel->scene_tree->node, @@ -404,7 +405,7 @@ static void process_cursor_move(struct tinywl_server *server, uint32_t time) { server->cursor->y - server->grab_y); } -static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { +static void process_cursor_resize(struct tinywl_server *server) { /* * Resizing the grabbed toplevel can be a little bit complicated, because we * could be resizing from any corner or edge. This not only resizes the @@ -446,10 +447,9 @@ static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { } } - struct wlr_box geo_box; - wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box); + struct wlr_box *geo_box = &toplevel->xdg_toplevel->base->geometry; wlr_scene_node_set_position(&toplevel->scene_tree->node, - new_left - geo_box.x, new_top - geo_box.y); + new_left - geo_box->x, new_top - geo_box->y); int new_width = new_right - new_left; int new_height = new_bottom - new_top; @@ -459,8 +459,8 @@ static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { struct wlr_box clip = { .width = new_width, .height = new_height, - .x = geo_box.x, - .y = geo_box.y, + .x = geo_box->x, + .y = geo_box->y, }; wlr_scene_subsurface_tree_set_clip(&toplevel->scene_tree->node, &clip); } @@ -468,10 +468,10 @@ static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { static void process_cursor_motion(struct tinywl_server *server, uint32_t time) { /* If the mode is non-passthrough, delegate to those functions. */ if (server->cursor_mode == TINYWL_CURSOR_MOVE) { - process_cursor_move(server, time); + process_cursor_move(server); return; } else if (server->cursor_mode == TINYWL_CURSOR_RESIZE) { - process_cursor_resize(server, time); + process_cursor_resize(server); return; } @@ -549,16 +549,16 @@ static void server_cursor_button(struct wl_listener *listener, void *data) { /* Notify the client with pointer focus that a button press has occurred */ wlr_seat_pointer_notify_button(server->seat, event->time_msec, event->button, event->state); - double sx, sy; - struct wlr_surface *surface = NULL; - struct tinywl_toplevel *toplevel = desktop_toplevel_at(server, - server->cursor->x, server->cursor->y, &surface, &sx, &sy); if (event->state == WL_POINTER_BUTTON_STATE_RELEASED) { /* If you released any buttons, we exit interactive move/resize mode. */ reset_cursor_mode(server); } else { /* Focus that client if the button was _pressed_ */ - focus_toplevel(toplevel, surface); + double sx, sy; + struct wlr_surface *surface = NULL; + struct tinywl_toplevel *toplevel = desktop_toplevel_at(server, + server->cursor->x, server->cursor->y, &surface, &sx, &sy); + focus_toplevel(toplevel); } } @@ -598,12 +598,11 @@ static void xdg_toplevel_commit(struct wl_listener *listener, void *data) { return; } - struct wlr_box geometry; - wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geometry); - wlr_scene_subsurface_tree_set_clip(&toplevel->xdg_scene_tree->node, &geometry); + struct wlr_box *geometry = &toplevel->xdg_toplevel->base->geometry; + wlr_scene_subsurface_tree_set_clip(&toplevel->xdg_scene_tree->node, geometry); - int border_width = geometry.width + (BORDER_THICKNESS * 2); - int border_height = geometry.height + (BORDER_THICKNESS * 2); + int border_width = geometry->width + (BORDER_THICKNESS * 2); + int border_height = geometry->height + (BORDER_THICKNESS * 2); // technically we dont actually need the hole here since optimized blur would // hide the border + shadow, but we do here to show compositors how to implement it @@ -612,7 +611,7 @@ static void xdg_toplevel_commit(struct wl_listener *listener, void *data) { wlr_scene_rect_set_clipped_region(toplevel->border, (struct clipped_region) { .corner_radius = toplevel->corner_radius, .corners = CORNER_LOCATION_ALL, - .area = { BORDER_THICKNESS, BORDER_THICKNESS, geometry.width, geometry.height } + .area = { BORDER_THICKNESS, BORDER_THICKNESS, geometry->width, geometry->height } }); int blur_sigma = toplevel->shadow->blur_sigma; @@ -826,7 +825,7 @@ static void xdg_toplevel_map(struct wl_listener *listener, void *data) { wl_list_insert(&toplevel->server->toplevels, &toplevel->link); - focus_toplevel(toplevel, toplevel->xdg_toplevel->base->surface); + focus_toplevel(toplevel); } static void xdg_toplevel_unmap(struct wl_listener *listener, void *data) { @@ -867,13 +866,7 @@ static void begin_interactive(struct tinywl_toplevel *toplevel, * compositor stops propegating pointer events to clients and instead * consumes them itself, to move or resize windows. */ struct tinywl_server *server = toplevel->server; - struct wlr_surface *focused_surface = - server->seat->pointer_state.focused_surface; - if (toplevel->xdg_toplevel->base->surface != - wlr_surface_get_root_surface(focused_surface)) { - /* Deny move/resize requests from unfocused clients. */ - return; - } + server->grabbed_toplevel = toplevel; server->cursor_mode = mode; @@ -881,17 +874,16 @@ static void begin_interactive(struct tinywl_toplevel *toplevel, server->grab_x = server->cursor->x - toplevel->scene_tree->node.x; server->grab_y = server->cursor->y - toplevel->scene_tree->node.y; } else { - struct wlr_box geo_box; - wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box); + struct wlr_box *geo_box = &toplevel->xdg_toplevel->base->geometry; - double border_x = (toplevel->scene_tree->node.x + geo_box.x) + - ((edges & WLR_EDGE_RIGHT) ? geo_box.width : 0); - double border_y = (toplevel->scene_tree->node.y + geo_box.y) + - ((edges & WLR_EDGE_BOTTOM) ? geo_box.height : 0); + double border_x = (toplevel->scene_tree->node.x + geo_box->x) + + ((edges & WLR_EDGE_RIGHT) ? geo_box->width : 0); + double border_y = (toplevel->scene_tree->node.y + geo_box->y) + + ((edges & WLR_EDGE_BOTTOM) ? geo_box->height : 0); server->grab_x = server->cursor->x - border_x; server->grab_y = server->cursor->y - border_y; - server->grab_geobox = geo_box; + server->grab_geobox = *geo_box; server->grab_geobox.x += toplevel->scene_tree->node.x; server->grab_geobox.y += toplevel->scene_tree->node.y; @@ -1263,9 +1255,26 @@ int main(int argc, char *argv[]) { socket); wl_display_run(server.wl_display); + /* Once wl_display_run returns, we destroy all clients then shut down the * server. */ wl_display_destroy_clients(server.wl_display); + + wl_list_remove(&server.new_xdg_toplevel.link); + wl_list_remove(&server.new_xdg_popup.link); + + wl_list_remove(&server.cursor_motion.link); + wl_list_remove(&server.cursor_motion_absolute.link); + wl_list_remove(&server.cursor_button.link); + wl_list_remove(&server.cursor_axis.link); + wl_list_remove(&server.cursor_frame.link); + + wl_list_remove(&server.new_input.link); + wl_list_remove(&server.request_cursor.link); + wl_list_remove(&server.request_set_selection.link); + + wl_list_remove(&server.new_output.link); + wlr_scene_node_destroy(&server.scene->tree.node); wlr_xcursor_manager_destroy(server.cursor_mgr); wlr_cursor_destroy(server.cursor); diff --git a/types/buffer/buffer.c b/types/buffer/buffer.c deleted file mode 100644 index bcb3092..0000000 --- a/types/buffer/buffer.c +++ /dev/null @@ -1,32 +0,0 @@ -#include "drm_fourcc.h" -#include "render/pixel_format.h" -#include "types/wlr_buffer.h" - -bool buffer_is_opaque(struct wlr_buffer *buffer) { - void *data; - uint32_t format; - size_t stride; - struct wlr_dmabuf_attributes dmabuf; - struct wlr_shm_attributes shm; - if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { - format = dmabuf.format; - } else if (wlr_buffer_get_shm(buffer, &shm)) { - format = shm.format; - } else if (wlr_buffer_begin_data_ptr_access(buffer, - WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { - bool opaque = false; - if (buffer->width == 1 && buffer->height == 1 && format == DRM_FORMAT_ARGB8888) { - // Special case for single-pixel-buffer-v1 - const uint8_t *argb8888 = data; // little-endian byte order - opaque = argb8888[3] == 0xFF; - } - wlr_buffer_end_data_ptr_access(buffer); - if (opaque) { - return true; - } - } else { - return false; - } - - return !pixel_format_has_alpha(format); -} diff --git a/types/meson.build b/types/meson.build index c0296eb..a7e855b 100644 --- a/types/meson.build +++ b/types/meson.build @@ -1,7 +1,6 @@ scenefx_files += files( 'scene/wlr_scene.c', 'output/wlr_output.c', - 'buffer/buffer.c', ) subdir('fx') diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 68937b2..3b54853 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -6,15 +6,17 @@ #include #include #include +#include +#include #include #include +#include #include #include #include #include #include #include -#include #include "scenefx/render/fx_renderer/fx_effect_framebuffers.h" #include "scenefx/render/fx_renderer/fx_renderer.h" @@ -22,7 +24,6 @@ #include "scenefx/types/fx/blur_data.h" #include "scenefx/types/fx/clipped_region.h" #include "scenefx/types/fx/corner_location.h" -#include "types/wlr_buffer.h" #include "types/wlr_output.h" #include "types/wlr_scene.h" #include "util/array.h" @@ -30,7 +31,14 @@ #include "util/time.h" #include "wlr/util/box.h" -#define HIGHLIGHT_DAMAGE_FADEOUT_TIME 250 +#include + +#if WLR_HAS_XWAYLAND +#include +#endif + +#define DMABUF_FEEDBACK_DEBOUNCE_FRAMES 30 +#define HIGHLIGHT_DAMAGE_FADEOUT_TIME 250 struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *node) { assert(node->type == WLR_SCENE_NODE_TREE); @@ -142,6 +150,13 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { 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)); } else if (node->type == WLR_SCENE_NODE_TREE) { struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); @@ -153,6 +168,8 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { } wl_list_remove(&scene->linux_dmabuf_v1_destroy.link); + wl_list_remove(&scene->gamma_control_manager_v1_destroy.link); + wl_list_remove(&scene->gamma_control_manager_v1_set_gamma.link); } else { assert(node->parent); } @@ -164,6 +181,8 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { } } + assert(wl_list_empty(&node->events.destroy.listener_list)); + wl_list_remove(&node->link); pixman_region32_fini(&node->visible); free(node); @@ -186,6 +205,8 @@ struct wlr_scene *wlr_scene_create(void) { wl_list_init(&scene->outputs); wl_list_init(&scene->linux_dmabuf_v1_destroy.link); + wl_list_init(&scene->gamma_control_manager_v1_destroy.link); + wl_list_init(&scene->gamma_control_manager_v1_set_gamma.link); const char *debug_damage_options[] = { "none", @@ -326,9 +347,14 @@ static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y, struct scene_update_data { pixman_region32_t *visible; pixman_region32_t *update_region; + struct wlr_box update_box; struct wl_list *outputs; bool calculate_visibility; +#if WLR_HAS_XWAYLAND + struct wlr_xwayland_surface *restack_above; +#endif + bool optimized_blur_dirty; }; @@ -344,11 +370,11 @@ static uint32_t region_area(pixman_region32_t *region) { return area; } -static void scale_output_damage(pixman_region32_t *damage, float scale) { - wlr_region_scale(damage, damage, scale); +static void scale_region(pixman_region32_t *region, float scale, bool round_up) { + wlr_region_scale(region, region, scale); - if (floor(scale) != scale) { - wlr_region_expand(damage, damage, 1); + if (round_up && floor(scale) != scale) { + wlr_region_expand(region, region, 1); } } @@ -366,55 +392,68 @@ struct render_data { bool has_blur; }; -static void transform_output_damage(pixman_region32_t *damage, const struct render_data *data) { +static void logical_to_buffer_coords(pixman_region32_t *region, const struct render_data *data, + bool round_up) { enum wl_output_transform transform = wlr_output_transform_invert(data->transform); - wlr_region_transform(damage, damage, transform, data->trans_width, data->trans_height); + scale_region(region, data->scale, round_up); + wlr_region_transform(region, region, transform, data->trans_width, data->trans_height); } -static void scene_output_damage(struct wlr_scene_output *scene_output, - const pixman_region32_t *region) { - if (wlr_damage_ring_add(&scene_output->damage_ring, region)) { - wlr_output_schedule_frame(scene_output->output); +static void output_to_buffer_coords(pixman_region32_t *damage, struct wlr_output *output) { + int width, height; + wlr_output_transformed_resolution(output, &width, &height); - struct wlr_output *output = scene_output->output; - enum wl_output_transform transform = - wlr_output_transform_invert(scene_output->output->transform); + wlr_region_transform(damage, damage, + wlr_output_transform_invert(output->transform), width, height); +} - int width = output->width; - int height = output->height; - if (transform & WL_OUTPUT_TRANSFORM_90) { - width = output->height; - height = output->width; - } +static int scale_length(int length, int offset, float scale) { + return round((offset + length) * scale) - round(offset * scale); +} + +static void scale_box(struct wlr_box *box, float scale) { + box->width = scale_length(box->width, box->x, scale); + box->height = scale_length(box->height, box->y, scale); + box->x = round(box->x * scale); + box->y = round(box->y * scale); +} - pixman_region32_t frame_damage; - pixman_region32_init(&frame_damage); - wlr_region_transform(&frame_damage, region, transform, width, height); +static void transform_output_box(struct wlr_box *box, const struct render_data *data) { + enum wl_output_transform transform = wlr_output_transform_invert(data->transform); + scale_box(box, data->scale); + wlr_box_transform(box, box, transform, data->trans_width, data->trans_height); +} + +static void scene_output_damage(struct wlr_scene_output *scene_output, + const pixman_region32_t *damage) { + struct wlr_output *output = scene_output->output; + + pixman_region32_t clipped; + pixman_region32_init(&clipped); + pixman_region32_intersect_rect(&clipped, damage, 0, 0, output->width, output->height); + + if (!pixman_region32_empty(&clipped)) { + wlr_output_schedule_frame(scene_output->output); + wlr_damage_ring_add(&scene_output->damage_ring, &clipped); pixman_region32_union(&scene_output->pending_commit_damage, - &scene_output->pending_commit_damage, &frame_damage); - pixman_region32_intersect_rect(&scene_output->pending_commit_damage, - &scene_output->pending_commit_damage, 0, 0, output->width, output->height); - pixman_region32_fini(&frame_damage); + &scene_output->pending_commit_damage, &clipped); } + + pixman_region32_fini(&clipped); } static void scene_output_damage_whole(struct wlr_scene_output *scene_output) { - struct wlr_damage_ring *ring = &scene_output->damage_ring; - - pixman_region32_t damage; - pixman_region32_init_rect(&damage, 0, 0, ring->width, ring->height); - scene_output_damage(scene_output, &damage); - pixman_region32_fini(&damage); -} + struct wlr_output *output = scene_output->output; -static void transform_output_box(struct wlr_box *box, const struct render_data *data) { - enum wl_output_transform transform = wlr_output_transform_invert(data->transform); - wlr_box_transform(box, box, transform, data->trans_width, data->trans_height); + pixman_region32_t damage; + pixman_region32_init_rect(&damage, 0, 0, output->width, output->height); + scene_output_damage(scene_output, &damage); + pixman_region32_fini(&damage); } static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *damage) { - if (!pixman_region32_not_empty(damage)) { + if (pixman_region32_empty(damage)) { return; } @@ -425,7 +464,8 @@ static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *dam pixman_region32_copy(&output_damage, damage); pixman_region32_translate(&output_damage, -scene_output->x, -scene_output->y); - scale_output_damage(&output_damage, scene_output->output->scale); + scale_region(&output_damage, scene_output->output->scale, true); + output_to_buffer_coords(&output_damage, scene_output->output); scene_output_damage(scene_output, &output_damage); pixman_region32_fini(&output_damage); } @@ -475,7 +515,7 @@ static void update_node_update_outputs(struct wlr_scene_node *node, pixman_region32_intersect_rect(&intersection, &node->visible, output_box.x, output_box.y, output_box.width, output_box.height); - if (pixman_region32_not_empty(&intersection)) { + if (!pixman_region32_empty(&intersection)) { uint32_t overlap = region_area(&intersection); if (overlap >= largest_overlap) { largest_overlap = overlap; @@ -539,6 +579,69 @@ static void update_node_update_outputs(struct wlr_scene_node *node, wl_signal_emit_mutable(&scene_buffer->events.outputs_update, &event); } +#if WLR_HAS_XWAYLAND +static struct wlr_xwayland_surface *scene_node_try_get_managed_xwayland_surface( + struct wlr_scene_node *node) { + if (node->type != WLR_SCENE_NODE_BUFFER) { + return NULL; + } + + struct wlr_scene_buffer *buffer_node = wlr_scene_buffer_from_node(node); + struct wlr_scene_surface *surface_node = wlr_scene_surface_try_from_buffer(buffer_node); + if (!surface_node) { + return NULL; + } + + struct wlr_xwayland_surface *xwayland_surface = + wlr_xwayland_surface_try_from_wlr_surface(surface_node->surface); + if (!xwayland_surface || xwayland_surface->override_redirect) { + return NULL; + } + + return xwayland_surface; +} + +static void restack_xwayland_surface(struct wlr_scene_node *node, + struct wlr_box *box, struct scene_update_data *data) { + struct wlr_xwayland_surface *xwayland_surface = + scene_node_try_get_managed_xwayland_surface(node); + if (!xwayland_surface) { + return; + } + + // ensure this node is entirely inside the update region. If not, we can't + // restack this node since we're not considering the whole thing. + if (wlr_box_contains_box(&data->update_box, box)) { + if (data->restack_above) { + wlr_xwayland_surface_restack(xwayland_surface, data->restack_above, XCB_STACK_MODE_BELOW); + } else { + wlr_xwayland_surface_restack(xwayland_surface, NULL, XCB_STACK_MODE_ABOVE); + } + } + + data->restack_above = xwayland_surface; +} + +static void restack_xwayland_surface_below(struct wlr_scene_node *node) { + if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + restack_xwayland_surface_below(child); + } + return; + } + + struct wlr_xwayland_surface *xwayland_surface = + scene_node_try_get_managed_xwayland_surface(node); + if (!xwayland_surface) { + return; + } + + wlr_xwayland_surface_restack(xwayland_surface, NULL, XCB_STACK_MODE_BELOW); +} +#endif + static bool scene_node_update_iterator(struct wlr_scene_node *node, int lx, int ly, void *_data) { struct scene_update_data *data = _data; @@ -571,6 +674,9 @@ static bool scene_node_update_iterator(struct wlr_scene_node *node, } update_node_update_outputs(node, data->outputs, NULL, NULL); +#if WLR_HAS_XWAYLAND + restack_xwayland_surface(node, &box, data); +#endif return false; } @@ -619,24 +725,23 @@ static void scene_update_region(struct wlr_scene *scene, pixman_region32_init(&visible); pixman_region32_copy(&visible, update_region); + struct pixman_box32 *region_box = pixman_region32_extents(update_region); struct scene_update_data data = { .visible = &visible, .update_region = update_region, + .update_box = { + .x = region_box->x1, + .y = region_box->y1, + .width = region_box->x2 - region_box->x1, + .height = region_box->y2 - region_box->y1, + }, .outputs = &scene->outputs, .calculate_visibility = scene->calculate_visibility, .optimized_blur_dirty = false, }; - struct pixman_box32 *region_box = pixman_region32_extents(update_region); - struct wlr_box box = { - .x = region_box->x1, - .y = region_box->y1, - .width = region_box->x2 - region_box->x1, - .height = region_box->y2 - region_box->y1, - }; - // update node visibility and output enter/leave events - scene_nodes_in_box(&scene->tree.node, &box, scene_node_update_iterator, &data); + scene_nodes_in_box(&scene->tree.node, &data.update_box, scene_node_update_iterator, &data); pixman_region32_fini(&visible); } @@ -647,6 +752,9 @@ static void scene_node_update(struct wlr_scene_node *node, int x, y; if (!wlr_scene_node_coords(node, &x, &y)) { +#if WLR_HAS_XWAYLAND + restack_xwayland_surface_below(node); +#endif if (damage) { scene_update_region(scene, damage); scene_damage_outputs(scene, damage); @@ -678,11 +786,13 @@ static void scene_node_update(struct wlr_scene_node *node, struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent, int width, int height, const float color[static 4]) { + assert(parent); + assert(width >= 0 && height >= 0); + struct wlr_scene_rect *scene_rect = calloc(1, sizeof(*scene_rect)); if (scene_rect == NULL) { return NULL; } - assert(parent); scene_node_init(&scene_rect->node, WLR_SCENE_NODE_RECT, parent); scene_rect->width = width; @@ -705,6 +815,8 @@ void wlr_scene_rect_set_size(struct wlr_scene_rect *rect, int width, int height) return; } + assert(width >= 0 && height >= 0); + rect->width = width; rect->height = height; scene_node_update(&rect->node, NULL); @@ -767,7 +879,7 @@ static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, scene_buffer->buffer = wlr_buffer_lock(buffer); scene_buffer->buffer_width = buffer->width; scene_buffer->buffer_height = buffer->height; - scene_buffer->buffer_is_opaque = buffer_is_opaque(buffer); + scene_buffer->buffer_is_opaque = wlr_buffer_is_opaque(buffer); scene_buffer->buffer_release.notify = scene_buffer_handle_buffer_release; wl_signal_add(&buffer->events.release, &scene_buffer->buffer_release); @@ -793,6 +905,18 @@ static void scene_buffer_set_texture(struct wlr_scene_buffer *scene_buffer, } } +static void scene_buffer_set_wait_timeline(struct wlr_scene_buffer *scene_buffer, + struct wlr_drm_syncobj_timeline *timeline, uint64_t point) { + wlr_drm_syncobj_timeline_unref(scene_buffer->wait_timeline); + if (timeline != NULL) { + scene_buffer->wait_timeline = wlr_drm_syncobj_timeline_ref(timeline); + scene_buffer->wait_point = point; + } else { + scene_buffer->wait_timeline = NULL; + scene_buffer->wait_point = 0; + } +} + void wlr_scene_rect_set_corner_radius(struct wlr_scene_rect *rect, int corner_radius, enum corner_location corners) { if (rect->corner_radius == corner_radius && rect->corners == corners) { @@ -977,6 +1101,7 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, wl_signal_init(&scene_buffer->events.output_leave); wl_signal_init(&scene_buffer->events.output_sample); wl_signal_init(&scene_buffer->events.frame_done); + pixman_region32_init(&scene_buffer->opaque_region); wl_list_init(&scene_buffer->buffer_release.link); wl_list_init(&scene_buffer->renderer_destroy.link); @@ -994,12 +1119,17 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, return scene_buffer; } -void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer, - struct wlr_buffer *buffer, const pixman_region32_t *damage) { +void wlr_scene_buffer_set_buffer_with_options(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer, const struct wlr_scene_buffer_set_buffer_options *options) { + const struct wlr_scene_buffer_set_buffer_options default_options = {0}; + if (options == NULL) { + options = &default_options; + } + // specifying a region for a NULL buffer doesn't make sense. We need to know // about the buffer to scale the buffer local coordinates down to scene // coordinates. - assert(buffer || !damage); + assert(buffer || !options->damage); bool mapped = buffer != NULL; bool prev_mapped = scene_buffer->buffer != NULL || scene_buffer->texture != NULL; @@ -1018,8 +1148,32 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff scene_buffer->buffer_height != buffer->height; } + // If this is a buffer change, check if it's a single pixel buffer. + // Cache that so we can still apply rendering optimisations even when + // the original buffer has been freed after texture upload. + if (buffer != scene_buffer->buffer) { + scene_buffer->is_single_pixel_buffer = false; + struct wlr_client_buffer *client_buffer = NULL; + if (buffer != NULL) { + client_buffer = wlr_client_buffer_get(buffer); + } + if (client_buffer != NULL && client_buffer->source != NULL) { + struct wlr_single_pixel_buffer_v1 *single_pixel_buffer = + wlr_single_pixel_buffer_v1_try_from_buffer(client_buffer->source); + if (single_pixel_buffer != NULL) { + scene_buffer->is_single_pixel_buffer = true; + scene_buffer->single_pixel_buffer_color[0] = single_pixel_buffer->r; + scene_buffer->single_pixel_buffer_color[1] = single_pixel_buffer->g; + scene_buffer->single_pixel_buffer_color[2] = single_pixel_buffer->b; + scene_buffer->single_pixel_buffer_color[3] = single_pixel_buffer->a; + } + } + } + scene_buffer_set_buffer(scene_buffer, buffer); scene_buffer_set_texture(scene_buffer, NULL); + scene_buffer_set_wait_timeline(scene_buffer, + options->wait_timeline, options->wait_point); if (update) { scene_node_update(&scene_buffer->node, NULL); @@ -1035,6 +1189,7 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff pixman_region32_t fallback_damage; pixman_region32_init_rect(&fallback_damage, 0, 0, buffer->width, buffer->height); + const pixman_region32_t *damage = options->damage; if (!damage) { damage = &fallback_damage; } @@ -1100,7 +1255,7 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff pixman_region32_t cull_region; pixman_region32_init(&cull_region); pixman_region32_copy(&cull_region, &scene_buffer->node.visible); - scale_output_damage(&cull_region, output_scale); + scale_region(&cull_region, output_scale, true); pixman_region32_translate(&cull_region, -lx * output_scale, -ly * output_scale); pixman_region32_intersect(&output_damage, &output_damage, &cull_region); pixman_region32_fini(&cull_region); @@ -1108,6 +1263,7 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff pixman_region32_translate(&output_damage, (int)round((lx - scene_output->x) * output_scale), (int)round((ly - scene_output->y) * output_scale)); + output_to_buffer_coords(&output_damage, scene_output->output); scene_output_damage(scene_output, &output_damage); pixman_region32_fini(&output_damage); } @@ -1116,9 +1272,17 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff pixman_region32_fini(&fallback_damage); } +void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer, const pixman_region32_t *damage) { + const struct wlr_scene_buffer_set_buffer_options options = { + .damage = damage, + }; + wlr_scene_buffer_set_buffer_with_options(scene_buffer, buffer, &options); +} + void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, struct wlr_buffer *buffer) { - wlr_scene_buffer_set_buffer_with_damage(scene_buffer, buffer, NULL); + wlr_scene_buffer_set_buffer_with_options(scene_buffer, buffer, NULL); } void wlr_scene_buffer_set_opaque_region(struct wlr_scene_buffer *scene_buffer, @@ -1148,6 +1312,7 @@ void wlr_scene_buffer_set_source_box(struct wlr_scene_buffer *scene_buffer, } if (box != NULL) { + assert(box->x >= 0 && box->y >= 0 && box->width >= 0 && box->height >= 0); scene_buffer->src_box = *box; } else { scene_buffer->src_box = (struct wlr_fbox){0}; @@ -1162,6 +1327,7 @@ void wlr_scene_buffer_set_dest_size(struct wlr_scene_buffer *scene_buffer, return; } + assert(width >= 0 && height >= 0); scene_buffer->dst_width = width; scene_buffer->dst_height = height; scene_node_update(&scene_buffer->node, NULL); @@ -1179,7 +1345,7 @@ void wlr_scene_buffer_set_transform(struct wlr_scene_buffer *scene_buffer, void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer, struct timespec *now) { - if (pixman_region32_not_empty(&scene_buffer->node.visible)) { + if (!pixman_region32_empty(&scene_buffer->node.visible)) { wl_signal_emit_mutable(&scene_buffer->events.frame_done, now); } } @@ -1190,6 +1356,7 @@ void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, return; } + assert(opacity >= 0 && opacity <= 1); scene_buffer->opacity = opacity; scene_node_update(&scene_buffer->node, NULL); } @@ -1304,17 +1471,6 @@ static void scene_node_get_size(struct wlr_scene_node *node, } } -static int scale_length(int length, int offset, float scale) { - return round((offset + length) * scale) - round(offset * scale); -} - -static void scale_box(struct wlr_box *box, float scale) { - box->width = scale_length(box->width, box->x, scale); - box->height = scale_length(box->height, box->y, scale); - box->x = round(box->x * scale); - box->y = round(box->y * scale); -} - void wlr_scene_node_set_enabled(struct wlr_scene_node *node, bool enabled) { if (node->enabled == enabled) { return; @@ -1534,7 +1690,6 @@ struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node, struct render_list_entry { struct wlr_scene_node *node; - bool sent_dmabuf_feedback; bool highlight_transparent_region; int x, y; }; @@ -1546,9 +1701,9 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren pixman_region32_init(&render_region); pixman_region32_copy(&render_region, &node->visible); pixman_region32_translate(&render_region, -data->logical.x, -data->logical.y); - scale_output_damage(&render_region, data->scale); + logical_to_buffer_coords(&render_region, data, true); pixman_region32_intersect(&render_region, &render_region, &data->damage); - if (!pixman_region32_not_empty(&render_region)) { + if (pixman_region32_empty(&render_region)) { pixman_region32_fini(&render_region); return; } @@ -1561,17 +1716,14 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren .y = y, }; scene_node_get_size(node, &dst_box.width, &dst_box.height); - scale_box(&dst_box, data->scale); + transform_output_box(&dst_box, data); pixman_region32_t opaque; pixman_region32_init(&opaque); scene_node_opaque_region(node, x, y, &opaque); - scale_output_damage(&opaque, data->scale); + logical_to_buffer_coords(&opaque, data, false); pixman_region32_subtract(&opaque, &render_region, &opaque); - transform_output_box(&dst_box, data); - transform_output_damage(&render_region, data); - enum wl_output_transform node_transform = wlr_output_transform_compose(WL_OUTPUT_TRANSFORM_NORMAL, data->transform); @@ -1587,13 +1739,13 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren corner_location_transform(node_transform, &rect_corners); // blur - bool has_alpha = pixman_region32_not_empty(&opaque); + bool has_alpha = !pixman_region32_empty(&opaque); if (has_alpha && scene_rect->backdrop_blur && is_scene_blur_enabled(&scene->blur_data)) { pixman_region32_t opaque_region; pixman_region32_init(&opaque_region); scene_node_opaque_region(node, x, y, &opaque_region); - scale_output_damage(&opaque_region, data->scale); + logical_to_buffer_coords(&opaque_region, data, false); /* TODO: should this be configurable? Borked when not 1.0, probably due to lack of premultiplication in the frag shader @@ -1737,6 +1889,22 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); enum corner_location buffer_corners = scene_buffer->corners; + if (scene_buffer->is_single_pixel_buffer) { + // Render the buffer as a rect, this is likely to be more efficient + wlr_render_pass_add_rect(&data->render_pass->base, &(struct wlr_render_rect_options){ + .box = dst_box, + .color = { + .r = (float)scene_buffer->single_pixel_buffer_color[0] / (float)UINT32_MAX, + .g = (float)scene_buffer->single_pixel_buffer_color[1] / (float)UINT32_MAX, + .b = (float)scene_buffer->single_pixel_buffer_color[2] / (float)UINT32_MAX, + .a = (float)scene_buffer->single_pixel_buffer_color[3] / + (float)UINT32_MAX * scene_buffer->opacity, + }, + .clip = &render_region, + }); + break; + } + struct wlr_texture *texture = scene_buffer_get_texture(scene_buffer, data->output->output->renderer); if (texture == NULL) { @@ -1754,9 +1922,9 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren pixman_region32_t opaque_region; pixman_region32_init(&opaque_region); - bool has_alpha = pixman_region32_not_empty(&opaque); + bool has_alpha = !pixman_region32_empty(&opaque); scene_node_opaque_region(node, x, y, &opaque_region); - scale_output_damage(&opaque_region, data->scale); + logical_to_buffer_coords(&opaque_region, data, false); if (has_alpha) { // Translate the opaque_region by the potential clipping offset. @@ -1804,8 +1972,10 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren .alpha = &scene_buffer->opacity, .filter_mode = scene_buffer->filter_mode, .blend_mode = !data->output->scene->calculate_visibility || - pixman_region32_not_empty(&opaque) ? + !pixman_region32_empty(&opaque) ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, + .wait_timeline = scene_buffer->wait_timeline, + .wait_point = scene_buffer->wait_point, }, .clip_box = &dst_box, .corners = buffer_corners, @@ -1820,11 +1990,11 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren }; wl_signal_emit_mutable(&scene_buffer->events.output_sample, &sample_event); if (entry->highlight_transparent_region) { - wlr_render_pass_add_rect(&data->render_pass->base, &(struct wlr_render_rect_options){ - .box = dst_box, - .color = { .r = 0, .g = 0.3, .b = 0, .a = 0.3 }, - .clip = &opaque, - }); + wlr_render_pass_add_rect(&data->render_pass->base, &(struct wlr_render_rect_options){ + .box = dst_box, + .color = { .r = 0, .g = 0.3, .b = 0, .a = 0.3 }, + .clip = &opaque, + }); } break; } @@ -1850,6 +2020,52 @@ void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene, wl_signal_add(&linux_dmabuf_v1->events.destroy, &scene->linux_dmabuf_v1_destroy); } +static void scene_handle_gamma_control_manager_v1_set_gamma(struct wl_listener *listener, + void *data) { + const struct wlr_gamma_control_manager_v1_set_gamma_event *event = data; + struct wlr_scene *scene = + wl_container_of(listener, scene, gamma_control_manager_v1_set_gamma); + struct wlr_scene_output *output = wlr_scene_get_scene_output(scene, event->output); + if (!output) { + // this scene might not own this output. + return; + } + + output->gamma_lut_changed = true; + output->gamma_lut = event->control; + wlr_output_schedule_frame(output->output); +} + +static void scene_handle_gamma_control_manager_v1_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene *scene = + wl_container_of(listener, scene, gamma_control_manager_v1_destroy); + wl_list_remove(&scene->gamma_control_manager_v1_destroy.link); + wl_list_init(&scene->gamma_control_manager_v1_destroy.link); + wl_list_remove(&scene->gamma_control_manager_v1_set_gamma.link); + wl_list_init(&scene->gamma_control_manager_v1_set_gamma.link); + scene->gamma_control_manager_v1 = NULL; + + struct wlr_scene_output *output; + wl_list_for_each(output, &scene->outputs, link) { + output->gamma_lut_changed = false; + output->gamma_lut = NULL; + } +} + +void wlr_scene_set_gamma_control_manager_v1(struct wlr_scene *scene, + struct wlr_gamma_control_manager_v1 *gamma_control) { + assert(scene->gamma_control_manager_v1 == NULL); + scene->gamma_control_manager_v1 = gamma_control; + + scene->gamma_control_manager_v1_destroy.notify = + scene_handle_gamma_control_manager_v1_destroy; + wl_signal_add(&gamma_control->events.destroy, &scene->gamma_control_manager_v1_destroy); + scene->gamma_control_manager_v1_set_gamma.notify = + scene_handle_gamma_control_manager_v1_set_gamma; + wl_signal_add(&gamma_control->events.set_gamma, &scene->gamma_control_manager_v1_set_gamma); +} + static void scene_output_handle_destroy(struct wlr_addon *addon) { struct wlr_scene_output *scene_output = wl_container_of(addon, scene_output, addon); @@ -1878,10 +2094,6 @@ static void scene_node_output_update(struct wlr_scene_node *node, static void scene_output_update_geometry(struct wlr_scene_output *scene_output, bool force_update) { - int ring_width, ring_height; - wlr_output_transformed_resolution(scene_output->output, &ring_width, &ring_height); - wlr_damage_ring_set_bounds(&scene_output->damage_ring, ring_width, ring_height); - scene_output_damage_whole(scene_output); scene_node_output_update(&scene_output->scene->tree.node, @@ -1921,13 +2133,31 @@ static void scene_output_handle_commit(struct wl_listener *listener, void *data) !wl_list_empty(&scene_output->damage_highlight_regions)) { wlr_output_schedule_frame(scene_output->output); } + + // Next time the output is enabled, try to re-apply the gamma LUT + if (scene_output->scene->gamma_control_manager_v1 && + (state->committed & WLR_OUTPUT_STATE_ENABLED) && + !scene_output->output->enabled) { + scene_output->gamma_lut_changed = true; + } } static void scene_output_handle_damage(struct wl_listener *listener, void *data) { struct wlr_scene_output *scene_output = wl_container_of(listener, scene_output, output_damage); + struct wlr_output *output = scene_output->output; struct wlr_output_event_damage *event = data; - scene_output_damage(scene_output, event->damage); + + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_copy(&damage, event->damage); + wlr_region_transform(&damage, &damage, + wlr_output_transform_invert(output->transform), width, height); + scene_output_damage(scene_output, &damage); + pixman_region32_fini(&damage); } static void scene_output_handle_needs_frame(struct wl_listener *listener, void *data) { @@ -1964,6 +2194,15 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, prev_output_link = ¤t_output->link; } + int drm_fd = wlr_backend_get_drm_fd(output->backend); + if (drm_fd >= 0 && output->backend->features.timeline && + output->renderer != NULL && output->renderer->features.timeline) { + scene_output->in_timeline = wlr_drm_syncobj_timeline_create(drm_fd); + if (scene_output->in_timeline == NULL) { + return NULL; + } + } + scene_output->index = prev_output_index + 1; assert(scene_output->index < 64); wl_list_insert(prev_output_link, &scene_output->link); @@ -2000,6 +2239,8 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { scene_node_output_update(&scene_output->scene->tree.node, &scene_output->scene->outputs, scene_output, NULL); + assert(wl_list_empty(&scene_output->events.destroy.listener_list)); + struct highlight_region *damage, *tmp_damage; wl_list_for_each_safe(damage, tmp_damage, &scene_output->damage_highlight_regions, link) { highlight_region_destroy(damage); @@ -2012,7 +2253,7 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { wl_list_remove(&scene_output->output_commit.link); wl_list_remove(&scene_output->output_damage.link); wl_list_remove(&scene_output->output_needs_frame.link); - + wlr_drm_syncobj_timeline_unref(scene_output->in_timeline); wl_array_release(&scene_output->render_list); free(scene_output); } @@ -2071,6 +2312,15 @@ struct render_list_constructor_data { bool fractional_scale; }; +static bool scene_buffer_is_black_opaque(struct wlr_scene_buffer *scene_buffer) { + return scene_buffer->is_single_pixel_buffer && + scene_buffer->single_pixel_buffer_color[0] == 0 && + scene_buffer->single_pixel_buffer_color[1] == 0 && + scene_buffer->single_pixel_buffer_color[2] == 0 && + scene_buffer->single_pixel_buffer_color[3] == UINT32_MAX && + scene_buffer->opacity == 1.0; +} + static bool construct_render_list_iterator(struct wlr_scene_node *node, int lx, int ly, void *_data) { struct render_list_constructor_data *data = _data; @@ -2093,12 +2343,22 @@ static bool construct_render_list_iterator(struct wlr_scene_node *node, } } + // Apply the same special-case to black opaque single-pixel buffers + if (node->type == WLR_SCENE_NODE_BUFFER && data->calculate_visibility && + (!data->fractional_scale || data->render_list->size == 0)) { + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + + if (scene_buffer_is_black_opaque(scene_buffer)) { + return false; + } + } + pixman_region32_t intersection; pixman_region32_init(&intersection); pixman_region32_intersect_rect(&intersection, &node->visible, data->box.x, data->box.y, data->box.width, data->box.height); - if (!pixman_region32_not_empty(&intersection)) { + if (pixman_region32_empty(&intersection)) { pixman_region32_fini(&intersection); return false; } @@ -2151,81 +2411,106 @@ static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene, wlr_linux_dmabuf_feedback_v1_finish(&feedback); } -static bool scene_entry_try_direct_scanout(struct render_list_entry *entry, - struct wlr_output_state *state, const struct render_data *data) { +enum scene_direct_scanout_result { + // This scene node is not a candidate for scanout + SCANOUT_INELIGIBLE, + + // This scene node is a candidate for scanout, but is currently + // incompatible due to e.g. buffer mismatch, and if possible we'd like to + // resolve this incompatibility. + SCANOUT_CANDIDATE, + + // Scanout is successful. + SCANOUT_SUCCESS, +}; + +static enum scene_direct_scanout_result scene_entry_try_direct_scanout( + struct render_list_entry *entry, struct wlr_output_state *state, + const struct render_data *data) { struct wlr_scene_output *scene_output = data->output; struct wlr_scene_node *node = entry->node; if (!scene_output->scene->direct_scanout) { - return false; + return SCANOUT_INELIGIBLE; } if (node->type != WLR_SCENE_NODE_BUFFER) { - return false; + return SCANOUT_INELIGIBLE; } if (state->committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_RENDER_FORMAT)) { // Legacy DRM will explode if we try to modeset with a direct scanout buffer - return false; + return SCANOUT_INELIGIBLE; } if (!wlr_output_is_direct_scanout_allowed(scene_output->output)) { - return false; + return SCANOUT_INELIGIBLE; } struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); - if (buffer->buffer == NULL) { - return false; + return SCANOUT_INELIGIBLE; } + // The native size of the buffer after any transform is applied int default_width = buffer->buffer->width; int default_height = buffer->buffer->height; wlr_output_transform_coords(buffer->transform, &default_width, &default_height); struct wlr_fbox default_box = { - .width = default_width, - .height = default_height, + .width = default_width, + .height = default_height, }; - if (!wlr_fbox_empty(&buffer->src_box) && - !wlr_fbox_equal(&buffer->src_box, &default_box)) { - return false; - } - if (buffer->transform != data->transform) { - return false; + return SCANOUT_INELIGIBLE; } - struct wlr_box node_box = { .x = entry->x, .y = entry->y }; - scene_node_get_size(node, &node_box.width, &node_box.height); - - if (!wlr_box_equal(&data->logical, &node_box)) { - return false; - } - - if (buffer->primary_output == scene_output) { + // 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. + if (scene_output->dmabuf_feedback_debounce >= DMABUF_FEEDBACK_DEBOUNCE_FRAMES + && buffer->primary_output == scene_output) { struct wlr_linux_dmabuf_feedback_v1_init_options options = { .main_renderer = scene_output->output->renderer, .scanout_primary_output = scene_output->output, }; scene_buffer_send_dmabuf_feedback(scene_output->scene, buffer, &options); - entry->sent_dmabuf_feedback = true; } struct wlr_output_state pending; wlr_output_state_init(&pending); if (!wlr_output_state_copy(&pending, state)) { - return false; + return SCANOUT_CANDIDATE; + } + + if (!wlr_fbox_empty(&buffer->src_box) && + !wlr_fbox_equal(&buffer->src_box, &default_box)) { + pending.buffer_src_box = buffer->src_box; } - wlr_output_state_set_buffer(&pending, buffer->buffer); + // Translate the position from scene coordinates to output coordinates + pending.buffer_dst_box.x = entry->x - scene_output->x; + pending.buffer_dst_box.y = entry->y - scene_output->y; + scene_node_get_size(node, &pending.buffer_dst_box.width, &pending.buffer_dst_box.height); + transform_output_box(&pending.buffer_dst_box, data); + + struct wlr_buffer *wlr_buffer = buffer->buffer; + struct wlr_client_buffer *client_buffer = wlr_client_buffer_get(wlr_buffer); + if (client_buffer != NULL && client_buffer->source != NULL && client_buffer->source->n_locks > 0) { + wlr_buffer = client_buffer->source; + } + + wlr_output_state_set_buffer(&pending, wlr_buffer); + if (buffer->wait_timeline != NULL) { + wlr_output_state_set_wait_timeline(&pending, buffer->wait_timeline, buffer->wait_point); + } if (!wlr_output_test_state(scene_output->output, &pending)) { wlr_output_state_finish(&pending); - return false; + return SCANOUT_CANDIDATE; } wlr_output_state_copy(state, &pending); @@ -2236,7 +2521,13 @@ static bool scene_entry_try_direct_scanout(struct render_list_entry *entry, .direct_scanout = true, }; wl_signal_emit_mutable(&buffer->events.output_sample, &sample_event); - return true; + return SCANOUT_SUCCESS; +} + +bool wlr_scene_output_needs_frame(struct wlr_scene_output *scene_output) { + return scene_output->output->needs_frame || + !pixman_region32_empty(&scene_output->pending_commit_damage) || + scene_output->gamma_lut_changed; } static void apply_blur_region(struct wlr_scene_node *node, @@ -2248,7 +2539,7 @@ static void apply_blur_region(struct wlr_scene_node *node, pixman_region32_init(&opaque_region); scene_node_opaque_region(node, x, y, &opaque_region); // Add the buffer to the blur_region if it's not fully opaque - if (!pixman_region32_not_empty(&opaque_region)) { + if (pixman_region32_empty(&opaque_region)) { struct wlr_box node_box = { .x = x, .y = y, @@ -2298,13 +2589,12 @@ static bool scene_output_has_blur(int list_len, break; } } - return pixman_region32_not_empty(blur_region); + return !pixman_region32_empty(blur_region); } bool wlr_scene_output_commit(struct wlr_scene_output *scene_output, const struct wlr_scene_output_state_options *options) { - if (!scene_output->output->needs_frame && !pixman_region32_not_empty( - &scene_output->pending_commit_damage)) { + if (!wlr_scene_output_needs_frame(scene_output)) { return true; } @@ -2325,6 +2615,35 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output, return ok; } +static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_output, + struct wlr_output_state *state) { + if (!scene_output->gamma_lut_changed) { + return; + } + + struct wlr_output_state gamma_pending = {0}; + if (!wlr_output_state_copy(&gamma_pending, state)) { + return; + } + + if (!wlr_gamma_control_v1_apply(scene_output->gamma_lut, &gamma_pending)) { + wlr_output_state_finish(&gamma_pending); + return; + } + + scene_output->gamma_lut_changed = false; + if (!wlr_output_test_state(scene_output->output, &gamma_pending)) { + wlr_gamma_control_v1_send_failed_and_destroy(scene_output->gamma_lut); + + scene_output->gamma_lut = NULL; + wlr_output_state_finish(&gamma_pending); + return; + } + + wlr_output_state_copy(state, &gamma_pending); + wlr_output_state_finish(&gamma_pending); +} + bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, struct wlr_output_state *state, const struct wlr_scene_output_state_options *options) { struct wlr_scene_output_state_options default_options = {0}; @@ -2400,9 +2719,6 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, struct render_list_entry *list_data = list_con.render_list->data; int list_len = list_con.render_list->size / sizeof(*list_data); - wlr_damage_ring_set_bounds(&scene_output->damage_ring, - render_data.trans_width, render_data.trans_height); - if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_RERENDER) { scene_output_damage_whole(scene_output); } @@ -2413,7 +2729,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, clock_gettime(CLOCK_MONOTONIC, &now); // add the current frame's damage if there is damage - if (pixman_region32_not_empty(&scene_output->damage_ring.current)) { + if (!pixman_region32_empty(&scene_output->damage_ring.current)) { struct highlight_region *current_damage = calloc(1, sizeof(*current_damage)); if (current_damage) { pixman_region32_init(¤t_damage->region); @@ -2436,7 +2752,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, struct timespec time_diff; timespec_sub(&time_diff, &now, &damage->when); if (timespec_to_msec(&time_diff) >= HIGHLIGHT_DAMAGE_FADEOUT_TIME || - !pixman_region32_not_empty(&damage->region)) { + pixman_region32_empty(&damage->region)) { highlight_region_destroy(damage); } } @@ -2451,10 +2767,24 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, // - There is only one entry in the render list // - There are no color transforms that need to be applied // - Damage highlight debugging is not enabled - bool scanout = options->color_transform == NULL && - list_len == 1 && debug_damage != WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT && - scene_entry_try_direct_scanout(&list_data[0], state, &render_data); + enum scene_direct_scanout_result scanout_result = SCANOUT_INELIGIBLE; + if (options->color_transform == NULL && list_len == 1 + && debug_damage != WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { + scanout_result = scene_entry_try_direct_scanout(&list_data[0], state, &render_data); + } + + if (scanout_result == SCANOUT_INELIGIBLE) { + if (scene_output->dmabuf_feedback_debounce > 0) { + // We cannot scan out, so count down towards sending composition dmabuf feedback + scene_output->dmabuf_feedback_debounce--; + } + } else if (scene_output->dmabuf_feedback_debounce < DMABUF_FEEDBACK_DEBOUNCE_FRAMES) { + // We either want to scan out or successfully scanned out, so count up towards sending + // scanout dmabuf feedback + scene_output->dmabuf_feedback_debounce++; + } + bool scanout = scanout_result == SCANOUT_SUCCESS; if (scene_output->prev_scanout != scanout) { scene_output->prev_scanout = scanout; wlr_log(WLR_DEBUG, "Direct scan-out %s", @@ -2462,6 +2792,8 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, } if (scanout) { + scene_output_state_attempt_gamma(scene_output, state); + if (timer) { struct timespec end_time, duration; clock_gettime(CLOCK_MONOTONIC, &end_time); @@ -2480,7 +2812,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, swapchain = output->swapchain; } - struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain, NULL); + struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain); if (buffer == NULL) { return false; } @@ -2496,12 +2828,15 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, timer->pre_render_duration = timespec_to_nsec(&duration); } + scene_output->in_point++; struct fx_gles_render_pass *render_pass = fx_renderer_begin_buffer_pass(output->renderer, buffer, output, &(struct fx_buffer_pass_options) { .base = &(struct wlr_buffer_pass_options){ .timer = timer ? timer->render_timer : NULL, .color_transform = options->color_transform, + .signal_timeline = scene_output->in_timeline, + .signal_point = scene_output->in_point, }, .swapchain = swapchain, } @@ -2593,7 +2928,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, pixman_region32_intersect(&opaque, &opaque, &entry->node->visible); pixman_region32_translate(&opaque, -scene_output->x, -scene_output->y); - wlr_region_scale(&opaque, &opaque, render_data.scale); + logical_to_buffer_coords(&opaque, &render_data, false); pixman_region32_subtract(&background, &background, &opaque); pixman_region32_fini(&opaque); } @@ -2607,7 +2942,6 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, } } - transform_output_damage(&background, &render_data); wlr_render_pass_add_rect(&render_pass->base, &(struct wlr_render_rect_options){ .box = { .width = buffer->width, .height = buffer->height }, .color = { .r = 0, .g = 0, .b = 0, .a = 1 }, @@ -2622,7 +2956,11 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, if (entry->node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(entry->node); - if (buffer->primary_output == scene_output && !entry->sent_dmabuf_feedback) { + // Direct scanout counts up to DMABUF_FEEDBACK_DEBOUNCE_FRAMES before sending new dmabuf + // feedback, and on composition we wait until it hits zero again. If we knew that an + // entry could never be a scanout candidate, we could send feedback to it + // unconditionally without debounce, but for now it is all or nothing + if (scene_output->dmabuf_feedback_debounce == 0 && buffer->primary_output == scene_output) { struct wlr_linux_dmabuf_feedback_v1_init_options options = { .main_renderer = output->renderer, .scanout_primary_output = NULL, @@ -2649,23 +2987,15 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, int64_t time_diff_ms = timespec_to_msec(&time_diff); float alpha = 1.0 - (double)time_diff_ms / HIGHLIGHT_DAMAGE_FADEOUT_TIME; - pixman_region32_t clip; - pixman_region32_init(&clip); - pixman_region32_copy(&clip, &damage->region); - transform_output_damage(&clip, &render_data); - wlr_render_pass_add_rect(&render_pass->base, &(struct wlr_render_rect_options){ .box = { .width = buffer->width, .height = buffer->height }, .color = { .r = alpha * 0.5, .g = 0, .b = 0, .a = alpha * 0.5 }, - .clip = &clip, + .clip = &damage->region, }); - - pixman_region32_fini(&clip); } } wlr_output_add_software_cursors_to_render_pass(output, &render_pass->base, &render_data.damage); - pixman_region32_fini(&render_data.damage); if (!wlr_render_pass_submit(&render_pass->base)) { @@ -2680,6 +3010,13 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, wlr_output_state_set_buffer(state, buffer); wlr_buffer_unlock(buffer); + if (scene_output->in_timeline != NULL) { + wlr_output_state_set_wait_timeline(state, scene_output->in_timeline, + scene_output->in_point); + } + + scene_output_state_attempt_gamma(scene_output, state); + return true; } diff --git a/util/matrix.c b/util/matrix.c new file mode 100644 index 0000000..640787c --- /dev/null +++ b/util/matrix.c @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include "util/matrix.h" + +void wlr_matrix_identity(float mat[static 9]) { + static const float identity[9] = { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }; + memcpy(mat, identity, sizeof(identity)); +} + +void wlr_matrix_multiply(float mat[static 9], const float a[static 9], + const float b[static 9]) { + float product[9]; + + product[0] = a[0]*b[0] + a[1]*b[3] + a[2]*b[6]; + product[1] = a[0]*b[1] + a[1]*b[4] + a[2]*b[7]; + product[2] = a[0]*b[2] + a[1]*b[5] + a[2]*b[8]; + + product[3] = a[3]*b[0] + a[4]*b[3] + a[5]*b[6]; + product[4] = a[3]*b[1] + a[4]*b[4] + a[5]*b[7]; + product[5] = a[3]*b[2] + a[4]*b[5] + a[5]*b[8]; + + product[6] = a[6]*b[0] + a[7]*b[3] + a[8]*b[6]; + product[7] = a[6]*b[1] + a[7]*b[4] + a[8]*b[7]; + product[8] = a[6]*b[2] + a[7]*b[5] + a[8]*b[8]; + + memcpy(mat, product, sizeof(product)); +} + +void wlr_matrix_translate(float mat[static 9], float x, float y) { + float translate[9] = { + 1.0f, 0.0f, x, + 0.0f, 1.0f, y, + 0.0f, 0.0f, 1.0f, + }; + wlr_matrix_multiply(mat, mat, translate); +} + +void wlr_matrix_scale(float mat[static 9], float x, float y) { + float scale[9] = { + x, 0.0f, 0.0f, + 0.0f, y, 0.0f, + 0.0f, 0.0f, 1.0f, + }; + wlr_matrix_multiply(mat, mat, scale); +} + +static const float transforms[][9] = { + [WL_OUTPUT_TRANSFORM_NORMAL] = { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_90] = { + 0.0f, 1.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_180] = { + -1.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_270] = { + 0.0f, -1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED] = { + -1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED_90] = { + 0.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED_180] = { + 1.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED_270] = { + 0.0f, -1.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, +}; + +void wlr_matrix_transform(float mat[static 9], + enum wl_output_transform transform) { + wlr_matrix_multiply(mat, mat, transforms[transform]); +} + +void matrix_projection(float mat[static 9], int width, int height, + enum wl_output_transform transform) { + memset(mat, 0, sizeof(*mat) * 9); + + const float *t = transforms[transform]; + float x = 2.0f / width; + float y = 2.0f / height; + + // Rotation + reflection + mat[0] = x * t[0]; + mat[1] = x * t[1]; + mat[3] = y * -t[3]; + mat[4] = y * -t[4]; + + // Translation + mat[2] = -copysign(1.0f, mat[0] + mat[1]); + mat[5] = -copysign(1.0f, mat[3] + mat[4]); + + // Identity + mat[8] = 1.0f; +} + +void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box, + enum wl_output_transform transform, const float projection[static 9]) { + int x = box->x; + int y = box->y; + int width = box->width; + int height = box->height; + + wlr_matrix_identity(mat); + wlr_matrix_translate(mat, x, y); + + wlr_matrix_scale(mat, width, height); + + if (transform != WL_OUTPUT_TRANSFORM_NORMAL) { + wlr_matrix_translate(mat, 0.5, 0.5); + wlr_matrix_transform(mat, transform); + wlr_matrix_translate(mat, -0.5, -0.5); + } + + wlr_matrix_multiply(mat, projection, mat); +} + +void matrix_invert(float out[static 9], float m[static 9]) { + float a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5], g = m[6], h = m[7], i = m[8]; + + // See: https://en.wikipedia.org/wiki/Determinant + float det = a*e*i + b*f*g + c*d*h - c*e*g - b*d*i - a*f*h; + assert(det != 0); + float inv_det = 1 / det; + + // See: https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices + float result[] = { + inv_det * (e*i - f*h), + inv_det * -(b*i - c*h), + inv_det * (b*f - c*e), + inv_det * -(d*i - f*g), + inv_det * (a*i - c*g), + inv_det * -(a*f - c*d), + inv_det * (d*h - e*g), + inv_det * -(a*h - b*g), + inv_det * (a*e - b*d), + }; + memcpy(out, result, sizeof(result)); +} diff --git a/util/meson.build b/util/meson.build index c81d15c..63033d1 100644 --- a/util/meson.build +++ b/util/meson.build @@ -1,5 +1,6 @@ scenefx_files += files( 'array.c', 'env.c', + 'matrix.c', 'time.c', ) diff --git a/util/time.c b/util/time.c index bc4a106..1a8f329 100644 --- a/util/time.c +++ b/util/time.c @@ -3,8 +3,6 @@ #include "util/time.h" -static const long NSEC_PER_SEC = 1000000000; - int64_t timespec_to_msec(const struct timespec *a) { return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; }