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>
151 lines
6.1 KiB
Rust
151 lines
6.1 KiB
Rust
use super::RenderData;
|
|
|
|
#[test]
|
|
fn rendering_does_not_require_a_dirty_signal() {
|
|
let mut data = RenderData::new(64).unwrap();
|
|
|
|
assert!(data.rows("signals").is_some());
|
|
for dirty in [0.0, 1.0, 0.0] {
|
|
data.write_signal(5, dirty);
|
|
assert!(data.render_allowed());
|
|
data.update_signals(0.001, 2, 0.002, 0);
|
|
assert!(data.render_allowed());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn skip_and_bundle_signals_still_block_rendering() {
|
|
let mut data = RenderData::new(64).unwrap();
|
|
data.write_signal(5, 0.0);
|
|
assert!(data.render_allowed());
|
|
|
|
data.write_signal(4, 1.0);
|
|
assert!(!data.render_allowed());
|
|
data.write_signal(4, 0.0);
|
|
assert!(data.render_allowed());
|
|
|
|
data.write_signal(6, 1.0);
|
|
assert!(!data.render_allowed());
|
|
data.write_signal(4, 1.0);
|
|
data.loadout_ready();
|
|
assert!(!data.render_allowed());
|
|
data.write_signal(4, 0.0);
|
|
assert!(data.render_allowed());
|
|
}
|
|
|
|
fn rows_of(data: &RenderData, name: &str) -> u32 {
|
|
data.rows(name).unwrap().rows
|
|
}
|
|
|
|
#[test]
|
|
fn spans_reuse_coalesced_gaps_before_growing() {
|
|
let mut data = RenderData::new(4096).unwrap();
|
|
data.create_rows("v".into(), 8, 16, "f32".into()).unwrap();
|
|
|
|
assert_eq!(data.allocate_object("v", 2).unwrap(), (0, false, false));
|
|
assert_eq!(data.allocate_object("v", 3).unwrap(), (2, false, false));
|
|
assert_eq!(data.allocate_object("v", 3).unwrap(), (5, false, false));
|
|
assert_eq!(rows_of(&data, "v"), 8);
|
|
|
|
// Freeing the two middle spans leaves one coalesced hole of 6 rows.
|
|
data.delete_object("v", 2).unwrap();
|
|
data.delete_object("v", 5).unwrap();
|
|
assert_eq!(data.delete_object("v", 5), Err("OBJECT_UNKNOWN"));
|
|
assert_eq!(data.allocate_object("v", 6).unwrap(), (2, false, false));
|
|
assert_eq!(rows_of(&data, "v"), 8);
|
|
|
|
// A span that does not fit doubles capacity instead of growing to the exact need; the only
|
|
// array in the arena extends in place, so nothing is staged.
|
|
assert_eq!(data.allocate_object("v", 1).unwrap(), (8, true, false));
|
|
assert_eq!(rows_of(&data, "v"), 16);
|
|
assert_eq!(data.allocate_object("v", 40).unwrap(), (9, true, false));
|
|
assert_eq!(rows_of(&data, "v"), 56);
|
|
assert_eq!(data.allocate_object("v", 0), Err("ROWS"));
|
|
}
|
|
|
|
#[test]
|
|
fn deleting_a_span_zeroes_every_row_and_keeps_neighbours() {
|
|
let mut data = RenderData::new(4096).unwrap();
|
|
data.create_rows("v".into(), 4, 16, "u32".into()).unwrap();
|
|
let (first, ..) = data.allocate_object("v", 2).unwrap();
|
|
let (second, ..) = data.allocate_object("v", 1).unwrap();
|
|
let base = (data.rows("v").unwrap().offset - data.base) as usize + data.arena_start;
|
|
data.arena[base..base + 48].fill(0xAB);
|
|
|
|
data.delete_object("v", first).unwrap();
|
|
assert!(data.bytes("v").unwrap()[..32].iter().all(|byte| *byte == 0));
|
|
assert!(data.bytes("v").unwrap()[32..48]
|
|
.iter()
|
|
.all(|byte| *byte == 0xAB));
|
|
assert_eq!(data.delete_rows("v"), Err("ROWS_ACTIVE"));
|
|
data.delete_object("v", second).unwrap();
|
|
data.delete_rows("v").unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn relocation_is_staged_until_the_writer_commits() {
|
|
let mut data = RenderData::new(4096).unwrap();
|
|
data.create_rows("a".into(), 1, 16, "u32".into()).unwrap();
|
|
data.create_rows("b".into(), 1, 16, "u32".into()).unwrap();
|
|
assert_eq!(data.allocate_object("a", 1).unwrap(), (0, false, false));
|
|
let old = data.rows("a").unwrap().clone();
|
|
let base = (old.offset - data.base) as usize + data.arena_start;
|
|
data.arena[base..base + 16].fill(0xCD);
|
|
|
|
// "b" sits right behind "a", so growing "a" must move it: the worker stages a zeroed region
|
|
// and keeps rendering from (and the arena owning) the old bytes.
|
|
let (staged, moved) = data.create_rows("a".into(), 5, 16, "u32".into()).unwrap();
|
|
assert!(moved);
|
|
assert_eq!(staged.rows, 5);
|
|
assert_ne!(staged.offset, old.offset);
|
|
assert_eq!(data.rows("a").unwrap().offset, old.offset);
|
|
assert_eq!(data.staged("a").unwrap().offset, staged.offset);
|
|
assert_eq!(
|
|
data.create_rows("a".into(), 6, 16, "u32".into()),
|
|
Err("ROWS_PENDING")
|
|
);
|
|
assert_eq!(data.delete_rows("a"), Err("ROWS_PENDING"));
|
|
assert!(!data.free.take(old.offset - data.base, old.bytes));
|
|
|
|
// The writer keeps writing the old rows until it copies them over and commits.
|
|
data.arena[base + 4..base + 8].fill(0xEF);
|
|
let target = (staged.offset - data.base) as usize + data.arena_start;
|
|
data.arena.copy_within(base..base + 16, target);
|
|
data.commit(&["a".into()]).unwrap();
|
|
assert_eq!(data.rows("a").unwrap().offset, staged.offset);
|
|
assert_eq!(
|
|
&data.bytes("a").unwrap()[..8],
|
|
&[0xCD, 0xCD, 0xCD, 0xCD, 0xEF, 0xEF, 0xEF, 0xEF]
|
|
);
|
|
assert!(data.bytes("a").unwrap()[16..].iter().all(|byte| *byte == 0));
|
|
assert!(data.free.take(old.offset - data.base, old.bytes));
|
|
assert_eq!(data.commit(&["a".into()]), Err("ROWS_PENDING"));
|
|
|
|
// Newly staged rows already feed the span allocator; the writer owns the staged region.
|
|
data.create_rows("c".into(), 1, 16, "u32".into()).unwrap();
|
|
assert_eq!(data.allocate_object("a", 4).unwrap(), (1, false, false));
|
|
let (id, grew, moved) = data.allocate_object("a", 1).unwrap();
|
|
assert_eq!((id, grew, moved), (5, true, true));
|
|
assert_eq!(data.staged("a").unwrap().rows, 10);
|
|
assert_eq!(data.rows("a").unwrap().rows, 5);
|
|
data.commit(&["a".into()]).unwrap();
|
|
assert_eq!(rows_of(&data, "a"), 10);
|
|
}
|
|
|
|
#[test]
|
|
fn discarding_a_staged_relocation_restores_capacity() {
|
|
let mut data = RenderData::new(4096).unwrap();
|
|
data.create_rows("a".into(), 2, 16, "u32".into()).unwrap();
|
|
data.create_rows("b".into(), 1, 16, "u32".into()).unwrap();
|
|
assert_eq!(data.allocate_object("a", 2).unwrap(), (0, false, false));
|
|
let (staged, moved) = data.create_rows("a".into(), 8, 16, "u32".into()).unwrap();
|
|
assert!(moved);
|
|
|
|
data.discard_pending();
|
|
assert_eq!(rows_of(&data, "a"), 2);
|
|
assert!(data.free.take(staged.offset - data.base, staged.bytes));
|
|
// Rows 2..8 must not be handed out: they only existed in the discarded region.
|
|
assert_eq!(data.allocate_object("a", 1).unwrap(), (2, true, false));
|
|
assert_eq!(rows_of(&data, "a"), 4);
|
|
}
|