blade_renderer.rs

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