1use bytemuck::{Pod, Zeroable};
2use wgpu::util::DeviceExt;
3
4use crate::target::Targets;
5
6#[repr(C)]
7#[derive(Copy, Clone, Pod, Zeroable)]
8struct PrimVertex {
9 pos: [f32; 2],
10}
11
12const QUAD_VERTS: &[PrimVertex] = &[
13 PrimVertex { pos: [0.0, 0.0] },
14 PrimVertex { pos: [1.0, 0.0] },
15 PrimVertex { pos: [1.0, 1.0] },
16 PrimVertex { pos: [0.0, 1.0] },
17];
18const QUAD_INDICES: &[u16] = &[0, 1, 2, 0, 2, 3];
19
20#[repr(C)]
21#[derive(Copy, Clone, Pod, Zeroable, Debug)]
22pub struct CircleInstance {
23 pub center: [f32; 2],
24 pub radius: f32,
25 pub thickness: f32,
27 pub color: [f32; 4],
28}
29
30pub(crate) struct PrimitiveBatcher {
31 quad_vb: wgpu::Buffer,
32 quad_ib: wgpu::Buffer,
33 instance_vb: wgpu::Buffer,
34 capacity: usize,
35 pipeline: wgpu::RenderPipeline,
36 pending: Vec<(i16, CircleInstance)>,
37}
38
39impl PrimitiveBatcher {
40 pub fn new(
41 device: &wgpu::Device,
42 surface_format: wgpu::TextureFormat,
43 camera_bgl: &wgpu::BindGroupLayout,
44 sample_count: u32,
45 depth_format: Option<wgpu::TextureFormat>,
46 ) -> Self {
47 let quad_vb = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
48 label: Some("prim.quad_vb"),
49 contents: bytemuck::cast_slice(QUAD_VERTS),
50 usage: wgpu::BufferUsages::VERTEX,
51 });
52 let quad_ib = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
53 label: Some("prim.quad_ib"),
54 contents: bytemuck::cast_slice(QUAD_INDICES),
55 usage: wgpu::BufferUsages::INDEX,
56 });
57 let capacity = 1024usize;
58 let instance_vb = device.create_buffer(&wgpu::BufferDescriptor {
59 label: Some("prim.circle_instances"),
60 size: (capacity * size_of::<CircleInstance>()) as u64,
61 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
62 mapped_at_creation: false,
63 });
64
65 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
66 label: Some("prim.circle.shader"),
67 source: wgpu::ShaderSource::Wgsl(
68 concat!(include_str!("srgb.wgsl"), include_str!("circle.wgsl")).into(),
69 ),
70 });
71 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
72 label: Some("prim.layout"),
73 bind_group_layouts: &[Some(camera_bgl)],
74 immediate_size: 0,
75 });
76
77 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
78 label: Some("prim.circle.pipeline"),
79 layout: Some(&layout),
80 vertex: wgpu::VertexState {
81 module: &shader,
82 entry_point: Some("vs_main"),
83 compilation_options: Default::default(),
84 buffers: &[
85 Some(wgpu::VertexBufferLayout {
86 array_stride: size_of::<PrimVertex>() as u64,
87 step_mode: wgpu::VertexStepMode::Vertex,
88 attributes: &wgpu::vertex_attr_array![0 => Float32x2],
89 }),
90 Some(wgpu::VertexBufferLayout {
91 array_stride: size_of::<CircleInstance>() as u64,
92 step_mode: wgpu::VertexStepMode::Instance,
93 attributes: &wgpu::vertex_attr_array![
94 2 => Float32x2,
95 3 => Float32,
96 4 => Float32,
97 5 => Float32x4,
98 ],
99 }),
100 ],
101 },
102 fragment: Some(wgpu::FragmentState {
103 module: &shader,
104 entry_point: Some("fs_main"),
105 compilation_options: Default::default(),
106 targets: &[Some(wgpu::ColorTargetState {
107 format: surface_format,
108 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
109 write_mask: wgpu::ColorWrites::ALL,
110 })],
111 }),
112 primitive: wgpu::PrimitiveState::default(),
113 depth_stencil: depth_format.map(crate::target::no_write_depth),
114 multisample: crate::target::multisample(sample_count),
115 multiview_mask: None,
116 cache: None,
117 });
118
119 Self {
120 quad_vb,
121 quad_ib,
122 instance_vb,
123 capacity,
124 pipeline,
125 pending: Vec::new(),
126 }
127 }
128
129 pub fn push(&mut self, layer: i16, inst: CircleInstance) {
130 self.pending.push((layer, inst));
131 }
132
133 pub fn collect_layers(&self, out: &mut std::collections::BTreeSet<i16>) {
135 out.extend(self.pending.iter().map(|(layer, _)| *layer));
136 }
137
138 pub fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
142 if self.pending.is_empty() {
143 return;
144 }
145 self.pending.sort_by_key(|(layer, _)| *layer);
146 if self.pending.len() > self.capacity {
147 self.capacity = self.pending.len().next_power_of_two();
148 self.instance_vb = device.create_buffer(&wgpu::BufferDescriptor {
149 label: Some("prim.circle_instances"),
150 size: (self.capacity * size_of::<CircleInstance>()) as u64,
151 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
152 mapped_at_creation: false,
153 });
154 }
155 let flat: Vec<CircleInstance> = self.pending.iter().map(|(_, c)| *c).collect();
156 queue.write_buffer(&self.instance_vb, 0, bytemuck::cast_slice(&flat));
157 }
158
159 pub fn draw_layer(
162 &self,
163 layer: i16,
164 encoder: &mut wgpu::CommandEncoder,
165 targets: &Targets<'_>,
166 camera_bg: &wgpu::BindGroup,
167 ) {
168 let lo = self.pending.partition_point(|(l, _)| *l < layer);
170 let hi = self.pending.partition_point(|(l, _)| *l <= layer);
171 if lo == hi {
172 return;
173 }
174
175 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
176 label: Some("prim.circle.pass"),
177 color_attachments: &[Some(targets.color_attachment(wgpu::LoadOp::Load))],
178 depth_stencil_attachment: targets.depth_attachment(wgpu::LoadOp::Load),
179 occlusion_query_set: None,
180 timestamp_writes: None,
181 multiview_mask: None,
182 });
183 pass.set_pipeline(&self.pipeline);
184 pass.set_bind_group(0, camera_bg, &[]);
185 pass.set_vertex_buffer(0, self.quad_vb.slice(..));
186 pass.set_index_buffer(self.quad_ib.slice(..), wgpu::IndexFormat::Uint16);
187 pass.set_vertex_buffer(1, self.instance_vb.slice(..));
188 pass.draw_indexed(0..6, 0, lo as u32..hi as u32);
189 }
190
191 pub fn clear(&mut self) {
192 self.pending.clear();
193 }
194}