mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
perf: improve rendering performance by ~20x
This commit is contained in:
@@ -353,6 +353,14 @@ impl GpuContext {
|
||||
self.supports_external_texture_copies
|
||||
}
|
||||
|
||||
/// The HTML canvas that owns the backing WebGL context, if running on the
|
||||
/// WebGL fallback. Callers on that path can mount this canvas directly
|
||||
/// instead of copying pixels out of it every frame.
|
||||
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
|
||||
pub fn gl_canvas(&self) -> Option<&web_sys::HtmlCanvasElement> {
|
||||
self.gl_canvas.as_ref()
|
||||
}
|
||||
|
||||
pub fn render_texture_to_surface(
|
||||
&self,
|
||||
texture: &wgpu::Texture,
|
||||
@@ -571,7 +579,7 @@ impl GpuContext {
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
|
||||
fn render_texture_to_gl_canvas_surface(
|
||||
pub fn render_texture_to_gl_canvas_surface(
|
||||
&self,
|
||||
texture: &wgpu::Texture,
|
||||
width: u32,
|
||||
|
||||
@@ -24,7 +24,7 @@ serde-wasm-bindgen = "0.6.5"
|
||||
time = { version = "0.1.0", path = "../crates/time", features = ["wasm"] }
|
||||
wasm-bindgen = "0.2.116"
|
||||
wasm-bindgen-futures = "0.4.66"
|
||||
web-sys = { version = "0.3.93", features = ["OffscreenCanvas", "HtmlCanvasElement", "CanvasRenderingContext2d", "Document", "Window"] }
|
||||
web-sys = { version = "0.3.93", features = ["OffscreenCanvas", "HtmlCanvasElement", "CanvasRenderingContext2d", "Document", "Window", "Performance"] }
|
||||
|
||||
[features]
|
||||
default = ["wasm"]
|
||||
|
||||
+39
-18
@@ -11,6 +11,7 @@ use crate::gpu::{
|
||||
import_canvas_texture, read_offscreen_canvas_property, read_serde_property, read_u32_property,
|
||||
with_gpu_runtime,
|
||||
};
|
||||
use crate::perf;
|
||||
|
||||
struct CompositorRuntime {
|
||||
canvas: web_sys::HtmlCanvasElement,
|
||||
@@ -24,13 +25,21 @@ thread_local! {
|
||||
#[wasm_bindgen(js_name = initCompositor)]
|
||||
pub fn init_compositor(width: u32, height: u32) -> Result<(), JsValue> {
|
||||
with_gpu_runtime(|gpu_runtime| {
|
||||
let document = web_sys::window()
|
||||
.and_then(|window| window.document())
|
||||
.ok_or_else(|| JsValue::from_str("Document is not available"))?;
|
||||
let canvas: web_sys::HtmlCanvasElement = document
|
||||
.create_element("canvas")?
|
||||
.dyn_into()
|
||||
.map_err(|_| JsValue::from_str("Failed to create compositor canvas"))?;
|
||||
// On WebGL, wgpu is bound to a specific canvas; reuse it so the UI
|
||||
// can mount the output directly instead of copying pixels through
|
||||
// an intermediate 2D canvas every frame. On WebGPU, surface rendering
|
||||
// works against any canvas so we create a fresh one.
|
||||
let canvas = if let Some(gl_canvas) = gpu_runtime.context.gl_canvas() {
|
||||
gl_canvas.clone()
|
||||
} else {
|
||||
let document = web_sys::window()
|
||||
.and_then(|window| window.document())
|
||||
.ok_or_else(|| JsValue::from_str("Document is not available"))?;
|
||||
document
|
||||
.create_element("canvas")?
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||
.map_err(|_| JsValue::from_str("Failed to create compositor canvas"))?
|
||||
};
|
||||
canvas.set_width(width);
|
||||
canvas.set_height(height);
|
||||
|
||||
@@ -119,8 +128,12 @@ pub fn release_texture(id: String) -> Result<(), JsValue> {
|
||||
|
||||
#[wasm_bindgen(js_name = renderFrame)]
|
||||
pub fn render_frame(options: JsValue) -> Result<(), JsValue> {
|
||||
perf::reset();
|
||||
|
||||
let t_deserialize = perf::now_ms();
|
||||
let frame: FrameDescriptor = serde_wasm_bindgen::from_value(options)
|
||||
.map_err(|error| JsValue::from_str(&format!("Invalid frame descriptor: {error}")))?;
|
||||
perf::record("wasm.deserialize", perf::now_ms() - t_deserialize);
|
||||
|
||||
with_gpu_runtime(|gpu_runtime| {
|
||||
COMPOSITOR_RUNTIME.with(|runtime| {
|
||||
@@ -132,13 +145,16 @@ pub fn render_frame(options: JsValue) -> Result<(), JsValue> {
|
||||
};
|
||||
|
||||
if gpu_runtime.context.supports_surface_rendering() {
|
||||
let t_surface = perf::now_ms();
|
||||
let surface = gpu_runtime
|
||||
.context
|
||||
.instance()
|
||||
.create_surface(wgpu::SurfaceTarget::Canvas(runtime.canvas.clone()))
|
||||
.map_err(|error| JsValue::from_str(&error.to_string()))?;
|
||||
perf::record("wasm.surfaceCreate", perf::now_ms() - t_surface);
|
||||
|
||||
runtime
|
||||
let t_render = perf::now_ms();
|
||||
let result = runtime
|
||||
.compositor
|
||||
.render_frame(
|
||||
&gpu_runtime.context,
|
||||
@@ -147,24 +163,29 @@ pub fn render_frame(options: JsValue) -> Result<(), JsValue> {
|
||||
surface: &surface,
|
||||
},
|
||||
)
|
||||
.map_err(|error| JsValue::from_str(&error.to_string()))
|
||||
.map_err(|error| JsValue::from_str(&error.to_string()));
|
||||
perf::record("wasm.renderFrameToSurface", perf::now_ms() - t_render);
|
||||
result
|
||||
} else {
|
||||
// WebGL cannot surface-render to an arbitrary canvas element.
|
||||
// Composite to a texture, then blit to the canvas via the 2D context.
|
||||
// WebGL surface-renders to the canvas its GL context was created
|
||||
// on; `init_compositor` already pointed `runtime.canvas` at that
|
||||
// same canvas, so presenting writes directly to the canvas the
|
||||
// UI mounts. No intermediate copy.
|
||||
let t_composite = perf::now_ms();
|
||||
let texture = runtime
|
||||
.compositor
|
||||
.render_frame_to_texture(&gpu_runtime.context, &frame)
|
||||
.map_err(|error| JsValue::from_str(&error.to_string()))?;
|
||||
perf::record("wasm.compositeToTexture", perf::now_ms() - t_composite);
|
||||
|
||||
let t_present = perf::now_ms();
|
||||
gpu_runtime
|
||||
.context
|
||||
.render_texture_via_gl_canvas(
|
||||
&texture,
|
||||
&runtime.canvas,
|
||||
frame.width,
|
||||
frame.height,
|
||||
)
|
||||
.map_err(|error| JsValue::from_str(&error.to_string()))
|
||||
.render_texture_to_gl_canvas_surface(&texture, frame.width, frame.height)
|
||||
.map_err(|error| JsValue::from_str(&error.to_string()))?;
|
||||
perf::record("wasm.presentToGlCanvas", perf::now_ms() - t_present);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
//! Per-frame profile buffer for the render pipeline.
|
||||
//!
|
||||
//! Sub-span timings are recorded into a thread-local during `renderFrame`
|
||||
//! and drained by JS via `getLastFrameProfile()`.
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use js_sys::{Array, Object, Reflect};
|
||||
use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
|
||||
|
||||
thread_local! {
|
||||
static LAST_FRAME_PROFILE: RefCell<Vec<(&'static str, f64)>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
pub(crate) fn now_ms() -> f64 {
|
||||
web_sys::window()
|
||||
.and_then(|window| window.performance())
|
||||
.map(|performance| performance.now())
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
pub(crate) fn reset() {
|
||||
LAST_FRAME_PROFILE.with(|cell| cell.borrow_mut().clear());
|
||||
}
|
||||
|
||||
pub(crate) fn record(name: &'static str, duration_ms: f64) {
|
||||
LAST_FRAME_PROFILE.with(|cell| cell.borrow_mut().push((name, duration_ms)));
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = getLastFrameProfile)]
|
||||
pub fn get_last_frame_profile() -> Array {
|
||||
LAST_FRAME_PROFILE.with(|cell| {
|
||||
let entries = cell.borrow();
|
||||
let array = Array::new_with_length(entries.len() as u32);
|
||||
for (index, (name, duration_ms)) in entries.iter().enumerate() {
|
||||
let entry = Object::new();
|
||||
Reflect::set(&entry, &JsValue::from_str("name"), &JsValue::from_str(name))
|
||||
.expect("set name");
|
||||
Reflect::set(
|
||||
&entry,
|
||||
&JsValue::from_str("durationMs"),
|
||||
&JsValue::from_f64(*duration_ms),
|
||||
)
|
||||
.expect("set durationMs");
|
||||
array.set(index as u32, entry.into());
|
||||
}
|
||||
array
|
||||
})
|
||||
}
|
||||
@@ -6,6 +6,8 @@ mod effects;
|
||||
mod gpu;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod masks;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod perf;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use compositor::*;
|
||||
@@ -15,4 +17,6 @@ pub use effects::*;
|
||||
pub use gpu::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use masks::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use perf::*;
|
||||
pub use time::*;
|
||||
|
||||
Reference in New Issue
Block a user