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