diff --git a/include/types/wlr_output_layer.h b/include/types/wlr_output_layer.h new file mode 100644 index 000000000..64412494a --- /dev/null +++ b/include/types/wlr_output_layer.h @@ -0,0 +1,11 @@ +#ifndef TYPES_WLR_OUTPUT_LAYER_H +#define TYPES_WLR_OUTPUT_LAYER_H + +#include + +void output_layer_destroy(struct wlr_output_layer *layer); +void output_layer_state_move(struct wlr_output_layer_state *dst, + struct wlr_output_layer_state *src); +void output_layer_state_clear(struct wlr_output_layer_state *state); + +#endif diff --git a/include/wlr/interfaces/wlr_output.h b/include/wlr/interfaces/wlr_output.h index 100754f68..ca9d4b43b 100644 --- a/include/wlr/interfaces/wlr_output.h +++ b/include/wlr/interfaces/wlr_output.h @@ -21,7 +21,8 @@ (WLR_OUTPUT_STATE_DAMAGE | \ WLR_OUTPUT_STATE_SCALE | \ WLR_OUTPUT_STATE_TRANSFORM | \ - WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) + WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED | \ + WLR_OUTPUT_STATE_LAYERS) /** * A backend implementation of wlr_output. @@ -93,6 +94,8 @@ struct wlr_output_impl { */ const struct wlr_drm_format_set *(*get_primary_formats)( struct wlr_output *output, uint32_t buffer_caps); + struct wlr_output_layer *(*create_layer)(struct wlr_output *output); + void (*destroy_layer)(struct wlr_output_layer *layer); }; /** @@ -146,4 +149,7 @@ void wlr_output_send_frame(struct wlr_output *output); void wlr_output_send_present(struct wlr_output *output, struct wlr_output_event_present *event); +void wlr_output_layer_init(struct wlr_output_layer *layer, + struct wlr_output *output); + #endif diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 6c07372b9..da146bc16 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -62,6 +62,7 @@ enum wlr_output_state_field { WLR_OUTPUT_STATE_TRANSFORM = 1 << 5, WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED = 1 << 6, WLR_OUTPUT_STATE_GAMMA_LUT = 1 << 7, + WLR_OUTPUT_STATE_LAYERS = 1 << 7, }; enum wlr_output_state_mode_type { @@ -94,6 +95,8 @@ struct wlr_output_state { // only valid if WLR_OUTPUT_STATE_GAMMA_LUT uint16_t *gamma_lut; size_t gamma_lut_size; + + struct wl_list layers; // wlr_output_layer.pending.link }; struct wlr_output_impl; @@ -144,6 +147,8 @@ struct wlr_output { // true for example with VR headsets bool non_desktop; + struct wl_list layers; // wlr_output_layer.current.link + struct wlr_output_state pending; // Commit sequence number. Incremented on each commit, may overflow. diff --git a/include/wlr/types/wlr_output_layer.h b/include/wlr/types/wlr_output_layer.h new file mode 100644 index 000000000..997e4f305 --- /dev/null +++ b/include/wlr/types/wlr_output_layer.h @@ -0,0 +1,93 @@ +/* + * 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_OUTPUT_LAYER_H +#define WLR_TYPES_WLR_OUTPUT_LAYER_H + +/** + * Output layers provide an API to display buffers without rendering them. It + * relies on backend features which are not always available: the backend can + * refuse to display a layer. However when the backend accepts to display a + * layer, performance and battery usage will be improved. + * + * When the backend refuses to display a layer, the caller is responsible for + * manually drawing the layer, e.g. with the renderer. The backend will assume + * that the output's primary buffer is used as the composition layer (see + * wlr_output_attach_render and wlr_output_attach_buffer). The backend will + * automatically handle details such as intersection between rejected layers: + * if a layer is rejected, all layers under it will be rejected too. + * + * The caller can use an output test (see wlr_output_test) to find out which + * layer will be accepted. + * + * Operations on output layers are double-buffered. An output commit is needed + * to apply the pending state. + */ + +#include + +enum wlr_output_layer_state_field { + WLR_OUTPUT_LAYER_STATE_LINK = 1 << 0, + WLR_OUTPUT_LAYER_STATE_BUFFER = 1 << 1, + WLR_OUTPUT_LAYER_STATE_POSITION = 1 << 2, +}; + +struct wlr_output_layer_state { + uint32_t committed; // enum wlr_output_layer_state_field + struct wl_list link; + struct wlr_buffer *buffer; // only valid if WLR_OUTPUT_LAYER_STATE_BUFFER + int x, y; // only valid if WLR_OUTPUT_LAYER_STATE_POSITION +}; + +struct wlr_output_layer { + struct wlr_output *output; + /** If true, the backend has accepted to display the layer. If false, the + * compositor needs to manually render the layer. After each output test or + * commit, the backend will update this field. */ + bool accepted; + + struct wlr_output_layer_state current, pending; +}; + +/** + * Create a new output layer. + */ +struct wlr_output_layer *wlr_output_layer_create(struct wlr_output *output); +/** + * Remove the output layer. This operation is double-buffered, see + * wlr_output_commit. + * + * Callers must not access the wlr_output_layer after calling this function. + */ +void wlr_output_layer_remove(struct wlr_output_layer *layer); +/** + * Attach a buffer to the layer. This operation is double-buffered, see + * wlr_output_commit. + */ +void wlr_output_layer_attach_buffer(struct wlr_output_layer *layer, + struct wlr_buffer *buffer); +/** + * Set the position of the layer relative to the output. The coordinates are + * given in output-buffer-local coordinates. This operation is double-buffered, + * see wlr_output_commit. + */ +void wlr_output_layer_move(struct wlr_output_layer *layer, int x, int y); +/** + * Move the layer right above the specified sibling. This operation is + * double-buffered, see wlr_output_commit. + */ +void wlr_output_layer_place_above(struct wlr_output_layer *layer, + struct wlr_output_layer *sibling); +/** + * Move the layer right below the specified sibling. This operation is + * double-buffered, see wlr_output_commit. + */ +void wlr_output_layer_place_below(struct wlr_output_layer *layer, + struct wlr_output_layer *sibling); + +#endif diff --git a/types/meson.build b/types/meson.build index 89e834cec..ec9957982 100644 --- a/types/meson.build +++ b/types/meson.build @@ -45,6 +45,7 @@ wlr_files += files( 'wlr_matrix.c', 'wlr_output_damage.c', 'wlr_output_layout.c', + 'wlr_output_layer.c', 'wlr_output_management_v1.c', 'wlr_output_power_management_v1.c', 'wlr_pointer_constraints_v1.c', diff --git a/types/output/output.c b/types/output/output.c index 6dc09d3a7..b04f3dbd9 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -7,6 +7,7 @@ #include #include "render/swapchain.h" #include "types/wlr_output.h" +#include "types/wlr_output_layer.h" #include "util/global.h" #include "util/signal.h" @@ -360,6 +361,8 @@ void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, wl_signal_init(&output->events.description); wl_signal_init(&output->events.destroy); pixman_region32_init(&output->pending.damage); + wl_list_init(&output->layers); + wl_list_init(&output->pending.layers); const char *no_hardware_cursors = getenv("WLR_NO_HARDWARE_CURSORS"); if (no_hardware_cursors != NULL && strcmp(no_hardware_cursors, "1") == 0) { @@ -505,6 +508,42 @@ void output_pending_resolution(struct wlr_output *output, int *width, } } +static void output_commit_layers(struct wlr_output *output) { + if (output->pending.committed & WLR_OUTPUT_STATE_LAYERS) { + // Update the layer ordering in the current output state + struct wlr_output_layer *layer; + wl_list_for_each(layer, &output->pending.layers, pending.link) { + wl_list_remove(&layer->current.link); + wl_list_insert(output->layers.prev, &layer->current.link); + } + } + + struct wlr_output_layer *layer, *tmp; + wl_list_for_each_safe(layer, tmp, &output->layers, current.link) { + if (wl_list_empty(&layer->pending.link)) { + output_layer_destroy(layer); + } else { + output_layer_state_move(&layer->current, &layer->pending); + } + } +} + +static void output_rollback_layers(struct wlr_output *output) { + if (output->pending.committed & WLR_OUTPUT_STATE_LAYERS) { + // Rollback the layer ordering in the pending output state + struct wlr_output_layer *layer; + wl_list_for_each(layer, &output->layers, current.link) { + wl_list_remove(&layer->pending.link); + wl_list_insert(output->pending.layers.prev, &layer->pending.link); + } + } + + struct wlr_output_layer *layer, *tmp; + wl_list_for_each_safe(layer, tmp, &output->pending.layers, pending.link) { + output_layer_state_clear(&layer->pending); + } +} + static bool output_basic_test(struct wlr_output *output) { if (output->pending.committed & WLR_OUTPUT_STATE_BUFFER) { if (output->frame_pending) { @@ -628,7 +667,7 @@ bool wlr_output_commit(struct wlr_output *output) { if (!output->impl->commit(output)) { wlr_buffer_unlock(back_buffer); - output_state_clear(&output->pending); + wlr_output_rollback(output); return false; } @@ -690,6 +729,7 @@ bool wlr_output_commit(struct wlr_output *output) { } uint32_t committed = output->pending.committed; + output_commit_layers(output); output_state_clear(&output->pending); struct wlr_output_event_commit event = { @@ -704,6 +744,7 @@ bool wlr_output_commit(struct wlr_output *output) { void wlr_output_rollback(struct wlr_output *output) { output_clear_back_buffer(output); + output_rollback_layers(output); output_state_clear(&output->pending); } diff --git a/types/wlr_output_layer.c b/types/wlr_output_layer.c new file mode 100644 index 000000000..154974ae1 --- /dev/null +++ b/types/wlr_output_layer.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include "types/wlr_output_layer.h" + +struct wlr_output_layer *wlr_output_layer_create(struct wlr_output *output) { + if (output->impl->create_layer) { + return output->impl->create_layer(output); + } + + struct wlr_output_layer *layer = calloc(1, sizeof(*layer)); + if (layer == NULL) { + return NULL; + } + wlr_output_layer_init(layer, output); + return layer; +} + +void wlr_output_layer_init(struct wlr_output_layer *layer, + struct wlr_output *output) { + layer->output = output; + wl_list_insert(output->layers.prev, &layer->current.link); + wl_list_insert(output->pending.layers.prev, &layer->pending.link); + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_LINK; + output->pending.committed |= WLR_OUTPUT_STATE_LAYERS; +} + +void output_layer_destroy(struct wlr_output_layer *layer) { + output_layer_state_clear(&layer->current); + output_layer_state_clear(&layer->pending); + + wl_list_remove(&layer->current.link); + wl_list_remove(&layer->pending.link); + + if (layer->output->impl->destroy_layer) { + layer->output->impl->destroy_layer(layer); + } else { + free(layer); + } +} + +void wlr_output_layer_remove(struct wlr_output_layer *layer) { + wlr_output_layer_attach_buffer(layer, NULL); + + wl_list_remove(&layer->pending.link); + wl_list_init(&layer->pending.link); + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_LINK; + layer->output->pending.committed |= WLR_OUTPUT_STATE_LAYERS; +} + +void wlr_output_layer_attach_buffer(struct wlr_output_layer *layer, + struct wlr_buffer *buffer) { + if (buffer == layer->current.buffer) { + layer->pending.committed &= ~WLR_OUTPUT_LAYER_STATE_BUFFER; + return; + } + + wlr_buffer_unlock(layer->pending.buffer); + layer->pending.buffer = NULL; + + if (buffer == NULL) { + return; + } + + layer->pending.buffer = wlr_buffer_lock(buffer); + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_BUFFER; +} + +void wlr_output_layer_move(struct wlr_output_layer *layer, int x, int y) { + if (x == layer->current.x && y == layer->current.y) { + layer->pending.committed &= ~WLR_OUTPUT_LAYER_STATE_POSITION; + return; + } + + layer->pending.x = x; + layer->pending.y = y; + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_POSITION; +} + +void wlr_output_layer_place_above(struct wlr_output_layer *layer, + struct wlr_output_layer *sibling) { + assert(layer->output == sibling->output); + + wl_list_remove(&layer->pending.link); + wl_list_insert(&sibling->pending.link, &layer->pending.link); + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_LINK; + layer->output->pending.committed |= WLR_OUTPUT_STATE_LAYERS; +} + +void wlr_output_layer_place_below(struct wlr_output_layer *layer, + struct wlr_output_layer *sibling) { + assert(layer->output == sibling->output); + + wl_list_remove(&layer->pending.link); + wl_list_insert(sibling->pending.link.prev, &layer->pending.link); + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_LINK; + layer->output->pending.committed |= WLR_OUTPUT_STATE_LAYERS; +} + +static void output_layer_state_copy(struct wlr_output_layer_state *dst, + struct wlr_output_layer_state *src) { + // link has already been taken care of + if (src->committed & WLR_OUTPUT_LAYER_STATE_BUFFER) { + wlr_buffer_unlock(dst->buffer); + if (src->buffer != NULL) { + dst->buffer = wlr_buffer_lock(src->buffer); + } else { + dst->buffer = NULL; + } + } + if (src->committed & WLR_OUTPUT_LAYER_STATE_POSITION) { + dst->x = src->x; + dst->y = src->y; + } + dst->committed |= src->committed; +} + +void output_layer_state_clear(struct wlr_output_layer_state *state) { + wlr_buffer_unlock(state->buffer); + state->buffer = NULL; + state->committed = 0; +} + +void output_layer_state_move(struct wlr_output_layer_state *dst, + struct wlr_output_layer_state *src) { + output_layer_state_copy(dst, src); + output_layer_state_clear(src); +}