diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index dcaa7394d..904ff93d5 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -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 { diff --git a/libweston/commit-timing.c b/libweston/commit-timing.c new file mode 100644 index 000000000..0abf21014 --- /dev/null +++ b/libweston/commit-timing.c @@ -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 + +#include +#include +#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; +} diff --git a/libweston/commit-timing.h b/libweston/commit-timing.h new file mode 100644 index 000000000..452e94a59 --- /dev/null +++ b/libweston/commit-timing.h @@ -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); diff --git a/libweston/compositor.c b/libweston/compositor.c index c98c71f68..4b38cc2fd 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -91,6 +91,7 @@ #include "weston-trace.h" #include "renderer-vulkan/vulkan-renderer.h" +#include #include #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; diff --git a/libweston/meson.build b/libweston/meson.build index bc065716e..83d39e494 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -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, diff --git a/libweston/surface-state.c b/libweston/surface-state.c index 44b9e4d25..01d886718 100644 --- a/libweston/surface-state.c +++ b/libweston/surface-state.c @@ -29,14 +29,17 @@ #include "config.h" #include +#include #include #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 #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; + } + } +} diff --git a/protocol/meson.build b/protocol/meson.build index 384be5b11..3935bac76 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -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' ], diff --git a/tests/commit-timing-test.c b/tests/commit-timing-test.c new file mode 100644 index 000000000..9bf2c87b4 --- /dev/null +++ b/tests/commit-timing-test.c @@ -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 +#include +#include + +#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; +} diff --git a/tests/meson.build b/tests/meson.build index ff4632770..6e8b0e4a5 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -30,7 +30,9 @@ lib_test_client = static_library( [ 'weston-test-client-helper.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_protocol_c, presentation_time_client_protocol_h, @@ -138,6 +140,7 @@ tests = [ dep_libdisplay_info, ], }, + { 'name': 'commit-timing', }, { 'name': 'config-parser', }, { 'name': 'constraints', diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index dfda50d67..c2ff7ea76 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -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); diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 699ea8fc0..753c1d01d 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -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