#[cfg(feature = "gpu")]
use wgpu::{util::DeviceExt, Device, Queue, RenderPipeline};
#[cfg(feature = "gpu")]
const VERTEX_SHADER_SOURCE: &str = r#"
struct VertexInput {
@location(0) position: vec2<f32>,
@location(1) color: vec4<f32>,
}
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
}
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
output.position = vec4<f32>(input.position, 0.0, 1.0);
output.color = input.color;
return output;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
return input.color;
}
"#;
#[cfg(feature = "gpu")]
#[derive(Debug)]
pub struct WGPURenderer<'window> {
instance: wgpu::Instance,
device: Option<Device>,
queue: Option<Queue>,
surface: Option<wgpu::Surface<'window>>,
config: Option<wgpu::SurfaceConfiguration>,
render_pipeline: Option<RenderPipeline>,
current_width: u32,
current_height: u32,
pending_vertices: Vec<RenderVertex>,
}
#[cfg(feature = "gpu")]
#[derive(Debug, Clone, Copy)]
struct RenderVertex {
position: [f32; 2],
color: [f32; 4],
}
#[cfg(feature = "gpu")]
impl<'window> WGPURenderer<'window> {
pub fn new() -> Result<Self, String> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
flags: wgpu::InstanceFlags::default(),
dx12_shader_compiler: wgpu::Dx12Compiler::default(),
gles_minor_version: wgpu::Gles3MinorVersion::default(),
});
Ok(Self {
instance,
device: None,
queue: None,
surface: None,
config: None,
render_pipeline: None,
current_width: 1,
current_height: 1,
pending_vertices: Vec::new(),
})
}
pub async fn attach_surface(
&mut self,
target: impl Into<wgpu::SurfaceTarget<'window>>,
) -> Result<(), String> {
let surface = self
.instance
.create_surface(target)
.map_err(|e| format!("Failed to create surface: {:?}", e))?;
let adapter = self
.instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.ok_or("No suitable GPU adapter found")?;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("CAD GPU Device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::default(),
},
None,
)
.await
.map_err(|e| format!("Failed to request device: {:?}", e))?;
let surface_capabilities = surface.get_capabilities(&adapter);
let format = surface_capabilities
.formats
.iter()
.find(|f| f.is_srgb())
.copied()
.unwrap_or(surface_capabilities.formats[0]);
let width = self.current_width.max(1);
let height = self.current_height.max(1);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width,
height,
present_mode: wgpu::PresentMode::Fifo,
alpha_mode: surface_capabilities.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &config);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("CAD Shader"),
source: wgpu::ShaderSource::Wgsl(VERTEX_SHADER_SOURCE.into()),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("CAD Pipeline Layout"),
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("CAD Render Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<RenderVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x4,
offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
shader_location: 1,
},
],
}],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
self.device = Some(device);
self.queue = Some(queue);
self.surface = Some(surface);
self.config = Some(config);
self.render_pipeline = Some(render_pipeline);
Ok(())
}
pub fn resize(&mut self, width: u32, height: u32) {
self.current_width = width.max(1);
self.current_height = height.max(1);
if let (Some(device), Some(surface), Some(config)) =
(&self.device, &self.surface, &mut self.config)
{
config.width = self.current_width;
config.height = self.current_height;
surface.configure(device, config);
}
}
pub fn clear(&mut self, _r: f32, _g: f32, _b: f32, _a: f32) {
self.pending_vertices.clear();
}
pub fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: &[f32; 4]) {
let v1 = RenderVertex {
position: [x1, y1],
color: *color,
};
let v2 = RenderVertex {
position: [x2, y2],
color: *color,
};
self.pending_vertices.push(v1);
self.pending_vertices.push(v2);
}
pub fn draw_circle(&mut self, cx: f32, cy: f32, r: f32, color: &[f32; 4], segments: u32) {
let segments = segments.max(32);
for i in 0..segments {
let theta1 = (i as f32 / segments as f32) * std::f32::consts::TAU;
let theta2 = ((i + 1) as f32 / segments as f32) * std::f32::consts::TAU;
let x1 = cx + theta1.cos() * r;
let y1 = cy + theta1.sin() * r;
let x2 = cx + theta2.cos() * r;
let y2 = cy + theta2.sin() * r;
self.draw_line(x1, y1, x2, y2, color);
}
}
pub fn present(&mut self) {
if self.pending_vertices.is_empty() {
return;
}
let device = match self.device.take() {
Some(d) => d,
None => return,
};
let queue = match self.queue.take() {
Some(q) => q,
None => {
self.device = Some(device);
return;
}
};
let surface = match self.surface.take() {
Some(s) => s,
None => {
self.device = Some(device);
self.queue = Some(queue);
return;
}
};
let config = match self.config.take() {
Some(c) => c,
None => {
self.device = Some(device);
self.queue = Some(queue);
self.surface = Some(surface);
return;
}
};
let frame = match surface.get_current_texture() {
Ok(frame) => frame,
Err(_) => {
self.device = Some(device);
self.queue = Some(queue);
self.surface = Some(surface);
self.config = Some(config);
return;
}
};
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("CAD Render Encoder"),
});
let vertex_data: Vec<f32> = self
.pending_vertices
.iter()
.flat_map(|v| v.position.iter().chain(v.color.iter()))
.copied()
.collect();
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("CAD Vertex Buffer"),
contents: bytemuck::cast_slice(&vertex_data),
usage: wgpu::BufferUsages::VERTEX,
});
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("CAD Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
if let Some(pipeline) = &self.render_pipeline {
render_pass.set_pipeline(pipeline);
render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
render_pass.draw(0..(self.pending_vertices.len() as u32), 0..1);
}
drop(render_pass);
queue.submit(std::iter::once(encoder.finish()));
frame.present();
self.device = Some(device);
self.queue = Some(queue);
self.surface = Some(surface);
self.config = Some(config);
}
pub fn flush(&mut self) {
self.present();
}
pub fn is_available() -> bool {
true
}
}