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:
Derek Foreman 2025-10-08 13:53:13 -05:00
parent 65227cb7d4
commit a3f0e97a71
11 changed files with 753 additions and 2 deletions

View file

@ -74,6 +74,12 @@ struct weston_point2d_device_normalized {
double y; double y;
}; };
struct weston_commit_timing_target {
bool valid;
bool satisfied;
struct timespec time;
};
struct weston_compositor; struct weston_compositor;
struct weston_surface; struct weston_surface;
struct weston_buffer; struct weston_buffer;
@ -437,6 +443,16 @@ struct weston_output {
* next repaint should be displayed */ * next repaint should be displayed */
struct timespec next_present; 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. */ /** For cancelling the idle_repaint callback on output destruction. */
struct wl_event_source *idle_repaint_source; struct wl_event_source *idle_repaint_source;
@ -1512,6 +1528,11 @@ struct weston_compositor {
bool latched; bool latched;
struct wl_list transaction_queue_list; /* weston_transaction_queue::link */ 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 { struct weston_solid_buffer_values {
@ -1808,6 +1829,9 @@ struct weston_surface_state {
/* wp_fifo_v1 */ /* wp_fifo_v1 */
bool fifo_barrier; bool fifo_barrier;
bool fifo_wait; bool fifo_wait;
/* commit_timing_v1 */
struct weston_commit_timing_target update_time;
}; };
struct weston_surface_activation_data { struct weston_surface_activation_data {
@ -1992,6 +2016,9 @@ struct weston_surface {
struct weston_fifo *fifo; struct weston_fifo *fifo;
bool fifo_barrier; /* Cleared after display */ bool fifo_barrier; /* Cleared after display */
struct wl_list fifo_barrier_link; /* output::fifo_barrier_surfaces */ struct wl_list fifo_barrier_link; /* output::fifo_barrier_surfaces */
/** commit_timing_v1 */
struct weston_commit_timer *commit_timer;
}; };
struct weston_subsurface { struct weston_subsurface {

270
libweston/commit-timing.c Normal file
View 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
View 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);

View file

@ -91,6 +91,7 @@
#include "weston-trace.h" #include "weston-trace.h"
#include "renderer-vulkan/vulkan-renderer.h" #include "renderer-vulkan/vulkan-renderer.h"
#include <libweston/commit-timing.h>
#include <libweston/fifo.h> #include <libweston/fifo.h>
#include "weston-log-internal.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) if (output->frame_flags & WESTON_FINISH_FRAME_TEARING)
return late ? *now : *present_time; 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 /* At the start of the repaint loop with VRR enabled - we may
* be able to paint early, so give it a try. * 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; frames_since = time_since / refresh_nsec;
timespec_add_nsec(&actual_present_time, &output->frame_time, refresh_nsec * (frames_since + 1)); 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 */ /* 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)); 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_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) { wl_list_for_each(output, &compositor->output_list, link) {
int64_t nsec_to_this; 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) if (output->repaint_status != REPAINT_SCHEDULED)
continue; continue;
@ -4288,6 +4325,12 @@ weston_output_finish_frame(struct weston_output *output,
goto out; 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 /* If we're doing game mode VRR, repainting right away
* might be better than waiting, so try now. * might be better than waiting, so try now.
* TODO: Come up with some better handling... * 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 * the repaint loop with VRR, because in that case we can potentially
* just repaint right away. * 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 && output->vrr_mode != WESTON_VRR_MODE_GAME &&
timespec_sub_to_msec(&output->next_present, &now) < weston_output_repaint_msec(output)) timespec_sub_to_msec(&output->next_present, &now) < weston_output_repaint_msec(output))
timespec_add_nsec(&output->next_present, timespec_add_nsec(&output->next_present,
@ -4336,6 +4380,7 @@ weston_output_finish_frame(struct weston_output *output,
out: out:
output->next_repaint = weston_output_repaint_from_present(output, &now, output->next_repaint = weston_output_repaint_from_present(output, &now,
&output->next_present); &output->next_present);
output->forced_present.valid = false;
output->repaint_status = REPAINT_SCHEDULED; output->repaint_status = REPAINT_SCHEDULED;
weston_repaint_timer_arm(compositor); weston_repaint_timer_arm(compositor);
} }
@ -9845,6 +9890,9 @@ weston_compositor_create(struct wl_display *display,
if (fifo_setup(ec) != 0) if (fifo_setup(ec) != 0)
goto fail; goto fail;
if (commit_timing_setup(ec) != 0)
goto fail;
if (weston_input_init(ec) != 0) if (weston_input_init(ec) != 0)
goto fail; goto fail;

View file

@ -23,6 +23,7 @@ srcs_libweston = [
'color-profile-param-builder.c', 'color-profile-param-builder.c',
'compositor.c', 'compositor.c',
'content-protection.c', 'content-protection.c',
'commit-timing.c',
'data-device.c', 'data-device.c',
'drm-formats.c', 'drm-formats.c',
'fifo.c', 'fifo.c',
@ -48,6 +49,8 @@ srcs_libweston = [
'weston-direct-display.c', 'weston-direct-display.c',
color_management_v1_protocol_c, color_management_v1_protocol_c,
color_management_v1_server_protocol_h, color_management_v1_server_protocol_h,
commit_timing_v1_protocol_c,
commit_timing_v1_server_protocol_h,
fifo_v1_protocol_c, fifo_v1_protocol_c,
fifo_v1_server_protocol_h, fifo_v1_server_protocol_h,
linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_protocol_c,

View file

@ -29,14 +29,17 @@
#include "config.h" #include "config.h"
#include <libweston/libweston.h> #include <libweston/libweston.h>
#include <libweston/commit-timing.h>
#include <libweston/fifo.h> #include <libweston/fifo.h>
#include "libweston-internal.h" #include "libweston-internal.h"
#include "backend.h" #include "backend.h"
#include "pixel-formats.h" #include "pixel-formats.h"
#include "shared/fd-util.h" #include "shared/fd-util.h"
#include "shared/timespec-util.h"
#include "shared/weston-assert.h" #include "shared/weston-assert.h"
#include "shared/xalloc.h" #include "shared/xalloc.h"
#include <sys/timerfd.h>
#include "timeline.h" #include "timeline.h"
#include "weston-trace.h" #include "weston-trace.h"
@ -155,6 +158,11 @@ weston_surface_state_init(struct weston_surface *surface,
state->fifo_barrier = false; state->fifo_barrier = false;
state->fifo_wait = 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 void
@ -491,6 +499,26 @@ weston_surface_apply_state(struct weston_surface *surface,
if (weston_surface_status_invalidates_visibility(status)) if (weston_surface_status_invalidates_visibility(status))
surface->output_visibility_dirty_mask |= surface->output_mask; 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; state->status = WESTON_SURFACE_CLEAN;
return status; return status;
@ -644,6 +672,9 @@ weston_surface_state_merge_from(struct weston_surface_state *dst,
dst->fifo_wait = src->fifo_wait; dst->fifo_wait = src->fifo_wait;
src->fifo_wait = false; src->fifo_wait = false;
dst->update_time = src->update_time;
weston_commit_timing_clear_target(&src->update_time);
dst->status |= src->status; dst->status |= src->status;
src->status = WESTON_SURFACE_CLEAN; src->status = WESTON_SURFACE_CLEAN;
} }
@ -738,6 +769,7 @@ weston_surface_create_transaction(struct weston_compositor *comp,
struct weston_transaction *tr; struct weston_transaction *tr;
struct weston_transaction_queue *parent; struct weston_transaction_queue *parent;
bool need_schedule = false;
tr = xzalloc(sizeof *tr); tr = xzalloc(sizeof *tr);
tr->flow_id = transaction_flow_id; tr->flow_id = transaction_flow_id;
@ -755,9 +787,13 @@ weston_surface_create_transaction(struct weston_compositor *comp,
parent = xzalloc(sizeof *parent); parent = xzalloc(sizeof *parent);
wl_list_init(&parent->transaction_list); wl_list_init(&parent->transaction_list);
wl_list_insert(&comp->transaction_queue_list, &parent->link); wl_list_insert(&comp->transaction_queue_list, &parent->link);
need_schedule = true;
} }
tr->queue = parent; tr->queue = parent;
wl_list_insert(parent->transaction_list.prev, &tr->link); wl_list_insert(parent->transaction_list.prev, &tr->link);
if (need_schedule)
weston_repaint_timer_arm(comp);
} }
static bool static bool
@ -767,6 +803,9 @@ weston_surface_state_ready(struct weston_surface *surface,
if (!weston_fifo_surface_state_ready(surface, state)) if (!weston_fifo_surface_state_ready(surface, state))
return false; return false;
if (!weston_commit_timing_surface_state_ready(surface, state))
return false;
return true; 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;
}
}
}

View file

@ -17,6 +17,7 @@ install_data(
generated_protocols = [ generated_protocols = [
[ 'color-management', 'staging', 'v1' ], [ 'color-management', 'staging', 'v1' ],
[ 'commit-timing', 'staging', 'v1' ],
[ 'fifo', 'staging', 'v1' ], [ 'fifo', 'staging', 'v1' ],
[ 'fullscreen-shell', 'unstable', 'v1' ], [ 'fullscreen-shell', 'unstable', 'v1' ],
[ 'fractional-scale', 'staging', 'v1' ], [ 'fractional-scale', 'staging', 'v1' ],

266
tests/commit-timing-test.c Normal file
View 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;
}

View file

@ -31,6 +31,8 @@ lib_test_client = static_library(
'weston-test-client-helper.c', 'weston-test-client-helper.c',
'weston-test-fixture-compositor.c', 'weston-test-fixture-compositor.c',
'xdg-client-helper.c', 'xdg-client-helper.c',
commit_timing_v1_client_protocol_h,
commit_timing_v1_protocol_c,
fifo_v1_client_protocol_h, fifo_v1_client_protocol_h,
fifo_v1_protocol_c, fifo_v1_protocol_c,
presentation_time_client_protocol_h, presentation_time_client_protocol_h,
@ -138,6 +140,7 @@ tests = [
dep_libdisplay_info, dep_libdisplay_info,
], ],
}, },
{ 'name': 'commit-timing', },
{ 'name': 'config-parser', }, { 'name': 'config-parser', },
{ {
'name': 'constraints', 'name': 'constraints',

View file

@ -938,6 +938,10 @@ handle_global(void *data, struct wl_registry *registry,
client->fifo_manager = client->fifo_manager =
wl_registry_bind(registry, id, wl_registry_bind(registry, id,
&wp_fifo_manager_v1_interface, 1); &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) if (client->fifo_manager)
wp_fifo_manager_v1_destroy(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) if (client->surface)
surface_destroy(client->surface); surface_destroy(client->surface);

View file

@ -45,6 +45,7 @@
#include "xdg-shell-client-protocol.h" #include "xdg-shell-client-protocol.h"
#include "weston-testsuite-data.h" #include "weston-testsuite-data.h"
#include "fifo-v1-client-protocol.h" #include "fifo-v1-client-protocol.h"
#include "commit-timing-v1-client-protocol.h"
struct client { struct client {
struct wl_display *wl_display; struct wl_display *wl_display;
@ -66,6 +67,7 @@ struct client {
struct test *test; struct test *test;
struct wp_fifo_manager_v1 *fifo_manager; 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 */ /* the seat that is actually used for input events */
struct input *input; struct input *input;
/* server can have more wl_seats. We need keep them all until we /* server can have more wl_seats. We need keep them all until we