blade_renderer.rs

  1// Doing `if let` gives you nice scoping with passes/encoders
  2#![allow(irrefutable_let_patterns)]
  3
  4use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT};
  5use crate::{
  6    AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs,
  7    MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
  8    ScaledPixels, Scene, Shadow, Size, Underline,
  9};
 10use blade_graphics as gpu;
 11use blade_util::{BufferBelt, BufferBeltDescriptor};
 12use bytemuck::{Pod, Zeroable};
 13use collections::HashMap;
 14#[cfg(target_os = "macos")]
 15use media::core_video::CVMetalTextureCache;
 16use std::{mem, sync::Arc};
 17
 18const MAX_FRAME_TIME_MS: u32 = 10000;
 19// Use 4x MSAA, all devices support it.
 20// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
 21const PATH_SAMPLE_COUNT: u32 = 4;
 22
 23#[repr(C)]
 24#[derive(Clone, Copy, Pod, Zeroable)]
 25struct GlobalParams {
 26    viewport_size: [f32; 2],
 27    premultiplied_alpha: u32,
 28    pad: u32,
 29}
 30
 31//Note: we can't use `Bounds` directly here because
 32// it doesn't implement Pod + Zeroable
 33#[repr(C)]
 34#[derive(Clone, Copy, Pod, Zeroable)]
 35struct PodBounds {
 36    origin: [f32; 2],
 37    size: [f32; 2],
 38}
 39
 40impl From<Bounds<ScaledPixels>> for PodBounds {
 41    fn from(bounds: Bounds<ScaledPixels>) -> Self {
 42        Self {
 43            origin: [bounds.origin.x.0, bounds.origin.y.0],
 44            size: [bounds.size.width.0, bounds.size.height.0],
 45        }
 46    }
 47}
 48
 49#[repr(C)]
 50#[derive(Clone, Copy, Pod, Zeroable)]
 51struct SurfaceParams {
 52    bounds: PodBounds,
 53    content_mask: PodBounds,
 54}
 55
 56#[derive(blade_macros::ShaderData)]
 57struct ShaderQuadsData {
 58    globals: GlobalParams,
 59    b_quads: gpu::BufferPiece,
 60}
 61
 62#[derive(blade_macros::ShaderData)]
 63struct ShaderShadowsData {
 64    globals: GlobalParams,
 65    b_shadows: gpu::BufferPiece,
 66}
 67
 68#[derive(blade_macros::ShaderData)]
 69struct ShaderPathRasterizationData {
 70    globals: GlobalParams,
 71    b_path_vertices: gpu::BufferPiece,
 72}
 73
 74#[derive(blade_macros::ShaderData)]
 75struct ShaderPathsData {
 76    globals: GlobalParams,
 77    t_sprite: gpu::TextureView,
 78    s_sprite: gpu::Sampler,
 79    b_path_sprites: gpu::BufferPiece,
 80}
 81
 82#[derive(blade_macros::ShaderData)]
 83struct ShaderUnderlinesData {
 84    globals: GlobalParams,
 85    b_underlines: gpu::BufferPiece,
 86}
 87
 88#[derive(blade_macros::ShaderData)]
 89struct ShaderMonoSpritesData {
 90    globals: GlobalParams,
 91    t_sprite: gpu::TextureView,
 92    s_sprite: gpu::Sampler,
 93    b_mono_sprites: gpu::BufferPiece,
 94}
 95
 96#[derive(blade_macros::ShaderData)]
 97struct ShaderPolySpritesData {
 98    globals: GlobalParams,
 99    t_sprite: gpu::TextureView,
100    s_sprite: gpu::Sampler,
101    b_poly_sprites: gpu::BufferPiece,
102}
103
104#[derive(blade_macros::ShaderData)]
105struct ShaderSurfacesData {
106    globals: GlobalParams,
107    surface_locals: SurfaceParams,
108    t_y: gpu::TextureView,
109    t_cb_cr: gpu::TextureView,
110    s_surface: gpu::Sampler,
111}
112
113#[derive(Clone, Debug, Eq, PartialEq)]
114#[repr(C)]
115struct PathSprite {
116    bounds: Bounds<ScaledPixels>,
117    color: Background,
118    tile: AtlasTile,
119}
120
121struct BladePipelines {
122    quads: gpu::RenderPipeline,
123    shadows: gpu::RenderPipeline,
124    path_rasterization: gpu::RenderPipeline,
125    paths: gpu::RenderPipeline,
126    underlines: gpu::RenderPipeline,
127    mono_sprites: gpu::RenderPipeline,
128    poly_sprites: gpu::RenderPipeline,
129    surfaces: gpu::RenderPipeline,
130}
131
132impl BladePipelines {
133    fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo) -> Self {
134        use gpu::ShaderData as _;
135
136        log::info!(
137            "Initializing Blade pipelines for surface {:?}",
138            surface_info
139        );
140        let shader = gpu.create_shader(gpu::ShaderDesc {
141            source: include_str!("shaders.wgsl"),
142        });
143        shader.check_struct_size::<GlobalParams>();
144        shader.check_struct_size::<SurfaceParams>();
145        shader.check_struct_size::<Quad>();
146        shader.check_struct_size::<Shadow>();
147        assert_eq!(
148            mem::size_of::<PathVertex<ScaledPixels>>(),
149            shader.get_struct_size("PathVertex") as usize,
150        );
151        shader.check_struct_size::<PathSprite>();
152        shader.check_struct_size::<Underline>();
153        shader.check_struct_size::<MonochromeSprite>();
154        shader.check_struct_size::<PolychromeSprite>();
155
156        // See https://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha/
157        let blend_mode = match surface_info.alpha {
158            gpu::AlphaMode::Ignored => gpu::BlendState::ALPHA_BLENDING,
159            gpu::AlphaMode::PreMultiplied => gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
160            gpu::AlphaMode::PostMultiplied => gpu::BlendState::ALPHA_BLENDING,
161        };
162        let color_targets = &[gpu::ColorTargetState {
163            format: surface_info.format,
164            blend: Some(blend_mode),
165            write_mask: gpu::ColorWrites::default(),
166        }];
167
168        Self {
169            quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
170                name: "quads",
171                data_layouts: &[&ShaderQuadsData::layout()],
172                vertex: shader.at("vs_quad"),
173                vertex_fetches: &[],
174                primitive: gpu::PrimitiveState {
175                    topology: gpu::PrimitiveTopology::TriangleStrip,
176                    ..Default::default()
177                },
178                depth_stencil: None,
179                fragment: Some(shader.at("fs_quad")),
180                color_targets,
181                multisample_state: gpu::MultisampleState::default(),
182            }),
183            shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
184                name: "shadows",
185                data_layouts: &[&ShaderShadowsData::layout()],
186                vertex: shader.at("vs_shadow"),
187                vertex_fetches: &[],
188                primitive: gpu::PrimitiveState {
189                    topology: gpu::PrimitiveTopology::TriangleStrip,
190                    ..Default::default()
191                },
192                depth_stencil: None,
193                fragment: Some(shader.at("fs_shadow")),
194                color_targets,
195                multisample_state: gpu::MultisampleState::default(),
196            }),
197            path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
198                name: "path_rasterization",
199                data_layouts: &[&ShaderPathRasterizationData::layout()],
200                vertex: shader.at("vs_path_rasterization"),
201                vertex_fetches: &[],
202                primitive: gpu::PrimitiveState {
203                    topology: gpu::PrimitiveTopology::TriangleList,
204                    ..Default::default()
205                },
206                depth_stencil: None,
207                fragment: Some(shader.at("fs_path_rasterization")),
208                color_targets: &[gpu::ColorTargetState {
209                    format: PATH_TEXTURE_FORMAT,
210                    blend: Some(gpu::BlendState::ADDITIVE),
211                    write_mask: gpu::ColorWrites::default(),
212                }],
213                multisample_state: gpu::MultisampleState {
214                    sample_count: PATH_SAMPLE_COUNT,
215                    ..Default::default()
216                },
217            }),
218            paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
219                name: "paths",
220                data_layouts: &[&ShaderPathsData::layout()],
221                vertex: shader.at("vs_path"),
222                vertex_fetches: &[],
223                primitive: gpu::PrimitiveState {
224                    topology: gpu::PrimitiveTopology::TriangleStrip,
225                    ..Default::default()
226                },
227                depth_stencil: None,
228                fragment: Some(shader.at("fs_path")),
229                color_targets,
230                multisample_state: gpu::MultisampleState::default(),
231            }),
232            underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
233                name: "underlines",
234                data_layouts: &[&ShaderUnderlinesData::layout()],
235                vertex: shader.at("vs_underline"),
236                vertex_fetches: &[],
237                primitive: gpu::PrimitiveState {
238                    topology: gpu::PrimitiveTopology::TriangleStrip,
239                    ..Default::default()
240                },
241                depth_stencil: None,
242                fragment: Some(shader.at("fs_underline")),
243                color_targets,
244                multisample_state: gpu::MultisampleState::default(),
245            }),
246            mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
247                name: "mono-sprites",
248                data_layouts: &[&ShaderMonoSpritesData::layout()],
249                vertex: shader.at("vs_mono_sprite"),
250                vertex_fetches: &[],
251                primitive: gpu::PrimitiveState {
252                    topology: gpu::PrimitiveTopology::TriangleStrip,
253                    ..Default::default()
254                },
255                depth_stencil: None,
256                fragment: Some(shader.at("fs_mono_sprite")),
257                color_targets,
258                multisample_state: gpu::MultisampleState::default(),
259            }),
260            poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
261                name: "poly-sprites",
262                data_layouts: &[&ShaderPolySpritesData::layout()],
263                vertex: shader.at("vs_poly_sprite"),
264                vertex_fetches: &[],
265                primitive: gpu::PrimitiveState {
266                    topology: gpu::PrimitiveTopology::TriangleStrip,
267                    ..Default::default()
268                },
269                depth_stencil: None,
270                fragment: Some(shader.at("fs_poly_sprite")),
271                color_targets,
272                multisample_state: gpu::MultisampleState::default(),
273            }),
274            surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
275                name: "surfaces",
276                data_layouts: &[&ShaderSurfacesData::layout()],
277                vertex: shader.at("vs_surface"),
278                vertex_fetches: &[],
279                primitive: gpu::PrimitiveState {
280                    topology: gpu::PrimitiveTopology::TriangleStrip,
281                    ..Default::default()
282                },
283                depth_stencil: None,
284                fragment: Some(shader.at("fs_surface")),
285                color_targets,
286                multisample_state: gpu::MultisampleState::default(),
287            }),
288        }
289    }
290
291    fn destroy(&mut self, gpu: &gpu::Context) {
292        gpu.destroy_render_pipeline(&mut self.quads);
293        gpu.destroy_render_pipeline(&mut self.shadows);
294        gpu.destroy_render_pipeline(&mut self.path_rasterization);
295        gpu.destroy_render_pipeline(&mut self.paths);
296        gpu.destroy_render_pipeline(&mut self.underlines);
297        gpu.destroy_render_pipeline(&mut self.mono_sprites);
298        gpu.destroy_render_pipeline(&mut self.poly_sprites);
299        gpu.destroy_render_pipeline(&mut self.surfaces);
300    }
301}
302
303pub struct BladeSurfaceConfig {
304    pub size: gpu::Extent,
305    pub transparent: bool,
306}
307
308//Note: we could see some of these fields moved into `BladeContext`
309// so that they are shared between windows. E.g. `pipelines`.
310// But that is complicated by the fact that pipelines depend on
311// the format and alpha mode.
312pub struct BladeRenderer {
313    gpu: Arc<gpu::Context>,
314    surface: gpu::Surface,
315    surface_config: gpu::SurfaceConfig,
316    command_encoder: gpu::CommandEncoder,
317    last_sync_point: Option<gpu::SyncPoint>,
318    pipelines: BladePipelines,
319    instance_belt: BufferBelt,
320    path_tiles: HashMap<PathId, AtlasTile>,
321    atlas: Arc<BladeAtlas>,
322    atlas_sampler: gpu::Sampler,
323    #[cfg(target_os = "macos")]
324    core_video_texture_cache: CVMetalTextureCache,
325}
326
327impl BladeRenderer {
328    pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(
329        context: &BladeContext,
330        window: &I,
331        config: BladeSurfaceConfig,
332    ) -> anyhow::Result<Self> {
333        let surface_config = gpu::SurfaceConfig {
334            size: config.size,
335            usage: gpu::TextureUsage::TARGET,
336            display_sync: gpu::DisplaySync::Recent,
337            color_space: gpu::ColorSpace::Linear,
338            allow_exclusive_full_screen: false,
339            transparent: config.transparent,
340        };
341        let surface = context
342            .gpu
343            .create_surface_configured(window, surface_config)
344            .unwrap();
345
346        let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
347            name: "main",
348            buffer_count: 2,
349        });
350        let pipelines = BladePipelines::new(&context.gpu, surface.info());
351        let instance_belt = BufferBelt::new(BufferBeltDescriptor {
352            memory: gpu::Memory::Shared,
353            min_chunk_size: 0x1000,
354            alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
355        });
356        let atlas = Arc::new(BladeAtlas::new(&context.gpu, PATH_SAMPLE_COUNT));
357        let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
358            name: "atlas",
359            mag_filter: gpu::FilterMode::Linear,
360            min_filter: gpu::FilterMode::Linear,
361            ..Default::default()
362        });
363
364        #[cfg(target_os = "macos")]
365        let core_video_texture_cache = unsafe {
366            CVMetalTextureCache::new(
367                objc2::rc::Retained::as_ptr(&context.gpu.metal_device()) as *mut _
368            )
369            .unwrap()
370        };
371
372        Ok(Self {
373            gpu: Arc::clone(&context.gpu),
374            surface,
375            surface_config,
376            command_encoder,
377            last_sync_point: None,
378            pipelines,
379            instance_belt,
380            path_tiles: HashMap::default(),
381            atlas,
382            atlas_sampler,
383            #[cfg(target_os = "macos")]
384            core_video_texture_cache,
385        })
386    }
387
388    fn wait_for_gpu(&mut self) {
389        if let Some(last_sp) = self.last_sync_point.take() {
390            if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
391                log::error!("GPU hung");
392                while !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {}
393            }
394        }
395    }
396
397    pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
398        self.update_drawable_size_impl(size, false);
399    }
400
401    /// Like `update_drawable_size` but skips the check that the size has changed. This is useful in
402    /// cases like restoring a window from minimization where the size is the same but the
403    /// renderer's swap chain needs to be recreated.
404    #[cfg_attr(any(target_os = "macos", target_os = "linux"), allow(dead_code))]
405    pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {
406        self.update_drawable_size_impl(size, true);
407    }
408
409    fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {
410        let gpu_size = gpu::Extent {
411            width: size.width.0 as u32,
412            height: size.height.0 as u32,
413            depth: 1,
414        };
415
416        if always_resize || gpu_size != self.surface_config.size {
417            self.wait_for_gpu();
418            self.surface_config.size = gpu_size;
419            self.gpu
420                .reconfigure_surface(&mut self.surface, self.surface_config);
421        }
422    }
423
424    pub fn update_transparency(&mut self, transparent: bool) {
425        if transparent != self.surface_config.transparent {
426            self.wait_for_gpu();
427            self.surface_config.transparent = transparent;
428            self.gpu
429                .reconfigure_surface(&mut self.surface, self.surface_config);
430            self.pipelines.destroy(&self.gpu);
431            self.pipelines = BladePipelines::new(&self.gpu, self.surface.info());
432        }
433    }
434
435    #[cfg_attr(any(target_os = "macos", feature = "wayland"), allow(dead_code))]
436    pub fn viewport_size(&self) -> gpu::Extent {
437        self.surface_config.size
438    }
439
440    pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
441        &self.atlas
442    }
443
444    #[cfg_attr(target_os = "macos", allow(dead_code))]
445    pub fn gpu_specs(&self) -> GpuSpecs {
446        let info = self.gpu.device_information();
447
448        GpuSpecs {
449            is_software_emulated: info.is_software_emulated,
450            device_name: info.device_name.clone(),
451            driver_name: info.driver_name.clone(),
452            driver_info: info.driver_info.clone(),
453        }
454    }
455
456    #[cfg(target_os = "macos")]
457    pub fn layer(&self) -> metal::MetalLayer {
458        unsafe { foreign_types::ForeignType::from_ptr(self.layer_ptr()) }
459    }
460
461    #[cfg(target_os = "macos")]
462    pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
463        objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
464    }
465
466    #[profiling::function]
467    fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
468        self.path_tiles.clear();
469        let mut vertices_by_texture_id = HashMap::default();
470
471        for path in paths {
472            let clipped_bounds = path
473                .bounds
474                .intersect(&path.content_mask.bounds)
475                .map_origin(|origin| origin.floor())
476                .map_size(|size| size.ceil());
477            let tile = self.atlas.allocate_for_rendering(
478                clipped_bounds.size.map(Into::into),
479                AtlasTextureKind::Path,
480                &mut self.command_encoder,
481            );
482            vertices_by_texture_id
483                .entry(tile.texture_id)
484                .or_insert(Vec::new())
485                .extend(path.vertices.iter().map(|vertex| PathVertex {
486                    xy_position: vertex.xy_position - clipped_bounds.origin
487                        + tile.bounds.origin.map(Into::into),
488                    st_position: vertex.st_position,
489                    content_mask: ContentMask {
490                        bounds: tile.bounds.map(Into::into),
491                    },
492                }));
493            self.path_tiles.insert(path.id, tile);
494        }
495
496        for (texture_id, vertices) in vertices_by_texture_id {
497            let tex_info = self.atlas.get_texture_info(texture_id);
498            let globals = GlobalParams {
499                viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
500                premultiplied_alpha: 0,
501                pad: 0,
502            };
503
504            let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
505            let frame_view = tex_info.raw_view;
506            let color_target = if let Some(msaa_view) = tex_info.msaa_view {
507                gpu::RenderTarget {
508                    view: msaa_view,
509                    init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
510                    finish_op: gpu::FinishOp::ResolveTo(frame_view),
511                }
512            } else {
513                gpu::RenderTarget {
514                    view: frame_view,
515                    init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
516                    finish_op: gpu::FinishOp::Store,
517                }
518            };
519
520            if let mut pass = self.command_encoder.render(
521                "paths",
522                gpu::RenderTargetSet {
523                    colors: &[color_target],
524                    depth_stencil: None,
525                },
526            ) {
527                let mut encoder = pass.with(&self.pipelines.path_rasterization);
528                encoder.bind(
529                    0,
530                    &ShaderPathRasterizationData {
531                        globals,
532                        b_path_vertices: vertex_buf,
533                    },
534                );
535                encoder.draw(0, vertices.len() as u32, 0, 1);
536            }
537        }
538    }
539
540    pub fn destroy(&mut self) {
541        self.wait_for_gpu();
542        self.atlas.destroy();
543        self.gpu.destroy_sampler(self.atlas_sampler);
544        self.instance_belt.destroy(&self.gpu);
545        self.gpu.destroy_command_encoder(&mut self.command_encoder);
546        self.pipelines.destroy(&self.gpu);
547        self.gpu.destroy_surface(&mut self.surface);
548    }
549
550    pub fn draw(&mut self, scene: &Scene) {
551        self.command_encoder.start();
552        self.atlas.before_frame(&mut self.command_encoder);
553        self.rasterize_paths(scene.paths());
554
555        let frame = {
556            profiling::scope!("acquire frame");
557            self.surface.acquire_frame()
558        };
559        self.command_encoder.init_texture(frame.texture());
560
561        let globals = GlobalParams {
562            viewport_size: [
563                self.surface_config.size.width as f32,
564                self.surface_config.size.height as f32,
565            ],
566            premultiplied_alpha: match self.surface.info().alpha {
567                gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
568                gpu::AlphaMode::PreMultiplied => 1,
569            },
570            pad: 0,
571        };
572
573        if let mut pass = self.command_encoder.render(
574            "main",
575            gpu::RenderTargetSet {
576                colors: &[gpu::RenderTarget {
577                    view: frame.texture_view(),
578                    init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
579                    finish_op: gpu::FinishOp::Store,
580                }],
581                depth_stencil: None,
582            },
583        ) {
584            profiling::scope!("render pass");
585            for batch in scene.batches() {
586                match batch {
587                    PrimitiveBatch::Quads(quads) => {
588                        let instance_buf =
589                            unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
590                        let mut encoder = pass.with(&self.pipelines.quads);
591                        encoder.bind(
592                            0,
593                            &ShaderQuadsData {
594                                globals,
595                                b_quads: instance_buf,
596                            },
597                        );
598                        encoder.draw(0, 4, 0, quads.len() as u32);
599                    }
600                    PrimitiveBatch::Shadows(shadows) => {
601                        let instance_buf =
602                            unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
603                        let mut encoder = pass.with(&self.pipelines.shadows);
604                        encoder.bind(
605                            0,
606                            &ShaderShadowsData {
607                                globals,
608                                b_shadows: instance_buf,
609                            },
610                        );
611                        encoder.draw(0, 4, 0, shadows.len() as u32);
612                    }
613                    PrimitiveBatch::Paths(paths) => {
614                        let mut encoder = pass.with(&self.pipelines.paths);
615                        // todo(linux): group by texture ID
616                        for path in paths {
617                            let tile = &self.path_tiles[&path.id];
618                            let tex_info = self.atlas.get_texture_info(tile.texture_id);
619                            let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
620                            let sprites = [PathSprite {
621                                bounds: Bounds {
622                                    origin: origin.map(|p| p.floor()),
623                                    size: tile.bounds.size.map(Into::into),
624                                },
625                                color: path.color,
626                                tile: (*tile).clone(),
627                            }];
628
629                            let instance_buf =
630                                unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
631                            encoder.bind(
632                                0,
633                                &ShaderPathsData {
634                                    globals,
635                                    t_sprite: tex_info.raw_view,
636                                    s_sprite: self.atlas_sampler,
637                                    b_path_sprites: instance_buf,
638                                },
639                            );
640                            encoder.draw(0, 4, 0, sprites.len() as u32);
641                        }
642                    }
643                    PrimitiveBatch::Underlines(underlines) => {
644                        let instance_buf =
645                            unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
646                        let mut encoder = pass.with(&self.pipelines.underlines);
647                        encoder.bind(
648                            0,
649                            &ShaderUnderlinesData {
650                                globals,
651                                b_underlines: instance_buf,
652                            },
653                        );
654                        encoder.draw(0, 4, 0, underlines.len() as u32);
655                    }
656                    PrimitiveBatch::MonochromeSprites {
657                        texture_id,
658                        sprites,
659                    } => {
660                        let tex_info = self.atlas.get_texture_info(texture_id);
661                        let instance_buf =
662                            unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
663                        let mut encoder = pass.with(&self.pipelines.mono_sprites);
664                        encoder.bind(
665                            0,
666                            &ShaderMonoSpritesData {
667                                globals,
668                                t_sprite: tex_info.raw_view,
669                                s_sprite: self.atlas_sampler,
670                                b_mono_sprites: instance_buf,
671                            },
672                        );
673                        encoder.draw(0, 4, 0, sprites.len() as u32);
674                    }
675                    PrimitiveBatch::PolychromeSprites {
676                        texture_id,
677                        sprites,
678                    } => {
679                        let tex_info = self.atlas.get_texture_info(texture_id);
680                        let instance_buf =
681                            unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
682                        let mut encoder = pass.with(&self.pipelines.poly_sprites);
683                        encoder.bind(
684                            0,
685                            &ShaderPolySpritesData {
686                                globals,
687                                t_sprite: tex_info.raw_view,
688                                s_sprite: self.atlas_sampler,
689                                b_poly_sprites: instance_buf,
690                            },
691                        );
692                        encoder.draw(0, 4, 0, sprites.len() as u32);
693                    }
694                    PrimitiveBatch::Surfaces(surfaces) => {
695                        let mut _encoder = pass.with(&self.pipelines.surfaces);
696
697                        for surface in surfaces {
698                            #[cfg(not(target_os = "macos"))]
699                            {
700                                let _ = surface;
701                                continue;
702                            };
703
704                            #[cfg(target_os = "macos")]
705                            {
706                                let (t_y, t_cb_cr) = unsafe {
707                                    use core_foundation::base::TCFType as _;
708                                    use std::ptr;
709
710                                    assert_eq!(
711                                        surface.image_buffer.pixel_format_type(),
712                                        media::core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
713                                    );
714
715                                    let y_texture = self
716                                        .core_video_texture_cache
717                                        .create_texture_from_image(
718                                            surface.image_buffer.as_concrete_TypeRef(),
719                                            ptr::null(),
720                                            metal::MTLPixelFormat::R8Unorm,
721                                            surface.image_buffer.plane_width(0),
722                                            surface.image_buffer.plane_height(0),
723                                            0,
724                                        )
725                                        .unwrap();
726                                    let cb_cr_texture = self
727                                        .core_video_texture_cache
728                                        .create_texture_from_image(
729                                            surface.image_buffer.as_concrete_TypeRef(),
730                                            ptr::null(),
731                                            metal::MTLPixelFormat::RG8Unorm,
732                                            surface.image_buffer.plane_width(1),
733                                            surface.image_buffer.plane_height(1),
734                                            1,
735                                        )
736                                        .unwrap();
737                                    (
738                                        gpu::TextureView::from_metal_texture(
739                                            &objc2::rc::Retained::retain(
740                                                foreign_types::ForeignTypeRef::as_ptr(
741                                                    y_texture.as_texture_ref(),
742                                                )
743                                                    as *mut objc2::runtime::ProtocolObject<
744                                                        dyn objc2_metal::MTLTexture,
745                                                    >,
746                                            )
747                                            .unwrap(),
748                                        ),
749                                        gpu::TextureView::from_metal_texture(
750                                            &objc2::rc::Retained::retain(
751                                                foreign_types::ForeignTypeRef::as_ptr(
752                                                    cb_cr_texture.as_texture_ref(),
753                                                )
754                                                    as *mut objc2::runtime::ProtocolObject<
755                                                        dyn objc2_metal::MTLTexture,
756                                                    >,
757                                            )
758                                            .unwrap(),
759                                        ),
760                                    )
761                                };
762
763                                _encoder.bind(
764                                    0,
765                                    &ShaderSurfacesData {
766                                        globals,
767                                        surface_locals: SurfaceParams {
768                                            bounds: surface.bounds.into(),
769                                            content_mask: surface.content_mask.bounds.into(),
770                                        },
771                                        t_y,
772                                        t_cb_cr,
773                                        s_surface: self.atlas_sampler,
774                                    },
775                                );
776
777                                _encoder.draw(0, 4, 0, 1);
778                            }
779                        }
780                    }
781                }
782            }
783        }
784
785        self.command_encoder.present(frame);
786        let sync_point = self.gpu.submit(&mut self.command_encoder);
787
788        profiling::scope!("finish");
789        self.instance_belt.flush(&sync_point);
790        self.atlas.after_frame(&sync_point);
791        self.atlas.clear_textures(AtlasTextureKind::Path);
792
793        self.wait_for_gpu();
794        self.last_sync_point = Some(sync_point);
795    }
796}