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
485                .bounds
486                .intersect(&path.content_mask.bounds)
487                .map_origin(|origin| origin.floor())
488                .map_size(|size| size.ceil());
489            let tile = self.atlas.allocate_for_rendering(
490                clipped_bounds.size.map(Into::into),
491                AtlasTextureKind::Path,
492                &mut self.command_encoder,
493            );
494            vertices_by_texture_id
495                .entry(tile.texture_id)
496                .or_insert(Vec::new())
497                .extend(path.vertices.iter().map(|vertex| PathVertex {
498                    xy_position: vertex.xy_position - clipped_bounds.origin
499                        + tile.bounds.origin.map(Into::into),
500                    st_position: vertex.st_position,
501                    content_mask: ContentMask {
502                        bounds: tile.bounds.map(Into::into),
503                    },
504                }));
505            self.path_tiles.insert(path.id, tile);
506        }
507
508        for (texture_id, vertices) in vertices_by_texture_id {
509            let tex_info = self.atlas.get_texture_info(texture_id);
510            let globals = GlobalParams {
511                viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
512                premultiplied_alpha: 0,
513                pad: 0,
514            };
515
516            let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
517            let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
518                colors: &[gpu::RenderTarget {
519                    view: tex_info.raw_view,
520                    init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
521                    finish_op: gpu::FinishOp::Store,
522                }],
523                depth_stencil: None,
524            });
525
526            let mut encoder = pass.with(&self.pipelines.path_rasterization);
527            encoder.bind(
528                0,
529                &ShaderPathRasterizationData {
530                    globals,
531                    b_path_vertices: vertex_buf,
532                },
533            );
534            encoder.draw(0, vertices.len() as u32, 0, 1);
535        }
536    }
537
538    pub fn destroy(&mut self) {
539        self.wait_for_gpu();
540        self.atlas.destroy();
541        self.instance_belt.destroy(&self.gpu);
542        self.gpu.destroy_command_encoder(&mut self.command_encoder);
543    }
544
545    pub fn draw(
546        &mut self,
547        scene: &Scene,
548        // Required to compile on macOS, but not currently supported.
549        _on_complete: Option<oneshot::Sender<()>>,
550    ) {
551        self.command_encoder.start();
552        self.atlas.before_frame(&mut self.command_encoder);
553        self.rasterize_paths(scene.paths());
554
555        let frame = {
556            profiling::scope!("acquire frame");
557            self.gpu.acquire_frame()
558        };
559        self.command_encoder.init_texture(frame.texture());
560
561        let globals = GlobalParams {
562            viewport_size: [
563                self.surface_config.size.width as f32,
564                self.surface_config.size.height as f32,
565            ],
566            premultiplied_alpha: match self.alpha_mode {
567                gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
568                gpu::AlphaMode::PreMultiplied => 1,
569            },
570            pad: 0,
571        };
572
573        if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
574            colors: &[gpu::RenderTarget {
575                view: frame.texture_view(),
576                init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
577                finish_op: gpu::FinishOp::Store,
578            }],
579            depth_stencil: None,
580        }) {
581            profiling::scope!("render pass");
582            for batch in scene.batches() {
583                match batch {
584                    PrimitiveBatch::Quads(quads) => {
585                        let instance_buf =
586                            unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
587                        let mut encoder = pass.with(&self.pipelines.quads);
588                        encoder.bind(
589                            0,
590                            &ShaderQuadsData {
591                                globals,
592                                b_quads: instance_buf,
593                            },
594                        );
595                        encoder.draw(0, 4, 0, quads.len() as u32);
596                    }
597                    PrimitiveBatch::Shadows(shadows) => {
598                        let instance_buf =
599                            unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
600                        let mut encoder = pass.with(&self.pipelines.shadows);
601                        encoder.bind(
602                            0,
603                            &ShaderShadowsData {
604                                globals,
605                                b_shadows: instance_buf,
606                            },
607                        );
608                        encoder.draw(0, 4, 0, shadows.len() as u32);
609                    }
610                    PrimitiveBatch::Paths(paths) => {
611                        let mut encoder = pass.with(&self.pipelines.paths);
612                        // todo(linux): group by texture ID
613                        for path in paths {
614                            let tile = &self.path_tiles[&path.id];
615                            let tex_info = self.atlas.get_texture_info(tile.texture_id);
616                            let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
617                            let sprites = [PathSprite {
618                                bounds: Bounds {
619                                    origin: origin.map(|p| p.floor()),
620                                    size: tile.bounds.size.map(Into::into),
621                                },
622                                color: path.color,
623                                tile: (*tile).clone(),
624                            }];
625
626                            let instance_buf =
627                                unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
628                            encoder.bind(
629                                0,
630                                &ShaderPathsData {
631                                    globals,
632                                    t_sprite: tex_info.raw_view,
633                                    s_sprite: self.atlas_sampler,
634                                    b_path_sprites: instance_buf,
635                                },
636                            );
637                            encoder.draw(0, 4, 0, sprites.len() as u32);
638                        }
639                    }
640                    PrimitiveBatch::Underlines(underlines) => {
641                        let instance_buf =
642                            unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
643                        let mut encoder = pass.with(&self.pipelines.underlines);
644                        encoder.bind(
645                            0,
646                            &ShaderUnderlinesData {
647                                globals,
648                                b_underlines: instance_buf,
649                            },
650                        );
651                        encoder.draw(0, 4, 0, underlines.len() as u32);
652                    }
653                    PrimitiveBatch::MonochromeSprites {
654                        texture_id,
655                        sprites,
656                    } => {
657                        let tex_info = self.atlas.get_texture_info(texture_id);
658                        let instance_buf =
659                            unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
660                        let mut encoder = pass.with(&self.pipelines.mono_sprites);
661                        encoder.bind(
662                            0,
663                            &ShaderMonoSpritesData {
664                                globals,
665                                t_sprite: tex_info.raw_view,
666                                s_sprite: self.atlas_sampler,
667                                b_mono_sprites: instance_buf,
668                            },
669                        );
670                        encoder.draw(0, 4, 0, sprites.len() as u32);
671                    }
672                    PrimitiveBatch::PolychromeSprites {
673                        texture_id,
674                        sprites,
675                    } => {
676                        let tex_info = self.atlas.get_texture_info(texture_id);
677                        let instance_buf =
678                            unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
679                        let mut encoder = pass.with(&self.pipelines.poly_sprites);
680                        encoder.bind(
681                            0,
682                            &ShaderPolySpritesData {
683                                globals,
684                                t_sprite: tex_info.raw_view,
685                                s_sprite: self.atlas_sampler,
686                                b_poly_sprites: instance_buf,
687                            },
688                        );
689                        encoder.draw(0, 4, 0, sprites.len() as u32);
690                    }
691                    PrimitiveBatch::Surfaces(surfaces) => {
692                        let mut _encoder = pass.with(&self.pipelines.surfaces);
693
694                        for surface in surfaces {
695                            #[cfg(not(target_os = "macos"))]
696                            {
697                                let _ = surface;
698                                continue;
699                            };
700
701                            #[cfg(target_os = "macos")]
702                            {
703                                let (t_y, t_cb_cr) = {
704                                    use core_foundation::base::TCFType as _;
705                                    use std::ptr;
706
707                                    assert_eq!(
708                                    surface.image_buffer.pixel_format_type(),
709                                    media::core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
710                                );
711
712                                    let y_texture = unsafe {
713                                        self.core_video_texture_cache
714                                            .create_texture_from_image(
715                                                surface.image_buffer.as_concrete_TypeRef(),
716                                                ptr::null(),
717                                                metal::MTLPixelFormat::R8Unorm,
718                                                surface.image_buffer.plane_width(0),
719                                                surface.image_buffer.plane_height(0),
720                                                0,
721                                            )
722                                            .unwrap()
723                                    };
724                                    let cb_cr_texture = unsafe {
725                                        self.core_video_texture_cache
726                                            .create_texture_from_image(
727                                                surface.image_buffer.as_concrete_TypeRef(),
728                                                ptr::null(),
729                                                metal::MTLPixelFormat::RG8Unorm,
730                                                surface.image_buffer.plane_width(1),
731                                                surface.image_buffer.plane_height(1),
732                                                1,
733                                            )
734                                            .unwrap()
735                                    };
736                                    (
737                                        gpu::TextureView::from_metal_texture(
738                                            y_texture.as_texture_ref(),
739                                        ),
740                                        gpu::TextureView::from_metal_texture(
741                                            cb_cr_texture.as_texture_ref(),
742                                        ),
743                                    )
744                                };
745
746                                _encoder.bind(
747                                    0,
748                                    &ShaderSurfacesData {
749                                        globals,
750                                        surface_locals: SurfaceParams {
751                                            bounds: surface.bounds.into(),
752                                            content_mask: surface.content_mask.bounds.into(),
753                                        },
754                                        t_y,
755                                        t_cb_cr,
756                                        s_surface: self.atlas_sampler,
757                                    },
758                                );
759
760                                _encoder.draw(0, 4, 0, 1);
761                            }
762                        }
763                    }
764                }
765            }
766        }
767
768        self.command_encoder.present(frame);
769        let sync_point = self.gpu.submit(&mut self.command_encoder);
770
771        profiling::scope!("finish");
772        self.instance_belt.flush(&sync_point);
773        self.atlas.after_frame(&sync_point);
774        self.atlas.clear_textures(AtlasTextureKind::Path);
775
776        self.wait_for_gpu();
777        self.last_sync_point = Some(sync_point);
778    }
779
780    /// Required to compile on macOS, but not currently supported.
781    #[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
782    pub fn fps(&self) -> f32 {
783        0.0
784    }
785}