mirror of
https://gitlab.freedesktop.org/wayland/weston.git
synced 2025-12-20 03:30:19 +01:00
compositor: Support the commit-timing protocol
Introduce support for the commit-timing protocol to allow applications to attach a presentation time to a content update. We use the repaint timer to schedule content updates in advance of the frame time when they should be displayed. Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
This commit is contained in:
parent
65227cb7d4
commit
a3f0e97a71
11 changed files with 753 additions and 2 deletions
|
|
@ -74,6 +74,12 @@ struct weston_point2d_device_normalized {
|
|||
double y;
|
||||
};
|
||||
|
||||
struct weston_commit_timing_target {
|
||||
bool valid;
|
||||
bool satisfied;
|
||||
struct timespec time;
|
||||
};
|
||||
|
||||
struct weston_compositor;
|
||||
struct weston_surface;
|
||||
struct weston_buffer;
|
||||
|
|
@ -437,6 +443,16 @@ struct weston_output {
|
|||
* next repaint should be displayed */
|
||||
struct timespec next_present;
|
||||
|
||||
/** commit-timing protocol will set this for the closest requested
|
||||
* presentation time from a surface on this output. It's only
|
||||
* valid during weston_repaint_timer_arm. */
|
||||
struct weston_commit_timing_target requested_present;
|
||||
/** commit-timing protocol will set this when state containing
|
||||
* a target time is applied. Weston will try to hit this time if
|
||||
* VRR is in use.
|
||||
*/
|
||||
struct weston_commit_timing_target forced_present;
|
||||
|
||||
/** For cancelling the idle_repaint callback on output destruction. */
|
||||
struct wl_event_source *idle_repaint_source;
|
||||
|
||||
|
|
@ -1512,6 +1528,11 @@ struct weston_compositor {
|
|||
bool latched;
|
||||
|
||||
struct wl_list transaction_queue_list; /* weston_transaction_queue::link */
|
||||
|
||||
/** commit_timing_v1 - target repaint time for content updates for
|
||||
* surfaces with no output
|
||||
*/
|
||||
struct weston_commit_timing_target requested_repaint_fallback;
|
||||
};
|
||||
|
||||
struct weston_solid_buffer_values {
|
||||
|
|
@ -1808,6 +1829,9 @@ struct weston_surface_state {
|
|||
/* wp_fifo_v1 */
|
||||
bool fifo_barrier;
|
||||
bool fifo_wait;
|
||||
|
||||
/* commit_timing_v1 */
|
||||
struct weston_commit_timing_target update_time;
|
||||
};
|
||||
|
||||
struct weston_surface_activation_data {
|
||||
|
|
@ -1992,6 +2016,9 @@ struct weston_surface {
|
|||
struct weston_fifo *fifo;
|
||||
bool fifo_barrier; /* Cleared after display */
|
||||
struct wl_list fifo_barrier_link; /* output::fifo_barrier_surfaces */
|
||||
|
||||
/** commit_timing_v1 */
|
||||
struct weston_commit_timer *commit_timer;
|
||||
};
|
||||
|
||||
struct weston_subsurface {
|
||||
|
|
|
|||
270
libweston/commit-timing.c
Normal file
270
libweston/commit-timing.c
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* Copyright 2025 Collabora, Ltd.
|
||||
*
|
||||
* 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 <assert.h>
|
||||
|
||||
#include <libweston/libweston.h>
|
||||
#include <libweston/commit-timing.h>
|
||||
#include "libweston-internal.h"
|
||||
#include "shared/helpers.h"
|
||||
#include "shared/timespec-util.h"
|
||||
#include "shared/xalloc.h"
|
||||
#include "weston-trace.h"
|
||||
|
||||
struct weston_commit_timer {
|
||||
struct weston_surface *surface;
|
||||
struct wl_listener surface_destroy_listener;
|
||||
uint64_t flow_id;
|
||||
};
|
||||
|
||||
static void
|
||||
commit_timer_destructor(struct wl_resource *resource)
|
||||
{
|
||||
struct weston_commit_timer *ct = wl_resource_get_user_data(resource);
|
||||
|
||||
if (ct->surface)
|
||||
wl_list_remove(&ct->surface_destroy_listener.link);
|
||||
|
||||
free(ct);
|
||||
}
|
||||
|
||||
static void
|
||||
commit_timer_set_target_time(struct wl_client *client,
|
||||
struct wl_resource *resource,
|
||||
uint32_t sec_hi,
|
||||
uint32_t sec_lo,
|
||||
uint32_t nsec)
|
||||
{
|
||||
struct weston_commit_timer *ct = wl_resource_get_user_data(resource);
|
||||
struct weston_surface *surface = ct->surface;
|
||||
uint64_t sec_u64 = u64_from_u32s(sec_hi, sec_lo);
|
||||
|
||||
if (!surface) {
|
||||
wl_resource_post_error(resource,
|
||||
WP_COMMIT_TIMER_V1_ERROR_SURFACE_DESTROYED,
|
||||
"surface destroyed");
|
||||
return;
|
||||
}
|
||||
if (surface->pending.update_time.valid) {
|
||||
wl_resource_post_error(resource,
|
||||
WP_COMMIT_TIMER_V1_ERROR_TIMESTAMP_EXISTS,
|
||||
"target timestamp already set");
|
||||
return;
|
||||
}
|
||||
if (nsec > 999999999) {
|
||||
wl_resource_post_error(resource,
|
||||
WP_COMMIT_TIMER_V1_ERROR_INVALID_TIMESTAMP,
|
||||
"target timestamp invalid");
|
||||
return;
|
||||
}
|
||||
if (sec_u64 > INT64_MAX) {
|
||||
wl_resource_post_error(resource,
|
||||
WP_COMMIT_TIMER_V1_ERROR_INVALID_TIMESTAMP,
|
||||
"target timestamp invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
surface->pending.update_time.valid = true;
|
||||
surface->pending.update_time.satisfied = false;
|
||||
surface->pending.update_time.time.tv_sec = (time_t) sec_u64;
|
||||
surface->pending.update_time.time.tv_nsec = nsec;
|
||||
}
|
||||
|
||||
static void
|
||||
commit_timer_destroy(struct wl_client *client, struct wl_resource *resource)
|
||||
{
|
||||
struct weston_commit_timer *ct = wl_resource_get_user_data(resource);
|
||||
struct weston_surface *surface = ct->surface;
|
||||
|
||||
wl_resource_destroy(resource);
|
||||
if (!surface)
|
||||
return;
|
||||
|
||||
surface->commit_timer = NULL;
|
||||
}
|
||||
|
||||
static const struct wp_commit_timer_v1_interface weston_commit_timer_interface = {
|
||||
commit_timer_set_target_time,
|
||||
commit_timer_destroy,
|
||||
};
|
||||
|
||||
static void
|
||||
commit_timing_manager_destroy(struct wl_client *client, struct wl_resource *resource)
|
||||
{
|
||||
wl_resource_destroy(resource);
|
||||
}
|
||||
|
||||
static void
|
||||
commit_timer_surface_destroy_cb(struct wl_listener *listener, void *data)
|
||||
{
|
||||
struct weston_commit_timer *ct =
|
||||
container_of(listener,
|
||||
struct weston_commit_timer, surface_destroy_listener);
|
||||
|
||||
ct->surface = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
commit_timing_manager_get_commit_timer(struct wl_client *client,
|
||||
struct wl_resource *manager_resource,
|
||||
uint32_t id,
|
||||
struct wl_resource *surface_resource)
|
||||
{
|
||||
struct weston_commit_timer *ct;
|
||||
struct weston_surface *surface = wl_resource_get_user_data(surface_resource);
|
||||
struct wl_resource *res;
|
||||
|
||||
if (surface->commit_timer) {
|
||||
wl_resource_post_error(manager_resource,
|
||||
WP_COMMIT_TIMING_MANAGER_V1_ERROR_COMMIT_TIMER_EXISTS,
|
||||
"Commit timing resource already exists on surface");
|
||||
return;
|
||||
}
|
||||
|
||||
res = wl_resource_create(client, &wp_commit_timer_v1_interface,
|
||||
wl_resource_get_version(manager_resource), id);
|
||||
ct = xzalloc(sizeof *ct);
|
||||
ct->surface = surface;
|
||||
surface->commit_timer = ct;
|
||||
ct->surface_destroy_listener.notify = commit_timer_surface_destroy_cb;
|
||||
wl_signal_add(&surface->destroy_signal, &ct->surface_destroy_listener);
|
||||
wl_resource_set_implementation(res, &weston_commit_timer_interface, ct,
|
||||
commit_timer_destructor);
|
||||
}
|
||||
|
||||
static const struct wp_commit_timing_manager_v1_interface weston_commit_timing_manager_v1_interface = {
|
||||
commit_timing_manager_destroy,
|
||||
commit_timing_manager_get_commit_timer,
|
||||
};
|
||||
|
||||
static void
|
||||
bind_commit_timing(struct wl_client *client, void *data, uint32_t version, uint32_t id)
|
||||
{
|
||||
struct wl_resource *resource;
|
||||
struct weston_compositor *compositor = data;
|
||||
|
||||
resource = wl_resource_create(client,
|
||||
&wp_commit_timing_manager_v1_interface,
|
||||
version, id);
|
||||
if (!resource) {
|
||||
wl_client_post_no_memory(client);
|
||||
return;
|
||||
}
|
||||
|
||||
wl_resource_set_implementation(resource,
|
||||
&weston_commit_timing_manager_v1_interface,
|
||||
compositor, NULL);
|
||||
}
|
||||
|
||||
/** Advertise commit-timing protocol support
|
||||
*
|
||||
* Sets up commit_timing_v1 support so it is advertised to clients.
|
||||
*
|
||||
* \param compositor The compositor to init for.
|
||||
* \return Zero on success, -1 on failure.
|
||||
*/
|
||||
int
|
||||
commit_timing_setup(struct weston_compositor *compositor)
|
||||
{
|
||||
if (!wl_global_create(compositor->wl_display,
|
||||
&wp_commit_timing_manager_v1_interface,
|
||||
1, compositor,
|
||||
bind_commit_timing))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Checks if surface state's timing requirements have been satisfied.
|
||||
* Once it's satisfied, it can never become unsatisfied, and we never
|
||||
* need to test it again. We still need to keep the timing information
|
||||
* around in case we're using it to move the frame presentation time
|
||||
* with VRR.
|
||||
*/
|
||||
bool
|
||||
weston_commit_timing_surface_state_ready(struct weston_surface *surface,
|
||||
struct weston_surface_state *state)
|
||||
{
|
||||
struct weston_output *output = surface->output;
|
||||
struct timespec target_repaint;
|
||||
struct timespec now_ts;
|
||||
|
||||
if (!state->update_time.valid || state->update_time.satisfied)
|
||||
return true;
|
||||
|
||||
weston_compositor_read_presentation_clock(surface->compositor,
|
||||
&now_ts);
|
||||
|
||||
if (timespec_sub_to_nsec(&state->update_time.time, &now_ts) < 0)
|
||||
goto ready;
|
||||
|
||||
/* If we have no output, the previous check against wall clock time
|
||||
* is all we can do.
|
||||
*/
|
||||
if (!output)
|
||||
return false;
|
||||
|
||||
/* If the output has a scheduled repaint, we should know for certain
|
||||
* when its content will be displayed, so we know for certain if
|
||||
* this content update is ready or not.
|
||||
*/
|
||||
if (output->repaint_status == REPAINT_SCHEDULED) {
|
||||
int64_t time_since = timespec_sub_to_nsec(&output->next_present,
|
||||
&state->update_time.time);
|
||||
|
||||
if (time_since >= 0)
|
||||
goto ready;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
target_repaint = weston_output_repaint_from_present(output, &now_ts,
|
||||
&state->update_time.time);
|
||||
|
||||
if (timespec_sub_to_nsec(&target_repaint, &now_ts) < 0)
|
||||
goto ready;
|
||||
|
||||
return false;
|
||||
ready:
|
||||
state->update_time.satisfied = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Clear a weston_commit_timing_target
|
||||
*
|
||||
* \param target target to clear
|
||||
*
|
||||
* Sets a timing target to invalid and clears all fields to known state.
|
||||
*/
|
||||
void
|
||||
weston_commit_timing_clear_target(struct weston_commit_timing_target *target)
|
||||
{
|
||||
target->valid = false;
|
||||
target->satisfied = false;
|
||||
target->time.tv_nsec = 0;
|
||||
target->time.tv_sec = 0;
|
||||
}
|
||||
43
libweston/commit-timing.h
Normal file
43
libweston/commit-timing.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2025 Collabora, Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "commit-timing-v1-server-protocol.h"
|
||||
|
||||
int
|
||||
commit_timing_setup(struct weston_compositor *compositor);
|
||||
|
||||
bool
|
||||
weston_commit_timing_surface_state_ready(struct weston_surface *surface,
|
||||
struct weston_surface_state *state);
|
||||
|
||||
void
|
||||
weston_commit_timing_update_output_targets(struct weston_compositor *compositor);
|
||||
|
||||
void
|
||||
weston_commit_timing_clear_target(struct weston_commit_timing_target *target);
|
||||
|
|
@ -91,6 +91,7 @@
|
|||
#include "weston-trace.h"
|
||||
#include "renderer-vulkan/vulkan-renderer.h"
|
||||
|
||||
#include <libweston/commit-timing.h>
|
||||
#include <libweston/fifo.h>
|
||||
#include "weston-log-internal.h"
|
||||
|
||||
|
|
@ -3933,6 +3934,12 @@ weston_output_repaint_from_present(const struct weston_output *output,
|
|||
if (output->frame_flags & WESTON_FINISH_FRAME_TEARING)
|
||||
return late ? *now : *present_time;
|
||||
|
||||
if (output->forced_present.valid &&
|
||||
output->vrr_mode == WESTON_VRR_MODE_GAME) {
|
||||
actual_present_time = *present_time;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* At the start of the repaint loop with VRR enabled - we may
|
||||
* be able to paint early, so give it a try.
|
||||
*/
|
||||
|
|
@ -3949,6 +3956,7 @@ weston_output_repaint_from_present(const struct weston_output *output,
|
|||
frames_since = time_since / refresh_nsec;
|
||||
timespec_add_nsec(&actual_present_time, &output->frame_time, refresh_nsec * (frames_since + 1));
|
||||
|
||||
out:
|
||||
/* Subtract the "repaint window" time to get the deadline for the presentation time */
|
||||
timespec_add_msec(&repaint_time, &actual_present_time, -weston_output_repaint_msec(output));
|
||||
|
||||
|
|
@ -3973,9 +3981,38 @@ weston_repaint_timer_arm(struct weston_compositor *compositor)
|
|||
|
||||
weston_compositor_read_presentation_clock(compositor, &now);
|
||||
|
||||
weston_commit_timing_update_output_targets(compositor);
|
||||
|
||||
/* If we have a timed content update on a surface with no output,
|
||||
* we'll update it based on wall clock time. We do that by firing
|
||||
* the repaint timer even if no output needs to repaint.
|
||||
*/
|
||||
if (compositor->requested_repaint_fallback.valid) {
|
||||
nsec_to_next = timespec_sub_to_nsec(&compositor->requested_repaint_fallback.time,
|
||||
&now);
|
||||
any_should_repaint = true;
|
||||
}
|
||||
|
||||
wl_list_for_each(output, &compositor->output_list, link) {
|
||||
int64_t nsec_to_this;
|
||||
|
||||
/* If we have a timed frame on this output, treat it like a
|
||||
* scheduled frame. If no repaint is scheduled when the
|
||||
* timer fires, transaction application will schedule one,
|
||||
* and we'll have a chance to deliver on time.
|
||||
*/
|
||||
if (output->requested_present.valid) {
|
||||
struct timespec target_repaint =
|
||||
weston_output_repaint_from_present(output, &now,
|
||||
&output->requested_present.time);
|
||||
|
||||
nsec_to_this = timespec_sub_to_nsec(&target_repaint, &now);
|
||||
if (!any_should_repaint || nsec_to_this < nsec_to_next) {
|
||||
nsec_to_next = nsec_to_this;
|
||||
any_should_repaint = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (output->repaint_status != REPAINT_SCHEDULED)
|
||||
continue;
|
||||
|
||||
|
|
@ -4288,6 +4325,12 @@ weston_output_finish_frame(struct weston_output *output,
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (output->forced_present.valid &&
|
||||
output->vrr_mode == WESTON_VRR_MODE_GAME) {
|
||||
output->next_present = output->forced_present.time;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If we're doing game mode VRR, repainting right away
|
||||
* might be better than waiting, so try now.
|
||||
* TODO: Come up with some better handling...
|
||||
|
|
@ -4326,7 +4369,8 @@ weston_output_finish_frame(struct weston_output *output,
|
|||
* the repaint loop with VRR, because in that case we can potentially
|
||||
* just repaint right away.
|
||||
*/
|
||||
while (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID &&
|
||||
while (!output->forced_present.valid &&
|
||||
presented_flags == WP_PRESENTATION_FEEDBACK_INVALID &&
|
||||
output->vrr_mode != WESTON_VRR_MODE_GAME &&
|
||||
timespec_sub_to_msec(&output->next_present, &now) < weston_output_repaint_msec(output))
|
||||
timespec_add_nsec(&output->next_present,
|
||||
|
|
@ -4336,6 +4380,7 @@ weston_output_finish_frame(struct weston_output *output,
|
|||
out:
|
||||
output->next_repaint = weston_output_repaint_from_present(output, &now,
|
||||
&output->next_present);
|
||||
output->forced_present.valid = false;
|
||||
output->repaint_status = REPAINT_SCHEDULED;
|
||||
weston_repaint_timer_arm(compositor);
|
||||
}
|
||||
|
|
@ -9845,6 +9890,9 @@ weston_compositor_create(struct wl_display *display,
|
|||
if (fifo_setup(ec) != 0)
|
||||
goto fail;
|
||||
|
||||
if (commit_timing_setup(ec) != 0)
|
||||
goto fail;
|
||||
|
||||
if (weston_input_init(ec) != 0)
|
||||
goto fail;
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ srcs_libweston = [
|
|||
'color-profile-param-builder.c',
|
||||
'compositor.c',
|
||||
'content-protection.c',
|
||||
'commit-timing.c',
|
||||
'data-device.c',
|
||||
'drm-formats.c',
|
||||
'fifo.c',
|
||||
|
|
@ -48,6 +49,8 @@ srcs_libweston = [
|
|||
'weston-direct-display.c',
|
||||
color_management_v1_protocol_c,
|
||||
color_management_v1_server_protocol_h,
|
||||
commit_timing_v1_protocol_c,
|
||||
commit_timing_v1_server_protocol_h,
|
||||
fifo_v1_protocol_c,
|
||||
fifo_v1_server_protocol_h,
|
||||
linux_dmabuf_unstable_v1_protocol_c,
|
||||
|
|
|
|||
|
|
@ -29,14 +29,17 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <libweston/libweston.h>
|
||||
#include <libweston/commit-timing.h>
|
||||
#include <libweston/fifo.h>
|
||||
#include "libweston-internal.h"
|
||||
|
||||
#include "backend.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"
|
||||
|
||||
|
|
@ -155,6 +158,11 @@ weston_surface_state_init(struct weston_surface *surface,
|
|||
|
||||
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
|
||||
|
|
@ -491,6 +499,26 @@ weston_surface_apply_state(struct weston_surface *surface,
|
|||
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;
|
||||
|
|
@ -644,6 +672,9 @@ weston_surface_state_merge_from(struct weston_surface_state *dst,
|
|||
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;
|
||||
}
|
||||
|
|
@ -738,6 +769,7 @@ weston_surface_create_transaction(struct weston_compositor *comp,
|
|||
|
||||
struct weston_transaction *tr;
|
||||
struct weston_transaction_queue *parent;
|
||||
bool need_schedule = false;
|
||||
|
||||
tr = xzalloc(sizeof *tr);
|
||||
tr->flow_id = transaction_flow_id;
|
||||
|
|
@ -755,9 +787,13 @@ weston_surface_create_transaction(struct weston_compositor *comp,
|
|||
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
|
||||
|
|
@ -767,6 +803,9 @@ weston_surface_state_ready(struct weston_surface *surface,
|
|||
if (!weston_fifo_surface_state_ready(surface, state))
|
||||
return false;
|
||||
|
||||
if (!weston_commit_timing_surface_state_ready(surface, state))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -945,3 +984,45 @@ weston_compositor_apply_transactions(struct weston_compositor *compositor)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ install_data(
|
|||
|
||||
generated_protocols = [
|
||||
[ 'color-management', 'staging', 'v1' ],
|
||||
[ 'commit-timing', 'staging', 'v1' ],
|
||||
[ 'fifo', 'staging', 'v1' ],
|
||||
[ 'fullscreen-shell', 'unstable', 'v1' ],
|
||||
[ 'fractional-scale', 'staging', 'v1' ],
|
||||
|
|
|
|||
266
tests/commit-timing-test.c
Normal file
266
tests/commit-timing-test.c
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* Copyright © 2025 Collabora, Ltd.
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "libweston-internal.h"
|
||||
#include "weston-test-client-helper.h"
|
||||
#include "weston-test-fixture-compositor.h"
|
||||
#include "weston-test-assert.h"
|
||||
#include "presentation-time-client-protocol.h"
|
||||
#include "shared/timespec-util.h"
|
||||
#include "shared/xalloc.h"
|
||||
|
||||
static enum test_result_code
|
||||
fixture_setup(struct weston_test_harness *harness)
|
||||
{
|
||||
struct compositor_setup setup;
|
||||
|
||||
compositor_setup_defaults(&setup);
|
||||
setup.renderer = WESTON_RENDERER_PIXMAN;
|
||||
setup.width = 320;
|
||||
setup.height = 240;
|
||||
setup.shell = SHELL_TEST_DESKTOP;
|
||||
setup.logging_scopes = "log,test-harness-plugin";
|
||||
setup.refresh = HIGHEST_OUTPUT_REFRESH;
|
||||
|
||||
return weston_test_harness_execute_as_client(harness, &setup);
|
||||
}
|
||||
DECLARE_FIXTURE_SETUP(fixture_setup);
|
||||
|
||||
static struct buffer *
|
||||
surface_commit_color(struct client *client, struct wl_surface *surface,
|
||||
pixman_color_t *color, int width, int height)
|
||||
{
|
||||
struct buffer *buf;
|
||||
|
||||
buf = create_shm_buffer_a8r8g8b8(client, width, height);
|
||||
fill_image_with_color(buf->image, color);
|
||||
wl_surface_attach(surface, buf->proxy, 0, 0);
|
||||
wl_surface_damage_buffer(surface, 0, 0, width, height);
|
||||
wl_surface_commit(surface);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Ensure we can only have one commit-timer object for a surface */
|
||||
TEST(get_two_timers)
|
||||
{
|
||||
struct client *client;
|
||||
struct wp_commit_timer_v1 *timer1, *timer2;
|
||||
|
||||
client = create_client_and_test_surface(100, 50, 100, 100);
|
||||
test_assert_ptr_not_null(client);
|
||||
|
||||
timer1 = wp_commit_timing_manager_v1_get_timer(client->commit_timing_manager,
|
||||
client->surface->wl_surface);
|
||||
timer2 = wp_commit_timing_manager_v1_get_timer(client->commit_timing_manager,
|
||||
client->surface->wl_surface);
|
||||
expect_protocol_error(client, &wp_commit_timing_manager_v1_interface,
|
||||
WP_COMMIT_TIMING_MANAGER_V1_ERROR_COMMIT_TIMER_EXISTS);
|
||||
wp_commit_timer_v1_destroy(timer2);
|
||||
wp_commit_timer_v1_destroy(timer1);
|
||||
client_destroy(client);
|
||||
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
/* Ensure we can get a second timer for a surface if we destroy the first. */
|
||||
TEST(get_two_timers_safely)
|
||||
{
|
||||
struct client *client;
|
||||
struct wp_commit_timer_v1 *timer;
|
||||
|
||||
client = create_client_and_test_surface(100, 50, 100, 100);
|
||||
test_assert_ptr_not_null(client);
|
||||
|
||||
timer = wp_commit_timing_manager_v1_get_timer(client->commit_timing_manager,
|
||||
client->surface->wl_surface);
|
||||
wp_commit_timer_v1_destroy(timer);
|
||||
timer = wp_commit_timing_manager_v1_get_timer(client->commit_timing_manager,
|
||||
client->surface->wl_surface);
|
||||
wp_commit_timer_v1_destroy(timer);
|
||||
client_roundtrip(client);
|
||||
client_destroy(client);
|
||||
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
/* Ensure the appropriate error occurs for using a timer object associated
|
||||
* with a destroyed surface.
|
||||
*/
|
||||
TEST(use_timer_on_destroyed_surface)
|
||||
{
|
||||
struct client *client;
|
||||
struct wp_commit_timer_v1 *timer;
|
||||
struct wp_presentation *pres;
|
||||
struct timespec now;
|
||||
|
||||
client = create_client_and_test_surface(100, 50, 100, 100);
|
||||
test_assert_ptr_not_null(client);
|
||||
|
||||
pres = client_get_presentation(client);
|
||||
|
||||
timer = wp_commit_timing_manager_v1_get_timer(client->commit_timing_manager,
|
||||
client->surface->wl_surface);
|
||||
surface_destroy(client->surface);
|
||||
client->surface = NULL;
|
||||
|
||||
clock_gettime(client_get_presentation_clock(client), &now);
|
||||
wp_commit_timer_v1_set_timestamp(timer,
|
||||
(uint64_t)now.tv_sec >> 32,
|
||||
now.tv_sec,
|
||||
now.tv_nsec);
|
||||
expect_protocol_error(client, &wp_commit_timer_v1_interface,
|
||||
WP_COMMIT_TIMER_V1_ERROR_SURFACE_DESTROYED);
|
||||
|
||||
wp_presentation_destroy(pres);
|
||||
wp_commit_timer_v1_destroy(timer);
|
||||
client_destroy(client);
|
||||
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
/* Ensure an error occurs for invalid tv_nsec. */
|
||||
TEST(invalid_timestamp)
|
||||
{
|
||||
struct client *client;
|
||||
struct wp_commit_timer_v1 *timer;
|
||||
struct wp_presentation *pres;
|
||||
struct timespec now;
|
||||
|
||||
client = create_client_and_test_surface(100, 50, 100, 100);
|
||||
test_assert_ptr_not_null(client);
|
||||
|
||||
pres = client_get_presentation(client);
|
||||
|
||||
timer = wp_commit_timing_manager_v1_get_timer(client->commit_timing_manager,
|
||||
client->surface->wl_surface);
|
||||
|
||||
clock_gettime(client_get_presentation_clock(client), &now);
|
||||
wp_commit_timer_v1_set_timestamp(timer,
|
||||
(uint64_t)now.tv_sec >> 32,
|
||||
now.tv_sec,
|
||||
1000000000);
|
||||
expect_protocol_error(client, &wp_commit_timer_v1_interface,
|
||||
WP_COMMIT_TIMER_V1_ERROR_INVALID_TIMESTAMP);
|
||||
|
||||
wp_presentation_destroy(pres);
|
||||
wp_commit_timer_v1_destroy(timer);
|
||||
client_destroy(client);
|
||||
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
/* Ensure an error occurs when a second timestamp is set before a
|
||||
* wl_surface.commit
|
||||
*/
|
||||
TEST(too_many_timestamps)
|
||||
{
|
||||
struct client *client;
|
||||
struct wp_commit_timer_v1 *timer;
|
||||
struct wp_presentation *pres;
|
||||
struct timespec now;
|
||||
|
||||
client = create_client_and_test_surface(100, 50, 100, 100);
|
||||
test_assert_ptr_not_null(client);
|
||||
|
||||
pres = client_get_presentation(client);
|
||||
|
||||
timer = wp_commit_timing_manager_v1_get_timer(client->commit_timing_manager,
|
||||
client->surface->wl_surface);
|
||||
|
||||
clock_gettime(client_get_presentation_clock(client), &now);
|
||||
wp_commit_timer_v1_set_timestamp(timer,
|
||||
(uint64_t)now.tv_sec >> 32,
|
||||
now.tv_sec,
|
||||
now.tv_nsec);
|
||||
wp_commit_timer_v1_set_timestamp(timer,
|
||||
(uint64_t)now.tv_sec >> 32,
|
||||
now.tv_sec,
|
||||
now.tv_nsec);
|
||||
expect_protocol_error(client, &wp_commit_timer_v1_interface,
|
||||
WP_COMMIT_TIMER_V1_ERROR_TIMESTAMP_EXISTS);
|
||||
|
||||
wp_presentation_destroy(pres);
|
||||
wp_commit_timer_v1_destroy(timer);
|
||||
client_destroy(client);
|
||||
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Ensure the compositor doesn't explode if we delete a surface with
|
||||
* timestamped content updates
|
||||
*/
|
||||
TEST(commit_timing_delete_surface_with_timestamps)
|
||||
{
|
||||
struct client *client;
|
||||
struct buffer *buf;
|
||||
struct wp_commit_timer_v1 *timer;
|
||||
struct timespec target;
|
||||
struct wp_presentation *pres;
|
||||
pixman_color_t red;
|
||||
int i;
|
||||
|
||||
color_rgb888(&red, 255, 0, 0);
|
||||
|
||||
client = create_client_and_test_surface(100, 50, 100, 100);
|
||||
test_assert_ptr_not_null(client);
|
||||
|
||||
pres = client_get_presentation(client);
|
||||
timer = wp_commit_timing_manager_v1_get_timer(client->commit_timing_manager,
|
||||
client->surface->wl_surface);
|
||||
buf = surface_commit_color(client, client->surface->wl_surface, &red, 100, 100);
|
||||
|
||||
clock_gettime(client_get_presentation_clock(client), &target);
|
||||
/* Load up some future transactions */
|
||||
for (i = 0; i < 10; i++) {
|
||||
timespec_add_nsec(&target, &target, (NSEC_PER_SEC * 60ULL));
|
||||
wp_commit_timer_v1_set_timestamp(timer,
|
||||
(uint64_t)target.tv_sec >> 32,
|
||||
target.tv_sec,
|
||||
target.tv_nsec);
|
||||
|
||||
wl_surface_commit(client->surface->wl_surface);
|
||||
}
|
||||
|
||||
/* Steal and destroy the surface */
|
||||
wl_surface_destroy(client->surface->wl_surface);
|
||||
client->surface->wl_surface = NULL;
|
||||
|
||||
client_roundtrip(client);
|
||||
|
||||
wp_commit_timer_v1_destroy(timer);
|
||||
wp_presentation_destroy(pres);
|
||||
buffer_destroy(buf);
|
||||
client_destroy(client);
|
||||
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
|
@ -31,6 +31,8 @@ lib_test_client = static_library(
|
|||
'weston-test-client-helper.c',
|
||||
'weston-test-fixture-compositor.c',
|
||||
'xdg-client-helper.c',
|
||||
commit_timing_v1_client_protocol_h,
|
||||
commit_timing_v1_protocol_c,
|
||||
fifo_v1_client_protocol_h,
|
||||
fifo_v1_protocol_c,
|
||||
presentation_time_client_protocol_h,
|
||||
|
|
@ -138,6 +140,7 @@ tests = [
|
|||
dep_libdisplay_info,
|
||||
],
|
||||
},
|
||||
{ 'name': 'commit-timing', },
|
||||
{ 'name': 'config-parser', },
|
||||
{
|
||||
'name': 'constraints',
|
||||
|
|
|
|||
|
|
@ -938,6 +938,10 @@ handle_global(void *data, struct wl_registry *registry,
|
|||
client->fifo_manager =
|
||||
wl_registry_bind(registry, id,
|
||||
&wp_fifo_manager_v1_interface, 1);
|
||||
} else if (strcmp(interface, wp_commit_timing_manager_v1_interface.name) == 0) {
|
||||
client->commit_timing_manager =
|
||||
wl_registry_bind(registry, id,
|
||||
&wp_commit_timing_manager_v1_interface, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1208,6 +1212,9 @@ client_destroy(struct client *client)
|
|||
if (client->fifo_manager)
|
||||
wp_fifo_manager_v1_destroy(client->fifo_manager);
|
||||
|
||||
if (client->commit_timing_manager)
|
||||
wp_commit_timing_manager_v1_destroy(client->commit_timing_manager);
|
||||
|
||||
if (client->surface)
|
||||
surface_destroy(client->surface);
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
#include "xdg-shell-client-protocol.h"
|
||||
#include "weston-testsuite-data.h"
|
||||
#include "fifo-v1-client-protocol.h"
|
||||
#include "commit-timing-v1-client-protocol.h"
|
||||
|
||||
struct client {
|
||||
struct wl_display *wl_display;
|
||||
|
|
@ -66,6 +67,7 @@ struct client {
|
|||
|
||||
struct test *test;
|
||||
struct wp_fifo_manager_v1 *fifo_manager;
|
||||
struct wp_commit_timing_manager_v1 *commit_timing_manager;
|
||||
/* the seat that is actually used for input events */
|
||||
struct input *input;
|
||||
/* server can have more wl_seats. We need keep them all until we
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue