blade_renderer.rs

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