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