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, Hsla, MonochromeSprite, Path,
  7    PathId, 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 blade_util::{BufferBelt, BufferBeltDescriptor};
 19use std::{mem, sync::Arc};
 20
 21const MAX_FRAME_TIME_MS: u32 = 1000;
 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
340pub struct BladeSurfaceConfig {
341    pub size: gpu::Extent,
342    pub transparent: bool,
343}
344
345pub struct BladeRenderer {
346    gpu: Arc<gpu::Context>,
347    surface_config: gpu::SurfaceConfig,
348    alpha_mode: gpu::AlphaMode,
349    command_encoder: gpu::CommandEncoder,
350    last_sync_point: Option<gpu::SyncPoint>,
351    pipelines: BladePipelines,
352    instance_belt: BufferBelt,
353    path_tiles: HashMap<PathId, AtlasTile>,
354    atlas: Arc<BladeAtlas>,
355    atlas_sampler: gpu::Sampler,
356    #[cfg(target_os = "macos")]
357    core_video_texture_cache: CVMetalTextureCache,
358}
359
360impl BladeRenderer {
361    pub fn new(gpu: Arc<gpu::Context>, config: BladeSurfaceConfig) -> Self {
362        let surface_config = gpu::SurfaceConfig {
363            size: config.size,
364            usage: gpu::TextureUsage::TARGET,
365            display_sync: gpu::DisplaySync::Recent,
366            color_space: gpu::ColorSpace::Linear,
367            allow_exclusive_full_screen: false,
368            transparent: config.transparent,
369        };
370        let surface_info = gpu.resize(surface_config);
371
372        let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
373            name: "main",
374            buffer_count: 2,
375        });
376        let pipelines = BladePipelines::new(&gpu, surface_info);
377        let instance_belt = BufferBelt::new(BufferBeltDescriptor {
378            memory: gpu::Memory::Shared,
379            min_chunk_size: 0x1000,
380            alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
381        });
382        let atlas = Arc::new(BladeAtlas::new(&gpu));
383        let atlas_sampler = gpu.create_sampler(gpu::SamplerDesc {
384            name: "atlas",
385            mag_filter: gpu::FilterMode::Linear,
386            min_filter: gpu::FilterMode::Linear,
387            ..Default::default()
388        });
389
390        #[cfg(target_os = "macos")]
391        let core_video_texture_cache = unsafe {
392            use foreign_types::ForeignType as _;
393            CVMetalTextureCache::new(gpu.metal_device().as_ptr()).unwrap()
394        };
395
396        Self {
397            gpu,
398            surface_config,
399            alpha_mode: surface_info.alpha,
400            command_encoder,
401            last_sync_point: None,
402            pipelines,
403            instance_belt,
404            path_tiles: HashMap::default(),
405            atlas,
406            atlas_sampler,
407            #[cfg(target_os = "macos")]
408            core_video_texture_cache,
409        }
410    }
411
412    fn wait_for_gpu(&mut self) {
413        if let Some(last_sp) = self.last_sync_point.take() {
414            if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
415                panic!("GPU hung");
416            }
417        }
418    }
419
420    pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
421        let gpu_size = gpu::Extent {
422            width: size.width.0 as u32,
423            height: size.height.0 as u32,
424            depth: 1,
425        };
426
427        if gpu_size != self.surface_config.size {
428            self.wait_for_gpu();
429            self.surface_config.size = gpu_size;
430            self.gpu.resize(self.surface_config);
431        }
432    }
433
434    pub fn update_transparency(&mut self, transparent: bool) {
435        if transparent != self.surface_config.transparent {
436            self.wait_for_gpu();
437            self.surface_config.transparent = transparent;
438            let surface_info = self.gpu.resize(self.surface_config);
439            self.pipelines = BladePipelines::new(&self.gpu, surface_info);
440            self.alpha_mode = surface_info.alpha;
441        }
442    }
443
444    #[cfg_attr(target_os = "macos", allow(dead_code))]
445    pub fn viewport_size(&self) -> gpu::Extent {
446        self.surface_config.size
447    }
448
449    pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
450        &self.atlas
451    }
452
453    #[cfg(target_os = "macos")]
454    pub fn layer(&self) -> metal::MetalLayer {
455        self.gpu.metal_layer().unwrap()
456    }
457
458    #[cfg(target_os = "macos")]
459    pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
460        use metal::foreign_types::ForeignType as _;
461        self.gpu.metal_layer().unwrap().as_ptr()
462    }
463
464    #[profiling::function]
465    fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
466        self.path_tiles.clear();
467        let mut vertices_by_texture_id = HashMap::default();
468
469        for path in paths {
470            let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
471            let tile = self.atlas.allocate_for_rendering(
472                clipped_bounds.size.map(Into::into),
473                AtlasTextureKind::Path,
474                &mut self.command_encoder,
475            );
476            vertices_by_texture_id
477                .entry(tile.texture_id)
478                .or_insert(Vec::new())
479                .extend(path.vertices.iter().map(|vertex| PathVertex {
480                    xy_position: vertex.xy_position - clipped_bounds.origin
481                        + tile.bounds.origin.map(Into::into),
482                    st_position: vertex.st_position,
483                    content_mask: ContentMask {
484                        bounds: tile.bounds.map(Into::into),
485                    },
486                }));
487            self.path_tiles.insert(path.id, tile);
488        }
489
490        for (texture_id, vertices) in vertices_by_texture_id {
491            let tex_info = self.atlas.get_texture_info(texture_id);
492            let globals = GlobalParams {
493                viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
494                premultiplied_alpha: 0,
495                pad: 0,
496            };
497
498            let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
499            let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
500                colors: &[gpu::RenderTarget {
501                    view: tex_info.raw_view,
502                    init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
503                    finish_op: gpu::FinishOp::Store,
504                }],
505                depth_stencil: None,
506            });
507
508            let mut encoder = pass.with(&self.pipelines.path_rasterization);
509            encoder.bind(
510                0,
511                &ShaderPathRasterizationData {
512                    globals,
513                    b_path_vertices: vertex_buf,
514                },
515            );
516            encoder.draw(0, vertices.len() as u32, 0, 1);
517        }
518    }
519
520    pub fn destroy(&mut self) {
521        self.wait_for_gpu();
522        self.atlas.destroy();
523        self.instance_belt.destroy(&self.gpu);
524        self.gpu.destroy_command_encoder(&mut self.command_encoder);
525    }
526
527    pub fn draw(&mut self, scene: &Scene) {
528        self.command_encoder.start();
529        self.atlas.before_frame(&mut self.command_encoder);
530        self.rasterize_paths(scene.paths());
531
532        let frame = {
533            profiling::scope!("acquire frame");
534            self.gpu.acquire_frame()
535        };
536        self.command_encoder.init_texture(frame.texture());
537
538        let globals = GlobalParams {
539            viewport_size: [
540                self.surface_config.size.width as f32,
541                self.surface_config.size.height as f32,
542            ],
543            premultiplied_alpha: match self.alpha_mode {
544                gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
545                gpu::AlphaMode::PreMultiplied => 1,
546            },
547            pad: 0,
548        };
549
550        if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
551            colors: &[gpu::RenderTarget {
552                view: frame.texture_view(),
553                init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
554                finish_op: gpu::FinishOp::Store,
555            }],
556            depth_stencil: None,
557        }) {
558            profiling::scope!("render pass");
559            for batch in scene.batches() {
560                match batch {
561                    PrimitiveBatch::Quads(quads) => {
562                        let instance_buf =
563                            unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
564                        let mut encoder = pass.with(&self.pipelines.quads);
565                        encoder.bind(
566                            0,
567                            &ShaderQuadsData {
568                                globals,
569                                b_quads: instance_buf,
570                            },
571                        );
572                        encoder.draw(0, 4, 0, quads.len() as u32);
573                    }
574                    PrimitiveBatch::Shadows(shadows) => {
575                        let instance_buf =
576                            unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
577                        let mut encoder = pass.with(&self.pipelines.shadows);
578                        encoder.bind(
579                            0,
580                            &ShaderShadowsData {
581                                globals,
582                                b_shadows: instance_buf,
583                            },
584                        );
585                        encoder.draw(0, 4, 0, shadows.len() as u32);
586                    }
587                    PrimitiveBatch::Paths(paths) => {
588                        let mut encoder = pass.with(&self.pipelines.paths);
589                        // todo(linux): group by texture ID
590                        for path in paths {
591                            let tile = &self.path_tiles[&path.id];
592                            let tex_info = self.atlas.get_texture_info(tile.texture_id);
593                            let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
594                            let sprites = [PathSprite {
595                                bounds: Bounds {
596                                    origin: origin.map(|p| p.floor()),
597                                    size: tile.bounds.size.map(Into::into),
598                                },
599                                color: path.color,
600                                tile: (*tile).clone(),
601                            }];
602
603                            let instance_buf =
604                                unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
605                            encoder.bind(
606                                0,
607                                &ShaderPathsData {
608                                    globals,
609                                    t_sprite: tex_info.raw_view,
610                                    s_sprite: self.atlas_sampler,
611                                    b_path_sprites: instance_buf,
612                                },
613                            );
614                            encoder.draw(0, 4, 0, sprites.len() as u32);
615                        }
616                    }
617                    PrimitiveBatch::Underlines(underlines) => {
618                        let instance_buf =
619                            unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
620                        let mut encoder = pass.with(&self.pipelines.underlines);
621                        encoder.bind(
622                            0,
623                            &ShaderUnderlinesData {
624                                globals,
625                                b_underlines: instance_buf,
626                            },
627                        );
628                        encoder.draw(0, 4, 0, underlines.len() as u32);
629                    }
630                    PrimitiveBatch::MonochromeSprites {
631                        texture_id,
632                        sprites,
633                    } => {
634                        let tex_info = self.atlas.get_texture_info(texture_id);
635                        let instance_buf =
636                            unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
637                        let mut encoder = pass.with(&self.pipelines.mono_sprites);
638                        encoder.bind(
639                            0,
640                            &ShaderMonoSpritesData {
641                                globals,
642                                t_sprite: tex_info.raw_view,
643                                s_sprite: self.atlas_sampler,
644                                b_mono_sprites: instance_buf,
645                            },
646                        );
647                        encoder.draw(0, 4, 0, sprites.len() as u32);
648                    }
649                    PrimitiveBatch::PolychromeSprites {
650                        texture_id,
651                        sprites,
652                    } => {
653                        let tex_info = self.atlas.get_texture_info(texture_id);
654                        let instance_buf =
655                            unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
656                        let mut encoder = pass.with(&self.pipelines.poly_sprites);
657                        encoder.bind(
658                            0,
659                            &ShaderPolySpritesData {
660                                globals,
661                                t_sprite: tex_info.raw_view,
662                                s_sprite: self.atlas_sampler,
663                                b_poly_sprites: instance_buf,
664                            },
665                        );
666                        encoder.draw(0, 4, 0, sprites.len() as u32);
667                    }
668                    PrimitiveBatch::Surfaces(surfaces) => {
669                        let mut _encoder = pass.with(&self.pipelines.surfaces);
670
671                        for surface in surfaces {
672                            #[cfg(not(target_os = "macos"))]
673                            {
674                                let _ = surface;
675                                continue;
676                            };
677
678                            #[cfg(target_os = "macos")]
679                            {
680                                let (t_y, t_cb_cr) = {
681                                    use core_foundation::base::TCFType as _;
682                                    use std::ptr;
683
684                                    assert_eq!(
685                                    surface.image_buffer.pixel_format_type(),
686                                    media::core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
687                                );
688
689                                    let y_texture = unsafe {
690                                        self.core_video_texture_cache
691                                            .create_texture_from_image(
692                                                surface.image_buffer.as_concrete_TypeRef(),
693                                                ptr::null(),
694                                                metal::MTLPixelFormat::R8Unorm,
695                                                surface.image_buffer.plane_width(0),
696                                                surface.image_buffer.plane_height(0),
697                                                0,
698                                            )
699                                            .unwrap()
700                                    };
701                                    let cb_cr_texture = unsafe {
702                                        self.core_video_texture_cache
703                                            .create_texture_from_image(
704                                                surface.image_buffer.as_concrete_TypeRef(),
705                                                ptr::null(),
706                                                metal::MTLPixelFormat::RG8Unorm,
707                                                surface.image_buffer.plane_width(1),
708                                                surface.image_buffer.plane_height(1),
709                                                1,
710                                            )
711                                            .unwrap()
712                                    };
713                                    (
714                                        gpu::TextureView::from_metal_texture(
715                                            y_texture.as_texture_ref(),
716                                        ),
717                                        gpu::TextureView::from_metal_texture(
718                                            cb_cr_texture.as_texture_ref(),
719                                        ),
720                                    )
721                                };
722
723                                _encoder.bind(
724                                    0,
725                                    &ShaderSurfacesData {
726                                        globals,
727                                        surface_locals: SurfaceParams {
728                                            bounds: surface.bounds.into(),
729                                            content_mask: surface.content_mask.bounds.into(),
730                                        },
731                                        t_y,
732                                        t_cb_cr,
733                                        s_surface: self.atlas_sampler,
734                                    },
735                                );
736
737                                _encoder.draw(0, 4, 0, 1);
738                            }
739                        }
740                    }
741                }
742            }
743        }
744
745        self.command_encoder.present(frame);
746        let sync_point = self.gpu.submit(&mut self.command_encoder);
747
748        profiling::scope!("finish");
749        self.instance_belt.flush(&sync_point);
750        self.atlas.after_frame(&sync_point);
751        self.atlas.clear_textures(AtlasTextureKind::Path);
752
753        self.wait_for_gpu();
754        self.last_sync_point = Some(sync_point);
755    }
756}