diff --git a/src/compositor/compositor_core.cpp b/src/compositor/compositor_core.cpp index 49b43b7..97b5a2d 100644 --- a/src/compositor/compositor_core.cpp +++ b/src/compositor/compositor_core.cpp @@ -10,6 +10,7 @@ extern "C" { #include #include +#include #include #include #include @@ -197,6 +198,23 @@ auto CompositorState::create_compositor() -> Result { } } + // Own a composite-OUTPUT signal timeline so every exported frame carries a producer-write + // fence (P5), independent of whether the client bound wp_linux_drm_syncobj_v1. Best-effort: + // absence degrades to the status-quo implicit path (see compositor_present.cpp). + if (renderer->features.timeline && drm_fd >= 0) { + present_signal_timeline = wlr_drm_syncobj_timeline_create(drm_fd); + present_timeline_supported = (present_signal_timeline != nullptr); + if (present_timeline_supported) { + GOGGLES_LOG_INFO("Compositor: composite-write signal timeline enabled"); + } else { + GOGGLES_LOG_WARN( + "Compositor: signal timeline creation failed; implicit signal sync fallback"); + } + } else { + GOGGLES_LOG_WARN("Compositor: renderer lacks features.timeline; composite-write fence " + "export unavailable (implicit signal sync fallback)"); + } + return {}; } @@ -382,6 +400,11 @@ void CompositorState::teardown() { allocator = nullptr; } + if (present_signal_timeline) { + wlr_drm_syncobj_timeline_unref(present_signal_timeline); + present_signal_timeline = nullptr; + } + if (renderer) { wlr_renderer_destroy(renderer); renderer = nullptr; diff --git a/src/compositor/compositor_present.cpp b/src/compositor/compositor_present.cpp index ef6f1f6..05b5b24 100644 --- a/src/compositor/compositor_present.cpp +++ b/src/compositor/compositor_present.cpp @@ -8,6 +8,7 @@ #include extern "C" { +#include #include #include #include @@ -653,7 +654,17 @@ bool CompositorState::render_surface_to_frame(const InputTarget& target) { return false; } - wlr_render_pass* pass = wlr_renderer_begin_buffer_pass(renderer, buffer, nullptr); + uint64_t pass_point = 0; + wlr_render_pass* pass = nullptr; + if (present_timeline_supported) { + pass_point = ++present_signal_point; + wlr_buffer_pass_options opts{}; + opts.signal_timeline = present_signal_timeline; + opts.signal_point = pass_point; + pass = wlr_renderer_begin_buffer_pass(renderer, buffer, &opts); + } else { + pass = wlr_renderer_begin_buffer_pass(renderer, buffer, nullptr); + } if (!pass) { wlr_buffer_unlock(buffer); return false; @@ -719,18 +730,31 @@ bool CompositorState::render_surface_to_frame(const InputTarget& target) { frame.image.handle = std::move(dup_fd); frame.frame_number = ++presented_frame_number; - // Export the acquire fence from the root surface so Vulkan waits on compositor writes. - wlr_linux_drm_syncobj_surface_v1_state* syncobj_state = - wlr_linux_drm_syncobj_v1_get_surface_state(root_surface); - if (syncobj_state && syncobj_state->acquire_timeline) { - int sync_file = wlr_drm_syncobj_timeline_export_sync_file(syncobj_state->acquire_timeline, - syncobj_state->acquire_point); - if (sync_file >= 0) { - frame.sync_fd = util::UniqueFd{sync_file}; + // Export the composite OUTPUT write fence so goggles waits on the compositor's render + // completion. The signal timeline is the compositor's own, independent of whether the client + // bound wp_linux_drm_syncobj_v1, so every client gets a producer-write fence when supported. + if (present_timeline_supported) { + bool materialized = false; + if (wlr_drm_syncobj_timeline_check(present_signal_timeline, pass_point, + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE, &materialized) && + materialized) { + int sync_file = + wlr_drm_syncobj_timeline_export_sync_file(present_signal_timeline, pass_point); + if (sync_file >= 0) { + frame.sync_fd = util::UniqueFd{sync_file}; + } else { + GOGGLES_LOG_WARN("Composite-write sync_file export failed (point {})", pass_point); + } + } else { + GOGGLES_LOG_WARN("Composite-write point {} not materialized at export; frame published " + "without producer fence", + pass_point); } } // Release stays tied to the exported buffer so wlroots can retire it after import completes. + wlr_linux_drm_syncobj_surface_v1_state* syncobj_state = + wlr_linux_drm_syncobj_v1_get_surface_state(root_surface); if (syncobj_state && syncobj_state->release_timeline) { wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer(syncobj_state, buffer); } diff --git a/src/compositor/compositor_state.hpp b/src/compositor/compositor_state.hpp index 623a7b3..b463689 100644 --- a/src/compositor/compositor_state.hpp +++ b/src/compositor/compositor_state.hpp @@ -26,6 +26,7 @@ struct wlr_allocator; struct wlr_backend; struct wlr_buffer; struct wlr_compositor; +struct wlr_drm_syncobj_timeline; struct wlr_layer_shell_v1; struct wlr_linux_drm_syncobj_manager_v1; struct wlr_output; @@ -61,6 +62,7 @@ using ::wlr_allocator; using ::wlr_backend; using ::wlr_buffer; using ::wlr_compositor; +using ::wlr_drm_syncobj_timeline; using ::wlr_linux_drm_syncobj_manager_v1; using ::wlr_output; using ::wlr_output_layout; @@ -166,6 +168,9 @@ struct CompositorState { std::vector> layer_hooks; wlr_layer_shell_v1* layer_shell = nullptr; wlr_linux_drm_syncobj_manager_v1* syncobj_manager = nullptr; + wlr_drm_syncobj_timeline* present_signal_timeline = nullptr; // composite-OUTPUT write timeline + uint64_t present_signal_point = 0; // monotonic, compositor-thread only + bool present_timeline_supported = false; wlr_drm_format present_format{}; std::string wayland_socket_name; mutable std::mutex hooks_mutex;