1use crate::atlas::{AtlasError, GpuAtlas};
2use bytemuck::{Pod, Zeroable};
3use densa_core::Color;
4use densa_draw::Instance;
5use densa_text::AtlasUpdate;
6use std::mem::size_of;
7
8#[repr(C)]
9#[derive(Debug, Clone, Copy, Pod, Zeroable)]
10struct Globals {
11 screen: [f32; 2],
12 _pad: [f32; 2],
13}
14
15#[derive(Debug)]
21pub struct GpuRenderer {
22 pipeline: wgpu::RenderPipeline,
23 bind_group_layout: wgpu::BindGroupLayout,
24 bind_group: wgpu::BindGroup,
25 sampler: wgpu::Sampler,
26 globals: wgpu::Buffer,
27 instances: wgpu::Buffer,
28 atlas: GpuAtlas,
29 bound_atlas: u64,
31 failed: Option<AtlasError>,
33 capacity: usize,
34}
35
36impl GpuRenderer {
37 pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat, capacity: usize) -> Self {
43 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
44 label: Some("densa atlas sampler"),
45 mag_filter: wgpu::FilterMode::Nearest,
47 min_filter: wgpu::FilterMode::Nearest,
48 ..Default::default()
49 });
50 let globals = device.create_buffer(&wgpu::BufferDescriptor {
51 label: Some("densa globals"),
52 size: size_of::<Globals>() as u64,
53 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
54 mapped_at_creation: false,
55 });
56 let instances = device.create_buffer(&wgpu::BufferDescriptor {
57 label: Some("densa instances"),
58 size: (capacity.max(1) * size_of::<Instance>()) as u64,
59 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
60 mapped_at_creation: false,
61 });
62 let atlas = GpuAtlas::new(device);
63 let bind_group_layout = bind_group_layout(device);
64 let bind_group = bind_group(device, &bind_group_layout, &globals, &atlas, &sampler);
65 let pipeline = pipeline(device, &bind_group_layout, format);
66
67 Self {
68 pipeline,
69 bind_group_layout,
70 bind_group,
71 sampler,
72 globals,
73 instances,
74 bound_atlas: atlas.version(),
75 failed: None,
76 atlas,
77 capacity,
78 }
79 }
80
81 pub fn capacity(&self) -> usize {
83 self.capacity
84 }
85
86 pub fn max_atlas_side(device: &wgpu::Device) -> u32 {
89 device.limits().max_texture_dimension_2d
90 }
91
92 pub fn atlas_error(&self) -> Option<AtlasError> {
95 self.failed
96 }
97
98 pub fn atlas_size(&self) -> (u32, u32) {
100 self.atlas.size()
101 }
102
103 pub fn apply_atlas_updates(
113 &mut self,
114 device: &wgpu::Device,
115 queue: &wgpu::Queue,
116 updates: impl IntoIterator<Item = AtlasUpdate>,
117 ) -> Result<usize, AtlasError> {
118 if let Some(error) = self.failed {
119 return Err(error);
120 }
121 let result = self.atlas.apply(device, queue, updates);
122 self.failed = result.err();
123 if self.atlas.version() != self.bound_atlas {
125 self.bind_group = bind_group(
126 device,
127 &self.bind_group_layout,
128 &self.globals,
129 &self.atlas,
130 &self.sampler,
131 );
132 self.bound_atlas = self.atlas.version();
133 }
134 result
135 }
136
137 pub fn prepare(&self, queue: &wgpu::Queue, screen: (u32, u32), instances: &[Instance]) -> u32 {
140 if self.failed.is_some() {
141 return 0;
142 }
143 let globals = Globals {
144 screen: [screen.0.max(1) as f32, screen.1.max(1) as f32],
145 _pad: [0.0; 2],
146 };
147 queue.write_buffer(&self.globals, 0, bytemuck::bytes_of(&globals));
148 let drawn = &instances[..instances.len().min(self.capacity)];
149 if !drawn.is_empty() {
150 queue.write_buffer(&self.instances, 0, bytemuck::cast_slice(drawn));
151 }
152 drawn.len() as u32
154 }
155
156 pub fn render(
158 &self,
159 encoder: &mut wgpu::CommandEncoder,
160 target: &wgpu::TextureView,
161 clear: Color,
162 count: u32,
163 ) {
164 let clear = wgpu::Color {
165 r: f64::from(clear.r),
166 g: f64::from(clear.g),
167 b: f64::from(clear.b),
168 a: f64::from(clear.a),
169 };
170 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
171 label: Some("densa instances"),
172 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
173 view: target,
174 depth_slice: None,
175 resolve_target: None,
176 ops: wgpu::Operations {
177 load: wgpu::LoadOp::Clear(clear),
178 store: wgpu::StoreOp::Store,
179 },
180 })],
181 depth_stencil_attachment: None,
182 timestamp_writes: None,
183 occlusion_query_set: None,
184 multiview_mask: None,
185 });
186 if count == 0 {
187 return;
188 }
189 pass.set_pipeline(&self.pipeline);
190 pass.set_bind_group(0, &self.bind_group, &[]);
191 pass.set_vertex_buffer(0, self.instances.slice(..));
192 pass.draw(0..4, 0..count);
193 }
194}
195
196fn bind_group(
197 device: &wgpu::Device,
198 layout: &wgpu::BindGroupLayout,
199 globals: &wgpu::Buffer,
200 atlas: &GpuAtlas,
201 sampler: &wgpu::Sampler,
202) -> wgpu::BindGroup {
203 let view = atlas.view();
204 device.create_bind_group(&wgpu::BindGroupDescriptor {
205 label: Some("densa bind group"),
206 layout,
207 entries: &[
208 wgpu::BindGroupEntry {
209 binding: 0,
210 resource: globals.as_entire_binding(),
211 },
212 wgpu::BindGroupEntry {
213 binding: 1,
214 resource: wgpu::BindingResource::TextureView(&view),
215 },
216 wgpu::BindGroupEntry {
217 binding: 2,
218 resource: wgpu::BindingResource::Sampler(sampler),
219 },
220 ],
221 })
222}
223
224fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
225 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
226 label: Some("densa bind group layout"),
227 entries: &[
228 wgpu::BindGroupLayoutEntry {
229 binding: 0,
230 visibility: wgpu::ShaderStages::VERTEX,
231 ty: wgpu::BindingType::Buffer {
232 ty: wgpu::BufferBindingType::Uniform,
233 has_dynamic_offset: false,
234 min_binding_size: None,
235 },
236 count: None,
237 },
238 wgpu::BindGroupLayoutEntry {
239 binding: 1,
240 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
242 ty: wgpu::BindingType::Texture {
243 sample_type: wgpu::TextureSampleType::Float { filterable: true },
244 view_dimension: wgpu::TextureViewDimension::D2,
245 multisampled: false,
246 },
247 count: None,
248 },
249 wgpu::BindGroupLayoutEntry {
250 binding: 2,
251 visibility: wgpu::ShaderStages::FRAGMENT,
252 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
253 count: None,
254 },
255 ],
256 })
257}
258
259fn pipeline(
260 device: &wgpu::Device,
261 layout: &wgpu::BindGroupLayout,
262 format: wgpu::TextureFormat,
263) -> wgpu::RenderPipeline {
264 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
265 label: Some("densa shader"),
266 source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
267 });
268 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
269 label: Some("densa pipeline layout"),
270 bind_group_layouts: &[Some(layout)],
271 immediate_size: 0,
272 });
273 const ATTRIBUTES: [wgpu::VertexAttribute; 3] =
274 wgpu::vertex_attr_array![0 => Float32x4, 1 => Float32x4, 2 => Float32x4];
275 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
276 label: Some("densa pipeline"),
277 layout: Some(&pipeline_layout),
278 vertex: wgpu::VertexState {
279 module: &shader,
280 entry_point: Some("vs_main"),
281 compilation_options: Default::default(),
282 buffers: &[Some(wgpu::VertexBufferLayout {
283 array_stride: size_of::<Instance>() as u64,
284 step_mode: wgpu::VertexStepMode::Instance,
285 attributes: &ATTRIBUTES,
286 })],
287 },
288 primitive: wgpu::PrimitiveState {
289 topology: wgpu::PrimitiveTopology::TriangleStrip,
290 ..Default::default()
291 },
292 depth_stencil: None,
293 multisample: wgpu::MultisampleState::default(),
294 fragment: Some(wgpu::FragmentState {
295 module: &shader,
296 entry_point: Some("fs_main"),
297 compilation_options: Default::default(),
298 targets: &[Some(wgpu::ColorTargetState {
299 format,
300 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
301 write_mask: wgpu::ColorWrites::ALL,
302 })],
303 }),
304 multiview_mask: None,
305 cache: None,
306 })
307}