use std::collections::HashMap;
use std::sync::Arc;
use thiserror::Error;
use wgpu::*;
#[derive(Error, Debug)]
pub enum WebGpuRealError {
#[error("WebGPU not supported: {0}")]
NotSupported(String),
#[error("Device initialization failed: {0}")]
DeviceInit(String),
#[error("Surface creation failed: {0}")]
SurfaceCreation(String),
#[error("Shader compilation failed: {0}")]
ShaderCompilation(String),
#[error("Buffer creation failed: {0}")]
BufferCreation(String),
#[error("Render pipeline creation failed: {0}")]
PipelineCreation(String),
}
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 {
pub async fn new(canvas: Option<&str>) -> Result<Self, WebGpuRealError> {
if !Self::is_webgpu_supported() {
return Err(WebGpuRealError::NotSupported(
"WebGPU is not supported in this environment".to_string(),
));
}
let instance = Instance::new(&InstanceDescriptor {
backends: Backends::all(),
..Default::default()
});
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)))?;
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)))?;
let (surface, surface_config) = if let Some(_canvas_id) = canvas {
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(),
})
}
pub fn is_webgpu_supported() -> bool {
let _instance = Instance::new(&InstanceDescriptor {
backends: Backends::all(),
..Default::default()
});
true }
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(())
}
pub fn create_line_pipeline(&mut self) -> Result<(), WebGpuRealError> {
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(())
}
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)
}
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(())
}
pub fn get_device_info(&self) -> String {
format!("Device: {:?}", self.device)
}
pub fn is_ready(&self) -> bool {
self.surface.is_some() && self.surface_config.is_some() && !self.render_pipelines.is_empty()
}
pub fn has_shader(&self, name: &str) -> bool {
self.shader_cache.contains_key(name)
}
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() {
let supported = WebGpuRealRenderer::is_webgpu_supported();
println!("WebGPU supported: {}", supported);
}
#[tokio::test]
async fn test_renderer_creation() {
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() {
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);
}
"#;
println!("Shader source compiled: {}", shader_source.len());
}
}