Files
llimphi/widgets/terminal/tests/cell_pipeline_smoke.rs
T
Sergio ccab39f140 refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel
Re-sincroniza las fuentes desde el monorepo (estaba en vello 0.5/wgpu 24 y con la
estructura vieja de eventloop) y suma el 3D:

- bump del workspace a vello 0.7 / wgpu 27 / parley 0.6, + accesskit 0.24 /
  accesskit_winit 0.33 / vello_hybrid 0.0.9.
- nuevos crates: llimphi-3d (voxels ray-march + mallas en un depth compartido,
  montable dentro de un View 2D vía set_viewport+scissor) y llimphi-voxel
  (world-gen, personajes, director de escenas) + shared/foreign-vox (puente .vox).
- README: sección "Not just 2D — a 3D voxel engine" + GIF (docs/llimphi_voxel.gif).
- excluido modules/allichay (arrastra deps fuera del alcance del front-door).
- cargo check --workspace: verde.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:40:00 +00:00

179 lines
5.6 KiB
Rust

//! Smoke test del `CellPipeline` (Fase 4.2 del SDD-TERMINAL).
//!
//! No verifica píxeles — eso requiere conocer la fuente exacta, font hinting
//! del rasterizer y un pipeline de comparación. Sí verifica:
//!
//! - `CellPipeline::new` compila el shader WGSL sin errores de naga.
//! - `create_atlas_texture` sube bytes a una `R8Unorm` sin pánico.
//! - `draw` ejecuta sin errores wgpu con un atlas vivo y N instancias —
//! por debajo y por arriba del cap del adapter, sin reasignar buffers.
//!
//! Corre en cualquier adapter wgpu disponible (en CI sin GPU = llvmpipe).
use llimphi_hal::{wgpu, Hal};
use llimphi_widget_terminal::cell_pipeline::{
pack_rgba, CellInstance, CellPipeline, CellUniforms,
};
use llimphi_widget_terminal::glyph_atlas::GlyphAtlas;
const W: u32 = 256;
const H: u32 = 256;
const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
fn make_target(device: &wgpu::Device) -> (wgpu::Texture, wgpu::TextureView) {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("cell-smoke-target"),
size: wgpu::Extent3d {
width: W,
height: H,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: FMT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
}
#[test]
fn pipeline_compila_y_dibuja_sin_panico() {
let hal = pollster::block_on(Hal::new(None)).expect("hal");
let pipeline = CellPipeline::new(&hal.device, FMT);
let (_tex, view) = make_target(&hal.device);
// Atlas con un par de glifos.
let mut atlas = GlyphAtlas::new(
llimphi_ui::llimphi_text::MONO_FONT_BYTES,
14.0,
16,
4,
)
.expect("atlas");
let slot_a = atlas.glyph_for('A').unwrap();
let slot_b = atlas.glyph_for('B').unwrap();
let (atlas_w, atlas_h) = atlas.size();
let (cell_w, cell_h) = atlas.cell_size();
let (_atlas_tex, atlas_view) =
CellPipeline::create_atlas_texture(&hal.device, &hal.queue, atlas.pixels(), atlas.size());
// Dos celdas, A y B en (0,0) y (cell_w,0).
let cells = vec![
CellInstance {
cell_x: 0.0,
cell_y: 0.0,
uv_x: slot_a.px as f32,
uv_y: slot_a.py as f32,
uv_w: cell_w as f32,
uv_h: cell_h as f32,
fg_rgba: pack_rgba(255, 255, 255, 255),
bg_rgba: pack_rgba(20, 20, 20, 255),
},
CellInstance {
cell_x: cell_w as f32,
cell_y: 0.0,
uv_x: slot_b.px as f32,
uv_y: slot_b.py as f32,
uv_w: cell_w as f32,
uv_h: cell_h as f32,
fg_rgba: pack_rgba(100, 255, 100, 255),
bg_rgba: pack_rgba(0, 0, 0, 255),
},
];
let uniforms = CellUniforms {
viewport_w: W as f32,
viewport_h: H as f32,
cell_w: cell_w as f32,
cell_h: cell_h as f32,
atlas_w: atlas_w as f32,
atlas_h: atlas_h as f32,
_pad0: 0.0,
_pad1: 0.0,
};
let mut encoder = hal.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("cell-smoke-encoder"),
});
// Clear primero para tener un load:Load coherente.
{
let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("cell-smoke-clear"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
}
pipeline.draw(
&hal.device,
&hal.queue,
&mut encoder,
&view,
&atlas_view,
&cells,
uniforms,
);
hal.queue.submit(std::iter::once(encoder.finish()));
hal.device.poll(wgpu::PollType::wait_indefinitely());
}
#[test]
fn draw_con_cero_instancias_es_no_op() {
let hal = pollster::block_on(Hal::new(None)).expect("hal");
let pipeline = CellPipeline::new(&hal.device, FMT);
let (_tex, view) = make_target(&hal.device);
let mut atlas = GlyphAtlas::new(
llimphi_ui::llimphi_text::MONO_FONT_BYTES,
14.0,
16,
4,
)
.unwrap();
let _ = atlas.glyph_for('A'); // tener algo en el atlas
let (_atlas_tex, atlas_view) =
CellPipeline::create_atlas_texture(&hal.device, &hal.queue, atlas.pixels(), atlas.size());
let (atlas_w, atlas_h) = atlas.size();
let (cell_w, cell_h) = atlas.cell_size();
let mut encoder = hal.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("cell-smoke-empty-encoder"),
});
pipeline.draw(
&hal.device,
&hal.queue,
&mut encoder,
&view,
&atlas_view,
&[],
CellUniforms {
viewport_w: W as f32,
viewport_h: H as f32,
cell_w: cell_w as f32,
cell_h: cell_h as f32,
atlas_w: atlas_w as f32,
atlas_h: atlas_h as f32,
_pad0: 0.0,
_pad1: 0.0,
},
);
hal.queue.submit(std::iter::once(encoder.finish()));
hal.device.poll(wgpu::PollType::wait_indefinitely());
}