blade_renderer.rs

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