Fix render pacing and profiler readback
Amp-Thread-ID: https://ampcode.com/threads/T-01a01380-b478-77d0-84a0-102880a5c5ae Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Generated
-22
@@ -117,15 +117,6 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-channel"
|
|
||||||
version = "0.3.34"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.34"
|
version = "0.3.34"
|
||||||
@@ -150,18 +141,6 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gloo-timers"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.7.1"
|
version = "2.7.1"
|
||||||
@@ -738,7 +717,6 @@ dependencies = [
|
|||||||
name = "yawn-core"
|
name = "yawn-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gloo-timers",
|
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -353,10 +353,10 @@ export class Scene {
|
|||||||
arenaBytes: options.arenaBytes,
|
arenaBytes: options.arenaBytes,
|
||||||
debug: options.debug,
|
debug: options.debug,
|
||||||
});
|
});
|
||||||
this.ready = this.#initialize(options.fps ?? 60);
|
this.ready = this.#initialize(options.fps);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #initialize(fps: number) {
|
async #initialize(fps?: number) {
|
||||||
await this.core.ready;
|
await this.core.ready;
|
||||||
for (const [name, stride, format] of rows)
|
for (const [name, stride, format] of rows)
|
||||||
await this.core.createRows({ name, rows: 1, stride, format });
|
await this.core.createRows({ name, rows: 1, stride, format });
|
||||||
@@ -373,7 +373,7 @@ export class Scene {
|
|||||||
this.core
|
this.core
|
||||||
.array("materials")
|
.array("materials")
|
||||||
.write(material, [1, 1, 1, 1, 0, 0.7, 0, 0, 0, 0, 1, 0.5]);
|
.write(material, [1, 1, 1, 1, 0, 0.7, 0, 0, 0, 0, 1, 0.5]);
|
||||||
await this.core.setFps(fps);
|
if (fps !== undefined) await this.core.setFps(fps);
|
||||||
await this.#compileRenderGraph();
|
await this.#compileRenderGraph();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ crate-type = ["cdylib"]
|
|||||||
path = "lib.rs"
|
path = "lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gloo-timers = { version = "0.3", features = ["futures"] }
|
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|||||||
+58
-34
@@ -1,9 +1,7 @@
|
|||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use gloo_timers::future::TimeoutFuture;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
|
||||||
use crate::gpu::Wgpu;
|
use crate::gpu::Wgpu;
|
||||||
use crate::gpu_resource::{GpuPass, ProfileMap};
|
use crate::gpu_resource::{GpuPass, ProfileMap};
|
||||||
@@ -11,6 +9,29 @@ use crate::graph::{ColorAttachment, DepthAttachment};
|
|||||||
use crate::render_data::RenderData;
|
use crate::render_data::RenderData;
|
||||||
use crate::store::{Loadout, Store};
|
use crate::store::{Loadout, Store};
|
||||||
|
|
||||||
|
#[wasm_bindgen(inline_js = r#"
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"#)]
|
||||||
|
extern "C" {
|
||||||
|
#[wasm_bindgen(js_name = nextFrame)]
|
||||||
|
async fn next_frame();
|
||||||
|
#[wasm_bindgen(js_name = yieldTask)]
|
||||||
|
async fn yield_task();
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RenderLoop {
|
pub struct RenderLoop {
|
||||||
playing: Cell<bool>,
|
playing: Cell<bool>,
|
||||||
fps: Cell<u32>,
|
fps: Cell<u32>,
|
||||||
@@ -19,24 +40,23 @@ pub struct RenderLoop {
|
|||||||
elapsed: Cell<f64>,
|
elapsed: Cell<f64>,
|
||||||
last: Cell<f64>,
|
last: Cell<f64>,
|
||||||
profiling: Cell<bool>,
|
profiling: Cell<bool>,
|
||||||
|
profile_pending: Cell<bool>,
|
||||||
|
profile_after: Cell<f64>,
|
||||||
profile: RefCell<Option<String>>,
|
profile: RefCell<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Submission {
|
|
||||||
completed: Arc<AtomicBool>,
|
|
||||||
profile: Option<ProfileMap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderLoop {
|
impl RenderLoop {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
playing: Cell::new(true),
|
playing: Cell::new(true),
|
||||||
fps: Cell::new(60),
|
fps: Cell::new(0),
|
||||||
started: Cell::new(false),
|
started: Cell::new(false),
|
||||||
frame: Cell::new(0),
|
frame: Cell::new(0),
|
||||||
elapsed: Cell::new(0.0),
|
elapsed: Cell::new(0.0),
|
||||||
last: Cell::new(js_sys::Date::now()),
|
last: Cell::new(js_sys::Date::now()),
|
||||||
profiling: Cell::new(false),
|
profiling: Cell::new(false),
|
||||||
|
profile_pending: Cell::new(false),
|
||||||
|
profile_after: Cell::new(0.0),
|
||||||
profile: RefCell::new(None),
|
profile: RefCell::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +71,7 @@ impl RenderLoop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_fps(&self, fps: u32) -> Result<(), &'static str> {
|
pub fn set_fps(&self, fps: u32) -> Result<(), &'static str> {
|
||||||
if !(1..=1000).contains(&fps) {
|
if fps > 1000 {
|
||||||
return Err("FPS");
|
return Err("FPS");
|
||||||
}
|
}
|
||||||
self.fps.set(fps);
|
self.fps.set(fps);
|
||||||
@@ -60,6 +80,7 @@ impl RenderLoop {
|
|||||||
|
|
||||||
pub fn set_profiling(&self, enabled: bool) {
|
pub fn set_profiling(&self, enabled: bool) {
|
||||||
self.profiling.set(enabled);
|
self.profiling.set(enabled);
|
||||||
|
self.profile_after.set(0.0);
|
||||||
if !enabled {
|
if !enabled {
|
||||||
self.profile.borrow_mut().take();
|
self.profile.borrow_mut().take();
|
||||||
}
|
}
|
||||||
@@ -81,8 +102,15 @@ impl RenderLoop {
|
|||||||
let control = self.clone();
|
let control = self.clone();
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
loop {
|
loop {
|
||||||
|
next_frame().await;
|
||||||
|
if !control.playing.get() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let started = js_sys::Date::now();
|
let started = js_sys::Date::now();
|
||||||
if control.playing.get() {
|
let fps = control.fps.get();
|
||||||
|
if fps != 0 && started - control.last.get() < 900.0 / f64::from(fps) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let delta = (started - control.last.replace(started)) / 1000.0;
|
let delta = (started - control.last.replace(started)) / 1000.0;
|
||||||
let elapsed = control.elapsed.get() + delta;
|
let elapsed = control.elapsed.get() + delta;
|
||||||
control.elapsed.set(elapsed);
|
control.elapsed.set(elapsed);
|
||||||
@@ -97,23 +125,26 @@ impl RenderLoop {
|
|||||||
if let (Some(gpu), Some(loadout)) =
|
if let (Some(gpu), Some(loadout)) =
|
||||||
(gpu.borrow_mut().as_mut(), store.borrow_mut().active_mut())
|
(gpu.borrow_mut().as_mut(), store.borrow_mut().active_mut())
|
||||||
{
|
{
|
||||||
gpu.render(loadout, &data.borrow(), control.profiling.get())
|
let profile = control.profiling.get()
|
||||||
|
&& !control.profile_pending.get()
|
||||||
|
&& started >= control.profile_after.get();
|
||||||
|
gpu.render(loadout, &data.borrow(), profile)
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
.map(|profile| (profile, gpu.adapter.clone(), gpu.width, gpu.height))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if let Some(submission) = submission {
|
if let Some((profile, adapter, width, height)) = submission {
|
||||||
while !submission.completed.load(Ordering::Acquire) {
|
control.profile_pending.set(true);
|
||||||
TimeoutFuture::new(0).await;
|
control.profile_after.set(started + 250.0);
|
||||||
}
|
let control = control.clone();
|
||||||
let wall_milliseconds = js_sys::Date::now() - started;
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
if let Some(profile) = submission.profile {
|
|
||||||
while profile.state() == 0 {
|
while profile.state() == 0 {
|
||||||
TimeoutFuture::new(0).await;
|
yield_task().await;
|
||||||
}
|
}
|
||||||
if profile.state() == 1 {
|
if profile.state() == 1 {
|
||||||
let passes = profile
|
let passes = profile
|
||||||
@@ -130,27 +161,24 @@ impl RenderLoop {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter_map(|pass| pass["milliseconds"].as_f64())
|
.filter_map(|pass| pass["milliseconds"].as_f64())
|
||||||
.sum::<f64>();
|
.sum::<f64>();
|
||||||
let gpu = gpu.borrow();
|
if control.profiling.get() {
|
||||||
let gpu = gpu.as_ref().unwrap();
|
|
||||||
*control.profile.borrow_mut() = Some(
|
*control.profile.borrow_mut() = Some(
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"frame": frame,
|
"frame": frame,
|
||||||
"milliseconds": milliseconds,
|
"milliseconds": milliseconds,
|
||||||
"wallMilliseconds": wall_milliseconds,
|
"readbackMilliseconds": js_sys::Date::now() - started,
|
||||||
"adapter": gpu.adapter,
|
"adapter": adapter,
|
||||||
"canvas": { "width": gpu.width, "height": gpu.height },
|
"canvas": { "width": width, "height": height },
|
||||||
"passes": passes,
|
"passes": passes,
|
||||||
})
|
})
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
control.profile_pending.set(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let target = 1000.0 / f64::from(control.fps.get());
|
|
||||||
let wait = (target - (js_sys::Date::now() - started)).max(0.0) as u32;
|
|
||||||
TimeoutFuture::new(if control.playing.get() { wait } else { 50 }).await;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +189,7 @@ impl Wgpu {
|
|||||||
loadout: &mut Loadout,
|
loadout: &mut Loadout,
|
||||||
data: &RenderData,
|
data: &RenderData,
|
||||||
profile: bool,
|
profile: bool,
|
||||||
) -> Result<Option<Submission>, String> {
|
) -> Result<Option<ProfileMap>, String> {
|
||||||
for buffer in loadout.resources.buffers.values() {
|
for buffer in loadout.resources.buffers.values() {
|
||||||
if !buffer.sync_each_frame {
|
if !buffer.sync_each_frame {
|
||||||
continue;
|
continue;
|
||||||
@@ -282,11 +310,7 @@ impl Wgpu {
|
|||||||
})
|
})
|
||||||
.flatten();
|
.flatten();
|
||||||
output.present();
|
output.present();
|
||||||
let completed = Arc::new(AtomicBool::new(false));
|
Ok(profile)
|
||||||
let callback = completed.clone();
|
|
||||||
self.queue
|
|
||||||
.on_submitted_work_done(move || callback.store(true, Ordering::Release));
|
|
||||||
Ok(Some(Submission { completed, profile }))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ onUnmounted(() => {
|
|||||||
<span>{{ adapterInfo || profile.adapter }}</span>
|
<span>{{ adapterInfo || profile.adapter }}</span>
|
||||||
<span>
|
<span>
|
||||||
{{ profile.canvas.width }}×{{ profile.canvas.height }} ·
|
{{ profile.canvas.width }}×{{ profile.canvas.height }} ·
|
||||||
{{ profile.wallMilliseconds.toFixed(2) }} ms wall
|
{{ profile.readbackMilliseconds.toFixed(2) }} ms readback latency
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="profilerSupported === false">
|
<p v-if="profilerSupported === false">
|
||||||
|
|||||||
@@ -402,7 +402,7 @@ for (let y = 0; y < 64; y++) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const scene = new Scene(canvas, { fps: 1000, hdr: true });
|
const scene = new Scene(canvas, { hdr: true });
|
||||||
await scene.ready;
|
await scene.ready;
|
||||||
log("Benchmark core ready; preparing geometry…");
|
log("Benchmark core ready; preparing geometry…");
|
||||||
const camera = new ArcRotateCamera(scene, {
|
const camera = new ArcRotateCamera(scene, {
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ await core.setProfiler(false);
|
|||||||
stop();
|
stop();
|
||||||
```
|
```
|
||||||
|
|
||||||
`new YawnCore(canvas, { debug: true })` enables the same timestamp-query mode at startup. Timings describe physical GPU passes and actual compiled draw counts; compatible indexed draws over consecutive instances collapse into one command. The sidebar also reports canvas size and wall-clock completion time.
|
`new YawnCore(canvas, { debug: true })` enables the same timestamp-query mode at startup. Samples are read back asynchronously, so profiling does not serialize the GPU queue. Timings describe physical GPU passes and actual compiled draw counts; compatible indexed draws over consecutive instances collapse into one command. The sidebar also reports canvas size and wall-clock readback latency.
|
||||||
|
|
||||||
The saved **Forward benchmark** playground renders 138 logical objects and 4.5 million triangles. Add `&grid=32` to lower geometry density or `&overdraw=1` to stack the objects while diagnosing depth and fragment cost.
|
The saved **Forward benchmark** playground renders 138 logical objects and 4.5 million triangles. Add `&grid=32` to lower geometry density or `&overdraw=1` to stack the objects while diagnosing depth and fragment cost.
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const scene = new Scene(canvas, { hdr: true, fps: 60 });
|
|||||||
await scene.ready;
|
await scene.ready;
|
||||||
```
|
```
|
||||||
|
|
||||||
`Scene` initializes conventional SOA rows and loads one clustered-forward HDR render graph. The core itself still starts with only its eight-float `info` row.
|
Omit `fps` to render as fast as the browser and GPU allow. `Scene` initializes conventional SOA rows and loads one clustered-forward HDR render graph. The core itself still starts with only its eight-float `info` row.
|
||||||
|
|
||||||
## 3. Add a triangle
|
## 3. Add a triangle
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user