mirror of
https://gitlab.freedesktop.org/wlroots/wlroots.git
synced 2025-11-18 06:59:44 -05:00
Add wlr_compositor buffer API
This also rolls wlr_buffer into wlr_renderer as a user of this new API.
This commit is contained in:
parent
21db2418b7
commit
8630988eae
10 changed files with 518 additions and 370 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -1,205 +0,0 @@
|
|||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <wlr/render/wlr_renderer.h>
|
||||
#include <wlr/types/wlr_buffer.h>
|
||||
#include <wlr/types/wlr_linux_dmabuf_v1.h>
|
||||
#include <wlr/util/log.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -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++;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue