shdrlib 0.2.0

High-level shader compilation and rendering library built on wgpu and naga
Documentation
use shdrlib::{AssetManager, Language};
use std::sync::Arc;
use winit::{
    application::ApplicationHandler,
    event::WindowEvent,
    event_loop::{ActiveEventLoop, EventLoop},
    window::{Window, WindowId},
};

struct App {
    window: Option<Arc<Window>>,
    assets: Option<AssetManager>,
    surface: Option<wgpu::Surface<'static>>,
    config: Option<wgpu::SurfaceConfiguration>,
}

impl Default for App {
    fn default() -> Self {
        Self {
            window: None,
            assets: None,
            surface: None,
            config: None,
        }
    }
}

impl ApplicationHandler for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        if self.window.is_some() {
            return;
        }

        println!("🔥 shdrlib Window Render Demo 🔥\n");

        // Create window
        let window_attributes = Window::default_attributes()
            .with_title("shdrlib - Live Triangle Render")
            .with_inner_size(winit::dpi::LogicalSize::new(800, 600));

        let window = Arc::new(event_loop.create_window(window_attributes).unwrap());
        println!("✅ Window created\n");

        // Initialize asset manager
        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);

        // Create surface using the same wgpu instance from AssetManager
        let surface = assets.instance().create_surface(window.clone()).unwrap();
        let size = window.inner_size();

        // Get surface capabilities from AssetManager's adapter
        let surface_caps = surface.get_capabilities(assets.adapter());
        let surface_format = surface_caps.formats.iter()
            .copied()
            .find(|f| f.is_srgb())
            .unwrap_or(surface_caps.formats[0]);

        println!("Surface format: {:?}\n", surface_format);

        // Configure surface
        let config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format: surface_format,
            width: size.width,
            height: size.height,
            present_mode: wgpu::PresentMode::Fifo,
            alpha_mode: surface_caps.alpha_modes[0],
            view_formats: vec![],
            desired_maximum_frame_latency: 2,
        };

        surface.configure(assets.device(), &config);

        // Define triangle shader
        let triangle_shader = r#"
            struct VertexOutput {
                @builtin(position) position: vec4<f32>,
                @location(0) color: vec3<f32>,
            }

            @vertex
            fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput {
                var positions = array<vec2<f32>, 3>(
                    vec2<f32>(0.0, 0.6),
                    vec2<f32>(-0.6, -0.6),
                    vec2<f32>(0.6, -0.6)
                );
                
                var colors = array<vec3<f32>, 3>(
                    vec3<f32>(1.0, 0.0, 0.0),  // Red
                    vec3<f32>(0.0, 1.0, 0.0),  // Green
                    vec3<f32>(0.0, 0.0, 1.0)   // Blue
                );

                var output: VertexOutput;
                output.position = vec4<f32>(positions[in_vertex_index], 0.0, 1.0);
                output.color = colors[in_vertex_index];
                return output;
            }

            @fragment
            fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
                return vec4<f32>(in.color, 1.0);
            }
        "#;

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

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

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

        // Create pipeline
        println!("🔧 Building pipeline...");
        assets.create_pipeline(
            "triangle",
            "triangle_vert",
            Some("triangle_frag"),
            vec![],
            surface_format,
        ).expect("Failed to create pipeline");

        println!("✅ Pipeline ready\n");
        println!("🎨 Starting render loop...\n");

        self.window = Some(window);
        self.assets = Some(assets);
        self.surface = Some(surface);
        self.config = Some(config);
    }

    fn window_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        _window_id: WindowId,
        event: WindowEvent,
    ) {
        match event {
            WindowEvent::CloseRequested => {
                println!("\n👋 Closing window...");
                event_loop.exit();
            }

            WindowEvent::Resized(physical_size) => {
                if let (Some(surface), Some(config), Some(assets)) = 
                    (&self.surface, &mut self.config, &self.assets) {
                    if physical_size.width > 0 && physical_size.height > 0 {
                        config.width = physical_size.width;
                        config.height = physical_size.height;
                        surface.configure(assets.device(), config);
                    }
                }
            }

            WindowEvent::RedrawRequested => {
                if let (Some(surface), Some(assets), Some(window)) = 
                    (&self.surface, &self.assets, &self.window) {
                    
                    // Get current surface texture
                    let output = match surface.get_current_texture() {
                        Ok(texture) => texture,
                        Err(e) => {
                            eprintln!("Failed to get surface texture: {:?}", e);
                            return;
                        }
                    };

                    let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());

                    // Get the pipeline
                    let duo = assets.get_pipeline("triangle").unwrap();

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

                    // Render pass
                    {
                        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                            label: Some("Render Pass"),
                            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                                view: &view,
                                resolve_target: None,
                                ops: wgpu::Operations {
                                    load: wgpu::LoadOp::Clear(wgpu::Color {
                                        r: 0.05,
                                        g: 0.05,
                                        b: 0.08,
                                        a: 1.0,
                                    }),
                                    store: wgpu::StoreOp::Store,
                                },
                            })],
                            depth_stencil_attachment: None,
                            timestamp_writes: None,
                            occlusion_query_set: None,
                        });

                        render_pass.set_pipeline(&duo.pipeline);
                        render_pass.draw(0..3, 0..1);
                    }

                    // Submit commands and present
                    assets.queue().submit(Some(encoder.finish()));
                    output.present();

                    window.request_redraw();
                }
            }

            _ => {}
        }
    }
}

fn main() {
    let event_loop = EventLoop::new().unwrap();
    let mut app = App::default();
    event_loop.run_app(&mut app).unwrap();
}