llimphi-raster 0.1.0

2D GPU rasterizer for llimphi over vello, with an opt-in CPU+GPU hybrid renderer.
Documentation
//! 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::PollType::wait_indefinitely());
}

#[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::PollType::wait_indefinitely());
}