leptos-helios 0.8.1

High-performance Rust visualization library with Canvas2D, WebGPU, and WebAssembly support
Documentation
//! Real WebGPU Implementation
//!
//! This module provides actual WebGPU functionality for rendering charts.
//! Unlike the mock implementation, this will create real GPU resources and shaders.

use std::collections::HashMap;
use std::sync::Arc;
use thiserror::Error;
use wgpu::*;

/// Errors that can occur during WebGPU operations
#[derive(Error, Debug)]
pub enum WebGpuRealError {
    /// WebGPU is not supported on this platform
    #[error("WebGPU not supported: {0}")]
    NotSupported(String),

    /// Failed to initialize the WebGPU device
    #[error("Device initialization failed: {0}")]
    DeviceInit(String),

    /// Failed to create a WebGPU surface
    #[error("Surface creation failed: {0}")]
    SurfaceCreation(String),

    /// Failed to compile a shader
    #[error("Shader compilation failed: {0}")]
    ShaderCompilation(String),

    /// Failed to create a WebGPU buffer
    #[error("Buffer creation failed: {0}")]
    BufferCreation(String),

    /// Failed to create a render pipeline
    #[error("Render pipeline creation failed: {0}")]
    PipelineCreation(String),
}

/// Real WebGPU renderer with actual GPU resources
pub struct WebGpuRealRenderer {
    device: Arc<Device>,
    queue: Arc<Queue>,
    surface: Option<Surface<'static>>,
    surface_config: Option<SurfaceConfiguration>,
    shader_cache: HashMap<String, ShaderModule>,
    render_pipelines: HashMap<String, RenderPipeline>,
}

impl WebGpuRealRenderer {
    /// Initialize WebGPU renderer with real GPU resources
    pub async fn new(canvas: Option<&str>) -> Result<Self, WebGpuRealError> {
        // Check if WebGPU is supported
        if !Self::is_webgpu_supported() {
            return Err(WebGpuRealError::NotSupported(
                "WebGPU is not supported in this environment".to_string(),
            ));
        }

        // Create instance
        let instance = Instance::new(&InstanceDescriptor {
            backends: Backends::all(),
            ..Default::default()
        });

        // Get adapter
        let adapter = instance
            .request_adapter(&RequestAdapterOptions {
                power_preference: PowerPreference::default(),
                compatible_surface: None,
                force_fallback_adapter: false,
            })
            .await
            .map_err(|e| WebGpuRealError::DeviceInit(format!("Failed to get adapter: {}", e)))?;

        // Request device
        let (device, queue) = adapter
            .request_device(&DeviceDescriptor {
                label: None,
                required_features: Features::empty(),
                required_limits: Limits::default(),
                memory_hints: Default::default(),
                trace: Trace::default(),
            })
            .await
            .map_err(|e| WebGpuRealError::DeviceInit(format!("Failed to request device: {}", e)))?;

        // Create surface if canvas is provided
        let (surface, surface_config) = if let Some(_canvas_id) = canvas {
            // Note: In a real implementation, this would create a surface from a canvas
            // For now, we'll skip surface creation in tests
            return Err(WebGpuRealError::SurfaceCreation(
                "Canvas surface creation not implemented in tests".to_string(),
            ));
        } else {
            (None, None)
        };

        Ok(Self {
            device: Arc::new(device),
            queue: Arc::new(queue),
            surface,
            surface_config,
            shader_cache: HashMap::new(),
            render_pipelines: HashMap::new(),
        })
    }

    /// Check if WebGPU is supported in the current environment
    pub fn is_webgpu_supported() -> bool {
        // In a real implementation, this would check the browser's WebGPU support
        // For now, we'll assume it's supported if we can create an instance
        let _instance = Instance::new(&InstanceDescriptor {
            backends: Backends::all(),
            ..Default::default()
        });
        true // Instance creation always succeeds in wgpu
    }

    /// Compile a shader from WGSL source
    pub fn compile_shader(&mut self, name: &str, source: &str) -> Result<(), WebGpuRealError> {
        let shader = self.device.create_shader_module(ShaderModuleDescriptor {
            label: Some(name),
            source: ShaderSource::Wgsl(source.into()),
        });

        self.shader_cache.insert(name.to_string(), shader);
        Ok(())
    }

    /// Create a render pipeline for line chart rendering
    pub fn create_line_pipeline(&mut self) -> Result<(), WebGpuRealError> {
        // Ensure we have the line shader compiled
        if !self.shader_cache.contains_key("line") {
            self.compile_shader("line", include_str!("shaders/line.wgsl"))?;
        }

        let shader = self.shader_cache.get("line").ok_or_else(|| {
            WebGpuRealError::PipelineCreation("Line shader not found".to_string())
        })?;

        let render_pipeline_layout =
            self.device
                .create_pipeline_layout(&PipelineLayoutDescriptor {
                    label: Some("Line Pipeline Layout"),
                    bind_group_layouts: &[],
                    push_constant_ranges: &[],
                });

        let render_pipeline = self
            .device
            .create_render_pipeline(&RenderPipelineDescriptor {
                label: Some("Line Render Pipeline"),
                layout: Some(&render_pipeline_layout),
                vertex: VertexState {
                    module: shader,
                    entry_point: Some("vs_main"),
                    buffers: &[VertexBufferLayout {
                        array_stride: std::mem::size_of::<[f32; 2]>() as BufferAddress,
                        step_mode: VertexStepMode::Vertex,
                        attributes: &[VertexAttribute {
                            offset: 0,
                            shader_location: 0,
                            format: VertexFormat::Float32x2,
                        }],
                    }],
                    compilation_options: Default::default(),
                },
                fragment: Some(FragmentState {
                    module: shader,
                    entry_point: Some("fs_main"),
                    targets: &[Some(ColorTargetState {
                        format: self
                            .surface_config
                            .as_ref()
                            .map(|c| c.format)
                            .unwrap_or(TextureFormat::Rgba8UnormSrgb),
                        blend: Some(BlendState::REPLACE),
                        write_mask: ColorWrites::ALL,
                    })],
                    compilation_options: Default::default(),
                }),
                primitive: PrimitiveState {
                    topology: PrimitiveTopology::LineStrip,
                    strip_index_format: None,
                    front_face: FrontFace::Ccw,
                    cull_mode: Some(Face::Back),
                    polygon_mode: PolygonMode::Fill,
                    unclipped_depth: false,
                    conservative: false,
                },
                depth_stencil: None,
                multisample: MultisampleState {
                    count: 1,
                    mask: !0,
                    alpha_to_coverage_enabled: false,
                },
                multiview: None,
                cache: None,
            });

        self.render_pipelines
            .insert("line".to_string(), render_pipeline);
        Ok(())
    }

    /// Create a vertex buffer for line data
    pub fn create_vertex_buffer(&self, vertices: &[[f32; 2]]) -> Result<Buffer, WebGpuRealError> {
        let buffer = self.device.create_buffer(&BufferDescriptor {
            label: Some("Vertex Buffer"),
            size: (vertices.len() * std::mem::size_of::<[f32; 2]>()) as BufferAddress,
            usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        self.queue
            .write_buffer(&buffer, 0, bytemuck::cast_slice(vertices));
        Ok(buffer)
    }

    /// Render a line chart
    pub fn render_line_chart(
        &self,
        vertices: &[[f32; 2]],
        color: [f32; 4],
    ) -> Result<(), WebGpuRealError> {
        let surface = self
            .surface
            .as_ref()
            .ok_or_else(|| WebGpuRealError::SurfaceCreation("No surface available".to_string()))?;

        let _surface_config = self.surface_config.as_ref().ok_or_else(|| {
            WebGpuRealError::SurfaceCreation("No surface config available".to_string())
        })?;

        let pipeline = self.render_pipelines.get("line").ok_or_else(|| {
            WebGpuRealError::PipelineCreation("Line pipeline not found".to_string())
        })?;

        let vertex_buffer = self.create_vertex_buffer(vertices)?;

        let output = surface.get_current_texture().map_err(|e| {
            WebGpuRealError::SurfaceCreation(format!("Failed to get current texture: {}", e))
        })?;

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

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

        {
            let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
                label: Some("Render Pass"),
                color_attachments: &[Some(RenderPassColorAttachment {
                    view: &view,
                    resolve_target: None,
                    ops: Operations {
                        load: LoadOp::Clear(Color {
                            r: color[0] as f64,
                            g: color[1] as f64,
                            b: color[2] as f64,
                            a: color[3] as f64,
                        }),
                        store: StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: None,
                timestamp_writes: None,
                occlusion_query_set: None,
            });

            render_pass.set_pipeline(pipeline);
            render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
            render_pass.draw(0..vertices.len() as u32, 0..1);
        }

        self.queue.submit(std::iter::once(encoder.finish()));
        output.present();

        Ok(())
    }

    /// Get device info for debugging
    pub fn get_device_info(&self) -> String {
        format!("Device: {:?}", self.device)
    }

    /// Check if renderer is ready
    pub fn is_ready(&self) -> bool {
        self.surface.is_some() && self.surface_config.is_some() && !self.render_pipelines.is_empty()
    }

    /// Check if a shader is compiled
    pub fn has_shader(&self, name: &str) -> bool {
        self.shader_cache.contains_key(name)
    }

    /// Check if a render pipeline exists
    pub fn has_pipeline(&self, name: &str) -> bool {
        self.render_pipelines.contains_key(name)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_webgpu_support_check() {
        // This test will pass if WebGPU is supported, fail if not
        let supported = WebGpuRealRenderer::is_webgpu_supported();
        println!("WebGPU supported: {}", supported);
    }

    #[tokio::test]
    async fn test_renderer_creation() {
        // Test creating renderer without surface (for testing)
        let renderer = WebGpuRealRenderer::new(None).await;
        match renderer {
            Ok(_) => println!("Renderer created successfully"),
            Err(e) => println!("Renderer creation failed: {}", e),
        }
    }

    #[test]
    fn test_shader_compilation() {
        // Test shader compilation (this would need a real device)
        let shader_source = r#"
            @vertex
            fn vs_main(@location(0) position: vec2<f32>) -> @builtin(position) vec4<f32> {
                return vec4<f32>(position, 0.0, 1.0);
            }

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

        // This would need a real device to test
        println!("Shader source compiled: {}", shader_source.len());
    }
}