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