tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 660Markdown 302TOML 157RON 312382 Sutichaykuna

⎇ main llimphi-3d/examples/voxel_demo.rs Rust · 144 siq'ikuna · 5.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel ccab39f14 · Sergio ·

1//! Demo headless de M1: una grilla de voxels densa renderizada por
2//! **ray-marching DDA** (sin meshear), compuesta sobre un fondo vello — el
3//! mismo orden que el runtime aplica a `View::gpu_paint_with`.
4//!
5//! `cargo run -p llimphi-3d --example voxel_demo --release -- [out.png] [yaw_deg] [dim]`
6
7use std::fs::File;
8use std::io::BufWriter;
9
10use llimphi_3d::glam::Vec3;
11use llimphi_3d::{Camera3d, VoxelGrid, VoxelRenderer};
12use llimphi_hal::{wgpu, Hal};
13use llimphi_raster::peniko::Color;
14use llimphi_raster::{vello, Renderer};
15
16const W: u32 = 720;
17const H: u32 = 480;
18const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
19
20fn main() {
21    let out = std::env::args()
22        .nth(1)
23        .unwrap_or_else(|| "voxel_demo.png".to_string());
24    let yaw_deg: f32 = std::env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(35.0);
25    let dim: u32 = std::env::args().nth(3).and_then(|s| s.parse().ok()).unwrap_or(64);
26
27    let hal = pollster::block_on(Hal::new(None)).expect("hal");
28    let mut renderer = Renderer::new(&hal).expect("renderer");
29
30    let grid = VoxelGrid::demo_scene([dim, dim, dim]);
31    let mut vr = VoxelRenderer::new(&hal.device, &hal.queue, FMT, &grid);
32    let (used, total) = vr.brick_usage();
33    let (pool, dense) = vr.memory_bytes();
34    eprintln!(
35        "brick pool: {used}/{total} bricks ocupados ({:.1}%) — pool {} KiB vs denso {} KiB ({:.1}× menos)",
36        used as f32 / total as f32 * 100.0,
37        pool / 1024,
38        dense / 1024,
39        dense as f32 / pool.max(1) as f32,
40    );
41
42    let inter = hal.device.create_texture(&wgpu::TextureDescriptor {
43        label: Some("inter"),
44        size: wgpu::Extent3d {
45            width: W,
46            height: H,
47            depth_or_array_layers: 1,
48        },
49        mip_level_count: 1,
50        sample_count: 1,
51        dimension: wgpu::TextureDimension::D2,
52        format: FMT,
53        usage: wgpu::TextureUsages::STORAGE_BINDING
54            | wgpu::TextureUsages::TEXTURE_BINDING
55            | wgpu::TextureUsages::RENDER_ATTACHMENT
56            | wgpu::TextureUsages::COPY_SRC,
57        view_formats: &[],
58    });
59    let inter_view = inter.create_view(&wgpu::TextureViewDescriptor::default());
60
61    // (1) Fondo vello.
62    let base = vello::Scene::new();
63    renderer
64        .render_to_view(&hal, &base, &inter_view, W, H, Color::from_rgba8(18, 22, 32, 255))
65        .expect("render base");
66
67    // (2) Pase voxel ray-march. Cámara orbitando el centro de la grilla (origen).
68    let d = dim as f32;
69    let camera = Camera3d::orbit(
70        Vec3::ZERO,
71        yaw_deg.to_radians(),
72        30_f32.to_radians(),
73        d * 1.7,
74    );
75    let mut enc = hal
76        .device
77        .create_command_encoder(&wgpu::CommandEncoderDescriptor {
78            label: Some("voxel-pass"),
79        });
80    vr.render(&hal.device, &hal.queue, &mut enc, &inter_view, (W, H), &camera);
81    hal.queue.submit(std::iter::once(enc.finish()));
82    let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
83
84    write_png(&hal, &inter, &out);
85    eprintln!("voxel_demo: escrito {out} ({W}x{H}, yaw={yaw_deg}°, dim={dim}³)");
86}
87
88fn write_png(hal: &Hal, target: &wgpu::Texture, path: &str) {
89    let unpadded = (W * 4) as usize;
90    let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
91    let padded = unpadded.div_ceil(align) * align;
92    let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
93        label: Some("readback"),
94        size: (padded * H as usize) as u64,
95        usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
96        mapped_at_creation: false,
97    });
98    let mut enc = hal
99        .device
100        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
101    enc.copy_texture_to_buffer(
102        wgpu::TexelCopyTextureInfo {
103            texture: target,
104            mip_level: 0,
105            origin: wgpu::Origin3d::ZERO,
106            aspect: wgpu::TextureAspect::All,
107        },
108        wgpu::TexelCopyBufferInfo {
109            buffer: &buf,
110            layout: wgpu::TexelCopyBufferLayout {
111                offset: 0,
112                bytes_per_row: Some(padded as u32),
113                rows_per_image: Some(H),
114            },
115        },
116        wgpu::Extent3d {
117            width: W,
118            height: H,
119            depth_or_array_layers: 1,
120        },
121    );
122    hal.queue.submit(std::iter::once(enc.finish()));
123    let slice = buf.slice(..);
124    let (tx, rx) = std::sync::mpsc::channel();
125    slice.map_async(wgpu::MapMode::Read, move |r| {
126        let _ = tx.send(r);
127    });
128    let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
129    rx.recv().unwrap().unwrap();
130    let data = slice.get_mapped_range();
131    let mut pixels = Vec::with_capacity((W * H * 4) as usize);
132    for row in 0..H as usize {
133        let s = row * padded;
134        pixels.extend_from_slice(&data[s..s + unpadded]);
135    }
136    drop(data);
137    buf.unmap();
138    let file = File::create(path).expect("png");
139    let mut enc = png::Encoder::new(BufWriter::new(file), W, H);
140    enc.set_color(png::ColorType::Rgba);
141    enc.set_depth(png::BitDepth::Eight);
142    let mut w = enc.write_header().unwrap();
143    w.write_image_data(&pixels).unwrap();
144}