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            .unwrap();
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(any(target_os = "macos", target_os = "linux"), allow(dead_code))]
425    pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {
426        self.update_drawable_size_impl(size, true);
427    }
428
429    fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {
430        let gpu_size = gpu::Extent {
431            width: size.width.0 as u32,
432            height: size.height.0 as u32,
433            depth: 1,
434        };
435
436        if always_resize || gpu_size != self.surface_config.size {
437            self.wait_for_gpu();
438            self.surface_config.size = gpu_size;
439            self.gpu
440                .reconfigure_surface(&mut self.surface, self.surface_config);
441        }
442    }
443
444    pub fn update_transparency(&mut self, transparent: bool) {
445        if transparent != self.surface_config.transparent {
446            self.wait_for_gpu();
447            self.surface_config.transparent = transparent;
448            self.gpu
449                .reconfigure_surface(&mut self.surface, self.surface_config);
450            self.pipelines.destroy(&self.gpu);
451            self.pipelines =
452                BladePipelines::new(&self.gpu, self.surface.info(), self.path_sample_count);
453        }
454    }
455
456    #[cfg_attr(any(target_os = "macos", feature = "wayland"), allow(dead_code))]
457    pub fn viewport_size(&self) -> gpu::Extent {
458        self.surface_config.size
459    }
460
461    pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
462        &self.atlas
463    }
464
465    #[cfg_attr(target_os = "macos", allow(dead_code))]
466    pub fn gpu_specs(&self) -> GpuSpecs {
467        let info = self.gpu.device_information();
468
469        GpuSpecs {
470            is_software_emulated: info.is_software_emulated,
471            device_name: info.device_name.clone(),
472            driver_name: info.driver_name.clone(),
473            driver_info: info.driver_info.clone(),
474        }
475    }
476
477    #[cfg(target_os = "macos")]
478    pub fn layer(&self) -> metal::MetalLayer {
479        unsafe { foreign_types::ForeignType::from_ptr(self.layer_ptr()) }
480    }
481
482    #[cfg(target_os = "macos")]
483    pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
484        objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
485    }
486
487    #[profiling::function]
488    fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
489        self.path_tiles.clear();
490        let mut vertices_by_texture_id = HashMap::default();
491
492        for path in paths {
493            let clipped_bounds = path
494                .bounds
495                .intersect(&path.content_mask.bounds)
496                .map_origin(|origin| origin.floor())
497                .map_size(|size| size.ceil());
498            let tile = self.atlas.allocate_for_rendering(
499                clipped_bounds.size.map(Into::into),
500                AtlasTextureKind::Path,
501                &mut self.command_encoder,
502            );
503            vertices_by_texture_id
504                .entry(tile.texture_id)
505                .or_insert(Vec::new())
506                .extend(path.vertices.iter().map(|vertex| PathVertex {
507                    xy_position: vertex.xy_position - clipped_bounds.origin
508                        + tile.bounds.origin.map(Into::into),
509                    st_position: vertex.st_position,
510                    content_mask: ContentMask {
511                        bounds: tile.bounds.map(Into::into),
512                    },
513                }));
514            self.path_tiles.insert(path.id, tile);
515        }
516
517        for (texture_id, vertices) in vertices_by_texture_id {
518            let tex_info = self.atlas.get_texture_info(texture_id);
519            let globals = GlobalParams {
520                viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
521                premultiplied_alpha: 0,
522                pad: 0,
523            };
524
525            let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
526            let frame_view = tex_info.raw_view;
527            let color_target = if let Some(msaa_view) = tex_info.msaa_view {
528                gpu::RenderTarget {
529                    view: msaa_view,
530                    init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
531                    finish_op: gpu::FinishOp::ResolveTo(frame_view),
532                }
533            } else {
534                gpu::RenderTarget {
535                    view: frame_view,
536                    init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
537                    finish_op: gpu::FinishOp::Store,
538                }
539            };
540
541            if let mut pass = self.command_encoder.render(
542                "paths",
543                gpu::RenderTargetSet {
544                    colors: &[color_target],
545                    depth_stencil: None,
546                },
547            ) {
548                let mut encoder = pass.with(&self.pipelines.path_rasterization);
549                encoder.bind(
550                    0,
551                    &ShaderPathRasterizationData {
552                        globals,
553                        b_path_vertices: vertex_buf,
554                    },
555                );
556                encoder.draw(0, vertices.len() as u32, 0, 1);
557            }
558        }
559    }
560
561    pub fn destroy(&mut self) {
562        self.wait_for_gpu();
563        self.atlas.destroy();
564        self.gpu.destroy_sampler(self.atlas_sampler);
565        self.instance_belt.destroy(&self.gpu);
566        self.gpu.destroy_command_encoder(&mut self.command_encoder);
567        self.pipelines.destroy(&self.gpu);
568        self.gpu.destroy_surface(&mut self.surface);
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.surface.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.surface.info().alpha {
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(
595            "main",
596            gpu::RenderTargetSet {
597                colors: &[gpu::RenderTarget {
598                    view: frame.texture_view(),
599                    init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
600                    finish_op: gpu::FinishOp::Store,
601                }],
602                depth_stencil: None,
603            },
604        ) {
605            profiling::scope!("render pass");
606            for batch in scene.batches() {
607                match batch {
608                    PrimitiveBatch::Quads(quads) => {
609                        let instance_buf =
610                            unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
611                        let mut encoder = pass.with(&self.pipelines.quads);
612                        encoder.bind(
613                            0,
614                            &ShaderQuadsData {
615                                globals,
616                                b_quads: instance_buf,
617                            },
618                        );
619                        encoder.draw(0, 4, 0, quads.len() as u32);
620                    }
621                    PrimitiveBatch::Shadows(shadows) => {
622                        let instance_buf =
623                            unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
624                        let mut encoder = pass.with(&self.pipelines.shadows);
625                        encoder.bind(
626                            0,
627                            &ShaderShadowsData {
628                                globals,
629                                b_shadows: instance_buf,
630                            },
631                        );
632                        encoder.draw(0, 4, 0, shadows.len() as u32);
633                    }
634                    PrimitiveBatch::Paths(paths) => {
635                        let mut encoder = pass.with(&self.pipelines.paths);
636                        // todo(linux): group by texture ID
637                        for path in paths {
638                            let tile = &self.path_tiles[&path.id];
639                            let tex_info = self.atlas.get_texture_info(tile.texture_id);
640                            let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
641                            let sprites = [PathSprite {
642                                bounds: Bounds {
643                                    origin: origin.map(|p| p.floor()),
644                                    size: tile.bounds.size.map(Into::into),
645                                },
646                                color: path.color,
647                                tile: (*tile).clone(),
648                            }];
649
650                            let instance_buf =
651                                unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
652                            encoder.bind(
653                                0,
654                                &ShaderPathsData {
655                                    globals,
656                                    t_sprite: tex_info.raw_view,
657                                    s_sprite: self.atlas_sampler,
658                                    b_path_sprites: instance_buf,
659                                },
660                            );
661                            encoder.draw(0, 4, 0, sprites.len() as u32);
662                        }
663                    }
664                    PrimitiveBatch::Underlines(underlines) => {
665                        let instance_buf =
666                            unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
667                        let mut encoder = pass.with(&self.pipelines.underlines);
668                        encoder.bind(
669                            0,
670                            &ShaderUnderlinesData {
671                                globals,
672                                b_underlines: instance_buf,
673                            },
674                        );
675                        encoder.draw(0, 4, 0, underlines.len() as u32);
676                    }
677                    PrimitiveBatch::MonochromeSprites {
678                        texture_id,
679                        sprites,
680                    } => {
681                        let tex_info = self.atlas.get_texture_info(texture_id);
682                        let instance_buf =
683                            unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
684                        let mut encoder = pass.with(&self.pipelines.mono_sprites);
685                        encoder.bind(
686                            0,
687                            &ShaderMonoSpritesData {
688                                globals,
689                                t_sprite: tex_info.raw_view,
690                                s_sprite: self.atlas_sampler,
691                                b_mono_sprites: instance_buf,
692                            },
693                        );
694                        encoder.draw(0, 4, 0, sprites.len() as u32);
695                    }
696                    PrimitiveBatch::PolychromeSprites {
697                        texture_id,
698                        sprites,
699                    } => {
700                        let tex_info = self.atlas.get_texture_info(texture_id);
701                        let instance_buf =
702                            unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
703                        let mut encoder = pass.with(&self.pipelines.poly_sprites);
704                        encoder.bind(
705                            0,
706                            &ShaderPolySpritesData {
707                                globals,
708                                t_sprite: tex_info.raw_view,
709                                s_sprite: self.atlas_sampler,
710                                b_poly_sprites: instance_buf,
711                            },
712                        );
713                        encoder.draw(0, 4, 0, sprites.len() as u32);
714                    }
715                    PrimitiveBatch::Surfaces(surfaces) => {
716                        let mut _encoder = pass.with(&self.pipelines.surfaces);
717
718                        for surface in surfaces {
719                            #[cfg(not(target_os = "macos"))]
720                            {
721                                let _ = surface;
722                                continue;
723                            };
724
725                            #[cfg(target_os = "macos")]
726                            {
727                                let (t_y, t_cb_cr) = unsafe {
728                                    use core_foundation::base::TCFType as _;
729                                    use std::ptr;
730
731                                    assert_eq!(
732                                        surface.image_buffer.get_pixel_format(),
733                                        core_video::pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
734                                    );
735
736                                    let y_texture = self
737                                        .core_video_texture_cache
738                                        .create_texture_from_image(
739                                            surface.image_buffer.as_concrete_TypeRef(),
740                                            ptr::null(),
741                                            metal::MTLPixelFormat::R8Unorm,
742                                            surface.image_buffer.get_width_of_plane(0),
743                                            surface.image_buffer.get_height_of_plane(0),
744                                            0,
745                                        )
746                                        .unwrap();
747                                    let cb_cr_texture = self
748                                        .core_video_texture_cache
749                                        .create_texture_from_image(
750                                            surface.image_buffer.as_concrete_TypeRef(),
751                                            ptr::null(),
752                                            metal::MTLPixelFormat::RG8Unorm,
753                                            surface.image_buffer.get_width_of_plane(1),
754                                            surface.image_buffer.get_height_of_plane(1),
755                                            1,
756                                        )
757                                        .unwrap();
758                                    (
759                                        gpu::TextureView::from_metal_texture(
760                                            &objc2::rc::Retained::retain(
761                                                foreign_types::ForeignTypeRef::as_ptr(
762                                                    y_texture.as_texture_ref(),
763                                                )
764                                                    as *mut objc2::runtime::ProtocolObject<
765                                                        dyn objc2_metal::MTLTexture,
766                                                    >,
767                                            )
768                                            .unwrap(),
769                                        ),
770                                        gpu::TextureView::from_metal_texture(
771                                            &objc2::rc::Retained::retain(
772                                                foreign_types::ForeignTypeRef::as_ptr(
773                                                    cb_cr_texture.as_texture_ref(),
774                                                )
775                                                    as *mut objc2::runtime::ProtocolObject<
776                                                        dyn objc2_metal::MTLTexture,
777                                                    >,
778                                            )
779                                            .unwrap(),
780                                        ),
781                                    )
782                                };
783
784                                _encoder.bind(
785                                    0,
786                                    &ShaderSurfacesData {
787                                        globals,
788                                        surface_locals: SurfaceParams {
789                                            bounds: surface.bounds.into(),
790                                            content_mask: surface.content_mask.bounds.into(),
791                                        },
792                                        t_y,
793                                        t_cb_cr,
794                                        s_surface: self.atlas_sampler,
795                                    },
796                                );
797
798                                _encoder.draw(0, 4, 0, 1);
799                            }
800                        }
801                    }
802                }
803            }
804        }
805
806        self.command_encoder.present(frame);
807        let sync_point = self.gpu.submit(&mut self.command_encoder);
808
809        profiling::scope!("finish");
810        self.instance_belt.flush(&sync_point);
811        self.atlas.after_frame(&sync_point);
812        self.atlas.clear_textures(AtlasTextureKind::Path);
813
814        self.wait_for_gpu();
815        self.last_sync_point = Some(sync_point);
816    }
817}