zenfg 0.1.0

A FrameGraph compiler and transient-resource executor for wgpu
Documentation
use std::{env, hint::black_box, time::Instant};

use zenfg::{
    BufferDesc, BufferRange, CompileOptions, FrameGraph, RootReason, TextureDesc, TextureViewDesc,
    WriteContents,
};

fn main() {
    let scenario = env::args().nth(1).unwrap_or_else(|| "linear-chain".into());
    let node_count = env::args()
        .nth(2)
        .and_then(|value| value.parse::<usize>().ok())
        .unwrap_or(64)
        .max(2);
    let iterations = env::args()
        .nth(3)
        .and_then(|value| value.parse::<usize>().ok())
        .unwrap_or(1_000);

    let start = Instant::now();
    for _ in 0..iterations {
        black_box(run(&scenario, node_count));
    }
    let elapsed = start.elapsed();
    println!(
        "scenario={scenario} nodes={node_count} iterations={iterations} total_ms={:.3} mean_us={:.3}",
        elapsed.as_secs_f64() * 1_000.0,
        elapsed.as_secs_f64() * 1_000_000.0 / iterations as f64
    );
}

fn run(scenario: &str, node_count: usize) -> usize {
    match scenario {
        "linear-chain" => linear_chain(node_count),
        "buffer-ranges" => buffer_ranges(node_count),
        "texture-subresources" => texture_subresources(node_count),
        "allocation-aliasing" => allocation_aliasing(node_count),
        _ => panic!(
            "unknown scenario {scenario}; expected linear-chain, buffer-ranges, texture-subresources, or allocation-aliasing"
        ),
    }
}

fn linear_chain(node_count: usize) -> usize {
    let mut graph = FrameGraph::new();
    let mut frame = graph.begin_frame();
    let buffers: Vec<_> = (0..node_count)
        .map(|index| {
            frame
                .create_buffer(BufferDesc::new(format!("b{index}"), 64))
                .unwrap()
        })
        .collect();
    for index in 0..node_count {
        let mut pass = frame.compute_pass(format!("p{index}"));
        if index > 0 {
            let _ = pass
                .storage_buffer_read(buffers[index - 1], BufferRange::whole())
                .unwrap();
        }
        let _ = pass
            .storage_buffer_write(
                buffers[index],
                BufferRange::whole(),
                WriteContents::Overwrite,
            )
            .unwrap();
        pass.finish().unwrap();
    }
    frame
        .mark_buffer_root(
            *buffers.last().unwrap(),
            BufferRange::whole(),
            RootReason::Output,
        )
        .unwrap();
    frame
        .compile(CompileOptions::default())
        .unwrap()
        .retained_node_count()
}

fn buffer_ranges(node_count: usize) -> usize {
    let mut graph = FrameGraph::new();
    let mut frame = graph.begin_frame();
    let buffer = frame
        .create_buffer(BufferDesc::new("ranges", (node_count * 4) as u64))
        .unwrap();
    for index in 0..node_count {
        let mut pass = frame.compute_pass(format!("write-{index}"));
        let _ = pass
            .storage_buffer_write(
                buffer,
                BufferRange::new((index * 4) as u64, 4),
                WriteContents::Overwrite,
            )
            .unwrap();
        pass.finish().unwrap();
    }
    let mut read = frame.command_pass("read-all");
    let _ = read
        .storage_buffer_read(buffer, BufferRange::whole())
        .unwrap();
    read.finish().unwrap();
    frame
        .compile(CompileOptions::default())
        .unwrap()
        .retained_node_count()
}

fn texture_subresources(node_count: usize) -> usize {
    let layers = node_count.min(1_024) as u32;
    let mut graph = FrameGraph::new();
    let mut frame = graph.begin_frame();
    let mut desc = TextureDesc::new_2d("layers", 16, 16, wgpu::TextureFormat::Rgba8Unorm);
    desc.size.depth_or_array_layers = layers;
    let texture = frame.create_texture(desc).unwrap();
    for layer in 0..layers {
        let view = frame
            .create_texture_view(
                texture,
                TextureViewDesc {
                    label: format!("layer-{layer}"),
                    base_array_layer: layer,
                    array_layer_count: Some(1),
                    ..Default::default()
                },
            )
            .unwrap();
        let mut pass = frame.compute_pass(format!("write-{layer}"));
        let _ = pass
            .storage_texture_write(view, WriteContents::Overwrite)
            .unwrap();
        pass.finish().unwrap();
    }
    let mut read = frame.command_pass("read-all");
    let _ = read.sampled_texture(texture).unwrap();
    read.finish().unwrap();
    frame
        .compile(CompileOptions::default())
        .unwrap()
        .retained_node_count()
}

fn allocation_aliasing(node_count: usize) -> usize {
    let mut graph = FrameGraph::new();
    let mut frame = graph.begin_frame();
    for index in 0..node_count {
        let buffer = frame
            .create_buffer(BufferDesc::new(format!("scratch-{index}"), 1_024))
            .unwrap();
        let mut pass = frame.compute_pass(format!("write-{index}"));
        let _ = pass
            .storage_buffer_write(buffer, BufferRange::whole(), WriteContents::Overwrite)
            .unwrap();
        pass.finish().unwrap();
        frame
            .mark_buffer_root(buffer, BufferRange::whole(), RootReason::Output)
            .unwrap();
    }
    frame
        .compile(CompileOptions::full_report())
        .unwrap()
        .allocations()
        .len()
}