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