1use super::{
2 sprite::{Sprite, Sprites},
3 transform2d::Transform2d,
4};
5use crate::{
6 color::Color,
7 id::Id,
8 renderer::{RenderCtx, RenderNode},
9 texture::Texture,
10 view::View,
11};
12use bytemuck::{cast_slice, Pod, Zeroable};
13use glam::Vec2;
14use std::collections::HashMap;
15use wgpu::util::DeviceExt;
16
17fn crate_pipeline(
18 ctx: &RenderCtx,
19 format: wgpu::TextureFormat,
20 sample_count: u32,
21) -> wgpu::RenderPipeline {
22 let shader_module = ctx
23 .device
24 .create_shader_module(&wgpu::include_wgsl!("shader.wgsl"));
25
26 let bind_group_layout = ctx
27 .device
28 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
29 label: Some("2d_bind_group_layout"),
30 entries: &[
31 wgpu::BindGroupLayoutEntry {
32 binding: 0,
33 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
34 ty: wgpu::BindingType::Texture {
35 sample_type: wgpu::TextureSampleType::Float { filterable: true },
36 view_dimension: wgpu::TextureViewDimension::D2,
37 multisampled: false,
38 },
39 count: None,
40 },
41 wgpu::BindGroupLayoutEntry {
42 binding: 1,
43 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
44 ty: wgpu::BindingType::Sampler {
45 filtering: true,
46 comparison: false,
47 },
48 count: None,
49 },
50 ],
51 });
52
53 let layout = ctx
54 .device
55 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
56 label: Some("2d_pipeline_layout"),
57 bind_group_layouts: &[&bind_group_layout],
58 push_constant_ranges: &[],
59 });
60
61 let pipeline = ctx
62 .device
63 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
64 label: Some("2d_pipeline"),
65 layout: Some(&layout),
66 vertex: wgpu::VertexState {
67 module: &shader_module,
68 buffers: &[wgpu::VertexBufferLayout {
69 array_stride: 36,
70 step_mode: wgpu::VertexStepMode::Vertex,
71 attributes: &[
72 wgpu::VertexAttribute {
73 format: wgpu::VertexFormat::Float32x3,
74 offset: 0,
75 shader_location: 0,
76 },
77 wgpu::VertexAttribute {
78 format: wgpu::VertexFormat::Float32x2,
79 offset: 12,
80 shader_location: 1,
81 },
82 wgpu::VertexAttribute {
83 format: wgpu::VertexFormat::Float32x4,
84 offset: 20,
85 shader_location: 2,
86 },
87 ],
88 }],
89 entry_point: "main",
90 },
91 fragment: Some(wgpu::FragmentState {
92 module: &shader_module,
93 targets: &[wgpu::ColorTargetState {
94 format,
95 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
96 write_mask: wgpu::ColorWrites::ALL,
97 }],
98 entry_point: "main",
99 }),
100 primitive: Default::default(),
101 multisample: wgpu::MultisampleState {
102 count: sample_count,
103 ..Default::default()
104 },
105 depth_stencil: Some(wgpu::DepthStencilState {
106 format: wgpu::TextureFormat::Depth24Plus,
107 depth_write_enabled: true,
108 depth_compare: wgpu::CompareFunction::LessEqual,
109 stencil: Default::default(),
110 bias: Default::default(),
111 }),
112 });
113
114 pipeline
115}
116
117pub struct Render2dCtx<'a> {
118 pub sprites: &'a mut Sprites,
119 pub render_ctx: &'a RenderCtx,
120}
121
122impl<'a> Render2dCtx<'a> {
123 #[inline]
124 pub fn draw_sprite(&mut self, sprite: Sprite) {
125 self.sprites.draw(sprite);
126 }
127
128 #[inline]
129 pub fn draw_texture(&mut self, texture: &mut Texture, transform: &Transform2d) {
130 let view = texture
131 .texture(self.render_ctx)
132 .create_view(&Default::default());
133
134 let sprite = Sprite {
135 transform: transform.matrix(),
136 width: texture.width,
137 height: texture.height,
138 depth: 0.0,
139 min: Vec2::ZERO,
140 max: Vec2::ONE,
141 texture_id: texture.id,
142 view,
143 };
144
145 self.sprites.draw(sprite)
146 }
147
148 #[inline]
149 pub fn draw_texture_offset(
150 &mut self,
151 texture: &mut Texture,
152 transform: &Transform2d,
153 offset: Vec2,
154 ) {
155 let mut transform = transform.clone();
156 transform.translation += offset;
157
158 self.draw_texture(texture, &transform);
159 }
160}
161
162#[repr(C)]
163#[derive(Clone, Copy, Pod, Zeroable)]
164struct Vertex2d {
165 position: [f32; 3],
166 uv: [f32; 2],
167 color: [f32; 4],
168}
169
170pub trait Render2d {
171 fn render(&mut self, ctx: &mut Render2dCtx);
172}
173
174pub struct Node2d {
175 clear_color: Color,
176 sample_count: u32,
177 width: u32,
178 height: u32,
179 depth_texture: Option<wgpu::TextureView>,
180 ms_texture: Option<wgpu::TextureView>,
181 bind_groups: HashMap<Id, wgpu::BindGroup>,
182 pipelines: HashMap<wgpu::TextureFormat, wgpu::RenderPipeline>,
183}
184
185impl Default for Node2d {
186 #[inline]
187 fn default() -> Self {
188 Self {
189 clear_color: Color::BLACK,
190 sample_count: 1,
191 width: 0,
192 height: 0,
193 depth_texture: None,
194 ms_texture: None,
195 bind_groups: Default::default(),
196 pipelines: Default::default(),
197 }
198 }
199}
200
201impl Node2d {
202 #[inline]
203 pub fn new(clear_color: Color, sample_count: u32) -> Self {
204 Self {
205 clear_color,
206 sample_count,
207 ..Default::default()
208 }
209 }
210}
211
212impl<S: Render2d> RenderNode<S> for Node2d {
213 #[inline]
214 fn run(&mut self, ctx: &RenderCtx, view: &View, state: &mut S) {
215 let depth = if let Some(ref mut depth) = self.depth_texture {
216 if self.width != view.width || self.height != view.height {
217 let texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
218 label: Some("2d_pass_depth"),
219 size: wgpu::Extent3d {
220 width: view.width,
221 height: view.height,
222 depth_or_array_layers: 1,
223 },
224 mip_level_count: 1,
225 sample_count: self.sample_count,
226 dimension: wgpu::TextureDimension::D2,
227 format: wgpu::TextureFormat::Depth24Plus,
228 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
229 });
230
231 let texture_view = texture.create_view(&Default::default());
232
233 if self.sample_count > 1 {
234 let texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
235 label: Some("2d_pass_ms"),
236 size: wgpu::Extent3d {
237 width: view.width,
238 height: view.height,
239 depth_or_array_layers: 1,
240 },
241 mip_level_count: 1,
242 sample_count: self.sample_count,
243 dimension: wgpu::TextureDimension::D2,
244 format: view.format,
245 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
246 });
247
248 let texture_view = texture.create_view(&Default::default());
249
250 self.ms_texture = Some(texture_view);
251 }
252
253 self.width = view.width;
254 self.height = view.height;
255
256 *depth = texture_view;
257 }
258
259 depth
260 } else {
261 let texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
262 label: Some("2d_pass_depth"),
263 size: wgpu::Extent3d {
264 width: view.width,
265 height: view.height,
266 depth_or_array_layers: 1,
267 },
268 mip_level_count: 1,
269 sample_count: self.sample_count,
270 dimension: wgpu::TextureDimension::D2,
271 format: wgpu::TextureFormat::Depth24Plus,
272 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
273 });
274
275 let texture_view = texture.create_view(&Default::default());
276
277 self.depth_texture = Some(texture_view);
278
279 if self.sample_count > 1 {
280 let texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
281 label: Some("2d_pass_ms"),
282 size: wgpu::Extent3d {
283 width: view.width,
284 height: view.height,
285 depth_or_array_layers: 1,
286 },
287 mip_level_count: 1,
288 sample_count: self.sample_count,
289 dimension: wgpu::TextureDimension::D2,
290 format: view.format,
291 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
292 });
293
294 let texture_view = texture.create_view(&Default::default());
295
296 self.ms_texture = Some(texture_view);
297 }
298
299 self.width = view.width;
300 self.height = view.height;
301
302 self.depth_texture.as_mut().unwrap()
303 };
304
305 let sample_count = self.sample_count;
306 let pipeline = self
307 .pipelines
308 .entry(view.format)
309 .or_insert_with(|| crate_pipeline(ctx, view.format, sample_count));
310
311 let mut sprites = Sprites::default();
312
313 let mut render_ctx = Render2dCtx {
314 sprites: &mut sprites,
315 render_ctx: ctx,
316 };
317
318 state.render(&mut render_ctx);
319
320 let mut encoder = ctx
321 .device
322 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
323 label: Some("2d_pass_encoder"),
324 });
325
326 let mut buffers = Vec::new();
327
328 let color_attachment = if self.sample_count > 1 {
329 wgpu::RenderPassColorAttachment {
330 view: self.ms_texture.as_ref().unwrap(),
331 resolve_target: Some(&view.target),
332 ops: wgpu::Operations {
333 load: wgpu::LoadOp::Clear(self.clear_color.into()),
334 store: true,
335 },
336 }
337 } else {
338 wgpu::RenderPassColorAttachment {
339 view: &view.target,
340 resolve_target: None,
341 ops: wgpu::Operations {
342 load: wgpu::LoadOp::Clear(self.clear_color.into()),
343 store: true,
344 },
345 }
346 };
347
348 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
349 label: Some("2d_pass"),
350 color_attachments: &[color_attachment],
351 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
352 view: depth,
353 depth_ops: Some(wgpu::Operations {
354 load: wgpu::LoadOp::Clear(1.0),
355 store: true,
356 }),
357 stencil_ops: None,
358 }),
359 });
360
361 render_pass.set_pipeline(pipeline);
362
363 for (id, sprites) in &sprites.batches {
364 let mut vertices = Vec::with_capacity(sprites.len());
365
366 for sprite in sprites {
367 let w = sprite.width as f32 / 2.0;
368 let h = sprite.height as f32 / 2.0;
369
370 let bl = Vec2::new(-w, -h);
371 let tl = Vec2::new(-w, h);
372 let br = Vec2::new(w, -h);
373 let tr = Vec2::new(w, h);
374
375 let bl = sprite.transform.transform_point2(bl);
376 let tl = sprite.transform.transform_point2(tl);
377 let br = sprite.transform.transform_point2(br);
378 let tr = sprite.transform.transform_point2(tr);
379
380 let bl = view.view_proj.transform_point3(bl.extend(sprite.depth));
381 let tl = view.view_proj.transform_point3(tl.extend(sprite.depth));
382 let br = view.view_proj.transform_point3(br.extend(sprite.depth));
383 let tr = view.view_proj.transform_point3(tr.extend(sprite.depth));
384
385 vertices.push(Vertex2d {
386 position: bl.into(),
387 uv: [sprite.min.x, sprite.max.y],
388 color: [1.0; 4],
389 });
390 vertices.push(Vertex2d {
391 position: tl.into(),
392 uv: [sprite.min.x, sprite.min.y],
393 color: [1.0; 4],
394 });
395 vertices.push(Vertex2d {
396 position: br.into(),
397 uv: [sprite.max.x, sprite.max.y],
398 color: [1.0; 4],
399 });
400 vertices.push(Vertex2d {
401 position: tl.into(),
402 uv: [sprite.min.x, sprite.min.y],
403 color: [1.0; 4],
404 });
405 vertices.push(Vertex2d {
406 position: br.into(),
407 uv: [sprite.max.x, sprite.max.y],
408 color: [1.0; 4],
409 });
410 vertices.push(Vertex2d {
411 position: tr.into(),
412 uv: [sprite.max.x, sprite.min.y],
413 color: [1.0; 4],
414 });
415 }
416
417 let vertex_buffer = ctx
418 .device
419 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
420 label: Some("sprite_batch_vertex"),
421 contents: cast_slice(&vertices),
422 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::VERTEX,
423 });
424
425 buffers.push(vertex_buffer);
426
427 if !self.bind_groups.contains_key(id) {
428 let sampler = ctx
429 .device
430 .create_sampler(&wgpu::SamplerDescriptor::default());
431
432 let layout =
433 ctx.device
434 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
435 label: Some("2d_pass_layout"),
436 entries: &[
437 wgpu::BindGroupLayoutEntry {
438 binding: 0,
439 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
440 ty: wgpu::BindingType::Texture {
441 sample_type: wgpu::TextureSampleType::Float {
442 filterable: true,
443 },
444 view_dimension: wgpu::TextureViewDimension::D2,
445 multisampled: false,
446 },
447 count: None,
448 },
449 wgpu::BindGroupLayoutEntry {
450 binding: 1,
451 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
452 ty: wgpu::BindingType::Sampler {
453 filtering: true,
454 comparison: false,
455 },
456 count: None,
457 },
458 ],
459 });
460
461 let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
462 label: Some("2d_pass_bind_group"),
463 layout: &layout,
464 entries: &[
465 wgpu::BindGroupEntry {
466 binding: 0,
467 resource: wgpu::BindingResource::TextureView(
468 &sprites.first().unwrap().view,
469 ),
470 },
471 wgpu::BindGroupEntry {
472 binding: 1,
473 resource: wgpu::BindingResource::Sampler(&sampler),
474 },
475 ],
476 });
477
478 self.bind_groups.insert(*id, bind_group);
479 }
480 }
481
482 let mut buffers = buffers.iter();
483
484 for (id, sprites) in sprites.batches {
485 render_pass.set_bind_group(0, self.bind_groups.get(&id).unwrap(), &[]);
486 render_pass.set_vertex_buffer(0, buffers.next().unwrap().slice(..));
487
488 render_pass.draw(0..sprites.len() as u32 * 6, 0..1);
489 }
490
491 drop(render_pass);
492
493 ctx.queue.submit(std::iter::once(encoder.finish()));
494 }
495}