tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
⎇ 333dca1d5fb473825c9128d402d4cc4e2a5176fc
llimphi-raster/tests/gpu_batch_smoke.rs
Historia
Quién tocó qué
crudo
refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel
1//! Smoke test del backend GPU directo (`llimphi_raster::gpu`).
2//!
3//! No verifica píxeles — eso requiere AA y un patrón conocido, y por
4//! ahora el módulo no garantiza pixel-exactness. Sí verifica que:
5//!
6//! - `GpuPipelines::new` compila los 3 shaders WGSL sin errores de naga.
7//! - `GpuBatch` acepta líneas, triángulos y rects mezclados sin pánico.
8//! - `flush` ejecuta sin errores wgpu y la `Maintain::Wait` retorna
9//! (= la GPU/llvmpipe terminó las pasadas).
10//!
11//! Corre en cualquier adapter wgpu disponible — en CI sin GPU usa
12//! llvmpipe, donde igual valida el ensamblado y la sintaxis WGSL.
13
14use llimphi_hal::{wgpu, Hal};
15use llimphi_raster::gpu::{GpuBatch, GpuPipelines};
16use llimphi_raster::peniko::Color;
17
18const W: u32 = 256;
19const H: u32 = 256;
20const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
21
22fn make_target(device: &wgpu::Device) -> (wgpu::Texture, wgpu::TextureView) {
23 let tex = device.create_texture(&wgpu::TextureDescriptor {
24 label: Some("smoke-target"),
25 size: wgpu::Extent3d {
26 width: W,
27 height: H,
28 depth_or_array_layers: 1,
29 },
30 mip_level_count: 1,
31 sample_count: 1,
32 dimension: wgpu::TextureDimension::D2,
33 format: FMT,
34 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
35 view_formats: &[],
36 });
37 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
38 (tex, view)
39}
40
41#[test]
42fn batch_with_rects_lines_tris_does_not_panic() {
43 let hal = pollster::block_on(Hal::new(None)).expect("hal");
44 let pipelines = GpuPipelines::new(&hal.device, FMT);
45 let (_tex, view) = make_target(&hal.device);
46
47 let mut batch = GpuBatch::new(&pipelines);
48 batch.line_width(2.0);
49
50 // Cuadrícula 8×8 de rects con color que varía.
51 for j in 0..8 {
52 for i in 0..8 {
53 let x = 8.0 + i as f32 * 30.0;
54 let y = 8.0 + j as f32 * 30.0;
55 let c = Color::from_rgba8(
56 (i * 32) as u8,
57 (j * 32) as u8,
58 100,
59 255,
60 );
61 batch.add_rect(x, y, 24.0, 24.0, c);
62 }
63 }
64
65 // Diagonal de líneas.
66 for k in 0..16 {
67 batch.add_line(
68 (0.0, k as f32 * 16.0),
69 (W as f32, (k + 1) as f32 * 16.0),
70 Color::from_rgba8(220, 220, 250, 180),
71 );
72 }
73
74 // Triángulo grande con color por vértice.
75 batch.add_tri(
76 (128.0, 32.0),
77 (64.0, 220.0),
78 (220.0, 220.0),
79 Color::from_rgba8(255, 80, 80, 200),
80 Color::from_rgba8(80, 255, 80, 200),
81 Color::from_rgba8(80, 80, 255, 200),
82 );
83
84 assert!(batch.primitive_count() > 0, "batch debería tener primitivas");
85
86 let mut encoder = hal
87 .device
88 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
89 label: Some("smoke-enc"),
90 });
91 batch.flush(
92 &hal.device,
93 &hal.queue,
94 &mut encoder,
95 &view,
96 (W as f32, H as f32),
97 wgpu::LoadOp::Clear(wgpu::Color::BLACK),
98 );
99 hal.queue.submit(std::iter::once(encoder.finish()));
100 hal.device.poll(wgpu::PollType::wait_indefinitely());
101}
102
103#[test]
104fn empty_batch_flush_is_no_op() {
105 let hal = pollster::block_on(Hal::new(None)).expect("hal");
106 let pipelines = GpuPipelines::new(&hal.device, FMT);
107 let (_tex, view) = make_target(&hal.device);
108
109 let batch = GpuBatch::new(&pipelines);
110 assert_eq!(batch.primitive_count(), 0);
111
112 let mut encoder = hal
113 .device
114 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
115 label: Some("smoke-empty-enc"),
116 });
117 // Con batch vacío, flush no debe crear render pass ni buffers.
118 batch.flush(
119 &hal.device,
120 &hal.queue,
121 &mut encoder,
122 &view,
123 (W as f32, H as f32),
124 wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
125 );
126 hal.queue.submit(std::iter::once(encoder.finish()));
127 hal.device.poll(wgpu::PollType::wait_indefinitely());
128}