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::TriangleList,
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::ADDITIVE),
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    fn make_surface_config(size: gpu::Extent) -> gpu::SurfaceConfig {
241        gpu::SurfaceConfig {
242            size,
243            usage: gpu::TextureUsage::TARGET,
244            frame_count: SURFACE_FRAME_COUNT,
245            //Note: this matches the original logic of the Metal backend,
246            // but ultimaterly we need to switch to `Linear`.
247            color_space: gpu::ColorSpace::Srgb,
248        }
249    }
250
251    pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
252        let surface_format = gpu.resize(Self::make_surface_config(size));
253        let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
254            name: "main",
255            buffer_count: 2,
256        });
257        let pipelines = BladePipelines::new(&gpu, surface_format);
258        let instance_belt = BladeBelt::new(BladeBeltDescriptor {
259            memory: gpu::Memory::Shared,
260            min_chunk_size: 0x1000,
261            alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
262        });
263        let atlas = Arc::new(BladeAtlas::new(&gpu));
264        let atlas_sampler = gpu.create_sampler(gpu::SamplerDesc {
265            name: "atlas",
266            mag_filter: gpu::FilterMode::Linear,
267            min_filter: gpu::FilterMode::Linear,
268            ..Default::default()
269        });
270
271        Self {
272            gpu,
273            command_encoder,
274            last_sync_point: None,
275            pipelines,
276            instance_belt,
277            viewport_size: size,
278            path_tiles: HashMap::default(),
279            atlas,
280            atlas_sampler,
281        }
282    }
283
284    fn wait_for_gpu(&mut self) {
285        if let Some(last_sp) = self.last_sync_point.take() {
286            if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
287                panic!("GPU hung");
288            }
289        }
290    }
291
292    pub fn destroy(&mut self) {
293        self.wait_for_gpu();
294        self.atlas.destroy();
295        self.instance_belt.destroy(&self.gpu);
296        self.gpu.destroy_command_encoder(&mut self.command_encoder);
297    }
298
299    pub fn resize(&mut self, size: gpu::Extent) {
300        self.wait_for_gpu();
301        self.gpu.resize(Self::make_surface_config(size));
302        self.viewport_size = size;
303    }
304
305    pub fn viewport_size(&self) -> gpu::Extent {
306        self.viewport_size
307    }
308
309    pub fn atlas(&self) -> &Arc<BladeAtlas> {
310        &self.atlas
311    }
312
313    fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
314        self.path_tiles.clear();
315        let mut vertices_by_texture_id = HashMap::default();
316
317        for path in paths {
318            let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
319            let tile = self
320                .atlas
321                .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
322            vertices_by_texture_id
323                .entry(tile.texture_id)
324                .or_insert(Vec::new())
325                .extend(path.vertices.iter().map(|vertex| PathVertex {
326                    xy_position: vertex.xy_position - clipped_bounds.origin
327                        + tile.bounds.origin.map(Into::into),
328                    st_position: vertex.st_position,
329                    content_mask: ContentMask {
330                        bounds: tile.bounds.map(Into::into),
331                    },
332                }));
333            self.path_tiles.insert(path.id, tile);
334        }
335
336        for (texture_id, vertices) in vertices_by_texture_id {
337            let tex_info = self.atlas.get_texture_info(texture_id);
338            let globals = GlobalParams {
339                viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
340                pad: [0; 2],
341            };
342
343            let vertex_buf = self.instance_belt.alloc_data(&vertices, &self.gpu);
344            let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
345                colors: &[gpu::RenderTarget {
346                    view: tex_info.raw_view,
347                    init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
348                    finish_op: gpu::FinishOp::Store,
349                }],
350                depth_stencil: None,
351            });
352
353            let mut encoder = pass.with(&self.pipelines.path_rasterization);
354            encoder.bind(
355                0,
356                &ShaderPathRasterizationData {
357                    globals,
358                    b_path_vertices: vertex_buf,
359                },
360            );
361            encoder.draw(0, vertices.len() as u32, 0, 1);
362        }
363    }
364
365    pub fn draw(&mut self, scene: &Scene) {
366        let frame = self.gpu.acquire_frame();
367        self.command_encoder.start();
368        self.command_encoder.init_texture(frame.texture());
369
370        self.atlas.before_frame(&mut self.command_encoder);
371        self.rasterize_paths(scene.paths());
372
373        let globals = GlobalParams {
374            viewport_size: [
375                self.viewport_size.width as f32,
376                self.viewport_size.height as f32,
377            ],
378            pad: [0; 2],
379        };
380
381        if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
382            colors: &[gpu::RenderTarget {
383                view: frame.texture_view(),
384                init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
385                finish_op: gpu::FinishOp::Store,
386            }],
387            depth_stencil: None,
388        }) {
389            for batch in scene.batches() {
390                match batch {
391                    PrimitiveBatch::Quads(quads) => {
392                        let instance_buf = self.instance_belt.alloc_data(quads, &self.gpu);
393                        let mut encoder = pass.with(&self.pipelines.quads);
394                        encoder.bind(
395                            0,
396                            &ShaderQuadsData {
397                                globals,
398                                b_quads: instance_buf,
399                            },
400                        );
401                        encoder.draw(0, 4, 0, quads.len() as u32);
402                    }
403                    PrimitiveBatch::Shadows(shadows) => {
404                        let instance_buf = self.instance_belt.alloc_data(shadows, &self.gpu);
405                        let mut encoder = pass.with(&self.pipelines.shadows);
406                        encoder.bind(
407                            0,
408                            &ShaderShadowsData {
409                                globals,
410                                b_shadows: instance_buf,
411                            },
412                        );
413                        encoder.draw(0, 4, 0, shadows.len() as u32);
414                    }
415                    PrimitiveBatch::Paths(paths) => {
416                        let mut encoder = pass.with(&self.pipelines.paths);
417                        //todo!(linux): group by texture ID
418                        for path in paths {
419                            let tile = &self.path_tiles[&path.id];
420                            let tex_info = self.atlas.get_texture_info(tile.texture_id);
421                            let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
422                            let sprites = [PathSprite {
423                                bounds: Bounds {
424                                    origin: origin.map(|p| p.floor()),
425                                    size: tile.bounds.size.map(Into::into),
426                                },
427                                color: path.color,
428                                tile: (*tile).clone(),
429                            }];
430
431                            let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
432                            encoder.bind(
433                                0,
434                                &ShaderPathsData {
435                                    globals,
436                                    t_sprite: tex_info.raw_view,
437                                    s_sprite: self.atlas_sampler,
438                                    b_path_sprites: instance_buf,
439                                },
440                            );
441                            encoder.draw(0, 4, 0, sprites.len() as u32);
442                        }
443                    }
444                    PrimitiveBatch::Underlines(underlines) => {
445                        let instance_buf = self.instance_belt.alloc_data(underlines, &self.gpu);
446                        let mut encoder = pass.with(&self.pipelines.underlines);
447                        encoder.bind(
448                            0,
449                            &ShaderUnderlinesData {
450                                globals,
451                                b_underlines: instance_buf,
452                            },
453                        );
454                        encoder.draw(0, 4, 0, underlines.len() as u32);
455                    }
456                    PrimitiveBatch::MonochromeSprites {
457                        texture_id,
458                        sprites,
459                    } => {
460                        let tex_info = self.atlas.get_texture_info(texture_id);
461                        let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
462                        let mut encoder = pass.with(&self.pipelines.mono_sprites);
463                        encoder.bind(
464                            0,
465                            &ShaderMonoSpritesData {
466                                globals,
467                                t_sprite: tex_info.raw_view,
468                                s_sprite: self.atlas_sampler,
469                                b_mono_sprites: instance_buf,
470                            },
471                        );
472                        encoder.draw(0, 4, 0, sprites.len() as u32);
473                    }
474                    PrimitiveBatch::PolychromeSprites {
475                        texture_id,
476                        sprites,
477                    } => {
478                        let tex_info = self.atlas.get_texture_info(texture_id);
479                        let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
480                        let mut encoder = pass.with(&self.pipelines.poly_sprites);
481                        encoder.bind(
482                            0,
483                            &ShaderPolySpritesData {
484                                globals,
485                                t_sprite: tex_info.raw_view,
486                                s_sprite: self.atlas_sampler,
487                                b_poly_sprites: instance_buf,
488                            },
489                        );
490                        encoder.draw(0, 4, 0, sprites.len() as u32);
491                    }
492                    PrimitiveBatch::Surfaces { .. } => {
493                        unimplemented!()
494                    }
495                }
496            }
497        }
498
499        self.command_encoder.present(frame);
500        let sync_point = self.gpu.submit(&mut self.command_encoder);
501
502        self.instance_belt.flush(&sync_point);
503        self.atlas.after_frame(&sync_point);
504        self.atlas.clear_textures(AtlasTextureKind::Path);
505
506        self.wait_for_gpu();
507        self.last_sync_point = Some(sync_point);
508    }
509}