mirror of
https://gitlab.freedesktop.org/wayland/weston.git
synced 2025-12-20 02:20:13 +01:00
For now limited to coefficients and ranges that are typically supported by KMS drivers. We notably leave out alpha modes and chroma locations for now. The protocol initialization is guarded by the WESTON_CAP_COLOR_REP backend capability and thus not enabled anywhere yet. Signed-off-by: Robert Mader <robert.mader@collabora.com>
1038 lines
32 KiB
C
1038 lines
32 KiB
C
/*
|
|
* Copyright © 2010-2011 Intel Corporation
|
|
* Copyright © 2008-2011 Kristian Høgsberg
|
|
* Copyright © 2012-2018, 2021 Collabora, Ltd.
|
|
* Copyright © 2017, 2018 General Electric Company
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the
|
|
* next paragraph) shall be included in all copies or substantial
|
|
* portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <libweston/libweston.h>
|
|
#include <libweston/commit-timing.h>
|
|
#include <libweston/fifo.h>
|
|
#include "libweston-internal.h"
|
|
|
|
#include "backend.h"
|
|
#include "color-representation.h"
|
|
#include "pixel-formats.h"
|
|
#include "shared/fd-util.h"
|
|
#include "shared/timespec-util.h"
|
|
#include "shared/weston-assert.h"
|
|
#include "shared/xalloc.h"
|
|
#include <sys/timerfd.h>
|
|
#include "timeline.h"
|
|
#include "weston-trace.h"
|
|
|
|
/* Deferred content updates:
|
|
*
|
|
* In the absence of readiness constraints, weston will apply content updates
|
|
* as they're delivered from clients via wl_surface.commit() requests.
|
|
*
|
|
* When readiness constraints exist, we must instead store the content update
|
|
* for later application. This is done by creating a weston_content_update,
|
|
* which is a wrapper for a weston_surface_state to be applied to a
|
|
* weston_surface.
|
|
*
|
|
* The weston_content_update is then added to a weston_transaction, which is
|
|
* an atomic group of content updates that can only be applied when the
|
|
* entire set is ready.
|
|
*
|
|
* The transactions themselves are stored within weston_transaction_queues,
|
|
* which contain an ordered sequence of transactions, each of which depends
|
|
* on the one before it in the list. Only the head transaction can be
|
|
* considered for application.
|
|
*
|
|
* The compositor holds a list of these queues, and will consider the head
|
|
* of each list any time transactions are applied (which must happen
|
|
* immediately before we "latch" content for a repaint, and some time
|
|
* after that repaint clears but before outputs are selected for the
|
|
* next repaint)
|
|
*/
|
|
struct weston_transaction_queue {
|
|
struct wl_list link; /* weston_compositor::transaction_queue_list */
|
|
struct wl_list transaction_list; /* weston_transaction::link */
|
|
};
|
|
|
|
struct weston_transaction {
|
|
struct weston_transaction_queue *queue;
|
|
uint64_t flow_id;
|
|
struct wl_list link; /* weston_transaction_queue::transaction_list */
|
|
struct wl_list content_update_list; /* weston_content_update::link */
|
|
};
|
|
|
|
struct weston_content_update {
|
|
struct weston_transaction *transaction;
|
|
struct weston_surface *surface;
|
|
struct weston_surface_state state;
|
|
struct wl_listener surface_destroy_listener;
|
|
struct wl_list link; /* weston_transaction::content_update_list */
|
|
};
|
|
|
|
static void
|
|
weston_surface_apply(struct weston_surface *surface,
|
|
struct weston_surface_state *state);
|
|
|
|
uint32_t
|
|
weston_surface_visibility_mask(struct weston_surface *surface)
|
|
{
|
|
struct weston_view *view;
|
|
uint32_t visibility_mask;
|
|
|
|
/* Assume the surface is visible on any output without up to date
|
|
* visibility information.
|
|
*/
|
|
visibility_mask = surface->output_visibility_dirty_mask;
|
|
|
|
/* We can skip the loop if it's dirty everywhere */
|
|
if (visibility_mask == surface->output_mask)
|
|
return visibility_mask;
|
|
|
|
wl_list_for_each(view, &surface->views, surface_link)
|
|
visibility_mask |= view->output_visibility_mask;
|
|
|
|
return visibility_mask;
|
|
}
|
|
|
|
static void
|
|
weston_surface_dirty_paint_nodes(struct weston_surface *surface,
|
|
enum weston_paint_node_status status)
|
|
{
|
|
struct weston_paint_node *node;
|
|
|
|
wl_list_for_each(node, &surface->paint_node_list, surface_link) {
|
|
assert(node->surface == surface);
|
|
|
|
node->status |= status;
|
|
}
|
|
}
|
|
|
|
void
|
|
weston_surface_state_init(struct weston_surface *surface,
|
|
struct weston_surface_state *state)
|
|
{
|
|
state->flow_id = 0;
|
|
state->status = WESTON_SURFACE_CLEAN;
|
|
state->buffer_ref.buffer = NULL;
|
|
state->buf_offset = weston_coord_surface(0, 0, surface);
|
|
|
|
pixman_region32_init(&state->damage_surface);
|
|
pixman_region32_init(&state->damage_buffer);
|
|
pixman_region32_init(&state->opaque);
|
|
region_init_infinite(&state->input);
|
|
|
|
wl_list_init(&state->frame_callback_list);
|
|
wl_list_init(&state->feedback_list);
|
|
|
|
state->buffer_viewport.buffer.transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
|
state->buffer_viewport.buffer.scale = 1;
|
|
state->buffer_viewport.buffer.src_width = wl_fixed_from_int(-1);
|
|
state->buffer_viewport.surface.width = -1;
|
|
|
|
state->acquire_fence_fd = -1;
|
|
|
|
state->desired_protection = WESTON_HDCP_DISABLE;
|
|
state->protection_mode = WESTON_SURFACE_PROTECTION_MODE_RELAXED;
|
|
|
|
state->color_profile = NULL;
|
|
state->render_intent = NULL;
|
|
|
|
weston_reset_color_representation(&state->color_representation);
|
|
|
|
state->fifo_barrier = false;
|
|
state->fifo_wait = false;
|
|
|
|
state->update_time.valid = false;
|
|
state->update_time.satisfied = false;
|
|
state->update_time.time.tv_sec = 0;
|
|
state->update_time.time.tv_nsec = 0;
|
|
}
|
|
|
|
void
|
|
weston_surface_state_fini(struct weston_surface_state *state)
|
|
{
|
|
struct wl_resource *cb, *next;
|
|
|
|
state->flow_id = 0;
|
|
wl_resource_for_each_safe(cb, next, &state->frame_callback_list)
|
|
wl_resource_destroy(cb);
|
|
|
|
weston_presentation_feedback_discard_list(&state->feedback_list);
|
|
|
|
pixman_region32_fini(&state->input);
|
|
pixman_region32_fini(&state->opaque);
|
|
pixman_region32_fini(&state->damage_surface);
|
|
pixman_region32_fini(&state->damage_buffer);
|
|
|
|
weston_buffer_reference(&state->buffer_ref, NULL,
|
|
BUFFER_WILL_NOT_BE_ACCESSED);
|
|
|
|
fd_clear(&state->acquire_fence_fd);
|
|
weston_buffer_release_reference(&state->buffer_release_ref, NULL);
|
|
|
|
weston_color_profile_unref(state->color_profile);
|
|
state->color_profile = NULL;
|
|
state->render_intent = NULL;
|
|
}
|
|
|
|
static enum weston_surface_status
|
|
weston_surface_attach(struct weston_surface *surface,
|
|
struct weston_surface_state *state,
|
|
enum weston_surface_status status)
|
|
{
|
|
WESTON_TRACE_FUNC_FLOW(&surface->flow_id);
|
|
struct weston_buffer *buffer = state->buffer_ref.buffer;
|
|
struct weston_buffer *old_buffer = surface->buffer_ref.buffer;
|
|
|
|
if (!buffer) {
|
|
if (weston_surface_is_mapped(surface)) {
|
|
weston_surface_unmap(surface);
|
|
/* This is the unmapping commit */
|
|
surface->is_unmapping = true;
|
|
status |= WESTON_SURFACE_DIRTY_BUFFER;
|
|
status |= WESTON_SURFACE_DIRTY_BUFFER_PARAMS;
|
|
status |= WESTON_SURFACE_DIRTY_SIZE;
|
|
}
|
|
|
|
weston_buffer_reference(&surface->buffer_ref, NULL,
|
|
BUFFER_WILL_NOT_BE_ACCESSED);
|
|
|
|
surface->width_from_buffer = 0;
|
|
surface->height_from_buffer = 0;
|
|
|
|
return status;
|
|
}
|
|
|
|
/* Recalculate the surface size if the buffer dimensions or the
|
|
* surface transforms (viewport, rotation/mirror, scale) have
|
|
* changed. */
|
|
if (!old_buffer ||
|
|
buffer->width != old_buffer->width ||
|
|
buffer->height != old_buffer->height ||
|
|
(status & WESTON_SURFACE_DIRTY_SIZE)) {
|
|
struct weston_buffer_viewport *vp = &state->buffer_viewport;
|
|
int32_t old_width = surface->width_from_buffer;
|
|
int32_t old_height = surface->height_from_buffer;
|
|
bool size_ok;
|
|
|
|
size_ok = convert_buffer_size_by_transform_scale(&surface->width_from_buffer,
|
|
&surface->height_from_buffer,
|
|
buffer, vp);
|
|
weston_assert_true(surface->compositor, size_ok);
|
|
|
|
if (surface->width_from_buffer != old_width ||
|
|
surface->height_from_buffer != old_height) {
|
|
status |= WESTON_SURFACE_DIRTY_SIZE;
|
|
}
|
|
}
|
|
|
|
if (!old_buffer ||
|
|
buffer->pixel_format != old_buffer->pixel_format ||
|
|
buffer->format_modifier != old_buffer->format_modifier) {
|
|
surface->is_opaque = pixel_format_is_opaque(buffer->pixel_format);
|
|
status |= WESTON_SURFACE_DIRTY_BUFFER_PARAMS;
|
|
weston_surface_dirty_paint_nodes(surface,
|
|
WESTON_PAINT_NODE_BUFFER_PARAMS_DIRTY);
|
|
}
|
|
|
|
status |= WESTON_SURFACE_DIRTY_BUFFER;
|
|
weston_surface_dirty_paint_nodes(surface,
|
|
WESTON_PAINT_NODE_BUFFER_DIRTY);
|
|
old_buffer = NULL;
|
|
weston_buffer_reference(&surface->buffer_ref, buffer,
|
|
BUFFER_MAY_BE_ACCESSED);
|
|
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
weston_surface_apply_subsurface_order(struct weston_surface *surface)
|
|
{
|
|
struct weston_compositor *comp = surface->compositor;
|
|
struct weston_subsurface *sub;
|
|
struct weston_view *view;
|
|
|
|
wl_list_for_each_reverse(sub, &surface->subsurface_list_pending,
|
|
parent_link_pending) {
|
|
wl_list_remove(&sub->parent_link);
|
|
wl_list_insert(&surface->subsurface_list, &sub->parent_link);
|
|
wl_list_for_each(view, &sub->surface->views, surface_link)
|
|
weston_view_geometry_dirty(view);
|
|
}
|
|
weston_assert_true(comp, comp->view_list_needs_rebuild);
|
|
}
|
|
|
|
/* Translate pending damage in buffer co-ordinates to surface
|
|
* co-ordinates and union it with a pixman_region32_t.
|
|
* This should only be called after the buffer is attached.
|
|
*/
|
|
static void
|
|
apply_damage_buffer(pixman_region32_t *dest,
|
|
struct weston_surface *surface,
|
|
struct weston_surface_state *state)
|
|
{
|
|
struct weston_buffer *buffer = surface->buffer_ref.buffer;
|
|
pixman_region32_t buffer_damage;
|
|
|
|
/* wl_surface.damage_buffer needs to be clipped to the buffer,
|
|
* translated into surface co-ordinates and unioned with
|
|
* any other surface damage.
|
|
* None of this makes sense if there is no buffer though.
|
|
*/
|
|
if (!buffer || !pixman_region32_not_empty(&state->damage_buffer))
|
|
return;
|
|
|
|
pixman_region32_intersect_rect(&state->damage_buffer,
|
|
&state->damage_buffer,
|
|
0, 0,
|
|
buffer->width, buffer->height);
|
|
pixman_region32_init(&buffer_damage);
|
|
weston_matrix_transform_region(&buffer_damage,
|
|
&surface->buffer_to_surface_matrix,
|
|
&state->damage_buffer);
|
|
pixman_region32_union(dest, dest, &buffer_damage);
|
|
pixman_region32_fini(&buffer_damage);
|
|
}
|
|
|
|
static void
|
|
weston_surface_set_desired_protection(struct weston_surface *surface,
|
|
enum weston_hdcp_protection protection)
|
|
{
|
|
struct weston_paint_node *pnode;
|
|
|
|
if (surface->desired_protection == protection)
|
|
return;
|
|
|
|
surface->desired_protection = protection;
|
|
|
|
wl_list_for_each(pnode, &surface->paint_node_list, surface_link) {
|
|
if (pixman_region32_not_empty(&pnode->visible))
|
|
weston_output_damage(pnode->output);
|
|
}
|
|
}
|
|
|
|
static void
|
|
weston_surface_set_protection_mode(struct weston_surface *surface,
|
|
enum weston_surface_protection_mode p_mode)
|
|
{
|
|
struct content_protection *cp = surface->compositor->content_protection;
|
|
struct protected_surface *psurface;
|
|
|
|
surface->protection_mode = p_mode;
|
|
wl_list_for_each(psurface, &cp->protected_list, link) {
|
|
if (!psurface || psurface->surface != surface)
|
|
continue;
|
|
weston_protected_surface_send_event(psurface,
|
|
surface->current_protection);
|
|
}
|
|
}
|
|
|
|
static bool
|
|
weston_surface_status_invalidates_visibility(enum weston_surface_status status)
|
|
{
|
|
return status & WESTON_SURFACE_DIRTY_SIZE ||
|
|
status & WESTON_SURFACE_DIRTY_POS ||
|
|
status & WESTON_SURFACE_DIRTY_BUFFER_PARAMS ||
|
|
status & WESTON_SURFACE_DIRTY_SUBSURFACE_CONFIG;
|
|
}
|
|
|
|
static enum weston_surface_status
|
|
weston_surface_apply_state(struct weston_surface *surface,
|
|
struct weston_surface_state *state)
|
|
{
|
|
WESTON_TRACE_FUNC_FLOW(&state->flow_id);
|
|
struct weston_view *view;
|
|
pixman_region32_t opaque;
|
|
enum weston_surface_status status = state->status;
|
|
|
|
assert(!surface->compositor->latched);
|
|
|
|
surface->flow_id = state->flow_id;
|
|
state->flow_id = 0;
|
|
|
|
/* wl_surface.set_buffer_transform */
|
|
/* wl_surface.set_buffer_scale */
|
|
/* wp_viewport.set_source */
|
|
/* wp_viewport.set_destination */
|
|
surface->buffer_viewport = state->buffer_viewport;
|
|
|
|
/* wp_presentation.feedback */
|
|
weston_presentation_feedback_discard_list(&surface->feedback_list);
|
|
|
|
/* wp_color_representation_v1.set_alpha_mode */
|
|
/* wp_color_representation_v1.set_coefficients_and_range */
|
|
/* wp_color_representation_v1.set_chroma_location */
|
|
surface->color_representation = state->color_representation;
|
|
|
|
/* wl_surface.attach */
|
|
if (status & WESTON_SURFACE_DIRTY_BUFFER) {
|
|
/* zwp_surface_synchronization_v1.set_acquire_fence */
|
|
fd_move(&surface->acquire_fence_fd,
|
|
&state->acquire_fence_fd);
|
|
/* zwp_surface_synchronization_v1.get_release */
|
|
weston_buffer_release_move(&surface->buffer_release_ref,
|
|
&state->buffer_release_ref);
|
|
|
|
status |= weston_surface_attach(surface, state, status);
|
|
}
|
|
weston_buffer_reference(&state->buffer_ref, NULL,
|
|
BUFFER_WILL_NOT_BE_ACCESSED);
|
|
assert(state->acquire_fence_fd == -1);
|
|
assert(state->buffer_release_ref.buffer_release == NULL);
|
|
|
|
if (status & WESTON_SURFACE_DIRTY_SIZE) {
|
|
weston_surface_build_buffer_matrix(surface,
|
|
&surface->surface_to_buffer_matrix);
|
|
weston_matrix_invert(&surface->buffer_to_surface_matrix,
|
|
&surface->surface_to_buffer_matrix);
|
|
weston_surface_dirty_paint_nodes(surface,
|
|
WESTON_PAINT_NODE_VIEW_DIRTY);
|
|
weston_surface_update_size(surface);
|
|
}
|
|
|
|
if ((status & (WESTON_SURFACE_DIRTY_BUFFER | WESTON_SURFACE_DIRTY_SIZE |
|
|
WESTON_SURFACE_DIRTY_POS)) &&
|
|
surface->committed)
|
|
surface->committed(surface, state->buf_offset);
|
|
|
|
state->buf_offset = weston_coord_surface(0, 0, surface);
|
|
|
|
/* wl_surface.damage and wl_surface.damage_buffer; only valid
|
|
* in the same cycle as wl_surface.commit */
|
|
if (status & WESTON_SURFACE_DIRTY_BUFFER) {
|
|
TL_POINT(surface->compositor, TLP_CORE_COMMIT_DAMAGE,
|
|
TLP_SURFACE(surface), TLP_END);
|
|
|
|
pixman_region32_union(&surface->damage, &surface->damage,
|
|
&state->damage_surface);
|
|
|
|
apply_damage_buffer(&surface->damage, surface, state);
|
|
surface->frame_commit_counter++;
|
|
|
|
pixman_region32_intersect_rect(&surface->damage,
|
|
&surface->damage,
|
|
0, 0,
|
|
surface->width, surface->height);
|
|
}
|
|
pixman_region32_clear(&state->damage_buffer);
|
|
pixman_region32_clear(&state->damage_surface);
|
|
|
|
/* wl_surface.set_opaque_region */
|
|
if (status & (WESTON_SURFACE_DIRTY_SIZE |
|
|
WESTON_SURFACE_DIRTY_BUFFER_PARAMS)) {
|
|
pixman_region32_init(&opaque);
|
|
pixman_region32_intersect_rect(&opaque, &state->opaque,
|
|
0, 0,
|
|
surface->width, surface->height);
|
|
|
|
if (!pixman_region32_equal(&opaque, &surface->opaque)) {
|
|
pixman_region32_copy(&surface->opaque, &opaque);
|
|
wl_list_for_each(view, &surface->views, surface_link) {
|
|
weston_view_geometry_dirty_internal(view);
|
|
weston_view_update_transform(view);
|
|
}
|
|
}
|
|
|
|
pixman_region32_fini(&opaque);
|
|
}
|
|
|
|
/* wl_surface.set_input_region */
|
|
if (status & (WESTON_SURFACE_DIRTY_SIZE | WESTON_SURFACE_DIRTY_INPUT)) {
|
|
pixman_region32_intersect_rect(&surface->input, &state->input,
|
|
0, 0,
|
|
surface->width, surface->height);
|
|
}
|
|
|
|
/* wl_surface.frame */
|
|
wl_list_insert_list(&surface->frame_callback_list,
|
|
&state->frame_callback_list);
|
|
wl_list_init(&state->frame_callback_list);
|
|
|
|
/* XXX:
|
|
* What should happen with a feedback request, if there
|
|
* is no wl_buffer attached for this commit?
|
|
*/
|
|
|
|
/* presentation.feedback */
|
|
wl_list_insert_list(&surface->feedback_list,
|
|
&state->feedback_list);
|
|
wl_list_init(&state->feedback_list);
|
|
|
|
/* weston_protected_surface.enforced/relaxed */
|
|
if (surface->protection_mode != state->protection_mode)
|
|
weston_surface_set_protection_mode(surface,
|
|
state->protection_mode);
|
|
|
|
/* weston_protected_surface.set_type */
|
|
weston_surface_set_desired_protection(surface, state->desired_protection);
|
|
|
|
/* color_management_surface_v1_interface.set_image_description or
|
|
* color_management_surface_v1_interface.unset_image_description */
|
|
weston_surface_set_color_profile(surface, state->color_profile,
|
|
state->render_intent);
|
|
|
|
wl_signal_emit(&surface->commit_signal, surface);
|
|
|
|
if (status & WESTON_SURFACE_DIRTY_SUBSURFACE_CONFIG)
|
|
weston_surface_apply_subsurface_order(surface);
|
|
|
|
/* Surface is now quiescent */
|
|
surface->is_unmapping = false;
|
|
surface->is_mapping = false;
|
|
|
|
if (state->fifo_barrier)
|
|
weston_fifo_surface_set_barrier(surface);
|
|
state->fifo_barrier = false;
|
|
|
|
if (weston_surface_status_invalidates_visibility(status))
|
|
surface->output_visibility_dirty_mask |= surface->output_mask;
|
|
|
|
/* If we have a target time and a driving output, we can try to use
|
|
* VRR to move the display time to hit it. If a repaint is already
|
|
* scheduled, then its exact time was used to satisfy our time
|
|
* constraint, so don't mess with it.
|
|
*
|
|
* We also need to make sure that if a bunch of updates become ready
|
|
* all at once, that we keep forced_present monotonic, so nothing
|
|
* is presented early.
|
|
*/
|
|
if (state->update_time.valid && surface->output &&
|
|
surface->output->repaint_status != REPAINT_SCHEDULED) {
|
|
if (!surface->output->forced_present.valid ||
|
|
timespec_sub_to_nsec(&state->update_time.time,
|
|
&surface->output->forced_present.time) > 0) {
|
|
surface->output->forced_present = state->update_time;
|
|
}
|
|
}
|
|
|
|
weston_commit_timing_clear_target(&state->update_time);
|
|
|
|
state->status = WESTON_SURFACE_CLEAN;
|
|
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
weston_subsurface_parent_apply(struct weston_subsurface *sub)
|
|
{
|
|
struct weston_view *view;
|
|
|
|
if (sub->position.changed) {
|
|
wl_list_for_each(view, &sub->surface->views, surface_link)
|
|
weston_view_set_rel_position(view,
|
|
sub->position.offset);
|
|
|
|
sub->position.changed = false;
|
|
}
|
|
|
|
if (sub->effectively_synchronized)
|
|
weston_surface_apply(sub->surface, &sub->cached);
|
|
}
|
|
|
|
|
|
/**
|
|
* \param surface The surface to be repainted
|
|
* \param status The weston_surface_status last applied
|
|
*
|
|
* Marks the output(s) that the surface is shown on as needing to be
|
|
* repainted. Tries to avoid repaints on occluded surfaces when
|
|
* possible by checking surface status dirty bits.
|
|
*
|
|
* See weston_output_schedule_repaint().
|
|
*/
|
|
static void
|
|
weston_surface_schedule_repaint(struct weston_surface *surface,
|
|
enum weston_surface_status status)
|
|
{
|
|
struct weston_output *output;
|
|
uint32_t visible_mask;
|
|
|
|
if (surface->output && surface->fifo_barrier)
|
|
weston_output_schedule_repaint(surface->output);
|
|
|
|
if (status == WESTON_SURFACE_CLEAN)
|
|
return;
|
|
|
|
visible_mask = weston_surface_visibility_mask(surface);
|
|
wl_list_for_each(output, &surface->compositor->output_list, link) {
|
|
if (visible_mask & (1u << output->id))
|
|
weston_output_schedule_repaint(output);
|
|
}
|
|
}
|
|
|
|
static void
|
|
weston_surface_apply(struct weston_surface *surface,
|
|
struct weston_surface_state *state)
|
|
{
|
|
WESTON_TRACE_FUNC_FLOW(&state->flow_id);
|
|
struct weston_subsurface *sub;
|
|
enum weston_surface_status status;
|
|
|
|
status = weston_surface_apply_state(surface, state);
|
|
|
|
weston_surface_schedule_repaint(surface, status);
|
|
|
|
wl_list_for_each(sub, &surface->subsurface_list, parent_link) {
|
|
if (sub->surface != surface)
|
|
weston_subsurface_parent_apply(sub);
|
|
}
|
|
}
|
|
|
|
static void
|
|
weston_surface_state_merge_from(struct weston_surface_state *dst,
|
|
struct weston_surface_state *src,
|
|
struct weston_surface *surface)
|
|
{
|
|
WESTON_TRACE_FUNC_FLOW(&dst->flow_id);
|
|
src->flow_id = 0;
|
|
|
|
|
|
/*
|
|
* If this commit would cause the surface to move by the
|
|
* attach(dx, dy) parameters, the old damage region must be
|
|
* translated to correspond to the new surface coordinate system
|
|
* origin.
|
|
*/
|
|
if (surface->pending.status & WESTON_SURFACE_DIRTY_POS) {
|
|
pixman_region32_translate(&dst->damage_surface,
|
|
-src->buf_offset.c.x,
|
|
-src->buf_offset.c.y);
|
|
}
|
|
pixman_region32_union(&dst->damage_surface,
|
|
&dst->damage_surface,
|
|
&src->damage_surface);
|
|
pixman_region32_clear(&src->damage_surface);
|
|
|
|
pixman_region32_union(&dst->damage_buffer,
|
|
&dst->damage_buffer,
|
|
&src->damage_buffer);
|
|
pixman_region32_clear(&src->damage_buffer);
|
|
|
|
dst->render_intent = src->render_intent;
|
|
weston_color_profile_unref(dst->color_profile);
|
|
dst->color_profile =
|
|
weston_color_profile_ref(src->color_profile);
|
|
|
|
weston_presentation_feedback_discard_list(&dst->feedback_list);
|
|
|
|
if (src->status & WESTON_SURFACE_DIRTY_BUFFER) {
|
|
weston_buffer_reference(&dst->buffer_ref,
|
|
src->buffer_ref.buffer,
|
|
src->buffer_ref.buffer ?
|
|
BUFFER_MAY_BE_ACCESSED :
|
|
BUFFER_WILL_NOT_BE_ACCESSED);
|
|
/* zwp_surface_synchronization_v1.set_acquire_fence */
|
|
fd_move(&dst->acquire_fence_fd,
|
|
&src->acquire_fence_fd);
|
|
/* zwp_surface_synchronization_v1.get_release */
|
|
weston_buffer_release_move(&dst->buffer_release_ref,
|
|
&src->buffer_release_ref);
|
|
}
|
|
dst->desired_protection = src->desired_protection;
|
|
dst->protection_mode = src->protection_mode;
|
|
assert(src->acquire_fence_fd == -1);
|
|
assert(src->buffer_release_ref.buffer_release == NULL);
|
|
dst->buf_offset = weston_coord_surface_add(dst->buf_offset,
|
|
src->buf_offset);
|
|
|
|
dst->buffer_viewport.buffer = src->buffer_viewport.buffer;
|
|
dst->buffer_viewport.surface = src->buffer_viewport.surface;
|
|
|
|
dst->color_representation = src->color_representation;
|
|
|
|
weston_buffer_reference(&src->buffer_ref,
|
|
NULL, BUFFER_WILL_NOT_BE_ACCESSED);
|
|
|
|
src->buf_offset = weston_coord_surface(0, 0, surface);
|
|
|
|
pixman_region32_copy(&dst->opaque, &src->opaque);
|
|
|
|
pixman_region32_copy(&dst->input, &src->input);
|
|
|
|
wl_list_insert_list(&dst->frame_callback_list,
|
|
&src->frame_callback_list);
|
|
wl_list_init(&src->frame_callback_list);
|
|
|
|
wl_list_insert_list(&dst->feedback_list,
|
|
&src->feedback_list);
|
|
wl_list_init(&src->feedback_list);
|
|
|
|
dst->fifo_barrier = src->fifo_barrier;
|
|
src->fifo_barrier = false;
|
|
dst->fifo_wait = src->fifo_wait;
|
|
src->fifo_wait = false;
|
|
|
|
dst->update_time = src->update_time;
|
|
weston_commit_timing_clear_target(&src->update_time);
|
|
|
|
dst->status |= src->status;
|
|
src->status = WESTON_SURFACE_CLEAN;
|
|
}
|
|
|
|
static struct weston_transaction_queue *
|
|
weston_surface_find_parent_transaction_queue(struct weston_compositor *comp,
|
|
struct weston_surface *surface)
|
|
{
|
|
struct weston_transaction_queue *tq;
|
|
struct weston_transaction *tr;
|
|
struct weston_content_update *cu;
|
|
|
|
wl_list_for_each(tq, &comp->transaction_queue_list, link) {
|
|
wl_list_for_each(tr, &tq->transaction_list, link)
|
|
wl_list_for_each(cu, &tr->content_update_list, link) {
|
|
if (cu->surface == surface)
|
|
return tq;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
weston_content_update_fini(struct weston_content_update *cu)
|
|
{
|
|
wl_list_remove(&cu->link);
|
|
weston_surface_state_fini(&cu->state);
|
|
wl_list_remove(&cu->surface_destroy_listener.link);
|
|
free(cu);
|
|
}
|
|
|
|
static void
|
|
content_update_surface_destroy(struct wl_listener *listener, void *data)
|
|
{
|
|
struct weston_content_update *cu = container_of(listener,
|
|
struct weston_content_update,
|
|
surface_destroy_listener);
|
|
struct weston_transaction *tr = cu->transaction;
|
|
struct weston_transaction_queue *tq = tr->queue;
|
|
|
|
weston_content_update_fini(cu);
|
|
|
|
/* If we were the last update in the transaction, remove it */
|
|
if (wl_list_empty(&tr->content_update_list)) {
|
|
wl_list_remove(&tr->link);
|
|
free(tr);
|
|
}
|
|
|
|
/* If removing a transaction emptied a list, remove that too */
|
|
if (wl_list_empty(&tq->transaction_list)) {
|
|
wl_list_remove(&tq->link);
|
|
free(tq);
|
|
}
|
|
}
|
|
|
|
static void
|
|
weston_transaction_add_content_update(struct weston_transaction *tr,
|
|
struct weston_surface *surface,
|
|
struct weston_surface_state *state)
|
|
{
|
|
struct weston_content_update *cu;
|
|
|
|
cu = xzalloc(sizeof *cu);
|
|
cu->transaction = tr;
|
|
/* Since surfaces don't maintain a list of transactions they're on, we
|
|
* can either have the surface destructor walk all transaction lists
|
|
* to remove any content updates for a destroyed surface, or hook
|
|
* the surface_destroy signal.
|
|
*
|
|
* The latter is a little easier, so set that up.
|
|
*/
|
|
cu->surface_destroy_listener.notify = content_update_surface_destroy;
|
|
wl_signal_add(&surface->destroy_signal,
|
|
&cu->surface_destroy_listener);
|
|
cu->surface = surface;
|
|
weston_surface_state_init(surface, &cu->state);
|
|
|
|
cu->state.flow_id = state->flow_id;
|
|
weston_surface_state_merge_from(&cu->state, state, surface);
|
|
|
|
wl_list_insert(&tr->content_update_list, &cu->link);
|
|
}
|
|
|
|
static void
|
|
weston_surface_create_transaction(struct weston_compositor *comp,
|
|
struct weston_surface *surface,
|
|
struct weston_surface_state *state)
|
|
{
|
|
uint64_t transaction_flow_id = 0;
|
|
WESTON_TRACE_FUNC_FLOW(&transaction_flow_id);
|
|
|
|
struct weston_transaction *tr;
|
|
struct weston_transaction_queue *parent;
|
|
bool need_schedule = false;
|
|
|
|
tr = xzalloc(sizeof *tr);
|
|
tr->flow_id = transaction_flow_id;
|
|
wl_list_init(&tr->content_update_list);
|
|
|
|
weston_transaction_add_content_update(tr, surface, state);
|
|
|
|
/* Figure out if we need to be blocked behind an existing transaction */
|
|
parent = weston_surface_find_parent_transaction_queue(comp, surface);
|
|
if (!parent) {
|
|
/* We weren't blocked by any existing transactions, so set up
|
|
* a new list so content updates for this surface can block
|
|
* behind us in the future
|
|
*/
|
|
parent = xzalloc(sizeof *parent);
|
|
wl_list_init(&parent->transaction_list);
|
|
wl_list_insert(&comp->transaction_queue_list, &parent->link);
|
|
need_schedule = true;
|
|
}
|
|
tr->queue = parent;
|
|
wl_list_insert(parent->transaction_list.prev, &tr->link);
|
|
|
|
if (need_schedule)
|
|
weston_repaint_timer_arm(comp);
|
|
}
|
|
|
|
static bool
|
|
weston_surface_state_ready(struct weston_surface *surface,
|
|
struct weston_surface_state *state)
|
|
{
|
|
if (!weston_fifo_surface_state_ready(surface, state))
|
|
return false;
|
|
|
|
if (!weston_commit_timing_surface_state_ready(surface, state))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
weston_surface_commit(struct weston_surface *surface)
|
|
{
|
|
WESTON_TRACE_FUNC_FLOW(&surface->pending.flow_id);
|
|
struct weston_compositor *comp = surface->compositor;
|
|
struct weston_subsurface *sub = weston_surface_to_subsurface(surface);
|
|
struct weston_surface_state *state = &surface->pending;
|
|
struct weston_transaction_queue *tq;
|
|
|
|
if (sub) {
|
|
weston_surface_state_merge_from(&sub->cached,
|
|
state,
|
|
surface);
|
|
if (sub->effectively_synchronized)
|
|
return;
|
|
|
|
state = &sub->cached;
|
|
}
|
|
|
|
/* Check if this surface is a member of a transaction list already.
|
|
* If it is, we're not ready to apply this state, so we'll have
|
|
* to make a new transaction and wait until we are.
|
|
*
|
|
* For now, we don't have a way to combine multiple content updates
|
|
* in a single transaction, so these effectively become per surface
|
|
* update streams.
|
|
*/
|
|
tq = weston_surface_find_parent_transaction_queue(comp, surface);
|
|
if (tq || !weston_surface_state_ready(surface, state)) {
|
|
weston_surface_create_transaction(comp, surface, state);
|
|
return;
|
|
}
|
|
|
|
weston_surface_apply(surface, state);
|
|
}
|
|
|
|
/** Recursively update effectively_synchronized state for a subsurface tree
|
|
*
|
|
* \param sub Subsurface to start from
|
|
*
|
|
* From wayland.xml :
|
|
* Even if a sub-surface is in desynchronized mode, it will behave as
|
|
* in synchronized mode, if its parent surface behaves as in
|
|
* synchronized mode. This rule is applied recursively throughout the
|
|
* tree of surfaces.
|
|
*
|
|
* In Weston, we call a surface "effectively synchronized" if it is either
|
|
* synchronized, or is forced to "behave as in synchronized mode" by a
|
|
* parent surface that is effectively synchronized.
|
|
*
|
|
* Calling weston_subsurface_update_effectively_synchronized on a subsurface
|
|
* will update the tree of subsurfaces to have accurate
|
|
* effectively_synchronized state below that point, by walking all descendants
|
|
* and combining their state with their immediate parent's state.
|
|
*
|
|
* Since every subsurface starts off synchronized, they also start off
|
|
* effectively synchronized, so we only need to call this function in response
|
|
* to synchronization changes from protocol requests (set_sync, set_desync) to
|
|
* keep the subsurface tree state up to date.
|
|
*/
|
|
static void
|
|
weston_subsurface_update_effectively_synchronized(struct weston_subsurface *sub)
|
|
{
|
|
bool parent_e_sync = false;
|
|
struct weston_subsurface *child;
|
|
struct weston_surface *surf = sub->surface;
|
|
WESTON_TRACE_FUNC_FLOW(&surf->flow_id);
|
|
|
|
if (sub->parent) {
|
|
struct weston_subsurface *parent;
|
|
|
|
parent = weston_surface_to_subsurface(sub->parent);
|
|
if (parent)
|
|
parent_e_sync = parent->effectively_synchronized;
|
|
}
|
|
|
|
/* This subsurface will be effectively synchronized if it is
|
|
* explicitly synchronized, or if a parent surface is effectively
|
|
* synchronized.
|
|
*
|
|
* Since we're called for every protocol driven change, and update
|
|
* recursively at that point, we know that the immediate parent
|
|
* state is always up to date, so we only have to test that here.
|
|
*/
|
|
sub->effectively_synchronized = parent_e_sync || sub->synchronized;
|
|
|
|
wl_list_for_each(child, &surf->subsurface_list, parent_link) {
|
|
if (child->surface == surf)
|
|
continue;
|
|
|
|
weston_subsurface_update_effectively_synchronized(child);
|
|
}
|
|
}
|
|
|
|
void
|
|
weston_subsurface_set_synchronized(struct weston_subsurface *sub, bool sync)
|
|
{
|
|
WESTON_TRACE_FUNC_FLOW(&sub->surface->flow_id);
|
|
bool old_e_sync = sub->effectively_synchronized;
|
|
|
|
if (sub->synchronized == sync)
|
|
return;
|
|
|
|
sub->synchronized = sync;
|
|
|
|
weston_subsurface_update_effectively_synchronized(sub);
|
|
|
|
/* If sub became effectively desynchronized, flush */
|
|
if (old_e_sync && !sub->effectively_synchronized)
|
|
weston_surface_apply(sub->surface, &sub->cached);
|
|
}
|
|
|
|
static void
|
|
apply_transaction(struct weston_transaction *transaction)
|
|
{
|
|
WESTON_TRACE_FUNC_FLOW(&transaction->flow_id);
|
|
struct weston_content_update *cu, *tmp;
|
|
|
|
wl_list_remove(&transaction->link);
|
|
|
|
wl_list_for_each_safe(cu, tmp, &transaction->content_update_list, link) {
|
|
weston_surface_apply(cu->surface, &cu->state);
|
|
weston_content_update_fini(cu);
|
|
}
|
|
|
|
free(transaction);
|
|
}
|
|
|
|
static bool
|
|
transaction_ready(struct weston_transaction *transaction)
|
|
{
|
|
WESTON_TRACE_FUNC_FLOW(&transaction->flow_id);
|
|
struct weston_content_update *cu;
|
|
|
|
/* Every content update within the transaction must be ready
|
|
* for the transaction to be applied.
|
|
*/
|
|
wl_list_for_each(cu, &transaction->content_update_list, link)
|
|
if (!weston_surface_state_ready(cu->surface, &cu->state))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
weston_compositor_apply_transactions(struct weston_compositor *compositor)
|
|
{
|
|
WESTON_TRACE_FUNC();
|
|
struct weston_transaction_queue *tq, *tq_tmp;
|
|
struct weston_transaction *tr, *tr_tmp;
|
|
|
|
assert(!compositor->latched);
|
|
|
|
/* The compositor has a list of transaction queues. These queues are
|
|
* independent streams of transactions, and the head of a queue blocks
|
|
* every transaction after it. We must consider (only) each queue head.
|
|
*/
|
|
wl_list_for_each_safe(tq, tq_tmp, &compositor->transaction_queue_list, link) {
|
|
/* Walk this queue and greedily consume any that are ready.
|
|
* As soon as one is not, we're done with the list, as all
|
|
* further members are blocked.
|
|
*/
|
|
wl_list_for_each_safe(tr, tr_tmp, &tq->transaction_list, link) {
|
|
if (!transaction_ready(tr))
|
|
break;
|
|
|
|
apply_transaction(tr);
|
|
}
|
|
|
|
if (wl_list_empty(&tq->transaction_list)) {
|
|
wl_list_remove(&tq->link);
|
|
free(tq);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Update output nearest commit-timing target times
|
|
*
|
|
* \param compositor weston_compositor
|
|
*
|
|
* Updates the list of upcoming deferred content updates so every output
|
|
* with a deferred update has a stored copy of the nearest ready time.
|
|
*/
|
|
void
|
|
weston_commit_timing_update_output_targets(struct weston_compositor *compositor)
|
|
{
|
|
struct weston_transaction_queue *tq;
|
|
struct weston_output *output;
|
|
|
|
weston_commit_timing_clear_target(&compositor->requested_repaint_fallback);
|
|
wl_list_for_each(output, &compositor->output_list, link)
|
|
weston_commit_timing_clear_target(&output->requested_present);
|
|
|
|
wl_list_for_each(tq, &compositor->transaction_queue_list, link) {
|
|
/* First transaction only - it blocks the rest */
|
|
struct weston_transaction *tr = wl_container_of(tq->transaction_list.next, tr, link);
|
|
struct weston_content_update *cu;
|
|
struct weston_commit_timing_target *target;
|
|
|
|
wl_list_for_each(cu, &tr->content_update_list, link) {
|
|
if (!cu->state.update_time.valid)
|
|
continue;
|
|
if (cu->state.update_time.satisfied)
|
|
continue;
|
|
|
|
if (cu->surface->output)
|
|
target = &cu->surface->output->requested_present;
|
|
else
|
|
target = &cu->surface->compositor->requested_repaint_fallback;
|
|
|
|
if (!target->valid ||
|
|
timespec_sub_to_nsec(&target->time,
|
|
&cu->state.update_time.time) > 0)
|
|
*target = cu->state.update_time;
|
|
}
|
|
}
|
|
}
|