blade_renderer.rs

  1// Doing `if let` gives you nice scoping with passes/encoders
  2#![allow(irrefutable_let_patterns)]
  3
  4use super::{BladeBelt, BladeBeltDescriptor};
  5use crate::{
  6    AtlasTextureKind, AtlasTile, BladeAtlas, Bounds, ContentMask, Hsla, Path, PathId, PathVertex,
  7    PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, PATH_TEXTURE_FORMAT,
  8};
  9use bytemuck::{Pod, Zeroable};
 10use collections::HashMap;
 11
 12use blade_graphics as gpu;
 13use std::{mem, sync::Arc};
 14
 15const SURFACE_FRAME_COUNT: u32 = 3;
 16const MAX_FRAME_TIME_MS: u32 = 1000;
 17
 18#[repr(C)]
 19#[derive(Clone, Copy, Pod, Zeroable)]
 20struct GlobalParams {
 21    viewport_size: [f32; 2],
 22    pad: [u32; 2],
 23}
 24
 25#[derive(blade_macros::ShaderData)]
 26struct ShaderQuadsData {
 27    globals: GlobalParams,
 28    b_quads: gpu::BufferPiece,
 29}
 30
 31#[derive(blade_macros::ShaderData)]
 32struct ShaderShadowsData {
 33    globals: GlobalParams,
 34    b_shadows: gpu::BufferPiece,
 35}
 36
 37#[derive(blade_macros::ShaderData)]
 38struct ShaderPathRasterizationData {
 39    globals: GlobalParams,
 40    b_path_vertices: gpu::BufferPiece,
 41}
 42
 43#[derive(blade_macros::ShaderData)]
 44struct ShaderPathsData {
 45    globals: GlobalParams,
 46    t_tile: gpu::TextureView,
 47    s_tile: gpu::Sampler,
 48    b_path_sprites: gpu::BufferPiece,
 49}
 50
 51#[derive(Clone, Debug, Eq, PartialEq)]
 52#[repr(C)]
 53struct PathSprite {
 54    bounds: Bounds<ScaledPixels>,
 55    color: Hsla,
 56    tile: AtlasTile,
 57}
 58
 59struct BladePipelines {
 60    quads: gpu::RenderPipeline,
 61    shadows: gpu::RenderPipeline,
 62    path_rasterization: gpu::RenderPipeline,
 63    paths: gpu::RenderPipeline,
 64}
 65
 66impl BladePipelines {
 67    fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self {
 68        let shader = gpu.create_shader(gpu::ShaderDesc {
 69            source: include_str!("shaders.wgsl"),
 70        });
 71        shader.check_struct_size::<Quad>();
 72        shader.check_struct_size::<Shadow>();
 73        assert_eq!(
 74            mem::size_of::<PathVertex<ScaledPixels>>(),
 75            shader.get_struct_size("PathVertex") as usize,
 76        );
 77        shader.check_struct_size::<PathSprite>();
 78
 79        let quads_layout = <ShaderQuadsData as gpu::ShaderData>::layout();
 80        let shadows_layout = <ShaderShadowsData as gpu::ShaderData>::layout();
 81        let path_rasterization_layout = <ShaderPathRasterizationData as gpu::ShaderData>::layout();
 82        let paths_layout = <ShaderPathsData as gpu::ShaderData>::layout();
 83
 84        Self {
 85            quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
 86                name: "quads",
 87                data_layouts: &[&quads_layout],
 88                vertex: shader.at("vs_quad"),
 89                primitive: gpu::PrimitiveState {
 90                    topology: gpu::PrimitiveTopology::TriangleStrip,
 91                    ..Default::default()
 92                },
 93                depth_stencil: None,
 94                fragment: shader.at("fs_quad"),
 95                color_targets: &[gpu::ColorTargetState {
 96                    format: surface_format,
 97                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
 98                    write_mask: gpu::ColorWrites::default(),
 99                }],
100            }),
101            shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
102                name: "shadows",
103                data_layouts: &[&shadows_layout],
104                vertex: shader.at("vs_shadow"),
105                primitive: gpu::PrimitiveState {
106                    topology: gpu::PrimitiveTopology::TriangleStrip,
107                    ..Default::default()
108                },
109                depth_stencil: None,
110                fragment: shader.at("fs_shadow"),
111                color_targets: &[gpu::ColorTargetState {
112                    format: surface_format,
113                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
114                    write_mask: gpu::ColorWrites::default(),
115                }],
116            }),
117            path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
118                name: "path_rasterization",
119                data_layouts: &[&path_rasterization_layout],
120                vertex: shader.at("vs_path_rasterization"),
121                primitive: gpu::PrimitiveState {
122                    topology: gpu::PrimitiveTopology::TriangleStrip,
123                    ..Default::default()
124                },
125                depth_stencil: None,
126                fragment: shader.at("fs_path_rasterization"),
127                color_targets: &[gpu::ColorTargetState {
128                    format: PATH_TEXTURE_FORMAT,
129                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
130                    write_mask: gpu::ColorWrites::default(),
131                }],
132            }),
133            paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
134                name: "paths",
135                data_layouts: &[&paths_layout],
136                vertex: shader.at("vs_path"),
137                primitive: gpu::PrimitiveState {
138                    topology: gpu::PrimitiveTopology::TriangleStrip,
139                    ..Default::default()
140                },
141                depth_stencil: None,
142                fragment: shader.at("fs_path"),
143                color_targets: &[gpu::ColorTargetState {
144                    format: surface_format,
145                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
146                    write_mask: gpu::ColorWrites::default(),
147                }],
148            }),
149        }
150    }
151}
152
153pub struct BladeRenderer {
154    gpu: Arc<gpu::Context>,
155    command_encoder: gpu::CommandEncoder,
156    last_sync_point: Option<gpu::SyncPoint>,
157    pipelines: BladePipelines,
158    instance_belt: BladeBelt,
159    viewport_size: gpu::Extent,
160    path_tiles: HashMap<PathId, AtlasTile>,
161    atlas: Arc<BladeAtlas>,
162    atlas_sampler: gpu::Sampler,
163}
164
165impl BladeRenderer {
166    pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
167        let surface_format = gpu.resize(gpu::SurfaceConfig {
168            size,
169            usage: gpu::TextureUsage::TARGET,
170            frame_count: SURFACE_FRAME_COUNT,
171        });
172        let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
173            name: "main",
174            buffer_count: 2,
175        });
176        let pipelines = BladePipelines::new(&gpu, surface_format);
177        let instance_belt = BladeBelt::new(BladeBeltDescriptor {
178            memory: gpu::Memory::Shared,
179            min_chunk_size: 0x1000,
180        });
181        let atlas = Arc::new(BladeAtlas::new(&gpu));
182        let atlas_sampler = gpu.create_sampler(gpu::SamplerDesc {
183            name: "atlas",
184            mag_filter: gpu::FilterMode::Linear,
185            min_filter: gpu::FilterMode::Linear,
186            ..Default::default()
187        });
188
189        Self {
190            gpu,
191            command_encoder,
192            last_sync_point: None,
193            pipelines,
194            instance_belt,
195            viewport_size: size,
196            path_tiles: HashMap::default(),
197            atlas,
198            atlas_sampler,
199        }
200    }
201
202    fn wait_for_gpu(&mut self) {
203        if let Some(last_sp) = self.last_sync_point.take() {
204            if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
205                panic!("GPU hung");
206            }
207        }
208    }
209
210    pub fn destroy(&mut self) {
211        self.wait_for_gpu();
212        self.atlas.destroy();
213        self.instance_belt.destroy(&self.gpu);
214        self.gpu.destroy_command_encoder(&mut self.command_encoder);
215    }
216
217    pub fn resize(&mut self, size: gpu::Extent) {
218        self.wait_for_gpu();
219        self.gpu.resize(gpu::SurfaceConfig {
220            size,
221            usage: gpu::TextureUsage::TARGET,
222            frame_count: SURFACE_FRAME_COUNT,
223        });
224        self.viewport_size = size;
225    }
226
227    pub fn atlas(&self) -> &Arc<BladeAtlas> {
228        &self.atlas
229    }
230
231    fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
232        self.path_tiles.clear();
233        let mut vertices_by_texture_id = HashMap::default();
234
235        for path in paths {
236            let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
237            let tile = self
238                .atlas
239                .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
240            vertices_by_texture_id
241                .entry(tile.texture_id)
242                .or_insert(Vec::new())
243                .extend(path.vertices.iter().map(|vertex| PathVertex {
244                    xy_position: vertex.xy_position - clipped_bounds.origin
245                        + tile.bounds.origin.map(Into::into),
246                    st_position: vertex.st_position,
247                    content_mask: ContentMask {
248                        bounds: tile.bounds.map(Into::into),
249                    },
250                }));
251            self.path_tiles.insert(path.id, tile);
252        }
253
254        for (texture_id, vertices) in vertices_by_texture_id {
255            let tex_info = self.atlas.get_texture_info(texture_id);
256            let globals = GlobalParams {
257                viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
258                pad: [0; 2],
259            };
260
261            let vertex_buf = self.instance_belt.alloc_data(&vertices, &self.gpu);
262            let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
263                colors: &[gpu::RenderTarget {
264                    view: tex_info.raw_view.unwrap(),
265                    init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
266                    finish_op: gpu::FinishOp::Store,
267                }],
268                depth_stencil: None,
269            });
270
271            let mut encoder = pass.with(&self.pipelines.path_rasterization);
272            encoder.bind(
273                0,
274                &ShaderPathRasterizationData {
275                    globals,
276                    b_path_vertices: vertex_buf,
277                },
278            );
279            encoder.draw(0, vertices.len() as u32, 0, 1);
280        }
281    }
282
283    pub fn draw(&mut self, scene: &Scene) {
284        let frame = self.gpu.acquire_frame();
285        self.command_encoder.start();
286        self.command_encoder.init_texture(frame.texture());
287
288        self.atlas.before_frame(&mut self.command_encoder);
289        self.rasterize_paths(scene.paths());
290
291        let globals = GlobalParams {
292            viewport_size: [
293                self.viewport_size.width as f32,
294                self.viewport_size.height as f32,
295            ],
296            pad: [0; 2],
297        };
298
299        if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
300            colors: &[gpu::RenderTarget {
301                view: frame.texture_view(),
302                init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
303                finish_op: gpu::FinishOp::Store,
304            }],
305            depth_stencil: None,
306        }) {
307            for batch in scene.batches() {
308                match batch {
309                    PrimitiveBatch::Quads(quads) => {
310                        let instance_buf = self.instance_belt.alloc_data(quads, &self.gpu);
311                        let mut encoder = pass.with(&self.pipelines.quads);
312                        encoder.bind(
313                            0,
314                            &ShaderQuadsData {
315                                globals,
316                                b_quads: instance_buf,
317                            },
318                        );
319                        encoder.draw(0, 4, 0, quads.len() as u32);
320                    }
321                    PrimitiveBatch::Shadows(shadows) => {
322                        let instance_buf = self.instance_belt.alloc_data(shadows, &self.gpu);
323                        let mut encoder = pass.with(&self.pipelines.shadows);
324                        encoder.bind(
325                            0,
326                            &ShaderShadowsData {
327                                globals,
328                                b_shadows: instance_buf,
329                            },
330                        );
331                        encoder.draw(0, 4, 0, shadows.len() as u32);
332                    }
333                    PrimitiveBatch::Paths(paths) => {
334                        let mut encoder = pass.with(&self.pipelines.paths);
335                        //TODO: group by texture ID
336                        for path in paths {
337                            let tile = &self.path_tiles[&path.id];
338                            let tex_info = self.atlas.get_texture_info(tile.texture_id);
339                            let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
340                            let sprites = [PathSprite {
341                                bounds: Bounds {
342                                    origin: origin.map(|p| p.floor()),
343                                    size: tile.bounds.size.map(Into::into),
344                                },
345                                color: path.color,
346                                tile: (*tile).clone(),
347                            }];
348
349                            let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
350                            encoder.bind(
351                                0,
352                                &ShaderPathsData {
353                                    globals,
354                                    t_tile: tex_info.raw_view.unwrap(),
355                                    s_tile: self.atlas_sampler,
356                                    b_path_sprites: instance_buf,
357                                },
358                            );
359                            encoder.draw(0, 4, 0, sprites.len() as u32);
360                        }
361                    }
362                    _ => continue,
363                }
364            }
365        }
366
367        self.command_encoder.present(frame);
368        let sync_point = self.gpu.submit(&mut self.command_encoder);
369
370        self.instance_belt.flush(&sync_point);
371        self.atlas.after_frame(&sync_point);
372        self.atlas.clear_textures(AtlasTextureKind::Path);
373
374        self.wait_for_gpu();
375        self.last_sync_point = Some(sync_point);
376    }
377}