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