diff --git a/crates/bootstrap/src/plugins.rs b/crates/bootstrap/src/plugins.rs index 6f523e4..5ee7e5c 100644 --- a/crates/bootstrap/src/plugins.rs +++ b/crates/bootstrap/src/plugins.rs @@ -68,11 +68,17 @@ pub fn add_runtime_plugins_with_role(app: &mut App, role: RuntimeRole) { }); if cfg!(target_os = "macos") && pipelined_rendering() { // The Metal surface must be created on the main thread; pipelined, only - // extraction runs there. + // extraction runs there. Window extraction inserts `ExtractedWindow` + // through commands, so they are applied first or the first frame finds + // no window here and the render thread creates the surface instead. render_app.add_systems( bevy::render::ExtractSchedule, - bevy::render::view::create_surfaces - .run_if(bevy::render::view::need_surface_configuration) + ( + bevy::ecs::schedule::ApplyDeferred, + bevy::render::view::create_surfaces + .run_if(bevy::render::view::need_surface_configuration), + ) + .chain() .after(bevy::render::camera::extract_cameras), ); } @@ -123,10 +129,9 @@ const PIPELINED_RENDERING_ENV: &str = "IW4L_PIPELINED_RENDERING"; /// Overlap rendering with the next main frame. Extraction remains the ownership /// boundary; the bounded render channel permits one outstanding frame. /// Set IW4L_PIPELINED_RENDERING=0 for synchronous presentation. -/// fn pipelined_rendering() -> bool { match std::env::var_os(PIPELINED_RENDERING_ENV) { - None => !cfg!(target_os = "macos"), + None => true, Some(_) => perf::switch(PIPELINED_RENDERING_ENV), } } @@ -138,16 +143,18 @@ const FRAME_LATENCY_ENV: &str = "IW4L_FRAME_LATENCY"; /// wgpu calls this a hint and the backend is free to clamp it: on Vulkan it is /// bound to the number of swapchain images, so a run that asked for two did /// not necessarily get two, and only a measurement says which. One is the -/// default because it is what the runtime shipped; the variable exists so the -/// other arm needs no rebuild, and the manifest records the number that was -/// asked for — never the number the driver granted, which this process cannot -/// read back. +/// default because it is what the runtime shipped, except on macOS: there a +/// latency of one leaves a single free drawable, so the render thread waits for +/// the display before every frame and pipelined rendering gains nothing. The +/// variable exists so the other arm needs no rebuild, and the manifest records +/// the number that was asked for — never the number the driver granted, which +/// this process cannot read back. /// /// The value is a count, not a switch: anything unparseable or zero is the /// default, and says so rather than silently picking an arm. Read once, so the /// window and the manifest cannot disagree and the complaint is made once. pub(crate) fn frame_latency() -> u32 { - const DEFAULT: u32 = 1; + const DEFAULT: u32 = if cfg!(target_os = "macos") { 2 } else { 1 }; static FRAMES: std::sync::OnceLock = std::sync::OnceLock::new(); *FRAMES.get_or_init(|| { let Some(asked) = std::env::var_os(FRAME_LATENCY_ENV) else { diff --git a/docs/BUILD.md b/docs/BUILD.md index c622514..02e5aad 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -32,8 +32,9 @@ make setup-windows # rustup target + cargo-xwin, once macOS needs only the Xcode command line tools (`xcode-select --install`) and rustup: winit, wgpu (Metal), CoreAudio and gilrs link system frameworks, and the `x11` / `wayland` features compile to nothing. Menu and -`make map mp_boneyard` run on Apple silicon (M2 Max, Metal). Pipelined -rendering is off by default there; `IW4L_PIPELINED_RENDERING=1` turns it on. +`make map mp_boneyard` run on Apple silicon (M2 Max, M4 Pro, Metal). Rendering +is pipelined with a frame latency of 2 there; `IW4L_PIPELINED_RENDERING=0` and +`IW4L_FRAME_LATENCY=1` restore the serialised arm. Game data: the Windows depot of a Steam copy, `steamcmd +@sSteamCmdForcePlatformType windows +force_install_dir ~/Games/MW2 +login +app_update 10190 +quit`, then `IW4L_GAMES=~/Games`.