diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h index 33f96b687..eba4fdc64 100644 --- a/include/wlr/render/wlr_renderer.h +++ b/include/wlr/render/wlr_renderer.h @@ -19,8 +19,10 @@ enum wlr_renderer_read_pixels_flags { WLR_RENDERER_READ_PIXELS_Y_INVERT = 1, }; -struct wlr_renderer_impl; +struct wlr_compositor; +struct wlr_commit; struct wlr_drm_format_set; +struct wlr_renderer_impl; struct wlr_renderer { const struct wlr_renderer_impl *impl; @@ -107,11 +109,14 @@ bool wlr_renderer_read_pixels(struct wlr_renderer *r, enum wl_shm_format fmt, */ bool wlr_renderer_format_supported(struct wlr_renderer *r, enum wl_shm_format fmt); -void wlr_renderer_init_wl_display(struct wlr_renderer *r, - struct wl_display *wl_display); /** * Destroys this wlr_renderer. Textures must be destroyed separately. */ void wlr_renderer_destroy(struct wlr_renderer *renderer); +void wlr_renderer_set_compositor(struct wlr_renderer *renderer, + struct wlr_compositor *comp); + +struct wlr_texture *wlr_commit_get_texture(struct wlr_commit *commit); + #endif diff --git a/include/wlr/types/meson.build b/include/wlr/types/meson.build index cf672c808..2b29c5ebd 100644 --- a/include/wlr/types/meson.build +++ b/include/wlr/types/meson.build @@ -1,6 +1,5 @@ install_headers( 'wlr_box.h', - 'wlr_buffer.h', 'wlr_compositor.h', 'wlr_cursor.h', 'wlr_data_control_v1.h', diff --git a/include/wlr/types/wlr_buffer.h b/include/wlr/types/wlr_buffer.h deleted file mode 100644 index 0c987b170..000000000 --- a/include/wlr/types/wlr_buffer.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This an unstable interface of wlroots. No guarantees are made regarding the - * future consistency of this API. - */ -#ifndef WLR_USE_UNSTABLE -#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" -#endif - -#ifndef WLR_TYPES_WLR_BUFFER_H -#define WLR_TYPES_WLR_BUFFER_H - -#include -#include - -/** - * A client buffer. - */ -struct wlr_buffer { - /** - * The buffer resource, if any. Will be NULL if the client destroys it. - */ - struct wl_resource *resource; - /** - * The buffer's texture, if any. A buffer will not have a texture if the - * client destroys the buffer before it has been released. - */ - struct wlr_texture *texture; - bool released; - size_t n_refs; - - struct wl_listener resource_destroy; -}; - -struct wlr_renderer; - -/** - * Check if a resource is a wl_buffer resource. - */ -bool wlr_resource_is_buffer(struct wl_resource *resource); -/** - * Get the size of a wl_buffer resource. - */ -bool wlr_buffer_get_resource_size(struct wl_resource *resource, - struct wlr_renderer *renderer, int *width, int *height); - -/** - * Upload a buffer to the GPU and reference it. - */ -struct wlr_buffer *wlr_buffer_create(struct wlr_renderer *renderer, - struct wl_resource *resource); -/** - * Reference the buffer. - */ -struct wlr_buffer *wlr_buffer_ref(struct wlr_buffer *buffer); -/** - * Unreference the buffer. After this call, `buffer` may not be accessed - * anymore. - */ -void wlr_buffer_unref(struct wlr_buffer *buffer); -/** - * Try to update the buffer's content. On success, returns the updated buffer - * and destroys the provided `buffer`. On error, `buffer` is intact and NULL is - * returned. - * - * Fails if there's more than one reference to the buffer or if the texture - * isn't mutable. - */ -struct wlr_buffer *wlr_buffer_apply_damage(struct wlr_buffer *buffer, - struct wl_resource *resource, pixman_region32_t *damage); - -#endif diff --git a/include/wlr/types/wlr_compositor.h b/include/wlr/types/wlr_compositor.h index adb97762c..e134d31e4 100644 --- a/include/wlr/types/wlr_compositor.h +++ b/include/wlr/types/wlr_compositor.h @@ -9,20 +9,79 @@ #ifndef WLR_TYPES_WLR_COMPOSITOR_H #define WLR_TYPES_WLR_COMPOSITOR_H +#include #include -#include struct wlr_commit; struct wlr_output; struct wlr_surface; +/* + * This represents a client-side wl_buffer. + * + * All of the wl_buffer "factory interfaces" are not managed by wlr_compositor, + * but we still need to know some extra information beyond the wl_resource + * handle. This information is retrieved by the callbacks specified in + * wlr_compositor_buffer_impl. + * + * If you are using wlr_renderer, you do not need to worry about this type, as + * wlr_renderer handles the buffer factory interfaces and the callbacks, but + * you must call wlr_renderer_set_compositor to set this up. + */ +struct wlr_buffer { + struct wl_resource *resource; + size_t ref_cnt; + + /* + * This should contain the protocol name of this buffer, used for some + * protocol conformance checks (e.g. "wl_shm"). As a special case, + * buffers created with the EGL extension "EGL_WL_bind_wayland_display" + * will be called "egl", regardless of what the underlying protocol is + * called (i.e. NOT "wl_drm"). This should point to statically + * allocated memory. + */ + const char *protocol; + + /* + * The size of the buffer in buffer coordinates. + * i.e. no scaling, cropping, or transforms applied. + */ + int32_t width; + int32_t height; +}; + +/* + * See struct wlr_buffer + */ +struct wlr_compositor_buffer_impl { + struct wlr_buffer *(*create)(void *data, struct wlr_buffer *buf, + pixman_region32_t *damage, struct wl_resource *res); + struct wlr_buffer *(*ref)(struct wlr_buffer *buf); + void (*unref)(struct wlr_buffer *buf); +}; + +/* + * Implementation of the wl_compositor global. + * + * This is responsible for creating wl_surfaces and wl_regions. + * + * The wlr_compositor's lifetime to tied to the lifetime of the wl_display + * it was created with, meaning that calling wl_display_destroy will destroy + * the wlr_compositor. There is no way to destroy it earlier than this, + * as clients can not be expected to handle this being removed. + * + * Is is an error to create two wlr_compositors for the same wl_display or + * otherwise advertise a second wl_compositor global on it. + */ struct wlr_compositor { struct wl_display *display; struct wl_global *global; - struct wlr_renderer *renderer; uint32_t ids; + const struct wlr_compositor_buffer_impl *buffer_impl; + void *buffer_data; + struct { struct wl_signal new_surface; struct wl_signal new_surface_2; @@ -32,6 +91,11 @@ struct wlr_compositor { struct wl_listener display_destroy; }; +struct wlr_compositor_new_state_args { + struct wlr_commit *old; + struct wlr_commit *new; +}; + struct wlr_surface_2 { struct wl_resource *resource; struct wlr_compositor *compositor; @@ -39,13 +103,13 @@ struct wlr_surface_2 { /* * This should contain the name of the protocol to prevent conflicts. * e.g. "xdg_toplevel". - * Also, it should point to statically allocated memory. + * This should point to statically allocated memory. */ const char *role_name; void *role_data; - struct wl_list committed; - struct wl_list frame_callbacks; + struct wl_list committed; // struct wlr_commit.link + struct wl_list frame_callbacks; // struct wl_resource struct { struct wl_signal commit; @@ -56,8 +120,6 @@ struct wlr_surface_2 { }; /* - * wlr_commit - * * A wlr_commit is a description for a "coherent presentation" of a surface's * contents, or could also be thought of as a "snaphot" of the surface at * commit time. It should contain all of the information to correctly display @@ -76,38 +138,59 @@ struct wlr_surface_2 { * For a surface, there will always be exactly 1 pending commit for a surface. * see wlr_surface_get_pending * - * Excluding the time when a before the first complete commit is ready, there - * will always be at least 1 complete commit for a surface. The latest complete + * Excluding the time before the first complete commit is ready, there will + * always be at least 1 complete commit for a surface. The latest complete * commit is called the current commit. * see wlr_surface_get_commit * * wlr_commits are reference counted. If you wish to save the contents of a - * commit for future use, simply do not unreference the commit. A referenced - * wlr_commit will even persist after the surface is destroyed, which can be - * useful for fade-out animations. Any extensions to wlr_surface should be - * prepared for the associated wlr_surface to not be available. + * commit for future use, simply do not unreference the commit. * * Upon commit destruction, several actions may be performed on objects which * will affect clients. For example, wl_buffer.release may be called for the * attached buffer. Holding onto too many commits for too long may cause * resource starvation in clients, so care should be taken to unreference * commits as soon as they're no longer needed. + * + * Inhibitiors: + * ----------- + * + * Each commit has a counter of inhibitors, which means that the commit is + * being prevented from becoming "complete". The intended use-case is for + * wl_subsurfaces preventing themselves from being presented until their + * parent has been commited, and wp_linux_explicit_synchronization from + * being presented until a fence is signalled. + * + * Inhibitors don't have any effect on commits which are already complete. + * + * Orphaned Commits: + * ---------------- + * + * A commit will stay valid even after the associated surface has been + * destroyed by the client. Such a commit is called an orphaned commit. + * Pending commits can never be orphaned, as wl_surface.commit can never be + * called on them. + * + * This can be useful for fade-out animations of a client's surface. + * + * Any extension to wlr_commit should be prepared for the associated + * wlr_surface to not be available. */ struct wlr_commit { struct wl_list link; + struct wlr_compositor *compositor; struct wlr_surface_2 *surface; - // If the user has called wl_surface.commit + // If the client has called wl_surface.commit bool committed; // See wlr_commit_inhibit size_t inhibit; size_t ref_cnt; // wl_surface.attach + struct wlr_buffer *buffer; struct wl_resource *buffer_resource; int32_t sx, sy; - // wl_surface.damage - pixman_region32_t surface_damage; // wl_surface.frame struct wl_list frame_callbacks; // wl_surface.set_opaque_region @@ -119,37 +202,83 @@ struct wlr_commit { // wl_surface.set_buffer_scale int32_t scale; // wl_surface.damage_buffer - pixman_region32_t buffer_damage; + pixman_region32_t damage; size_t state_len; void **state; struct { + /* + * arg: struct wlr_commit *this + * The client called wl_surface.commit. + */ struct wl_signal commit; + /* + * arg: struct wlr_commit *this + * The commit has transitioned to the complete state, and is + * now ready to be presented. + */ struct wl_signal complete; + /* + * arg: struct wlr_commit *this + * The wlr_commit has been destroyed. + */ struct wl_signal destroy; } events; }; -struct wlr_compositor_new_state_args { - struct wlr_commit *old; - struct wlr_commit *new; -}; - -struct wlr_compositor *wlr_compositor_create(struct wl_display *display, - struct wlr_renderer *renderer); +struct wlr_compositor *wlr_compositor_create(struct wl_display *display); +void wlr_compositor_set_buffer_impl(struct wlr_compositor *comp, + void *data, const struct wlr_compositor_buffer_impl *impl); uint32_t wlr_compositor_register(struct wlr_compositor *compositor); struct wlr_surface_2 *wlr_surface_from_resource_2(struct wl_resource *resource); +/* + * Get the latest complete commit for a surface. + * This increases the reference count of the commit. + * + * Returns NULL if there is no complete commit yet. + */ struct wlr_commit *wlr_surface_get_commit(struct wlr_surface_2 *surface); +/* + * Get the current pending commit for a surface. + * This DOES NOT increase the reference count of the commit. + * This is not intended to be used outside of extensions to wlr_commit. + * + * This will never fail. + */ struct wlr_commit *wlr_surface_get_pending(struct wlr_surface_2 *surface); +/* + * Gets the latest committed or complete commit. + * This increases the reference count of the commit. + * This is not intended to be used outside of extensions to wlr_commit. + * + * If you are looking for the latest complete commit, use + * wlr_surface_get_commit instead. + * + * Returns NULL if there is no committed/complete commit yet. + */ struct wlr_commit *wlr_surface_get_latest(struct wlr_surface_2 *surface); void wlr_surface_send_enter_2(struct wlr_surface_2 *surf, struct wlr_output *output); void wlr_surface_send_leave_2(struct wlr_surface_2 *surf, struct wlr_output *output); +/* + * Increases the reference count of a commit. + * commit must be non-null. + * + * commit is returned from this function as a convinience to do + * `struct wlr_commit *saved = wlr_commit_ref(commit);` + */ struct wlr_commit *wlr_commit_ref(struct wlr_commit *commit); +/* + * Decreases the reference count for a commit. + * commit must be non-null and have a positive reference count. + * + * The commit is not necessarily freed immediately when the reference count + * reaches zero, depending on its state and position in the commit queue. + */ void wlr_commit_unref(struct wlr_commit *commit); bool wlr_commit_is_complete(struct wlr_commit *c); diff --git a/meson.build b/meson.build index 5cb7ef375..1a3cd57ac 100644 --- a/meson.build +++ b/meson.build @@ -169,7 +169,7 @@ summary = [ message('\n'.join(summary)) executable('test-server', 'test-server.c', - dependencies: wlroots) + dependencies: [wlroots, gbm]) executable('test-client', 'test-client.c', dependencies: wayland_client) @@ -185,35 +185,3 @@ pkgconfig.generate( description: 'Wayland compositor library', ) -git = find_program('git', required: false) -if git.found() - all_files = run_command( - git, - '--git-dir=@0@/.git'.format(meson.current_source_dir()), - 'ls-files', - ':/*.[ch]', - ) - all_files = files(all_files.stdout().split()) - - etags = find_program('etags', required: false) - if etags.found() and all_files.length() > 0 - custom_target( - 'etags', - build_by_default: true, - input: all_files, - output: 'TAGS', - command: [etags, '-o', '@OUTPUT@', '@INPUT@'], - ) - endif - - ctags = find_program('ctags', required: false) - if ctags.found() and all_files.length() > 0 - custom_target( - 'ctags', - build_by_default: true, - input: all_files, - output: 'tags', - command: [ctags, '-f', '@OUTPUT@', '@INPUT@'], - ) - endif -endif diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c index 802cbdbd8..353cd805d 100644 --- a/render/wlr_renderer.c +++ b/render/wlr_renderer.c @@ -1,9 +1,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -147,15 +149,232 @@ bool wlr_renderer_format_supported(struct wlr_renderer *r, return r->impl->format_supported(r, fmt); } -void wlr_renderer_init_wl_display(struct wlr_renderer *r, - struct wl_display *wl_display) { - if (wl_display_init_shm(wl_display)) { - wlr_log(WLR_ERROR, "Failed to initialize shm"); +struct buffer { + struct wlr_buffer base; + struct wl_list link; + + bool released; + struct wlr_texture *texture; + + struct wl_listener resource_destroy; +}; + +static void buffer_resource_destroy(struct wl_listener *listener, void *data) { + struct buffer *buf = wl_container_of(listener, buf, resource_destroy); + + buf->base.resource = NULL; + + /* + * At this point, if the wl_buffer comes from a dmabuf-based buffer, we + * still haven't released it (i.e. we'll read it in the future) but the + * client destroyed it. Reading the texture itself should be fine + * because we still hold a reference to the dmabuf via the texture. + * However the client could decide to re-use the same dmabuf for + * something else, in which case we'll read garbage. We decide to + * accept this risk. + */ +} + +static struct wlr_buffer *create_buffer(struct wlr_renderer *renderer, + struct wl_resource *res) { + struct buffer *buf = calloc(1, sizeof(*buf)); + if (!buf) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + wl_resource_post_no_memory(res); + return NULL; + } + + struct wl_shm_buffer *shm = wl_shm_buffer_get(res); + if (shm) { + enum wl_shm_format fmt = wl_shm_buffer_get_format(shm); + int32_t stride = wl_shm_buffer_get_stride(shm); + int32_t width = wl_shm_buffer_get_width(shm); + int32_t height = wl_shm_buffer_get_height(shm); + + wl_shm_buffer_begin_access(shm); + void *data = wl_shm_buffer_get_data(shm); + buf->texture = wlr_texture_from_pixels(renderer, fmt, stride, + width, height, data); + wl_shm_buffer_end_access(shm); + + /* + * We have uploaded the data, we don't need to access the + * wl_buffer anymore + */ + wl_buffer_send_release(res); + buf->released = true; + buf->base.protocol = "wl_shm"; + } else if (wlr_renderer_resource_is_wl_drm_buffer(renderer, res)) { + buf->texture = wlr_texture_from_wl_drm(renderer, res); + buf->base.protocol = "egl"; + } else if (wlr_dmabuf_v1_resource_is_buffer(res)) { + struct wlr_dmabuf_v1_buffer *dmabuf = + wlr_dmabuf_v1_buffer_from_buffer_resource(res); + buf->texture = wlr_texture_from_dmabuf(renderer, &dmabuf->attributes); + buf->base.protocol = "zwp_linux_dmabuf_v1"; + } else { + wlr_log(WLR_ERROR, "Cannot upload texture: unknown buffer type"); + + // Instead of just logging the error, also disconnect the client with a + // fatal protocol error so that it's clear something went wrong. + wl_resource_post_error(res, 0, "unknown buffer type"); + free(buf); + return NULL; + } + + if (!buf->texture) { + wlr_log(WLR_ERROR, "Failed to create texture"); + wl_resource_post_error(res, 0, "failed to use buffer"); + free(buf); + return NULL; + } + + int width, height; + wlr_texture_get_size(buf->texture, &width, &height); + buf->base.width = width; + buf->base.height = height; + buf->base.ref_cnt = 1; + + return &buf->base; +} + +static struct wlr_buffer *buffer_ref(struct wlr_buffer *buffer) { + assert(buffer); + ++buffer->ref_cnt; + return buffer; +} + +static void buffer_unref(struct wlr_buffer *buffer_base) { + struct buffer *buffer = wl_container_of(buffer_base, buffer, base); + assert(buffer->base.ref_cnt > 0); + + if (--buffer->base.ref_cnt > 0) { return; } + if (buffer->base.resource) { + if (!buffer->released) { + wl_buffer_send_release(buffer->base.resource); + } + wl_list_remove(&buffer->resource_destroy.link); + } + + wlr_texture_destroy(buffer->texture); + free(buffer); +} + +static struct wlr_buffer *buffer_create(void *userdata, + struct wlr_buffer *buffer_base, pixman_region32_t *buffer_damage, + struct wl_resource *res) { + struct wlr_renderer *renderer = userdata; + struct buffer *buffer = wl_container_of(buffer_base, buffer, base); + + if (!buffer) { + return create_buffer(renderer, res); + } + + /* + * The old buffer is still being used somewhere else, so we cannot + * change/reuse it. + */ + if (buffer->base.ref_cnt != 1) { + buffer_unref(&buffer->base); + return create_buffer(renderer, res); + } + + /* + * Any kind of buffer sharing only happens with wl_shm. + * dmabuf-based textures are cheap to create and aren't modifiable. + */ + struct wl_shm_buffer *old_shm = wl_shm_buffer_get(buffer->base.resource); + struct wl_shm_buffer *new_shm = wl_shm_buffer_get(res); + if (!old_shm || !new_shm) { + buffer_unref(&buffer->base); + return create_buffer(renderer, res); + } + + /* + * We can't change the shm format. + */ + enum wl_shm_format old_fmt = wl_shm_buffer_get_format(old_shm); + enum wl_shm_format new_fmt = wl_shm_buffer_get_format(new_shm); + if (old_fmt != new_fmt) { + buffer_unref(&buffer->base); + return create_buffer(renderer, res); + } + + /* + * We can't change the dimensions. + */ + int32_t stride = wl_shm_buffer_get_stride(new_shm); + int32_t width = wl_shm_buffer_get_width(new_shm); + int32_t height = wl_shm_buffer_get_height(new_shm); + int old_width, old_height; + wlr_texture_get_size(buffer->texture, &old_width, &old_height); + if (width != old_width || height != old_height) { + buffer_unref(&buffer->base); + return create_buffer(renderer, res); + } + + wl_shm_buffer_begin_access(new_shm); + void *data = wl_shm_buffer_get_data(new_shm); + + /* + * Apply damage to buffer. + */ + pixman_region32_t damage; + pixman_region32_init_rect(&damage, 0, 0, width, height); + pixman_region32_intersect(&damage, &damage, buffer_damage); + int n; + pixman_box32_t *rects = pixman_region32_rectangles(&damage, &n); + for (int i = 0; i < n; ++i) { + pixman_box32_t *r = &rects[i]; + if (!wlr_texture_write_pixels(buffer->texture, stride, + r->x2 - r->x1, r->y2 - r->y1, r->x1, r->y1, + r->x1, r->y1, data)) { + wlr_log(WLR_ERROR, "Failed to write pixels"); + wl_resource_post_error(res, 0, "failed to use buffer"); + buffer_unref(&buffer->base); + wl_shm_buffer_end_access(new_shm); + pixman_region32_fini(&damage); + return NULL; + } + } + + wl_shm_buffer_end_access(new_shm); + pixman_region32_fini(&damage); + /* + * We've copied the data into a local GPU buffer, so we don't need to + * hold onto the wl_buffer anymore. + */ + wl_buffer_send_release(res); + + wl_list_remove(&buffer->resource_destroy.link); + wl_resource_add_destroy_listener(res, &buffer->resource_destroy); + buffer->resource_destroy.notify = buffer_resource_destroy; + + buffer->base.resource = res; + buffer->released = true; + + return &buffer->base; +} + + +static const struct wlr_compositor_buffer_impl buffer_impl = { + .create = buffer_create, + .ref = buffer_ref, + .unref = buffer_unref, +}; + +void wlr_renderer_set_compositor(struct wlr_renderer *renderer, + struct wlr_compositor *comp) { + assert(renderer); + assert(comp); + + struct wl_display *display = comp->display; + size_t len; - const enum wl_shm_format *formats = wlr_renderer_get_formats(r, &len); + const enum wl_shm_format *formats = wlr_renderer_get_formats(renderer, &len); if (formats == NULL) { wlr_log(WLR_ERROR, "Failed to initialize shm: cannot get formats"); return; @@ -165,17 +384,35 @@ void wlr_renderer_init_wl_display(struct wlr_renderer *r, // These formats are already added by default if (formats[i] != WL_SHM_FORMAT_ARGB8888 && formats[i] != WL_SHM_FORMAT_XRGB8888) { - wl_display_add_shm_format(wl_display, formats[i]); + wl_display_add_shm_format(display, formats[i]); } } - if (r->impl->texture_from_dmabuf) { - wlr_linux_dmabuf_v1_create(wl_display, r); + if (wl_display_init_shm(display)) { + wlr_log(WLR_ERROR, "Failed to initialize shm"); + return; } - if (r->impl->init_wl_display) { - r->impl->init_wl_display(r, wl_display); + if (renderer->impl->texture_from_dmabuf) { + wlr_linux_dmabuf_v1_create(display, renderer); } + + if (renderer->impl->init_wl_display) { + renderer->impl->init_wl_display(renderer, display); + } + + wlr_compositor_set_buffer_impl(comp, renderer, &buffer_impl); +} + +struct wlr_texture *wlr_commit_get_texture(struct wlr_commit *commit) { + assert(commit); + + if (commit->buffer) { + struct buffer *buffer = wl_container_of(commit->buffer, buffer, base); + return buffer->texture; + } + + return NULL; } struct wlr_renderer *wlr_renderer_autocreate(struct wlr_egl *egl, diff --git a/test-server.c b/test-server.c index 88955e5c8..b6628eb3a 100644 --- a/test-server.c +++ b/test-server.c @@ -1,8 +1,14 @@ +#include +#include #include +#include #include #include #include +#include +#include +#include struct wl_display *display; @@ -18,12 +24,26 @@ int main(void) signal(SIGINT, sigint); - struct wlr_compositor *comp = wlr_compositor_create(display, NULL); + int fd = open("/dev/dri/renderD128", O_RDWR); + struct gbm_device *gbm = gbm_create_device(fd); + + struct wlr_egl egl = {0}; + struct wlr_renderer *renderer = wlr_renderer_autocreate(&egl, + EGL_PLATFORM_GBM_MESA, gbm, NULL, GBM_FORMAT_XRGB8888); + + struct wlr_compositor *comp = wlr_compositor_create(display); struct wlr_subcompositor *subcomp = wlr_subcompositor_create(comp); + wlr_renderer_set_compositor(renderer, comp); + (void)subcomp; wl_display_run(display); + wlr_renderer_destroy(renderer); + wlr_egl_finish(&egl); + gbm_device_destroy(gbm); + close(fd); + wl_display_destroy_clients(display); wl_display_destroy(display); } diff --git a/types/meson.build b/types/meson.build index 5e7407c5d..4fa2f7ade 100644 --- a/types/meson.build +++ b/types/meson.build @@ -24,7 +24,6 @@ lib_wlr_types = static_library( #'xdg_shell/wlr_xdg_surface.c', #'xdg_shell/wlr_xdg_toplevel.c', 'wlr_box.c', - 'wlr_buffer.c', 'wlr_compositor.c', 'wlr_subcompositor.c', #'wlr_cursor.c', diff --git a/types/wlr_buffer.c b/types/wlr_buffer.c deleted file mode 100644 index cec3475cf..000000000 --- a/types/wlr_buffer.c +++ /dev/null @@ -1,205 +0,0 @@ -#include -#include -#include -#include -#include -#include - -bool wlr_resource_is_buffer(struct wl_resource *resource) { - return strcmp(wl_resource_get_class(resource), wl_buffer_interface.name) == 0; -} - -bool wlr_buffer_get_resource_size(struct wl_resource *resource, - struct wlr_renderer *renderer, int *width, int *height) { - assert(wlr_resource_is_buffer(resource)); - - struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource); - if (shm_buf != NULL) { - *width = wl_shm_buffer_get_width(shm_buf); - *height = wl_shm_buffer_get_height(shm_buf); - } else if (wlr_renderer_resource_is_wl_drm_buffer(renderer, - resource)) { - wlr_renderer_wl_drm_buffer_get_size(renderer, resource, - width, height); - } else if (wlr_dmabuf_v1_resource_is_buffer(resource)) { - struct wlr_dmabuf_v1_buffer *dmabuf = - wlr_dmabuf_v1_buffer_from_buffer_resource(resource); - *width = dmabuf->attributes.width; - *height = dmabuf->attributes.height; - } else { - *width = *height = 0; - return false; - } - - return true; -} - - -static void buffer_resource_handle_destroy(struct wl_listener *listener, - void *data) { - struct wlr_buffer *buffer = - wl_container_of(listener, buffer, resource_destroy); - wl_list_remove(&buffer->resource_destroy.link); - wl_list_init(&buffer->resource_destroy.link); - buffer->resource = NULL; - - // At this point, if the wl_buffer comes from linux-dmabuf or wl_drm, we - // still haven't released it (ie. we'll read it in the future) but the - // client destroyed it. Reading the texture itself should be fine because - // we still hold a reference to the DMA-BUF via the texture. However the - // client could decide to re-use the same DMA-BUF for something else, in - // which case we'll read garbage. We decide to accept this risk. -} - -struct wlr_buffer *wlr_buffer_create(struct wlr_renderer *renderer, - struct wl_resource *resource) { - assert(wlr_resource_is_buffer(resource)); - - struct wlr_texture *texture = NULL; - bool released = false; - - struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource); - if (shm_buf != NULL) { - enum wl_shm_format fmt = wl_shm_buffer_get_format(shm_buf); - int32_t stride = wl_shm_buffer_get_stride(shm_buf); - int32_t width = wl_shm_buffer_get_width(shm_buf); - int32_t height = wl_shm_buffer_get_height(shm_buf); - - wl_shm_buffer_begin_access(shm_buf); - void *data = wl_shm_buffer_get_data(shm_buf); - texture = wlr_texture_from_pixels(renderer, fmt, stride, - width, height, data); - wl_shm_buffer_end_access(shm_buf); - - // We have uploaded the data, we don't need to access the wl_buffer - // anymore - wl_buffer_send_release(resource); - released = true; - } else if (wlr_renderer_resource_is_wl_drm_buffer(renderer, resource)) { - texture = wlr_texture_from_wl_drm(renderer, resource); - } else if (wlr_dmabuf_v1_resource_is_buffer(resource)) { - struct wlr_dmabuf_v1_buffer *dmabuf = - wlr_dmabuf_v1_buffer_from_buffer_resource(resource); - texture = wlr_texture_from_dmabuf(renderer, &dmabuf->attributes); - - // We have imported the DMA-BUF, but we need to prevent the client from - // re-using the same DMA-BUF for the next frames, so we don't release - // the buffer yet. - } else { - wlr_log(WLR_ERROR, "Cannot upload texture: unknown buffer type"); - - // Instead of just logging the error, also disconnect the client with a - // fatal protocol error so that it's clear something went wrong. - wl_resource_post_error(resource, 0, "unknown buffer type"); - return NULL; - } - - if (texture == NULL) { - wlr_log(WLR_ERROR, "Failed to upload texture"); - return NULL; - } - - struct wlr_buffer *buffer = calloc(1, sizeof(struct wlr_buffer)); - if (buffer == NULL) { - wlr_texture_destroy(texture); - return NULL; - } - buffer->resource = resource; - buffer->texture = texture; - buffer->released = released; - buffer->n_refs = 1; - - wl_resource_add_destroy_listener(resource, &buffer->resource_destroy); - buffer->resource_destroy.notify = buffer_resource_handle_destroy; - - return buffer; -} - -struct wlr_buffer *wlr_buffer_ref(struct wlr_buffer *buffer) { - buffer->n_refs++; - return buffer; -} - -void wlr_buffer_unref(struct wlr_buffer *buffer) { - if (buffer == NULL) { - return; - } - - assert(buffer->n_refs > 0); - buffer->n_refs--; - if (buffer->n_refs > 0) { - return; - } - - if (!buffer->released && buffer->resource != NULL) { - wl_buffer_send_release(buffer->resource); - } - - wl_list_remove(&buffer->resource_destroy.link); - wlr_texture_destroy(buffer->texture); - free(buffer); -} - -struct wlr_buffer *wlr_buffer_apply_damage(struct wlr_buffer *buffer, - struct wl_resource *resource, pixman_region32_t *damage) { - assert(wlr_resource_is_buffer(resource)); - - if (buffer->n_refs > 1) { - // Someone else still has a reference to the buffer - return NULL; - } - - struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource); - struct wl_shm_buffer *old_shm_buf = wl_shm_buffer_get(buffer->resource); - if (shm_buf == NULL || old_shm_buf == NULL) { - // Uploading only damaged regions only works for wl_shm buffers and - // mutable textures (created from wl_shm buffer) - return NULL; - } - - enum wl_shm_format new_fmt = wl_shm_buffer_get_format(shm_buf); - enum wl_shm_format old_fmt = wl_shm_buffer_get_format(old_shm_buf); - if (new_fmt != old_fmt) { - // Uploading to textures can't change the format - return NULL; - } - - int32_t stride = wl_shm_buffer_get_stride(shm_buf); - int32_t width = wl_shm_buffer_get_width(shm_buf); - int32_t height = wl_shm_buffer_get_height(shm_buf); - - int32_t texture_width, texture_height; - wlr_texture_get_size(buffer->texture, &texture_width, &texture_height); - if (width != texture_width || height != texture_height) { - return NULL; - } - - wl_shm_buffer_begin_access(shm_buf); - void *data = wl_shm_buffer_get_data(shm_buf); - - int n; - pixman_box32_t *rects = pixman_region32_rectangles(damage, &n); - for (int i = 0; i < n; ++i) { - pixman_box32_t *r = &rects[i]; - if (!wlr_texture_write_pixels(buffer->texture, stride, - r->x2 - r->x1, r->y2 - r->y1, r->x1, r->y1, - r->x1, r->y1, data)) { - wl_shm_buffer_end_access(shm_buf); - return NULL; - } - } - - wl_shm_buffer_end_access(shm_buf); - - // We have uploaded the data, we don't need to access the wl_buffer - // anymore - wl_buffer_send_release(resource); - - wl_list_remove(&buffer->resource_destroy.link); - wl_resource_add_destroy_listener(resource, &buffer->resource_destroy); - buffer->resource_destroy.notify = buffer_resource_handle_destroy; - - buffer->resource = resource; - buffer->released = true; - return buffer; -} diff --git a/types/wlr_compositor.c b/types/wlr_compositor.c index a0d3b763f..3fa7e555d 100644 --- a/types/wlr_compositor.c +++ b/types/wlr_compositor.c @@ -18,6 +18,22 @@ pixman_region32_t *wlr_region_from_resource(struct wl_resource *resource) { return wl_resource_get_user_data(resource); } +static struct wlr_buffer *compositor_buffer_create(struct wlr_compositor *comp, + struct wlr_buffer *old, pixman_region32_t *damage, + struct wl_resource *res) { + return comp->buffer_impl->create(comp->buffer_data, old, damage, res); +} + +static struct wlr_buffer *compositor_buffer_ref(struct wlr_compositor *comp, + struct wlr_buffer *buf) { + return comp->buffer_impl->ref(buf); +} + +static void compositor_buffer_unref(struct wlr_compositor *comp, + struct wlr_buffer *buf) { + comp->buffer_impl->unref(buf); +} + static void region_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } @@ -70,27 +86,30 @@ static struct wlr_commit *commit_create(struct wlr_surface_2 *surf) { return NULL; } + c->compositor = comp; c->surface = surf; wl_signal_init(&c->events.commit); wl_signal_init(&c->events.complete); wl_signal_init(&c->events.destroy); - pixman_region32_init(&c->surface_damage); wl_list_init(&c->frame_callbacks); pixman_region32_init(&c->opaque_region); pixman_region32_init_rect(&c->input_region, INT_MIN, INT_MIN, UINT_MAX, UINT_MAX); c->scale = 1; - pixman_region32_init(&c->buffer_damage); + pixman_region32_init(&c->damage); return c; } static void commit_destroy(struct wlr_commit *c) { - pixman_region32_fini(&c->surface_damage); pixman_region32_fini(&c->opaque_region); pixman_region32_fini(&c->input_region); - pixman_region32_fini(&c->buffer_damage); + pixman_region32_fini(&c->damage); + + if (c->buffer) { + compositor_buffer_unref(c->compositor, c->buffer); + } wlr_signal_emit_safe(&c->events.destroy, c); @@ -206,8 +225,17 @@ static void surface_damage(struct wl_client *client, struct wl_resource *res, struct wlr_surface_2 *surf = wlr_surface_from_resource_2(res); struct wlr_commit *commit = wlr_surface_get_pending(surf); - pixman_region32_union_rect(&commit->surface_damage, - &commit->surface_damage, x, y, width, height); + /* + * We choose to not track surface-coordinate damage; it's not worth the + * effort. Clients are recommended by the wayland protocol to use + * buffer-coordinate damage instead. + * + * We still need to acknowledge that damage happened, so we damage the + * entire buffer instead. + */ + + pixman_region32_union_rect(&commit->damage, + &commit->damage, 0, 0, UINT_MAX, UINT_MAX); } static void callback_resource_destroy(struct wl_resource *resource) { @@ -305,28 +333,43 @@ static void surface_damage_buffer(struct wl_client *client, struct wl_resource * return; } - pixman_region32_union_rect(&commit->buffer_damage, - &commit->buffer_damage, x, y, width, height); + pixman_region32_union_rect(&commit->damage, + &commit->damage, x, y, width, height); } static void surface_commit(struct wl_client *client, struct wl_resource *res) { struct wlr_surface_2 *surf = wlr_surface_from_resource_2(res); + struct wlr_compositor *comp = surf->compositor; struct wlr_commit *commit = wlr_surface_get_pending(surf); + struct wlr_commit *previous = wlr_surface_get_latest(surf); commit->committed = true; wl_list_insert(&surf->committed, &commit->link); - surf->pending = commit_create(surf); - if (!surf->pending) { - wl_resource_post_no_memory(surf->resource); - return; + if (commit->buffer_resource) { + /* + * If we're complete, that means that we can safely "steal" the + * buffer from the previous commit, as we're going to prune it + * right after this block. + * + * This is an optimisation for wl_shm buffers specifically, + * and allows us to skip some allocations and copies. + */ + struct wlr_buffer *old = NULL; + if (previous && previous->ref_cnt == 1 && wlr_commit_is_complete(commit)) { + old = previous->buffer; + previous->buffer = NULL; + } + + commit->buffer = compositor_buffer_create(comp, old, &commit->damage, + commit->buffer_resource); + } else if (previous && previous->buffer) { + commit->buffer = compositor_buffer_ref(comp, previous->buffer); } - surf->pending->buffer_resource = commit->buffer_resource; - pixman_region32_copy(&surf->pending->opaque_region, &commit->opaque_region); - pixman_region32_copy(&surf->pending->input_region, &commit->input_region); - surf->pending->transform = commit->transform; - surf->pending->scale = commit->scale; + if (previous) { + wlr_commit_unref(previous); + } /* * The wayland protocol says we'll signal these in the order they're @@ -335,12 +378,28 @@ static void surface_commit(struct wl_client *client, struct wl_resource *res) { wl_list_insert_list(surf->frame_callbacks.prev, &commit->frame_callbacks); wl_list_init(&commit->frame_callbacks); + wlr_signal_emit_safe(&commit->events.commit, commit); + + surf->pending = commit_create(surf); + if (!surf->pending) { + wl_resource_post_no_memory(surf->resource); + return; + } + + /* + * Copy old wl_surface state to next pending commit. + */ + pixman_region32_copy(&surf->pending->opaque_region, &commit->opaque_region); + pixman_region32_copy(&surf->pending->input_region, &commit->input_region); + surf->pending->transform = commit->transform; + surf->pending->scale = commit->scale; + + struct wlr_compositor_new_state_args args = { .old = commit, .new = surf->pending, }; - wlr_signal_emit_safe(&commit->events.commit, commit); wlr_signal_emit_safe(&surf->compositor->events.new_state, &args); surface_prune_commits(surf); @@ -492,8 +551,7 @@ static void compositor_display_destroy(struct wl_listener *listener, void *data) free(comp); } -struct wlr_compositor *wlr_compositor_create(struct wl_display *display, - struct wlr_renderer *renderer) { +struct wlr_compositor *wlr_compositor_create(struct wl_display *display) { struct wlr_compositor *comp = calloc(1, sizeof(*comp)); if (!comp) { wlr_log_errno(WLR_ERROR, "Allocation failed"); @@ -508,7 +566,6 @@ struct wlr_compositor *wlr_compositor_create(struct wl_display *display, free(comp); return NULL; } - comp->renderer = renderer; wl_signal_init(&comp->events.new_surface); wl_signal_init(&comp->events.new_surface_2); @@ -520,6 +577,16 @@ struct wlr_compositor *wlr_compositor_create(struct wl_display *display, return comp; } +void wlr_compositor_set_buffer_impl(struct wlr_compositor *comp, + void *data, const struct wlr_compositor_buffer_impl *impl) { + assert(comp); + assert(impl); + assert(!comp->buffer_impl); + + comp->buffer_data = data; + comp->buffer_impl = impl; +} + uint32_t wlr_compositor_register(struct wlr_compositor *compositor) { assert(compositor); return compositor->ids++;