feat: llimphi standalone — framework UI soberano extraído del monorepo
Motor gráfico Llimphi como workspace independiente: bucle Elm (input→update→view→layout→raster→present) sobre wgpu+vello+taffy+parley. Núcleo (hal/raster/layout/text/ui/theme/surface/motion/icons) + ~40 widgets + módulos, sin dependencias al resto del monorepo. cargo check --workspace pasa (64 crates). Puerta de entrada: cargo run -p llimphi-ui --example counter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
//! Smoke test del backend GPU directo (`llimphi_raster::gpu`).
|
||||
//!
|
||||
//! No verifica píxeles — eso requiere AA y un patrón conocido, y por
|
||||
//! ahora el módulo no garantiza pixel-exactness. Sí verifica que:
|
||||
//!
|
||||
//! - `GpuPipelines::new` compila los 3 shaders WGSL sin errores de naga.
|
||||
//! - `GpuBatch` acepta líneas, triángulos y rects mezclados sin pánico.
|
||||
//! - `flush` ejecuta sin errores wgpu y la `Maintain::Wait` retorna
|
||||
//! (= la GPU/llvmpipe terminó las pasadas).
|
||||
//!
|
||||
//! Corre en cualquier adapter wgpu disponible — en CI sin GPU usa
|
||||
//! llvmpipe, donde igual valida el ensamblado y la sintaxis WGSL.
|
||||
|
||||
use llimphi_hal::{wgpu, Hal};
|
||||
use llimphi_raster::gpu::{GpuBatch, GpuPipelines};
|
||||
use llimphi_raster::peniko::Color;
|
||||
|
||||
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("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 batch_with_rects_lines_tris_does_not_panic() {
|
||||
let hal = pollster::block_on(Hal::new(None)).expect("hal");
|
||||
let pipelines = GpuPipelines::new(&hal.device, FMT);
|
||||
let (_tex, view) = make_target(&hal.device);
|
||||
|
||||
let mut batch = GpuBatch::new(&pipelines);
|
||||
batch.line_width(2.0);
|
||||
|
||||
// Cuadrícula 8×8 de rects con color que varía.
|
||||
for j in 0..8 {
|
||||
for i in 0..8 {
|
||||
let x = 8.0 + i as f32 * 30.0;
|
||||
let y = 8.0 + j as f32 * 30.0;
|
||||
let c = Color::from_rgba8(
|
||||
(i * 32) as u8,
|
||||
(j * 32) as u8,
|
||||
100,
|
||||
255,
|
||||
);
|
||||
batch.add_rect(x, y, 24.0, 24.0, c);
|
||||
}
|
||||
}
|
||||
|
||||
// Diagonal de líneas.
|
||||
for k in 0..16 {
|
||||
batch.add_line(
|
||||
(0.0, k as f32 * 16.0),
|
||||
(W as f32, (k + 1) as f32 * 16.0),
|
||||
Color::from_rgba8(220, 220, 250, 180),
|
||||
);
|
||||
}
|
||||
|
||||
// Triángulo grande con color por vértice.
|
||||
batch.add_tri(
|
||||
(128.0, 32.0),
|
||||
(64.0, 220.0),
|
||||
(220.0, 220.0),
|
||||
Color::from_rgba8(255, 80, 80, 200),
|
||||
Color::from_rgba8(80, 255, 80, 200),
|
||||
Color::from_rgba8(80, 80, 255, 200),
|
||||
);
|
||||
|
||||
assert!(batch.primitive_count() > 0, "batch debería tener primitivas");
|
||||
|
||||
let mut encoder = hal
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("smoke-enc"),
|
||||
});
|
||||
batch.flush(
|
||||
&hal.device,
|
||||
&hal.queue,
|
||||
&mut encoder,
|
||||
&view,
|
||||
(W as f32, H as f32),
|
||||
wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||
);
|
||||
hal.queue.submit(std::iter::once(encoder.finish()));
|
||||
hal.device.poll(wgpu::Maintain::Wait);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_batch_flush_is_no_op() {
|
||||
let hal = pollster::block_on(Hal::new(None)).expect("hal");
|
||||
let pipelines = GpuPipelines::new(&hal.device, FMT);
|
||||
let (_tex, view) = make_target(&hal.device);
|
||||
|
||||
let batch = GpuBatch::new(&pipelines);
|
||||
assert_eq!(batch.primitive_count(), 0);
|
||||
|
||||
let mut encoder = hal
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("smoke-empty-enc"),
|
||||
});
|
||||
// Con batch vacío, flush no debe crear render pass ni buffers.
|
||||
batch.flush(
|
||||
&hal.device,
|
||||
&hal.queue,
|
||||
&mut encoder,
|
||||
&view,
|
||||
(W as f32, H as f32),
|
||||
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
);
|
||||
hal.queue.submit(std::iter::once(encoder.finish()));
|
||||
hal.device.poll(wgpu::Maintain::Wait);
|
||||
}
|
||||
Reference in New Issue
Block a user