mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
Merge pull request #276 from storytold/fix/262-gpu-why
Render: render.backend says why it renders on the CPU; mcp --gpu documented (#262)
This commit is contained in:
7 files changed
+46
-5
No files matched your search
@@ -322,8 +322,10 @@ fn backend(args: &Args, default_demo: bool) -> Result<Backend, Failure> {
|
||||
fn session(args: &Args) -> Result<effectcraft_engine::Session, Failure> {
|
||||
let mut s = effectcraft_host::session();
|
||||
if args.flag("--gpu") {
|
||||
let g = effectcraft_gpu::Gpu::headless().ok_or_else(|| Failure::Error("--gpu: no usable GPU adapter".into()))?;
|
||||
let g = effectcraft_gpu::Gpu::try_headless().map_err(|e| Failure::Error(format!("--gpu: no usable GPU adapter ({e})")))?;
|
||||
s.accel = Some(std::sync::Arc::new(g));
|
||||
} else {
|
||||
s.accel_note = Some("headless: renders on the CPU unless started with --gpu".into());
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
@@ -1069,7 +1071,7 @@ fn adv3d_bench_comp(p: &mut effectcraft_engine::project::Project) -> Option<Item
|
||||
/// warm; "up/dn MB" are the GPU's uploads / readbacks per warm GPU frame.
|
||||
fn bench_gpu(s: &Session, args: &Args) -> Result<(), Failure> {
|
||||
use effectcraft_render::Backend;
|
||||
let gpu = effectcraft_gpu::Gpu::headless().ok_or_else(|| Failure::Error("--gpu: no usable GPU adapter".into()))?;
|
||||
let gpu = effectcraft_gpu::Gpu::try_headless().map_err(|e| Failure::Error(format!("--gpu: no usable GPU adapter ({e})")))?;
|
||||
let n = args.num("--n")?.unwrap_or(10.0).max(1.0) as usize;
|
||||
// Every comp, plus an adjustment-layer comp built for the benchmark (the main comp under a
|
||||
// full-frame adjustment layer with a GPU effect stack) and an Advanced 3D comp (`--adv3d`:
|
||||
|
||||
@@ -546,11 +546,20 @@ fn render_backend(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
pub fn backend_status(s: &Session) -> Value {
|
||||
let adapter = s.accel.as_ref().map(|a| a.name());
|
||||
let gpu = s.project.settings.gpu_acceleration;
|
||||
// Why it renders on the CPU (#262).
|
||||
let why = if !gpu {
|
||||
Some("the project's renderer is Mercury Software Only".to_string())
|
||||
} else if adapter.is_none() {
|
||||
Some(s.accel_note.clone().unwrap_or_else(|| "no GPU compositor is attached".into()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
json!({
|
||||
"renderer": if gpu { "Mercury GPU Acceleration" } else { "Mercury Software Only" },
|
||||
"gpuAcceleration": gpu,
|
||||
"adapter": adapter,
|
||||
"active": if gpu && adapter.is_some() { "gpu" } else { "cpu" },
|
||||
"why": why,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -340,6 +340,9 @@ pub struct Session {
|
||||
/// that ask for [`effectcraft_render::Backend::Gpu`]/`Auto`, and by Render Queue exports
|
||||
/// when the project's renderer is the GPU.
|
||||
pub accel: Option<Arc<dyn effectcraft_render::Accelerator>>,
|
||||
/// Why there is no [`Session::accel`] (`render.backend`'s `why`): the host's reason, e.g. a
|
||||
/// headless start without `--gpu` or the compositor's setup error.
|
||||
pub accel_note: Option<String>,
|
||||
/// Expression syntax checker (set by the host that links the expression engine).
|
||||
pub expr_check: Option<fn(&str) -> std::result::Result<(), String>>,
|
||||
pub importer: Option<Arc<dyn Importer>>,
|
||||
@@ -473,6 +476,7 @@ impl Default for Session {
|
||||
footage: Arc::new(NoFootage),
|
||||
expr: None,
|
||||
accel: None,
|
||||
accel_note: None,
|
||||
expr_check: None,
|
||||
importer: None,
|
||||
exporter: None,
|
||||
|
||||
@@ -536,3 +536,15 @@ fn unicode_label_colors_fall_back_without_panicking() {
|
||||
s.execute("prefs.set", json!({"key": "labels.0.color", "value": "#aBcD09"})).unwrap();
|
||||
assert_eq!(s.prefs.label_rgb(Label::Red), [0xab, 0xcd, 9]);
|
||||
}
|
||||
|
||||
/// `render.backend` says why it renders on the CPU (#262): the Software Only renderer, or the
|
||||
/// host's reason there is no GPU compositor.
|
||||
#[test]
|
||||
fn render_backend_says_why_it_renders_on_the_cpu() {
|
||||
let mut s = Session::default();
|
||||
let why = |s: &mut Session| s.execute("render.backend", json!({})).unwrap()["why"].clone();
|
||||
assert_eq!(why(&mut s), json!("no GPU compositor is attached"));
|
||||
s.accel_note = Some("headless: renders on the CPU unless started with --gpu".into());
|
||||
assert_eq!(why(&mut s), json!("headless: renders on the CPU unless started with --gpu"));
|
||||
assert_eq!(s.execute("render.backend", json!({"backend": "cpu"})).unwrap()["why"], json!("the project's renderer is Mercury Software Only"));
|
||||
}
|
||||
@@ -117,7 +117,13 @@ impl Gpu {
|
||||
/// On a device of its own (CLI, tests, benchmarks); `None` without a usable adapter.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn headless() -> Option<Gpu> {
|
||||
GpuContext::headless().map(Gpu::from_context)
|
||||
Gpu::try_headless().map_err(|e| log::info!("gpu: {e}")).ok()
|
||||
}
|
||||
|
||||
/// [`Gpu::headless`], with why there is none.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn try_headless() -> Result<Gpu, String> {
|
||||
pollster::block_on(GpuContext::request()).map(Gpu::from_context)
|
||||
}
|
||||
|
||||
/// On an existing device (the desktop app shares egui-wgpu's).
|
||||
|
||||
@@ -503,6 +503,7 @@ impl EffectcraftApp {
|
||||
self.gpu_checked = true;
|
||||
self.frames.retire_gpu();
|
||||
self.session.accel = None;
|
||||
self.session.accel_note = Some(format!("GPU preview retired: {}", failure.reason));
|
||||
self.session.layer_cache.clear();
|
||||
self.gpu = None;
|
||||
if let Some(rs) = &self.wgpu {
|
||||
@@ -559,10 +560,14 @@ impl EffectcraftApp {
|
||||
self.gpu = Some(g);
|
||||
self.wgpu = Some(rs.clone());
|
||||
}
|
||||
Ok(Err(e)) => log::info!("GPU compositor unavailable: {e}"),
|
||||
Ok(Err(e)) => {
|
||||
log::info!("GPU compositor unavailable: {e}");
|
||||
self.session.accel_note = Some(format!("GPU compositor unavailable: {e}"));
|
||||
}
|
||||
Err(e) => {
|
||||
let reason = e.downcast_ref::<String>().map(String::as_str).or_else(|| e.downcast_ref::<&str>().copied()).unwrap_or("backend panicked");
|
||||
log::warn!("GPU compositor setup failed; using CPU compositing: {reason}");
|
||||
self.session.accel_note = Some(format!("GPU compositor setup failed: {reason}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -36,7 +36,10 @@ You can also register it from the command line:
|
||||
Desktop, Cursor and the like) take the same `command` and `args`.
|
||||
|
||||
- **Headless** (`["mcp"]`): an in-process session with no window. Add `"--demo"` or
|
||||
`"--project", "file.ecproj"` to start with content. Startup is instant.
|
||||
`"--project", "file.ecproj"` to start with content. Startup is instant. It renders on the CPU
|
||||
unless you add `"--gpu"` (`["mcp", "--gpu"]`), which attaches the GPU compositor on a device of
|
||||
its own; if no adapter is usable the server says why and exits. `get_project`'s `renderer`
|
||||
(`render.backend`) reports `active` (`gpu` / `cpu`) and, on the CPU, `why`.
|
||||
- **Bridge** (`["mcp", "--bridge", "9877"]`): drives a running `effectcraft --control 9877`, so you
|
||||
see every change live. Bridge mode adds `screenshot` and the `ui_*` tools.
|
||||
|
||||
|
||||
Reference in new issue
Block a user