Render continuously and optimize the glTF viewer
Remove vsync and dirty-flag frame gating, optimize PBR sampling and lighting, add a direct-to-canvas loadout, and preserve camera state on viewer resize. Add regression coverage and update rendering documentation. Amp-Thread-ID: https://ampcode.com/threads/T-01a0b382-1e79-712b-abd1-d29201c9bb5e Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
+41
-30
@@ -1,5 +1,7 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
@@ -14,22 +16,22 @@ const channel = new MessageChannel();
|
||||
const ready = [];
|
||||
channel.port1.onmessage = () => ready.shift()?.();
|
||||
|
||||
export function nextFrame() {
|
||||
return new Promise(resolve => requestAnimationFrame(resolve));
|
||||
}
|
||||
|
||||
export function yieldTask() {
|
||||
return new Promise(resolve => {
|
||||
ready.push(resolve);
|
||||
channel.port2.postMessage(0);
|
||||
});
|
||||
}
|
||||
|
||||
export function idleTask() {
|
||||
return new Promise(resolve => setTimeout(resolve, 16));
|
||||
}
|
||||
"#)]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = nextFrame)]
|
||||
async fn next_frame();
|
||||
#[wasm_bindgen(js_name = yieldTask)]
|
||||
async fn yield_task();
|
||||
#[wasm_bindgen(js_name = idleTask)]
|
||||
async fn idle_task();
|
||||
}
|
||||
|
||||
pub struct RenderLoop {
|
||||
@@ -102,13 +104,15 @@ impl RenderLoop {
|
||||
let control = self.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
loop {
|
||||
next_frame().await;
|
||||
// Let control messages run without waiting for display vsync.
|
||||
yield_task().await;
|
||||
if !control.playing.get() {
|
||||
idle_task().await;
|
||||
continue;
|
||||
}
|
||||
let started = js_sys::Date::now();
|
||||
let fps = control.fps.get();
|
||||
if fps != 0 && started - control.last.get() < 900.0 / f64::from(fps) {
|
||||
if fps != 0 && started - control.last.get() < 1000.0 / f64::from(fps) {
|
||||
continue;
|
||||
}
|
||||
let delta = (started - control.last.replace(started)) / 1000.0;
|
||||
@@ -120,37 +124,40 @@ impl RenderLoop {
|
||||
let mut data = data.borrow_mut();
|
||||
data.update_signals(delta as f32, frame, elapsed as f32, control.fps.get());
|
||||
}
|
||||
let submission = if data.borrow().render_pending() {
|
||||
let submission = if data.borrow().render_allowed() {
|
||||
if let (Some(gpu), Some(loadout)) =
|
||||
(gpu.borrow_mut().as_mut(), store.borrow_mut().active_mut())
|
||||
{
|
||||
if data.borrow_mut().begin_render() {
|
||||
let profile = control.profiling.get()
|
||||
&& !control.profile_pending.get()
|
||||
&& started >= control.profile_after.get();
|
||||
let result = {
|
||||
let data = data.borrow();
|
||||
gpu.render(loadout, &data, profile)
|
||||
};
|
||||
match result {
|
||||
Ok(profile) => profile.map(|profile| {
|
||||
(profile, gpu.adapter.clone(), gpu.width, gpu.height)
|
||||
}),
|
||||
Err(_) => {
|
||||
data.borrow_mut().mark_dirty();
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let profile = control.profiling.get()
|
||||
&& !control.profile_pending.get()
|
||||
&& started >= control.profile_after.get();
|
||||
gpu.render(loadout, &data.borrow(), profile)
|
||||
.ok()
|
||||
.map(|profile| {
|
||||
let completed = Arc::new(AtomicBool::new(false));
|
||||
let callback = completed.clone();
|
||||
gpu.queue.on_submitted_work_done(move || {
|
||||
callback.store(true, Ordering::Release);
|
||||
});
|
||||
(
|
||||
profile,
|
||||
gpu.adapter.clone(),
|
||||
gpu.width,
|
||||
gpu.height,
|
||||
completed,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some((profile, adapter, width, height)) = submission {
|
||||
let Some((profile, adapter, width, height, completed)) = submission else {
|
||||
idle_task().await;
|
||||
continue;
|
||||
};
|
||||
if let Some(profile) = profile {
|
||||
control.profile_pending.set(true);
|
||||
control.profile_after.set(started + 250.0);
|
||||
let control = control.clone();
|
||||
@@ -190,6 +197,10 @@ impl RenderLoop {
|
||||
control.profile_pending.set(false);
|
||||
});
|
||||
}
|
||||
// Bound GPU work in flight instead of queuing frames faster than it can draw.
|
||||
while !completed.load(Ordering::Acquire) {
|
||||
yield_task().await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user