diff options
author | Erik Reider <[email protected]> | 2024-01-06 02:31:14 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2024-01-06 02:31:14 +0100 |
commit | 6759e8da7ab53a46b0eb04e5045b8c67262c3a11 (patch) | |
tree | c65ed83ca04b61bdbae7e1b8a7f2c16f29b89730 | |
parent | b929a2bbadf467864796ad4ec90882ce86cfebff (diff) | |
parent | ace97585b2b4d8cbb5ead6cd0f72fa8e8889c9d7 (diff) |
Merge pull request #24 from wlrfx/wlroots-0.17-rebase
Rebase to wlroots 0.17
47 files changed, 4876 insertions, 2415 deletions
diff --git a/include/meson.build b/include/meson.build deleted file mode 100644 index 4c388f0..0000000 --- a/include/meson.build +++ /dev/null @@ -1 +0,0 @@ -exclude_files = ['meson.build', 'config.h.in', 'version.h.in'] diff --git a/include/render/egl.h b/include/render/egl.h new file mode 100644 index 0000000..e8b8596 --- /dev/null +++ b/include/render/egl.h @@ -0,0 +1,117 @@ +#ifndef RENDER_EGL_H +#define RENDER_EGL_H + +#include <wlr/render/egl.h> + +struct wlr_egl { + EGLDisplay display; + EGLContext context; + EGLDeviceEXT device; // may be EGL_NO_DEVICE_EXT + struct gbm_device *gbm_device; + + struct { + // Display extensions + bool KHR_image_base; + bool EXT_image_dma_buf_import; + bool EXT_image_dma_buf_import_modifiers; + bool IMG_context_priority; + bool EXT_create_context_robustness; + + // Device extensions + bool EXT_device_drm; + bool EXT_device_drm_render_node; + + // Client extensions + bool EXT_device_query; + bool KHR_platform_gbm; + bool EXT_platform_device; + bool KHR_display_reference; + } exts; + + struct { + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT; + PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; + PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT; + PFNEGLDEBUGMESSAGECONTROLKHRPROC eglDebugMessageControlKHR; + PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT; + PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT; + PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT; + } procs; + + bool has_modifiers; + struct wlr_drm_format_set dmabuf_texture_formats; + struct wlr_drm_format_set dmabuf_render_formats; +}; + +struct wlr_egl_context { + EGLDisplay display; + EGLContext context; + EGLSurface draw_surface; + EGLSurface read_surface; +}; + +/** + * Initializes an EGL context for the given DRM FD. + * + * Will attempt to load all possibly required API functions. + */ +struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd); + +/** + * Frees all related EGL resources, makes the context not-current and + * unbinds a bound wayland display. + */ +void wlr_egl_destroy(struct wlr_egl *egl); + +/** + * Creates an EGL image from the given dmabuf attributes. Check usability + * of the dmabuf with wlr_egl_check_import_dmabuf once first. + */ +EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_attributes *attributes, bool *external_only); + +/** + * Get DMA-BUF formats suitable for sampling usage. + */ +const struct wlr_drm_format_set *wlr_egl_get_dmabuf_texture_formats( + struct wlr_egl *egl); +/** + * Get DMA-BUF formats suitable for rendering usage. + */ +const struct wlr_drm_format_set *wlr_egl_get_dmabuf_render_formats( + struct wlr_egl *egl); + +/** + * Destroys an EGL image created with the given wlr_egl. + */ +bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImageKHR image); + +int wlr_egl_dup_drm_fd(struct wlr_egl *egl); + +/** + * Save the current EGL context to the structure provided in the argument. + * + * This includes display, context, draw surface and read surface. + */ +void wlr_egl_save_context(struct wlr_egl_context *context); + +/** + * Restore EGL context that was previously saved using wlr_egl_save_current(). + */ +bool wlr_egl_restore_context(struct wlr_egl_context *context); + +/** + * Make the EGL context current. + * + * Callers are expected to clear the current context when they are done by + * calling wlr_egl_unset_current(). + */ +bool wlr_egl_make_current(struct wlr_egl *egl); + +bool wlr_egl_unset_current(struct wlr_egl *egl); + +bool wlr_egl_is_current(struct wlr_egl *egl); + +#endif diff --git a/include/render/fx_renderer/fx_renderer.h b/include/render/fx_renderer/fx_renderer.h index f569aa9..4d99866 100644 --- a/include/render/fx_renderer/fx_renderer.h +++ b/include/render/fx_renderer/fx_renderer.h @@ -4,72 +4,141 @@ #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> #include <stdbool.h> +#include <time.h> #include <wlr/render/egl.h> +#include <wlr/render/interface.h> #include <wlr/render/wlr_texture.h> #include <wlr/util/addon.h> #include <wlr/util/box.h> -#include "render/fx_renderer/fx_stencilbuffer.h" + +#include "render/fx_renderer/shaders.h" +#include "render/pass.h" #include "types/fx/shadow_data.h" -enum fx_tex_shader_source { - SHADER_SOURCE_TEXTURE_RGBA = 1, - SHADER_SOURCE_TEXTURE_RGBX = 2, - SHADER_SOURCE_TEXTURE_EXTERNAL = 3, +struct fx_pixel_format { + uint32_t drm_format; + // optional field, if empty then internalformat = format + GLint gl_internalformat; + GLint gl_format, gl_type; + bool has_alpha; }; -struct quad_shader { - GLuint program; - GLint proj; - GLint color; - GLint pos_attrib; -}; +bool is_fx_pixel_format_supported(const struct fx_renderer *renderer, + const struct fx_pixel_format *format); +const struct fx_pixel_format *get_fx_format_from_drm(uint32_t fmt); +const struct fx_pixel_format *get_fx_format_from_gl( + GLint gl_format, GLint gl_type, bool alpha); +const uint32_t *get_fx_shm_formats(const struct fx_renderer *renderer, + size_t *len); + +/// +/// fx_framebuffer +/// + +struct fx_framebuffer { + struct wlr_buffer *buffer; + struct fx_renderer *renderer; + struct wl_list link; // fx_renderer.buffers + + EGLImageKHR image; + GLuint rbo; + GLuint fbo; + GLuint sb; // Stencil -struct tex_shader { - GLuint program; - GLint proj; - GLint tex; - GLint alpha; - GLint pos_attrib; - GLint tex_attrib; - GLint size; - GLint position; - GLint radius; + struct wlr_addon addon; }; -struct stencil_mask_shader { - GLuint program; - GLint proj; - GLint color; - GLint pos_attrib; - GLint half_size; - GLint position; - GLint radius; +struct fx_framebuffer *fx_framebuffer_get_or_create(struct fx_renderer *renderer, + struct wlr_buffer *wlr_buffer); + +void fx_framebuffer_bind(struct fx_framebuffer *buffer); + +void fx_framebuffer_bind_wlr_fbo(struct fx_renderer *renderer); + +void fx_framebuffer_destroy(struct fx_framebuffer *buffer); + +/// +/// fx_texture +/// + +struct fx_texture { + struct wlr_texture wlr_texture; + struct fx_renderer *fx_renderer; + struct wl_list link; // fx_renderer.textures + + // Basically: + // GL_TEXTURE_2D == mutable + // GL_TEXTURE_EXTERNAL_OES == immutable + GLuint target; + GLuint tex; + + EGLImageKHR image; + + bool has_alpha; + + // Only affects target == GL_TEXTURE_2D + uint32_t drm_format; // used to interpret upload data + // If imported from a wlr_buffer + struct wlr_buffer *buffer; + struct wlr_addon buffer_addon; }; -struct box_shadow_shader { - GLuint program; - GLint proj; - GLint color; - GLint pos_attrib; - GLint position; - GLint size; - GLint blur_sigma; - GLint corner_radius; +struct fx_texture_attribs { + GLenum target; /* either GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES */ + GLuint tex; + + bool has_alpha; }; -struct fx_renderer { - float projection[9]; +struct fx_texture *fx_get_texture(struct wlr_texture *wlr_texture); - struct fx_stencilbuffer stencil_buffer; +struct wlr_texture *fx_texture_from_buffer(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *buffer); - struct wlr_addon addon; +void fx_texture_destroy(struct fx_texture *texture); + +bool wlr_texture_is_fx(struct wlr_texture *wlr_texture); +void fx_texture_get_attribs(struct wlr_texture *texture, + struct fx_texture_attribs *attribs); + +/// +/// fx_renderer +/// + +struct fx_renderer { + struct wlr_renderer wlr_renderer; + + float projection[9]; + struct wlr_egl *egl; + int drm_fd; + + const char *exts_str; struct { + bool EXT_read_format_bgra; + bool KHR_debug; bool OES_egl_image_external; + bool OES_egl_image; + bool EXT_texture_type_2_10_10_10_REV; + bool OES_texture_half_float_linear; + bool EXT_texture_norm16; + bool EXT_disjoint_timer_query; } exts; struct { PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; + PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR; + PFNGLDEBUGMESSAGECONTROLKHRPROC glDebugMessageControlKHR; + PFNGLPOPDEBUGGROUPKHRPROC glPopDebugGroupKHR; + PFNGLPUSHDEBUGGROUPKHRPROC glPushDebugGroupKHR; + PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES; + PFNGLGETGRAPHICSRESETSTATUSKHRPROC glGetGraphicsResetStatusKHR; + PFNGLGENQUERIESEXTPROC glGenQueriesEXT; + PFNGLDELETEQUERIESEXTPROC glDeleteQueriesEXT; + PFNGLQUERYCOUNTEREXTPROC glQueryCounterEXT; + PFNGLGETQUERYOBJECTIVEXTPROC glGetQueryObjectivEXT; + PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64vEXT; + PFNGLGETINTEGER64VEXTPROC glGetInteger64vEXT; } procs; struct { @@ -80,48 +149,47 @@ struct fx_renderer { struct box_shadow_shader box_shadow; struct stencil_mask_shader stencil_mask; } shaders; -}; - -void fx_renderer_init_addon(struct wlr_egl *egl, struct wlr_addon_set *addons, - const void * owner); - -struct fx_renderer *fx_renderer_addon_find(struct wlr_addon_set *addons, - const void * owner); - -struct fx_renderer *fx_renderer_create(struct wlr_egl *egl); -void fx_renderer_fini(struct fx_renderer *renderer); + struct wl_list buffers; // fx_framebuffer.link + struct wl_list textures; // fx_texture.link -void fx_renderer_begin(struct fx_renderer *renderer, int width, int height); - -void fx_renderer_clear(const float color[static 4]); - -void fx_renderer_scissor(struct wlr_box *box); - -// Initialize the stenciling work -void fx_renderer_stencil_mask_init(void); - -// Close the mask -void fx_renderer_stencil_mask_close(bool draw_inside_mask); - -// Finish stenciling and clear the buffer -void fx_renderer_stencil_mask_fini(void); - -void fx_renderer_stencil_enable(void); - -void fx_renderer_stencil_disable(void); - -bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, - struct wlr_texture *wlr_texture, const struct wlr_fbox *src_box, - const struct wlr_box *dst_box, const float matrix[static 9], - float opacity, int corner_radius); + struct fx_framebuffer *current_buffer; + uint32_t viewport_width, viewport_height; +}; -void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box, - const float color[static 4], const float projection[static 9]); +bool wlr_renderer_is_fx(struct wlr_renderer *wlr_renderer); + +struct fx_renderer *fx_get_renderer( + struct wlr_renderer *wlr_renderer); +struct fx_render_timer *fx_get_render_timer( + struct wlr_render_timer *timer); +struct fx_texture *fx_get_texture( + struct wlr_texture *wlr_texture); + +struct wlr_renderer *fx_renderer_create_with_drm_fd(int drm_fd); +struct wlr_renderer *fx_renderer_create(struct wlr_backend *backend); +struct wlr_renderer *fx_renderer_create_egl(struct wlr_egl *egl); + +struct wlr_egl *wlr_fx_renderer_get_egl(struct wlr_renderer *renderer); + +void push_fx_debug_(struct fx_renderer *renderer, + const char *file, const char *func); +#define push_fx_debug(renderer) push_fx_debug_(renderer, _WLR_FILENAME, __func__) +void pop_fx_debug(struct fx_renderer *renderer); + +/// +/// Render Timer +/// + +struct fx_render_timer { + struct wlr_render_timer base; + struct fx_renderer *renderer; + struct timespec cpu_start; + struct timespec cpu_end; + GLuint id; + GLint64 gl_cpu_end; +}; -void fx_render_box_shadow(struct fx_renderer *renderer, - const struct wlr_box *box, const struct wlr_box *stencil_box, - const float matrix[static 9], int corner_radius, - struct shadow_data *shadow_data); +bool wlr_render_timer_is_fx(struct wlr_render_timer *timer); #endif diff --git a/include/render/fx_renderer/fx_stencilbuffer.h b/include/render/fx_renderer/fx_stencilbuffer.h deleted file mode 100644 index 6909f96..0000000 --- a/include/render/fx_renderer/fx_stencilbuffer.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef FX_STENCILBUFFER_H -#define FX_STENCILBUFFER_H - -#include <GLES2/gl2.h> -#include <stdbool.h> -#include <wlr/render/wlr_texture.h> - -struct fx_stencilbuffer { - GLuint rb; - int width; - int height; -}; - -struct fx_stencilbuffer fx_stencilbuffer_create(void); - -void fx_stencilbuffer_init(struct fx_stencilbuffer *stencil_buffer, int width, int height); - -void fx_stencilbuffer_release(struct fx_stencilbuffer *stencil_buffer); - -#endif diff --git a/include/render/fx_renderer/matrix.h b/include/render/fx_renderer/matrix.h index 6931e8d..c3bae42 100644 --- a/include/render/fx_renderer/matrix.h +++ b/include/render/fx_renderer/matrix.h @@ -3,7 +3,13 @@ #include <wlr/types/wlr_output.h> +/** + * 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); + enum wl_output_transform transform); #endif diff --git a/include/render/fx_renderer/shaders.h b/include/render/fx_renderer/shaders.h new file mode 100644 index 0000000..92a14d5 --- /dev/null +++ b/include/render/fx_renderer/shaders.h @@ -0,0 +1,65 @@ +#ifndef _FX_SHADERS_H +#define _FX_SHADERS_H + +#include <GLES2/gl2.h> +#include <stdbool.h> + +struct fx_renderer; + +GLuint compile_shader(GLuint type, const GLchar *src); + +GLuint link_program(const GLchar *frag_src); + +bool check_gl_ext(const char *exts, const char *ext); + +void load_gl_proc(void *proc_ptr, const char *name); + +enum fx_tex_shader_source { + SHADER_SOURCE_TEXTURE_RGBA = 1, + SHADER_SOURCE_TEXTURE_RGBX = 2, + SHADER_SOURCE_TEXTURE_EXTERNAL = 3, +}; + +struct quad_shader { + GLuint program; + GLint proj; + GLint color; + GLint pos_attrib; +}; + +struct tex_shader { + GLuint program; + GLint proj; + GLint tex_proj; + GLint tex; + GLint alpha; + GLint pos_attrib; + GLint size; + GLint position; + GLint radius; +}; + +struct stencil_mask_shader { + GLuint program; + GLint proj; + GLint color; + GLint pos_attrib; + GLint half_size; + GLint position; + GLint radius; +}; + +struct box_shadow_shader { + GLuint program; + GLint proj; + GLint color; + GLint pos_attrib; + GLint position; + GLint size; + GLint blur_sigma; + GLint corner_radius; +}; + +bool link_shaders(struct fx_renderer *renderer); + +#endif diff --git a/include/render/fx_renderer/util.h b/include/render/fx_renderer/util.h new file mode 100644 index 0000000..c0afc69 --- /dev/null +++ b/include/render/fx_renderer/util.h @@ -0,0 +1,11 @@ +#ifndef _FX_UTIL_H +#define _FX_UTIL_H + +#include <stdbool.h> +#include <stdlib.h> +#include <wlr/backend/interface.h> + +bool open_preferred_drm_fd(struct wlr_backend *backend, int *drm_fd_ptr, + bool *own_drm_fd); + +#endif diff --git a/include/render/pass.h b/include/render/pass.h new file mode 100644 index 0000000..05bf1e9 --- /dev/null +++ b/include/render/pass.h @@ -0,0 +1,69 @@ +#ifndef FX_RENDER_PASS_H +#define FX_RENDER_PASS_H + +#include <stdbool.h> +#include <wlr/render/pass.h> +#include <wlr/util/box.h> +#include <wlr/render/interface.h> +#include "types/fx/shadow_data.h" + +struct fx_gles_render_pass { + struct wlr_render_pass base; + struct fx_framebuffer *buffer; + float projection_matrix[9]; + struct fx_render_timer *timer; +}; + +/** + * Begin a new render pass with the supplied destination buffer. + * + * Callers must call wlr_render_pass_submit() once they are done with the + * render pass. + */ +struct fx_gles_render_pass *fx_renderer_begin_buffer_pass(struct wlr_renderer *renderer, + struct wlr_buffer *buffer, const struct wlr_buffer_pass_options *options); + +struct fx_render_texture_options { + struct wlr_render_texture_options base; + float scale; + struct wlr_box *clip_box; // Used to clip csd. Ignored if NULL + int corner_radius; +}; + +struct fx_render_texture_options fx_render_texture_options_default( + const struct wlr_render_texture_options *base); + +struct fx_render_rect_options { + struct wlr_render_rect_options base; + float scale; +}; + +struct fx_render_rect_options fx_render_rect_options_default( + const struct wlr_render_rect_options *base); + +/** + * Render a fx texture. + */ +void fx_render_pass_add_texture(struct fx_gles_render_pass *render_pass, + const struct fx_render_texture_options *options); + +/** + * Render a rectangle. + */ +void fx_render_pass_add_rect(struct fx_gles_render_pass *render_pass, + const struct fx_render_rect_options *options); + +/** + * Render a stencil mask. + */ +void fx_render_pass_add_stencil_mask(struct fx_gles_render_pass *pass, + const struct fx_render_rect_options *fx_options, int corner_radius); + +/** + * Render a box shadow. + */ +void fx_render_pass_add_box_shadow(struct fx_gles_render_pass *pass, + const struct fx_render_rect_options *fx_options, + int corner_radius, struct shadow_data *shadow_data); + +#endif diff --git a/include/render/pixel_format.h b/include/render/pixel_format.h index d045b6a..6ca11f3 100644 --- a/include/render/pixel_format.h +++ b/include/render/pixel_format.h @@ -1,26 +1,64 @@ -#ifndef RENDER_PIXEL_FORMAT_H -#define RENDER_PIXEL_FORMAT_H +#ifndef FX_RENDER_PIXEL_FORMAT_H +#define FX_RENDER_PIXEL_FORMAT_H #include <wayland-server-protocol.h> +/** + * Information about a pixel format. + * + * A pixel format is identified via its DRM four character code (see <drm_fourcc.h>). + * + * Simple formats have a block size of 1×1 pixels and bytes_per_block contains + * the number of bytes per pixel (including padding). + * + * Tiled formats (e.g. sub-sampled YCbCr) are described with a block size + * greater than 1×1 pixels. A block is a rectangle of pixels which are stored + * next to each other in a byte-aligned memory region. + */ struct wlr_pixel_format_info { - uint32_t drm_format; + uint32_t drm_format; - /* Equivalent of the format if it has an alpha channel, - * DRM_FORMAT_INVALID (0) if NA - */ - uint32_t opaque_substitute; + /* Equivalent of the format if it has an alpha channel, + * DRM_FORMAT_INVALID (0) if NA + */ + uint32_t opaque_substitute; - /* Bits per pixels */ - uint32_t bpp; + /* Bytes per block (including padding) */ + uint32_t bytes_per_block; + /* Size of a block in pixels (zero for 1×1) */ + uint32_t block_width, block_height; - /* True if the format has an alpha channel */ - bool has_alpha; + /* True if the format has an alpha channel */ + bool has_alpha; }; +/** + * Get pixel format information from a DRM FourCC. + * + * NULL is returned if the pixel format is unknown. + */ const struct wlr_pixel_format_info *drm_get_pixel_format_info(uint32_t fmt); +/** + * Get the number of pixels per block for a pixel format. + */ +uint32_t pixel_format_info_pixels_per_block(const struct wlr_pixel_format_info *info); +/** + * Get the minimum stride for a given pixel format and width. + */ +int32_t pixel_format_info_min_stride(const struct wlr_pixel_format_info *info, int32_t width); +/** + * Check whether a stride is large enough for a given pixel format and width. + */ +bool pixel_format_info_check_stride(const struct wlr_pixel_format_info *info, + int32_t stride, int32_t width); +/** + * Convert an enum wl_shm_format to a DRM FourCC. + */ uint32_t convert_wl_shm_format_to_drm(enum wl_shm_format fmt); +/** + * Convert a DRM FourCC to an enum wl_shm_format. + */ enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt); #endif diff --git a/include/types/fx/shadow_data.h b/include/types/fx/shadow_data.h index 804acfe..d96a084 100644 --- a/include/types/fx/shadow_data.h +++ b/include/types/fx/shadow_data.h @@ -3,10 +3,11 @@ #include <stdbool.h> #include <wlr/util/addon.h> +#include <wlr/render/pass.h> struct shadow_data { bool enabled; - float *color; + struct wlr_render_color color; float blur_sigma; }; diff --git a/include/types/wlr_buffer.h b/include/types/wlr_buffer.h index 59d78e9..016cae8 100644 --- a/include/types/wlr_buffer.h +++ b/include/types/wlr_buffer.h @@ -3,75 +3,6 @@ #include <wlr/types/wlr_buffer.h> -struct wlr_shm_client_buffer { - struct wlr_buffer base; - - uint32_t format; - size_t stride; - - // The following fields are NULL if the client has destroyed the wl_buffer - struct wl_resource *resource; - struct wl_shm_buffer *shm_buffer; - - // This is used to keep the backing storage alive after the client has - // destroyed the wl_buffer - struct wl_shm_pool *saved_shm_pool; - void *saved_data; - - struct wl_listener resource_destroy; - struct wl_listener release; -}; - -struct wlr_shm_client_buffer *shm_client_buffer_get_or_create( - struct wl_resource *resource); - -/** - * A read-only buffer that holds a data pointer. - * - * This is suitable for passing raw pixel data to a function that accepts a - * wlr_buffer. - */ -struct wlr_readonly_data_buffer { - struct wlr_buffer base; - - const void *data; - uint32_t format; - size_t stride; - - void *saved_data; -}; - -/** - * Wraps a read-only data pointer into a wlr_buffer. The data pointer may be - * accessed until readonly_data_buffer_drop() is called. - */ -struct wlr_readonly_data_buffer *readonly_data_buffer_create(uint32_t format, - size_t stride, uint32_t width, uint32_t height, const void *data); -/** - * Drops ownership of the buffer (see wlr_buffer_drop() for more details) and - * perform a copy of the data pointer if a consumer still has the buffer locked. - */ -bool readonly_data_buffer_drop(struct wlr_readonly_data_buffer *buffer); - -struct wlr_dmabuf_buffer { - struct wlr_buffer base; - struct wlr_dmabuf_attributes dmabuf; - bool saved; -}; - -/** - * Wraps a DMA-BUF into a wlr_buffer. The DMA-BUF may be accessed until - * dmabuf_buffer_drop() is called. - */ -struct wlr_dmabuf_buffer *dmabuf_buffer_create( - struct wlr_dmabuf_attributes *dmabuf); -/** - * Drops ownership of the buffer (see wlr_buffer_drop() for more details) and - * takes a reference to the DMA-BUF (by dup'ing its file descriptors) if a - * consumer still has the buffer locked. - */ -bool dmabuf_buffer_drop(struct wlr_dmabuf_buffer *buffer); - /** * Check whether a buffer is fully opaque. * diff --git a/include/types/wlr_output.h b/include/types/wlr_output.h new file mode 100644 index 0000000..b239b6a --- /dev/null +++ b/include/types/wlr_output.h @@ -0,0 +1,10 @@ +#ifndef TYPES_WLR_OUTPUT_H +#define TYPES_WLR_OUTPUT_H + +#include <wlr/render/drm_format_set.h> +#include <wlr/types/wlr_output.h> + +void output_pending_resolution(struct wlr_output *output, + const struct wlr_output_state *state, int *width, int *height); + +#endif diff --git a/include/types/wlr_scene.h b/include/types/wlr_scene.h index 64c11bc..80dcfd1 100644 --- a/include/types/wlr_scene.h +++ b/include/types/wlr_scene.h @@ -5,4 +5,6 @@ struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node); +void scene_surface_set_clip(struct wlr_scene_surface *surface, struct wlr_box *clip); + #endif diff --git a/include/util/env.h b/include/util/env.h index 6720fa8..e271f4b 100644 --- a/include/util/env.h +++ b/include/util/env.h @@ -4,8 +4,20 @@ #include <stdbool.h> #include <unistd.h> +/** + * Parse a bool from an environment variable. + * + * On success, the parsed value is returned. On error, false is returned. + */ bool env_parse_bool(const char *option); -ssize_t env_parse_switch(const char *option, const char **switches); +/** + * Pick a choice from an environment variable. + * + * On success, the choice index is returned. On error, zero is returned. + * + * switches is a NULL-terminated array. + */ +size_t env_parse_switch(const char *option, const char **switches); #endif diff --git a/include/util/time.h b/include/util/time.h index 287698d..3f76aa4 100644 --- a/include/util/time.h +++ b/include/util/time.h @@ -1,12 +1,13 @@ #ifndef UTIL_TIME_H #define UTIL_TIME_H +#include <stdint.h> #include <time.h> /** * Get the current time, in milliseconds. */ -uint32_t get_current_time_msec(void); +int64_t get_current_time_msec(void); /** * Convert a timespec to milliseconds. @@ -14,6 +15,11 @@ uint32_t get_current_time_msec(void); int64_t timespec_to_msec(const struct timespec *a); /** + * Convert a timespec to nanoseconds. + */ +int64_t timespec_to_nsec(const struct timespec *a); + +/** * Convert nanoseconds to a timespec. */ void timespec_from_nsec(struct timespec *r, int64_t nsec); diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 7b4c002..f52e167 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -20,21 +20,33 @@ */ #include <pixman.h> +#include <time.h> #include <wayland-server-core.h> -#include <wlr/types/wlr_compositor.h> +#include <wlr/render/wlr_renderer.h> #include <wlr/types/wlr_damage_ring.h> #include "types/fx/shadow_data.h" +#include <wlr/types/wlr_linux_dmabuf_v1.h> +#include <wlr/util/addon.h> +#include <wlr/util/box.h> struct wlr_output; struct wlr_output_layout; +struct wlr_output_layout_output; struct wlr_xdg_surface; struct wlr_layer_surface_v1; +struct wlr_drag_icon; +struct wlr_surface; struct wlr_scene_node; struct wlr_scene_buffer; +struct wlr_scene_output_layout; + +struct wlr_presentation; +struct wlr_linux_dmabuf_v1; +struct wlr_output_state; typedef bool (*wlr_scene_buffer_point_accepts_input_func_t)( - struct wlr_scene_buffer *buffer, int sx, int sy); + struct wlr_scene_buffer *buffer, double *sx, double *sy); typedef void (*wlr_scene_buffer_iterator_func_t)( struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); @@ -89,10 +101,12 @@ struct wlr_scene { // May be NULL struct wlr_presentation *presentation; + struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1; // private state struct wl_listener presentation_destroy; + struct wl_listener linux_dmabuf_v1_destroy; enum wlr_scene_debug_damage_option debug_damage_option; bool direct_scanout; @@ -106,11 +120,14 @@ struct wlr_scene_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_present; + struct wl_listener output_sample; struct wl_listener frame_done; struct wl_listener surface_destroy; struct wl_listener surface_commit; @@ -123,6 +140,16 @@ struct wlr_scene_rect { float color[4]; }; +struct wlr_scene_outputs_update_event { + struct wlr_scene_output **active; + size_t size; +}; + +struct wlr_scene_output_sample_event { + struct wlr_scene_output *output; + bool direct_scanout; +}; + /** A scene-graph node displaying a buffer */ struct wlr_scene_buffer { struct wlr_scene_node node; @@ -131,9 +158,10 @@ struct wlr_scene_buffer { struct wlr_buffer *buffer; struct { + struct wl_signal outputs_update; // struct wlr_scene_outputs_update_event struct wl_signal output_enter; // struct wlr_scene_output struct wl_signal output_leave; // struct wlr_scene_output - struct wl_signal output_present; // struct wlr_scene_output + struct wl_signal output_sample; // struct wlr_scene_output_sample_event struct wl_signal frame_done; // struct timespec } events; @@ -148,18 +176,20 @@ struct wlr_scene_buffer { */ struct wlr_scene_output *primary_output; - // private state - float opacity; int corner_radius; struct shadow_data shadow_data; - - uint64_t active_outputs; - struct wlr_texture *texture; + enum wlr_scale_filter_mode filter_mode; struct wlr_fbox src_box; int dst_width, dst_height; 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; }; /** A viewport for an output in the scene-graph */ @@ -183,7 +213,6 @@ struct wlr_scene_output { bool prev_scanout; struct wl_listener output_commit; - struct wl_listener output_mode; struct wl_listener output_damage; struct wl_listener output_needs_frame; @@ -192,6 +221,11 @@ struct wlr_scene_output { struct wl_array render_list; }; +struct wlr_scene_timer { + int64_t pre_render_duration; + struct wlr_render_timer *render_timer; +}; + /** A layer shell scene helper */ struct wlr_scene_layer_surface_v1 { struct wlr_scene_tree *tree; @@ -269,6 +303,7 @@ struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node, * Create a new scene-graph. */ struct wlr_scene *wlr_scene_create(void); + /** * Handle presentation feedback for all surfaces in the scene, assuming that * scene outputs and the scene rendering functions are used. @@ -279,6 +314,15 @@ void wlr_scene_set_presentation(struct wlr_scene *scene, struct wlr_presentation *presentation); /** + * Handles linux_dmabuf_v1 feedback for all surfaces in the scene. + * + * Asserts that a struct wlr_linux_dmabuf_v1 hasn't already been set for the scene. + */ +void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene, + struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1); + + +/** * Add a node displaying nothing but its children. */ struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent); @@ -295,13 +339,29 @@ struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent); struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_tree *parent, struct wlr_surface *surface); +/** + * If this node represents a wlr_scene_buffer, that buffer will be returned. It + * is not legal to feed a node that does not represent a wlr_scene_buffer. + */ struct wlr_scene_buffer *wlr_scene_buffer_from_node(struct wlr_scene_node *node); /** + * If this node represents a wlr_scene_tree, that tree will be returned. It + * is not legal to feed a node that does not represent a wlr_scene_tree. + */ +struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *node); + +/** + * If this node represents a wlr_scene_rect, that rect will be returned. It + * is not legal to feed a node that does not represent a wlr_scene_rect. + */ +struct wlr_scene_rect *wlr_scene_rect_from_node(struct wlr_scene_node *node); + +/** * If this buffer is backed by a surface, then the struct wlr_scene_surface is * returned. If not, NULL will be returned. */ -struct wlr_scene_surface *wlr_scene_surface_from_buffer( +struct wlr_scene_surface *wlr_scene_surface_try_from_buffer( struct wlr_scene_buffer *scene_buffer); /** @@ -343,14 +403,14 @@ void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, * the whole buffer node will be damaged. */ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer, - struct wlr_buffer *buffer, pixman_region32_t *region); + struct wlr_buffer *buffer, const pixman_region32_t *region); /** * 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. */ void wlr_scene_buffer_set_opaque_region(struct wlr_scene_buffer *scene_buffer, - pixman_region32_t *region); + const pixman_region32_t *region); /** * Set the source rectangle describing the region of the buffer which will be @@ -384,6 +444,12 @@ void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, float opacity); /** +* Sets the filter mode to use when scaling the buffer +*/ +void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, + enum wlr_scale_filter_mode filter_mode); + +/** * Sets the corner radius of this buffer */ void wlr_scene_buffer_set_corner_radius(struct wlr_scene_buffer *scene_buffer, @@ -417,10 +483,32 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output); */ void wlr_scene_output_set_position(struct wlr_scene_output *scene_output, int lx, int ly); + +struct wlr_scene_output_state_options { + struct wlr_scene_timer *timer; +}; + /** * Render and commit an output. */ -bool wlr_scene_output_commit(struct wlr_scene_output *scene_output); +bool wlr_scene_output_commit(struct wlr_scene_output *scene_output, + const struct wlr_scene_output_state_options *options); + +/** + * Render and populate given output state. + */ +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); + +/** + * Retrieve the duration in nanoseconds between the last wlr_scene_output_commit() call and the end + * of its operations, including those on the GPU that may have finished after the call returned. + * + * Returns -1 if the duration is unavailable. + */ +int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer); +void wlr_scene_timer_finish(struct wlr_scene_timer *timer); + /** * Call wlr_surface_send_frame_done() on all surfaces in the scene rendered by * wlr_scene_output_commit() for which wlr_scene_surface.primary_output @@ -446,15 +534,24 @@ struct wlr_scene_output *wlr_scene_get_scene_output(struct wlr_scene *scene, /** * Attach an output layout to a scene. * - * Adding, removing, or repositioning an output in the output layout - * will respectively add, remove or reposition a corresponding - * scene-graph output. When the output layout is destroyed, scene-graph - * outputs which were created by this helper will be destroyed. + * The resulting scene output layout allows to synchronize the positions of scene + * outputs with the positions of corresponding layout outputs. + * + * It is automatically destroyed when the scene or the output layout is destroyed. */ -bool wlr_scene_attach_output_layout(struct wlr_scene *scene, +struct wlr_scene_output_layout *wlr_scene_attach_output_layout(struct wlr_scene *scene, struct wlr_output_layout *output_layout); /** + * Add an output to the scene output layout. + * + * When the layout output is repositioned, the scene output will be repositioned + * accordingly. + */ +void wlr_scene_output_layout_add_output(struct wlr_scene_output_layout *sol, + struct wlr_output_layout_output *lo, struct wlr_scene_output *so); + +/** * Add a node displaying a surface and all of its sub-surfaces to the * scene-graph. */ @@ -462,6 +559,16 @@ struct wlr_scene_tree *wlr_scene_subsurface_tree_create( struct wlr_scene_tree *parent, struct wlr_surface *surface); /** + * Sets a cropping region for any subsurface trees that are children of this + * scene node. The clip coordinate space will be that of the root surface of + * the subsurface tree. + * + * A NULL or empty clip will disable clipping + */ +void wlr_scene_subsurface_tree_set_clip(struct wlr_scene_node *node, + struct wlr_box *clip); + +/** * Add a node displaying an xdg_surface and all of its sub-surfaces to the * scene-graph. * @@ -495,4 +602,11 @@ void wlr_scene_layer_surface_v1_configure( struct wlr_scene_layer_surface_v1 *scene_layer_surface, const struct wlr_box *full_area, struct wlr_box *usable_area); +/** + * Add a node displaying a drag icon and all its sub-surfaces to the + * scene-graph. + */ +struct wlr_scene_tree *wlr_scene_drag_icon_create( + struct wlr_scene_tree *parent, struct wlr_drag_icon *drag_icon); + #endif diff --git a/meson.build b/meson.build index e96f2e6..070a07a 100644 --- a/meson.build +++ b/meson.build @@ -80,34 +80,20 @@ if cc.has_argument('-fmacro-prefix-map=/prefix/to/hide=') ) else add_project_arguments( - '-DWLR_REL_SRC_DIR="@0@"'.format(relative_dir), + '-D_WLR_REL_SRC_DIR="@0@"'.format(relative_dir), language: 'c', ) endif -features = { - 'drm-backend': false, - 'x11-backend': false, - 'libinput-backend': false, - 'xwayland': false, - 'gles2-renderer': false, - 'vulkan-renderer': false, - 'gbm-allocator': false, -} -internal_features = { - 'xcb-errors': false, - 'egl': false, -} - wayland_project_options = ['tests=false', 'documentation=false'] wayland_server = dependency('wayland-server', - version: '>=1.21', + version: '>=1.22', fallback: 'wayland', default_options: wayland_project_options, ) wlroots_options = [ 'examples=false' ] -wlroots_version = ['>=0.16.0', '<0.17.0'] +wlroots_version = ['>=0.17.0', '<0.18.0'] subproject( 'wlroots', default_options: wlroots_options, @@ -120,7 +106,7 @@ wlroots = dependency('wlroots', ) drm = dependency('libdrm', - version: '>=2.4.113', + version: '>=2.4.114', fallback: 'libdrm', default_options: [ 'intel=disabled', @@ -140,9 +126,11 @@ drm = dependency('libdrm', 'tests=false', ], ) -xkbcommon = dependency('xkbcommon') -udev = dependency('libudev') -pixman = dependency('pixman-1') +pixman = dependency('pixman-1', + version: '>=0.42.0', + fallback: 'pixman', + default_options: ['werror=false'], +) math = cc.find_library('m') rt = cc.find_library('rt') @@ -151,8 +139,6 @@ wlr_deps = [ wlroots, wayland_server, drm, - xkbcommon, - udev, pixman, math, rt, @@ -164,43 +150,25 @@ subdir('render') subdir('types') subdir('util') -subdir('include') - -foreach name, have : internal_features - add_project_arguments( - '-DHAS_@0@=@1@'.format(name.underscorify().to_upper(), have.to_int()), - language: 'c', - ) -endforeach - scenefx_inc = include_directories('include') -proto_inc = include_directories('protocol') lib_scenefx = library( meson.project_name(), wlr_files, soversion: soversion.to_string(), dependencies: wlr_deps, - include_directories: [ scenefx_inc, proto_inc ], + include_directories: [ scenefx_inc ], install: true, ) -wlr_vars = {} -foreach name, have : features - wlr_vars += { 'have_' + name.underscorify(): have.to_string() } -endforeach - scenefx = declare_dependency( link_with: lib_scenefx, dependencies: wlr_deps, include_directories: scenefx_inc, - variables: wlr_vars, ) meson.override_dependency('scenefx', scenefx) -summary(features + internal_features, bool_yn: true) - if get_option('examples') # TODO: subdir('examples') subdir('tinywl') @@ -212,5 +180,4 @@ pkgconfig.generate(lib_scenefx, filebase: meson.project_name(), name: meson.project_name(), description: 'Wlroots effects library', - variables: wlr_vars, ) diff --git a/render/egl.c b/render/egl.c new file mode 100644 index 0000000..162634b --- /dev/null +++ b/render/egl.c @@ -0,0 +1,1021 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <drm_fourcc.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <gbm.h> +#include <wlr/render/egl.h> +#include <wlr/util/log.h> +#include <wlr/util/region.h> +#include <xf86drm.h> +#include "render/egl.h" +#include "util/env.h" + +static enum wlr_log_importance egl_log_importance_to_wlr(EGLint type) { + switch (type) { + case EGL_DEBUG_MSG_CRITICAL_KHR: return WLR_ERROR; + case EGL_DEBUG_MSG_ERROR_KHR: return WLR_ERROR; + case EGL_DEBUG_MSG_WARN_KHR: return WLR_ERROR; + case EGL_DEBUG_MSG_INFO_KHR: return WLR_INFO; + default: return WLR_INFO; + } +} + +static const char *egl_error_str(EGLint error) { + switch (error) { + case EGL_SUCCESS: + return "EGL_SUCCESS"; + case EGL_NOT_INITIALIZED: + return "EGL_NOT_INITIALIZED"; + case EGL_BAD_ACCESS: + return "EGL_BAD_ACCESS"; + case EGL_BAD_ALLOC: + return "EGL_BAD_ALLOC"; + case EGL_BAD_ATTRIBUTE: + return "EGL_BAD_ATTRIBUTE"; + case EGL_BAD_CONTEXT: + return "EGL_BAD_CONTEXT"; + case EGL_BAD_CONFIG: + return "EGL_BAD_CONFIG"; + case EGL_BAD_CURRENT_SURFACE: + return "EGL_BAD_CURRENT_SURFACE"; + case EGL_BAD_DISPLAY: + return "EGL_BAD_DISPLAY"; + case EGL_BAD_DEVICE_EXT: + return "EGL_BAD_DEVICE_EXT"; + case EGL_BAD_SURFACE: + return "EGL_BAD_SURFACE"; + case EGL_BAD_MATCH: + return "EGL_BAD_MATCH"; + case EGL_BAD_PARAMETER: + return "EGL_BAD_PARAMETER"; + case EGL_BAD_NATIVE_PIXMAP: + return "EGL_BAD_NATIVE_PIXMAP"; + case EGL_BAD_NATIVE_WINDOW: + return "EGL_BAD_NATIVE_WINDOW"; + case EGL_CONTEXT_LOST: + return "EGL_CONTEXT_LOST"; + } + return "unknown error"; +} + +static void egl_log(EGLenum error, const char *command, EGLint msg_type, + EGLLabelKHR thread, EGLLabelKHR obj, const char *msg) { + _wlr_log(egl_log_importance_to_wlr(msg_type), + "[EGL] command: %s, error: %s (0x%x), message: \"%s\"", + command, egl_error_str(error), error, msg); +} + +static bool check_egl_ext(const char *exts, const char *ext) { + size_t extlen = strlen(ext); + const char *end = exts + strlen(exts); + + while (exts < end) { + if (*exts == ' ') { + exts++; + continue; + } + size_t n = strcspn(exts, " "); + if (n == extlen && strncmp(ext, exts, n) == 0) { + return true; + } + exts += n; + } + return false; +} + +static void load_egl_proc(void *proc_ptr, const char *name) { + void *proc = (void *)eglGetProcAddress(name); + if (proc == NULL) { + wlr_log(WLR_ERROR, "eglGetProcAddress(%s) failed", name); + abort(); + } + *(void **)proc_ptr = proc; +} + +static int get_egl_dmabuf_formats(struct wlr_egl *egl, EGLint **formats); +static int get_egl_dmabuf_modifiers(struct wlr_egl *egl, EGLint format, + uint64_t **modifiers, EGLBoolean **external_only); + +static void log_modifier(uint64_t modifier, bool external_only) { + char *mod_name = drmGetFormatModifierName(modifier); + wlr_log(WLR_DEBUG, " %s (0x%016"PRIX64"): ✓ texture %s render", + mod_name ? mod_name : "<unknown>", modifier, external_only ? "✗" : "✓"); + free(mod_name); +} + +static void init_dmabuf_formats(struct wlr_egl *egl) { + bool no_modifiers = env_parse_bool("WLR_EGL_NO_MODIFIERS"); + if (no_modifiers) { + wlr_log(WLR_INFO, "WLR_EGL_NO_MODIFIERS set, disabling modifiers for EGL"); + } + + EGLint *formats; + int formats_len = get_egl_dmabuf_formats(egl, &formats); + if (formats_len < 0) { + return; + } + + wlr_log(WLR_DEBUG, "Supported DMA-BUF formats:"); + + bool has_modifiers = false; + for (int i = 0; i < formats_len; i++) { + EGLint fmt = formats[i]; + + uint64_t *modifiers = NULL; + EGLBoolean *external_only = NULL; + int modifiers_len = 0; + if (!no_modifiers) { + modifiers_len = get_egl_dmabuf_modifiers(egl, fmt, &modifiers, &external_only); + } + if (modifiers_len < 0) { + continue; + } + + has_modifiers = has_modifiers || modifiers_len > 0; + + bool all_external_only = true; + for (int j = 0; j < modifiers_len; j++) { + wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt, + modifiers[j]); + if (!external_only[j]) { + wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt, + modifiers[j]); + all_external_only = false; + } + } + + // EGL always supports implicit modifiers. If at least one modifier supports rendering, + // assume the implicit modifier supports rendering too. + wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt, + DRM_FORMAT_MOD_INVALID); + if (modifiers_len == 0 || !all_external_only) { + wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt, + DRM_FORMAT_MOD_INVALID); + } + + if (modifiers_len == 0) { + // Asume the linear layout is supported if the driver doesn't + // explicitly say otherwise + wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt, + DRM_FORMAT_MOD_LINEAR); + wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt, + DRM_FORMAT_MOD_LINEAR); + } + + if (wlr_log_get_verbosity() >= WLR_DEBUG) { + char *fmt_name = drmGetFormatName(fmt); + wlr_log(WLR_DEBUG, " %s (0x%08"PRIX32")", + fmt_name ? fmt_name : "<unknown>", fmt); + free(fmt_name); + + log_modifier(DRM_FORMAT_MOD_INVALID, false); + if (modifiers_len == 0) { + log_modifier(DRM_FORMAT_MOD_LINEAR, false); + } + for (int j = 0; j < modifiers_len; j++) { + log_modifier(modifiers[j], external_only[j]); + } + } + + free(modifiers); + free(external_only); + } + free(formats); + + egl->has_modifiers = has_modifiers; + if (!no_modifiers) { + wlr_log(WLR_DEBUG, "EGL DMA-BUF format modifiers %s", + has_modifiers ? "supported" : "unsupported"); + } +} + +static struct wlr_egl *egl_create(void) { + const char *client_exts_str = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (client_exts_str == NULL) { + if (eglGetError() == EGL_BAD_DISPLAY) { + wlr_log(WLR_ERROR, "EGL_EXT_client_extensions not supported"); + } else { + wlr_log(WLR_ERROR, "Failed to query EGL client extensions"); + } + return NULL; + } + + wlr_log(WLR_INFO, "Supported EGL client extensions: %s", client_exts_str); + + if (!check_egl_ext(client_exts_str, "EGL_EXT_platform_base")) { + wlr_log(WLR_ERROR, "EGL_EXT_platform_base not supported"); + return NULL; + } + + struct wlr_egl *egl = calloc(1, sizeof(*egl)); + if (egl == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + load_egl_proc(&egl->procs.eglGetPlatformDisplayEXT, + "eglGetPlatformDisplayEXT"); + + egl->exts.KHR_platform_gbm = check_egl_ext(client_exts_str, + "EGL_KHR_platform_gbm"); + egl->exts.EXT_platform_device = check_egl_ext(client_exts_str, + "EGL_EXT_platform_device"); + egl->exts.KHR_display_reference = check_egl_ext(client_exts_str, + "EGL_KHR_display_reference"); + + if (check_egl_ext(client_exts_str, "EGL_EXT_device_base") || check_egl_ext(client_exts_str, "EGL_EXT_device_enumeration")) { + load_egl_proc(&egl->procs.eglQueryDevicesEXT, "eglQueryDevicesEXT"); + } + + if (check_egl_ext(client_exts_str, "EGL_EXT_device_base") || check_egl_ext(client_exts_str, "EGL_EXT_device_query")) { + egl->exts.EXT_device_query = true; + load_egl_proc(&egl->procs.eglQueryDeviceStringEXT, + "eglQueryDeviceStringEXT"); + load_egl_proc(&egl->procs.eglQueryDisplayAttribEXT, + "eglQueryDisplayAttribEXT"); + } + + if (check_egl_ext(client_exts_str, "EGL_KHR_debug")) { + load_egl_proc(&egl->procs.eglDebugMessageControlKHR, + "eglDebugMessageControlKHR"); + + static const EGLAttrib debug_attribs[] = { + EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, + EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, + EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, + EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, + EGL_NONE, + }; + egl->procs.eglDebugMessageControlKHR(egl_log, debug_attribs); + } + + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to bind to the OpenGL ES API"); + free(egl); + return NULL; + } + + return egl; +} + +static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { + egl->display = display; + + EGLint major, minor; + if (eglInitialize(egl->display, &major, &minor) == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to initialize EGL"); + return false; + } + + const char *display_exts_str = eglQueryString(egl->display, EGL_EXTENSIONS); + if (display_exts_str == NULL) { + wlr_log(WLR_ERROR, "Failed to query EGL display extensions"); + return false; + } + + if (check_egl_ext(display_exts_str, "EGL_KHR_image_base")) { + egl->exts.KHR_image_base = true; + load_egl_proc(&egl->procs.eglCreateImageKHR, "eglCreateImageKHR"); + load_egl_proc(&egl->procs.eglDestroyImageKHR, "eglDestroyImageKHR"); + } + + egl->exts.EXT_image_dma_buf_import = + check_egl_ext(display_exts_str, "EGL_EXT_image_dma_buf_import"); + if (check_egl_ext(display_exts_str, + "EGL_EXT_image_dma_buf_import_modifiers")) { + egl->exts.EXT_image_dma_buf_import_modifiers = true; + load_egl_proc(&egl->procs.eglQueryDmaBufFormatsEXT, + "eglQueryDmaBufFormatsEXT"); + load_egl_proc(&egl->procs.eglQueryDmaBufModifiersEXT, + "eglQueryDmaBufModifiersEXT"); + } + + egl->exts.EXT_create_context_robustness = + check_egl_ext(display_exts_str, "EGL_EXT_create_context_robustness"); + + const char *device_exts_str = NULL, *driver_name = NULL; + if (egl->exts.EXT_device_query) { + EGLAttrib device_attrib; + if (!egl->procs.eglQueryDisplayAttribEXT(egl->display, + EGL_DEVICE_EXT, &device_attrib)) { + wlr_log(WLR_ERROR, "eglQueryDisplayAttribEXT(EGL_DEVICE_EXT) failed"); + return false; + } + egl->device = (EGLDeviceEXT)device_attrib; + + device_exts_str = + egl->procs.eglQueryDeviceStringEXT(egl->device, EGL_EXTENSIONS); + if (device_exts_str == NULL) { + wlr_log(WLR_ERROR, "eglQueryDeviceStringEXT(EGL_EXTENSIONS) failed"); + 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, + EGL_DRIVER_NAME_EXT); + } +#endif + + egl->exts.EXT_device_drm = + 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"); + } + + if (!check_egl_ext(display_exts_str, "EGL_KHR_no_config_context") && + !check_egl_ext(display_exts_str, "EGL_MESA_configless_context")) { + wlr_log(WLR_ERROR, "EGL_KHR_no_config_context or " + "EGL_MESA_configless_context not supported"); + return false; + } + + if (!check_egl_ext(display_exts_str, "EGL_KHR_surfaceless_context")) { + wlr_log(WLR_ERROR, "EGL_KHR_surfaceless_context not supported"); + return false; + } + + egl->exts.IMG_context_priority = + check_egl_ext(display_exts_str, "EGL_IMG_context_priority"); + + wlr_log(WLR_INFO, "Using EGL %d.%d", (int)major, (int)minor); + wlr_log(WLR_INFO, "Supported EGL display extensions: %s", display_exts_str); + if (device_exts_str != NULL) { + wlr_log(WLR_INFO, "Supported EGL device extensions: %s", device_exts_str); + } + wlr_log(WLR_INFO, "EGL vendor: %s", eglQueryString(egl->display, EGL_VENDOR)); + if (driver_name != NULL) { + wlr_log(WLR_INFO, "EGL driver name: %s", driver_name); + } + + init_dmabuf_formats(egl); + + return true; +} + +static bool egl_init(struct wlr_egl *egl, EGLenum platform, + void *remote_display) { + EGLint display_attribs[3] = {0}; + size_t display_attribs_len = 0; + + if (egl->exts.KHR_display_reference) { + display_attribs[display_attribs_len++] = EGL_TRACK_REFERENCES_KHR; + display_attribs[display_attribs_len++] = EGL_TRUE; + } + + display_attribs[display_attribs_len++] = EGL_NONE; + assert(display_attribs_len < sizeof(display_attribs) / sizeof(display_attribs[0])); + + EGLDisplay display = egl->procs.eglGetPlatformDisplayEXT(platform, + remote_display, display_attribs); + if (display == EGL_NO_DISPLAY) { + wlr_log(WLR_ERROR, "Failed to create EGL display"); + return false; + } + + if (!egl_init_display(egl, display)) { + if (egl->exts.KHR_display_reference) { + eglTerminate(display); + } + return false; + } + + size_t atti = 0; + EGLint attribs[7]; + attribs[atti++] = EGL_CONTEXT_CLIENT_VERSION; + attribs[atti++] = 2; + + // Request a high priority context if possible + // TODO: only do this if we're running as the DRM master + bool request_high_priority = egl->exts.IMG_context_priority; + + // Try to reschedule all of our rendering to be completed first. If it + // fails, it will fallback to the default priority (MEDIUM). + if (request_high_priority) { + attribs[atti++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG; + attribs[atti++] = EGL_CONTEXT_PRIORITY_HIGH_IMG; + } + + if (egl->exts.EXT_create_context_robustness) { + attribs[atti++] = EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT; + attribs[atti++] = EGL_LOSE_CONTEXT_ON_RESET_EXT; + } + + attribs[atti++] = EGL_NONE; + assert(atti <= sizeof(attribs)/sizeof(attribs[0])); + + egl->context = eglCreateContext(egl->display, EGL_NO_CONFIG_KHR, + EGL_NO_CONTEXT, attribs); + if (egl->context == EGL_NO_CONTEXT) { + wlr_log(WLR_ERROR, "Failed to create EGL context"); + return false; + } + + if (request_high_priority) { + EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; + eglQueryContext(egl->display, egl->context, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority); + if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) { + wlr_log(WLR_INFO, "Failed to obtain a high priority context"); + } else { + wlr_log(WLR_DEBUG, "Obtained high priority context"); + } + } + + return true; +} + +static bool device_has_name(const drmDevice *device, const char *name); + +static EGLDeviceEXT get_egl_device_from_drm_fd(struct wlr_egl *egl, + int drm_fd) { + if (egl->procs.eglQueryDevicesEXT == NULL) { + wlr_log(WLR_DEBUG, "EGL_EXT_device_enumeration not supported"); + return EGL_NO_DEVICE_EXT; + } else if (!egl->exts.EXT_device_query) { + wlr_log(WLR_DEBUG, "EGL_EXT_device_query not supported"); + return EGL_NO_DEVICE_EXT; + } + + EGLint nb_devices = 0; + if (!egl->procs.eglQueryDevicesEXT(0, NULL, &nb_devices)) { + wlr_log(WLR_ERROR, "Failed to query EGL devices"); + return EGL_NO_DEVICE_EXT; + } + + EGLDeviceEXT *devices = calloc(nb_devices, sizeof(*devices)); + if (devices == NULL) { + wlr_log_errno(WLR_ERROR, "Failed to allocate EGL device list"); + return EGL_NO_DEVICE_EXT; + } + + if (!egl->procs.eglQueryDevicesEXT(nb_devices, devices, &nb_devices)) { + wlr_log(WLR_ERROR, "Failed to query EGL 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; + } + + 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) { + continue; + } + + if (device_has_name(device, egl_device_name)) { + wlr_log(WLR_DEBUG, "Using EGL device %s", egl_device_name); + egl_device = devices[i]; + break; + } + } + + drmFreeDevice(&device); + free(devices); + + return egl_device; +} + +static int open_render_node(int drm_fd) { + char *render_name = drmGetRenderDeviceNameFromFd(drm_fd); + if (render_name == NULL) { + // This can happen on split render/display platforms, fallback to + // primary node + render_name = drmGetPrimaryDeviceNameFromFd(drm_fd); + if (render_name == NULL) { + wlr_log_errno(WLR_ERROR, "drmGetPrimaryDeviceNameFromFd failed"); + return -1; + } + wlr_log(WLR_DEBUG, "DRM device '%s' has no render node, " + "falling back to primary node", render_name); + } + + int render_fd = open(render_name, O_RDWR | O_CLOEXEC); + if (render_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open DRM node '%s'", render_name); + } + free(render_name); + return render_fd; +} + +struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) { + struct wlr_egl *egl = egl_create(); + if (egl == NULL) { + wlr_log(WLR_ERROR, "Failed to create EGL context"); + return NULL; + } + + if (egl->exts.EXT_platform_device) { + /* + * Search for the EGL device matching the DRM fd using the + * EXT_device_enumeration extension. + */ + EGLDeviceEXT egl_device = get_egl_device_from_drm_fd(egl, drm_fd); + if (egl_device != EGL_NO_DEVICE_EXT) { + if (egl_init(egl, EGL_PLATFORM_DEVICE_EXT, egl_device)) { + wlr_log(WLR_DEBUG, "Using EGL_PLATFORM_DEVICE_EXT"); + return egl; + } + goto error; + } + /* Falls back on GBM in case the device was not found */ + } else { + wlr_log(WLR_DEBUG, "EXT_platform_device not supported"); + } + + if (egl->exts.KHR_platform_gbm) { + int gbm_fd = open_render_node(drm_fd); + if (gbm_fd < 0) { + wlr_log(WLR_ERROR, "Failed to open DRM render node"); + goto error; + } + + egl->gbm_device = gbm_create_device(gbm_fd); + if (!egl->gbm_device) { + close(gbm_fd); + wlr_log(WLR_ERROR, "Failed to create GBM device"); + goto error; + } + + if (egl_init(egl, EGL_PLATFORM_GBM_KHR, egl->gbm_device)) { + wlr_log(WLR_DEBUG, "Using EGL_PLATFORM_GBM_KHR"); + return egl; + } + + gbm_device_destroy(egl->gbm_device); + close(gbm_fd); + } else { + wlr_log(WLR_DEBUG, "KHR_platform_gbm not supported"); + } + +error: + wlr_log(WLR_ERROR, "Failed to initialize EGL context"); + free(egl); + eglReleaseThread(); + return NULL; +} + +struct wlr_egl *wlr_egl_create_with_context(EGLDisplay display, + EGLContext context) { + EGLint client_type; + if (!eglQueryContext(display, context, EGL_CONTEXT_CLIENT_TYPE, &client_type) || + client_type != EGL_OPENGL_ES_API) { + wlr_log(WLR_ERROR, "Unsupported EGL context client type (need OpenGL ES)"); + return NULL; + } + + EGLint client_version; + if (!eglQueryContext(display, context, EGL_CONTEXT_CLIENT_VERSION, &client_version) || + client_version < 2) { + wlr_log(WLR_ERROR, "Unsupported EGL context client version (need OpenGL ES >= 2)"); + return NULL; + } + + struct wlr_egl *egl = egl_create(); + if (egl == NULL) { + return NULL; + } + + if (!egl_init_display(egl, display)) { + free(egl); + return NULL; + } + + egl->context = context; + + return egl; +} + +void wlr_egl_destroy(struct wlr_egl *egl) { + if (egl == NULL) { + return; + } + + wlr_drm_format_set_finish(&egl->dmabuf_render_formats); + wlr_drm_format_set_finish(&egl->dmabuf_texture_formats); + + eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(egl->display, egl->context); + + if (egl->exts.KHR_display_reference) { + eglTerminate(egl->display); + } + + eglReleaseThread(); + + if (egl->gbm_device) { + int gbm_fd = gbm_device_get_fd(egl->gbm_device); + gbm_device_destroy(egl->gbm_device); + close(gbm_fd); + } + + free(egl); +} + +EGLDisplay wlr_egl_get_display(struct wlr_egl *egl) { + return egl->display; +} + +EGLContext wlr_egl_get_context(struct wlr_egl *egl) { + return egl->context; +} + +bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImage image) { + if (!egl->exts.KHR_image_base) { + return false; + } + if (!image) { + return true; + } + return egl->procs.eglDestroyImageKHR(egl->display, image); +} + +bool wlr_egl_make_current(struct wlr_egl *egl) { + if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, + egl->context)) { + wlr_log(WLR_ERROR, "eglMakeCurrent failed"); + return false; + } + return true; +} + +bool wlr_egl_unset_current(struct wlr_egl *egl) { + if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)) { + wlr_log(WLR_ERROR, "eglMakeCurrent failed"); + return false; + } + return true; +} + +bool wlr_egl_is_current(struct wlr_egl *egl) { + return eglGetCurrentContext() == egl->context; +} + +void wlr_egl_save_context(struct wlr_egl_context *context) { + context->display = eglGetCurrentDisplay(); + context->context = eglGetCurrentContext(); + context->draw_surface = eglGetCurrentSurface(EGL_DRAW); + context->read_surface = eglGetCurrentSurface(EGL_READ); +} + +bool wlr_egl_restore_context(struct wlr_egl_context *context) { + // If the saved context is a null-context, we must use the current + // display instead of the saved display because eglMakeCurrent() can't + // handle EGL_NO_DISPLAY. + EGLDisplay display = context->display == EGL_NO_DISPLAY ? + eglGetCurrentDisplay() : context->display; + + // If the current display is also EGL_NO_DISPLAY, we assume that there + // is currently no context set and no action needs to be taken to unset + // the context. + if (display == EGL_NO_DISPLAY) { + return true; + } + + return eglMakeCurrent(display, context->draw_surface, + context->read_surface, context->context); +} + +EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_attributes *attributes, bool *external_only) { + if (!egl->exts.KHR_image_base || !egl->exts.EXT_image_dma_buf_import) { + wlr_log(WLR_ERROR, "dmabuf import extension not present"); + return NULL; + } + + if (attributes->modifier != DRM_FORMAT_MOD_INVALID && + attributes->modifier != DRM_FORMAT_MOD_LINEAR && + !egl->has_modifiers) { + wlr_log(WLR_ERROR, "EGL implementation doesn't support modifiers"); + return NULL; + } + + unsigned int atti = 0; + EGLint attribs[50]; + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = attributes->width; + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = attributes->height; + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = attributes->format; + + struct { + EGLint fd; + EGLint offset; + EGLint pitch; + EGLint mod_lo; + EGLint mod_hi; + } attr_names[WLR_DMABUF_MAX_PLANES] = { + { + EGL_DMA_BUF_PLANE0_FD_EXT, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + EGL_DMA_BUF_PLANE0_PITCH_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE1_FD_EXT, + EGL_DMA_BUF_PLANE1_OFFSET_EXT, + EGL_DMA_BUF_PLANE1_PITCH_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE2_FD_EXT, + EGL_DMA_BUF_PLANE2_OFFSET_EXT, + EGL_DMA_BUF_PLANE2_PITCH_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE3_FD_EXT, + EGL_DMA_BUF_PLANE3_OFFSET_EXT, + EGL_DMA_BUF_PLANE3_PITCH_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT + } + }; + + for (int i = 0; i < attributes->n_planes; i++) { + attribs[atti++] = attr_names[i].fd; + attribs[atti++] = attributes->fd[i]; + attribs[atti++] = attr_names[i].offset; + attribs[atti++] = attributes->offset[i]; + attribs[atti++] = attr_names[i].pitch; + attribs[atti++] = attributes->stride[i]; + if (egl->has_modifiers && + attributes->modifier != DRM_FORMAT_MOD_INVALID) { + attribs[atti++] = attr_names[i].mod_lo; + attribs[atti++] = attributes->modifier & 0xFFFFFFFF; + attribs[atti++] = attr_names[i].mod_hi; + attribs[atti++] = attributes->modifier >> 32; + } + } + + // Our clients don't expect our usage to trash the buffer contents + attribs[atti++] = EGL_IMAGE_PRESERVED_KHR; + attribs[atti++] = EGL_TRUE; + + attribs[atti++] = EGL_NONE; + assert(atti < sizeof(attribs)/sizeof(attribs[0])); + + EGLImageKHR image = egl->procs.eglCreateImageKHR(egl->display, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, NULL, attribs); + if (image == EGL_NO_IMAGE_KHR) { + wlr_log(WLR_ERROR, "eglCreateImageKHR failed"); + return EGL_NO_IMAGE_KHR; + } + + *external_only = !wlr_drm_format_set_has(&egl->dmabuf_render_formats, + attributes->format, attributes->modifier); + return image; +} + +static int get_egl_dmabuf_formats(struct wlr_egl *egl, EGLint **formats) { + if (!egl->exts.EXT_image_dma_buf_import) { + wlr_log(WLR_DEBUG, "DMA-BUF import extension not present"); + return -1; + } + + // when we only have the image_dmabuf_import extension we can't query + // which formats are supported. These two are on almost always + // supported; it's the intended way to just try to create buffers. + // Just a guess but better than not supporting dmabufs at all, + // given that the modifiers extension isn't supported everywhere. + if (!egl->exts.EXT_image_dma_buf_import_modifiers) { + static const EGLint fallback_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + }; + int num = sizeof(fallback_formats) / sizeof(fallback_formats[0]); + + *formats = calloc(num, sizeof(**formats)); + if (!*formats) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return -1; + } + + memcpy(*formats, fallback_formats, num * sizeof(**formats)); + return num; + } + + EGLint num; + if (!egl->procs.eglQueryDmaBufFormatsEXT(egl->display, 0, NULL, &num)) { + wlr_log(WLR_ERROR, "Failed to query number of dmabuf formats"); + return -1; + } + + *formats = calloc(num, sizeof(**formats)); + if (*formats == NULL) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + return -1; + } + + if (!egl->procs.eglQueryDmaBufFormatsEXT(egl->display, num, *formats, &num)) { + wlr_log(WLR_ERROR, "Failed to query dmabuf format"); + free(*formats); + return -1; + } + return num; +} + +static int get_egl_dmabuf_modifiers(struct wlr_egl *egl, EGLint format, + uint64_t **modifiers, EGLBoolean **external_only) { + *modifiers = NULL; + *external_only = NULL; + + if (!egl->exts.EXT_image_dma_buf_import) { + wlr_log(WLR_DEBUG, "DMA-BUF extension not present"); + return -1; + } + if (!egl->exts.EXT_image_dma_buf_import_modifiers) { + return 0; + } + + EGLint num; + if (!egl->procs.eglQueryDmaBufModifiersEXT(egl->display, format, 0, + NULL, NULL, &num)) { + wlr_log(WLR_ERROR, "Failed to query dmabuf number of modifiers"); + return -1; + } + if (num == 0) { + return 0; + } + + *modifiers = calloc(num, sizeof(**modifiers)); + if (*modifiers == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return -1; + } + *external_only = calloc(num, sizeof(**external_only)); + if (*external_only == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + free(*modifiers); + *modifiers = NULL; + return -1; + } + + if (!egl->procs.eglQueryDmaBufModifiersEXT(egl->display, format, num, + *modifiers, *external_only, &num)) { + wlr_log(WLR_ERROR, "Failed to query dmabuf modifiers"); + free(*modifiers); + free(*external_only); + return -1; + } + return num; +} + +const struct wlr_drm_format_set *wlr_egl_get_dmabuf_texture_formats( + struct wlr_egl *egl) { + return &egl->dmabuf_texture_formats; +} + +const struct wlr_drm_format_set *wlr_egl_get_dmabuf_render_formats( + struct wlr_egl *egl) { + return &egl->dmabuf_render_formats; +} + +static bool device_has_name(const drmDevice *device, const char *name) { + for (size_t i = 0; i < DRM_NODE_MAX; i++) { + if (!(device->available_nodes & (1 << i))) { + continue; + } + if (strcmp(device->nodes[i], name) == 0) { + return true; + } + } + return false; +} + +static char *get_render_name(const char *name) { + uint32_t flags = 0; + int devices_len = drmGetDevices2(flags, NULL, 0); + if (devices_len < 0) { + wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len)); + return NULL; + } + drmDevice **devices = calloc(devices_len, sizeof(*devices)); + if (devices == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + devices_len = drmGetDevices2(flags, devices, devices_len); + if (devices_len < 0) { + free(devices); + wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len)); + return NULL; + } + + const drmDevice *match = NULL; + for (int i = 0; i < devices_len; i++) { + if (device_has_name(devices[i], name)) { + match = devices[i]; + break; + } + } + + char *render_name = NULL; + if (match == NULL) { + wlr_log(WLR_ERROR, "Cannot find DRM device %s", name); + } else if (!(match->available_nodes & (1 << DRM_NODE_RENDER))) { + // Likely a split display/render setup. Pick the primary node and hope + // Mesa will open the right render node under-the-hood. + wlr_log(WLR_DEBUG, "DRM device %s has no render node, " + "falling back to primary node", name); + assert(match->available_nodes & (1 << DRM_NODE_PRIMARY)); + render_name = strdup(match->nodes[DRM_NODE_PRIMARY]); + } else { + render_name = strdup(match->nodes[DRM_NODE_RENDER]); + } + + for (int i = 0; i < devices_len; i++) { + drmFreeDevice(&devices[i]); + } + free(devices); + + return render_name; +} + +static int dup_egl_device_drm_fd(struct wlr_egl *egl) { + if (egl->device == EGL_NO_DEVICE_EXT || (!egl->exts.EXT_device_drm && + !egl->exts.EXT_device_drm_render_node)) { + return -1; + } + + char *render_name = NULL; +#ifdef EGL_EXT_device_drm_render_node + if (egl->exts.EXT_device_drm_render_node) { + const char *name = egl->procs.eglQueryDeviceStringEXT(egl->device, + EGL_DRM_RENDER_NODE_FILE_EXT); + if (name == NULL) { + wlr_log(WLR_DEBUG, "EGL device has no render node"); + return -1; + } + render_name = strdup(name); + } +#endif + + if (render_name == NULL) { + const char *primary_name = egl->procs.eglQueryDeviceStringEXT(egl->device, + EGL_DRM_DEVICE_FILE_EXT); + if (primary_name == NULL) { + wlr_log(WLR_ERROR, + "eglQueryDeviceStringEXT(EGL_DRM_DEVICE_FILE_EXT) failed"); + return -1; + } + + render_name = get_render_name(primary_name); + if (render_name == NULL) { + wlr_log(WLR_ERROR, "Can't find render node name for device %s", + primary_name); + return -1; + } + } + + int render_fd = open(render_name, O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (render_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open DRM render node %s", + render_name); + free(render_name); + return -1; + } + free(render_name); + + return render_fd; +} + +int wlr_egl_dup_drm_fd(struct wlr_egl *egl) { + int fd = dup_egl_device_drm_fd(egl); + if (fd >= 0) { + return fd; + } + + // Fallback to GBM's FD if we can't use EGLDevice + if (egl->gbm_device == NULL) { + return -1; + } + + fd = fcntl(gbm_device_get_fd(egl->gbm_device), F_DUPFD_CLOEXEC, 0); + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to dup GBM FD"); + } + return fd; +} diff --git a/render/fx_renderer/fx_framebuffer.c b/render/fx_renderer/fx_framebuffer.c new file mode 100644 index 0000000..fea101b --- /dev/null +++ b/render/fx_renderer/fx_framebuffer.c @@ -0,0 +1,137 @@ +#include <stdio.h> +#include <stdlib.h> +#include <wlr/interfaces/wlr_buffer.h> +#include <wlr/render/allocator.h> +#include <wlr/render/interface.h> +#include <wlr/render/swapchain.h> +#include <wlr/util/log.h> + +#include "render/egl.h" +#include "render/fx_renderer/fx_renderer.h" + +static void handle_buffer_destroy(struct wlr_addon *addon) { + struct fx_framebuffer *buffer = + wl_container_of(addon, buffer, addon); + fx_framebuffer_destroy(buffer); +} + +static const struct wlr_addon_interface buffer_addon_impl = { + .name = "fx_framebuffer", + .destroy = handle_buffer_destroy, +}; + +struct fx_framebuffer *fx_framebuffer_get_or_create(struct fx_renderer *renderer, + struct wlr_buffer *wlr_buffer) { + struct wlr_addon *addon = + wlr_addon_find(&wlr_buffer->addons, renderer, &buffer_addon_impl); + if (addon) { + struct fx_framebuffer *buffer = wl_container_of(addon, buffer, addon); + return buffer; + } + + struct fx_framebuffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + buffer->buffer = wlr_buffer; + buffer->renderer = renderer; + + struct wlr_dmabuf_attributes dmabuf = {0}; + if (!wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) { + goto error_buffer; + } + + bool external_only; + buffer->image = wlr_egl_create_image_from_dmabuf(renderer->egl, + &dmabuf, &external_only); + if (buffer->image == EGL_NO_IMAGE_KHR) { + goto error_buffer; + } + + push_fx_debug(renderer); + + glGenRenderbuffers(1, &buffer->rbo); + glBindRenderbuffer(GL_RENDERBUFFER, buffer->rbo); + renderer->procs.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, + buffer->image); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + glGenFramebuffers(1, &buffer->fbo); + glBindFramebuffer(GL_FRAMEBUFFER, buffer->fbo); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, buffer->rbo); + GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (fb_status != GL_FRAMEBUFFER_COMPLETE) { + wlr_log(WLR_ERROR, "Failed to create FBO"); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + goto error_image; + } + + // Init stencil buffer + glGenRenderbuffers(1, &buffer->sb); + glBindRenderbuffer(GL_RENDERBUFFER, buffer->sb); + glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, + wlr_buffer->width, wlr_buffer->height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, buffer->sb); + fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (fb_status != GL_FRAMEBUFFER_COMPLETE) { + wlr_log(WLR_ERROR, + "Stencil buffer incomplete, couldn't create! (FB status: %i)", + fb_status); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + goto error_stencil; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + pop_fx_debug(renderer); + + wlr_addon_init(&buffer->addon, &wlr_buffer->addons, renderer, + &buffer_addon_impl); + + wl_list_insert(&renderer->buffers, &buffer->link); + + wlr_log(WLR_DEBUG, "Created GL FBO for buffer %dx%d", + wlr_buffer->width, wlr_buffer->height); + + return buffer; + +error_stencil: + glDeleteRenderbuffers(1, &buffer->sb); +error_image: + wlr_egl_destroy_image(renderer->egl, buffer->image); +error_buffer: + free(buffer); + return NULL; +} + +void fx_framebuffer_bind(struct fx_framebuffer *fx_buffer) { + glBindFramebuffer(GL_FRAMEBUFFER, fx_buffer->fbo); +} + +void fx_framebuffer_bind_wlr_fbo(struct fx_renderer *renderer) { + glBindFramebuffer(GL_FRAMEBUFFER, renderer->current_buffer->fbo); +} + +void fx_framebuffer_destroy(struct fx_framebuffer *fx_buffer) { + // Release the framebuffer + wl_list_remove(&fx_buffer->link); + wlr_addon_finish(&fx_buffer->addon); + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(fx_buffer->renderer->egl); + + glDeleteFramebuffers(1, &fx_buffer->fbo); + fx_buffer->fbo = -1; + glDeleteRenderbuffers(1, &fx_buffer->rbo); + fx_buffer->rbo = -1; + + wlr_egl_destroy_image(fx_buffer->renderer->egl, fx_buffer->image); + + wlr_egl_restore_context(&prev_ctx); + + free(fx_buffer); +} diff --git a/render/fx_renderer/fx_pass.c b/render/fx_renderer/fx_pass.c new file mode 100644 index 0000000..9a8c90c --- /dev/null +++ b/render/fx_renderer/fx_pass.c @@ -0,0 +1,509 @@ +#define _POSIX_C_SOURCE 199309L +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <pixman.h> +#include <time.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> + +#include "render/egl.h" +#include "render/fx_renderer/fx_renderer.h" +#include "render/fx_renderer/matrix.h" +#include "render/pass.h" +#include "types/fx/shadow_data.h" + +#define MAX_QUADS 86 // 4kb + +struct fx_render_texture_options fx_render_texture_options_default( + const struct wlr_render_texture_options *base) { + struct fx_render_texture_options options = { + .corner_radius = 0, + .scale = 1.0f, + .clip_box = NULL, + }; + memcpy(&options.base, base, sizeof(*base)); + return options; +} + +struct fx_render_rect_options fx_render_rect_options_default( + const struct wlr_render_rect_options *base) { + struct fx_render_rect_options options = { + .scale = 1.0f, + }; + memcpy(&options.base, base, sizeof(*base)); + return options; +} + +/// +/// Base Wlroots pass functions +/// + +static const struct wlr_render_pass_impl render_pass_impl; + +static struct fx_gles_render_pass *get_render_pass(struct wlr_render_pass *wlr_pass) { + assert(wlr_pass->impl == &render_pass_impl); + struct fx_gles_render_pass *pass = wl_container_of(wlr_pass, pass, base); + return pass; +} + +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; + + push_fx_debug(renderer); + + if (timer) { + // clear disjoint flag + GLint64 disjoint; + renderer->procs.glGetInteger64vEXT(GL_GPU_DISJOINT_EXT, &disjoint); + // set up the query + renderer->procs.glQueryCounterEXT(timer->id, GL_TIMESTAMP_EXT); + // get end-of-CPU-work time in GL time domain + renderer->procs.glGetInteger64vEXT(GL_TIMESTAMP_EXT, &timer->gl_cpu_end); + // get end-of-CPU-work time in CPU time domain + clock_gettime(CLOCK_MONOTONIC, &timer->cpu_end); + } + + glFlush(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + pop_fx_debug(renderer); + + wlr_buffer_unlock(pass->buffer->buffer); + free(pass); + + return true; +} + +static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, + const struct wlr_render_texture_options *options) { + struct fx_gles_render_pass *pass = get_render_pass(wlr_pass); + const struct fx_render_texture_options fx_options = + fx_render_texture_options_default(options); + // Re-use fx function but with default options + fx_render_pass_add_texture(pass, &fx_options); +} + +static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, + const struct wlr_render_rect_options *options) { + struct fx_gles_render_pass *pass = get_render_pass(wlr_pass); + const struct fx_render_rect_options fx_options = + fx_render_rect_options_default(options); + // Re-use fx function but with default options + fx_render_pass_add_rect(pass, &fx_options); +} + +static const struct wlr_render_pass_impl render_pass_impl = { + .submit = render_pass_submit, + .add_texture = render_pass_add_texture, + .add_rect = render_pass_add_rect, +}; + +/// +/// FX pass functions +/// + +static void render(const struct wlr_box *box, const pixman_region32_t *clip, GLint attrib) { + pixman_region32_t region; + pixman_region32_init_rect(®ion, box->x, box->y, box->width, box->height); + + if (clip) { + pixman_region32_intersect(®ion, ®ion, clip); + } + + int rects_len; + const pixman_box32_t *rects = pixman_region32_rectangles(®ion, &rects_len); + if (rects_len == 0) { + pixman_region32_fini(®ion); + return; + } + + glEnableVertexAttribArray(attrib); + + for (int i = 0; i < rects_len;) { + int batch = rects_len - i < MAX_QUADS ? rects_len - i : MAX_QUADS; + int batch_end = batch + i; + + size_t vert_index = 0; + GLfloat verts[MAX_QUADS * 6 * 2]; + for (; i < batch_end; i++) { + const pixman_box32_t *rect = &rects[i]; + + verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; + verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; + verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; + verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height; + verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; + verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width; + verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height; + } + + glVertexAttribPointer(attrib, 2, GL_FLOAT, GL_FALSE, 0, verts); + glDrawArrays(GL_TRIANGLES, 0, batch * 6); + } + + glDisableVertexAttribArray(attrib); + + pixman_region32_fini(®ion); +} + +static void set_proj_matrix(GLint loc, float proj[9], const struct wlr_box *box) { + float gl_matrix[9]; + wlr_matrix_identity(gl_matrix); + wlr_matrix_translate(gl_matrix, box->x, box->y); + wlr_matrix_scale(gl_matrix, box->width, box->height); + wlr_matrix_multiply(gl_matrix, proj, gl_matrix); + glUniformMatrix3fv(loc, 1, GL_FALSE, gl_matrix); +} + +static void set_tex_matrix(GLint loc, enum wl_output_transform trans, + const struct wlr_fbox *box) { + float tex_matrix[9]; + wlr_matrix_identity(tex_matrix); + wlr_matrix_translate(tex_matrix, box->x, box->y); + wlr_matrix_scale(tex_matrix, box->width, box->height); + wlr_matrix_translate(tex_matrix, .5, .5); + + // since textures have a different origin point we have to transform + // differently if we are rotating + if (trans & WL_OUTPUT_TRANSFORM_90) { + wlr_matrix_transform(tex_matrix, wlr_output_transform_invert(trans)); + } else { + wlr_matrix_transform(tex_matrix, trans); + } + wlr_matrix_translate(tex_matrix, -.5, -.5); + + glUniformMatrix3fv(loc, 1, GL_FALSE, tex_matrix); +} + +static void setup_blending(enum wlr_render_blend_mode mode) { + switch (mode) { + case WLR_RENDER_BLEND_MODE_PREMULTIPLIED: + glEnable(GL_BLEND); + break; + case WLR_RENDER_BLEND_MODE_NONE: + glDisable(GL_BLEND); + break; + } +} + +// Initialize the stenciling work +static void stencil_mask_init(void) { + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); + glEnable(GL_STENCIL_TEST); + + glStencilFunc(GL_ALWAYS, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + // Disable writing to color buffer + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); +} + +// Close the mask +static void stencil_mask_close(bool draw_inside_mask) { + // Reenable writing to color buffer + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + if (draw_inside_mask) { + glStencilFunc(GL_EQUAL, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + return; + } + glStencilFunc(GL_NOTEQUAL, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); +} + +// Finish stenciling and clear the buffer +static void stencil_mask_fini(void) { + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); + glDisable(GL_STENCIL_TEST); +} + +// make sure the texture source box does not try and sample outside of the +// texture +static void check_tex_src_box(const struct wlr_render_texture_options *options) { + if (!wlr_fbox_empty(&options->src_box)) { + const struct wlr_fbox *box = &options->src_box; + assert(box->x >= 0 && box->y >= 0 && + box->x + box->width <= options->texture->width && + box->y + box->height <= options->texture->height); + } +} + +void fx_render_pass_add_texture(struct fx_gles_render_pass *pass, + const struct fx_render_texture_options *fx_options) { + const struct wlr_render_texture_options *options = &fx_options->base; + + check_tex_src_box(options); + + struct fx_renderer *renderer = pass->buffer->renderer; + struct fx_texture *texture = fx_get_texture(options->texture); + + struct tex_shader *shader = NULL; + + switch (texture->target) { + case GL_TEXTURE_2D: + if (texture->has_alpha) { + shader = &renderer->shaders.tex_rgba; + } else { + shader = &renderer->shaders.tex_rgbx; + } + break; + case GL_TEXTURE_EXTERNAL_OES: + // EGL_EXT_image_dma_buf_import_modifiers requires + // GL_OES_EGL_image_external + assert(renderer->exts.OES_egl_image_external); + shader = &renderer->shaders.tex_ext; + break; + default: + abort(); + } + + struct wlr_box dst_box; + struct wlr_fbox src_fbox; + wlr_render_texture_options_get_src_box(options, &src_fbox); + wlr_render_texture_options_get_dst_box(options, &dst_box); + float alpha = wlr_render_texture_options_get_alpha(options); + + struct wlr_box *clip_box = &dst_box; + if (!wlr_box_empty(fx_options->clip_box)) { + clip_box = fx_options->clip_box; + } + + src_fbox.x /= options->texture->width; + src_fbox.y /= options->texture->height; + src_fbox.width /= options->texture->width; + src_fbox.height /= options->texture->height; + + push_fx_debug(renderer); + setup_blending(!texture->has_alpha && alpha == 1.0 ? + WLR_RENDER_BLEND_MODE_NONE : options->blend_mode); + + glUseProgram(shader->program); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(texture->target, texture->tex); + + switch (options->filter_mode) { + case WLR_SCALE_FILTER_BILINEAR: + glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + break; + case WLR_SCALE_FILTER_NEAREST: + glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + break; + } + + glUniform1i(shader->tex, 0); + glUniform1f(shader->alpha, alpha); + glUniform2f(shader->size, clip_box->width, clip_box->height); + glUniform2f(shader->position, clip_box->x, clip_box->y); + glUniform1f(shader->radius, fx_options->corner_radius); + + set_proj_matrix(shader->proj, pass->projection_matrix, &dst_box); + set_tex_matrix(shader->tex_proj, options->transform, &src_fbox); + + render(&dst_box, options->clip, shader->pos_attrib); + + glBindTexture(texture->target, 0); + pop_fx_debug(renderer); +} + +void fx_render_pass_add_rect(struct fx_gles_render_pass *pass, + const struct fx_render_rect_options *fx_options) { + const struct wlr_render_rect_options *options = &fx_options->base; + + struct fx_renderer *renderer = pass->buffer->renderer; + + const struct wlr_render_color *color = &options->color; + struct wlr_box box; + wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box); + + push_fx_debug(renderer); + setup_blending(color->a == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode); + + glUseProgram(renderer->shaders.quad.program); + + set_proj_matrix(renderer->shaders.quad.proj, pass->projection_matrix, &box); + glUniform4f(renderer->shaders.quad.color, color->r, color->g, color->b, color->a); + + render(&box, options->clip, renderer->shaders.quad.pos_attrib); + + pop_fx_debug(renderer); +} + +void fx_render_pass_add_stencil_mask(struct fx_gles_render_pass *pass, + const struct fx_render_rect_options *fx_options, int corner_radius) { + const struct wlr_render_rect_options *options = &fx_options->base; + + struct fx_renderer *renderer = pass->buffer->renderer; + + const struct wlr_render_color *color = &options->color; + struct wlr_box box; + wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box); + assert(box.width > 0 && box.height > 0); + + push_fx_debug(renderer); + setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED); + + glUseProgram(renderer->shaders.stencil_mask.program); + + set_proj_matrix(renderer->shaders.stencil_mask.proj, pass->projection_matrix, &box); + glUniform4f(renderer->shaders.stencil_mask.color, color->r, color->g, color->b, color->a); + glUniform2f(renderer->shaders.stencil_mask.half_size, box.width * 0.5, box.height * 0.5); + glUniform2f(renderer->shaders.stencil_mask.position, box.x, box.y); + glUniform1f(renderer->shaders.stencil_mask.radius, corner_radius); + + render(&box, options->clip, renderer->shaders.stencil_mask.pos_attrib); + + pop_fx_debug(renderer); +} + +void fx_render_pass_add_box_shadow(struct fx_gles_render_pass *pass, + const struct fx_render_rect_options *fx_options, + int corner_radius, struct shadow_data *shadow_data) { + const struct wlr_render_rect_options *options = &fx_options->base; + + struct fx_renderer *renderer = pass->buffer->renderer; + + const struct wlr_render_color *color = &shadow_data->color; + struct wlr_box box; + wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box); + assert(box.width > 0 && box.height > 0); + struct wlr_box surface_box = box; + float blur_sigma = shadow_data->blur_sigma * fx_options->scale; + + // Extend the size of the box + box.x -= blur_sigma; + box.y -= blur_sigma; + box.width += blur_sigma * 2; + box.height += blur_sigma * 2; + + pixman_region32_t render_region; + pixman_region32_init(&render_region); + + pixman_region32_t inner_region; + pixman_region32_init(&inner_region); + pixman_region32_union_rect(&inner_region, &inner_region, + surface_box.x + corner_radius * 0.5, + surface_box.y + corner_radius * 0.5, + surface_box.width - corner_radius, + surface_box.height - corner_radius); + pixman_region32_subtract(&render_region, options->clip, &inner_region); + pixman_region32_fini(&inner_region); + + push_fx_debug(renderer); + + // Init stencil work + stencil_mask_init(); + // Draw the rounded rect as a mask + fx_render_pass_add_stencil_mask(pass, fx_options, corner_radius); + stencil_mask_close(false); + + // blending will practically always be needed (unless we have a madman + // who uses opaque shadows with zero sigma), so just enable it + setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glUseProgram(renderer->shaders.box_shadow.program); + + set_proj_matrix(renderer->shaders.box_shadow.proj, pass->projection_matrix, &box); + glUniform4f(renderer->shaders.box_shadow.color, color->r, color->g, color->b, color->a); + glUniform1f(renderer->shaders.box_shadow.blur_sigma, blur_sigma); + glUniform1f(renderer->shaders.box_shadow.corner_radius, corner_radius); + glUniform2f(renderer->shaders.box_shadow.size, box.width, box.height); + glUniform2f(renderer->shaders.box_shadow.position, box.x, box.y); + + render(&box, &render_region, renderer->shaders.box_shadow.pos_attrib); + pixman_region32_fini(&render_region); + + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + stencil_mask_fini(); + + pop_fx_debug(renderer); +} + +static const char *reset_status_str(GLenum status) { + switch (status) { + case GL_GUILTY_CONTEXT_RESET_KHR: + return "guilty"; + case GL_INNOCENT_CONTEXT_RESET_KHR: + return "innocent"; + case GL_UNKNOWN_CONTEXT_RESET_KHR: + return "unknown"; + default: + return "<invalid>"; + } +} + +static struct fx_gles_render_pass *begin_buffer_pass(struct fx_framebuffer *buffer, + struct fx_render_timer *timer) { + struct fx_renderer *renderer = buffer->renderer; + struct wlr_buffer *wlr_buffer = buffer->buffer; + + if (renderer->procs.glGetGraphicsResetStatusKHR) { + GLenum status = renderer->procs.glGetGraphicsResetStatusKHR(); + if (status != GL_NO_ERROR) { + wlr_log(WLR_ERROR, "GPU reset (%s)", reset_status_str(status)); + wl_signal_emit_mutable(&renderer->wlr_renderer.events.lost, NULL); + return NULL; + } + } + + struct fx_gles_render_pass *pass = calloc(1, sizeof(*pass)); + if (pass == NULL) { + return NULL; + } + + wlr_render_pass_init(&pass->base, &render_pass_impl); + wlr_buffer_lock(wlr_buffer); + pass->buffer = buffer; + pass->timer = timer; + + matrix_projection(pass->projection_matrix, wlr_buffer->width, wlr_buffer->height, + WL_OUTPUT_TRANSFORM_FLIPPED_180); + + push_fx_debug(renderer); + glBindFramebuffer(GL_FRAMEBUFFER, buffer->fbo); + + glViewport(0, 0, wlr_buffer->width, wlr_buffer->height); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_SCISSOR_TEST); + pop_fx_debug(renderer); + + return pass; +} + +struct fx_gles_render_pass *fx_renderer_begin_buffer_pass(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *wlr_buffer, const struct wlr_buffer_pass_options *options) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); + if (!wlr_egl_make_current(renderer->egl)) { + return NULL; + } + + struct fx_render_timer *timer = NULL; + if (options->timer) { + timer = fx_get_render_timer(options->timer); + clock_gettime(CLOCK_MONOTONIC, &timer->cpu_start); + } + + struct fx_framebuffer *buffer = fx_framebuffer_get_or_create(renderer, wlr_buffer); + if (!buffer) { + return NULL; + } + + struct fx_gles_render_pass *pass = begin_buffer_pass(buffer, timer); + if (!pass) { + return NULL; + } + return pass; +} diff --git a/render/fx_renderer/fx_renderer.c b/render/fx_renderer/fx_renderer.c index 730e314..da9437c 100644 --- a/render/fx_renderer/fx_renderer.c +++ b/render/fx_renderer/fx_renderer.c @@ -3,27 +3,28 @@ https://gitlab.freedesktop.org/wlroots/wlroots/-/tree/master/render/gles2 */ +#define _POSIX_C_SOURCE 199309L #include <assert.h> +#include <drm_fourcc.h> #include <GLES2/gl2.h> #include <stdio.h> #include <stdlib.h> +#include <time.h> +#include <unistd.h> #include <wlr/backend.h> +#include <wlr/render/allocator.h> #include <wlr/render/egl.h> -#include <wlr/render/gles2.h> +#include <wlr/render/interface.h> #include <wlr/types/wlr_matrix.h> #include <wlr/util/box.h> #include <wlr/util/log.h> +#include "render/egl.h" #include "render/fx_renderer/fx_renderer.h" -#include "render/fx_renderer/fx_stencilbuffer.h" #include "render/fx_renderer/matrix.h" - -// shaders -#include "common_vert_src.h" -#include "quad_frag_src.h" -#include "tex_frag_src.h" -#include "stencil_mask_frag_src.h" -#include "box_shadow_frag_src.h" +#include "render/fx_renderer/util.h" +#include "render/pixel_format.h" +#include "util/time.h" static const GLfloat verts[] = { 1, 0, // top right @@ -32,372 +33,179 @@ static const GLfloat verts[] = { 0, 1, // bottom left }; -static GLuint compile_shader(GLuint type, const GLchar *src) { - GLuint shader = glCreateShader(type); - glShaderSource(shader, 1, &src, NULL); - glCompileShader(shader); - - GLint ok; - glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); - if (ok == GL_FALSE) { - wlr_log(WLR_ERROR, "Failed to compile shader"); - glDeleteShader(shader); - shader = 0; - } +static const struct wlr_renderer_impl renderer_impl; +static const struct wlr_render_timer_impl render_timer_impl; - return shader; +bool wlr_renderer_is_fx(struct wlr_renderer *wlr_renderer) { + return wlr_renderer->impl == &renderer_impl; } -static GLuint link_program(const GLchar *frag_src) { - const GLchar *vert_src = common_vert_src; - GLuint vert = compile_shader(GL_VERTEX_SHADER, vert_src); - if (!vert) { - goto error; - } - - GLuint frag = compile_shader(GL_FRAGMENT_SHADER, frag_src); - if (!frag) { - glDeleteShader(vert); - goto error; - } - - GLuint prog = glCreateProgram(); - glAttachShader(prog, vert); - glAttachShader(prog, frag); - glLinkProgram(prog); - - glDetachShader(prog, vert); - glDetachShader(prog, frag); - glDeleteShader(vert); - glDeleteShader(frag); - - GLint ok; - glGetProgramiv(prog, GL_LINK_STATUS, &ok); - if (ok == GL_FALSE) { - wlr_log(WLR_ERROR, "Failed to link shader"); - glDeleteProgram(prog); - goto error; - } - - return prog; - -error: - return 0; +struct fx_renderer *fx_get_renderer( + struct wlr_renderer *wlr_renderer) { + assert(wlr_renderer_is_fx(wlr_renderer)); + struct fx_renderer *renderer = wl_container_of(wlr_renderer, renderer, wlr_renderer); + return renderer; } -static bool link_quad_program(struct quad_shader *shader) { - GLuint prog; - shader->program = prog = link_program(quad_frag_src); - if (!shader->program) { - return false; - } - - shader->proj = glGetUniformLocation(prog, "proj"); - shader->color = glGetUniformLocation(prog, "color"); - shader->pos_attrib = glGetAttribLocation(prog, "pos"); +static struct fx_renderer *fx_get_renderer_in_context( + struct wlr_renderer *wlr_renderer) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); + assert(wlr_egl_is_current(renderer->egl)); + assert(renderer->current_buffer != NULL); + return renderer; +} - return true; +bool wlr_render_timer_is_fx(struct wlr_render_timer *timer) { + return timer->impl == &render_timer_impl; } -static bool link_tex_program(struct tex_shader *shader, - enum fx_tex_shader_source source) { - GLchar frag_src[2048]; - snprintf(frag_src, sizeof(frag_src), - "#define SOURCE %d\n%s", source, tex_frag_src); +struct fx_render_timer *fx_get_render_timer(struct wlr_render_timer *wlr_timer) { + assert(wlr_render_timer_is_fx(wlr_timer)); + struct fx_render_timer *timer = wl_container_of(wlr_timer, timer, base); + return timer; +} - GLuint prog; - shader->program = prog = link_program(frag_src); - if (!shader->program) { - return false; - } +static bool fx_bind_main_buffer(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *wlr_buffer) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); - shader->proj = glGetUniformLocation(prog, "proj"); - shader->tex = glGetUniformLocation(prog, "tex"); - shader->alpha = glGetUniformLocation(prog, "alpha"); - shader->pos_attrib = glGetAttribLocation(prog, "pos"); - shader->tex_attrib = glGetAttribLocation(prog, "texcoord"); - shader->size = glGetUniformLocation(prog, "size"); - shader->position = glGetUniformLocation(prog, "position"); - shader->radius = glGetUniformLocation(prog, "radius"); + if (renderer->current_buffer != NULL) { + assert(wlr_egl_is_current(renderer->egl)); - return true; -} + push_fx_debug(renderer); + glFlush(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + pop_fx_debug(renderer); -static bool link_stencil_mask_program(struct stencil_mask_shader *shader) { - GLuint prog; - shader->program = prog = link_program(stencil_mask_frag_src); - if (!shader->program) { - return false; + wlr_buffer_unlock(renderer->current_buffer->buffer); + renderer->current_buffer = NULL; } - shader->proj = glGetUniformLocation(prog, "proj"); - shader->color = glGetUniformLocation(prog, "color"); - shader->pos_attrib = glGetAttribLocation(prog, "pos"); - shader->position = glGetUniformLocation(prog, "position"); - shader->half_size = glGetUniformLocation(prog, "half_size"); - shader->radius = glGetUniformLocation(prog, "radius"); + if (wlr_buffer == NULL) { + wlr_egl_unset_current(renderer->egl); + return true; + } - return true; -} + wlr_egl_make_current(renderer->egl); -static bool link_box_shadow_program(struct box_shadow_shader *shader) { - GLuint prog; - shader->program = prog = link_program(box_shadow_frag_src); - if (!shader->program) { + struct fx_framebuffer *buffer = fx_framebuffer_get_or_create(renderer, wlr_buffer); + if (buffer == NULL) { return false; } - shader->proj = glGetUniformLocation(prog, "proj"); - shader->color = glGetUniformLocation(prog, "color"); - shader->pos_attrib = glGetAttribLocation(prog, "pos"); - shader->position = glGetUniformLocation(prog, "position"); - shader->size = glGetUniformLocation(prog, "size"); - shader->blur_sigma = glGetUniformLocation(prog, "blur_sigma"); - shader->corner_radius = glGetUniformLocation(prog, "corner_radius"); - return true; -} + wlr_buffer_lock(wlr_buffer); + renderer->current_buffer = buffer; -static bool check_gl_ext(const char *exts, const char *ext) { - size_t extlen = strlen(ext); - const char *end = exts + strlen(exts); + push_fx_debug(renderer); + glBindFramebuffer(GL_FRAMEBUFFER, renderer->current_buffer->fbo); + pop_fx_debug(renderer); - while (exts < end) { - if (exts[0] == ' ') { - exts++; - continue; - } - size_t n = strcspn(exts, " "); - if (n == extlen && strncmp(ext, exts, n) == 0) { - return true; - } - exts += n; - } - return false; + return true; } -static void load_gl_proc(void *proc_ptr, const char *name) { - void *proc = (void *)eglGetProcAddress(name); - if (proc == NULL) { - wlr_log(WLR_ERROR, "GLES2 RENDERER: eglGetProcAddress(%s) failed", name); - abort(); +static const char *reset_status_str(GLenum status) { + switch (status) { + case GL_GUILTY_CONTEXT_RESET_KHR: + return "guilty"; + case GL_INNOCENT_CONTEXT_RESET_KHR: + return "innocent"; + case GL_UNKNOWN_CONTEXT_RESET_KHR: + return "unknown"; + default: + return "<invalid>"; } - *(void **)proc_ptr = proc; } -static void fx_renderer_handle_destroy(struct wlr_addon *addon) { +static bool fx_renderer_begin(struct wlr_renderer *wlr_renderer, uint32_t width, + uint32_t height) { struct fx_renderer *renderer = - wl_container_of(addon, renderer, addon); - fx_renderer_fini(renderer); - free(renderer); -} -static const struct wlr_addon_interface fx_renderer_addon_impl = { - .name = "fx_renderer", - .destroy = fx_renderer_handle_destroy, -}; - -void fx_renderer_init_addon(struct wlr_egl *egl, struct wlr_addon_set *addons, - const void * owner) { - struct fx_renderer *renderer = fx_renderer_create(egl); - if (!renderer) { - wlr_log(WLR_ERROR, "Failed to create fx_renderer"); - abort(); - } - wlr_addon_init(&renderer->addon, addons, owner, &fx_renderer_addon_impl); -} - -struct fx_renderer *fx_renderer_addon_find(struct wlr_addon_set *addons, - const void * owner) { - struct wlr_addon *addon = - wlr_addon_find(addons, owner, &fx_renderer_addon_impl); - if (addon == NULL) { - return NULL; - } - struct fx_renderer *renderer = wl_container_of(addon, renderer, addon); - return renderer; -} - -struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) { - struct fx_renderer *renderer = calloc(1, sizeof(struct fx_renderer)); - if (renderer == NULL) { - return NULL; - } - - if (!eglMakeCurrent(wlr_egl_get_display(egl), EGL_NO_SURFACE, EGL_NO_SURFACE, - wlr_egl_get_context(egl))) { - wlr_log(WLR_ERROR, "GLES2 RENDERER: Could not make EGL current"); - return NULL; - } - - renderer->stencil_buffer = fx_stencilbuffer_create(); - - // get extensions - const char *exts_str = (const char *)glGetString(GL_EXTENSIONS); - if (exts_str == NULL) { - wlr_log(WLR_ERROR, "GLES2 RENDERER: Failed to get GL_EXTENSIONS"); - return NULL; - } - - wlr_log(WLR_INFO, "Creating scenefx GLES2 renderer"); - wlr_log(WLR_INFO, "Using %s", glGetString(GL_VERSION)); - wlr_log(WLR_INFO, "GL vendor: %s", glGetString(GL_VENDOR)); - wlr_log(WLR_INFO, "GL renderer: %s", glGetString(GL_RENDERER)); - wlr_log(WLR_INFO, "Supported GLES2 extensions: %s", exts_str); + fx_get_renderer_in_context(wlr_renderer); - // TODO: the rest of the gl checks - if (check_gl_ext(exts_str, "GL_OES_EGL_image_external")) { - renderer->exts.OES_egl_image_external = true; - load_gl_proc(&renderer->procs.glEGLImageTargetTexture2DOES, - "glEGLImageTargetTexture2DOES"); - } + push_fx_debug(renderer); - // quad fragment shader - if (!link_quad_program(&renderer->shaders.quad)) { - goto error; - } - // fragment shaders - if (!link_tex_program(&renderer->shaders.tex_rgba, SHADER_SOURCE_TEXTURE_RGBA)) { - goto error; - } - if (!link_tex_program(&renderer->shaders.tex_rgbx, SHADER_SOURCE_TEXTURE_RGBX)) { - goto error; - } - if (!link_tex_program(&renderer->shaders.tex_ext, SHADER_SOURCE_TEXTURE_EXTERNAL)) { - goto error; - } - - // stencil mask shader - if (!link_stencil_mask_program(&renderer->shaders.stencil_mask)) { - goto error; - } - // box shadow shader - if (!link_box_shadow_program(&renderer->shaders.box_shadow)) { - goto error; + if (renderer->procs.glGetGraphicsResetStatusKHR) { + GLenum status = renderer->procs.glGetGraphicsResetStatusKHR(); + if (status != GL_NO_ERROR) { + wlr_log(WLR_ERROR, "GPU reset (%s)", reset_status_str(status)); + wl_signal_emit_mutable(&wlr_renderer->events.lost, NULL); + pop_fx_debug(renderer); + return false; + } } - if (!eglMakeCurrent(wlr_egl_get_display(egl), - EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { - wlr_log(WLR_ERROR, "GLES2 RENDERER: Could not unset current EGL"); - goto error; - } + glViewport(0, 0, width, height); + renderer->viewport_width = width; + renderer->viewport_height = height; - wlr_log(WLR_INFO, "GLES2 RENDERER: Shaders Initialized Successfully"); - return renderer; + // refresh projection matrix + matrix_projection(renderer->projection, width, height, + WL_OUTPUT_TRANSFORM_FLIPPED_180); -error: - glDeleteProgram(renderer->shaders.quad.program); - glDeleteProgram(renderer->shaders.tex_rgba.program); - glDeleteProgram(renderer->shaders.tex_rgbx.program); - glDeleteProgram(renderer->shaders.tex_ext.program); - glDeleteProgram(renderer->shaders.stencil_mask.program); - glDeleteProgram(renderer->shaders.box_shadow.program); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - if (!eglMakeCurrent(wlr_egl_get_display(egl), - EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { - wlr_log(WLR_ERROR, "GLES2 RENDERER: Could not unset current EGL"); - } + // XXX: maybe we should save output projection and remove some of the need + // for users to sling matricies themselves - // TODO: more freeing? - free(renderer); + pop_fx_debug(renderer); - wlr_log(WLR_ERROR, "GLES2 RENDERER: Error Initializing Shaders"); - return NULL; + return true; } -void fx_renderer_fini(struct fx_renderer *renderer) { - fx_stencilbuffer_release(&renderer->stencil_buffer); +static void fx_renderer_end(struct wlr_renderer *wlr_renderer) { + fx_get_renderer_in_context(wlr_renderer); + // no-op } -void fx_renderer_begin(struct fx_renderer *renderer, int width, int height) { - fx_stencilbuffer_init(&renderer->stencil_buffer, width, height); - - glViewport(0, 0, width, height); - - // refresh projection matrix - matrix_projection(renderer->projection, width, height, - WL_OUTPUT_TRANSFORM_FLIPPED_180); - - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); -} +static void fx_renderer_clear(struct wlr_renderer *wlr_renderer, + const float color[static 4]) { + struct fx_renderer *renderer = + fx_get_renderer_in_context(wlr_renderer); -void fx_renderer_clear(const float color[static 4]) { + push_fx_debug(renderer); glClearColor(color[0], color[1], color[2], color[3]); glClearStencil(0); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + pop_fx_debug(renderer); } -void fx_renderer_scissor(struct wlr_box *box) { - if (box) { +static void fx_renderer_scissor(struct wlr_renderer *wlr_renderer, + struct wlr_box *box) { + struct fx_renderer *renderer = + fx_get_renderer_in_context(wlr_renderer); + + push_fx_debug(renderer); + if (box != NULL) { glScissor(box->x, box->y, box->width, box->height); glEnable(GL_SCISSOR_TEST); } else { glDisable(GL_SCISSOR_TEST); } + pop_fx_debug(renderer); } -void fx_renderer_stencil_mask_init(void) { - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - glEnable(GL_STENCIL_TEST); - - glStencilFunc(GL_ALWAYS, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - // Disable writing to color buffer - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); -} - -void fx_renderer_stencil_mask_close(bool draw_inside_mask) { - // Reenable writing to color buffer - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - if (draw_inside_mask) { - glStencilFunc(GL_EQUAL, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - return; - } - glStencilFunc(GL_NOTEQUAL, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); -} - -void fx_renderer_stencil_mask_fini(void) { - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - glDisable(GL_STENCIL_TEST); -} - -void fx_renderer_stencil_enable(void) { - glEnable(GL_STENCIL_TEST); -} - -void fx_renderer_stencil_disable(void) { - glDisable(GL_STENCIL_TEST); -} - -bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, - struct wlr_texture *wlr_texture, const struct wlr_fbox *src_box, - const struct wlr_box *dst_box, const float matrix[static 9], - float opacity, int corner_radius) { - - assert(wlr_texture_is_gles2(wlr_texture)); - struct wlr_gles2_texture_attribs texture_attrs; - wlr_gles2_texture_get_attribs(wlr_texture, &texture_attrs); +static bool fx_render_subtexture_with_matrix( + struct wlr_renderer *wlr_renderer, struct wlr_texture *wlr_texture, + const struct wlr_fbox *box, const float matrix[static 9], + float alpha) { + struct fx_renderer *renderer = fx_get_renderer_in_context(wlr_renderer); + struct fx_texture *texture = fx_get_texture(wlr_texture); + assert(texture->fx_renderer == renderer); struct tex_shader *shader = NULL; - switch (texture_attrs.target) { + switch (texture->target) { case GL_TEXTURE_2D: - if (texture_attrs.has_alpha) { + if (texture->has_alpha) { shader = &renderer->shaders.tex_rgba; } else { shader = &renderer->shaders.tex_rgbx; } break; case GL_TEXTURE_EXTERNAL_OES: + // EGL_EXT_image_dma_buf_import_modifiers requires + // GL_OES_EGL_image_external + assert(renderer->exts.OES_egl_image_external); shader = &renderer->shaders.tex_ext; - - if (!renderer->exts.OES_egl_image_external) { - wlr_log(WLR_ERROR, "Failed to render texture: " - "GL_TEXTURE_EXTERNAL_OES not supported"); - return false; - } break; default: wlr_log(WLR_ERROR, "Aborting render"); @@ -407,12 +215,9 @@ bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, float gl_matrix[9]; wlr_matrix_multiply(gl_matrix, renderer->projection, matrix); - // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set - // to GL_FALSE - wlr_matrix_transpose(gl_matrix, gl_matrix); + push_fx_debug(renderer); - // if there's no opacity or rounded corners we don't need to blend - if (!texture_attrs.has_alpha && opacity == 1.0 && !corner_radius) { + if (!texture->has_alpha && alpha == 1.0) { glDisable(GL_BLEND); } else { glEnable(GL_BLEND); @@ -421,62 +226,49 @@ bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glActiveTexture(GL_TEXTURE0); - glBindTexture(texture_attrs.target, texture_attrs.tex); + glBindTexture(texture->target, texture->tex); - glTexParameteri(texture_attrs.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glUseProgram(shader->program); glUniformMatrix3fv(shader->proj, 1, GL_FALSE, gl_matrix); glUniform1i(shader->tex, 0); - glUniform2f(shader->size, dst_box->width, dst_box->height); - glUniform2f(shader->position, dst_box->x, dst_box->y); - glUniform1f(shader->alpha, opacity); - glUniform1f(shader->radius, corner_radius); - - const GLfloat x1 = src_box->x / wlr_texture->width; - const GLfloat y1 = src_box->y / wlr_texture->height; - const GLfloat x2 = (src_box->x + src_box->width) / wlr_texture->width; - const GLfloat y2 = (src_box->y + src_box->height) / wlr_texture->height; - const GLfloat texcoord[] = { - x2, y1, // top right - x1, y1, // top left - x2, y2, // bottom right - x1, y2, // bottom left - }; + glUniform1f(shader->alpha, alpha); + glUniform2f(shader->size, box->width, box->height); + glUniform2f(shader->position, box->x, box->y); + glUniform1f(shader->radius, 0); + + float tex_matrix[9]; + wlr_matrix_identity(tex_matrix); + wlr_matrix_translate(tex_matrix, box->x / texture->wlr_texture.width, + box->y / texture->wlr_texture.height); + wlr_matrix_scale(tex_matrix, box->width / texture->wlr_texture.width, + box->height / texture->wlr_texture.height); + glUniformMatrix3fv(shader->tex_proj, 1, GL_FALSE, tex_matrix); glVertexAttribPointer(shader->pos_attrib, 2, GL_FLOAT, GL_FALSE, 0, verts); - glVertexAttribPointer(shader->tex_attrib, 2, GL_FLOAT, GL_FALSE, 0, texcoord); glEnableVertexAttribArray(shader->pos_attrib); - glEnableVertexAttribArray(shader->tex_attrib); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(shader->pos_attrib); - glDisableVertexAttribArray(shader->tex_attrib); - glBindTexture(texture_attrs.target, 0); + glBindTexture(texture->target, 0); + pop_fx_debug(renderer); return true; } -void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box, - const float color[static 4], const float projection[static 9]) { - if (box->width == 0 || box->height == 0) { - return; - } - assert(box->width > 0 && box->height > 0); - float matrix[9]; - wlr_matrix_project_box(matrix, box, WL_OUTPUT_TRANSFORM_NORMAL, 0, projection); +static void fx_render_quad_with_matrix(struct wlr_renderer *wlr_renderer, + const float color[static 4], const float matrix[static 9]) { + struct fx_renderer *renderer = fx_get_renderer_in_context(wlr_renderer); float gl_matrix[9]; wlr_matrix_multiply(gl_matrix, renderer->projection, matrix); - // TODO: investigate why matrix is flipped prior to this cmd - // wlr_matrix_multiply(gl_matrix, flip_180, gl_matrix); - - wlr_matrix_transpose(gl_matrix, gl_matrix); + push_fx_debug(renderer); if (color[3] == 1.0) { glDisable(GL_BLEND); @@ -484,115 +276,505 @@ void fx_render_rect(struct fx_renderer *renderer, const struct wlr_box *box, glEnable(GL_BLEND); } - struct quad_shader shader = renderer->shaders.quad; - glUseProgram(shader.program); + glUseProgram(renderer->shaders.quad.program); - glUniformMatrix3fv(shader.proj, 1, GL_FALSE, gl_matrix); - glUniform4f(shader.color, color[0], color[1], color[2], color[3]); + glUniformMatrix3fv(renderer->shaders.quad.proj, 1, GL_FALSE, gl_matrix); + glUniform4f(renderer->shaders.quad.color, color[0], color[1], color[2], color[3]); - glVertexAttribPointer(shader.pos_attrib, 2, GL_FLOAT, GL_FALSE, + glVertexAttribPointer(renderer->shaders.quad.pos_attrib, 2, GL_FLOAT, GL_FALSE, 0, verts); - glEnableVertexAttribArray(shader.pos_attrib); + glEnableVertexAttribArray(renderer->shaders.quad.pos_attrib); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glDisableVertexAttribArray(shader.pos_attrib); + glDisableVertexAttribArray(renderer->shaders.quad.pos_attrib); + + pop_fx_debug(renderer); } -static void fx_render_stencil_mask(struct fx_renderer *renderer, - const struct wlr_box *box, const float matrix[static 9], - int corner_radius) { - if (box->width == 0 || box->height == 0) { - return; +static const uint32_t *fx_get_shm_texture_formats( + struct wlr_renderer *wlr_renderer, size_t *len) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); + return get_fx_shm_formats(renderer, len); +} + +static const struct wlr_drm_format_set *fx_get_dmabuf_texture_formats( + struct wlr_renderer *wlr_renderer) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); + return wlr_egl_get_dmabuf_texture_formats(renderer->egl); +} + +static const struct wlr_drm_format_set *fx_get_render_formats( + struct wlr_renderer *wlr_renderer) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); + return wlr_egl_get_dmabuf_render_formats(renderer->egl); +} + +static uint32_t fx_preferred_read_format( + struct wlr_renderer *wlr_renderer) { + struct fx_renderer *renderer = + fx_get_renderer_in_context(wlr_renderer); + + push_fx_debug(renderer); + + GLint gl_format = -1, gl_type = -1, alpha_size = -1; + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &gl_format); + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &gl_type); + glGetIntegerv(GL_ALPHA_BITS, &alpha_size); + + pop_fx_debug(renderer); + + const struct fx_pixel_format *fmt = + get_fx_format_from_gl(gl_format, gl_type, alpha_size > 0); + if (fmt != NULL) { + return fmt->drm_format; } - assert(box->width > 0 && box->height > 0); - // TODO: just pass gl_matrix? - float gl_matrix[9]; - wlr_matrix_multiply(gl_matrix, renderer->projection, matrix); + if (renderer->exts.EXT_read_format_bgra) { + return DRM_FORMAT_XRGB8888; + } + return DRM_FORMAT_XBGR8888; +} - // TODO: investigate why matrix is flipped prior to this cmd - // wlr_matrix_multiply(gl_matrix, flip_180, gl_matrix); +static bool fx_read_pixels(struct wlr_renderer *wlr_renderer, + uint32_t drm_format, uint32_t stride, + uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y, + uint32_t dst_x, uint32_t dst_y, void *data) { + struct fx_renderer *renderer = + fx_get_renderer_in_context(wlr_renderer); - wlr_matrix_transpose(gl_matrix, gl_matrix); + const struct fx_pixel_format *fmt = + get_fx_format_from_drm(drm_format); + if (fmt == NULL || !is_fx_pixel_format_supported(renderer, fmt)) { + wlr_log(WLR_ERROR, "Cannot read pixels: unsupported pixel format 0x%"PRIX32, drm_format); + return false; + } - glEnable(GL_BLEND); + if (fmt->gl_format == GL_BGRA_EXT && !renderer->exts.EXT_read_format_bgra) { + wlr_log(WLR_ERROR, + "Cannot read pixels: missing GL_EXT_read_format_bgra extension"); + return false; + } - struct stencil_mask_shader shader = renderer->shaders.stencil_mask; + const struct wlr_pixel_format_info *drm_fmt = + drm_get_pixel_format_info(fmt->drm_format); + assert(drm_fmt); + if (pixel_format_info_pixels_per_block(drm_fmt) != 1) { + wlr_log(WLR_ERROR, "Cannot read pixels: block formats are not supported"); + return false; + } - glUseProgram(shader.program); + push_fx_debug(renderer); - glUniformMatrix3fv(shader.proj, 1, GL_FALSE, gl_matrix); + // Make sure any pending drawing is finished before we try to read it + glFinish(); - glUniform2f(shader.half_size, box->width * 0.5, box->height * 0.5); - glUniform2f(shader.position, box->x, box->y); - glUniform1f(shader.radius, corner_radius); + glGetError(); // Clear the error flag - glVertexAttribPointer(shader.pos_attrib, 2, GL_FLOAT, GL_FALSE, - 0, verts); + unsigned char *p = (unsigned char *)data + dst_y * stride; + glPixelStorei(GL_PACK_ALIGNMENT, 1); + uint32_t pack_stride = pixel_format_info_min_stride(drm_fmt, width); + if (pack_stride == stride && dst_x == 0) { + // Under these particular conditions, we can read the pixels with only + // one glReadPixels call - glEnableVertexAttribArray(shader.pos_attrib); + glReadPixels(src_x, src_y, width, height, fmt->gl_format, fmt->gl_type, p); + } else { + // Unfortunately GLES2 doesn't support GL_PACK_ROW_LENGTH, so we have to read + // the lines out row by row + for (size_t i = 0; i < height; ++i) { + uint32_t y = src_y + i; + glReadPixels(src_x, y, width, 1, fmt->gl_format, + fmt->gl_type, p + i * stride + dst_x * drm_fmt->bytes_per_block); + } + } - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + pop_fx_debug(renderer); + + return glGetError() == GL_NO_ERROR; +} + +static int fx_get_drm_fd(struct wlr_renderer *wlr_renderer) { + struct fx_renderer *renderer = + fx_get_renderer(wlr_renderer); + + if (renderer->drm_fd < 0) { + renderer->drm_fd = wlr_egl_dup_drm_fd(renderer->egl); + } - glDisableVertexAttribArray(shader.pos_attrib); + return renderer->drm_fd; } -void fx_render_box_shadow(struct fx_renderer *renderer, - const struct wlr_box *box, const struct wlr_box *stencil_box, - const float matrix[static 9], int corner_radius, - struct shadow_data *shadow_data) { - if (box->width == 0 || box->height == 0) { +static uint32_t fx_get_render_buffer_caps(struct wlr_renderer *wlr_renderer) { + return WLR_BUFFER_CAP_DMABUF; +} + +static void fx_renderer_destroy(struct wlr_renderer *wlr_renderer) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); + + wlr_egl_make_current(renderer->egl); + + struct fx_framebuffer *fx_buffer, *fx_buffer_tmp; + wl_list_for_each_safe(fx_buffer, fx_buffer_tmp, &renderer->buffers, link) { + fx_framebuffer_destroy(fx_buffer); + } + + struct fx_texture *tex, *tex_tmp; + wl_list_for_each_safe(tex, tex_tmp, &renderer->textures, link) { + fx_texture_destroy(tex); + } + + push_fx_debug(renderer); + glDeleteProgram(renderer->shaders.quad.program); + glDeleteProgram(renderer->shaders.tex_rgba.program); + glDeleteProgram(renderer->shaders.tex_rgbx.program); + glDeleteProgram(renderer->shaders.tex_ext.program); + pop_fx_debug(renderer); + + if (renderer->exts.KHR_debug) { + glDisable(GL_DEBUG_OUTPUT_KHR); + renderer->procs.glDebugMessageCallbackKHR(NULL, NULL); + } + + wlr_egl_unset_current(renderer->egl); + wlr_egl_destroy(renderer->egl); + + if (renderer->drm_fd >= 0) { + close(renderer->drm_fd); + } + + free(renderer); +} + +static struct wlr_render_pass *begin_buffer_pass(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *wlr_buffer, const struct wlr_buffer_pass_options *options) { + struct fx_gles_render_pass *pass = + fx_renderer_begin_buffer_pass(wlr_renderer, wlr_buffer, options); + if (!pass) { + return NULL; + } + return &pass->base; +} + +static struct wlr_render_timer *fx_render_timer_create(struct wlr_renderer *wlr_renderer) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); + if (!renderer->exts.EXT_disjoint_timer_query) { + wlr_log(WLR_ERROR, "can't create timer, EXT_disjoint_timer_query not available"); + return NULL; + } + + struct fx_render_timer *timer = calloc(1, sizeof(*timer)); + if (!timer) { + return NULL; + } + timer->base.impl = &render_timer_impl; + timer->renderer = renderer; + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(renderer->egl); + renderer->procs.glGenQueriesEXT(1, &timer->id); + wlr_egl_restore_context(&prev_ctx); + + return &timer->base; +} + +static int fx_get_render_time(struct wlr_render_timer *wlr_timer) { + struct fx_render_timer *timer = fx_get_render_timer(wlr_timer); + struct fx_renderer *renderer = timer->renderer; + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(renderer->egl); + + GLint64 disjoint; + renderer->procs.glGetInteger64vEXT(GL_GPU_DISJOINT_EXT, &disjoint); + if (disjoint) { + wlr_log(WLR_ERROR, "a disjoint operation occurred and the render timer is invalid"); + wlr_egl_restore_context(&prev_ctx); + return -1; + } + + GLint available; + renderer->procs.glGetQueryObjectivEXT(timer->id, + GL_QUERY_RESULT_AVAILABLE_EXT, &available); + if (!available) { + wlr_log(WLR_ERROR, "timer was read too early, gpu isn't done!"); + wlr_egl_restore_context(&prev_ctx); + return -1; + } + + GLuint64 gl_render_end; + renderer->procs.glGetQueryObjectui64vEXT(timer->id, GL_QUERY_RESULT_EXT, + &gl_render_end); + + int64_t cpu_nsec_total = timespec_to_nsec(&timer->cpu_end) - timespec_to_nsec(&timer->cpu_start); + + wlr_egl_restore_context(&prev_ctx); + return gl_render_end - timer->gl_cpu_end + cpu_nsec_total; +} + +static void fx_render_timer_destroy(struct wlr_render_timer *wlr_timer) { + struct fx_render_timer *timer = wl_container_of(wlr_timer, timer, base); + struct fx_renderer *renderer = timer->renderer; + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(renderer->egl); + renderer->procs.glDeleteQueriesEXT(1, &timer->id); + wlr_egl_restore_context(&prev_ctx); + free(timer); +} + +static const struct wlr_renderer_impl renderer_impl = { + .destroy = fx_renderer_destroy, + .bind_buffer = fx_bind_main_buffer, + .begin = fx_renderer_begin, + .end = fx_renderer_end, + .clear = fx_renderer_clear, + .scissor = fx_renderer_scissor, + .render_subtexture_with_matrix = fx_render_subtexture_with_matrix, + .render_quad_with_matrix = fx_render_quad_with_matrix, + .get_shm_texture_formats = fx_get_shm_texture_formats, + .get_dmabuf_texture_formats = fx_get_dmabuf_texture_formats, + .get_render_formats = fx_get_render_formats, + .preferred_read_format = fx_preferred_read_format, + .read_pixels = fx_read_pixels, + .get_drm_fd = fx_get_drm_fd, + .get_render_buffer_caps = fx_get_render_buffer_caps, + .texture_from_buffer = fx_texture_from_buffer, + .begin_buffer_pass = begin_buffer_pass, + .render_timer_create = fx_render_timer_create, +}; + +static const struct wlr_render_timer_impl render_timer_impl = { + .get_duration_ns = fx_get_render_time, + .destroy = fx_render_timer_destroy, +}; + +void push_fx_debug_(struct fx_renderer *renderer, + const char *file, const char *func) { + if (!renderer->procs.glPushDebugGroupKHR) { return; } - assert(box->width > 0 && box->height > 0); - float *color = shadow_data->color; - float blur_sigma = shadow_data->blur_sigma; + int len = snprintf(NULL, 0, "%s:%s", file, func) + 1; + char str[len]; + snprintf(str, len, "%s:%s", file, func); + renderer->procs.glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 1, -1, str); +} - float gl_matrix[9]; - wlr_matrix_multiply(gl_matrix, renderer->projection, matrix); +void pop_fx_debug(struct fx_renderer *renderer) { + if (renderer->procs.glPopDebugGroupKHR) { + renderer->procs.glPopDebugGroupKHR(); + } +} + +static enum wlr_log_importance fx_log_importance_to_wlr(GLenum type) { + switch (type) { + case GL_DEBUG_TYPE_ERROR_KHR: return WLR_ERROR; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR: return WLR_ERROR; + case GL_DEBUG_TYPE_PORTABILITY_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_PERFORMANCE_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_OTHER_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_MARKER_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_PUSH_GROUP_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_POP_GROUP_KHR: return WLR_DEBUG; + default: return WLR_DEBUG; + } +} - // TODO: investigate why matrix is flipped prior to this cmd - // wlr_matrix_multiply(gl_matrix, flip_180, gl_matrix); +static void fx_log(GLenum src, GLenum type, GLuint id, GLenum severity, + GLsizei len, const GLchar *msg, const void *user) { + _wlr_log(fx_log_importance_to_wlr(type), "[GLES2] %s", msg); +} - wlr_matrix_transpose(gl_matrix, gl_matrix); +static struct wlr_renderer *renderer_autocreate(struct wlr_backend *backend, int drm_fd) { + bool own_drm_fd = false; + if (!open_preferred_drm_fd(backend, &drm_fd, &own_drm_fd)) { + wlr_log(WLR_ERROR, "Cannot create GLES2 renderer: no DRM FD available"); + return NULL; + } + + struct wlr_egl *egl = wlr_egl_create_with_drm_fd(drm_fd); + if (egl == NULL) { + wlr_log(WLR_ERROR, "Could not initialize EGL"); + return NULL; + } - // Init stencil work - fx_renderer_stencil_mask_init(); - // Draw the rounded rect as a mask - fx_render_stencil_mask(renderer, stencil_box, matrix, corner_radius); - fx_renderer_stencil_mask_close(false); + struct wlr_renderer *renderer = fx_renderer_create_egl(egl); + if (!renderer) { + wlr_log(WLR_ERROR, "Failed to create the FX renderer"); + wlr_egl_destroy(egl); + return NULL; + } - // blending will practically always be needed (unless we have a madman - // who uses opaque shadows with zero sigma), so just enable it - glEnable(GL_BLEND); + if (own_drm_fd && drm_fd >= 0) { + close(drm_fd); + } - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + return renderer; +} - struct box_shadow_shader shader = renderer->shaders.box_shadow; +struct wlr_renderer *fx_renderer_create_with_drm_fd(int drm_fd) { + assert(drm_fd >= 0); - glUseProgram(shader.program); + return renderer_autocreate(NULL, drm_fd); +} - glUniformMatrix3fv(shader.proj, 1, GL_FALSE, gl_matrix); - glUniform4f(shader.color, color[0], color[1], color[2], color[3]); - glUniform1f(shader.blur_sigma, blur_sigma); - glUniform1f(shader.corner_radius, corner_radius); +struct wlr_renderer *fx_renderer_create(struct wlr_backend *backend) { + return renderer_autocreate(backend, -1); +} - glUniform2f(shader.size, box->width, box->height); - glUniform2f(shader.position, box->x, box->y); +struct wlr_renderer *fx_renderer_create_egl(struct wlr_egl *egl) { + if (!wlr_egl_make_current(egl)) { + return NULL; + } - glVertexAttribPointer(shader.pos_attrib, 2, GL_FLOAT, GL_FALSE, - 0, verts); + const char *exts_str = (const char *)glGetString(GL_EXTENSIONS); + if (exts_str == NULL) { + wlr_log(WLR_ERROR, "Failed to get GL_EXTENSIONS"); + return NULL; + } - glEnableVertexAttribArray(shader.pos_attrib); + struct fx_renderer *renderer = calloc(1, sizeof(struct fx_renderer)); + if (renderer == NULL) { + return NULL; + } + wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + wl_list_init(&renderer->buffers); + wl_list_init(&renderer->textures); - glDisableVertexAttribArray(shader.pos_attrib); + renderer->egl = egl; + renderer->exts_str = exts_str; + renderer->drm_fd = -1; - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + wlr_log(WLR_INFO, "Creating scenefx FX renderer"); + wlr_log(WLR_INFO, "Using %s", glGetString(GL_VERSION)); + wlr_log(WLR_INFO, "GL vendor: %s", glGetString(GL_VENDOR)); + wlr_log(WLR_INFO, "GL renderer: %s", glGetString(GL_RENDERER)); + wlr_log(WLR_INFO, "Supported FX extensions: %s", exts_str); + + if (!renderer->egl->exts.EXT_image_dma_buf_import) { + wlr_log(WLR_ERROR, "EGL_EXT_image_dma_buf_import not supported"); + free(renderer); + return NULL; + } + if (!check_gl_ext(exts_str, "GL_EXT_texture_format_BGRA8888")) { + wlr_log(WLR_ERROR, "BGRA8888 format not supported by GLES2"); + free(renderer); + return NULL; + } + if (!check_gl_ext(exts_str, "GL_EXT_unpack_subimage")) { + wlr_log(WLR_ERROR, "GL_EXT_unpack_subimage not supported"); + free(renderer); + return NULL; + } + + renderer->exts.EXT_read_format_bgra = + check_gl_ext(exts_str, "GL_EXT_read_format_bgra"); + + renderer->exts.EXT_texture_type_2_10_10_10_REV = + check_gl_ext(exts_str, "GL_EXT_texture_type_2_10_10_10_REV"); + + renderer->exts.OES_texture_half_float_linear = + check_gl_ext(exts_str, "GL_OES_texture_half_float_linear"); - fx_renderer_stencil_mask_fini(); + renderer->exts.EXT_texture_norm16 = + check_gl_ext(exts_str, "GL_EXT_texture_norm16"); + + if (check_gl_ext(exts_str, "GL_KHR_debug")) { + renderer->exts.KHR_debug = true; + load_gl_proc(&renderer->procs.glDebugMessageCallbackKHR, + "glDebugMessageCallbackKHR"); + load_gl_proc(&renderer->procs.glDebugMessageControlKHR, + "glDebugMessageControlKHR"); + } + + // TODO: the rest of the gl checks + if (check_gl_ext(exts_str, "GL_OES_EGL_image_external")) { + renderer->exts.OES_egl_image_external = true; + load_gl_proc(&renderer->procs.glEGLImageTargetTexture2DOES, + "glEGLImageTargetTexture2DOES"); + } + + if (check_gl_ext(exts_str, "GL_OES_EGL_image")) { + renderer->exts.OES_egl_image = true; + load_gl_proc(&renderer->procs.glEGLImageTargetRenderbufferStorageOES, + "glEGLImageTargetRenderbufferStorageOES"); + } + + if (check_gl_ext(exts_str, "GL_KHR_robustness")) { + GLint notif_strategy = 0; + glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_KHR, ¬if_strategy); + switch (notif_strategy) { + case GL_LOSE_CONTEXT_ON_RESET_KHR: + wlr_log(WLR_DEBUG, "GPU reset notifications are enabled"); + load_gl_proc(&renderer->procs.glGetGraphicsResetStatusKHR, + "glGetGraphicsResetStatusKHR"); + break; + case GL_NO_RESET_NOTIFICATION_KHR: + wlr_log(WLR_DEBUG, "GPU reset notifications are disabled"); + break; + } + } + + if (check_gl_ext(exts_str, "GL_EXT_disjoint_timer_query")) { + renderer->exts.EXT_disjoint_timer_query = true; + load_gl_proc(&renderer->procs.glGenQueriesEXT, "glGenQueriesEXT"); + load_gl_proc(&renderer->procs.glDeleteQueriesEXT, "glDeleteQueriesEXT"); + load_gl_proc(&renderer->procs.glQueryCounterEXT, "glQueryCounterEXT"); + load_gl_proc(&renderer->procs.glGetQueryObjectivEXT, "glGetQueryObjectivEXT"); + load_gl_proc(&renderer->procs.glGetQueryObjectui64vEXT, "glGetQueryObjectui64vEXT"); + load_gl_proc(&renderer->procs.glGetInteger64vEXT, "glGetInteger64vEXT"); + } + + if (renderer->exts.KHR_debug) { + glEnable(GL_DEBUG_OUTPUT_KHR); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); + renderer->procs.glDebugMessageCallbackKHR(fx_log, NULL); + + // Silence unwanted message types + renderer->procs.glDebugMessageControlKHR(GL_DONT_CARE, + GL_DEBUG_TYPE_POP_GROUP_KHR, GL_DONT_CARE, 0, NULL, GL_FALSE); + renderer->procs.glDebugMessageControlKHR(GL_DONT_CARE, + GL_DEBUG_TYPE_PUSH_GROUP_KHR, GL_DONT_CARE, 0, NULL, GL_FALSE); + } + + push_fx_debug(renderer); + + // Link all shaders + if (!link_shaders(renderer)) { + goto error; + } + pop_fx_debug(renderer); + + wlr_log(WLR_INFO, "FX RENDERER: Shaders Initialized Successfully"); + + wlr_egl_unset_current(renderer->egl); + + return &renderer->wlr_renderer; + +error: + glDeleteProgram(renderer->shaders.quad.program); + glDeleteProgram(renderer->shaders.tex_rgba.program); + glDeleteProgram(renderer->shaders.tex_rgbx.program); + glDeleteProgram(renderer->shaders.tex_ext.program); + glDeleteProgram(renderer->shaders.stencil_mask.program); + glDeleteProgram(renderer->shaders.box_shadow.program); + + pop_fx_debug(renderer); + + if (renderer->exts.KHR_debug) { + glDisable(GL_DEBUG_OUTPUT_KHR); + renderer->procs.glDebugMessageCallbackKHR(NULL, NULL); + } + + wlr_egl_unset_current(renderer->egl); + + free(renderer); + return NULL; } diff --git a/render/fx_renderer/fx_stencilbuffer.c b/render/fx_renderer/fx_stencilbuffer.c deleted file mode 100644 index 4f57216..0000000 --- a/render/fx_renderer/fx_stencilbuffer.c +++ /dev/null @@ -1,50 +0,0 @@ -#include <assert.h> -#include <wlr/render/gles2.h> -#include <wlr/util/log.h> - -#include "include/render/fx_renderer/fx_stencilbuffer.h" - -struct fx_stencilbuffer fx_stencilbuffer_create(void) { - return (struct fx_stencilbuffer) { - .rb = -1, - .width = -1, - .height = -1, - }; -} - -void fx_stencilbuffer_init(struct fx_stencilbuffer *stencil_buffer, int width, int height) { - bool first_alloc = false; - - if (stencil_buffer->rb == (uint32_t) -1) { - glGenRenderbuffers(1, &stencil_buffer->rb); - first_alloc = true; - } - - if (first_alloc || stencil_buffer->width != width || stencil_buffer->height != height) { - glBindRenderbuffer(GL_RENDERBUFFER, stencil_buffer->rb); - glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height); - stencil_buffer->width = width; - stencil_buffer->height = height; - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - wlr_log(WLR_ERROR, - "Stencil buffer incomplete, couldn't create! (FB status: %i)", - status); - return; - } - } - - // Reattach the RenderBuffer to the FrameBuffer - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, - GL_RENDERBUFFER, stencil_buffer->rb); -} - -void fx_stencilbuffer_release(struct fx_stencilbuffer *stencil_buffer) { - if (stencil_buffer->rb != (uint32_t) -1 && stencil_buffer->rb) { - glDeleteRenderbuffers(1, &stencil_buffer->rb); - } - stencil_buffer->rb = -1; - stencil_buffer->width = -1; - stencil_buffer->height = -1; -} diff --git a/render/fx_renderer/fx_texture.c b/render/fx_renderer/fx_texture.c new file mode 100644 index 0000000..1952311 --- /dev/null +++ b/render/fx_renderer/fx_texture.c @@ -0,0 +1,368 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/render/interface.h> +#include <wlr/types/wlr_buffer.h> +#include <wlr/util/log.h> +#include <drm_fourcc.h> + +#include "render/fx_renderer/fx_renderer.h" +#include "render/pixel_format.h" +#include "render/egl.h" + +static const struct wlr_texture_impl texture_impl; + +bool wlr_texture_is_fx(struct wlr_texture *wlr_texture) { + return wlr_texture->impl == &texture_impl; +} + +struct fx_texture *fx_get_texture(struct wlr_texture *wlr_texture) { + assert(wlr_texture_is_fx(wlr_texture)); + struct fx_texture *texture = wl_container_of(wlr_texture, texture, wlr_texture); + return texture; +} + +static bool fx_texture_update_from_buffer(struct wlr_texture *wlr_texture, + struct wlr_buffer *buffer, const pixman_region32_t *damage) { + struct fx_texture *texture = fx_get_texture(wlr_texture); + + if (texture->target != GL_TEXTURE_2D || texture->image != EGL_NO_IMAGE_KHR) { + return false; + } + + void *data; + uint32_t format; + size_t stride; + if (!wlr_buffer_begin_data_ptr_access(buffer, + WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { + return false; + } + + if (format != texture->drm_format) { + wlr_buffer_end_data_ptr_access(buffer); + return false; + } + + const struct fx_pixel_format *fmt = + get_fx_format_from_drm(texture->drm_format); + assert(fmt); + + const struct wlr_pixel_format_info *drm_fmt = + drm_get_pixel_format_info(texture->drm_format); + assert(drm_fmt); + if (pixel_format_info_pixels_per_block(drm_fmt) != 1) { + wlr_buffer_end_data_ptr_access(buffer); + wlr_log(WLR_ERROR, "Cannot update texture: block formats are not supported"); + return false; + } + + if (!pixel_format_info_check_stride(drm_fmt, stride, buffer->width)) { + wlr_buffer_end_data_ptr_access(buffer); + return false; + } + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(texture->fx_renderer->egl); + + push_fx_debug(texture->fx_renderer); + + glBindTexture(GL_TEXTURE_2D, texture->tex); + + int rects_len = 0; + pixman_box32_t *rects = pixman_region32_rectangles(damage, &rects_len); + + for (int i = 0; i < rects_len; i++) { + pixman_box32_t rect = rects[i]; + + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / drm_fmt->bytes_per_block); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1); + + int width = rect.x2 - rect.x1; + int height = rect.y2 - rect.y1; + glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, + fmt->gl_format, fmt->gl_type, data); + } + + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + + pop_fx_debug(texture->fx_renderer); + + wlr_egl_restore_context(&prev_ctx); + + wlr_buffer_end_data_ptr_access(buffer); + + return true; +} + +static bool fx_texture_invalidate(struct fx_texture *texture) { + if (texture->image == EGL_NO_IMAGE_KHR) { + return false; + } + if (texture->target == GL_TEXTURE_EXTERNAL_OES) { + // External changes are immediately made visible by the GL implementation + return true; + } + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(texture->fx_renderer->egl); + + push_fx_debug(texture->fx_renderer); + + glBindTexture(texture->target, texture->tex); + texture->fx_renderer->procs.glEGLImageTargetTexture2DOES(texture->target, + texture->image); + glBindTexture(texture->target, 0); + + pop_fx_debug(texture->fx_renderer); + + wlr_egl_restore_context(&prev_ctx); + + return true; +} + +void fx_texture_destroy(struct fx_texture *texture) { + wl_list_remove(&texture->link); + if (texture->buffer != NULL) { + wlr_addon_finish(&texture->buffer_addon); + } + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(texture->fx_renderer->egl); + + glDeleteTextures(1, &texture->tex); + wlr_egl_destroy_image(texture->fx_renderer->egl, texture->image); + + wlr_egl_restore_context(&prev_ctx); + + free(texture); +} + +static void fx_texture_unref(struct wlr_texture *wlr_texture) { + struct fx_texture *texture = fx_get_texture(wlr_texture); + if (texture->buffer != NULL) { + // Keep the texture around, in case the buffer is re-used later. We're + // still listening to the buffer's destroy event. + wlr_buffer_unlock(texture->buffer); + } else { + fx_texture_destroy(texture); + } +} + +static const struct wlr_texture_impl texture_impl = { + .update_from_buffer = fx_texture_update_from_buffer, + .destroy = fx_texture_unref, +}; + +static struct fx_texture *fx_texture_create( + struct fx_renderer *renderer, uint32_t width, uint32_t height) { + struct fx_texture *texture = calloc(1, sizeof(struct fx_texture)); + if (texture == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + wlr_texture_init(&texture->wlr_texture, &renderer->wlr_renderer, + &texture_impl, width, height); + texture->fx_renderer = renderer; + wl_list_insert(&renderer->textures, &texture->link); + return texture; +} + +static struct wlr_texture *fx_texture_from_pixels( + struct wlr_renderer *wlr_renderer, + uint32_t drm_format, uint32_t stride, uint32_t width, + uint32_t height, const void *data) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); + + const struct fx_pixel_format *fmt = get_fx_format_from_drm(drm_format); + if (fmt == NULL) { + wlr_log(WLR_ERROR, "Unsupported pixel format 0x%"PRIX32, drm_format); + return NULL; + } + + const struct wlr_pixel_format_info *drm_fmt = + drm_get_pixel_format_info(drm_format); + assert(drm_fmt); + if (pixel_format_info_pixels_per_block(drm_fmt) != 1) { + wlr_log(WLR_ERROR, "Cannot upload texture: block formats are not supported"); + return NULL; + } + + if (!pixel_format_info_check_stride(drm_fmt, stride, width)) { + return NULL; + } + + struct fx_texture *texture = + fx_texture_create(renderer, width, height); + if (texture == NULL) { + return NULL; + } + texture->target = GL_TEXTURE_2D; + texture->has_alpha = fmt->has_alpha; + texture->drm_format = fmt->drm_format; + + GLint internal_format = fmt->gl_internalformat; + if (!internal_format) { + internal_format = fmt->gl_format; + } + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(renderer->egl); + + push_fx_debug(renderer); + + glGenTextures(1, &texture->tex); + glBindTexture(GL_TEXTURE_2D, texture->tex); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / drm_fmt->bytes_per_block); + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, + fmt->gl_format, fmt->gl_type, data); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + + pop_fx_debug(renderer); + + wlr_egl_restore_context(&prev_ctx); + + return &texture->wlr_texture; +} + +static struct wlr_texture *fx_texture_from_dmabuf( + struct wlr_renderer *wlr_renderer, + struct wlr_dmabuf_attributes *attribs) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); + + if (!renderer->procs.glEGLImageTargetTexture2DOES) { + return NULL; + } + + struct fx_texture *texture = + fx_texture_create(renderer, attribs->width, attribs->height); + if (texture == NULL) { + return NULL; + } + texture->drm_format = DRM_FORMAT_INVALID; // texture can't be written anyways + + const struct wlr_pixel_format_info *drm_fmt = + drm_get_pixel_format_info(attribs->format); + if (drm_fmt != NULL) { + texture->has_alpha = drm_fmt->has_alpha; + } else { + // We don't know, assume the texture has an alpha channel + texture->has_alpha = true; + } + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(renderer->egl); + + bool external_only; + texture->image = + wlr_egl_create_image_from_dmabuf(renderer->egl, attribs, &external_only); + if (texture->image == EGL_NO_IMAGE_KHR) { + wlr_log(WLR_ERROR, "Failed to create EGL image from DMA-BUF"); + wlr_egl_restore_context(&prev_ctx); + wl_list_remove(&texture->link); + free(texture); + return NULL; + } + + texture->target = external_only ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; + + push_fx_debug(renderer); + + glGenTextures(1, &texture->tex); + glBindTexture(texture->target, texture->tex); + glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + renderer->procs.glEGLImageTargetTexture2DOES(texture->target, texture->image); + glBindTexture(texture->target, 0); + + pop_fx_debug(renderer); + + wlr_egl_restore_context(&prev_ctx); + + return &texture->wlr_texture; +} + +static void texture_handle_buffer_destroy(struct wlr_addon *addon) { + struct fx_texture *texture = + wl_container_of(addon, texture, buffer_addon); + fx_texture_destroy(texture); +} + +static const struct wlr_addon_interface texture_addon_impl = { + .name = "fx_texture", + .destroy = texture_handle_buffer_destroy, +}; + +static struct wlr_texture *fx_texture_from_dmabuf_buffer( + struct fx_renderer *renderer, struct wlr_buffer *buffer, + struct wlr_dmabuf_attributes *dmabuf) { + struct wlr_addon *addon = + wlr_addon_find(&buffer->addons, renderer, &texture_addon_impl); + if (addon != NULL) { + struct fx_texture *texture = + wl_container_of(addon, texture, buffer_addon); + if (!fx_texture_invalidate(texture)) { + wlr_log(WLR_ERROR, "Failed to invalidate texture"); + return false; + } + wlr_buffer_lock(texture->buffer); + return &texture->wlr_texture; + } + + struct wlr_texture *wlr_texture = + fx_texture_from_dmabuf(&renderer->wlr_renderer, dmabuf); + if (wlr_texture == NULL) { + return false; + } + + struct fx_texture *texture = fx_get_texture(wlr_texture); + texture->buffer = wlr_buffer_lock(buffer); + wlr_addon_init(&texture->buffer_addon, &buffer->addons, + renderer, &texture_addon_impl); + + return &texture->wlr_texture; +} + +struct wlr_texture *fx_texture_from_buffer(struct wlr_renderer *wlr_renderer, + struct wlr_buffer *buffer) { + struct fx_renderer *renderer = fx_get_renderer(wlr_renderer); + + void *data; + uint32_t format; + size_t stride; + struct wlr_dmabuf_attributes dmabuf; + if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { + return fx_texture_from_dmabuf_buffer(renderer, buffer, &dmabuf); + } else if (wlr_buffer_begin_data_ptr_access(buffer, + WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { + struct wlr_texture *tex = fx_texture_from_pixels(wlr_renderer, + format, stride, buffer->width, buffer->height, data); + wlr_buffer_end_data_ptr_access(buffer); + return tex; + } else { + return NULL; + } +} + +void fx_texture_get_attribs(struct wlr_texture *wlr_texture, + struct fx_texture_attribs *attribs) { + struct fx_texture *texture = fx_get_texture(wlr_texture); + *attribs = (struct fx_texture_attribs){ + .target = texture->target, + .tex = texture->tex, + .has_alpha = texture->has_alpha, + }; +} diff --git a/render/fx_renderer/gles2/shaders/box_shadow.frag b/render/fx_renderer/gles2/shaders/box_shadow.frag index c9b2b91..92d40fc 100644 --- a/render/fx_renderer/gles2/shaders/box_shadow.frag +++ b/render/fx_renderer/gles2/shaders/box_shadow.frag @@ -1,6 +1,11 @@ // Writeup: https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/ +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else precision mediump float; +#endif + varying vec4 v_color; varying vec2 v_texcoord; diff --git a/render/fx_renderer/gles2/shaders/common.vert b/render/fx_renderer/gles2/shaders/common.vert index 811e0f2..9e7b073 100644 --- a/render/fx_renderer/gles2/shaders/common.vert +++ b/render/fx_renderer/gles2/shaders/common.vert @@ -1,12 +1,13 @@ uniform mat3 proj; uniform vec4 color; +uniform mat3 tex_proj; attribute vec2 pos; -attribute vec2 texcoord; varying vec4 v_color; varying vec2 v_texcoord; void main() { - gl_Position = vec4(proj * vec3(pos, 1.0), 1.0); - v_color = color; - v_texcoord = texcoord; + vec3 pos3 = vec3(pos, 1.0); + gl_Position = vec4(pos3 * proj, 1.0); + v_color = color; + v_texcoord = (pos3 * tex_proj).xy; } diff --git a/render/fx_renderer/gles2/shaders/quad.frag b/render/fx_renderer/gles2/shaders/quad.frag index 7c76327..97d3a31 100644 --- a/render/fx_renderer/gles2/shaders/quad.frag +++ b/render/fx_renderer/gles2/shaders/quad.frag @@ -1,7 +1,13 @@ +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else precision mediump float; +#endif + varying vec4 v_color; varying vec2 v_texcoord; +uniform vec4 color; void main() { - gl_FragColor = v_color; + gl_FragColor = color; } diff --git a/render/fx_renderer/gles2/shaders/stencil_mask.frag b/render/fx_renderer/gles2/shaders/stencil_mask.frag index ee03307..523adc8 100644 --- a/render/fx_renderer/gles2/shaders/stencil_mask.frag +++ b/render/fx_renderer/gles2/shaders/stencil_mask.frag @@ -1,4 +1,9 @@ +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else precision mediump float; +#endif + varying vec2 v_texcoord; uniform vec2 half_size; diff --git a/render/fx_renderer/gles2/shaders/tex.frag b/render/fx_renderer/gles2/shaders/tex.frag index bd3c596..8c14373 100644 --- a/render/fx_renderer/gles2/shaders/tex.frag +++ b/render/fx_renderer/gles2/shaders/tex.frag @@ -10,7 +10,11 @@ #extension GL_OES_EGL_image_external : require #endif +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else precision mediump float; +#endif varying vec2 v_texcoord; @@ -21,6 +25,7 @@ uniform sampler2D tex; #endif uniform float alpha; + uniform vec2 size; uniform vec2 position; uniform float radius; diff --git a/render/fx_renderer/meson.build b/render/fx_renderer/meson.build index 394caa3..d7160a8 100644 --- a/render/fx_renderer/meson.build +++ b/render/fx_renderer/meson.build @@ -7,7 +7,12 @@ endif wlr_files += files( 'matrix.c', - 'fx_stencilbuffer.c', + 'util.c', + 'shaders.c', + 'pixel_format.c', + 'fx_pass.c', + 'fx_framebuffer.c', + 'fx_texture.c', 'fx_renderer.c', ) @@ -18,7 +23,6 @@ if 'gles2' in renderers or 'auto' in renderers if egl.found() and gbm.found() and glesv2.found() wlr_deps += [egl, gbm, glesv2] - internal_features += { 'egl': true , 'gles2-renderer': true } endif subdir('gles2') endif diff --git a/render/fx_renderer/pixel_format.c b/render/fx_renderer/pixel_format.c new file mode 100644 index 0000000..2693018 --- /dev/null +++ b/render/fx_renderer/pixel_format.c @@ -0,0 +1,175 @@ +#include <drm_fourcc.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include "render/fx_renderer/fx_renderer.h" + +/* + * The DRM formats are little endian while the GL formats are big endian, + * so DRM_FORMAT_ARGB8888 is actually compatible with GL_BGRA_EXT. + */ +static const struct fx_pixel_format formats[] = { + { + .drm_format = DRM_FORMAT_ARGB8888, + .gl_format = GL_BGRA_EXT, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XRGB8888, + .gl_format = GL_BGRA_EXT, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_XBGR8888, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR8888, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_BGR888, + .gl_format = GL_RGB, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = false, + }, +#if WLR_LITTLE_ENDIAN + { + .drm_format = DRM_FORMAT_RGBX4444, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_RGBA4444, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGBX5551, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_RGBA5551, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGB565, + .gl_format = GL_RGB, + .gl_type = GL_UNSIGNED_SHORT_5_6_5, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_XBGR2101010, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR2101010, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616F, + .gl_format = GL_RGBA, + .gl_type = GL_HALF_FLOAT_OES, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616F, + .gl_format = GL_RGBA, + .gl_type = GL_HALF_FLOAT_OES, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616, + .gl_internalformat = GL_RGBA16_EXT, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT, + .has_alpha = false, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616, + .gl_internalformat = GL_RGBA16_EXT, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_SHORT, + .has_alpha = true, + }, +#endif +}; + +// TODO: more pixel formats + +/* + * Return true if supported for texturing, even if other operations like + * reading aren't supported. + */ +bool is_fx_pixel_format_supported(const struct fx_renderer *renderer, + const struct fx_pixel_format *format) { + if (format->gl_type == GL_UNSIGNED_INT_2_10_10_10_REV_EXT + && !renderer->exts.EXT_texture_type_2_10_10_10_REV) { + return false; + } + if (format->gl_type == GL_HALF_FLOAT_OES + && !renderer->exts.OES_texture_half_float_linear) { + return false; + } + if (format->gl_type == GL_UNSIGNED_SHORT + && !renderer->exts.EXT_texture_norm16) { + return false; + } + /* + * Note that we don't need to check for GL_EXT_texture_format_BGRA8888 + * here, since we've already checked if we have it at renderer creation + * time and bailed out if not. We do the check there because Wayland + * requires all compositors to support SHM buffers in that format. + */ + return true; +} + +const struct fx_pixel_format *get_fx_format_from_drm(uint32_t fmt) { + for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { + if (formats[i].drm_format == fmt) { + return &formats[i]; + } + } + return NULL; +} + +const struct fx_pixel_format *get_fx_format_from_gl( + GLint gl_format, GLint gl_type, bool alpha) { + for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { + if (formats[i].gl_format == gl_format && + formats[i].gl_type == gl_type && + formats[i].has_alpha == alpha) { + return &formats[i]; + } + } + return NULL; +} + +const uint32_t *get_fx_shm_formats(const struct fx_renderer *renderer, + size_t *len) { + static uint32_t shm_formats[sizeof(formats) / sizeof(formats[0])]; + size_t j = 0; + for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) { + if (!is_fx_pixel_format_supported(renderer, &formats[i])) { + continue; + } + shm_formats[j++] = formats[i].drm_format; + } + *len = j; + return shm_formats; +} diff --git a/render/fx_renderer/shaders.c b/render/fx_renderer/shaders.c new file mode 100644 index 0000000..9257ca3 --- /dev/null +++ b/render/fx_renderer/shaders.c @@ -0,0 +1,203 @@ +#include <EGL/egl.h> +#include <stdio.h> +#include <stdlib.h> +#include <wlr/util/log.h> + +#include "render/fx_renderer/fx_renderer.h" +#include "render/fx_renderer/shaders.h" + +// shaders +#include "common_vert_src.h" +#include "quad_frag_src.h" +#include "tex_frag_src.h" +#include "stencil_mask_frag_src.h" +#include "box_shadow_frag_src.h" + +GLuint compile_shader(GLuint type, const GLchar *src) { + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &src, NULL); + glCompileShader(shader); + + GLint ok; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + if (ok == GL_FALSE) { + wlr_log(WLR_ERROR, "Failed to compile shader"); + glDeleteShader(shader); + shader = 0; + } + + return shader; +} + +GLuint link_program(const GLchar *frag_src) { + const GLchar *vert_src = common_vert_src; + GLuint vert = compile_shader(GL_VERTEX_SHADER, vert_src); + if (!vert) { + goto error; + } + + GLuint frag = compile_shader(GL_FRAGMENT_SHADER, frag_src); + if (!frag) { + glDeleteShader(vert); + goto error; + } + + GLuint prog = glCreateProgram(); + glAttachShader(prog, vert); + glAttachShader(prog, frag); + glLinkProgram(prog); + + glDetachShader(prog, vert); + glDetachShader(prog, frag); + glDeleteShader(vert); + glDeleteShader(frag); + + GLint ok; + glGetProgramiv(prog, GL_LINK_STATUS, &ok); + if (ok == GL_FALSE) { + wlr_log(WLR_ERROR, "Failed to link shader"); + glDeleteProgram(prog); + goto error; + } + + return prog; + +error: + return 0; +} + + +bool check_gl_ext(const char *exts, const char *ext) { + size_t extlen = strlen(ext); + const char *end = exts + strlen(exts); + + while (exts < end) { + if (exts[0] == ' ') { + exts++; + continue; + } + size_t n = strcspn(exts, " "); + if (n == extlen && strncmp(ext, exts, n) == 0) { + return true; + } + exts += n; + } + return false; +} + +void load_gl_proc(void *proc_ptr, const char *name) { + void *proc = (void *)eglGetProcAddress(name); + if (proc == NULL) { + wlr_log(WLR_ERROR, "FX RENDERER: eglGetProcAddress(%s) failed", name); + abort(); + } + *(void **)proc_ptr = proc; +} + +// Shaders + +static bool link_quad_program(struct quad_shader *shader) { + GLuint prog; + shader->program = prog = link_program(quad_frag_src); + if (!shader->program) { + return false; + } + + shader->proj = glGetUniformLocation(prog, "proj"); + shader->color = glGetUniformLocation(prog, "color"); + shader->pos_attrib = glGetAttribLocation(prog, "pos"); + + return true; +} + +static bool link_tex_program(struct tex_shader *shader, + enum fx_tex_shader_source source) { + GLchar frag_src[2048]; + snprintf(frag_src, sizeof(frag_src), + "#define SOURCE %d\n%s", source, tex_frag_src); + + GLuint prog; + shader->program = prog = link_program(frag_src); + if (!shader->program) { + return false; + } + + shader->proj = glGetUniformLocation(prog, "proj"); + shader->tex = glGetUniformLocation(prog, "tex"); + shader->alpha = glGetUniformLocation(prog, "alpha"); + shader->pos_attrib = glGetAttribLocation(prog, "pos"); + shader->tex_proj = glGetUniformLocation(prog, "tex_proj"); + shader->size = glGetUniformLocation(prog, "size"); + shader->position = glGetUniformLocation(prog, "position"); + shader->radius = glGetUniformLocation(prog, "radius"); + + return true; +} + +static bool link_stencil_mask_program(struct stencil_mask_shader *shader) { + GLuint prog; + shader->program = prog = link_program(stencil_mask_frag_src); + if (!shader->program) { + return false; + } + + shader->proj = glGetUniformLocation(prog, "proj"); + shader->color = glGetUniformLocation(prog, "color"); + shader->pos_attrib = glGetAttribLocation(prog, "pos"); + shader->position = glGetUniformLocation(prog, "position"); + shader->half_size = glGetUniformLocation(prog, "half_size"); + shader->radius = glGetUniformLocation(prog, "radius"); + + return true; +} + +static bool link_box_shadow_program(struct box_shadow_shader *shader) { + GLuint prog; + shader->program = prog = link_program(box_shadow_frag_src); + if (!shader->program) { + return false; + } + shader->proj = glGetUniformLocation(prog, "proj"); + shader->color = glGetUniformLocation(prog, "color"); + shader->pos_attrib = glGetAttribLocation(prog, "pos"); + shader->position = glGetUniformLocation(prog, "position"); + shader->size = glGetUniformLocation(prog, "size"); + shader->blur_sigma = glGetUniformLocation(prog, "blur_sigma"); + shader->corner_radius = glGetUniformLocation(prog, "corner_radius"); + + return true; +} + +bool link_shaders(struct fx_renderer *renderer) { + // quad fragment shader + if (!link_quad_program(&renderer->shaders.quad)) { + wlr_log(WLR_ERROR, "Could not link quad shader"); + return false; + } + // fragment shaders + if (!link_tex_program(&renderer->shaders.tex_rgba, SHADER_SOURCE_TEXTURE_RGBA)) { + wlr_log(WLR_ERROR, "Could not link tex_RGBA shader"); + return false; + } + if (!link_tex_program(&renderer->shaders.tex_rgbx, SHADER_SOURCE_TEXTURE_RGBX)) { + wlr_log(WLR_ERROR, "Could not link tex_RGBX shader"); + return false; + } + if (!link_tex_program(&renderer->shaders.tex_ext, SHADER_SOURCE_TEXTURE_EXTERNAL)) { + wlr_log(WLR_ERROR, "Could not link tex_EXTERNAL shader"); + return false; + } + + // stencil mask shader + if (!link_stencil_mask_program(&renderer->shaders.stencil_mask)) { + wlr_log(WLR_ERROR, "Could not link stencil mask shader"); + return false; + } + // box shadow shader + if (!link_box_shadow_program(&renderer->shaders.box_shadow)) { + wlr_log(WLR_ERROR, "Could not link box shadow shader"); + return false; + } + + return true; +} diff --git a/render/fx_renderer/util.c b/render/fx_renderer/util.c new file mode 100644 index 0000000..c262aab --- /dev/null +++ b/render/fx_renderer/util.c @@ -0,0 +1,113 @@ +#define _POSIX_C_SOURCE 200809L +#include <fcntl.h> +#include <unistd.h> +#include <wlr/util/log.h> +#include <wlr/types/wlr_buffer.h> +#include <xf86drm.h> + +#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); +} + +static int open_drm_render_node(void) { + uint32_t flags = 0; + int devices_len = drmGetDevices2(flags, NULL, 0); + if (devices_len < 0) { + wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len)); + return -1; + } + drmDevice **devices = calloc(devices_len, sizeof(*devices)); + if (devices == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return -1; + } + devices_len = drmGetDevices2(flags, devices, devices_len); + if (devices_len < 0) { + free(devices); + wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len)); + return -1; + } + + int fd = -1; + for (int i = 0; i < devices_len; i++) { + drmDevice *dev = devices[i]; + if (dev->available_nodes & (1 << DRM_NODE_RENDER)) { + const char *name = dev->nodes[DRM_NODE_RENDER]; + wlr_log(WLR_DEBUG, "Opening DRM render node '%s'", name); + fd = open(name, O_RDWR | O_CLOEXEC); + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open '%s'", name); + goto out; + } + break; + } + } + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to find any DRM render node"); + } + +out: + for (int i = 0; i < devices_len; i++) { + drmFreeDevice(&devices[i]); + } + free(devices); + + return fd; +} + +bool open_preferred_drm_fd(struct wlr_backend *backend, int *drm_fd_ptr, + bool *own_drm_fd) { + if (*drm_fd_ptr >= 0) { + return true; + } + + // Allow the user to override the render node + const char *render_name = getenv("WLR_RENDER_DRM_DEVICE"); + if (render_name != NULL) { + wlr_log(WLR_INFO, + "Opening DRM render node '%s' from WLR_RENDER_DRM_DEVICE", + render_name); + int drm_fd = open(render_name, O_RDWR | O_CLOEXEC); + if (drm_fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open '%s'", render_name); + return false; + } + if (drmGetNodeTypeFromFd(drm_fd) != DRM_NODE_RENDER) { + wlr_log(WLR_ERROR, "'%s' is not a DRM render node", render_name); + close(drm_fd); + return false; + } + *drm_fd_ptr = drm_fd; + *own_drm_fd = true; + return true; + } + + // Prefer the backend's DRM node, if any + int backend_drm_fd = wlr_backend_get_drm_fd(backend); + if (backend_drm_fd >= 0) { + *drm_fd_ptr = backend_drm_fd; + *own_drm_fd = false; + return true; + } + + // 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) { + int drm_fd = open_drm_render_node(); + if (drm_fd < 0) { + return false; + } + *drm_fd_ptr = drm_fd; + *own_drm_fd = true; + return true; + } + + return false; +} diff --git a/render/meson.build b/render/meson.build index 4dd5c6a..56579ee 100644 --- a/render/meson.build +++ b/render/meson.build @@ -1,5 +1,6 @@ wlr_files += files( 'pixel_format.c', + 'egl.c', ) subdir('fx_renderer') diff --git a/render/pixel_format.c b/render/pixel_format.c index 46bcecf..b81f561 100644 --- a/render/pixel_format.c +++ b/render/pixel_format.c @@ -1,178 +1,252 @@ -#include "render/pixel_format.h" +#include <assert.h> #include <drm_fourcc.h> +#include <wlr/util/log.h> +#include "render/pixel_format.h" static const struct wlr_pixel_format_info pixel_format_info[] = { - { - .drm_format = DRM_FORMAT_XRGB8888, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_ARGB8888, - .opaque_substitute = DRM_FORMAT_XRGB8888, - .bpp = 32, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_XBGR8888, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_ABGR8888, - .opaque_substitute = DRM_FORMAT_XBGR8888, - .bpp = 32, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_RGBX8888, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_RGBA8888, - .opaque_substitute = DRM_FORMAT_RGBX8888, - .bpp = 32, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_BGRX8888, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_BGRA8888, - .opaque_substitute = DRM_FORMAT_BGRX8888, - .bpp = 32, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_BGR888, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 24, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_RGBX4444, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 16, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_RGBA4444, - .opaque_substitute = DRM_FORMAT_RGBX4444, - .bpp = 16, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_RGBX5551, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 16, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_RGBA5551, - .opaque_substitute = DRM_FORMAT_RGBX5551, - .bpp = 16, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_RGB565, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 16, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_BGR565, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 16, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_XRGB2101010, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_ARGB2101010, - .opaque_substitute = DRM_FORMAT_XRGB2101010, - .bpp = 32, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_XBGR2101010, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_ABGR2101010, - .opaque_substitute = DRM_FORMAT_XBGR2101010, - .bpp = 32, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_XBGR16161616F, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 64, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_ABGR16161616F, - .opaque_substitute = DRM_FORMAT_XBGR16161616F, - .bpp = 64, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_XBGR16161616, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 64, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_ABGR16161616, - .opaque_substitute = DRM_FORMAT_XBGR16161616, - .bpp = 64, - .has_alpha = true, - }, + { + .drm_format = DRM_FORMAT_XRGB8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_ARGB8888, + .opaque_substitute = DRM_FORMAT_XRGB8888, + .bytes_per_block = 4, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_ABGR8888, + .opaque_substitute = DRM_FORMAT_XBGR8888, + .bytes_per_block = 4, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGBX8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_RGBA8888, + .opaque_substitute = DRM_FORMAT_RGBX8888, + .bytes_per_block = 4, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_BGRX8888, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_BGRA8888, + .opaque_substitute = DRM_FORMAT_BGRX8888, + .bytes_per_block = 4, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_R8, + .bytes_per_block = 1, + }, + { + .drm_format = DRM_FORMAT_GR88, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_RGB888, + .bytes_per_block = 3, + }, + { + .drm_format = DRM_FORMAT_BGR888, + .bytes_per_block = 3, + }, + { + .drm_format = DRM_FORMAT_RGBX4444, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_RGBA4444, + .opaque_substitute = DRM_FORMAT_RGBX4444, + .bytes_per_block = 2, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_BGRX4444, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_BGRA4444, + .opaque_substitute = DRM_FORMAT_BGRX4444, + .bytes_per_block = 2, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGBX5551, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_RGBA5551, + .opaque_substitute = DRM_FORMAT_RGBX5551, + .bytes_per_block = 2, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_BGRX5551, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_BGRA5551, + .opaque_substitute = DRM_FORMAT_BGRX5551, + .bytes_per_block = 2, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XRGB1555, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_ARGB1555, + .opaque_substitute = DRM_FORMAT_XRGB1555, + .bytes_per_block = 2, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGB565, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_BGR565, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_XRGB2101010, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_ARGB2101010, + .opaque_substitute = DRM_FORMAT_XRGB2101010, + .bytes_per_block = 4, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR2101010, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_ABGR2101010, + .opaque_substitute = DRM_FORMAT_XBGR2101010, + .bytes_per_block = 4, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616F, + .bytes_per_block = 8, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616F, + .opaque_substitute = DRM_FORMAT_XBGR16161616F, + .bytes_per_block = 8, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_XBGR16161616, + .bytes_per_block = 8, + }, + { + .drm_format = DRM_FORMAT_ABGR16161616, + .opaque_substitute = DRM_FORMAT_XBGR16161616, + .bytes_per_block = 8, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_YVYU, + .bytes_per_block = 4, + .block_width = 2, + .block_height = 1, + }, + { + .drm_format = DRM_FORMAT_VYUY, + .bytes_per_block = 4, + .block_width = 2, + .block_height = 1, + }, }; static const size_t pixel_format_info_size = - sizeof(pixel_format_info) / sizeof(pixel_format_info[0]); + sizeof(pixel_format_info) / sizeof(pixel_format_info[0]); const struct wlr_pixel_format_info *drm_get_pixel_format_info(uint32_t fmt) { - for (size_t i = 0; i < pixel_format_info_size; ++i) { - if (pixel_format_info[i].drm_format == fmt) { - return &pixel_format_info[i]; - } - } + for (size_t i = 0; i < pixel_format_info_size; ++i) { + if (pixel_format_info[i].drm_format == fmt) { + return &pixel_format_info[i]; + } + } - return NULL; + return NULL; } uint32_t convert_wl_shm_format_to_drm(enum wl_shm_format fmt) { - switch (fmt) { - case WL_SHM_FORMAT_XRGB8888: - return DRM_FORMAT_XRGB8888; - case WL_SHM_FORMAT_ARGB8888: - return DRM_FORMAT_ARGB8888; - default: - return (uint32_t)fmt; - } + switch (fmt) { + case WL_SHM_FORMAT_XRGB8888: + return DRM_FORMAT_XRGB8888; + case WL_SHM_FORMAT_ARGB8888: + return DRM_FORMAT_ARGB8888; + default: + return (uint32_t)fmt; + } } enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt) { - switch (fmt) { - case DRM_FORMAT_XRGB8888: - return WL_SHM_FORMAT_XRGB8888; - case DRM_FORMAT_ARGB8888: - return WL_SHM_FORMAT_ARGB8888; - default: - return (enum wl_shm_format)fmt; - } + switch (fmt) { + case DRM_FORMAT_XRGB8888: + return WL_SHM_FORMAT_XRGB8888; + case DRM_FORMAT_ARGB8888: + return WL_SHM_FORMAT_ARGB8888; + default: + return (enum wl_shm_format)fmt; + } +} + +uint32_t pixel_format_info_pixels_per_block(const struct wlr_pixel_format_info *info) { + uint32_t pixels = info->block_width * info->block_height; + return pixels > 0 ? pixels : 1; +} + +static int32_t div_round_up(int32_t dividend, int32_t divisor) { + int32_t quotient = dividend / divisor; + if (dividend % divisor != 0) { + quotient++; + } + return quotient; +} + +int32_t pixel_format_info_min_stride(const struct wlr_pixel_format_info *fmt, int32_t width) { + int32_t pixels_per_block = (int32_t)pixel_format_info_pixels_per_block(fmt); + int32_t bytes_per_block = (int32_t)fmt->bytes_per_block; + if (width > INT32_MAX / bytes_per_block) { + wlr_log(WLR_DEBUG, "Invalid width %d (overflow)", width); + return 0; + } + return div_round_up(width * bytes_per_block, pixels_per_block); +} + +bool pixel_format_info_check_stride(const struct wlr_pixel_format_info *fmt, + int32_t stride, int32_t width) { + int32_t bytes_per_block = (int32_t)fmt->bytes_per_block; + if (stride % bytes_per_block != 0) { + wlr_log(WLR_DEBUG, "Invalid stride %d (incompatible with %d " + "bytes-per-block)", stride, bytes_per_block); + return false; + } + + int32_t min_stride = pixel_format_info_min_stride(fmt, width); + if (min_stride <= 0) { + return false; + } else if (stride < min_stride) { + wlr_log(WLR_DEBUG, "Invalid stride %d (too small for %d " + "bytes-per-block and width %d)", stride, bytes_per_block, width); + return false; + } + + return true; } diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c index 56091e3..03fafee 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -29,6 +29,8 @@ #include "types/wlr_scene.h" +#include "render/fx_renderer/fx_renderer.h" + /* For brevity's sake, struct members are annotated where they are used. */ enum tinywl_cursor_mode { TINYWL_CURSOR_PASSTHROUGH, @@ -42,10 +44,11 @@ struct tinywl_server { struct wlr_renderer *renderer; struct wlr_allocator *allocator; struct wlr_scene *scene; + struct wlr_scene_output_layout *scene_layout; struct wlr_xdg_shell *xdg_shell; struct wl_listener new_xdg_surface; - struct wl_list views; + struct wl_list toplevels; struct wlr_cursor *cursor; struct wlr_xcursor_manager *cursor_mgr; @@ -61,7 +64,7 @@ struct tinywl_server { struct wl_listener request_set_selection; struct wl_list keyboards; enum tinywl_cursor_mode cursor_mode; - struct tinywl_view *grabbed_view; + struct tinywl_toplevel *grabbed_toplevel; double grab_x, grab_y; struct wlr_box grab_geobox; uint32_t resize_edges; @@ -76,10 +79,11 @@ struct tinywl_output { struct tinywl_server *server; struct wlr_output *wlr_output; struct wl_listener frame; + struct wl_listener request_state; struct wl_listener destroy; }; -struct tinywl_view { +struct tinywl_toplevel { struct wl_list link; struct tinywl_server *server; struct wlr_xdg_toplevel *xdg_toplevel; @@ -91,9 +95,6 @@ struct tinywl_view { struct wl_listener request_resize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; - int x, y; - - struct wlr_addon addon; float opacity; int corner_radius; @@ -110,50 +111,12 @@ struct tinywl_keyboard { struct wl_listener destroy; }; -static void tinywl_view_destroy(struct tinywl_view *view) { - wl_list_remove(&view->map.link); - wl_list_remove(&view->unmap.link); - wl_list_remove(&view->destroy.link); - wl_list_remove(&view->request_move.link); - wl_list_remove(&view->request_resize.link); - wl_list_remove(&view->request_maximize.link); - wl_list_remove(&view->request_fullscreen.link); - - free(view); -} - -static void tinywl_view_addon_handle_destroy(struct wlr_addon *addon) { - struct tinywl_view *view = wl_container_of(addon, view, addon); - - tinywl_view_destroy(view); -} -static const struct wlr_addon_interface tinywl_view_addon_impl = { - .name = "tinywl_view", - .destroy = tinywl_view_addon_handle_destroy, -}; - -static void tinywl_view_addon_assign(struct tinywl_view *view, struct wlr_addon_set *addons, - const void * owner) { - wlr_addon_init(&view->addon, addons, owner, &tinywl_view_addon_impl); -} - -static struct tinywl_view *tinywl_view_addon_get(struct wlr_addon_set *addons, - const void * owner) { - struct wlr_addon *addon = - wlr_addon_find(addons, owner, &tinywl_view_addon_impl); - if (addon == NULL) { - return NULL; - } - struct tinywl_view *view = wl_container_of(addon, view, addon); - return view; -} - -static void focus_view(struct tinywl_view *view, struct wlr_surface *surface) { +static void focus_toplevel(struct tinywl_toplevel *toplevel, struct wlr_surface *surface) { /* Note: this function only deals with keyboard focus. */ - if (view == NULL) { + if (toplevel == NULL) { return; } - struct tinywl_server *server = view->server; + struct tinywl_server *server = toplevel->server; struct wlr_seat *seat = server->seat; struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; if (prev_surface == surface) { @@ -166,25 +129,26 @@ static void focus_view(struct tinywl_view *view, struct wlr_surface *surface) { * it no longer has focus and the client will repaint accordingly, e.g. * stop displaying a caret. */ - struct wlr_xdg_surface *previous = wlr_xdg_surface_from_wlr_surface( - seat->keyboard_state.focused_surface); - assert(previous->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); - wlr_xdg_toplevel_set_activated(previous->toplevel, false); + struct wlr_xdg_toplevel *prev_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(prev_surface); + if (prev_toplevel != NULL) { + wlr_xdg_toplevel_set_activated(prev_toplevel, false); + } } struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); - /* Move the view to the front */ - wlr_scene_node_raise_to_top(&view->scene_tree->node); - wl_list_remove(&view->link); - wl_list_insert(&server->views, &view->link); + /* Move the toplevel to the front */ + wlr_scene_node_raise_to_top(&toplevel->scene_tree->node); + wl_list_remove(&toplevel->link); + wl_list_insert(&server->toplevels, &toplevel->link); /* Activate the new surface */ - wlr_xdg_toplevel_set_activated(view->xdg_toplevel, true); + wlr_xdg_toplevel_set_activated(toplevel->xdg_toplevel, true); /* * Tell the seat to have the keyboard enter this surface. wlroots will keep * track of this and automatically send key events to the appropriate * clients without additional work on your part. */ if (keyboard != NULL) { - wlr_seat_keyboard_notify_enter(seat, view->xdg_toplevel->base->surface, + wlr_seat_keyboard_notify_enter(seat, toplevel->xdg_toplevel->base->surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); } } @@ -220,13 +184,13 @@ static bool handle_keybinding(struct tinywl_server *server, xkb_keysym_t sym) { wl_display_terminate(server->wl_display); break; case XKB_KEY_F1: - /* Cycle to the next view */ - if (wl_list_length(&server->views) < 2) { + /* Cycle to the next toplevel */ + if (wl_list_length(&server->toplevels) < 2) { break; } - struct tinywl_view *next_view = wl_container_of( - server->views.prev, next_view, link); - focus_view(next_view, next_view->xdg_toplevel->base->surface); + struct tinywl_toplevel *next_toplevel = + wl_container_of(server->toplevels.prev, next_toplevel, link); + focus_toplevel(next_toplevel, next_toplevel->xdg_toplevel->base->surface); break; default: return false; @@ -287,8 +251,7 @@ static void server_new_keyboard(struct tinywl_server *server, struct wlr_input_device *device) { struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(device); - struct tinywl_keyboard *keyboard = - calloc(1, sizeof(struct tinywl_keyboard)); + struct tinywl_keyboard *keyboard = calloc(1, sizeof(*keyboard)); keyboard->server = server; keyboard->wlr_keyboard = wlr_keyboard; @@ -382,12 +345,12 @@ static void seat_request_set_selection(struct wl_listener *listener, void *data) wlr_seat_set_selection(server->seat, event->source, event->serial); } -static struct tinywl_view *desktop_view_at( +static struct tinywl_toplevel *desktop_toplevel_at( struct tinywl_server *server, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { /* This returns the topmost node in the scene at the given layout coords. - * we only care about surface nodes as we are specifically looking for a - * surface in the surface tree of a tinywl_view. */ + * We only care about surface nodes as we are specifically looking for a + * surface in the surface tree of a tinywl_toplevel. */ struct wlr_scene_node *node = wlr_scene_node_at( &server->scene->tree.node, lx, ly, sx, sy); if (node == NULL || node->type != WLR_SCENE_NODE_BUFFER) { @@ -395,13 +358,13 @@ static struct tinywl_view *desktop_view_at( } struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); struct wlr_scene_surface *scene_surface = - wlr_scene_surface_from_buffer(scene_buffer); + wlr_scene_surface_try_from_buffer(scene_buffer); if (!scene_surface) { return NULL; } *surface = scene_surface->surface; - /* Find the node corresponding to the tinywl_view at the root of this + /* Find the node corresponding to the tinywl_toplevel at the root of this * surface tree, it is the only one for which we set the data field. */ struct wlr_scene_tree *tree = node->parent; while (tree != NULL && tree->node.data == NULL) { @@ -413,29 +376,29 @@ static struct tinywl_view *desktop_view_at( static void reset_cursor_mode(struct tinywl_server *server) { /* Reset the cursor mode to passthrough. */ server->cursor_mode = TINYWL_CURSOR_PASSTHROUGH; - server->grabbed_view = NULL; + server->grabbed_toplevel = NULL; } static void process_cursor_move(struct tinywl_server *server, uint32_t time) { - /* Move the grabbed view to the new position. */ - struct tinywl_view *view = server->grabbed_view; - view->x = server->cursor->x - server->grab_x; - view->y = server->cursor->y - server->grab_y; - wlr_scene_node_set_position(&view->scene_tree->node, view->x, view->y); + /* Move the grabbed toplevel to the new position. */ + struct tinywl_toplevel *toplevel = server->grabbed_toplevel; + wlr_scene_node_set_position(&toplevel->scene_tree->node, + server->cursor->x - server->grab_x, + server->cursor->y - server->grab_y); } static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { /* - * Resizing the grabbed view can be a little bit complicated, because we - * could be resizing from any corner or edge. This not only resizes the view - * on one or two axes, but can also move the view if you resize from the top - * or left edges (or top-left corner). + * 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 + * toplevel on one or two axes, but can also move the toplevel if you resize + * from the top or left edges (or top-left corner). * - * Note that I took some shortcuts here. In a more fleshed-out compositor, - * you'd wait for the client to prepare a buffer at the new size, then - * commit any movement that was prepared. + * Note that some shortcuts are taken here. In a more fleshed-out + * compositor, you'd wait for the client to prepare a buffer at the new + * size, then commit any movement that was prepared. */ - struct tinywl_view *view = server->grabbed_view; + struct tinywl_toplevel *toplevel = server->grabbed_toplevel; double border_x = server->cursor->x - server->grab_x; double border_y = server->cursor->y - server->grab_y; int new_left = server->grab_geobox.x; @@ -467,14 +430,13 @@ static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { } struct wlr_box geo_box; - wlr_xdg_surface_get_geometry(view->xdg_toplevel->base, &geo_box); - view->x = new_left - geo_box.x; - view->y = new_top - geo_box.y; - wlr_scene_node_set_position(&view->scene_tree->node, view->x, view->y); + wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box); + wlr_scene_node_set_position(&toplevel->scene_tree->node, + new_left - geo_box.x, new_top - geo_box.y); int new_width = new_right - new_left; int new_height = new_bottom - new_top; - wlr_xdg_toplevel_set_size(view->xdg_toplevel, new_width, new_height); + wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, new_width, new_height); } static void process_cursor_motion(struct tinywl_server *server, uint32_t time) { @@ -487,18 +449,17 @@ static void process_cursor_motion(struct tinywl_server *server, uint32_t time) { return; } - /* Otherwise, find the view under the pointer and send the event along. */ + /* Otherwise, find the toplevel under the pointer and send the event along. */ double sx, sy; struct wlr_seat *seat = server->seat; struct wlr_surface *surface = NULL; - struct tinywl_view *view = desktop_view_at(server, + struct tinywl_toplevel *toplevel = desktop_toplevel_at(server, server->cursor->x, server->cursor->y, &surface, &sx, &sy); - if (!view) { - /* If there's no view under the cursor, set the cursor image to a + if (!toplevel) { + /* If there's no toplevel under the cursor, set the cursor image to a * default. This is what makes the cursor image appear when you move it - * around the screen, not over any views. */ - wlr_xcursor_manager_set_cursor_image( - server->cursor_mgr, "left_ptr", server->cursor); + * around the screen, not over any toplevels. */ + wlr_cursor_set_xcursor(server->cursor, server->cursor_mgr, "default"); } if (surface) { /* @@ -564,14 +525,14 @@ static void server_cursor_button(struct wl_listener *listener, void *data) { event->time_msec, event->button, event->state); double sx, sy; struct wlr_surface *surface = NULL; - struct tinywl_view *view = desktop_view_at(server, + struct tinywl_toplevel *toplevel = desktop_toplevel_at(server, server->cursor->x, server->cursor->y, &surface, &sx, &sy); if (event->state == WLR_BUTTON_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_view(view, surface); + focus_toplevel(toplevel, surface); } } @@ -599,47 +560,45 @@ static void server_cursor_frame(struct wl_listener *listener, void *data) { } static void output_configure_scene(struct wlr_scene_node *node, - struct tinywl_view *view) { + struct tinywl_toplevel *toplevel) { if (!node->enabled) { return; } - struct tinywl_view *_view = tinywl_view_addon_get(&node->addons, node); - if (_view) { - view = _view; + struct tinywl_toplevel *_toplevel = node->data; + if (_toplevel) { + toplevel = _toplevel; } if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); - struct wlr_scene_surface * scene_surface - = wlr_scene_surface_from_buffer(buffer); + struct wlr_scene_surface * scene_surface = + wlr_scene_surface_try_from_buffer(buffer); if (!scene_surface) { return; } - struct wlr_xdg_surface *xdg_surface = NULL; - if (wlr_surface_is_xdg_surface(scene_surface->surface)) { - xdg_surface = wlr_xdg_surface_from_wlr_surface(scene_surface->surface); - } + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); - if (view && + if (toplevel && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { // TODO: Be able to set whole decoration_data instead of calling // each individually? - wlr_scene_buffer_set_opacity(buffer, view->opacity); + wlr_scene_buffer_set_opacity(buffer, toplevel->opacity); - if (!wlr_surface_is_subsurface(xdg_surface->surface)) { - wlr_scene_buffer_set_corner_radius(buffer, view->corner_radius); - wlr_scene_buffer_set_shadow_data(buffer, view->shadow_data); + if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) { + wlr_scene_buffer_set_corner_radius(buffer, toplevel->corner_radius); + wlr_scene_buffer_set_shadow_data(buffer, toplevel->shadow_data); } } } else if (node->type == WLR_SCENE_NODE_TREE) { struct wlr_scene_tree *tree = wl_container_of(node, tree, node); struct wlr_scene_node *node; wl_list_for_each(node, &tree->children, link) { - output_configure_scene(node, view); + output_configure_scene(node, toplevel); } } } @@ -656,17 +615,27 @@ static void output_frame(struct wl_listener *listener, void *data) { output_configure_scene(&scene_output->scene->tree.node, NULL); /* Render the scene if needed and commit the output */ - wlr_scene_output_commit(scene_output); + wlr_scene_output_commit(scene_output, NULL); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); wlr_scene_output_send_frame_done(scene_output, &now); } +static void output_request_state(struct wl_listener *listener, void *data) { + /* This function is called when the backend requests a new state for + * the output. For example, Wayland and X11 backends request a new mode + * when the output window is resized. */ + struct tinywl_output *output = wl_container_of(listener, output, request_state); + const struct wlr_output_event_request_state *event = data; + wlr_output_commit_state(output->wlr_output, event->state); +} + static void output_destroy(struct wl_listener *listener, void *data) { struct tinywl_output *output = wl_container_of(listener, output, destroy); wl_list_remove(&output->frame.link); + wl_list_remove(&output->request_state.link); wl_list_remove(&output->destroy.link); wl_list_remove(&output->link); free(output); @@ -683,30 +652,39 @@ static void server_new_output(struct wl_listener *listener, void *data) { * and our renderer. Must be done once, before commiting the output */ wlr_output_init_render(wlr_output, server->allocator, server->renderer); + /* The output may be disabled, switch it on. */ + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + /* Some backends don't have modes. DRM+KMS does, and we need to set a mode * before we can use the output. The mode is a tuple of (width, height, * refresh rate), and each monitor supports only a specific set of modes. We * just pick the monitor's preferred mode, a more sophisticated compositor * would let the user configure it. */ - if (!wl_list_empty(&wlr_output->modes)) { - struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); - wlr_output_set_mode(wlr_output, mode); - wlr_output_enable(wlr_output, true); - if (!wlr_output_commit(wlr_output)) { - return; - } + struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); } + /* Atomically applies the new output state. */ + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + /* Allocates and configures our state for this output */ - struct tinywl_output *output = - calloc(1, sizeof(struct tinywl_output)); + struct tinywl_output *output = calloc(1, sizeof(*output)); output->wlr_output = wlr_output; output->server = server; - /* Sets up a listener for the frame notify event. */ + + /* Sets up a listener for the frame event. */ output->frame.notify = output_frame; wl_signal_add(&wlr_output->events.frame, &output->frame); - /* Sets up a listener for the destroy notify event. */ + /* Sets up a listener for the state request event. */ + output->request_state.notify = output_request_state; + wl_signal_add(&wlr_output->events.request_state, &output->request_state); + + /* Sets up a listener for the destroy event. */ output->destroy.notify = output_destroy; wl_signal_add(&wlr_output->events.destroy, &output->destroy); @@ -721,70 +699,81 @@ static void server_new_output(struct wl_listener *listener, void *data) { * display, which Wayland clients can see to find out information about the * output (such as DPI, scale factor, manufacturer, etc). */ - wlr_output_layout_add_auto(server->output_layout, wlr_output); + struct wlr_output_layout_output *l_output = wlr_output_layout_add_auto(server->output_layout, + wlr_output); + struct wlr_scene_output *scene_output = wlr_scene_output_create(server->scene, wlr_output); + wlr_scene_output_layout_add_output(server->scene_layout, l_output, scene_output); } static void xdg_toplevel_map(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ - struct tinywl_view *view = wl_container_of(listener, view, map); + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, map); - wl_list_insert(&view->server->views, &view->link); + wl_list_insert(&toplevel->server->toplevels, &toplevel->link); - focus_view(view, view->xdg_toplevel->base->surface); + focus_toplevel(toplevel, toplevel->xdg_toplevel->base->surface); } static void xdg_toplevel_unmap(struct wl_listener *listener, void *data) { /* Called when the surface is unmapped, and should no longer be shown. */ - struct tinywl_view *view = wl_container_of(listener, view, unmap); + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, unmap); - /* Reset the cursor mode if the grabbed view was unmapped. */ - if (view == view->server->grabbed_view) { - reset_cursor_mode(view->server); + /* Reset the cursor mode if the grabbed toplevel was unmapped. */ + if (toplevel == toplevel->server->grabbed_toplevel) { + reset_cursor_mode(toplevel->server); } - wl_list_remove(&view->link); + wl_list_remove(&toplevel->link); } static void xdg_toplevel_destroy(struct wl_listener *listener, void *data) { - /* Called when the surface is destroyed and should never be shown again. */ - struct tinywl_view *view = wl_container_of(listener, view, destroy); - - tinywl_view_destroy(view); + /* Called when the xdg_toplevel is destroyed. */ + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, destroy); + + wl_list_remove(&toplevel->map.link); + wl_list_remove(&toplevel->unmap.link); + wl_list_remove(&toplevel->destroy.link); + wl_list_remove(&toplevel->request_move.link); + wl_list_remove(&toplevel->request_resize.link); + wl_list_remove(&toplevel->request_maximize.link); + wl_list_remove(&toplevel->request_fullscreen.link); + + free(toplevel); } -static void begin_interactive(struct tinywl_view *view, +static void begin_interactive(struct tinywl_toplevel *toplevel, enum tinywl_cursor_mode mode, uint32_t edges) { /* This function sets up an interactive move or resize operation, where the * compositor stops propegating pointer events to clients and instead * consumes them itself, to move or resize windows. */ - struct tinywl_server *server = view->server; + struct tinywl_server *server = toplevel->server; struct wlr_surface *focused_surface = server->seat->pointer_state.focused_surface; - if (view->xdg_toplevel->base->surface != + if (toplevel->xdg_toplevel->base->surface != wlr_surface_get_root_surface(focused_surface)) { /* Deny move/resize requests from unfocused clients. */ return; } - server->grabbed_view = view; + server->grabbed_toplevel = toplevel; server->cursor_mode = mode; if (mode == TINYWL_CURSOR_MOVE) { - server->grab_x = server->cursor->x - view->x; - server->grab_y = server->cursor->y - view->y; + 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(view->xdg_toplevel->base, &geo_box); + wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box); - double border_x = (view->x + geo_box.x) + + double border_x = (toplevel->scene_tree->node.x + geo_box.x) + ((edges & WLR_EDGE_RIGHT) ? geo_box.width : 0); - double border_y = (view->y + geo_box.y) + + 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.x += view->x; - server->grab_geobox.y += view->y; + server->grab_geobox.x += toplevel->scene_tree->node.x; + server->grab_geobox.y += toplevel->scene_tree->node.y; server->resize_edges = edges; } @@ -797,8 +786,8 @@ static void xdg_toplevel_request_move( * decorations. Note that a more sophisticated compositor should check the * provided serial against a list of button press serials sent to this * client, to prevent the client from requesting this whenever they want. */ - struct tinywl_view *view = wl_container_of(listener, view, request_move); - begin_interactive(view, TINYWL_CURSOR_MOVE, 0); + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, request_move); + begin_interactive(toplevel, TINYWL_CURSOR_MOVE, 0); } static void xdg_toplevel_request_resize( @@ -809,8 +798,8 @@ static void xdg_toplevel_request_resize( * provided serial against a list of button press serials sent to this * client, to prevent the client from requesting this whenever they want. */ struct wlr_xdg_toplevel_resize_event *event = data; - struct tinywl_view *view = wl_container_of(listener, view, request_resize); - begin_interactive(view, TINYWL_CURSOR_RESIZE, event->edges); + struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, request_resize); + begin_interactive(toplevel, TINYWL_CURSOR_RESIZE, event->edges); } static void xdg_toplevel_request_maximize( @@ -820,17 +809,17 @@ static void xdg_toplevel_request_maximize( * client-side decorations. tinywl doesn't support maximization, but * to conform to xdg-shell protocol we still must send a configure. * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ - struct tinywl_view *view = - wl_container_of(listener, view, request_maximize); - wlr_xdg_surface_schedule_configure(view->xdg_toplevel->base); + struct tinywl_toplevel *toplevel = + wl_container_of(listener, toplevel, request_maximize); + wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base); } static void xdg_toplevel_request_fullscreen( struct wl_listener *listener, void *data) { /* Just as with request_maximize, we must send a configure here. */ - struct tinywl_view *view = - wl_container_of(listener, view, request_fullscreen); - wlr_xdg_surface_schedule_configure(view->xdg_toplevel->base); + struct tinywl_toplevel *toplevel = + wl_container_of(listener, toplevel, request_fullscreen); + wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base); } static void server_new_xdg_surface(struct wl_listener *listener, void *data) { @@ -846,8 +835,9 @@ static void server_new_xdg_surface(struct wl_listener *listener, void *data) { * we always set the user data field of xdg_surfaces to the corresponding * scene node. */ if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { - struct wlr_xdg_surface *parent = wlr_xdg_surface_from_wlr_surface( - xdg_surface->popup->parent); + struct wlr_xdg_surface *parent = + wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent); + assert(parent != NULL); struct wlr_scene_tree *parent_tree = parent->data; xdg_surface->data = wlr_scene_xdg_surface_create( parent_tree, xdg_surface); @@ -855,46 +845,42 @@ static void server_new_xdg_surface(struct wl_listener *listener, void *data) { } assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); - /* Allocate a tinywl_view for this surface */ - struct tinywl_view *view = - calloc(1, sizeof(struct tinywl_view)); - view->server = server; - view->xdg_toplevel = xdg_surface->toplevel; - view->scene_tree = wlr_scene_xdg_surface_create( - &view->server->scene->tree, view->xdg_toplevel->base); - view->scene_tree->node.data = view; - xdg_surface->data = view->scene_tree; - - /* Add the view to the trees addon */ - tinywl_view_addon_assign(view, &view->scene_tree->node.addons, &view->scene_tree->node); + /* Allocate a tinywl_toplevel for this surface */ + struct tinywl_toplevel *toplevel = calloc(1, sizeof(*toplevel)); + toplevel->server = server; + toplevel->xdg_toplevel = xdg_surface->toplevel; + toplevel->scene_tree = wlr_scene_xdg_surface_create( + &toplevel->server->scene->tree, toplevel->xdg_toplevel->base); + toplevel->scene_tree->node.data = toplevel; + xdg_surface->data = toplevel->scene_tree; /* Set the scene_nodes decoration data */ - view->opacity = 1; - view->corner_radius = 20; - view->shadow_data = shadow_data_get_default(); - view->shadow_data.enabled = true; - memcpy(view->shadow_data.color, (float[]) {1.0f, 0.0f, 0.0f, 1.0f}, sizeof(float[4])); + toplevel->opacity = 1; + toplevel->corner_radius = 20; + toplevel->shadow_data = shadow_data_get_default(); + toplevel->shadow_data.enabled = true; + toplevel->shadow_data.color = (struct wlr_render_color) {1.0f, 0.0f, 0.0f, 1.0f}; /* Listen to the various events it can emit */ - view->map.notify = xdg_toplevel_map; - wl_signal_add(&xdg_surface->events.map, &view->map); - view->unmap.notify = xdg_toplevel_unmap; - wl_signal_add(&xdg_surface->events.unmap, &view->unmap); - view->destroy.notify = xdg_toplevel_destroy; - wl_signal_add(&xdg_surface->events.destroy, &view->destroy); + toplevel->map.notify = xdg_toplevel_map; + wl_signal_add(&xdg_surface->surface->events.map, &toplevel->map); + toplevel->unmap.notify = xdg_toplevel_unmap; + wl_signal_add(&xdg_surface->surface->events.unmap, &toplevel->unmap); + toplevel->destroy.notify = xdg_toplevel_destroy; + wl_signal_add(&xdg_surface->events.destroy, &toplevel->destroy); /* cotd */ - struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel; - view->request_move.notify = xdg_toplevel_request_move; - wl_signal_add(&toplevel->events.request_move, &view->request_move); - view->request_resize.notify = xdg_toplevel_request_resize; - wl_signal_add(&toplevel->events.request_resize, &view->request_resize); - view->request_maximize.notify = xdg_toplevel_request_maximize; - wl_signal_add(&toplevel->events.request_maximize, - &view->request_maximize); - view->request_fullscreen.notify = xdg_toplevel_request_fullscreen; - wl_signal_add(&toplevel->events.request_fullscreen, - &view->request_fullscreen); + struct wlr_xdg_toplevel *xdg_toplevel = xdg_surface->toplevel; + toplevel->request_move.notify = xdg_toplevel_request_move; + wl_signal_add(&xdg_toplevel->events.request_move, &toplevel->request_move); + toplevel->request_resize.notify = xdg_toplevel_request_resize; + wl_signal_add(&xdg_toplevel->events.request_resize, &toplevel->request_resize); + toplevel->request_maximize.notify = xdg_toplevel_request_maximize; + wl_signal_add(&xdg_toplevel->events.request_maximize, + &toplevel->request_maximize); + toplevel->request_fullscreen.notify = xdg_toplevel_request_fullscreen; + wl_signal_add(&xdg_toplevel->events.request_fullscreen, + &toplevel->request_fullscreen); } int main(int argc, char *argv[]) { @@ -917,7 +903,7 @@ int main(int argc, char *argv[]) { return 0; } - struct tinywl_server server; + struct tinywl_server server = {0}; /* The Wayland display is managed by libwayland. It handles accepting * clients from the Unix socket, manging Wayland globals, and so on. */ server.wl_display = wl_display_create(); @@ -925,7 +911,7 @@ int main(int argc, char *argv[]) { * output hardware. The autocreate option will choose the most suitable * backend based on the current environment, such as opening an X11 window * if an X11 server is running. */ - server.backend = wlr_backend_autocreate(server.wl_display); + server.backend = wlr_backend_autocreate(server.wl_display, NULL); if (server.backend == NULL) { wlr_log(WLR_ERROR, "failed to create wlr_backend"); return 1; @@ -935,7 +921,7 @@ int main(int argc, char *argv[]) { * can also specify a renderer using the WLR_RENDERER env var. * The renderer is responsible for defining the various pixel formats it * supports for shared memory, this configures that for clients. */ - server.renderer = wlr_renderer_autocreate(server.backend); + server.renderer = fx_renderer_create(server.backend); if (server.renderer == NULL) { wlr_log(WLR_ERROR, "failed to create wlr_renderer"); return 1; @@ -947,7 +933,7 @@ int main(int argc, char *argv[]) { * The allocator is the bridge between the renderer and the backend. It * handles the buffer creation, allowing wlroots to render onto the * screen */ - server.allocator = wlr_allocator_autocreate(server.backend, + server.allocator = wlr_allocator_autocreate(server.backend, server.renderer); if (server.allocator == NULL) { wlr_log(WLR_ERROR, "failed to create wlr_allocator"); @@ -961,7 +947,7 @@ int main(int argc, char *argv[]) { * to dig your fingers in and play with their behavior if you want. Note that * the clients cannot set the selection directly without compositor approval, * see the handling of the request_set_selection event below.*/ - wlr_compositor_create(server.wl_display, server.renderer); + wlr_compositor_create(server.wl_display, 5, server.renderer); wlr_subcompositor_create(server.wl_display); wlr_data_device_manager_create(server.wl_display); @@ -982,15 +968,13 @@ int main(int argc, char *argv[]) { * necessary. */ server.scene = wlr_scene_create(); - wlr_scene_attach_output_layout(server.scene, server.output_layout); + server.scene_layout = wlr_scene_attach_output_layout(server.scene, server.output_layout); /* Set up xdg-shell version 3. The xdg-shell is a Wayland protocol which is - * used for application windows. For more detail on shells, refer to my - * article: - * - * https://drewdevault.com/2018/07/29/Wayland-shells.html + * used for application windows. For more detail on shells, refer to + * https://drewdevault.com/2018/07/29/Wayland-shells.html. */ - wl_list_init(&server.views); + wl_list_init(&server.toplevels); server.xdg_shell = wlr_xdg_shell_create(server.wl_display, 3); server.new_xdg_surface.notify = server_new_xdg_surface; wl_signal_add(&server.xdg_shell->events.new_surface, @@ -1006,19 +990,16 @@ int main(int argc, char *argv[]) { /* Creates an xcursor manager, another wlroots utility which loads up * Xcursor themes to source cursor images from and makes sure that cursor * images are available at all scale factors on the screen (necessary for - * HiDPI support). We add a cursor theme at scale factor 1 to begin with. */ + * HiDPI support). */ server.cursor_mgr = wlr_xcursor_manager_create(NULL, 24); - wlr_xcursor_manager_load(server.cursor_mgr, 1); /* * wlr_cursor *only* displays an image on screen. It does not move around * when the pointer moves. However, we can attach input devices to it, and * it will generate aggregate events for all of them. In these events, we * can choose how we want to process them, forwarding them to clients and - * moving the cursor around. More detail on this process is described in my - * input handling blog post: - * - * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html + * moving the cursor around. More detail on this process is described in + * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html. * * And more comments are sprinkled throughout the notify functions above. */ @@ -1083,8 +1064,12 @@ int main(int argc, char *argv[]) { socket); wl_display_run(server.wl_display); - /* Once wl_display_run returns, we shut down the server. */ + /* Once wl_display_run returns, we destroy all clients then shut down the + * server. */ wl_display_destroy_clients(server.wl_display); + wlr_scene_node_destroy(&server.scene->tree.node); + wlr_xcursor_manager_destroy(server.cursor_mgr); + wlr_output_layout_destroy(server.output_layout); wl_display_destroy(server.wl_display); return 0; } diff --git a/types/buffer/buffer.c b/types/buffer/buffer.c index d4c47bc..5303927 100644 --- a/types/buffer/buffer.c +++ b/types/buffer/buffer.c @@ -2,28 +2,27 @@ #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)) { - wlr_buffer_end_data_ptr_access(buffer); - } else { - return false; - } + 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)) { + wlr_buffer_end_data_ptr_access(buffer); + } else { + return false; + } - const struct wlr_pixel_format_info *format_info = - drm_get_pixel_format_info(format); - if (format_info == NULL) { - return false; - } + const struct wlr_pixel_format_info *format_info = + drm_get_pixel_format_info(format); + if (format_info == NULL) { + return false; + } - return !format_info->has_alpha; + return !format_info->has_alpha; } diff --git a/types/fx/shadow_data.c b/types/fx/shadow_data.c index 0c2d1d2..fae76cf 100644 --- a/types/fx/shadow_data.c +++ b/types/fx/shadow_data.c @@ -5,14 +5,13 @@ #include "wlr/util/log.h" struct shadow_data shadow_data_get_default(void) { - static float default_shadow_color[] = {0.0f, 0.0f, 0.0f, 0.5f}; return (struct shadow_data) { .blur_sigma = 20, - .color = default_shadow_color, + .color = {0.0f, 0.0f, 0.0f, 0.5f}, .enabled = false, }; } bool scene_buffer_has_shadow(struct shadow_data *data) { - return data->enabled && data->blur_sigma > 0 && data->color[3] > 0.0; + return data->enabled && data->blur_sigma > 0 && data->color.a > 0.0; } diff --git a/types/meson.build b/types/meson.build index b5857d0..0527b7c 100644 --- a/types/meson.build +++ b/types/meson.build @@ -1,10 +1,6 @@ wlr_files += files( - 'scene/subsurface_tree.c', - 'scene/surface.c', + 'output/wlr_output.c', 'scene/wlr_scene.c', - 'scene/output_layout.c', - 'scene/xdg_shell.c', - 'scene/layer_shell_v1.c', 'buffer/buffer.c', ) diff --git a/types/output/wlr_output.c b/types/output/wlr_output.c new file mode 100644 index 0000000..43325cf --- /dev/null +++ b/types/output/wlr_output.c @@ -0,0 +1,24 @@ +#define _POSIX_C_SOURCE 200809L +#include <stdlib.h> + +#include "types/wlr_output.h" + +void output_pending_resolution(struct wlr_output *output, + const struct wlr_output_state *state, int *width, int *height) { + if (state->committed & WLR_OUTPUT_STATE_MODE) { + switch (state->mode_type) { + case WLR_OUTPUT_STATE_MODE_FIXED: + *width = state->mode->width; + *height = state->mode->height; + return; + case WLR_OUTPUT_STATE_MODE_CUSTOM: + *width = state->custom_mode.width; + *height = state->custom_mode.height; + return; + } + abort(); + } else { + *width = output->width; + *height = output->height; + } +} diff --git a/types/scene/layer_shell_v1.c b/types/scene/layer_shell_v1.c deleted file mode 100644 index 3ed616a..0000000 --- a/types/scene/layer_shell_v1.c +++ /dev/null @@ -1,186 +0,0 @@ -#include <stdlib.h> -#include <wlr/types/wlr_scene.h> -#include <wlr/types/wlr_layer_shell_v1.h> - -static void scene_layer_surface_handle_tree_destroy( - struct wl_listener *listener, void *data) { - struct wlr_scene_layer_surface_v1 *scene_layer_surface = - wl_container_of(listener, scene_layer_surface, tree_destroy); - // tree and surface_node will be cleaned up by scene_node_finish - wl_list_remove(&scene_layer_surface->tree_destroy.link); - wl_list_remove(&scene_layer_surface->layer_surface_destroy.link); - wl_list_remove(&scene_layer_surface->layer_surface_map.link); - wl_list_remove(&scene_layer_surface->layer_surface_unmap.link); - free(scene_layer_surface); -} - -static void scene_layer_surface_handle_layer_surface_destroy( - struct wl_listener *listener, void *data) { - struct wlr_scene_layer_surface_v1 *scene_layer_surface = - wl_container_of(listener, scene_layer_surface, layer_surface_destroy); - wlr_scene_node_destroy(&scene_layer_surface->tree->node); -} - -static void scene_layer_surface_handle_layer_surface_map( - struct wl_listener *listener, void *data) { - struct wlr_scene_layer_surface_v1 *scene_layer_surface = - wl_container_of(listener, scene_layer_surface, layer_surface_map); - wlr_scene_node_set_enabled(&scene_layer_surface->tree->node, true); -} - -static void scene_layer_surface_handle_layer_surface_unmap( - struct wl_listener *listener, void *data) { - struct wlr_scene_layer_surface_v1 *scene_layer_surface = - wl_container_of(listener, scene_layer_surface, layer_surface_unmap); - wlr_scene_node_set_enabled(&scene_layer_surface->tree->node, false); -} - -static void layer_surface_exclusive_zone( - struct wlr_layer_surface_v1_state *state, - struct wlr_box *usable_area) { - switch (state->anchor) { - case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP: - case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): - // Anchor top - usable_area->y += state->exclusive_zone + state->margin.top; - usable_area->height -= state->exclusive_zone + state->margin.top; - break; - case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: - case (ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): - // Anchor bottom - usable_area->height -= state->exclusive_zone + state->margin.bottom; - break; - case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT: - case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT): - // Anchor left - usable_area->x += state->exclusive_zone + state->margin.left; - usable_area->width -= state->exclusive_zone + state->margin.left; - break; - case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: - case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): - // Anchor right - usable_area->width -= state->exclusive_zone + state->margin.right; - break; - } -} - -void wlr_scene_layer_surface_v1_configure( - struct wlr_scene_layer_surface_v1 *scene_layer_surface, - const struct wlr_box *full_area, struct wlr_box *usable_area) { - struct wlr_layer_surface_v1 *layer_surface = - scene_layer_surface->layer_surface; - struct wlr_layer_surface_v1_state *state = &layer_surface->current; - - // If the exclusive zone is set to -1, the layer surface will use the - // full area of the output, otherwise it is constrained to the - // remaining usable area. - struct wlr_box bounds; - if (state->exclusive_zone == -1) { - bounds = *full_area; - } else { - bounds = *usable_area; - } - - struct wlr_box box = { - .width = state->desired_width, - .height = state->desired_height, - }; - - // Horizontal positioning - if (box.width == 0) { - box.x = bounds.x + state->margin.left; - box.width = bounds.width - - (state->margin.left + state->margin.right); - } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT && - state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) { - box.x = bounds.x + bounds.width/2 -box.width/2; - } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) { - box.x = bounds.x + state->margin.left; - } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) { - box.x = bounds.x + bounds.width - box.width - state->margin.right; - } else { - box.x = bounds.x + bounds.width/2 - box.width/2; - } - - // Vertical positioning - if (box.height == 0) { - box.y = bounds.y + state->margin.top; - box.height = bounds.height - - (state->margin.top + state->margin.bottom); - } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP && - state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) { - box.y = bounds.y + bounds.height/2 - box.height/2; - } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) { - box.y = bounds.y + state->margin.top; - } else if (state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) { - box.y = bounds.y + bounds.height - box.height - state->margin.bottom; - } else { - box.y = bounds.y + bounds.height/2 - box.height/2; - } - - wlr_scene_node_set_position(&scene_layer_surface->tree->node, box.x, box.y); - wlr_layer_surface_v1_configure(layer_surface, box.width, box.height); - - if (layer_surface->mapped && state->exclusive_zone > 0) { - layer_surface_exclusive_zone(state, usable_area); - } -} - -struct wlr_scene_layer_surface_v1 *wlr_scene_layer_surface_v1_create( - struct wlr_scene_tree *parent, - struct wlr_layer_surface_v1 *layer_surface) { - struct wlr_scene_layer_surface_v1 *scene_layer_surface = - calloc(1, sizeof(*scene_layer_surface)); - if (scene_layer_surface == NULL) { - return NULL; - } - - scene_layer_surface->layer_surface = layer_surface; - - scene_layer_surface->tree = wlr_scene_tree_create(parent); - if (scene_layer_surface->tree == NULL) { - free(scene_layer_surface); - return NULL; - } - - struct wlr_scene_tree *surface_tree = wlr_scene_subsurface_tree_create( - scene_layer_surface->tree, layer_surface->surface); - if (surface_tree == NULL) { - wlr_scene_node_destroy(&scene_layer_surface->tree->node); - free(scene_layer_surface); - return NULL; - } - - scene_layer_surface->tree_destroy.notify = - scene_layer_surface_handle_tree_destroy; - wl_signal_add(&scene_layer_surface->tree->node.events.destroy, - &scene_layer_surface->tree_destroy); - - scene_layer_surface->layer_surface_destroy.notify = - scene_layer_surface_handle_layer_surface_destroy; - wl_signal_add(&layer_surface->events.destroy, - &scene_layer_surface->layer_surface_destroy); - - scene_layer_surface->layer_surface_map.notify = - scene_layer_surface_handle_layer_surface_map; - wl_signal_add(&layer_surface->events.map, - &scene_layer_surface->layer_surface_map); - - scene_layer_surface->layer_surface_unmap.notify = - scene_layer_surface_handle_layer_surface_unmap; - wl_signal_add(&layer_surface->events.unmap, - &scene_layer_surface->layer_surface_unmap); - - wlr_scene_node_set_enabled(&scene_layer_surface->tree->node, - layer_surface->mapped); - - return scene_layer_surface; -} diff --git a/types/scene/output_layout.c b/types/scene/output_layout.c deleted file mode 100644 index 1d1484a..0000000 --- a/types/scene/output_layout.c +++ /dev/null @@ -1,157 +0,0 @@ -#include <stdlib.h> -#include <wlr/types/wlr_output_layout.h> -#include <wlr/types/wlr_scene.h> - -struct wlr_scene_output_layout { - struct wlr_output_layout *layout; - struct wlr_scene *scene; - - struct wl_list outputs; // wlr_scene_output_layout_output.link - - struct wl_listener layout_add; - struct wl_listener layout_change; - struct wl_listener layout_destroy; - struct wl_listener scene_destroy; -}; - -struct wlr_scene_output_layout_output { - struct wlr_output_layout_output *layout_output; - struct wlr_scene_output *scene_output; - - struct wl_list link; // wlr_scene_output_layout.outputs - - struct wl_listener layout_output_destroy; - struct wl_listener scene_output_destroy; -}; - -static void scene_output_layout_output_destroy( - struct wlr_scene_output_layout_output *solo) { - wl_list_remove(&solo->layout_output_destroy.link); - wl_list_remove(&solo->scene_output_destroy.link); - wl_list_remove(&solo->link); - wlr_scene_output_destroy(solo->scene_output); - free(solo); -} - -static void scene_output_layout_output_handle_layout_output_destroy( - struct wl_listener *listener, void *data) { - struct wlr_scene_output_layout_output *solo = - wl_container_of(listener, solo, layout_output_destroy); - scene_output_layout_output_destroy(solo); -} - -static void scene_output_layout_output_handle_scene_output_destroy( - struct wl_listener *listener, void *data) { - struct wlr_scene_output_layout_output *solo = - wl_container_of(listener, solo, scene_output_destroy); - solo->scene_output = NULL; - scene_output_layout_output_destroy(solo); -} - -static void scene_output_layout_destroy(struct wlr_scene_output_layout *sol) { - struct wlr_scene_output_layout_output *solo, *tmp; - wl_list_for_each_safe(solo, tmp, &sol->outputs, link) { - scene_output_layout_output_destroy(solo); - } - wl_list_remove(&sol->layout_add.link); - wl_list_remove(&sol->layout_change.link); - wl_list_remove(&sol->layout_destroy.link); - wl_list_remove(&sol->scene_destroy.link); - free(sol); -} - -static void scene_output_layout_handle_layout_change( - struct wl_listener *listener, void *data) { - struct wlr_scene_output_layout *sol = - wl_container_of(listener, sol, layout_change); - - struct wlr_scene_output_layout_output *solo; - wl_list_for_each(solo, &sol->outputs, link) { - wlr_scene_output_set_position(solo->scene_output, - solo->layout_output->x, solo->layout_output->y); - } -} - -static void scene_output_layout_add(struct wlr_scene_output_layout *sol, - struct wlr_output_layout_output *lo) { - struct wlr_scene_output_layout_output *solo = calloc(1, sizeof(*solo)); - if (solo == NULL) { - return; - } - - solo->scene_output = wlr_scene_output_create(sol->scene, lo->output); - if (solo->scene_output == NULL) { - free(solo); - return; - } - - solo->layout_output = lo; - - solo->layout_output_destroy.notify = - scene_output_layout_output_handle_layout_output_destroy; - wl_signal_add(&lo->events.destroy, &solo->layout_output_destroy); - - solo->scene_output_destroy.notify = - scene_output_layout_output_handle_scene_output_destroy; - wl_signal_add(&solo->scene_output->events.destroy, - &solo->scene_output_destroy); - - wl_list_insert(&sol->outputs, &solo->link); - - wlr_scene_output_set_position(solo->scene_output, lo->x, lo->y); -} - -static void scene_output_layout_handle_layout_add( - struct wl_listener *listener, void *data) { - struct wlr_scene_output_layout *sol = - wl_container_of(listener, sol, layout_add); - struct wlr_output_layout_output *lo = data; - - scene_output_layout_add(sol, lo); -} - -static void scene_output_layout_handle_layout_destroy( - struct wl_listener *listener, void *data) { - struct wlr_scene_output_layout *sol = - wl_container_of(listener, sol, layout_destroy); - scene_output_layout_destroy(sol); -} - -static void scene_output_layout_handle_scene_destroy( - struct wl_listener *listener, void *data) { - struct wlr_scene_output_layout *sol = - wl_container_of(listener, sol, scene_destroy); - scene_output_layout_destroy(sol); -} - -bool wlr_scene_attach_output_layout(struct wlr_scene *scene, - struct wlr_output_layout *output_layout) { - struct wlr_scene_output_layout *sol = calloc(1, sizeof(*sol)); - if (sol == NULL) { - return false; - } - - sol->scene = scene; - sol->layout = output_layout; - - wl_list_init(&sol->outputs); - - sol->layout_destroy.notify = scene_output_layout_handle_layout_destroy; - wl_signal_add(&output_layout->events.destroy, &sol->layout_destroy); - - sol->layout_change.notify = scene_output_layout_handle_layout_change; - wl_signal_add(&output_layout->events.change, &sol->layout_change); - - sol->layout_add.notify = scene_output_layout_handle_layout_add; - wl_signal_add(&output_layout->events.add, &sol->layout_add); - - sol->scene_destroy.notify = scene_output_layout_handle_scene_destroy; - wl_signal_add(&scene->tree.node.events.destroy, &sol->scene_destroy); - - struct wlr_output_layout_output *lo; - wl_list_for_each(lo, &output_layout->outputs, link) { - scene_output_layout_add(sol, lo); - } - - return true; -} diff --git a/types/scene/subsurface_tree.c b/types/scene/subsurface_tree.c deleted file mode 100644 index 35420ab..0000000 --- a/types/scene/subsurface_tree.c +++ /dev/null @@ -1,259 +0,0 @@ -#include <assert.h> -#include <stdlib.h> -#include <wlr/types/wlr_scene.h> -#include <wlr/types/wlr_subcompositor.h> -#include <wlr/util/addon.h> - -/** - * A tree for a surface and all of its child sub-surfaces. - * - * `tree` contains `scene_surface` and one node per sub-surface. - */ -struct wlr_scene_subsurface_tree { - struct wlr_scene_tree *tree; - struct wlr_surface *surface; - struct wlr_scene_surface *scene_surface; - - struct wl_listener tree_destroy; - struct wl_listener surface_destroy; - struct wl_listener surface_commit; - struct wl_listener surface_new_subsurface; - - struct wlr_scene_subsurface_tree *parent; // NULL for the top-level surface - - // Only valid if the surface is a sub-surface - - struct wlr_addon surface_addon; - - struct wl_listener subsurface_destroy; - struct wl_listener subsurface_map; - struct wl_listener subsurface_unmap; -}; - -static void subsurface_tree_handle_tree_destroy(struct wl_listener *listener, - void *data) { - struct wlr_scene_subsurface_tree *subsurface_tree = - wl_container_of(listener, subsurface_tree, tree_destroy); - // tree and scene_surface will be cleaned up by scene_node_finish - if (subsurface_tree->parent) { - wlr_addon_finish(&subsurface_tree->surface_addon); - wl_list_remove(&subsurface_tree->subsurface_destroy.link); - wl_list_remove(&subsurface_tree->subsurface_map.link); - wl_list_remove(&subsurface_tree->subsurface_unmap.link); - } - wl_list_remove(&subsurface_tree->tree_destroy.link); - wl_list_remove(&subsurface_tree->surface_destroy.link); - wl_list_remove(&subsurface_tree->surface_commit.link); - wl_list_remove(&subsurface_tree->surface_new_subsurface.link); - free(subsurface_tree); -} - -static const struct wlr_addon_interface subsurface_tree_addon_impl; - -static struct wlr_scene_subsurface_tree *subsurface_tree_from_subsurface( - struct wlr_scene_subsurface_tree *parent, - struct wlr_subsurface *subsurface) { - struct wlr_addon *addon = wlr_addon_find(&subsurface->surface->addons, - parent, &subsurface_tree_addon_impl); - assert(addon != NULL); - struct wlr_scene_subsurface_tree *subsurface_tree = - wl_container_of(addon, subsurface_tree, surface_addon); - return subsurface_tree; -} - -static void subsurface_tree_reconfigure( - struct wlr_scene_subsurface_tree *subsurface_tree) { - struct wlr_surface *surface = subsurface_tree->surface; - - struct wlr_scene_node *prev = NULL; - struct wlr_subsurface *subsurface; - wl_list_for_each(subsurface, &surface->current.subsurfaces_below, - current.link) { - struct wlr_scene_subsurface_tree *child = - subsurface_tree_from_subsurface(subsurface_tree, subsurface); - if (prev != NULL) { - wlr_scene_node_place_above(&child->tree->node, prev); - } - prev = &child->tree->node; - - wlr_scene_node_set_position(&child->tree->node, - subsurface->current.x, subsurface->current.y); - } - - if (prev != NULL) { - wlr_scene_node_place_above(&subsurface_tree->scene_surface->buffer->node, prev); - } - prev = &subsurface_tree->scene_surface->buffer->node; - - wl_list_for_each(subsurface, &surface->current.subsurfaces_above, - current.link) { - struct wlr_scene_subsurface_tree *child = - subsurface_tree_from_subsurface(subsurface_tree, subsurface); - wlr_scene_node_place_above(&child->tree->node, prev); - prev = &child->tree->node; - - wlr_scene_node_set_position(&child->tree->node, - subsurface->current.x, subsurface->current.y); - } -} - -static void subsurface_tree_handle_surface_destroy(struct wl_listener *listener, - void *data) { - struct wlr_scene_subsurface_tree *subsurface_tree = - wl_container_of(listener, subsurface_tree, surface_destroy); - wlr_scene_node_destroy(&subsurface_tree->tree->node); -} - -static void subsurface_tree_handle_surface_commit(struct wl_listener *listener, - void *data) { - struct wlr_scene_subsurface_tree *subsurface_tree = - wl_container_of(listener, subsurface_tree, surface_commit); - - // TODO: only do this on subsurface order or position change - subsurface_tree_reconfigure(subsurface_tree); -} - -static void subsurface_tree_handle_subsurface_destroy(struct wl_listener *listener, - void *data) { - struct wlr_scene_subsurface_tree *subsurface_tree = - wl_container_of(listener, subsurface_tree, subsurface_destroy); - wlr_scene_node_destroy(&subsurface_tree->tree->node); -} - -static void subsurface_tree_handle_subsurface_map(struct wl_listener *listener, - void *data) { - struct wlr_scene_subsurface_tree *subsurface_tree = - wl_container_of(listener, subsurface_tree, subsurface_map); - - wlr_scene_node_set_enabled(&subsurface_tree->tree->node, true); -} - -static void subsurface_tree_handle_subsurface_unmap(struct wl_listener *listener, - void *data) { - struct wlr_scene_subsurface_tree *subsurface_tree = - wl_container_of(listener, subsurface_tree, subsurface_unmap); - - wlr_scene_node_set_enabled(&subsurface_tree->tree->node, false); -} - -static void subsurface_tree_addon_destroy(struct wlr_addon *addon) { - struct wlr_scene_subsurface_tree *subsurface_tree = - wl_container_of(addon, subsurface_tree, surface_addon); - wlr_scene_node_destroy(&subsurface_tree->tree->node); -} - -static const struct wlr_addon_interface subsurface_tree_addon_impl = { - .name = "wlr_scene_subsurface_tree", - .destroy = subsurface_tree_addon_destroy, -}; - -static struct wlr_scene_subsurface_tree *scene_surface_tree_create( - struct wlr_scene_tree *parent, struct wlr_surface *surface); - -static bool subsurface_tree_create_subsurface( - struct wlr_scene_subsurface_tree *parent, - struct wlr_subsurface *subsurface) { - struct wlr_scene_subsurface_tree *child = scene_surface_tree_create( - parent->tree, subsurface->surface); - if (child == NULL) { - return false; - } - - child->parent = parent; - wlr_scene_node_set_enabled(&child->tree->node, subsurface->mapped); - - wlr_addon_init(&child->surface_addon, &subsurface->surface->addons, - parent, &subsurface_tree_addon_impl); - - child->subsurface_destroy.notify = subsurface_tree_handle_subsurface_destroy; - wl_signal_add(&subsurface->events.destroy, &child->subsurface_destroy); - - child->subsurface_map.notify = subsurface_tree_handle_subsurface_map; - wl_signal_add(&subsurface->events.map, &child->subsurface_map); - - child->subsurface_unmap.notify = subsurface_tree_handle_subsurface_unmap; - wl_signal_add(&subsurface->events.unmap, &child->subsurface_unmap); - - return true; -} - -static void subsurface_tree_handle_surface_new_subsurface( - struct wl_listener *listener, void *data) { - struct wlr_scene_subsurface_tree *subsurface_tree = - wl_container_of(listener, subsurface_tree, surface_new_subsurface); - struct wlr_subsurface *subsurface = data; - if (!subsurface_tree_create_subsurface(subsurface_tree, subsurface)) { - wl_resource_post_no_memory(subsurface->resource); - } -} - -static struct wlr_scene_subsurface_tree *scene_surface_tree_create( - struct wlr_scene_tree *parent, struct wlr_surface *surface) { - struct wlr_scene_subsurface_tree *subsurface_tree = - calloc(1, sizeof(*subsurface_tree)); - if (subsurface_tree == NULL) { - return NULL; - } - - subsurface_tree->tree = wlr_scene_tree_create(parent); - if (subsurface_tree->tree == NULL) { - goto error_surface_tree; - } - - subsurface_tree->scene_surface = - wlr_scene_surface_create(subsurface_tree->tree, surface); - if (subsurface_tree->scene_surface == NULL) { - goto error_scene_surface; - } - - subsurface_tree->surface = surface; - - struct wlr_subsurface *subsurface; - wl_list_for_each(subsurface, &surface->current.subsurfaces_below, - current.link) { - if (!subsurface_tree_create_subsurface(subsurface_tree, subsurface)) { - goto error_scene_surface; - } - } - wl_list_for_each(subsurface, &surface->current.subsurfaces_above, - current.link) { - if (!subsurface_tree_create_subsurface(subsurface_tree, subsurface)) { - goto error_scene_surface; - } - } - - subsurface_tree_reconfigure(subsurface_tree); - - subsurface_tree->tree_destroy.notify = subsurface_tree_handle_tree_destroy; - wl_signal_add(&subsurface_tree->tree->node.events.destroy, - &subsurface_tree->tree_destroy); - - subsurface_tree->surface_destroy.notify = subsurface_tree_handle_surface_destroy; - wl_signal_add(&surface->events.destroy, &subsurface_tree->surface_destroy); - - subsurface_tree->surface_commit.notify = subsurface_tree_handle_surface_commit; - wl_signal_add(&surface->events.commit, &subsurface_tree->surface_commit); - - subsurface_tree->surface_new_subsurface.notify = - subsurface_tree_handle_surface_new_subsurface; - wl_signal_add(&surface->events.new_subsurface, - &subsurface_tree->surface_new_subsurface); - - return subsurface_tree; - -error_scene_surface: - wlr_scene_node_destroy(&subsurface_tree->tree->node); -error_surface_tree: - free(subsurface_tree); - return NULL; -} - -struct wlr_scene_tree *wlr_scene_subsurface_tree_create( - struct wlr_scene_tree *parent, struct wlr_surface *surface) { - struct wlr_scene_subsurface_tree *subsurface_tree = - scene_surface_tree_create(parent, surface); - if (subsurface_tree == NULL) { - return NULL; - } - return subsurface_tree->tree; -} diff --git a/types/scene/surface.c b/types/scene/surface.c deleted file mode 100644 index 553cc42..0000000 --- a/types/scene/surface.c +++ /dev/null @@ -1,211 +0,0 @@ -#include <assert.h> -#include <stdlib.h> -#include <wlr/types/wlr_scene.h> -#include <wlr/types/wlr_presentation_time.h> -#include "types/wlr_scene.h" - -static void handle_scene_buffer_output_enter( - struct wl_listener *listener, void *data) { - struct wlr_scene_surface *surface = - wl_container_of(listener, surface, output_enter); - struct wlr_scene_output *output = data; - - wlr_surface_send_enter(surface->surface, output->output); -} - -static void handle_scene_buffer_output_leave( - struct wl_listener *listener, void *data) { - struct wlr_scene_surface *surface = - wl_container_of(listener, surface, output_leave); - struct wlr_scene_output *output = data; - - wlr_surface_send_leave(surface->surface, output->output); -} - -static void handle_scene_buffer_output_present( - struct wl_listener *listener, void *data) { - struct wlr_scene_surface *surface = - wl_container_of(listener, surface, output_present); - struct wlr_scene_output *scene_output = data; - - if (surface->buffer->primary_output == scene_output) { - struct wlr_scene *root = scene_node_get_root(&surface->buffer->node); - struct wlr_presentation *presentation = root->presentation; - - if (presentation) { - wlr_presentation_surface_sampled_on_output( - presentation, surface->surface, scene_output->output); - } - } -} - -static void handle_scene_buffer_frame_done( - struct wl_listener *listener, void *data) { - struct wlr_scene_surface *surface = - wl_container_of(listener, surface, frame_done); - struct timespec *now = data; - - wlr_surface_send_frame_done(surface->surface, now); -} - -static void scene_surface_handle_surface_destroy( - struct wl_listener *listener, void *data) { - struct wlr_scene_surface *surface = - wl_container_of(listener, surface, surface_destroy); - - wlr_scene_node_destroy(&surface->buffer->node); -} - -// This is used for wlr_scene where it unconditionally locks buffers preventing -// reuse of the existing texture for shm clients. With the usage pattern of -// wlr_scene surface handling, we can mark its locked buffer as safe -// for mutation. -static void client_buffer_mark_next_can_damage(struct wlr_client_buffer *buffer) { - buffer->n_ignore_locks++; -} - -static void scene_buffer_unmark_client_buffer(struct wlr_scene_buffer *scene_buffer) { - if (!scene_buffer->buffer) { - return; - } - - struct wlr_client_buffer *buffer = wlr_client_buffer_get(scene_buffer->buffer); - if (!buffer) { - return; - } - - assert(buffer->n_ignore_locks > 0); - buffer->n_ignore_locks--; -} - -static void set_buffer_with_surface_state(struct wlr_scene_buffer *scene_buffer, - struct wlr_surface *surface) { - struct wlr_surface_state *state = &surface->current; - - wlr_scene_buffer_set_opaque_region(scene_buffer, &surface->opaque_region); - - struct wlr_fbox src_box; - wlr_surface_get_buffer_source_box(surface, &src_box); - wlr_scene_buffer_set_source_box(scene_buffer, &src_box); - - wlr_scene_buffer_set_dest_size(scene_buffer, state->width, state->height); - wlr_scene_buffer_set_transform(scene_buffer, state->transform); - - scene_buffer_unmark_client_buffer(scene_buffer); - - if (surface->buffer) { - client_buffer_mark_next_can_damage(surface->buffer); - - wlr_scene_buffer_set_buffer_with_damage(scene_buffer, - &surface->buffer->base, &surface->buffer_damage); - } else { - wlr_scene_buffer_set_buffer(scene_buffer, NULL); - } -} - -static void handle_scene_surface_surface_commit( - struct wl_listener *listener, void *data) { - struct wlr_scene_surface *surface = - wl_container_of(listener, surface, surface_commit); - struct wlr_scene_buffer *scene_buffer = surface->buffer; - - set_buffer_with_surface_state(scene_buffer, surface->surface); - - // If the surface has requested a frame done event, honour that. The - // frame_callback_list will be populated in this case. We should only - // schedule the frame however if the node is enabled and there is an - // output intersecting, otherwise the frame done events would never reach - // the surface anyway. - int lx, ly; - bool enabled = wlr_scene_node_coords(&scene_buffer->node, &lx, &ly); - - if (!wl_list_empty(&surface->surface->current.frame_callback_list) && - surface->buffer->primary_output != NULL && enabled) { - wlr_output_schedule_frame(surface->buffer->primary_output->output); - } -} - -static bool scene_buffer_point_accepts_input(struct wlr_scene_buffer *scene_buffer, - int sx, int sy) { - struct wlr_scene_surface *scene_surface = - wlr_scene_surface_from_buffer(scene_buffer); - - return wlr_surface_point_accepts_input(scene_surface->surface, sx, sy); -} - -static void surface_addon_destroy(struct wlr_addon *addon) { - struct wlr_scene_surface *surface = wl_container_of(addon, surface, addon); - - scene_buffer_unmark_client_buffer(surface->buffer); - - wlr_addon_finish(&surface->addon); - - wl_list_remove(&surface->output_enter.link); - wl_list_remove(&surface->output_leave.link); - wl_list_remove(&surface->output_present.link); - wl_list_remove(&surface->frame_done.link); - wl_list_remove(&surface->surface_destroy.link); - wl_list_remove(&surface->surface_commit.link); - - free(surface); -} - -static const struct wlr_addon_interface surface_addon_impl = { - .name = "wlr_scene_surface", - .destroy = surface_addon_destroy, -}; - -struct wlr_scene_surface *wlr_scene_surface_from_buffer( - struct wlr_scene_buffer *scene_buffer) { - struct wlr_addon *addon = wlr_addon_find(&scene_buffer->node.addons, - scene_buffer, &surface_addon_impl); - if (!addon) { - return NULL; - } - - struct wlr_scene_surface *surface = wl_container_of(addon, surface, addon); - return surface; -} - -struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_tree *parent, - struct wlr_surface *wlr_surface) { - struct wlr_scene_surface *surface = calloc(1, sizeof(*surface)); - if (surface == NULL) { - return NULL; - } - - struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_create(parent, NULL); - if (!scene_buffer) { - free(surface); - return NULL; - } - - surface->buffer = scene_buffer; - surface->surface = wlr_surface; - scene_buffer->point_accepts_input = scene_buffer_point_accepts_input; - - surface->output_enter.notify = handle_scene_buffer_output_enter; - wl_signal_add(&scene_buffer->events.output_enter, &surface->output_enter); - - surface->output_leave.notify = handle_scene_buffer_output_leave; - wl_signal_add(&scene_buffer->events.output_leave, &surface->output_leave); - - surface->output_present.notify = handle_scene_buffer_output_present; - wl_signal_add(&scene_buffer->events.output_present, &surface->output_present); - - surface->frame_done.notify = handle_scene_buffer_frame_done; - wl_signal_add(&scene_buffer->events.frame_done, &surface->frame_done); - - surface->surface_destroy.notify = scene_surface_handle_surface_destroy; - wl_signal_add(&wlr_surface->events.destroy, &surface->surface_destroy); - - surface->surface_commit.notify = handle_scene_surface_surface_commit; - wl_signal_add(&wlr_surface->events.commit, &surface->surface_commit); - - wlr_addon_init(&surface->addon, &scene_buffer->node.addons, - scene_buffer, &surface_addon_impl); - - set_buffer_with_surface_state(scene_buffer, wlr_surface); - - return surface; -} diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 4a32a6b..8ded6a8 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -4,18 +4,21 @@ #include <string.h> #include <wlr/backend.h> #include <wlr/render/gles2.h> -#include <wlr/types/wlr_xdg_shell.h> #include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_damage_ring.h> #include <wlr/types/wlr_matrix.h> #include <wlr/types/wlr_presentation_time.h> #include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_subcompositor.h> +#include <wlr/types/wlr_xdg_shell.h> #include <wlr/util/log.h> #include <wlr/util/region.h> -#include "render/fx_renderer/fx_renderer.h" +#include <wlr/render/swapchain.h> + +#include "render/pass.h" #include "types/fx/shadow_data.h" #include "types/wlr_buffer.h" +#include "types/wlr_output.h" #include "types/wlr_scene.h" #include "util/array.h" #include "util/env.h" @@ -23,14 +26,13 @@ #define HIGHLIGHT_DAMAGE_FADEOUT_TIME 250 -static struct wlr_scene_tree *scene_tree_from_node(struct wlr_scene_node *node) { +struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *node) { assert(node->type == WLR_SCENE_NODE_TREE); struct wlr_scene_tree *tree = wl_container_of(node, tree, node); return tree; } -static struct wlr_scene_rect *scene_rect_from_node( - struct wlr_scene_node *node) { +struct wlr_scene_rect *wlr_scene_rect_from_node(struct wlr_scene_node *node) { assert(node->type == WLR_SCENE_NODE_RECT); struct wlr_scene_rect *rect = wl_container_of(node, rect, node); return rect; @@ -46,7 +48,7 @@ struct wlr_scene_buffer *wlr_scene_buffer_from_node( struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node) { struct wlr_scene_tree *tree; if (node->type == WLR_SCENE_NODE_TREE) { - tree = scene_tree_from_node(node); + tree = wlr_scene_tree_from_node(node); } else { tree = node->parent; } @@ -54,15 +56,17 @@ struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node) { while (tree->node.parent != NULL) { tree = tree->node.parent; } - return (struct wlr_scene *)tree; + struct wlr_scene *scene = wl_container_of(tree, scene, tree); + return scene; } static void scene_node_init(struct wlr_scene_node *node, enum wlr_scene_node_type type, struct wlr_scene_tree *parent) { - memset(node, 0, sizeof(*node)); - node->type = type; - node->parent = parent; - node->enabled = true; + *node = (struct wlr_scene_node){ + .type = type, + .parent = parent, + .enabled = true, + }; wl_list_init(&node->link); @@ -114,7 +118,7 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { wlr_buffer_unlock(scene_buffer->buffer); pixman_region32_fini(&scene_buffer->opaque_region); } else if (node->type == WLR_SCENE_NODE_TREE) { - struct wlr_scene_tree *scene_tree = scene_tree_from_node(node); + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); if (scene_tree == &scene->tree) { assert(!node->parent); @@ -124,6 +128,7 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { } wl_list_remove(&scene->presentation_destroy.link); + wl_list_remove(&scene->linux_dmabuf_v1_destroy.link); } else { assert(node->parent); } @@ -142,13 +147,13 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { static void scene_tree_init(struct wlr_scene_tree *tree, struct wlr_scene_tree *parent) { - memset(tree, 0, sizeof(*tree)); + *tree = (struct wlr_scene_tree){0}; scene_node_init(&tree->node, WLR_SCENE_NODE_TREE, parent); wl_list_init(&tree->children); } struct wlr_scene *wlr_scene_create(void) { - struct wlr_scene *scene = calloc(1, sizeof(struct wlr_scene)); + struct wlr_scene *scene = calloc(1, sizeof(*scene)); if (scene == NULL) { return NULL; } @@ -157,6 +162,7 @@ struct wlr_scene *wlr_scene_create(void) { wl_list_init(&scene->outputs); wl_list_init(&scene->presentation_destroy.link); + wl_list_init(&scene->linux_dmabuf_v1_destroy.link); const char *debug_damage_options[] = { "none", @@ -175,7 +181,7 @@ struct wlr_scene *wlr_scene_create(void) { struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent) { assert(parent); - struct wlr_scene_tree *tree = calloc(1, sizeof(struct wlr_scene_tree)); + struct wlr_scene_tree *tree = calloc(1, sizeof(*tree)); if (tree == NULL) { return NULL; } @@ -197,7 +203,7 @@ static bool _scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box switch (node->type) { case WLR_SCENE_NODE_TREE:; - struct wlr_scene_tree *scene_tree = scene_tree_from_node(node); + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); struct wlr_scene_node *child; wl_list_for_each_reverse(child, &scene_tree->children, link) { if (_scene_nodes_in_box(child, box, iterator, user_data, lx + child->x, ly + child->y)) { @@ -230,8 +236,11 @@ static bool scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box, static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y, pixman_region32_t *opaque) { + int width, height; + scene_node_get_size(node, &width, &height); + if (node->type == WLR_SCENE_NODE_RECT) { - struct wlr_scene_rect *scene_rect = scene_rect_from_node(node); + struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node); if (scene_rect->color[3] != 1) { return; } @@ -242,20 +251,22 @@ static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y, return; } - // Buffer is translucent - if (scene_buffer->opacity != 1 || scene_buffer->corner_radius > 0) { + if (scene_buffer->opacity != 1) { + return; + } + + if (scene_buffer->corner_radius > 0) { return; } if (!buffer_is_opaque(scene_buffer->buffer)) { pixman_region32_copy(opaque, &scene_buffer->opaque_region); + pixman_region32_intersect_rect(opaque, opaque, 0, 0, width, height); pixman_region32_translate(opaque, x, y); return; } } - int width, height; - scene_node_get_size(node, &width, &height); pixman_region32_fini(opaque); pixman_region32_init_rect(opaque, x, y, width, height); } @@ -287,6 +298,28 @@ static void scale_output_damage(pixman_region32_t *damage, float scale) { } } +struct render_data { + enum wl_output_transform transform; + float scale; + struct wlr_box logical; + int trans_width, trans_height; + + struct wlr_scene_output *output; + + struct fx_gles_render_pass *render_pass; + pixman_region32_t damage; +}; + +static void transform_output_damage(pixman_region32_t *damage, const struct render_data *data) { + enum wl_output_transform transform = wlr_output_transform_invert(data->transform); + wlr_region_transform(damage, damage, transform, data->trans_width, data->trans_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); + wlr_box_transform(box, box, transform, data->trans_width, data->trans_height); +} + static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *damage) { if (!pixman_region32_not_empty(damage)) { return; @@ -308,7 +341,8 @@ static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *dam } static void update_node_update_outputs(struct wlr_scene_node *node, - struct wl_list *outputs, struct wlr_scene_output *ignore) { + struct wl_list *outputs, struct wlr_scene_output *ignore, + struct wlr_scene_output *force) { if (node->type != WLR_SCENE_NODE_BUFFER) { return; } @@ -316,8 +350,10 @@ static void update_node_update_outputs(struct wlr_scene_node *node, struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); uint32_t largest_overlap = 0; + struct wlr_scene_output *old_primary_output = scene_buffer->primary_output; scene_buffer->primary_output = NULL; + size_t count = 0; uint64_t active_outputs = 0; // let's update the outputs in two steps: @@ -356,11 +392,17 @@ static void update_node_update_outputs(struct wlr_scene_node *node, } active_outputs |= 1ull << scene_output->index; + count++; } pixman_region32_fini(&intersection); } + if (old_primary_output != scene_buffer->primary_output) { + scene_buffer->prev_feedback_options = + (struct wlr_linux_dmabuf_feedback_v1_init_options){0}; + } + uint64_t old_active = scene_buffer->active_outputs; scene_buffer->active_outputs = active_outputs; @@ -379,6 +421,31 @@ static void update_node_update_outputs(struct wlr_scene_node *node, // if there are active outputs on this node, we should always have a primary // output assert(!scene_buffer->active_outputs || scene_buffer->primary_output); + + // Skip output update event if nothing was updated + if (old_active == active_outputs && + (!force || ((1ull << force->index) & ~active_outputs)) && + old_primary_output == scene_buffer->primary_output) { + return; + } + + struct wlr_scene_output *outputs_array[64]; + struct wlr_scene_outputs_update_event event = { + .active = outputs_array, + .size = count, + }; + + size_t i = 0; + wl_list_for_each(scene_output, outputs, link) { + if (~active_outputs & (1ull << scene_output->index)) { + continue; + } + + assert(i < count); + outputs_array[i++] = scene_output; + } + + wl_signal_emit_mutable(&scene_buffer->events.outputs_update, &event); } static bool scene_node_update_iterator(struct wlr_scene_node *node, @@ -404,13 +471,14 @@ static bool scene_node_update_iterator(struct wlr_scene_node *node, // Expand the nodes visible region by the shadow size if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); - struct shadow_data *data = &buffer->shadow_data; - if (scene_buffer_has_shadow(data)) { - wlr_region_expand(&node->visible, &node->visible, data->blur_sigma); + struct shadow_data *shadow_data = &buffer->shadow_data; + if (scene_buffer_has_shadow(shadow_data)) { + wlr_region_expand(&node->visible, &node->visible, shadow_data->blur_sigma); + wlr_region_expand(data->visible, data->visible, shadow_data->blur_sigma); } } - update_node_update_outputs(node, data->outputs, NULL); + update_node_update_outputs(node, data->outputs, NULL, NULL); return false; } @@ -422,7 +490,7 @@ static void scene_node_visibility(struct wlr_scene_node *node, } if (node->type == WLR_SCENE_NODE_TREE) { - struct wlr_scene_tree *scene_tree = scene_tree_from_node(node); + 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) { scene_node_visibility(child, visible); @@ -440,7 +508,7 @@ static void scene_node_bounds(struct wlr_scene_node *node, } if (node->type == WLR_SCENE_NODE_TREE) { - struct wlr_scene_tree *scene_tree = scene_tree_from_node(node); + 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) { scene_node_bounds(child, x + child->x, y + child->y, visible); @@ -517,8 +585,7 @@ 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]) { - struct wlr_scene_rect *scene_rect = - calloc(1, sizeof(struct wlr_scene_rect)); + struct wlr_scene_rect *scene_rect = calloc(1, sizeof(*scene_rect)); if (scene_rect == NULL) { return NULL; } @@ -566,12 +633,12 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, scene_buffer->buffer = wlr_buffer_lock(buffer); } + wl_signal_init(&scene_buffer->events.outputs_update); wl_signal_init(&scene_buffer->events.output_enter); wl_signal_init(&scene_buffer->events.output_leave); - wl_signal_init(&scene_buffer->events.output_present); + wl_signal_init(&scene_buffer->events.output_sample); wl_signal_init(&scene_buffer->events.frame_done); pixman_region32_init(&scene_buffer->opaque_region); - scene_buffer->opacity = 1; scene_buffer->corner_radius = 0; scene_buffer->shadow_data = shadow_data_get_default(); @@ -582,14 +649,13 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, } void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer, - struct wlr_buffer *buffer, pixman_region32_t *damage) { + struct wlr_buffer *buffer, const pixman_region32_t *damage) { // 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); bool update = false; - wlr_buffer_unlock(scene_buffer->buffer); wlr_texture_destroy(scene_buffer->texture); scene_buffer->texture = NULL; @@ -603,8 +669,10 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff (scene_buffer->buffer->width != buffer->width || scene_buffer->buffer->height != buffer->height)); + wlr_buffer_unlock(scene_buffer->buffer); scene_buffer->buffer = wlr_buffer_lock(buffer); } else { + wlr_buffer_unlock(scene_buffer->buffer); update = true; scene_buffer->buffer = NULL; } @@ -694,8 +762,8 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff pixman_region32_fini(&cull_region); pixman_region32_translate(&output_damage, - (lx - scene_output->x) * output_scale, - (ly - scene_output->y) * output_scale); + (int)round((lx - scene_output->x) * output_scale), + (int)round((ly - scene_output->y) * output_scale)); if (wlr_damage_ring_add(&scene_output->damage_ring, &output_damage)) { wlr_output_schedule_frame(scene_output->output); } @@ -712,27 +780,35 @@ void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, } void wlr_scene_buffer_set_opaque_region(struct wlr_scene_buffer *scene_buffer, - pixman_region32_t *region) { + const pixman_region32_t *region) { if (pixman_region32_equal(&scene_buffer->opaque_region, region)) { return; } pixman_region32_copy(&scene_buffer->opaque_region, region); - scene_node_update(&scene_buffer->node, NULL); + + int x, y; + if (!wlr_scene_node_coords(&scene_buffer->node, &x, &y)) { + return; + } + + pixman_region32_t update_region; + pixman_region32_init(&update_region); + scene_node_bounds(&scene_buffer->node, x, y, &update_region); + scene_update_region(scene_node_get_root(&scene_buffer->node), &update_region); + pixman_region32_fini(&update_region); } void wlr_scene_buffer_set_source_box(struct wlr_scene_buffer *scene_buffer, const struct wlr_fbox *box) { - struct wlr_fbox *cur = &scene_buffer->src_box; - if ((wlr_fbox_empty(box) && wlr_fbox_empty(cur)) || - (box != NULL && wlr_fbox_equal(cur, box))) { + if (wlr_fbox_equal(&scene_buffer->src_box, box)) { return; } if (box != NULL) { - memcpy(cur, box, sizeof(*box)); + scene_buffer->src_box = *box; } else { - memset(cur, 0, sizeof(*cur)); + scene_buffer->src_box = (struct wlr_fbox){0}; } scene_node_update(&scene_buffer->node, NULL); @@ -776,6 +852,16 @@ void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, scene_node_update(&scene_buffer->node, NULL); } +void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, + enum wlr_scale_filter_mode filter_mode) { + if (scene_buffer->filter_mode == filter_mode) { + return; + } + + scene_buffer->filter_mode = filter_mode; + scene_node_update(&scene_buffer->node, NULL); +} + void wlr_scene_buffer_set_corner_radius(struct wlr_scene_buffer *scene_buffer, int radii) { if (scene_buffer->corner_radius == radii) { @@ -791,7 +877,10 @@ void wlr_scene_buffer_set_shadow_data(struct wlr_scene_buffer *scene_buffer, struct shadow_data *buff_data = &scene_buffer->shadow_data; if (buff_data->enabled == shadow_data.enabled && buff_data->blur_sigma == shadow_data.blur_sigma && - buff_data->color && shadow_data.color) { + buff_data->color.r && shadow_data.color.r && + buff_data->color.g && shadow_data.color.g && + buff_data->color.b && shadow_data.color.b && + buff_data->color.a && shadow_data.color.a) { return; } @@ -826,7 +915,7 @@ static void scene_node_get_size(struct wlr_scene_node *node, case WLR_SCENE_NODE_TREE: return; case WLR_SCENE_NODE_RECT:; - struct wlr_scene_rect *scene_rect = scene_rect_from_node(node); + struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node); *width = scene_rect->width; *height = scene_rect->height; break; @@ -995,7 +1084,7 @@ static void scene_node_for_each_scene_buffer(struct wlr_scene_node *node, struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); user_iterator(scene_buffer, lx, ly, user_data); } else if (node->type == WLR_SCENE_NODE_TREE) { - struct wlr_scene_tree *scene_tree = scene_tree_from_node(node); + 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) { scene_node_for_each_scene_buffer(child, lx, ly, user_iterator, user_data); @@ -1025,7 +1114,7 @@ static bool scene_node_at_iterator(struct wlr_scene_node *node, struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); if (scene_buffer->point_accepts_input && - !scene_buffer->point_accepts_input(scene_buffer, rx, ry)) { + !scene_buffer->point_accepts_input(scene_buffer, &rx, &ry)) { return false; } } @@ -1063,208 +1152,175 @@ struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node, return NULL; } -static void scissor_output(struct wlr_output *output, pixman_box32_t *rect) { - struct wlr_box box = { - .x = rect->x1, - .y = rect->y1, - .width = rect->x2 - rect->x1, - .height = rect->y2 - rect->y1, - }; - - int ow, oh; - wlr_output_transformed_resolution(output, &ow, &oh); - - enum wl_output_transform transform = - wlr_output_transform_invert(output->transform); - wlr_box_transform(&box, &box, transform, ow, oh); - - fx_renderer_scissor(&box); -} - -static void render_rect(struct fx_renderer *fx_renderer, struct wlr_output *output, - pixman_region32_t *damage, const float color[static 4], - const struct wlr_box *box, const float matrix[static 9]) { - assert(fx_renderer); - - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); - for (int i = 0; i < nrects; ++i) { - scissor_output(output, &rects[i]); - fx_render_rect(fx_renderer, box, color, matrix); - } -} - -static void render_texture(struct fx_renderer *fx_renderer, struct wlr_output *output, - pixman_region32_t *damage, struct wlr_texture *texture, - const struct wlr_fbox *src_box, const struct wlr_box *dst_box, - const float matrix[static 9], float opacity, int corner_radius) { - assert(fx_renderer); - - struct wlr_fbox default_src_box = {0}; - if (wlr_fbox_empty(src_box)) { - default_src_box.width = texture->width; - default_src_box.height = texture->height; - src_box = &default_src_box; - } - - // ensure the box is updated as per the output orientation - struct wlr_box transformed_box; - int width, height; - wlr_output_transformed_resolution(output, &width, &height); - wlr_box_transform(&transformed_box, dst_box, - wlr_output_transform_invert(output->transform), width, height); - - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects); - for (int i = 0; i < nrects; ++i) { - scissor_output(output, &rects[i]); - - fx_render_subtexture_with_matrix(fx_renderer, texture, src_box, - &transformed_box, matrix, opacity, corner_radius); +// Some surfaces (mostly GTK 4) decorate their windows with shadows +// which extends the node size past the actual window size. This gets +// the actual surface geometry, mostly ignoring CSD decorations +// but only if we need to. +static void clip_xdg(struct wlr_scene_node *node, + pixman_region32_t *clip, struct wlr_box *dst_box, + int x, int y, float scale) { + struct wlr_scene_buffer *scene_buffer = NULL; + switch (node->type) { + default: + return; + case WLR_SCENE_NODE_BUFFER: + scene_buffer = wlr_scene_buffer_from_node(node); + break; } -} - -static void render_box_shadow(struct fx_renderer *fx_renderer, - struct wlr_output *output, pixman_region32_t *surface_damage, - const struct wlr_box *surface_box, int corner_radius, - struct shadow_data *shadow_data) { - // don't damage area behind window since we dont render it anyway - pixman_region32_t inner_region; - pixman_region32_init(&inner_region); - pixman_region32_union_rect(&inner_region, &inner_region, - surface_box->x + corner_radius * 0.5, - surface_box->y + corner_radius * 0.5, - surface_box->width - corner_radius, - surface_box->height - corner_radius); - pixman_region32_intersect(&inner_region, &inner_region, surface_damage); - pixman_region32_t damage; - pixman_region32_init(&damage); - pixman_region32_subtract(&damage, surface_damage, &inner_region); - if (!pixman_region32_not_empty(&damage)) { - goto damage_finish; + if (!scene_buffer || (scene_buffer->corner_radius == 0 && + !scene_buffer_has_shadow(&scene_buffer->shadow_data))) { + return; } - struct wlr_box shadow_box = { - .x = surface_box->x - shadow_data->blur_sigma, - .y = surface_box->y - shadow_data->blur_sigma, - .width = surface_box->width + 2 * shadow_data->blur_sigma, - .height = surface_box->height + 2 * shadow_data->blur_sigma, - }; - float matrix[9]; - wlr_matrix_project_box(matrix, &shadow_box, WL_OUTPUT_TRANSFORM_NORMAL, 0, - output->transform_matrix); + struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(scene_buffer); + struct wlr_xdg_surface *xdg_surface = NULL; + if (scene_surface && + (xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface))) { + struct wlr_box geometry; + wlr_xdg_surface_get_geometry(xdg_surface, &geometry); + scale_box(&geometry, scale); - // ensure the box is updated as per the output orientation - struct wlr_box transformed_box; - int width, height; - wlr_output_transformed_resolution(output, &width, &height); - wlr_box_transform(&transformed_box, &shadow_box, - wlr_output_transform_invert(output->transform), width, height); + if (dst_box->width > geometry.width) { + dst_box->width = geometry.width; + dst_box->x = geometry.x + x; + } + if (dst_box->height > geometry.height) { + dst_box->height = geometry.height; + dst_box->y = geometry.y + y; + } - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); - for (int i = 0; i < nrects; ++i) { - scissor_output(output, &rects[i]); - fx_render_box_shadow(fx_renderer, &transformed_box, surface_box, matrix, - corner_radius, shadow_data); + pixman_region32_intersect_rect(clip, clip, + dst_box->x, dst_box->y, + dst_box->width, dst_box->height); } - -damage_finish: - pixman_region32_fini(&damage); - pixman_region32_fini(&inner_region); } -static void scene_node_render(struct fx_renderer *fx_renderer, struct wlr_scene_node *node, - struct wlr_scene_output *scene_output, pixman_region32_t *damage) { +struct render_list_entry { + struct wlr_scene_node *node; + bool sent_dmabuf_feedback; int x, y; - wlr_scene_node_coords(node, &x, &y); - x -= scene_output->x; - y -= scene_output->y; +}; - struct wlr_output *output = scene_output->output; +static void scene_entry_render(struct render_list_entry *entry, const struct render_data *data) { + struct wlr_scene_node *node = entry->node; pixman_region32_t render_region; pixman_region32_init(&render_region); pixman_region32_copy(&render_region, &node->visible); - pixman_region32_translate(&render_region, -scene_output->x, -scene_output->y); - scale_output_damage(&render_region, output->scale); - pixman_region32_intersect(&render_region, &render_region, damage); + pixman_region32_translate(&render_region, -data->logical.x, -data->logical.y); + scale_output_damage(&render_region, data->scale); + pixman_region32_intersect(&render_region, &render_region, &data->damage); if (!pixman_region32_not_empty(&render_region)) { pixman_region32_fini(&render_region); return; } struct wlr_box dst_box = { - .x = x, - .y = y, + .x = entry->x - data->logical.x, + .y = entry->y - data->logical.y, }; scene_node_get_size(node, &dst_box.width, &dst_box.height); - scale_box(&dst_box, output->scale); + scale_box(&dst_box, data->scale); + + pixman_region32_t opaque; + pixman_region32_init(&opaque); + scene_node_opaque_region(node, dst_box.x, dst_box.y, &opaque); + scale_output_damage(&opaque, data->scale); + pixman_region32_subtract(&opaque, &render_region, &opaque); + + struct wlr_box xdg_box = dst_box; + // Tries to clip + clip_xdg(node, &render_region, &xdg_box, dst_box.x, dst_box.y, data->scale); + + transform_output_box(&dst_box, data); + transform_output_box(&xdg_box, data); + transform_output_damage(&render_region, data); - struct wlr_texture *texture; - float matrix[9]; - enum wl_output_transform transform; switch (node->type) { case WLR_SCENE_NODE_TREE: assert(false); break; case WLR_SCENE_NODE_RECT:; - struct wlr_scene_rect *scene_rect = scene_rect_from_node(node); - - render_rect(fx_renderer, output, &render_region, scene_rect->color, &dst_box, - output->transform_matrix); + struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node); + + struct fx_render_rect_options rect_options = { + .base = { + .box = dst_box, + .color = { + .r = scene_rect->color[0], + .g = scene_rect->color[1], + .b = scene_rect->color[2], + .a = scene_rect->color[3], + }, + .clip = &render_region, + }, + .scale = data->scale, + }; + fx_render_pass_add_rect(data->render_pass, &rect_options); break; case WLR_SCENE_NODE_BUFFER:; struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); assert(scene_buffer->buffer); - struct wlr_renderer *renderer = output->renderer; - texture = scene_buffer_get_texture(scene_buffer, renderer); - - transform = wlr_output_transform_invert(scene_buffer->transform); - wlr_matrix_project_box(matrix, &dst_box, transform, 0.0, - output->transform_matrix); - - // Some surfaces (mostly GTK 4) decorate their windows with shadows - // which extends the node size past the actual window size. This gets - // the actual surface geometry, mostly ignoring CSD decorations - // but only if we need to. - if (scene_buffer->corner_radius != 0 || - scene_buffer_has_shadow(&scene_buffer->shadow_data)) { - struct wlr_scene_surface *scene_surface = NULL; - if ((scene_surface = wlr_scene_surface_from_buffer(scene_buffer)) && - wlr_surface_is_xdg_surface(scene_surface->surface)) { - struct wlr_xdg_surface *xdg_surface = - wlr_xdg_surface_from_wlr_surface(scene_surface->surface); - - struct wlr_box geometry; - wlr_xdg_surface_get_geometry(xdg_surface, &geometry); - dst_box.width = fmin(dst_box.width, geometry.width); - dst_box.height = fmin(dst_box.height, geometry.height); - dst_box.x = fmax(dst_box.x, geometry.x + x); - dst_box.y = fmax(dst_box.y, geometry.y + y); - } + struct wlr_texture *texture = scene_buffer_get_texture(scene_buffer, + data->output->output->renderer); + if (texture == NULL) { + break; } + enum wl_output_transform transform = + wlr_output_transform_invert(scene_buffer->transform); + transform = wlr_output_transform_compose(transform, data->transform); + // Shadow if (scene_buffer_has_shadow(&scene_buffer->shadow_data)) { // TODO: Compensate for SSD borders here - render_box_shadow(fx_renderer, output, &render_region, &dst_box, - scene_buffer->corner_radius, &scene_buffer->shadow_data); + pixman_region32_t shadow_clip; + pixman_region32_init(&shadow_clip); + // Extend the size of the clip box + wlr_region_expand(&shadow_clip, &render_region, + scene_buffer->shadow_data.blur_sigma * data->scale); + struct fx_render_rect_options shadow_options = { + .base = { + .box = xdg_box, + .clip = &shadow_clip, + }, + .scale = data->scale, + }; + fx_render_pass_add_box_shadow(data->render_pass, &shadow_options, + scene_buffer->corner_radius * data->scale, + &scene_buffer->shadow_data); + pixman_region32_fini(&shadow_clip); } - // Clip the damage to the dst_box before rendering the texture - pixman_region32_intersect_rect(&render_region, &render_region, - dst_box.x, dst_box.y, dst_box.width, dst_box.height); - - render_texture(fx_renderer, output, &render_region, texture, &scene_buffer->src_box, - &dst_box, matrix, scene_buffer->opacity, scene_buffer->corner_radius); + struct fx_render_texture_options tex_options = { + .base = (struct wlr_render_texture_options){ + .texture = texture, + .src_box = scene_buffer->src_box, + .dst_box = dst_box, + .transform = transform, + .clip = &render_region, + .alpha = &scene_buffer->opacity, + .filter_mode = scene_buffer->filter_mode, + .blend_mode = pixman_region32_not_empty(&opaque) ? + WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, + }, + .scale = data->scale, + .clip_box = &xdg_box, + .corner_radius = scene_buffer->corner_radius * data->scale, + }; + fx_render_pass_add_texture(data->render_pass, &tex_options); - wl_signal_emit_mutable(&scene_buffer->events.output_present, scene_output); + struct wlr_scene_output_sample_event sample_event = { + .output = data->output, + .direct_scanout = false, + }; + wl_signal_emit_mutable(&scene_buffer->events.output_sample, &sample_event); break; } + pixman_region32_fini(&opaque); pixman_region32_fini(&render_region); } @@ -1285,6 +1341,23 @@ void wlr_scene_set_presentation(struct wlr_scene *scene, wl_signal_add(&presentation->events.destroy, &scene->presentation_destroy); } +static void scene_handle_linux_dmabuf_v1_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene *scene = + wl_container_of(listener, scene, linux_dmabuf_v1_destroy); + wl_list_remove(&scene->linux_dmabuf_v1_destroy.link); + wl_list_init(&scene->linux_dmabuf_v1_destroy.link); + scene->linux_dmabuf_v1 = NULL; +} + +void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene, + struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1) { + assert(scene->linux_dmabuf_v1 == NULL); + scene->linux_dmabuf_v1 = linux_dmabuf_v1; + scene->linux_dmabuf_v1_destroy.notify = scene_handle_linux_dmabuf_v1_destroy; + wl_signal_add(&linux_dmabuf_v1->events.destroy, &scene->linux_dmabuf_v1_destroy); +} + static void scene_output_handle_destroy(struct wlr_addon *addon) { struct wlr_scene_output *scene_output = wl_container_of(addon, scene_output, addon); @@ -1296,50 +1369,47 @@ static const struct wlr_addon_interface output_addon_impl = { .destroy = scene_output_handle_destroy, }; - static void scene_node_output_update(struct wlr_scene_node *node, - struct wl_list *outputs, struct wlr_scene_output *ignore) { + struct wl_list *outputs, struct wlr_scene_output *ignore, + struct wlr_scene_output *force) { if (node->type == WLR_SCENE_NODE_TREE) { - struct wlr_scene_tree *scene_tree = scene_tree_from_node(node); + 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) { - scene_node_output_update(child, outputs, ignore); + scene_node_output_update(child, outputs, ignore, force); } return; } - update_node_update_outputs(node, outputs, ignore); + update_node_update_outputs(node, outputs, ignore, force); } -static void scene_output_update_geometry(struct wlr_scene_output *scene_output) { - int width, height; - wlr_output_transformed_resolution(scene_output->output, &width, &height); - wlr_damage_ring_set_bounds(&scene_output->damage_ring, width, height); +static void scene_output_update_geometry(struct wlr_scene_output *scene_output, + bool force_update) { + wlr_damage_ring_add_whole(&scene_output->damage_ring); wlr_output_schedule_frame(scene_output->output); scene_node_output_update(&scene_output->scene->tree.node, - &scene_output->scene->outputs, NULL); + &scene_output->scene->outputs, NULL, force_update ? scene_output : NULL); } static void scene_output_handle_commit(struct wl_listener *listener, void *data) { struct wlr_scene_output *scene_output = wl_container_of(listener, scene_output, output_commit); struct wlr_output_event_commit *event = data; + const struct wlr_output_state *state = event->state; + + bool force_update = state->committed & ( + WLR_OUTPUT_STATE_TRANSFORM | + WLR_OUTPUT_STATE_SCALE | + WLR_OUTPUT_STATE_SUBPIXEL); - if (event->committed & (WLR_OUTPUT_STATE_MODE | - WLR_OUTPUT_STATE_TRANSFORM | - WLR_OUTPUT_STATE_SCALE | + if (force_update || state->committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ENABLED)) { - scene_output_update_geometry(scene_output); + scene_output_update_geometry(scene_output, force_update); } } -static void scene_output_handle_mode(struct wl_listener *listener, void *data) { - struct wlr_scene_output *scene_output = wl_container_of(listener, - scene_output, output_mode); - scene_output_update_geometry(scene_output); -} - 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); @@ -1366,10 +1436,6 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, scene_output->scene = scene; wlr_addon_init(&scene_output->addon, &output->addons, scene, &output_addon_impl); - // Init FX Renderer - struct wlr_egl *egl = wlr_gles2_renderer_get_egl(output->renderer); - fx_renderer_init_addon(egl, &output->addons, scene); - wlr_damage_ring_init(&scene_output->damage_ring); wl_list_init(&scene_output->damage_highlight_regions); @@ -1395,16 +1461,13 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, scene_output->output_commit.notify = scene_output_handle_commit; wl_signal_add(&output->events.commit, &scene_output->output_commit); - scene_output->output_mode.notify = scene_output_handle_mode; - wl_signal_add(&output->events.mode, &scene_output->output_mode); - scene_output->output_damage.notify = scene_output_handle_damage; wl_signal_add(&output->events.damage, &scene_output->output_damage); scene_output->output_needs_frame.notify = scene_output_handle_needs_frame; wl_signal_add(&output->events.needs_frame, &scene_output->output_needs_frame); - scene_output_update_geometry(scene_output); + scene_output_update_geometry(scene_output, false); return scene_output; } @@ -1423,7 +1486,7 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { wl_signal_emit_mutable(&scene_output->events.destroy, NULL); scene_node_output_update(&scene_output->scene->tree.node, - &scene_output->scene->outputs, scene_output); + &scene_output->scene->outputs, scene_output, NULL); struct highlight_region *damage, *tmp_damage; wl_list_for_each_safe(damage, tmp_damage, &scene_output->damage_highlight_regions, link) { @@ -1434,7 +1497,6 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { wlr_damage_ring_finish(&scene_output->damage_ring); wl_list_remove(&scene_output->link); wl_list_remove(&scene_output->output_commit.link); - wl_list_remove(&scene_output->output_mode.link); wl_list_remove(&scene_output->output_damage.link); wl_list_remove(&scene_output->output_needs_frame.link); @@ -1463,14 +1525,14 @@ void wlr_scene_output_set_position(struct wlr_scene_output *scene_output, scene_output->x = lx; scene_output->y = ly; - scene_output_update_geometry(scene_output); + scene_output_update_geometry(scene_output, false); } static bool scene_node_invisible(struct wlr_scene_node *node) { if (node->type == WLR_SCENE_NODE_TREE) { return true; } else if (node->type == WLR_SCENE_NODE_RECT) { - struct wlr_scene_rect *rect = scene_rect_from_node(node); + struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node); return rect->color[3] == 0.f; } else if (node->type == WLR_SCENE_NODE_BUFFER) { @@ -1500,7 +1562,7 @@ static bool construct_render_list_iterator(struct wlr_scene_node *node, // If we see a black rect, we can ignore rendering everything under the rect // and even the rect itself. if (node->type == WLR_SCENE_NODE_RECT && data->calculate_visibility) { - struct wlr_scene_rect *rect = scene_rect_from_node(node); + struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node); float *black = (float[4]){ 0.f, 0.f, 0.f, 1.f }; if (memcmp(rect->color, black, sizeof(float) * 4) == 0) { @@ -1520,16 +1582,66 @@ static bool construct_render_list_iterator(struct wlr_scene_node *node, pixman_region32_fini(&intersection); - struct wlr_scene_node **entry = wl_array_add(data->render_list, - sizeof(struct wlr_scene_node *)); - if (entry) { - *entry = node; + struct render_list_entry *entry = wl_array_add(data->render_list, sizeof(*entry)); + if (!entry) { + return false; } + + *entry = (struct render_list_entry){ + .node = node, + .x = lx, + .y = ly, + }; + return false; } -static bool scene_node_try_direct_scanout(struct wlr_scene_node *node, - struct wlr_scene_output *scene_output, struct wlr_box *box) { +static void output_state_apply_damage(const struct render_data *data, + struct wlr_output_state *state) { + pixman_region32_t frame_damage; + pixman_region32_init(&frame_damage); + pixman_region32_copy(&frame_damage, &data->output->damage_ring.current); + transform_output_damage(&frame_damage, data); + wlr_output_state_set_damage(state, &frame_damage); + pixman_region32_fini(&frame_damage); +} + +static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene, + struct wlr_scene_buffer *scene_buffer, + const struct wlr_linux_dmabuf_feedback_v1_init_options *options) { + if (!scene->linux_dmabuf_v1) { + return; + } + + struct wlr_scene_surface *surface = wlr_scene_surface_try_from_buffer(scene_buffer); + if (!surface) { + return; + } + + // compare to the previous options so that we don't send + // duplicate feedback events. + if (memcmp(options, &scene_buffer->prev_feedback_options, sizeof(*options)) == 0) { + return; + } + + scene_buffer->prev_feedback_options = *options; + + struct wlr_linux_dmabuf_feedback_v1 feedback = {0}; + if (!wlr_linux_dmabuf_feedback_v1_init_with_options(&feedback, options)) { + return; + } + + wlr_linux_dmabuf_v1_set_surface_feedback(scene->linux_dmabuf_v1, + surface->surface, &feedback); + + 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) { + struct wlr_scene_output *scene_output = data->output; + struct wlr_scene_node *node = entry->node; + if (!scene_output->scene->direct_scanout) { return false; } @@ -1545,8 +1657,18 @@ static bool scene_node_try_direct_scanout(struct wlr_scene_node *node, return false; } + 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; + } + + if (!wlr_output_is_direct_scanout_allowed(scene_output->output)) { + return false; + } + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); - struct wlr_output *output = scene_output->output; struct wlr_fbox default_box = {0}; if (buffer->transform & WL_OUTPUT_TRANSFORM_90) { @@ -1562,75 +1684,170 @@ static bool scene_node_try_direct_scanout(struct wlr_scene_node *node, return false; } - if (buffer->transform != output->transform) { + if (buffer->transform != data->transform) { return false; } - struct wlr_box node_box; - wlr_scene_node_coords(node, &node_box.x, &node_box.y); + 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(box, &node_box)) { + if (!wlr_box_equal(&data->logical, &node_box)) { return false; } - wlr_output_attach_buffer(output, buffer->buffer); - if (!wlr_output_test(output)) { - wlr_output_rollback(output); + if (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; + } + + wlr_output_state_set_buffer(&pending, buffer->buffer); + output_state_apply_damage(data, &pending); + + if (!wlr_output_test_state(scene_output->output, &pending)) { + wlr_output_state_finish(&pending); return false; } - return wlr_output_commit(output); + wlr_output_state_copy(state, &pending); + wlr_output_state_finish(&pending); + + struct wlr_scene_output_sample_event sample_event = { + .output = scene_output, + .direct_scanout = true, + }; + wl_signal_emit_mutable(&buffer->events.output_sample, &sample_event); + return true; } -bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { +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->damage_ring.current)) { + return true; + } + + bool ok = false; + struct wlr_output_state state; + wlr_output_state_init(&state); + if (!wlr_scene_output_build_state(scene_output, &state, options)) { + goto out; + } + + ok = wlr_output_commit_state(scene_output->output, &state); + if (!ok) { + goto out; + } + + wlr_damage_ring_rotate(&scene_output->damage_ring); + +out: + wlr_output_state_finish(&state); + return ok; +} + +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}; + if (!options) { + options = &default_options; + } + struct wlr_scene_timer *timer = options->timer; + struct timespec start_time; + if (timer) { + clock_gettime(CLOCK_MONOTONIC, &start_time); + wlr_scene_timer_finish(timer); + *timer = (struct wlr_scene_timer){0}; + } + + if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) { + // if the state is being disabled, do nothing. + return true; + } + struct wlr_output *output = scene_output->output; enum wlr_scene_debug_damage_option debug_damage = scene_output->scene->debug_damage_option; - // Find the fx_renderer addon - struct fx_renderer *renderer = - fx_renderer_addon_find(&output->addons, scene_output->scene); - assert(renderer != NULL); + struct render_data render_data = { + .transform = output->transform, + .scale = output->scale, + .logical = { .x = scene_output->x, .y = scene_output->y }, + .output = scene_output, + }; + + output_pending_resolution(output, state, + &render_data.trans_width, &render_data.trans_height); + + if (state->committed & WLR_OUTPUT_STATE_TRANSFORM) { + if (render_data.transform != state->transform) { + wlr_damage_ring_add_whole(&scene_output->damage_ring); + } + + render_data.transform = state->transform; + } + + if (state->committed & WLR_OUTPUT_STATE_SCALE) { + if (render_data.scale != state->scale) { + wlr_damage_ring_add_whole(&scene_output->damage_ring); + } + + render_data.scale = state->scale; + } + + if (render_data.transform & WL_OUTPUT_TRANSFORM_90) { + int tmp = render_data.trans_width; + render_data.trans_width = render_data.trans_height; + render_data.trans_height = tmp; + } + + render_data.logical.width = render_data.trans_width / render_data.scale; + render_data.logical.height = render_data.trans_height / render_data.scale; struct render_list_constructor_data list_con = { - .box = { .x = scene_output->x, .y = scene_output->y }, + .box = render_data.logical, .render_list = &scene_output->render_list, .calculate_visibility = scene_output->scene->calculate_visibility, }; - wlr_output_effective_resolution(output, - &list_con.box.width, &list_con.box.height); list_con.render_list->size = 0; scene_nodes_in_box(&scene_output->scene->tree.node, &list_con.box, construct_render_list_iterator, &list_con); array_realloc(list_con.render_list, list_con.render_list->size); - int list_len = list_con.render_list->size / sizeof(struct wlr_scene_node *); - struct wlr_scene_node **list_data = list_con.render_list->data; + struct render_list_entry *list_data = list_con.render_list->data; + int list_len = list_con.render_list->size / sizeof(*list_data); - // if there is only one thing to render let's see if that thing can be - // directly scanned out - bool scanout = false; - if (list_len == 1) { - struct wlr_scene_node *node = list_data[0]; - scanout = scene_node_try_direct_scanout(node, scene_output, &list_con.box); - } + bool scanout = list_len == 1 && + scene_entry_try_direct_scanout(&list_data[0], state, &render_data); if (scene_output->prev_scanout != scanout) { scene_output->prev_scanout = scanout; wlr_log(WLR_DEBUG, "Direct scan-out %s", scanout ? "enabled" : "disabled"); - // When exiting direct scan-out, damage everything - wlr_damage_ring_add_whole(&scene_output->damage_ring); + if (!scanout) { + // When exiting direct scan-out, damage everything + wlr_damage_ring_add_whole(&scene_output->damage_ring); + } } if (scanout) { - struct wlr_scene_node *node = list_data[0]; - - assert(node->type == WLR_SCENE_NODE_BUFFER); - struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); - wl_signal_emit_mutable(&buffer->events.output_present, scene_output); + if (timer) { + struct timespec end_time, duration; + clock_gettime(CLOCK_MONOTONIC, &end_time); + timespec_sub(&duration, &end_time, &start_time); + timer->pre_render_duration = timespec_to_nsec(&duration); + } return true; } @@ -1645,8 +1862,7 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { // add the current frame's damage if there is damage if (pixman_region32_not_empty(&scene_output->damage_ring.current)) { - struct highlight_region *current_damage = - calloc(1, sizeof(*current_damage)); + struct highlight_region *current_damage = calloc(1, sizeof(*current_damage)); if (current_damage) { pixman_region32_init(¤t_damage->region); pixman_region32_copy(¤t_damage->region, @@ -1677,38 +1893,54 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { pixman_region32_fini(&acc_damage); } + wlr_damage_ring_set_bounds(&scene_output->damage_ring, + render_data.trans_width, render_data.trans_height); + + if (!wlr_output_configure_primary_swapchain(output, state, &output->swapchain)) { + return false; + } + int buffer_age; - if (!wlr_output_attach_render(output, &buffer_age)) { + struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain, &buffer_age); + if (buffer == NULL) { return false; } - pixman_region32_t damage; - pixman_region32_init(&damage); - wlr_damage_ring_get_buffer_damage(&scene_output->damage_ring, - buffer_age, &damage); - if (!output->needs_frame && !pixman_region32_not_empty( - &scene_output->damage_ring.current)) { - pixman_region32_fini(&damage); - wlr_output_rollback(output); - return true; + if (timer) { + timer->render_timer = wlr_render_timer_create(output->renderer); + + struct timespec end_time, duration; + clock_gettime(CLOCK_MONOTONIC, &end_time); + timespec_sub(&duration, &end_time, &start_time); + timer->pre_render_duration = timespec_to_nsec(&duration); } - fx_renderer_begin(renderer, output->width, output->height); + struct fx_gles_render_pass *render_pass = + fx_renderer_begin_buffer_pass(output->renderer, buffer, + &(struct wlr_buffer_pass_options){ + .timer = timer ? timer->render_timer : NULL, + } + ); + if (render_pass == NULL) { + wlr_buffer_unlock(buffer); + return false; + } + + render_data.render_pass = render_pass; + pixman_region32_init(&render_data.damage); + wlr_damage_ring_get_buffer_damage(&scene_output->damage_ring, + buffer_age, &render_data.damage); pixman_region32_t background; pixman_region32_init(&background); - pixman_region32_copy(&background, &damage); + pixman_region32_copy(&background, &render_data.damage); // Cull areas of the background that are occluded by opaque regions of // scene nodes above. Those scene nodes will just render atop having us // never see the background. if (scene_output->scene->calculate_visibility) { - float output_scale = scene_output->output->scale; - for (int i = list_len - 1; i >= 0; i--) { - struct wlr_scene_node *node = list_data[i]; - int x, y; - wlr_scene_node_coords(node, &x, &y); + struct render_list_entry *entry = &list_data[i]; // We must only cull opaque regions that are visible by the node. // The node's visibility will have the knowledge of a black rect @@ -1717,38 +1949,49 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { // rendering in that black rect region, consider the node's visibility. pixman_region32_t opaque; pixman_region32_init(&opaque); - scene_node_opaque_region(node, x, y, &opaque); - pixman_region32_intersect(&opaque, &opaque, &node->visible); + scene_node_opaque_region(entry->node, entry->x, entry->y, &opaque); + pixman_region32_intersect(&opaque, &opaque, &entry->node->visible); pixman_region32_translate(&opaque, -scene_output->x, -scene_output->y); - wlr_region_scale(&opaque, &opaque, output_scale); + wlr_region_scale(&opaque, &opaque, render_data.scale); pixman_region32_subtract(&background, &background, &opaque); pixman_region32_fini(&opaque); } - if (floor(output_scale) != output_scale) { + if (floor(render_data.scale) != render_data.scale) { wlr_region_expand(&background, &background, 1); // reintersect with the damage because we never want to render // outside of the damage region - pixman_region32_intersect(&background, &background, &damage); + pixman_region32_intersect(&background, &background, &render_data.damage); } } - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(&background, &nrects); - for (int i = 0; i < nrects; ++i) { - scissor_output(output, &rects[i]); - fx_renderer_clear((float[4]){ 0.0, 0.0, 0.0, 1.0 }); - } + 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 }, + .clip = &background, + }); pixman_region32_fini(&background); for (int i = list_len - 1; i >= 0; i--) { - struct wlr_scene_node *node = list_data[i]; - scene_node_render(renderer, node, scene_output, &damage); - } + struct render_list_entry *entry = &list_data[i]; + scene_entry_render(entry, &render_data); + + if (entry->node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(entry->node); - fx_renderer_scissor(NULL); + if (buffer->primary_output == scene_output && !entry->sent_dmabuf_feedback) { + struct wlr_linux_dmabuf_feedback_v1_init_options options = { + .main_renderer = output->renderer, + .scanout_primary_output = NULL, + }; + + scene_buffer_send_dmabuf_feedback(scene_output->scene, buffer, &options); + } + } + } if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { struct highlight_region *damage; @@ -1758,55 +2001,48 @@ bool wlr_scene_output_commit(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; - int nrects; - pixman_box32_t *rects = pixman_region32_rectangles(&damage->region, &nrects); - for (int i = 0; i < nrects; ++i) { - struct wlr_box box = { - .x = rects[i].x1, - .y = rects[i].y1, - .width = rects[i].x2 - rects[i].x1, - .height = rects[i].y2 - rects[i].y1, - }; - - float color[4] = { alpha * .5, 0.0, 0.0, alpha * .5 }; - fx_render_rect(renderer, &box, color, output->transform_matrix); - } + 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 = &damage->region, + }); } } - // Draw the software cursors - wlr_renderer_begin(output->renderer, output->width, output->height); - wlr_output_render_software_cursors(output, &damage); - wlr_renderer_end(output->renderer); - - pixman_region32_fini(&damage); + wlr_output_add_software_cursors_to_render_pass(output, &render_pass->base, &render_data.damage); - int tr_width, tr_height; - wlr_output_transformed_resolution(output, &tr_width, &tr_height); + pixman_region32_fini(&render_data.damage); - enum wl_output_transform transform = - wlr_output_transform_invert(output->transform); - - pixman_region32_t frame_damage; - pixman_region32_init(&frame_damage); - wlr_region_transform(&frame_damage, - &scene_output->damage_ring.current, - transform, tr_width, tr_height); - wlr_output_set_damage(output, &frame_damage); - pixman_region32_fini(&frame_damage); - - bool success = wlr_output_commit(output); - - if (success) { - wlr_damage_ring_rotate(&scene_output->damage_ring); + if (!wlr_render_pass_submit(&render_pass->base)) { + wlr_buffer_unlock(buffer); + return false; } + wlr_output_state_set_buffer(state, buffer); + wlr_buffer_unlock(buffer); + output_state_apply_damage(&render_data, state); + if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT && !wl_list_empty(&scene_output->damage_highlight_regions)) { wlr_output_schedule_frame(scene_output->output); } - return success; + return true; +} + +int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer) { + int64_t pre_render = timer->pre_render_duration; + if (!timer->render_timer) { + return pre_render; + } + int64_t render = wlr_render_timer_get_duration_ns(timer->render_timer); + return render != -1 ? pre_render + render : -1; +} + +void wlr_scene_timer_finish(struct wlr_scene_timer *timer) { + if (timer->render_timer) { + wlr_render_timer_destroy(timer->render_timer); + } } static void scene_node_send_frame_done(struct wlr_scene_node *node, @@ -1823,7 +2059,7 @@ static void scene_node_send_frame_done(struct wlr_scene_node *node, wlr_scene_buffer_send_frame_done(scene_buffer, now); } } else if (node->type == WLR_SCENE_NODE_TREE) { - struct wlr_scene_tree *scene_tree = scene_tree_from_node(node); + 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) { scene_node_send_frame_done(child, scene_output, now); @@ -1858,7 +2094,7 @@ static void scene_output_for_each_scene_buffer(const struct wlr_box *output_box, user_iterator(scene_buffer, lx, ly, user_data); } } else if (node->type == WLR_SCENE_NODE_TREE) { - struct wlr_scene_tree *scene_tree = scene_tree_from_node(node); + 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) { scene_output_for_each_scene_buffer(output_box, child, lx, ly, diff --git a/types/scene/xdg_shell.c b/types/scene/xdg_shell.c deleted file mode 100644 index 1688348..0000000 --- a/types/scene/xdg_shell.c +++ /dev/null @@ -1,124 +0,0 @@ -#include <stdlib.h> -#include <wlr/types/wlr_scene.h> -#include <wlr/types/wlr_xdg_shell.h> - -struct wlr_scene_xdg_surface { - struct wlr_scene_tree *tree; - struct wlr_xdg_surface *xdg_surface; - struct wlr_scene_tree *surface_tree; - - struct wl_listener tree_destroy; - struct wl_listener xdg_surface_destroy; - struct wl_listener xdg_surface_map; - struct wl_listener xdg_surface_unmap; - struct wl_listener xdg_surface_commit; -}; - -static void scene_xdg_surface_handle_tree_destroy(struct wl_listener *listener, - void *data) { - struct wlr_scene_xdg_surface *scene_xdg_surface = - wl_container_of(listener, scene_xdg_surface, tree_destroy); - // tree and surface_node will be cleaned up by scene_node_finish - wl_list_remove(&scene_xdg_surface->tree_destroy.link); - wl_list_remove(&scene_xdg_surface->xdg_surface_destroy.link); - wl_list_remove(&scene_xdg_surface->xdg_surface_map.link); - wl_list_remove(&scene_xdg_surface->xdg_surface_unmap.link); - wl_list_remove(&scene_xdg_surface->xdg_surface_commit.link); - free(scene_xdg_surface); -} - -static void scene_xdg_surface_handle_xdg_surface_destroy(struct wl_listener *listener, - void *data) { - struct wlr_scene_xdg_surface *scene_xdg_surface = - wl_container_of(listener, scene_xdg_surface, xdg_surface_destroy); - wlr_scene_node_destroy(&scene_xdg_surface->tree->node); -} - -static void scene_xdg_surface_handle_xdg_surface_map(struct wl_listener *listener, - void *data) { - struct wlr_scene_xdg_surface *scene_xdg_surface = - wl_container_of(listener, scene_xdg_surface, xdg_surface_map); - wlr_scene_node_set_enabled(&scene_xdg_surface->tree->node, true); -} - -static void scene_xdg_surface_handle_xdg_surface_unmap(struct wl_listener *listener, - void *data) { - struct wlr_scene_xdg_surface *scene_xdg_surface = - wl_container_of(listener, scene_xdg_surface, xdg_surface_unmap); - wlr_scene_node_set_enabled(&scene_xdg_surface->tree->node, false); -} - -static void scene_xdg_surface_update_position( - struct wlr_scene_xdg_surface *scene_xdg_surface) { - struct wlr_xdg_surface *xdg_surface = scene_xdg_surface->xdg_surface; - - struct wlr_box geo = {0}; - wlr_xdg_surface_get_geometry(xdg_surface, &geo); - wlr_scene_node_set_position(&scene_xdg_surface->surface_tree->node, - -geo.x, -geo.y); - - if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { - struct wlr_xdg_popup *popup = xdg_surface->popup; - wlr_scene_node_set_position(&scene_xdg_surface->tree->node, - popup->current.geometry.x, popup->current.geometry.y); - } -} - -static void scene_xdg_surface_handle_xdg_surface_commit(struct wl_listener *listener, - void *data) { - struct wlr_scene_xdg_surface *scene_xdg_surface = - wl_container_of(listener, scene_xdg_surface, xdg_surface_commit); - scene_xdg_surface_update_position(scene_xdg_surface); -} - -struct wlr_scene_tree *wlr_scene_xdg_surface_create( - struct wlr_scene_tree *parent, struct wlr_xdg_surface *xdg_surface) { - struct wlr_scene_xdg_surface *scene_xdg_surface = - calloc(1, sizeof(*scene_xdg_surface)); - if (scene_xdg_surface == NULL) { - return NULL; - } - - scene_xdg_surface->xdg_surface = xdg_surface; - - scene_xdg_surface->tree = wlr_scene_tree_create(parent); - if (scene_xdg_surface->tree == NULL) { - free(scene_xdg_surface); - return NULL; - } - - scene_xdg_surface->surface_tree = wlr_scene_subsurface_tree_create( - scene_xdg_surface->tree, xdg_surface->surface); - if (scene_xdg_surface->surface_tree == NULL) { - wlr_scene_node_destroy(&scene_xdg_surface->tree->node); - free(scene_xdg_surface); - return NULL; - } - - scene_xdg_surface->tree_destroy.notify = - scene_xdg_surface_handle_tree_destroy; - wl_signal_add(&scene_xdg_surface->tree->node.events.destroy, - &scene_xdg_surface->tree_destroy); - - scene_xdg_surface->xdg_surface_destroy.notify = - scene_xdg_surface_handle_xdg_surface_destroy; - wl_signal_add(&xdg_surface->events.destroy, &scene_xdg_surface->xdg_surface_destroy); - - scene_xdg_surface->xdg_surface_map.notify = - scene_xdg_surface_handle_xdg_surface_map; - wl_signal_add(&xdg_surface->events.map, &scene_xdg_surface->xdg_surface_map); - - scene_xdg_surface->xdg_surface_unmap.notify = - scene_xdg_surface_handle_xdg_surface_unmap; - wl_signal_add(&xdg_surface->events.unmap, &scene_xdg_surface->xdg_surface_unmap); - - scene_xdg_surface->xdg_surface_commit.notify = - scene_xdg_surface_handle_xdg_surface_commit; - wl_signal_add(&xdg_surface->surface->events.commit, - &scene_xdg_surface->xdg_surface_commit); - - wlr_scene_node_set_enabled(&scene_xdg_surface->tree->node, xdg_surface->mapped); - scene_xdg_surface_update_position(scene_xdg_surface); - - return scene_xdg_surface->tree; -} @@ -19,7 +19,7 @@ bool env_parse_bool(const char *option) { return false; } -ssize_t env_parse_switch(const char *option, const char **switches) { +size_t env_parse_switch(const char *option, const char **switches) { const char *env = getenv(option); if (env) { wlr_log(WLR_INFO, "Loading %s option: %s", option, env); diff --git a/util/time.c b/util/time.c index 06e42b4..78faac5 100644 --- a/util/time.c +++ b/util/time.c @@ -10,12 +10,16 @@ int64_t timespec_to_msec(const struct timespec *a) { return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; } +int64_t timespec_to_nsec(const struct timespec *a) { + return (int64_t)a->tv_sec * NSEC_PER_SEC + a->tv_nsec; +} + void timespec_from_nsec(struct timespec *r, int64_t nsec) { r->tv_sec = nsec / NSEC_PER_SEC; r->tv_nsec = nsec % NSEC_PER_SEC; } -uint32_t get_current_time_msec(void) { +int64_t get_current_time_msec(void) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return timespec_to_msec(&now); |