use bytemuck::{Pod, Zeroable};
use wgpu::util::DeviceExt;
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct QuadVertex {
pub position: [f32; 2],
pub color: [f32; 4],
pub rect_center: [f32; 2],
pub rect_half_size: [f32; 2],
pub corner_radius: f32,
pub _padding: f32,
}
impl QuadVertex {
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<QuadVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: 8,
shader_location: 1,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: 24,
shader_location: 2,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: 32,
shader_location: 3,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: 40,
shader_location: 4,
format: wgpu::VertexFormat::Float32,
},
],
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct QuadUniforms {
pub screen_size: [f32; 2],
pub _padding: [f32; 2],
}
pub const QUAD_SHADER: &str = r#"
struct Uniforms {
screen_size: vec2<f32>,
_padding: vec2<f32>,
};
@group(0) @binding(0)
var<uniform> uniforms: Uniforms;
struct VertexInput {
@location(0) position: vec2<f32>,
@location(1) color: vec4<f32>,
@location(2) rect_center: vec2<f32>,
@location(3) rect_half_size: vec2<f32>,
@location(4) corner_radius: f32,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) pixel_pos: vec2<f32>,
@location(2) rect_center: vec2<f32>,
@location(3) rect_half_size: vec2<f32>,
@location(4) corner_radius: f32,
};
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let ndc = vec2<f32>(
in.position.x / uniforms.screen_size.x * 2.0 - 1.0,
1.0 - in.position.y / uniforms.screen_size.y * 2.0,
);
out.clip_position = vec4<f32>(ndc, 0.0, 1.0);
out.color = in.color;
out.pixel_pos = in.position;
out.rect_center = in.rect_center;
out.rect_half_size = in.rect_half_size;
out.corner_radius = in.corner_radius;
return out;
}
fn rounded_rect_sdf(pos: vec2<f32>, center: vec2<f32>, half_size: vec2<f32>, radius: f32) -> f32 {
let d = abs(pos - center) - half_size + vec2<f32>(radius);
return length(max(d, vec2<f32>(0.0))) + min(max(d.x, d.y), 0.0) - radius;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
if in.corner_radius > 0.0 {
let dist = rounded_rect_sdf(in.pixel_pos, in.rect_center, in.rect_half_size, in.corner_radius);
let aa = fwidth(dist);
let alpha = 1.0 - smoothstep(-aa, aa, dist);
return vec4<f32>(in.color.rgb, in.color.a * alpha);
}
return in.color;
}
"#;
pub struct QuadPipeline {
pub pipeline: wgpu::RenderPipeline,
pub uniform_buffer: wgpu::Buffer,
pub uniform_bind_group: wgpu::BindGroup,
pub vertices: Vec<QuadVertex>,
pub indices: Vec<u32>,
}
impl QuadPipeline {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("quad_shader"),
source: wgpu::ShaderSource::Wgsl(QUAD_SHADER.into()),
});
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("quad_uniforms"),
contents: bytemuck::cast_slice(&[QuadUniforms {
screen_size: [800.0, 600.0],
_padding: [0.0; 2],
}]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("quad_bind_group_layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("quad_bind_group"),
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("quad_pipeline_layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("quad_pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[QuadVertex::desc()],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
Self {
pipeline,
uniform_buffer,
uniform_bind_group,
vertices: Vec::new(),
indices: Vec::new(),
}
}
pub fn push_rect(
&mut self,
x: f32,
y: f32,
w: f32,
h: f32,
color: [f32; 4],
corner_radius: f32,
) {
let base = self.vertices.len() as u32;
let cx = x + w / 2.0;
let cy = y + h / 2.0;
let hx = w / 2.0;
let hy = h / 2.0;
let make_vertex = |px: f32, py: f32| QuadVertex {
position: [px, py],
color,
rect_center: [cx, cy],
rect_half_size: [hx, hy],
corner_radius,
_padding: 0.0,
};
self.vertices.push(make_vertex(x, y));
self.vertices.push(make_vertex(x + w, y));
self.vertices.push(make_vertex(x + w, y + h));
self.vertices.push(make_vertex(x, y + h));
self.indices.extend_from_slice(&[
base,
base + 1,
base + 2,
base,
base + 2,
base + 3,
]);
}
pub fn clear(&mut self) {
self.vertices.clear();
self.indices.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quad_vertex_size() {
assert_eq!(std::mem::size_of::<QuadVertex>(), 48);
}
#[test]
fn quad_vertex_is_pod() {
let v = QuadVertex {
position: [10.0, 20.0],
color: [1.0, 0.0, 0.0, 1.0],
rect_center: [50.0, 50.0],
rect_half_size: [40.0, 30.0],
corner_radius: 5.0,
_padding: 0.0,
};
let bytes = bytemuck::bytes_of(&v);
assert_eq!(bytes.len(), 48);
}
}