diff --git a/apps/nescope/src/handlers.rs b/apps/nescope/src/handlers.rs index 08415e58..2a0d341e 100644 --- a/apps/nescope/src/handlers.rs +++ b/apps/nescope/src/handlers.rs @@ -115,7 +115,7 @@ impl CompositorHandler for NescopeState { window.on_commit(); self.game_frame_count += 1; - self.on_surface_committed(&root, &window); + self.present_now(&root, &window); } fn destroyed(&mut self, surface: &WlSurface) { diff --git a/apps/nescope/src/main.rs b/apps/nescope/src/main.rs index 0d1a4785..6256354e 100644 --- a/apps/nescope/src/main.rs +++ b/apps/nescope/src/main.rs @@ -416,16 +416,10 @@ fn main() { } // ── Input dispatch timer ──────────────────────────────────────────────── - // Just input and cursor. High rate so a 1000 Hz mouse isn't coalesced; + // Just input and cursor. High rate so a 500 Hz (2 ms) mouse isn't coalesced; // cursor piggybacks on the same tick because it is change-gated // internally and costs nothing when idle. - // - // There is deliberately no frame-callback timer. Frame callbacks and - // presentation feedback are driven by commits — see - // NescopeState::on_surface_committed. A timer at any rate would pace - // the client against something other than its own submit loop, which - // is what produces the periodic hitches. - let tick_interval = Duration::from_millis(1); + let tick_interval = Duration::from_millis(2); loop_handle .insert_source(Timer::from_duration(tick_interval), move |_, _, data| { if let Some(ref mut li) = data.libinput { diff --git a/apps/nescope/src/state.rs b/apps/nescope/src/state.rs index 9d9fa61e..b3d13fb6 100644 --- a/apps/nescope/src/state.rs +++ b/apps/nescope/src/state.rs @@ -190,6 +190,8 @@ pub struct NescopeState { /// Timestamp of the last non-keyboard pointer event (for inactivity hiding). pub last_pointer_activity: std::time::Instant, + pub presentation_seq: u64, + // ── Dimensions + frame rate ─────────────────────────────────────────── pub width: u32, pub height: u32, @@ -323,6 +325,7 @@ impl NescopeState { last_sent_cursor_pos: Point::from((-1.0f64, -1.0f64)), last_sent_cursor_status: 0xFF, last_pointer_activity: std::time::Instant::now(), + presentation_seq: 0, width, height, fps, @@ -732,55 +735,25 @@ impl NescopeState { } } - // ----------------------------------------------------------------------- - // Commit-driven frame signals - // - // Called from CompositorHandler::commit for every non-sync commit that - // belongs to a mapped window. Fires the wl_surface.frame callbacks the - // client asked for and resolves the wp_presentation_feedback for the - // frame that just arrived. - // - // NOT driven by a timer. A frame callback means "the compositor is ready - // for your next frame"; for a headless capture compositor that is true - // the moment the commit lands, because there is no renderer and no - // scan-out between us and the client. A timer at any rate decouples the - // client's submit loop from the signal it is pacing itself with — too - // early for a slow client (over-submits), too late for a fast one - // (throttles), and when the two rates are close but not equal the phase - // slips and produces periodic hitches. The client's own rate is the - // correct rate; that is the whole point of the callback. - // ----------------------------------------------------------------------- - - pub(crate) fn on_surface_committed(&mut self, root: &WlSurface, window: &Window) { + pub(crate) fn present_now(&mut self, root: &WlSurface, window: &Window) { let output = self.output.clone(); let now = self.clock.now(); + self.presentation_seq += 1; - // Frame callbacks for the whole tree. `None` throttle: no minimum - // interval — the client decides its own cadence, we answer every - // frame it hands us. - send_frames_surface_tree(root, &output, now, None, |_, _| Some(output.clone())); - - // Presentation feedback for the same frame, resolved as `presented` - // immediately. `discarded` is the wrong resolution here: it means - // "the next frame superseded this one before it reached the - // display," which is not what happened — this frame is the one the - // capture layer will see, and the client that waits on it (Vulkan - // FIFO present, VKD3D-Proton, DXVK) is waiting precisely for this. - let mut feedback = OutputPresentationFeedback::new(&output); + let mut fb = OutputPresentationFeedback::new(&output); window.take_presentation_feedback( - &mut feedback, + &mut fb, |_, _| Some(output.clone()), |_, _| wp_presentation_feedback::Kind::Vsync, ); - feedback.presented( + fb.presented( now, - output - .current_mode() - .map(|m| Refresh::fixed(Duration::from_secs_f64(1_000.0 / m.refresh as f64))) - .unwrap_or(Refresh::Unknown), - 0, + Refresh::Unknown, + self.presentation_seq, wp_presentation_feedback::Kind::Vsync, ); + + send_frames_surface_tree(root, &output, now, None, |_, _| Some(output.clone())); } pub(crate) fn send_cursor_update(&mut self) { diff --git a/build/Containerfile b/build/Containerfile index a5bd6bc3..ae939881 100644 --- a/build/Containerfile +++ b/build/Containerfile @@ -45,7 +45,7 @@ RUN pacman -Syu --noconfirm FROM initial AS builder RUN pacman -S --noconfirm --needed \ - cmake meson ninja git pkgconf \ + cmake meson ninja git pkgconf mold \ python python-mako python-yaml python-packaging python-ply \ bison flex \ libpciaccess libepoxy libglvnd \ @@ -64,6 +64,7 @@ RUN pacman -S --noconfirm --needed \ && pacman -Scc --noconfirm WORKDIR /build ENV ARTIFACTS=/artifacts +ENV RUSTFLAGS="-C link-arg=-fuse-ld=mold" # ─────────────────────────────────────────────────────────── @@ -72,7 +73,7 @@ ENV ARTIFACTS=/artifacts FROM builder AS mesa-build ARG MESA_GIT=https://gitlab.freedesktop.org/nowrep/mesa.git -ARG MESA_COMMIT=1af89bea491deabc4741700f8df4d41edfd22f7a +ARG MESA_COMMIT=49162a0ba8f3647917bc4c94d13eea6b51a1e49f ARG JOBS= # Our patches to the amdgpu native-context path, applied on top of the pinned @@ -101,7 +102,7 @@ RUN test -n "$JOBS" || JOBS=$(nproc) && \ -Dgbm=disabled \ -Dgallium-va=disabled \ -Db_ndebug=true && \ - ninja -C builddir -j${JOBS:-$(nproc)} && \ + mold -run ninja -C builddir -j${JOBS:-$(nproc)} && \ DESTDIR=/artifacts/mesa ninja -C builddir install && \ rm -rf /build/mesa-src && \ find /artifacts/mesa -type f -printf '/%P\n' > /artifacts/mesa/.manifest