ike/d2/
render.rs

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}