shdrlib 0.2.0

High-level shader compilation and rendering library built on wgpu and naga
Documentation
use shdrlib::{AssetManager, RuntimeManager, Language};
use std::fs;

fn main() {
    println!("🔥 shdrlib Triangle Demo 🔥\n");

    // Initialize asset manager (sets up GPU automatically)
    let mut assets = AssetManager::new();
    let gpu_info = assets.gpu_info();
    println!("GPU: {} ({:?})", gpu_info.name, gpu_info.backend);
    println!("Driver: {}\n", gpu_info.driver_info);

    // Define a simple triangle shader (WGSL)
    let triangle_shader = r#"
        @vertex
        fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
            var positions = array<vec2<f32>, 3>(
                vec2<f32>(0.0, 0.5),
                vec2<f32>(-0.5, -0.5),
                vec2<f32>(0.5, -0.5)
            );
            let pos = positions[in_vertex_index];
            return vec4<f32>(pos, 0.0, 1.0);
        }

        @fragment
        fn fs_main() -> @location(0) vec4<f32> {
            return vec4<f32>(1.0, 0.0, 0.5, 1.0); // Hot pink
        }
    "#;

    println!("📝 Compiling shaders...");
    
    // Add vertex shader
    assets.add_shader(
        "triangle_vert",
        triangle_shader,
        naga::ShaderStage::Vertex,
        Language::WGSL,
    ).expect("Failed to compile vertex shader");

    // Add fragment shader
    assets.add_shader(
        "triangle_frag",
        triangle_shader,
        naga::ShaderStage::Fragment,
        Language::WGSL,
    ).expect("Failed to compile fragment shader");

    println!("✅ Shaders compiled successfully\n");

    // Create render pipeline
    println!("🔧 Building render pipeline...");
    assets.create_pipeline(
        "triangle_pipeline",
        "triangle_vert",
        Some("triangle_frag"),
        vec![], // No vertex buffers (using @builtin(vertex_index))
        wgpu::TextureFormat::Rgba8UnormSrgb,
    ).expect("Failed to create pipeline");

    println!("✅ Pipeline created\n");

    // Create runtime manager
    let runtime = RuntimeManager::new(&assets);

    // Render offscreen to a texture
    println!("🎨 Rendering triangle (800x600)...");
    let texture = runtime.render_to_texture(
        "triangle_pipeline",
        800,
        600,
        wgpu::TextureFormat::Rgba8UnormSrgb,
        wgpu::Color {
            r: 0.1,
            g: 0.1,
            b: 0.15,
            a: 1.0,
        },
        3, // Draw 3 vertices (the triangle)
    ).expect("Render failed");

    println!("✅ Render complete\n");

    // Read back the pixel data to verify rendering
    println!("📸 Reading back pixel data...");
    
    let bytes_per_pixel = 4; // RGBA8
    let padded_width = {
        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as u32;
        let unpadded_bytes = 800 * bytes_per_pixel;
        ((unpadded_bytes + align - 1) / align) * align
    };

    let buffer_size = (padded_width * 600) as u64;
    let staging_buffer = assets.device().create_buffer(&wgpu::BufferDescriptor {
        label: Some("Readback Buffer"),
        size: buffer_size,
        usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
        mapped_at_creation: false,
    });

    let mut encoder = assets.device().create_command_encoder(&wgpu::CommandEncoderDescriptor {
        label: Some("Readback Encoder"),
    });

    encoder.copy_texture_to_buffer(
        wgpu::ImageCopyTexture {
            texture: &texture,
            mip_level: 0,
            origin: wgpu::Origin3d::ZERO,
            aspect: wgpu::TextureAspect::All,
        },
        wgpu::ImageCopyBuffer {
            buffer: &staging_buffer,
            layout: wgpu::ImageDataLayout {
                offset: 0,
                bytes_per_row: Some(padded_width),
                rows_per_image: Some(600),
            },
        },
        wgpu::Extent3d {
            width: 800,
            height: 600,
            depth_or_array_layers: 1,
        },
    );

    assets.queue().submit(Some(encoder.finish()));

    // Map the buffer and read data
    let buffer_slice = staging_buffer.slice(..);
    let (tx, rx) = std::sync::mpsc::channel();
    buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
        tx.send(result).unwrap();
    });

    assets.device().poll(wgpu::Maintain::Wait);
    rx.recv().unwrap().expect("Failed to map buffer");

    {
        let data = buffer_slice.get_mapped_range();
        
        // Sample some pixels to verify rendering
        let sample_pixel = |x: usize, y: usize| -> (u8, u8, u8, u8) {
            let offset = y * padded_width as usize + x * 4;
            (data[offset], data[offset + 1], data[offset + 2], data[offset + 3])
        };

        // Check center (should be pink from triangle)
        let center = sample_pixel(400, 300);
        println!("Center pixel (400, 300): RGBA({}, {}, {}, {})", center.0, center.1, center.2, center.3);

        // Check top-left corner (should be background color)
        let corner = sample_pixel(10, 10);
        println!("Corner pixel (10, 10): RGBA({}, {}, {}, {})", corner.0, corner.1, corner.2, corner.3);

        // Check if center has pink color (high R, low G, high B)
        if center.0 > 200 && center.2 > 100 && center.1 < 50 {
            println!("\n✅ SUCCESS: Triangle rendered correctly! Pink detected at center.");
        } else {
            println!("\n⚠️  WARNING: Unexpected color at center. Triangle might not be visible.");
        }

        // Save as PPM image for visual inspection
        println!("\n💾 Saving render as 'output.ppm'...");
        let mut ppm = String::from("P3\n800 600\n255\n");
        for y in 0..600 {
            for x in 0..800 {
                let offset = y * padded_width as usize + x * 4;
                let r = data[offset];
                let g = data[offset + 1];
                let b = data[offset + 2];
                ppm.push_str(&format!("{} {} {} ", r, g, b));
            }
            ppm.push('\n');
        }
        fs::write("output.ppm", ppm).expect("Failed to write image");
    }

    staging_buffer.unmap();

    println!("✅ Image saved! Open output.ppm to see your triangle\n");
    println!("🎉 Demo complete! Your shader pipeline works!");
}