mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
feat: nescapture capture improvements and drive mounts (#337)
Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
ebc0242b49
commit
6811c93d51
@@ -109,6 +109,11 @@ impl CompositorHandler for NescopeState {
|
||||
.cloned()
|
||||
{
|
||||
window.on_commit();
|
||||
// A game frame, which is what the stats claim to report. Counted
|
||||
// here rather than on the frame-callback tick: that tick fires
|
||||
// whether anything was drawn or not, so counting it reported the
|
||||
// compositor's own cadence back as the game's rate.
|
||||
self.game_frame_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
//! Options:
|
||||
//! --width <N> Output width [default: 1920]
|
||||
//! --height <N> Output height [default: 1080]
|
||||
//! --fps <N> Virtual refresh rate [default: 60]
|
||||
//! --fps <N> Virtual refresh rate, advertised only [default: 60]
|
||||
//! --frame-callback-hz <N> wl_surface.frame cadence [default: 1000]
|
||||
//! --hdr Enable HDR protocols (wp_color_management_v1 + gamescope_swapchain)
|
||||
//! --socket <NAME> Wayland socket name [default: nescope-0]
|
||||
//! ```
|
||||
@@ -94,10 +95,36 @@ struct Args {
|
||||
#[arg(long, default_value = "1080", env = "NESCOPE_HEIGHT")]
|
||||
height: u32,
|
||||
|
||||
/// Virtual output refresh rate (fps).
|
||||
/// Virtual output refresh rate, as advertised to clients.
|
||||
///
|
||||
/// **Advertised only — this does not pace anything.** It is what a game
|
||||
/// reads as its monitor's refresh rate, so it should be the rate the
|
||||
/// session actually sends at: a game with V-Sync on will lock to it, and
|
||||
/// one that reads the mode to build a settings list will offer it.
|
||||
///
|
||||
/// Pacing is `--frame-callback-hz`, and the two used to be this one value.
|
||||
/// That made an honest advertisement and a non-binding cadence mutually
|
||||
/// exclusive, which is why the default sat at 60 while sessions asked for
|
||||
/// 120.
|
||||
#[arg(long, default_value = "60", env = "NESCOPE_FPS")]
|
||||
fps: u32,
|
||||
|
||||
/// How often `wl_surface.frame` callbacks are sent, in hertz.
|
||||
///
|
||||
/// This is the only rate that can throttle a client, and only a FIFO one:
|
||||
/// `IMMEDIATE` and `MAILBOX` swapchains ignore these callbacks entirely.
|
||||
/// It is therefore not a frame limiter — it cannot hold a game whose
|
||||
/// V-Sync is off, which is every game whose player turned it off. That job
|
||||
/// belongs to the capture layer, which sees every present and can hold the
|
||||
/// application whatever its swapchain does.
|
||||
///
|
||||
/// So the default is set high enough never to bind, and the compositor
|
||||
/// stops being a second opinion on the frame rate. The cost is the timer
|
||||
/// itself: a wakeup per tick, each sending callbacks to the surfaces in
|
||||
/// the space. Lower it if that shows up on a small box.
|
||||
#[arg(long, default_value = "1000", env = "NESCOPE_FRAME_CALLBACK_HZ")]
|
||||
frame_callback_hz: u32,
|
||||
|
||||
/// Enable HDR protocols (wp_color_management_v1 + gamescope_swapchain_factory_v2).
|
||||
#[arg(long, env = "NESCOPE_HDR")]
|
||||
hdr: bool,
|
||||
@@ -386,9 +413,17 @@ fn main() {
|
||||
}
|
||||
|
||||
// ── Frame-callback timer ──────────────────────────────────────────────
|
||||
// Send wl_surface.frame done events at the target fps. This is what
|
||||
// drives the game's render loop in the absence of a real scanout.
|
||||
let frame_interval = Duration::from_micros(1_000_000 / args.fps.max(1) as u64);
|
||||
// Sends wl_surface.frame done events, releases the held buffer and posts
|
||||
// presentation feedback.
|
||||
//
|
||||
// Deliberately *not* `--fps`. This cadence only ever throttles a FIFO
|
||||
// client, so using it as a frame limiter caps the games that opted into
|
||||
// V-Sync and does nothing at all to the ones that did not — which is the
|
||||
// wrong way round, and it capped them at 60 while sessions asked for 120.
|
||||
// The capture layer holds the game instead, and this runs fast enough to
|
||||
// stay out of the way.
|
||||
let frame_interval =
|
||||
Duration::from_micros(1_000_000 / args.frame_callback_hz.max(1) as u64);
|
||||
loop_handle
|
||||
.insert_source(Timer::from_duration(frame_interval), move |_, _, data| {
|
||||
if let Some(ref mut li) = data.libinput {
|
||||
|
||||
@@ -178,7 +178,7 @@ pub struct NescopeState {
|
||||
/// Whether the cursor has been explicitly positioned at least once.
|
||||
pub cursor_initialized: bool,
|
||||
/// Game FPS tracking: frame count since last stats send.
|
||||
game_frame_count: u64,
|
||||
pub game_frame_count: u64,
|
||||
/// Last time stats were sent.
|
||||
last_stats_time: std::time::Instant,
|
||||
/// Last cursor position sent over IPC (for change detection).
|
||||
@@ -753,13 +753,16 @@ impl NescopeState {
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Frame callbacks — driven by the fps timer in main.rs
|
||||
// Frame callbacks — driven by the frame-callback timer in main.rs
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// Called from the calloop timer at target fps.
|
||||
/// Called from the calloop timer at `--frame-callback-hz`.
|
||||
///
|
||||
/// Note what this does *not* count: the game's frames. This runs whether or
|
||||
/// not anything was drawn, so counting ticks here reported the timer's own
|
||||
/// rate as the game's — true only while the two were the same number, which
|
||||
/// they no longer are. `game_frame_count` is incremented on commit.
|
||||
pub fn on_frame_tick(&mut self) {
|
||||
self.game_frame_count += 1;
|
||||
|
||||
let output = self.output.clone();
|
||||
let now = self.clock.now();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user