blade_renderer.rs

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