mod support;
use cranpose_render_common::graph::{
CachePolicy, DrawCommandId, DrawRunNode, IsolationReasons, LayerNode, PrimitivePhase,
ProjectiveTransform, RenderGraph, RenderNode,
};
use cranpose_render_common::raster_cache::LayerRasterCacheHashes;
use cranpose_render_common::style_shared::DrawPlacement;
use cranpose_render_common::Renderer;
use cranpose_ui_graphics::{
Brush, Color, CommandReplayState, DrawScope, DrawScopeDefault, GraphicsLayer, Point, Rect,
};
const SIZE: u32 = 408;
const CENTER: f32 = 204.0;
const FRAMES: usize = 8;
fn record_frame(frame: usize) -> DrawScopeDefault {
let mut scope =
DrawScopeDefault::new(cranpose_ui_graphics::Size::new(SIZE as f32, SIZE as f32));
let breathing = 1.0 - 0.0005 * frame as f32;
scope.draw_rect_at(
Rect {
x: 0.0,
y: 0.0,
width: SIZE as f32,
height: SIZE as f32,
},
Brush::solid(Color(0.02, 0.02, 0.05, 1.0)),
);
for m in 0..(2 + frame % 3) {
let x = 30.0 + frame as f32 * 7.0 + m as f32 * 15.0;
scope.draw_circle(
Brush::solid(Color(1.0, 1.0, 1.0, 1.0)),
Point::new(x + 4.0, 44.0 + m as f32 * 12.0),
4.0,
);
}
for (ring, (radius, band, speed)) in [
(150.0f32, 10.0f32, 0.013f32),
(120.0, 9.0, -0.008),
(90.0, 8.0, 0.019),
]
.into_iter()
.enumerate()
{
let radius = radius * breathing;
let band = band * breathing;
let count = 420usize;
let sweep = std::f32::consts::TAU / count as f32 * 0.8;
for i in 0..count {
let start = i as f32 * (std::f32::consts::TAU / count as f32) + speed * frame as f32;
scope.draw_annular_sector(
Brush::solid(Color(0.3, 0.5 + (i % 5) as f32 * 0.08, 0.8, 1.0)),
Point::new(CENTER, CENTER),
radius - band,
radius,
start,
sweep,
);
}
if ring == 1 {
for s in 0..(30 + (frame * 13) % 25) {
let a = s as f32 * 0.7 + frame as f32 * 0.31;
let r = 60.0 + ((s * 17 + frame * 29) % 90) as f32;
scope.draw_circle(
Brush::solid(Color(1.0, 0.6, 0.2, 0.8)),
Point::new(CENTER + a.cos() * r, CENTER + a.sin() * r),
2.5,
);
}
}
}
for d in 0..220 {
let angle = d as f32 * 0.285;
let orbit = 55.0 + (d % 7) as f32 * 3.0;
let alpha = 0.25 + 0.7 * (((d + frame * 3) % 11) as f32 / 10.0);
scope.draw_annular_sector(
Brush::solid(Color(0.9, 0.85, 0.4, alpha)),
Point::new(CENTER, CENTER),
orbit - 3.0,
orbit + 3.0,
angle - 0.02,
0.04,
);
}
scope
}
fn build_sequence(bypass: &mut dyn FnMut(u32) -> bool) -> Vec<RenderGraph> {
let mut state = CommandReplayState::default();
let command = DrawCommandId {
node_id: 7,
command_index: 0,
placement: DrawPlacement::Behind,
};
(0..FRAMES)
.map(|frame| {
let scope = record_frame(frame);
let outcome = state.advance(scope.recorded());
let center = state.center();
let (finished, replay) = scope.finish_replay(center, outcome, bypass);
let fallback = std::rc::Rc::new(finished.recording);
let replay = replay.map(|mut frame| {
frame.fallback = Some(fallback);
Box::new(frame)
});
let bounds = Rect {
x: 0.0,
y: 0.0,
width: SIZE as f32,
height: SIZE as f32,
};
RenderGraph::new(LayerNode {
node_id: None,
local_bounds: bounds,
transform_to_parent: ProjectiveTransform::identity(),
content_offset: Point::default(),
motion_context_animated: false,
translated_content_context: false,
translated_content_offset: Point::default(),
scene_children_origin: Point::default(),
scene_children_layer_translation: Point::default(),
graphics_layer: GraphicsLayer::default(),
clip_to_bounds: false,
shadow_clip: None,
hit_test: None,
has_hit_targets: false,
isolation: IsolationReasons::default(),
cache_policy: CachePolicy::None,
cache_hashes: LayerRasterCacheHashes::default(),
cache_hashes_valid: false,
children: vec![RenderNode::DrawRun(DrawRunNode::for_command_replayed(
PrimitivePhase::BeforeChildren,
Some(command),
std::rc::Rc::new(finished.primitives),
replay,
))],
})
})
.collect()
}
fn render_sequence(renderer: &mut support::LockedRenderer, graphs: &[RenderGraph]) -> Vec<Vec<u8>> {
graphs
.iter()
.enumerate()
.map(|(frame, graph)| {
renderer.scene_mut().graph = Some(graph.clone());
let captured = renderer
.capture_frame(SIZE, SIZE)
.unwrap_or_else(|err| panic!("frame {frame} capture failed: {err:?}"));
assert_eq!((captured.width, captured.height), (SIZE, SIZE));
captured.pixels
})
.collect()
}
#[test]
fn command_feed_matches_the_full_pipeline_pixel_for_pixel() {
let mut renderer = match support::headless_renderer() {
Ok(renderer) => renderer,
Err(err) => {
eprintln!("skipping command feed parity: headless WGPU init failed: {err}");
return;
}
};
let graphs = build_sequence(&mut |_| false);
let retained_frames: usize = graphs
.iter()
.filter(|graph| match &graph.root.children[0] {
RenderNode::DrawRun(run) => run.replay.is_some(),
_ => false,
})
.count();
assert!(
retained_frames >= FRAMES - 2,
"the recorder should retain from the partition frame on, got {retained_frames}"
);
std::env::set_var("CRANPOSE_ARC_MESH", "0");
std::env::set_var("CRANPOSE_SIMILARITY_REPLAY", "0");
let baseline = render_sequence(&mut renderer, &graphs);
std::env::set_var("CRANPOSE_SIMILARITY_REPLAY", "1");
std::env::set_var("CRANPOSE_COMMAND_FEED", "1");
let fed = render_sequence(&mut renderer, &graphs);
cranpose_render_common::scene_builder::set_retained_feed_epoch(Some(
cranpose_render_wgpu::retained_feed_generation(),
));
let command = DrawCommandId {
node_id: 7,
command_index: 0,
placement: DrawPlacement::Behind,
};
let bypassed_graphs = build_sequence(&mut |slot| {
cranpose_render_common::scene_builder::retained_slot_confirmed(command, slot)
});
let bypassed_primitives: usize = bypassed_graphs
.iter()
.map(|graph| match &graph.root.children[0] {
RenderNode::DrawRun(run) => run.primitives.len(),
_ => 0,
})
.sum();
let full_primitives: usize = graphs
.iter()
.map(|graph| match &graph.root.children[0] {
RenderNode::DrawRun(run) => run.primitives.len(),
_ => 0,
})
.sum();
eprintln!("primitives materialized: full {full_primitives} bypassed {bypassed_primitives}");
assert!(
bypassed_primitives < full_primitives / 2,
"confirmed slots should have bypassed materialization \
({bypassed_primitives} of {full_primitives} still materialized)"
);
let fed2 = render_sequence(&mut renderer, &graphs);
for (frame, (a, b)) in fed.iter().zip(&fed2).enumerate() {
let differing = a.iter().zip(b).filter(|(a, b)| a.abs_diff(**b) > 0).count();
eprintln!("fed-again frame {frame}: vs fed differing {differing}");
}
let bypassed = render_sequence(&mut renderer, &bypassed_graphs);
std::env::remove_var("CRANPOSE_COMMAND_FEED");
std::env::remove_var("CRANPOSE_SIMILARITY_REPLAY");
std::env::remove_var("CRANPOSE_ARC_MESH");
let (feed_slots, patches, remat_misses) = cranpose_render_wgpu::command_feed_live_stats();
assert_eq!(
remat_misses, 0,
"no bypassed span may have hit the fail-closed remat-miss terminal"
);
assert!(
feed_slots >= 4,
"the rings and twinkles should hold identity-fed slots, got {feed_slots}"
);
assert!(
patches > 0,
"twinkle recolors should have gone through the patch path"
);
for (frame, (baseline, fed)) in baseline.iter().zip(&fed).enumerate() {
assert_eq!(baseline.len(), fed.len());
let mut worst = 0u8;
let mut differing = 0usize;
let (mut min_x, mut min_y, mut max_x, mut max_y) = (u32::MAX, u32::MAX, 0u32, 0u32);
for (i, (a, b)) in baseline.iter().zip(fed).enumerate() {
let diff = a.abs_diff(*b);
worst = worst.max(diff);
if diff > 3 {
differing += 1;
let pixel = (i / 4) as u32;
let (x, y) = (pixel % SIZE, pixel / SIZE);
min_x = min_x.min(x);
min_y = min_y.min(y);
max_x = max_x.max(x);
max_y = max_y.max(y);
}
}
eprintln!(
"frame {frame}: differing {differing} worst {worst} \
region x {min_x}..{max_x} y {min_y}..{max_y}"
);
if frame < 2 {
assert_eq!(
differing, 0,
"frame {frame}: dynamic and capture frames are byte-exact"
);
} else {
assert!(
differing < 2500 && worst < 160,
"frame {frame}: {differing} channels diverged (worst {worst}) — beyond the\n flat detector's edge-AA envelope"
);
}
}
let mut deviated = false;
for (frame, (control, bypassed)) in fed2.iter().zip(&bypassed).enumerate() {
let differing = control
.iter()
.zip(bypassed)
.filter(|(a, b)| a.abs_diff(**b) > 0)
.count();
eprintln!("bypassed frame {frame}: vs control differing {differing}");
deviated |= differing != 0;
}
assert!(
!deviated,
"bypassed render deviates from the control render"
);
}