mirror of
https://github.com/vladtrc/iw4L
synced 2026-10-25 09:59:40 -04:00
Let pipelined rendering run on macOS by creating the surface during extraction
IW4L_PIPELINED_RENDERING=1 panicked on the first frame on macOS ("can only
access NSView on the main thread" in raw-window-metal), which is why the
default is off there. Bevy's create_surfaces takes a NonSendMarker so that
the multi-threaded executor hands it back to the main thread, but the render
app's Render schedule runs on a single-threaded executor. With pipelining on
that schedule runs on the render thread, so create_surfaces built the
CAMetalLayer there and touched the NSView off the main thread.
With pipelining on, macOS now also runs create_surfaces in the render app's
ExtractSchedule, after extract_cameras and so after the windows are
extracted. Extraction runs on the main thread, so the surface is created and
configured there. The Render schedule's instance then finds it configured
and skips, or after a resize reconfigures it, which only sets CAMetalLayer
properties and is allowed off the main thread.
The macOS default stays off, so nothing changes unless
IW4L_PIPELINED_RENDERING=1 is set. docs/BUILD.md now says how to turn it on
instead of pointing at the plugins.rs explanation this removes.
Measured on an Apple M3 (mp_terminal, 9 bots, uncapped present, bench
recorder), pipelining off -> on: 87 -> 105 fps average, frame p50 10.8 ->
8.3 ms, p99 19.4 -> 18.4 ms, worst frames about the same (36 -> 34 ms),
main/render overlap 5.4 ms per frame, presented state one frame older.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,16 @@ pub fn add_runtime_plugins_with_role(app: &mut App, role: RuntimeRole) {
|
||||
render_app.edit_schedule(bevy::render::ExtractSchedule, |schedule| {
|
||||
schedule.set_executor(bevy::ecs::schedule::SingleThreadedExecutor::new());
|
||||
});
|
||||
if cfg!(target_os = "macos") && pipelined_rendering() {
|
||||
// The Metal surface must be created on the main thread; pipelined, only
|
||||
// extraction runs there.
|
||||
render_app.add_systems(
|
||||
bevy::render::ExtractSchedule,
|
||||
bevy::render::view::create_surfaces
|
||||
.run_if(bevy::render::view::need_surface_configuration)
|
||||
.after(bevy::render::camera::extract_cameras),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,11 +138,7 @@ const PIPELINED_RENDERING_ENV: &str = "IW4L_PIPELINED_RENDERING";
|
||||
/// boundary; the bounded render channel permits one outstanding frame.
|
||||
/// Set IW4L_PIPELINED_RENDERING=0 for synchronous presentation.
|
||||
///
|
||||
/// Off by default on macOS: AppKit only lets the main thread touch the NSView
|
||||
/// behind the Metal surface. Bevy hands `create_surfaces` back to the main
|
||||
/// thread through the multi-threaded executor, which the single-threaded
|
||||
/// `Render` schedule above bypasses, so the render thread would create it and
|
||||
/// panic in `raw-window-metal`.
|
||||
/// Off by default on macOS.
|
||||
fn pipelined_rendering() -> bool {
|
||||
match std::env::var_os(PIPELINED_RENDERING_ENV) {
|
||||
None => !cfg!(target_os = "macos"),
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ 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 (`bootstrap/src/plugins.rs` says why).
|
||||
rendering is off by default there; `IW4L_PIPELINED_RENDERING=1` turns it on.
|
||||
Game data: the Windows depot of a Steam copy, `steamcmd
|
||||
+@sSteamCmdForcePlatformType windows +force_install_dir ~/Games/MW2 +login
|
||||
<user> +app_update 10190 +quit`, then `IW4L_GAMES=~/Games`.
|
||||
|
||||
Reference in New Issue
Block a user