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