cranpose-render-wgpu 0.0.60

WGPU renderer backend for Cranpose
Documentation
mod support;

use cranpose_core::NodeId;
use cranpose_render_common::graph::{
    CachePolicy, DrawPrimitiveNode, IsolationReasons, LayerNode, PrimitiveEntry, PrimitiveNode,
    PrimitivePhase, ProjectiveTransform, RenderGraph, RenderNode,
};
use cranpose_render_common::raster_cache::LayerRasterCacheHashes;
use cranpose_render_common::Renderer;
use cranpose_ui_graphics::{Brush, Color, GraphicsLayer, Rect};

fn test_layer(
    node_id: Option<NodeId>,
    cache_policy: CachePolicy,
    local_bounds: Rect,
    transform_to_parent: ProjectiveTransform,
    children: Vec<RenderNode>,
) -> LayerNode {
    LayerNode {
        node_id,
        local_bounds,
        transform_to_parent,
        motion_context_animated: false,
        translated_content_context: false,
        graphics_layer: GraphicsLayer::default(),
        clip_to_bounds: false,
        shadow_clip: None,
        hit_test: None,
        has_hit_targets: false,
        isolation: IsolationReasons::default(),
        cache_policy,
        cache_hashes: LayerRasterCacheHashes::default(),
        cache_hashes_valid: false,
        children,
    }
}

fn card_layer(node_id: NodeId, y: f32) -> LayerNode {
    let local_bounds = Rect {
        x: 0.0,
        y: 0.0,
        width: 96.0,
        height: 28.0,
    };
    let primitive = PrimitiveEntry {
        phase: PrimitivePhase::BeforeChildren,
        node: PrimitiveNode::Draw(DrawPrimitiveNode {
            primitive: cranpose_ui_graphics::DrawPrimitive::Rect {
                rect: local_bounds,
                brush: Brush::solid(Color(0.15, 0.35, 0.85, 1.0)),
            },
            clip: None,
        }),
    };
    let mut layer = test_layer(
        Some(node_id),
        CachePolicy::Auto,
        local_bounds,
        ProjectiveTransform::translation(12.0, y),
        vec![RenderNode::Primitive(primitive)],
    );
    layer.graphics_layer.alpha = 0.85;
    layer
}

fn scroll_like_graph(offsets: &[f32]) -> RenderGraph {
    let children = offsets
        .iter()
        .enumerate()
        .map(|(index, y)| RenderNode::Layer(Box::new(card_layer(index + 1, *y))))
        .collect();
    RenderGraph::new(test_layer(
        Some(10_000),
        CachePolicy::None,
        Rect {
            x: 0.0,
            y: 0.0,
            width: 128.0,
            height: 160.0,
        },
        ProjectiveTransform::identity(),
        children,
    ))
}

#[test]
fn capture_frame_reuses_cached_child_layers_during_rigid_scroll() {
    let mut renderer = match support::headless_renderer() {
        Ok(renderer) => renderer,
        Err(err) => {
            eprintln!(
                "skipping rigid scroll cache reuse assertion because headless WGPU init failed: {}",
                err
            );
            return;
        }
    };

    renderer.scene_mut().graph = Some(scroll_like_graph(&[8.0, 44.0, 80.0, 116.0]));
    renderer
        .capture_frame(160, 180)
        .expect("first capture should succeed");
    let first_stats = renderer.last_frame_stats().expect("first frame stats");

    renderer.scene_mut().graph = Some(scroll_like_graph(&[15.25, 51.25, 87.25, 123.25]));
    renderer
        .capture_frame(160, 180)
        .expect("second capture should succeed");
    let second_stats = renderer.last_frame_stats().expect("second frame stats");

    assert_eq!(first_stats.layer_cache_hits, 0);
    assert_eq!(first_stats.layer_cache_misses, 4);
    assert_eq!(second_stats.layer_cache_hits, 4);
    assert_eq!(second_stats.layer_cache_misses, 0);
    assert_eq!(second_stats.layer_cache_evictions, 0);
    assert!(
        second_stats.isolated_layer_renders < first_stats.isolated_layer_renders,
        "cached child layers should avoid repainting isolated child surfaces"
    );
    assert!(
        second_stats.isolated_layer_pixels < first_stats.isolated_layer_pixels,
        "rigid scroll should reduce isolated layer repaint area after cache warmup"
    );
    assert!(
        second_stats.offscreen_acquires < first_stats.offscreen_acquires,
        "cache reuse should reduce offscreen acquisitions on the second frame"
    );
}