tawasuyu

tawasuyu/llimphi

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

Rust 229TOML 101Markdown 82Shell 24052 Definiciones

⎇ main llimphi-3d/examples/voxel_dynamic_demo.rs Rust · 173 líneas · 6.5 KB Historia Quién tocó qué crudo

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

1//! Demo headless de M3: **mutación incremental** de la grilla en GPU.
2//!
3//! Renderiza la escena, luego (a) agrega un bloque flotante en aire antes vacío
4//! y (b) carva un mordisco en la esfera — cada edición sube SÓLO su sub-caja vía
5//! `VoxelRenderer::sync` (no re-sube el grid ni remesha). Vuelca un PNG "antes"
6//! y uno "después", e imprime los bytes subidos vs el grid completo.
7//!
8//! El bloque flotante es el test clave del coarse map: si `sync` no actualizara
9//! la ocupación gruesa, el brick seguiría marcado vacío y el bloque sería
10//! invisible (lo saltaría el DDA grueso).
11//!
12//! `cargo run -p llimphi-3d --example voxel_dynamic_demo --release -- [dim]`
13
14use std::fs::File;
15use std::io::BufWriter;
16
17use llimphi_3d::glam::Vec3;
18use llimphi_3d::{Camera3d, VoxelGrid, VoxelRenderer};
19use llimphi_hal::{wgpu, Hal};
20use llimphi_raster::peniko::Color;
21use llimphi_raster::{vello, Renderer};
22
23const W: u32 = 720;
24const H: u32 = 480;
25const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
26
27fn main() {
28    let dim: u32 = std::env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(64);
29
30    let hal = pollster::block_on(Hal::new(None)).expect("hal");
31    let mut renderer = Renderer::new(&hal).expect("renderer");
32
33    let mut grid = VoxelGrid::demo_scene([dim, dim, dim]);
34    let mut vr = VoxelRenderer::new(&hal.device, &hal.queue, FMT, &grid);
35
36    let camera = Camera3d::orbit(Vec3::ZERO, 35_f32.to_radians(), 30_f32.to_radians(), dim as f32 * 1.7);
37
38    // ── Frame ANTES ──────────────────────────────────────────────────────
39    render_frame(&hal, &mut renderer, &mut vr, &camera, "/tmp/m3_antes.png");
40
41    let full = dim * dim * dim * 4;
42
43    // ── Edición (a): bloque flotante en aire vacío (arriba, a un costado) ──
44    let bx = dim / 6;
45    let by = dim * 4 / 5;
46    let bz = dim / 6;
47    for z in 0..8 {
48        for y in 0..8 {
49            for x in 0..8 {
50                grid.set(bx + x, by + y, bz + z, [240, 150, 40]);
51            }
52        }
53    }
54    let n_a = vr.sync(&hal.queue, &mut grid);
55    eprintln!("edición (a) bloque flotante: subidos {n_a} B  ({:.3}% del grid completo)", n_a as f32 / full as f32 * 100.0);
56
57    // ── Edición (b): mordisco cúbico en lo alto de la esfera ──────────────
58    let cx = dim / 2;
59    let cy = dim * 7 / 10;
60    let cz = dim / 2;
61    for z in 0..(dim / 4) {
62        for y in 0..(dim / 4) {
63            for x in 0..(dim / 4) {
64                grid.clear(cx + x, cy + y, cz - dim / 8 + z);
65            }
66        }
67    }
68    let n_b = vr.sync(&hal.queue, &mut grid);
69    eprintln!("edición (b) mordisco esfera: subidos {n_b} B  ({:.3}% del grid completo)", n_b as f32 / full as f32 * 100.0);
70
71    // ── Frame DESPUÉS ────────────────────────────────────────────────────
72    render_frame(&hal, &mut renderer, &mut vr, &camera, "/tmp/m3_despues.png");
73    eprintln!("voxel_dynamic_demo: /tmp/m3_antes.png + /tmp/m3_despues.png (dim={dim}³)");
74}
75
76fn render_frame(
77    hal: &Hal,
78    renderer: &mut Renderer,
79    vr: &mut VoxelRenderer,
80    camera: &Camera3d,
81    out: &str,
82) {
83    let inter = hal.device.create_texture(&wgpu::TextureDescriptor {
84        label: Some("inter"),
85        size: wgpu::Extent3d {
86            width: W,
87            height: H,
88            depth_or_array_layers: 1,
89        },
90        mip_level_count: 1,
91        sample_count: 1,
92        dimension: wgpu::TextureDimension::D2,
93        format: FMT,
94        usage: wgpu::TextureUsages::STORAGE_BINDING
95            | wgpu::TextureUsages::TEXTURE_BINDING
96            | wgpu::TextureUsages::RENDER_ATTACHMENT
97            | wgpu::TextureUsages::COPY_SRC,
98        view_formats: &[],
99    });
100    let inter_view = inter.create_view(&wgpu::TextureViewDescriptor::default());
101
102    let base = vello::Scene::new();
103    renderer
104        .render_to_view(hal, &base, &inter_view, W, H, Color::from_rgba8(18, 22, 32, 255))
105        .expect("render base");
106
107    let mut enc = hal
108        .device
109        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("voxel-pass") });
110    vr.render(&hal.device, &hal.queue, &mut enc, &inter_view, (W, H), camera);
111    hal.queue.submit(std::iter::once(enc.finish()));
112    let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
113
114    write_png(hal, &inter, out);
115}
116
117fn write_png(hal: &Hal, target: &wgpu::Texture, path: &str) {
118    let unpadded = (W * 4) as usize;
119    let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
120    let padded = unpadded.div_ceil(align) * align;
121    let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
122        label: Some("readback"),
123        size: (padded * H as usize) as u64,
124        usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
125        mapped_at_creation: false,
126    });
127    let mut enc = hal
128        .device
129        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
130    enc.copy_texture_to_buffer(
131        wgpu::TexelCopyTextureInfo {
132            texture: target,
133            mip_level: 0,
134            origin: wgpu::Origin3d::ZERO,
135            aspect: wgpu::TextureAspect::All,
136        },
137        wgpu::TexelCopyBufferInfo {
138            buffer: &buf,
139            layout: wgpu::TexelCopyBufferLayout {
140                offset: 0,
141                bytes_per_row: Some(padded as u32),
142                rows_per_image: Some(H),
143            },
144        },
145        wgpu::Extent3d {
146            width: W,
147            height: H,
148            depth_or_array_layers: 1,
149        },
150    );
151    hal.queue.submit(std::iter::once(enc.finish()));
152    let slice = buf.slice(..);
153    let (tx, rx) = std::sync::mpsc::channel();
154    slice.map_async(wgpu::MapMode::Read, move |r| {
155        let _ = tx.send(r);
156    });
157    let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
158    rx.recv().unwrap().unwrap();
159    let data = slice.get_mapped_range();
160    let mut pixels = Vec::with_capacity((W * H * 4) as usize);
161    for row in 0..H as usize {
162        let s = row * padded;
163        pixels.extend_from_slice(&data[s..s + unpadded]);
164    }
165    drop(data);
166    buf.unmap();
167    let file = File::create(path).expect("png");
168    let mut enc = png::Encoder::new(BufWriter::new(file), W, H);
169    enc.set_color(png::ColorType::Rgba);
170    enc.set_depth(png::BitDepth::Eight);
171    let mut w = enc.write_header().unwrap();
172    w.write_image_data(&pixels).unwrap();
173}