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, MonochromeSprite, Path,
  7    PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow,
  8    Underline, PATH_TEXTURE_FORMAT,
  9};
 10use bytemuck::{Pod, Zeroable};
 11use collections::HashMap;
 12
 13use blade_graphics as gpu;
 14use std::{mem, sync::Arc};
 15
 16const SURFACE_FRAME_COUNT: u32 = 3;
 17const MAX_FRAME_TIME_MS: u32 = 1000;
 18
 19#[repr(C)]
 20#[derive(Clone, Copy, Pod, Zeroable)]
 21struct GlobalParams {
 22    viewport_size: [f32; 2],
 23    pad: [u32; 2],
 24}
 25
 26#[derive(blade_macros::ShaderData)]
 27struct ShaderQuadsData {
 28    globals: GlobalParams,
 29    b_quads: gpu::BufferPiece,
 30}
 31
 32#[derive(blade_macros::ShaderData)]
 33struct ShaderShadowsData {
 34    globals: GlobalParams,
 35    b_shadows: gpu::BufferPiece,
 36}
 37
 38#[derive(blade_macros::ShaderData)]
 39struct ShaderPathRasterizationData {
 40    globals: GlobalParams,
 41    b_path_vertices: gpu::BufferPiece,
 42}
 43
 44#[derive(blade_macros::ShaderData)]
 45struct ShaderPathsData {
 46    globals: GlobalParams,
 47    t_sprite: gpu::TextureView,
 48    s_sprite: gpu::Sampler,
 49    b_path_sprites: gpu::BufferPiece,
 50}
 51
 52#[derive(blade_macros::ShaderData)]
 53struct ShaderUnderlinesData {
 54    globals: GlobalParams,
 55    b_underlines: gpu::BufferPiece,
 56}
 57
 58#[derive(blade_macros::ShaderData)]
 59struct ShaderMonoSpritesData {
 60    globals: GlobalParams,
 61    t_sprite: gpu::TextureView,
 62    s_sprite: gpu::Sampler,
 63    b_mono_sprites: gpu::BufferPiece,
 64}
 65
 66#[derive(blade_macros::ShaderData)]
 67struct ShaderPolySpritesData {
 68    globals: GlobalParams,
 69    t_sprite: gpu::TextureView,
 70    s_sprite: gpu::Sampler,
 71    b_poly_sprites: gpu::BufferPiece,
 72}
 73
 74#[derive(Clone, Debug, Eq, PartialEq)]
 75#[repr(C)]
 76struct PathSprite {
 77    bounds: Bounds<ScaledPixels>,
 78    color: Hsla,
 79    tile: AtlasTile,
 80}
 81
 82struct BladePipelines {
 83    quads: gpu::RenderPipeline,
 84    shadows: gpu::RenderPipeline,
 85    path_rasterization: gpu::RenderPipeline,
 86    paths: gpu::RenderPipeline,
 87    underlines: gpu::RenderPipeline,
 88    mono_sprites: gpu::RenderPipeline,
 89    poly_sprites: gpu::RenderPipeline,
 90}
 91
 92impl BladePipelines {
 93    fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self {
 94        use gpu::ShaderData as _;
 95
 96        let shader = gpu.create_shader(gpu::ShaderDesc {
 97            source: include_str!("shaders.wgsl"),
 98        });
 99        shader.check_struct_size::<Quad>();
100        shader.check_struct_size::<Shadow>();
101        assert_eq!(
102            mem::size_of::<PathVertex<ScaledPixels>>(),
103            shader.get_struct_size("PathVertex") as usize,
104        );
105        shader.check_struct_size::<PathSprite>();
106        shader.check_struct_size::<Underline>();
107        shader.check_struct_size::<MonochromeSprite>();
108        shader.check_struct_size::<PolychromeSprite>();
109
110        Self {
111            quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
112                name: "quads",
113                data_layouts: &[&ShaderQuadsData::layout()],
114                vertex: shader.at("vs_quad"),
115                primitive: gpu::PrimitiveState {
116                    topology: gpu::PrimitiveTopology::TriangleStrip,
117                    ..Default::default()
118                },
119                depth_stencil: None,
120                fragment: shader.at("fs_quad"),
121                color_targets: &[gpu::ColorTargetState {
122                    format: surface_format,
123                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
124                    write_mask: gpu::ColorWrites::default(),
125                }],
126            }),
127            shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
128                name: "shadows",
129                data_layouts: &[&ShaderShadowsData::layout()],
130                vertex: shader.at("vs_shadow"),
131                primitive: gpu::PrimitiveState {
132                    topology: gpu::PrimitiveTopology::TriangleStrip,
133                    ..Default::default()
134                },
135                depth_stencil: None,
136                fragment: shader.at("fs_shadow"),
137                color_targets: &[gpu::ColorTargetState {
138                    format: surface_format,
139                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
140                    write_mask: gpu::ColorWrites::default(),
141                }],
142            }),
143            path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
144                name: "path_rasterization",
145                data_layouts: &[&ShaderPathRasterizationData::layout()],
146                vertex: shader.at("vs_path_rasterization"),
147                primitive: gpu::PrimitiveState {
148                    topology: gpu::PrimitiveTopology::TriangleStrip,
149                    ..Default::default()
150                },
151                depth_stencil: None,
152                fragment: shader.at("fs_path_rasterization"),
153                color_targets: &[gpu::ColorTargetState {
154                    format: PATH_TEXTURE_FORMAT,
155                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
156                    write_mask: gpu::ColorWrites::default(),
157                }],
158            }),
159            paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
160                name: "paths",
161                data_layouts: &[&ShaderPathsData::layout()],
162                vertex: shader.at("vs_path"),
163                primitive: gpu::PrimitiveState {
164                    topology: gpu::PrimitiveTopology::TriangleStrip,
165                    ..Default::default()
166                },
167                depth_stencil: None,
168                fragment: shader.at("fs_path"),
169                color_targets: &[gpu::ColorTargetState {
170                    format: surface_format,
171                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
172                    write_mask: gpu::ColorWrites::default(),
173                }],
174            }),
175            underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
176                name: "underlines",
177                data_layouts: &[&ShaderUnderlinesData::layout()],
178                vertex: shader.at("vs_underline"),
179                primitive: gpu::PrimitiveState {
180                    topology: gpu::PrimitiveTopology::TriangleStrip,
181                    ..Default::default()
182                },
183                depth_stencil: None,
184                fragment: shader.at("fs_underline"),
185                color_targets: &[gpu::ColorTargetState {
186                    format: surface_format,
187                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
188                    write_mask: gpu::ColorWrites::default(),
189                }],
190            }),
191            mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
192                name: "mono-sprites",
193                data_layouts: &[&ShaderMonoSpritesData::layout()],
194                vertex: shader.at("vs_mono_sprite"),
195                primitive: gpu::PrimitiveState {
196                    topology: gpu::PrimitiveTopology::TriangleStrip,
197                    ..Default::default()
198                },
199                depth_stencil: None,
200                fragment: shader.at("fs_mono_sprite"),
201                color_targets: &[gpu::ColorTargetState {
202                    format: surface_format,
203                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
204                    write_mask: gpu::ColorWrites::default(),
205                }],
206            }),
207            poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
208                name: "poly-sprites",
209                data_layouts: &[&ShaderPolySpritesData::layout()],
210                vertex: shader.at("vs_poly_sprite"),
211                primitive: gpu::PrimitiveState {
212                    topology: gpu::PrimitiveTopology::TriangleStrip,
213                    ..Default::default()
214                },
215                depth_stencil: None,
216                fragment: shader.at("fs_poly_sprite"),
217                color_targets: &[gpu::ColorTargetState {
218                    format: surface_format,
219                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
220                    write_mask: gpu::ColorWrites::default(),
221                }],
222            }),
223        }
224    }
225}
226
227pub struct BladeRenderer {
228    gpu: Arc<gpu::Context>,
229    command_encoder: gpu::CommandEncoder,
230    last_sync_point: Option<gpu::SyncPoint>,
231    pipelines: BladePipelines,
232    instance_belt: BladeBelt,
233    viewport_size: gpu::Extent,
234    path_tiles: HashMap<PathId, AtlasTile>,
235    atlas: Arc<BladeAtlas>,
236    atlas_sampler: gpu::Sampler,
237}
238
239impl BladeRenderer {
240    pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
241        let surface_format = gpu.resize(gpu::SurfaceConfig {
242            size,
243            usage: gpu::TextureUsage::TARGET,
244            frame_count: SURFACE_FRAME_COUNT,
245        });
246        let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
247            name: "main",
248            buffer_count: 2,
249        });
250        let pipelines = BladePipelines::new(&gpu, surface_format);
251        let instance_belt = BladeBelt::new(BladeBeltDescriptor {
252            memory: gpu::Memory::Shared,
253            min_chunk_size: 0x1000,
254            alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
255        });
256        let atlas = Arc::new(BladeAtlas::new(&gpu));
257        let atlas_sampler = gpu.create_sampler(gpu::SamplerDesc {
258            name: "atlas",
259            mag_filter: gpu::FilterMode::Linear,
260            min_filter: gpu::FilterMode::Linear,
261            ..Default::default()
262        });
263
264        Self {
265            gpu,
266            command_encoder,
267            last_sync_point: None,
268            pipelines,
269            instance_belt,
270            viewport_size: size,
271            path_tiles: HashMap::default(),
272            atlas,
273            atlas_sampler,
274        }
275    }
276
277    fn wait_for_gpu(&mut self) {
278        if let Some(last_sp) = self.last_sync_point.take() {
279            if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
280                panic!("GPU hung");
281            }
282        }
283    }
284
285    pub fn destroy(&mut self) {
286        self.wait_for_gpu();
287        self.atlas.destroy();
288        self.instance_belt.destroy(&self.gpu);
289        self.gpu.destroy_command_encoder(&mut self.command_encoder);
290    }
291
292    pub fn resize(&mut self, size: gpu::Extent) {
293        self.wait_for_gpu();
294        self.gpu.resize(gpu::SurfaceConfig {
295            size,
296            usage: gpu::TextureUsage::TARGET,
297            frame_count: SURFACE_FRAME_COUNT,
298        });
299        self.viewport_size = size;
300    }
301
302    pub fn atlas(&self) -> &Arc<BladeAtlas> {
303        &self.atlas
304    }
305
306    fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
307        self.path_tiles.clear();
308        let mut vertices_by_texture_id = HashMap::default();
309
310        for path in paths {
311            let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
312            let tile = self
313                .atlas
314                .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
315            vertices_by_texture_id
316                .entry(tile.texture_id)
317                .or_insert(Vec::new())
318                .extend(path.vertices.iter().map(|vertex| PathVertex {
319                    xy_position: vertex.xy_position - clipped_bounds.origin
320                        + tile.bounds.origin.map(Into::into),
321                    st_position: vertex.st_position,
322                    content_mask: ContentMask {
323                        bounds: tile.bounds.map(Into::into),
324                    },
325                }));
326            self.path_tiles.insert(path.id, tile);
327        }
328
329        for (texture_id, vertices) in vertices_by_texture_id {
330            let tex_info = self.atlas.get_texture_info(texture_id);
331            let globals = GlobalParams {
332                viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
333                pad: [0; 2],
334            };
335
336            let vertex_buf = self.instance_belt.alloc_data(&vertices, &self.gpu);
337            let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
338                colors: &[gpu::RenderTarget {
339                    view: tex_info.raw_view,
340                    init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
341                    finish_op: gpu::FinishOp::Store,
342                }],
343                depth_stencil: None,
344            });
345
346            let mut encoder = pass.with(&self.pipelines.path_rasterization);
347            encoder.bind(
348                0,
349                &ShaderPathRasterizationData {
350                    globals,
351                    b_path_vertices: vertex_buf,
352                },
353            );
354            encoder.draw(0, vertices.len() as u32, 0, 1);
355        }
356    }
357
358    pub fn draw(&mut self, scene: &Scene) {
359        let frame = self.gpu.acquire_frame();
360        self.command_encoder.start();
361        self.command_encoder.init_texture(frame.texture());
362
363        self.atlas.before_frame(&mut self.command_encoder);
364        self.rasterize_paths(scene.paths());
365
366        let globals = GlobalParams {
367            viewport_size: [
368                self.viewport_size.width as f32,
369                self.viewport_size.height as f32,
370            ],
371            pad: [0; 2],
372        };
373
374        if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
375            colors: &[gpu::RenderTarget {
376                view: frame.texture_view(),
377                init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
378                finish_op: gpu::FinishOp::Store,
379            }],
380            depth_stencil: None,
381        }) {
382            for batch in scene.batches() {
383                match batch {
384                    PrimitiveBatch::Quads(quads) => {
385                        let instance_buf = self.instance_belt.alloc_data(quads, &self.gpu);
386                        let mut encoder = pass.with(&self.pipelines.quads);
387                        encoder.bind(
388                            0,
389                            &ShaderQuadsData {
390                                globals,
391                                b_quads: instance_buf,
392                            },
393                        );
394                        encoder.draw(0, 4, 0, quads.len() as u32);
395                    }
396                    PrimitiveBatch::Shadows(shadows) => {
397                        let instance_buf = self.instance_belt.alloc_data(shadows, &self.gpu);
398                        let mut encoder = pass.with(&self.pipelines.shadows);
399                        encoder.bind(
400                            0,
401                            &ShaderShadowsData {
402                                globals,
403                                b_shadows: instance_buf,
404                            },
405                        );
406                        encoder.draw(0, 4, 0, shadows.len() as u32);
407                    }
408                    PrimitiveBatch::Paths(paths) => {
409                        let mut encoder = pass.with(&self.pipelines.paths);
410                        //TODO: group by texture ID
411                        for path in paths {
412                            let tile = &self.path_tiles[&path.id];
413                            let tex_info = self.atlas.get_texture_info(tile.texture_id);
414                            let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
415                            let sprites = [PathSprite {
416                                bounds: Bounds {
417                                    origin: origin.map(|p| p.floor()),
418                                    size: tile.bounds.size.map(Into::into),
419                                },
420                                color: path.color,
421                                tile: (*tile).clone(),
422                            }];
423
424                            let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
425                            encoder.bind(
426                                0,
427                                &ShaderPathsData {
428                                    globals,
429                                    t_sprite: tex_info.raw_view,
430                                    s_sprite: self.atlas_sampler,
431                                    b_path_sprites: instance_buf,
432                                },
433                            );
434                            encoder.draw(0, 4, 0, sprites.len() as u32);
435                        }
436                    }
437                    PrimitiveBatch::Underlines(underlines) => {
438                        let instance_buf = self.instance_belt.alloc_data(underlines, &self.gpu);
439                        let mut encoder = pass.with(&self.pipelines.underlines);
440                        encoder.bind(
441                            0,
442                            &ShaderUnderlinesData {
443                                globals,
444                                b_underlines: instance_buf,
445                            },
446                        );
447                        encoder.draw(0, 4, 0, underlines.len() as u32);
448                    }
449                    PrimitiveBatch::MonochromeSprites {
450                        texture_id,
451                        sprites,
452                    } => {
453                        let tex_info = self.atlas.get_texture_info(texture_id);
454                        let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
455                        let mut encoder = pass.with(&self.pipelines.mono_sprites);
456                        encoder.bind(
457                            0,
458                            &ShaderMonoSpritesData {
459                                globals,
460                                t_sprite: tex_info.raw_view,
461                                s_sprite: self.atlas_sampler,
462                                b_mono_sprites: instance_buf,
463                            },
464                        );
465                        encoder.draw(0, 4, 0, sprites.len() as u32);
466                    }
467                    PrimitiveBatch::PolychromeSprites {
468                        texture_id,
469                        sprites,
470                    } => {
471                        let tex_info = self.atlas.get_texture_info(texture_id);
472                        let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
473                        let mut encoder = pass.with(&self.pipelines.poly_sprites);
474                        encoder.bind(
475                            0,
476                            &ShaderPolySpritesData {
477                                globals,
478                                t_sprite: tex_info.raw_view,
479                                s_sprite: self.atlas_sampler,
480                                b_poly_sprites: instance_buf,
481                            },
482                        );
483                        encoder.draw(0, 4, 0, sprites.len() as u32);
484                    }
485                    PrimitiveBatch::Surfaces { .. } => {
486                        unimplemented!()
487                    }
488                }
489            }
490        }
491
492        self.command_encoder.present(frame);
493        let sync_point = self.gpu.submit(&mut self.command_encoder);
494
495        self.instance_belt.flush(&sync_point);
496        self.atlas.after_frame(&sync_point);
497        self.atlas.clear_textures(AtlasTextureKind::Path);
498
499        self.wait_for_gpu();
500        self.last_sync_point = Some(sync_point);
501    }
502}