Render only when shared state changes
Add shared-data and bundle invalidation signals, and have handles publish them automatically for SAB and graph mutations. Document the raw core worker protocol in a dedicated VitePress site and simplify the glTF import and picking example. Amp-Thread-ID: https://ampcode.com/threads/T-01a01ff8-b91f-724f-8952-f07c6b5042fd Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
+9
-3
@@ -155,7 +155,9 @@ impl Core {
|
||||
self.data
|
||||
.borrow_mut()
|
||||
.delete_object(name, id)
|
||||
.map_err(JsError::new)
|
||||
.map_err(JsError::new)?;
|
||||
self.data.borrow_mut().mark_dirty();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile_graph(&self, source: &str) -> Result<String, JsError> {
|
||||
@@ -171,7 +173,9 @@ impl Core {
|
||||
self.store
|
||||
.borrow_mut()
|
||||
.switch(id, gpu, &self.data.borrow())
|
||||
.map_err(|error| JsError::new(&error))
|
||||
.map_err(|error| JsError::new(&error))?;
|
||||
self.data.borrow_mut().loadout_ready();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn upload_texture(
|
||||
@@ -187,7 +191,9 @@ impl Core {
|
||||
self.store
|
||||
.borrow_mut()
|
||||
.upload_texture(name, mip_level, image, gpu)
|
||||
.map_err(|error| JsError::new(&error))
|
||||
.map_err(|error| JsError::new(&error))?;
|
||||
self.data.borrow_mut().mark_dirty();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_texture(&self, name: &str) {
|
||||
|
||||
+42
-10
@@ -46,7 +46,8 @@ impl RenderData {
|
||||
rows: HashMap::new(),
|
||||
slots: HashMap::new(),
|
||||
};
|
||||
data.create_rows("info".into(), 1, 32, "f32".into())?;
|
||||
data.create_rows("signals".into(), 1, 32, "f32".into())?;
|
||||
data.write_signal(5, 1.0);
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
@@ -72,7 +73,7 @@ impl RenderData {
|
||||
if rows <= current.rows {
|
||||
return Ok(current);
|
||||
}
|
||||
if name == "info" {
|
||||
if name == "signals" {
|
||||
return Err("ROWS_BUILTIN");
|
||||
}
|
||||
return self.grow_rows(current, rows);
|
||||
@@ -95,7 +96,7 @@ impl RenderData {
|
||||
}
|
||||
|
||||
pub fn delete_rows(&mut self, name: &str) -> Result<(), &'static str> {
|
||||
if name == "info" {
|
||||
if name == "signals" {
|
||||
return Err("ROWS_BUILTIN");
|
||||
}
|
||||
if self
|
||||
@@ -128,7 +129,7 @@ impl RenderData {
|
||||
}
|
||||
|
||||
pub fn allocate_object(&mut self, name: &str) -> Result<(u32, bool), &'static str> {
|
||||
if name == "info" {
|
||||
if name == "signals" {
|
||||
return Err("ROWS_BUILTIN");
|
||||
}
|
||||
let slots = self.slots.get(name).ok_or("ROWS_UNKNOWN")?;
|
||||
@@ -198,8 +199,8 @@ impl RenderData {
|
||||
Ok(descriptor)
|
||||
}
|
||||
|
||||
pub fn update_info(&mut self, delta: f32, frame: u32, elapsed: f32, fps: u32) {
|
||||
let start = self.arena_start + (self.rows["info"].offset - self.base) as usize;
|
||||
pub fn update_signals(&mut self, delta: f32, frame: u32, elapsed: f32, fps: u32) {
|
||||
let start = self.arena_start + (self.rows["signals"].offset - self.base) as usize;
|
||||
for (index, value) in [delta, frame as f32, elapsed, fps as f32]
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -209,10 +210,37 @@ impl RenderData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip_render(&self) -> bool {
|
||||
let rows = &self.rows["info"];
|
||||
let start = self.arena_start + (rows.offset - self.base) as usize + 16;
|
||||
f32::from_le_bytes(self.arena[start..start + 4].try_into().unwrap()) == 1.0
|
||||
pub fn render_pending(&self) -> bool {
|
||||
self.signal(4) == 0.0 && self.signal(5) != 0.0 && self.signal(6) == 0.0
|
||||
}
|
||||
|
||||
pub fn begin_render(&mut self) -> bool {
|
||||
if !self.render_pending() {
|
||||
return false;
|
||||
}
|
||||
self.write_signal(5, 0.0);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn mark_dirty(&mut self) {
|
||||
self.write_signal(5, 1.0);
|
||||
}
|
||||
|
||||
pub fn loadout_ready(&mut self) {
|
||||
self.write_signal(6, 0.0);
|
||||
self.mark_dirty();
|
||||
}
|
||||
|
||||
fn signal(&self, index: usize) -> f32 {
|
||||
let rows = &self.rows["signals"];
|
||||
let start = self.arena_start + (rows.offset - self.base) as usize + index * 4;
|
||||
f32::from_le_bytes(self.arena[start..start + 4].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn write_signal(&mut self, index: usize, value: f32) {
|
||||
let rows = &self.rows["signals"];
|
||||
let start = self.arena_start + (rows.offset - self.base) as usize + index * 4;
|
||||
self.arena[start..start + 4].copy_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
|
||||
fn reserve(&mut self, bytes: u32) -> Result<u32, &'static str> {
|
||||
@@ -263,3 +291,7 @@ fn align(value: u32) -> Option<u32> {
|
||||
.checked_add(ALIGNMENT - 1)
|
||||
.map(|value| value & !(ALIGNMENT - 1))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "render_data_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
use super::RenderData;
|
||||
|
||||
#[test]
|
||||
fn dirty_signal_is_consumed_once() {
|
||||
let mut data = RenderData::new(64).unwrap();
|
||||
|
||||
assert!(data.rows("signals").is_some());
|
||||
assert!(data.begin_render());
|
||||
assert!(!data.begin_render());
|
||||
|
||||
data.mark_dirty();
|
||||
assert!(data.begin_render());
|
||||
assert!(!data.render_pending());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_and_bundle_signals_hold_dirty_work() {
|
||||
let mut data = RenderData::new(64).unwrap();
|
||||
assert!(data.begin_render());
|
||||
|
||||
data.write_signal(4, 1.0);
|
||||
data.mark_dirty();
|
||||
assert!(!data.begin_render());
|
||||
data.write_signal(4, 0.0);
|
||||
assert!(data.begin_render());
|
||||
|
||||
data.write_signal(6, 1.0);
|
||||
data.mark_dirty();
|
||||
assert!(!data.begin_render());
|
||||
data.loadout_ready();
|
||||
assert!(data.begin_render());
|
||||
}
|
||||
+25
-13
@@ -116,22 +116,34 @@ impl RenderLoop {
|
||||
control.elapsed.set(elapsed);
|
||||
let frame = control.frame.get().wrapping_add(1);
|
||||
control.frame.set(frame);
|
||||
let skip = {
|
||||
{
|
||||
let mut data = data.borrow_mut();
|
||||
data.update_info(delta as f32, frame, elapsed as f32, control.fps.get());
|
||||
data.skip_render()
|
||||
};
|
||||
let submission = if !skip {
|
||||
data.update_signals(delta as f32, frame, elapsed as f32, control.fps.get());
|
||||
}
|
||||
let submission = if data.borrow().render_pending() {
|
||||
if let (Some(gpu), Some(loadout)) =
|
||||
(gpu.borrow_mut().as_mut(), store.borrow_mut().active_mut())
|
||||
{
|
||||
let profile = control.profiling.get()
|
||||
&& !control.profile_pending.get()
|
||||
&& started >= control.profile_after.get();
|
||||
gpu.render(loadout, &data.borrow(), profile)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|profile| (profile, gpu.adapter.clone(), gpu.width, gpu.height))
|
||||
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
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -206,7 +218,7 @@ impl Wgpu {
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
self.surface.get_current_texture().map_err(|_| "SURFACE")?
|
||||
}
|
||||
Err(wgpu::SurfaceError::Timeout) => return Ok(None),
|
||||
Err(wgpu::SurfaceError::Timeout) => return Err("SURFACE".into()),
|
||||
Err(_) => return Err("SURFACE".into()),
|
||||
};
|
||||
let surface_view = output
|
||||
|
||||
Reference in New Issue
Block a user