/* * 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 #include #include #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 #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; } } }