metal_renderer.rs

   1use crate::metal_atlas::MetalAtlas;
   2use anyhow::Result;
   3use block::ConcreteBlock;
   4use cocoa::{
   5    base::{NO, YES},
   6    foundation::{NSSize, NSUInteger},
   7    quartzcore::AutoresizingMask,
   8};
   9use gpui::{
  10    AtlasTextureId, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface,
  11    Path, Point, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
  12    Surface, Underline, point, size,
  13};
  14#[cfg(any(test, feature = "test-support"))]
  15use image::RgbaImage;
  16
  17use core_foundation::base::TCFType;
  18use core_video::{
  19    metal_texture::CVMetalTextureGetTexture, metal_texture_cache::CVMetalTextureCache,
  20    pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
  21};
  22use foreign_types::{ForeignType, ForeignTypeRef};
  23use metal::{
  24    CAMetalLayer, CommandQueue, MTLGPUFamily, MTLPixelFormat, MTLResourceOptions, NSRange,
  25    RenderPassColorAttachmentDescriptorRef,
  26};
  27use objc::{self, msg_send, sel, sel_impl};
  28use parking_lot::Mutex;
  29
  30use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc};
  31
  32// Exported to metal
  33pub(crate) type PointF = gpui::Point<f32>;
  34
  35#[cfg(not(feature = "runtime_shaders"))]
  36const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
  37#[cfg(feature = "runtime_shaders")]
  38const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
  39// Use 4x MSAA, all devices support it.
  40// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
  41const PATH_SAMPLE_COUNT: u32 = 4;
  42
  43pub(crate) type Context = Arc<Mutex<InstanceBufferPool>>;
  44pub(crate) type Renderer = MetalRenderer;
  45
  46pub(crate) unsafe fn new_renderer(
  47    context: self::Context,
  48    _native_window: *mut c_void,
  49    _native_view: *mut c_void,
  50    _bounds: gpui::Size<f32>,
  51    transparent: bool,
  52) -> Renderer {
  53    MetalRenderer::new(context, transparent)
  54}
  55
  56pub(crate) struct InstanceBufferPool {
  57    buffer_size: usize,
  58    buffers: Vec<metal::Buffer>,
  59}
  60
  61impl Default for InstanceBufferPool {
  62    fn default() -> Self {
  63        Self {
  64            buffer_size: 2 * 1024 * 1024,
  65            buffers: Vec::new(),
  66        }
  67    }
  68}
  69
  70pub(crate) struct InstanceBuffer {
  71    metal_buffer: metal::Buffer,
  72    size: usize,
  73}
  74
  75impl InstanceBufferPool {
  76    pub(crate) fn reset(&mut self, buffer_size: usize) {
  77        self.buffer_size = buffer_size;
  78        self.buffers.clear();
  79    }
  80
  81    pub(crate) fn acquire(
  82        &mut self,
  83        device: &metal::Device,
  84        unified_memory: bool,
  85    ) -> InstanceBuffer {
  86        let buffer = self.buffers.pop().unwrap_or_else(|| {
  87            let options = if unified_memory {
  88                MTLResourceOptions::StorageModeShared
  89                    // Buffers are write only which can benefit from the combined cache
  90                    // https://developer.apple.com/documentation/metal/mtlresourceoptions/cpucachemodewritecombined
  91                    | MTLResourceOptions::CPUCacheModeWriteCombined
  92            } else {
  93                MTLResourceOptions::StorageModeManaged
  94            };
  95
  96            device.new_buffer(self.buffer_size as u64, options)
  97        });
  98        InstanceBuffer {
  99            metal_buffer: buffer,
 100            size: self.buffer_size,
 101        }
 102    }
 103
 104    pub(crate) fn release(&mut self, buffer: InstanceBuffer) {
 105        if buffer.size == self.buffer_size {
 106            self.buffers.push(buffer.metal_buffer)
 107        }
 108    }
 109}
 110
 111pub(crate) struct MetalRenderer {
 112    device: metal::Device,
 113    layer: Option<metal::MetalLayer>,
 114    is_apple_gpu: bool,
 115    is_unified_memory: bool,
 116    presents_with_transaction: bool,
 117    /// For headless rendering, tracks whether output should be opaque
 118    opaque: bool,
 119    command_queue: CommandQueue,
 120    paths_rasterization_pipeline_state: metal::RenderPipelineState,
 121    path_sprites_pipeline_state: metal::RenderPipelineState,
 122    shadows_pipeline_state: metal::RenderPipelineState,
 123    quads_pipeline_state: metal::RenderPipelineState,
 124    underlines_pipeline_state: metal::RenderPipelineState,
 125    monochrome_sprites_pipeline_state: metal::RenderPipelineState,
 126    polychrome_sprites_pipeline_state: metal::RenderPipelineState,
 127    surfaces_pipeline_state: metal::RenderPipelineState,
 128    unit_vertices: metal::Buffer,
 129    #[allow(clippy::arc_with_non_send_sync)]
 130    instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
 131    sprite_atlas: Arc<MetalAtlas>,
 132    core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache,
 133    path_intermediate_texture: Option<metal::Texture>,
 134    path_intermediate_msaa_texture: Option<metal::Texture>,
 135    path_sample_count: u32,
 136}
 137
 138#[repr(C)]
 139pub struct PathRasterizationVertex {
 140    pub xy_position: Point<ScaledPixels>,
 141    pub st_position: Point<f32>,
 142    pub color: Background,
 143    pub bounds: Bounds<ScaledPixels>,
 144}
 145
 146impl MetalRenderer {
 147    /// Creates a new MetalRenderer with a CAMetalLayer for window-based rendering.
 148    pub fn new(instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>, transparent: bool) -> Self {
 149        let device = Self::create_device();
 150
 151        let layer = metal::MetalLayer::new();
 152        layer.set_device(&device);
 153        layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
 154        // Support direct-to-display rendering if the window is not transparent
 155        // https://developer.apple.com/documentation/metal/managing-your-game-window-for-metal-in-macos
 156        layer.set_opaque(!transparent);
 157        layer.set_maximum_drawable_count(3);
 158        // Allow texture reading for visual tests (captures screenshots without ScreenCaptureKit)
 159        #[cfg(any(test, feature = "test-support"))]
 160        layer.set_framebuffer_only(false);
 161        unsafe {
 162            let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
 163            let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
 164            let _: () = msg_send![
 165                &*layer,
 166                setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
 167                    | AutoresizingMask::HEIGHT_SIZABLE
 168            ];
 169        }
 170
 171        Self::new_internal(device, Some(layer), !transparent, instance_buffer_pool)
 172    }
 173
 174    /// Creates a new headless MetalRenderer for offscreen rendering without a window.
 175    ///
 176    /// This renderer can render scenes to images without requiring a CAMetalLayer,
 177    /// window, or AppKit. Use `render_scene_to_image()` to render scenes.
 178    #[cfg(any(test, feature = "test-support"))]
 179    pub fn new_headless(instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>) -> Self {
 180        let device = Self::create_device();
 181        Self::new_internal(device, None, true, instance_buffer_pool)
 182    }
 183
 184    fn create_device() -> metal::Device {
 185        // Prefer low‐power integrated GPUs on Intel Mac. On Apple
 186        // Silicon, there is only ever one GPU, so this is equivalent to
 187        // `metal::Device::system_default()`.
 188        if let Some(d) = metal::Device::all()
 189            .into_iter()
 190            .min_by_key(|d| (d.is_removable(), !d.is_low_power()))
 191        {
 192            d
 193        } else {
 194            // For some reason `all()` can return an empty list, see https://github.com/zed-industries/zed/issues/37689
 195            // In that case, we fall back to the system default device.
 196            log::error!(
 197                "Unable to enumerate Metal devices; attempting to use system default device"
 198            );
 199            metal::Device::system_default().unwrap_or_else(|| {
 200                log::error!("unable to access a compatible graphics device");
 201                std::process::exit(1);
 202            })
 203        }
 204    }
 205
 206    fn new_internal(
 207        device: metal::Device,
 208        layer: Option<metal::MetalLayer>,
 209        opaque: bool,
 210        instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
 211    ) -> Self {
 212        #[cfg(feature = "runtime_shaders")]
 213        let library = device
 214            .new_library_with_source(&SHADERS_SOURCE_FILE, &metal::CompileOptions::new())
 215            .expect("error building metal library");
 216        #[cfg(not(feature = "runtime_shaders"))]
 217        let library = device
 218            .new_library_with_data(SHADERS_METALLIB)
 219            .expect("error building metal library");
 220
 221        fn to_float2_bits(point: PointF) -> u64 {
 222            let mut output = point.y.to_bits() as u64;
 223            output <<= 32;
 224            output |= point.x.to_bits() as u64;
 225            output
 226        }
 227
 228        // Shared memory can be used only if CPU and GPU share the same memory space.
 229        // https://developer.apple.com/documentation/metal/setting-resource-storage-modes
 230        let is_unified_memory = device.has_unified_memory();
 231        // Apple GPU families support memoryless textures, which can significantly reduce
 232        // memory usage by keeping render targets in on-chip tile memory instead of
 233        // allocating backing store in system memory.
 234        // https://developer.apple.com/documentation/metal/mtlgpufamily
 235        let is_apple_gpu = device.supports_family(MTLGPUFamily::Apple1);
 236
 237        let unit_vertices = [
 238            to_float2_bits(point(0., 0.)),
 239            to_float2_bits(point(1., 0.)),
 240            to_float2_bits(point(0., 1.)),
 241            to_float2_bits(point(0., 1.)),
 242            to_float2_bits(point(1., 0.)),
 243            to_float2_bits(point(1., 1.)),
 244        ];
 245        let unit_vertices = device.new_buffer_with_data(
 246            unit_vertices.as_ptr() as *const c_void,
 247            mem::size_of_val(&unit_vertices) as u64,
 248            if is_unified_memory {
 249                MTLResourceOptions::StorageModeShared
 250                    | MTLResourceOptions::CPUCacheModeWriteCombined
 251            } else {
 252                MTLResourceOptions::StorageModeManaged
 253            },
 254        );
 255
 256        let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state(
 257            &device,
 258            &library,
 259            "paths_rasterization",
 260            "path_rasterization_vertex",
 261            "path_rasterization_fragment",
 262            MTLPixelFormat::BGRA8Unorm,
 263            PATH_SAMPLE_COUNT,
 264        );
 265        let path_sprites_pipeline_state = build_path_sprite_pipeline_state(
 266            &device,
 267            &library,
 268            "path_sprites",
 269            "path_sprite_vertex",
 270            "path_sprite_fragment",
 271            MTLPixelFormat::BGRA8Unorm,
 272        );
 273        let shadows_pipeline_state = build_pipeline_state(
 274            &device,
 275            &library,
 276            "shadows",
 277            "shadow_vertex",
 278            "shadow_fragment",
 279            MTLPixelFormat::BGRA8Unorm,
 280        );
 281        let quads_pipeline_state = build_pipeline_state(
 282            &device,
 283            &library,
 284            "quads",
 285            "quad_vertex",
 286            "quad_fragment",
 287            MTLPixelFormat::BGRA8Unorm,
 288        );
 289        let underlines_pipeline_state = build_pipeline_state(
 290            &device,
 291            &library,
 292            "underlines",
 293            "underline_vertex",
 294            "underline_fragment",
 295            MTLPixelFormat::BGRA8Unorm,
 296        );
 297        let monochrome_sprites_pipeline_state = build_pipeline_state(
 298            &device,
 299            &library,
 300            "monochrome_sprites",
 301            "monochrome_sprite_vertex",
 302            "monochrome_sprite_fragment",
 303            MTLPixelFormat::BGRA8Unorm,
 304        );
 305        let polychrome_sprites_pipeline_state = build_pipeline_state(
 306            &device,
 307            &library,
 308            "polychrome_sprites",
 309            "polychrome_sprite_vertex",
 310            "polychrome_sprite_fragment",
 311            MTLPixelFormat::BGRA8Unorm,
 312        );
 313        let surfaces_pipeline_state = build_pipeline_state(
 314            &device,
 315            &library,
 316            "surfaces",
 317            "surface_vertex",
 318            "surface_fragment",
 319            MTLPixelFormat::BGRA8Unorm,
 320        );
 321
 322        let command_queue = device.new_command_queue();
 323        let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), is_apple_gpu));
 324        let core_video_texture_cache =
 325            CVMetalTextureCache::new(None, device.clone(), None).unwrap();
 326
 327        Self {
 328            device,
 329            layer,
 330            presents_with_transaction: false,
 331            is_apple_gpu,
 332            is_unified_memory,
 333            opaque,
 334            command_queue,
 335            paths_rasterization_pipeline_state,
 336            path_sprites_pipeline_state,
 337            shadows_pipeline_state,
 338            quads_pipeline_state,
 339            underlines_pipeline_state,
 340            monochrome_sprites_pipeline_state,
 341            polychrome_sprites_pipeline_state,
 342            surfaces_pipeline_state,
 343            unit_vertices,
 344            instance_buffer_pool,
 345            sprite_atlas,
 346            core_video_texture_cache,
 347            path_intermediate_texture: None,
 348            path_intermediate_msaa_texture: None,
 349            path_sample_count: PATH_SAMPLE_COUNT,
 350        }
 351    }
 352
 353    pub fn layer(&self) -> Option<&metal::MetalLayerRef> {
 354        self.layer.as_ref().map(|l| l.as_ref())
 355    }
 356
 357    pub fn layer_ptr(&self) -> *mut CAMetalLayer {
 358        self.layer
 359            .as_ref()
 360            .map(|l| l.as_ptr())
 361            .unwrap_or(ptr::null_mut())
 362    }
 363
 364    pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
 365        &self.sprite_atlas
 366    }
 367
 368    pub fn set_presents_with_transaction(&mut self, presents_with_transaction: bool) {
 369        self.presents_with_transaction = presents_with_transaction;
 370        if let Some(layer) = &self.layer {
 371            layer.set_presents_with_transaction(presents_with_transaction);
 372        }
 373    }
 374
 375    pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
 376        if let Some(layer) = &self.layer {
 377            let ns_size = NSSize {
 378                width: size.width.0 as f64,
 379                height: size.height.0 as f64,
 380            };
 381            unsafe {
 382                let _: () = msg_send![
 383                    layer.as_ref(),
 384                    setDrawableSize: ns_size
 385                ];
 386            }
 387        }
 388        self.update_path_intermediate_textures(size);
 389    }
 390
 391    fn update_path_intermediate_textures(&mut self, size: Size<DevicePixels>) {
 392        // We are uncertain when this happens, but sometimes size can be 0 here. Most likely before
 393        // the layout pass on window creation. Zero-sized texture creation causes SIGABRT.
 394        // https://github.com/zed-industries/zed/issues/36229
 395        if size.width.0 <= 0 || size.height.0 <= 0 {
 396            self.path_intermediate_texture = None;
 397            self.path_intermediate_msaa_texture = None;
 398            return;
 399        }
 400
 401        let texture_descriptor = metal::TextureDescriptor::new();
 402        texture_descriptor.set_width(size.width.0 as u64);
 403        texture_descriptor.set_height(size.height.0 as u64);
 404        texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
 405        texture_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
 406        texture_descriptor
 407            .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
 408        self.path_intermediate_texture = Some(self.device.new_texture(&texture_descriptor));
 409
 410        if self.path_sample_count > 1 {
 411            // https://developer.apple.com/documentation/metal/choosing-a-resource-storage-mode-for-apple-gpus
 412            // Rendering MSAA textures are done in a single pass, so we can use memory-less storage on Apple Silicon
 413            let storage_mode = if self.is_apple_gpu {
 414                metal::MTLStorageMode::Memoryless
 415            } else {
 416                metal::MTLStorageMode::Private
 417            };
 418
 419            let msaa_descriptor = texture_descriptor;
 420            msaa_descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
 421            msaa_descriptor.set_storage_mode(storage_mode);
 422            msaa_descriptor.set_sample_count(self.path_sample_count as _);
 423            self.path_intermediate_msaa_texture = Some(self.device.new_texture(&msaa_descriptor));
 424        } else {
 425            self.path_intermediate_msaa_texture = None;
 426        }
 427    }
 428
 429    pub fn update_transparency(&mut self, transparent: bool) {
 430        self.opaque = !transparent;
 431        if let Some(layer) = &self.layer {
 432            layer.set_opaque(!transparent);
 433        }
 434    }
 435
 436    pub fn destroy(&self) {
 437        // nothing to do
 438    }
 439
 440    pub fn draw(&mut self, scene: &Scene) {
 441        let layer = match &self.layer {
 442            Some(l) => l.clone(),
 443            None => {
 444                log::error!(
 445                    "draw() called on headless renderer - use render_scene_to_image() instead"
 446                );
 447                return;
 448            }
 449        };
 450        let viewport_size = layer.drawable_size();
 451        let viewport_size: Size<DevicePixels> = size(
 452            (viewport_size.width.ceil() as i32).into(),
 453            (viewport_size.height.ceil() as i32).into(),
 454        );
 455        let drawable = if let Some(drawable) = layer.next_drawable() {
 456            drawable
 457        } else {
 458            log::error!(
 459                "failed to retrieve next drawable, drawable size: {:?}",
 460                viewport_size
 461            );
 462            return;
 463        };
 464
 465        loop {
 466            let mut instance_buffer = self
 467                .instance_buffer_pool
 468                .lock()
 469                .acquire(&self.device, self.is_unified_memory);
 470
 471            let command_buffer =
 472                self.draw_primitives(scene, &mut instance_buffer, drawable, viewport_size);
 473
 474            match command_buffer {
 475                Ok(command_buffer) => {
 476                    let instance_buffer_pool = self.instance_buffer_pool.clone();
 477                    let instance_buffer = Cell::new(Some(instance_buffer));
 478                    let block = ConcreteBlock::new(move |_| {
 479                        if let Some(instance_buffer) = instance_buffer.take() {
 480                            instance_buffer_pool.lock().release(instance_buffer);
 481                        }
 482                    });
 483                    let block = block.copy();
 484                    command_buffer.add_completed_handler(&block);
 485
 486                    if self.presents_with_transaction {
 487                        command_buffer.commit();
 488                        command_buffer.wait_until_scheduled();
 489                        drawable.present();
 490                    } else {
 491                        command_buffer.present_drawable(drawable);
 492                        command_buffer.commit();
 493                    }
 494                    return;
 495                }
 496                Err(err) => {
 497                    log::error!(
 498                        "failed to render: {}. retrying with larger instance buffer size",
 499                        err
 500                    );
 501                    let mut instance_buffer_pool = self.instance_buffer_pool.lock();
 502                    let buffer_size = instance_buffer_pool.buffer_size;
 503                    if buffer_size >= 256 * 1024 * 1024 {
 504                        log::error!("instance buffer size grew too large: {}", buffer_size);
 505                        break;
 506                    }
 507                    instance_buffer_pool.reset(buffer_size * 2);
 508                    log::info!(
 509                        "increased instance buffer size to {}",
 510                        instance_buffer_pool.buffer_size
 511                    );
 512                }
 513            }
 514        }
 515    }
 516
 517    /// Renders the scene to a texture and returns the pixel data as an RGBA image.
 518    /// This does not present the frame to screen - useful for visual testing
 519    /// where we want to capture what would be rendered without displaying it.
 520    ///
 521    /// Note: This requires a layer-backed renderer. For headless rendering,
 522    /// use `render_scene_to_image()` instead.
 523    #[cfg(any(test, feature = "test-support"))]
 524    pub fn render_to_image(&mut self, scene: &Scene) -> Result<RgbaImage> {
 525        let layer = self
 526            .layer
 527            .clone()
 528            .ok_or_else(|| anyhow::anyhow!("render_to_image requires a layer-backed renderer"))?;
 529        let viewport_size = layer.drawable_size();
 530        let viewport_size: Size<DevicePixels> = size(
 531            (viewport_size.width.ceil() as i32).into(),
 532            (viewport_size.height.ceil() as i32).into(),
 533        );
 534        let drawable = layer
 535            .next_drawable()
 536            .ok_or_else(|| anyhow::anyhow!("Failed to get drawable for render_to_image"))?;
 537
 538        loop {
 539            let mut instance_buffer = self
 540                .instance_buffer_pool
 541                .lock()
 542                .acquire(&self.device, self.is_unified_memory);
 543
 544            let command_buffer =
 545                self.draw_primitives(scene, &mut instance_buffer, drawable, viewport_size);
 546
 547            match command_buffer {
 548                Ok(command_buffer) => {
 549                    let instance_buffer_pool = self.instance_buffer_pool.clone();
 550                    let instance_buffer = Cell::new(Some(instance_buffer));
 551                    let block = ConcreteBlock::new(move |_| {
 552                        if let Some(instance_buffer) = instance_buffer.take() {
 553                            instance_buffer_pool.lock().release(instance_buffer);
 554                        }
 555                    });
 556                    let block = block.copy();
 557                    command_buffer.add_completed_handler(&block);
 558
 559                    // Commit and wait for completion without presenting
 560                    command_buffer.commit();
 561                    command_buffer.wait_until_completed();
 562
 563                    // Read pixels from the texture
 564                    let texture = drawable.texture();
 565                    let width = texture.width() as u32;
 566                    let height = texture.height() as u32;
 567                    let bytes_per_row = width as usize * 4;
 568                    let buffer_size = height as usize * bytes_per_row;
 569
 570                    let mut pixels = vec![0u8; buffer_size];
 571
 572                    let region = metal::MTLRegion {
 573                        origin: metal::MTLOrigin { x: 0, y: 0, z: 0 },
 574                        size: metal::MTLSize {
 575                            width: width as u64,
 576                            height: height as u64,
 577                            depth: 1,
 578                        },
 579                    };
 580
 581                    texture.get_bytes(
 582                        pixels.as_mut_ptr() as *mut std::ffi::c_void,
 583                        bytes_per_row as u64,
 584                        region,
 585                        0,
 586                    );
 587
 588                    // Convert BGRA to RGBA (swap B and R channels)
 589                    for chunk in pixels.chunks_exact_mut(4) {
 590                        chunk.swap(0, 2);
 591                    }
 592
 593                    return RgbaImage::from_raw(width, height, pixels).ok_or_else(|| {
 594                        anyhow::anyhow!("Failed to create RgbaImage from pixel data")
 595                    });
 596                }
 597                Err(err) => {
 598                    log::error!(
 599                        "failed to render: {}. retrying with larger instance buffer size",
 600                        err
 601                    );
 602                    let mut instance_buffer_pool = self.instance_buffer_pool.lock();
 603                    let buffer_size = instance_buffer_pool.buffer_size;
 604                    if buffer_size >= 256 * 1024 * 1024 {
 605                        anyhow::bail!("instance buffer size grew too large: {}", buffer_size);
 606                    }
 607                    instance_buffer_pool.reset(buffer_size * 2);
 608                    log::info!(
 609                        "increased instance buffer size to {}",
 610                        instance_buffer_pool.buffer_size
 611                    );
 612                }
 613            }
 614        }
 615    }
 616
 617    /// Renders a scene to an image without requiring a window or CAMetalLayer.
 618    ///
 619    /// This is the primary method for headless rendering. It creates an offscreen
 620    /// texture, renders the scene to it, and returns the pixel data as an RGBA image.
 621    #[cfg(any(test, feature = "test-support"))]
 622    pub fn render_scene_to_image(
 623        &mut self,
 624        scene: &Scene,
 625        size: Size<DevicePixels>,
 626    ) -> Result<RgbaImage> {
 627        if size.width.0 <= 0 || size.height.0 <= 0 {
 628            anyhow::bail!("Invalid size for render_scene_to_image: {:?}", size);
 629        }
 630
 631        // Update path intermediate textures for this size
 632        self.update_path_intermediate_textures(size);
 633
 634        // Create an offscreen texture as render target
 635        let texture_descriptor = metal::TextureDescriptor::new();
 636        texture_descriptor.set_width(size.width.0 as u64);
 637        texture_descriptor.set_height(size.height.0 as u64);
 638        texture_descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
 639        texture_descriptor
 640            .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
 641        texture_descriptor.set_storage_mode(metal::MTLStorageMode::Managed);
 642        let target_texture = self.device.new_texture(&texture_descriptor);
 643
 644        loop {
 645            let mut instance_buffer = self
 646                .instance_buffer_pool
 647                .lock()
 648                .acquire(&self.device, self.is_unified_memory);
 649
 650            let command_buffer =
 651                self.draw_primitives_to_texture(scene, &mut instance_buffer, &target_texture, size);
 652
 653            match command_buffer {
 654                Ok(command_buffer) => {
 655                    let instance_buffer_pool = self.instance_buffer_pool.clone();
 656                    let instance_buffer = Cell::new(Some(instance_buffer));
 657                    let block = ConcreteBlock::new(move |_| {
 658                        if let Some(instance_buffer) = instance_buffer.take() {
 659                            instance_buffer_pool.lock().release(instance_buffer);
 660                        }
 661                    });
 662                    let block = block.copy();
 663                    command_buffer.add_completed_handler(&block);
 664
 665                    // On discrete GPUs (non-unified memory), Managed textures
 666                    // require an explicit blit synchronize before the CPU can
 667                    // read back the rendered data. Without this, get_bytes
 668                    // returns stale zeros.
 669                    if !self.is_unified_memory {
 670                        let blit = command_buffer.new_blit_command_encoder();
 671                        blit.synchronize_resource(&target_texture);
 672                        blit.end_encoding();
 673                    }
 674
 675                    // Commit and wait for completion
 676                    command_buffer.commit();
 677                    command_buffer.wait_until_completed();
 678
 679                    // Read pixels from the texture
 680                    let width = size.width.0 as u32;
 681                    let height = size.height.0 as u32;
 682                    let bytes_per_row = width as usize * 4;
 683                    let buffer_size = height as usize * bytes_per_row;
 684
 685                    let mut pixels = vec![0u8; buffer_size];
 686
 687                    let region = metal::MTLRegion {
 688                        origin: metal::MTLOrigin { x: 0, y: 0, z: 0 },
 689                        size: metal::MTLSize {
 690                            width: width as u64,
 691                            height: height as u64,
 692                            depth: 1,
 693                        },
 694                    };
 695
 696                    target_texture.get_bytes(
 697                        pixels.as_mut_ptr() as *mut std::ffi::c_void,
 698                        bytes_per_row as u64,
 699                        region,
 700                        0,
 701                    );
 702
 703                    // Convert BGRA to RGBA (swap B and R channels)
 704                    for chunk in pixels.chunks_exact_mut(4) {
 705                        chunk.swap(0, 2);
 706                    }
 707
 708                    return RgbaImage::from_raw(width, height, pixels).ok_or_else(|| {
 709                        anyhow::anyhow!("Failed to create RgbaImage from pixel data")
 710                    });
 711                }
 712                Err(err) => {
 713                    log::error!(
 714                        "failed to render: {}. retrying with larger instance buffer size",
 715                        err
 716                    );
 717                    let mut instance_buffer_pool = self.instance_buffer_pool.lock();
 718                    let buffer_size = instance_buffer_pool.buffer_size;
 719                    if buffer_size >= 256 * 1024 * 1024 {
 720                        anyhow::bail!("instance buffer size grew too large: {}", buffer_size);
 721                    }
 722                    instance_buffer_pool.reset(buffer_size * 2);
 723                    log::info!(
 724                        "increased instance buffer size to {}",
 725                        instance_buffer_pool.buffer_size
 726                    );
 727                }
 728            }
 729        }
 730    }
 731
 732    fn draw_primitives(
 733        &mut self,
 734        scene: &Scene,
 735        instance_buffer: &mut InstanceBuffer,
 736        drawable: &metal::MetalDrawableRef,
 737        viewport_size: Size<DevicePixels>,
 738    ) -> Result<metal::CommandBuffer> {
 739        self.draw_primitives_to_texture(scene, instance_buffer, drawable.texture(), viewport_size)
 740    }
 741
 742    fn draw_primitives_to_texture(
 743        &mut self,
 744        scene: &Scene,
 745        instance_buffer: &mut InstanceBuffer,
 746        texture: &metal::TextureRef,
 747        viewport_size: Size<DevicePixels>,
 748    ) -> Result<metal::CommandBuffer> {
 749        let command_queue = self.command_queue.clone();
 750        let command_buffer = command_queue.new_command_buffer();
 751        let alpha = if self.opaque { 1. } else { 0. };
 752        let mut instance_offset = 0;
 753
 754        let mut command_encoder = new_command_encoder_for_texture(
 755            command_buffer,
 756            texture,
 757            viewport_size,
 758            |color_attachment| {
 759                color_attachment.set_load_action(metal::MTLLoadAction::Clear);
 760                color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
 761            },
 762        );
 763
 764        for batch in scene.batches() {
 765            let ok = match batch {
 766                PrimitiveBatch::Shadows(range) => self.draw_shadows(
 767                    &scene.shadows[range],
 768                    instance_buffer,
 769                    &mut instance_offset,
 770                    viewport_size,
 771                    command_encoder,
 772                ),
 773                PrimitiveBatch::Quads(range) => self.draw_quads(
 774                    &scene.quads[range],
 775                    instance_buffer,
 776                    &mut instance_offset,
 777                    viewport_size,
 778                    command_encoder,
 779                ),
 780                PrimitiveBatch::Paths(range) => {
 781                    let paths = &scene.paths[range];
 782                    command_encoder.end_encoding();
 783
 784                    let did_draw = self.draw_paths_to_intermediate(
 785                        paths,
 786                        instance_buffer,
 787                        &mut instance_offset,
 788                        viewport_size,
 789                        command_buffer,
 790                    );
 791
 792                    command_encoder = new_command_encoder_for_texture(
 793                        command_buffer,
 794                        texture,
 795                        viewport_size,
 796                        |color_attachment| {
 797                            color_attachment.set_load_action(metal::MTLLoadAction::Load);
 798                        },
 799                    );
 800
 801                    if did_draw {
 802                        self.draw_paths_from_intermediate(
 803                            paths,
 804                            instance_buffer,
 805                            &mut instance_offset,
 806                            viewport_size,
 807                            command_encoder,
 808                        )
 809                    } else {
 810                        false
 811                    }
 812                }
 813                PrimitiveBatch::Underlines(range) => self.draw_underlines(
 814                    &scene.underlines[range],
 815                    instance_buffer,
 816                    &mut instance_offset,
 817                    viewport_size,
 818                    command_encoder,
 819                ),
 820                PrimitiveBatch::MonochromeSprites { texture_id, range } => self
 821                    .draw_monochrome_sprites(
 822                        texture_id,
 823                        &scene.monochrome_sprites[range],
 824                        instance_buffer,
 825                        &mut instance_offset,
 826                        viewport_size,
 827                        command_encoder,
 828                    ),
 829                PrimitiveBatch::PolychromeSprites { texture_id, range } => self
 830                    .draw_polychrome_sprites(
 831                        texture_id,
 832                        &scene.polychrome_sprites[range],
 833                        instance_buffer,
 834                        &mut instance_offset,
 835                        viewport_size,
 836                        command_encoder,
 837                    ),
 838                PrimitiveBatch::Surfaces(range) => self.draw_surfaces(
 839                    &scene.surfaces[range],
 840                    instance_buffer,
 841                    &mut instance_offset,
 842                    viewport_size,
 843                    command_encoder,
 844                ),
 845                PrimitiveBatch::SubpixelSprites { .. } => unreachable!(),
 846            };
 847            if !ok {
 848                command_encoder.end_encoding();
 849                anyhow::bail!(
 850                    "scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
 851                    scene.paths.len(),
 852                    scene.shadows.len(),
 853                    scene.quads.len(),
 854                    scene.underlines.len(),
 855                    scene.monochrome_sprites.len(),
 856                    scene.polychrome_sprites.len(),
 857                    scene.surfaces.len(),
 858                );
 859            }
 860        }
 861
 862        command_encoder.end_encoding();
 863
 864        if !self.is_unified_memory {
 865            // Sync the instance buffer to the GPU
 866            instance_buffer.metal_buffer.did_modify_range(NSRange {
 867                location: 0,
 868                length: instance_offset as NSUInteger,
 869            });
 870        }
 871
 872        Ok(command_buffer.to_owned())
 873    }
 874
 875    fn draw_paths_to_intermediate(
 876        &self,
 877        paths: &[Path<ScaledPixels>],
 878        instance_buffer: &mut InstanceBuffer,
 879        instance_offset: &mut usize,
 880        viewport_size: Size<DevicePixels>,
 881        command_buffer: &metal::CommandBufferRef,
 882    ) -> bool {
 883        if paths.is_empty() {
 884            return true;
 885        }
 886        let Some(intermediate_texture) = &self.path_intermediate_texture else {
 887            return false;
 888        };
 889
 890        let render_pass_descriptor = metal::RenderPassDescriptor::new();
 891        let color_attachment = render_pass_descriptor
 892            .color_attachments()
 893            .object_at(0)
 894            .unwrap();
 895        color_attachment.set_load_action(metal::MTLLoadAction::Clear);
 896        color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 0.));
 897
 898        if let Some(msaa_texture) = &self.path_intermediate_msaa_texture {
 899            color_attachment.set_texture(Some(msaa_texture));
 900            color_attachment.set_resolve_texture(Some(intermediate_texture));
 901            color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
 902        } else {
 903            color_attachment.set_texture(Some(intermediate_texture));
 904            color_attachment.set_store_action(metal::MTLStoreAction::Store);
 905        }
 906
 907        let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
 908        command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
 909
 910        align_offset(instance_offset);
 911        let mut vertices = Vec::new();
 912        for path in paths {
 913            vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
 914                xy_position: v.xy_position,
 915                st_position: v.st_position,
 916                color: path.color,
 917                bounds: path.bounds.intersect(&path.content_mask.bounds),
 918            }));
 919        }
 920        let vertices_bytes_len = mem::size_of_val(vertices.as_slice());
 921        let next_offset = *instance_offset + vertices_bytes_len;
 922        if next_offset > instance_buffer.size {
 923            command_encoder.end_encoding();
 924            return false;
 925        }
 926        command_encoder.set_vertex_buffer(
 927            PathRasterizationInputIndex::Vertices as u64,
 928            Some(&instance_buffer.metal_buffer),
 929            *instance_offset as u64,
 930        );
 931        command_encoder.set_vertex_bytes(
 932            PathRasterizationInputIndex::ViewportSize as u64,
 933            mem::size_of_val(&viewport_size) as u64,
 934            &viewport_size as *const Size<DevicePixels> as *const _,
 935        );
 936        command_encoder.set_fragment_buffer(
 937            PathRasterizationInputIndex::Vertices as u64,
 938            Some(&instance_buffer.metal_buffer),
 939            *instance_offset as u64,
 940        );
 941        let buffer_contents =
 942            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
 943        unsafe {
 944            ptr::copy_nonoverlapping(
 945                vertices.as_ptr() as *const u8,
 946                buffer_contents,
 947                vertices_bytes_len,
 948            );
 949        }
 950        command_encoder.draw_primitives(
 951            metal::MTLPrimitiveType::Triangle,
 952            0,
 953            vertices.len() as u64,
 954        );
 955        *instance_offset = next_offset;
 956
 957        command_encoder.end_encoding();
 958        true
 959    }
 960
 961    fn draw_shadows(
 962        &self,
 963        shadows: &[Shadow],
 964        instance_buffer: &mut InstanceBuffer,
 965        instance_offset: &mut usize,
 966        viewport_size: Size<DevicePixels>,
 967        command_encoder: &metal::RenderCommandEncoderRef,
 968    ) -> bool {
 969        if shadows.is_empty() {
 970            return true;
 971        }
 972        align_offset(instance_offset);
 973
 974        command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
 975        command_encoder.set_vertex_buffer(
 976            ShadowInputIndex::Vertices as u64,
 977            Some(&self.unit_vertices),
 978            0,
 979        );
 980        command_encoder.set_vertex_buffer(
 981            ShadowInputIndex::Shadows as u64,
 982            Some(&instance_buffer.metal_buffer),
 983            *instance_offset as u64,
 984        );
 985        command_encoder.set_fragment_buffer(
 986            ShadowInputIndex::Shadows as u64,
 987            Some(&instance_buffer.metal_buffer),
 988            *instance_offset as u64,
 989        );
 990
 991        command_encoder.set_vertex_bytes(
 992            ShadowInputIndex::ViewportSize as u64,
 993            mem::size_of_val(&viewport_size) as u64,
 994            &viewport_size as *const Size<DevicePixels> as *const _,
 995        );
 996
 997        let shadow_bytes_len = mem::size_of_val(shadows);
 998        let buffer_contents =
 999            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
1000
1001        let next_offset = *instance_offset + shadow_bytes_len;
1002        if next_offset > instance_buffer.size {
1003            return false;
1004        }
1005
1006        unsafe {
1007            ptr::copy_nonoverlapping(
1008                shadows.as_ptr() as *const u8,
1009                buffer_contents,
1010                shadow_bytes_len,
1011            );
1012        }
1013
1014        command_encoder.draw_primitives_instanced(
1015            metal::MTLPrimitiveType::Triangle,
1016            0,
1017            6,
1018            shadows.len() as u64,
1019        );
1020        *instance_offset = next_offset;
1021        true
1022    }
1023
1024    fn draw_quads(
1025        &self,
1026        quads: &[Quad],
1027        instance_buffer: &mut InstanceBuffer,
1028        instance_offset: &mut usize,
1029        viewport_size: Size<DevicePixels>,
1030        command_encoder: &metal::RenderCommandEncoderRef,
1031    ) -> bool {
1032        if quads.is_empty() {
1033            return true;
1034        }
1035        align_offset(instance_offset);
1036
1037        command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
1038        command_encoder.set_vertex_buffer(
1039            QuadInputIndex::Vertices as u64,
1040            Some(&self.unit_vertices),
1041            0,
1042        );
1043        command_encoder.set_vertex_buffer(
1044            QuadInputIndex::Quads as u64,
1045            Some(&instance_buffer.metal_buffer),
1046            *instance_offset as u64,
1047        );
1048        command_encoder.set_fragment_buffer(
1049            QuadInputIndex::Quads as u64,
1050            Some(&instance_buffer.metal_buffer),
1051            *instance_offset as u64,
1052        );
1053
1054        command_encoder.set_vertex_bytes(
1055            QuadInputIndex::ViewportSize as u64,
1056            mem::size_of_val(&viewport_size) as u64,
1057            &viewport_size as *const Size<DevicePixels> as *const _,
1058        );
1059
1060        let quad_bytes_len = mem::size_of_val(quads);
1061        let buffer_contents =
1062            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
1063
1064        let next_offset = *instance_offset + quad_bytes_len;
1065        if next_offset > instance_buffer.size {
1066            return false;
1067        }
1068
1069        unsafe {
1070            ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
1071        }
1072
1073        command_encoder.draw_primitives_instanced(
1074            metal::MTLPrimitiveType::Triangle,
1075            0,
1076            6,
1077            quads.len() as u64,
1078        );
1079        *instance_offset = next_offset;
1080        true
1081    }
1082
1083    fn draw_paths_from_intermediate(
1084        &self,
1085        paths: &[Path<ScaledPixels>],
1086        instance_buffer: &mut InstanceBuffer,
1087        instance_offset: &mut usize,
1088        viewport_size: Size<DevicePixels>,
1089        command_encoder: &metal::RenderCommandEncoderRef,
1090    ) -> bool {
1091        let Some(first_path) = paths.first() else {
1092            return true;
1093        };
1094
1095        let Some(ref intermediate_texture) = self.path_intermediate_texture else {
1096            return false;
1097        };
1098
1099        command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
1100        command_encoder.set_vertex_buffer(
1101            SpriteInputIndex::Vertices as u64,
1102            Some(&self.unit_vertices),
1103            0,
1104        );
1105        command_encoder.set_vertex_bytes(
1106            SpriteInputIndex::ViewportSize as u64,
1107            mem::size_of_val(&viewport_size) as u64,
1108            &viewport_size as *const Size<DevicePixels> as *const _,
1109        );
1110
1111        command_encoder.set_fragment_texture(
1112            SpriteInputIndex::AtlasTexture as u64,
1113            Some(intermediate_texture),
1114        );
1115
1116        // When copying paths from the intermediate texture to the drawable,
1117        // each pixel must only be copied once, in case of transparent paths.
1118        //
1119        // If all paths have the same draw order, then their bounds are all
1120        // disjoint, so we can copy each path's bounds individually. If this
1121        // batch combines different draw orders, we perform a single copy
1122        // for a minimal spanning rect.
1123        let sprites;
1124        if paths.last().unwrap().order == first_path.order {
1125            sprites = paths
1126                .iter()
1127                .map(|path| PathSprite {
1128                    bounds: path.clipped_bounds(),
1129                })
1130                .collect();
1131        } else {
1132            let mut bounds = first_path.clipped_bounds();
1133            for path in paths.iter().skip(1) {
1134                bounds = bounds.union(&path.clipped_bounds());
1135            }
1136            sprites = vec![PathSprite { bounds }];
1137        }
1138
1139        align_offset(instance_offset);
1140        let sprite_bytes_len = mem::size_of_val(sprites.as_slice());
1141        let next_offset = *instance_offset + sprite_bytes_len;
1142        if next_offset > instance_buffer.size {
1143            return false;
1144        }
1145
1146        command_encoder.set_vertex_buffer(
1147            SpriteInputIndex::Sprites as u64,
1148            Some(&instance_buffer.metal_buffer),
1149            *instance_offset as u64,
1150        );
1151
1152        let buffer_contents =
1153            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
1154        unsafe {
1155            ptr::copy_nonoverlapping(
1156                sprites.as_ptr() as *const u8,
1157                buffer_contents,
1158                sprite_bytes_len,
1159            );
1160        }
1161
1162        command_encoder.draw_primitives_instanced(
1163            metal::MTLPrimitiveType::Triangle,
1164            0,
1165            6,
1166            sprites.len() as u64,
1167        );
1168        *instance_offset = next_offset;
1169
1170        true
1171    }
1172
1173    fn draw_underlines(
1174        &self,
1175        underlines: &[Underline],
1176        instance_buffer: &mut InstanceBuffer,
1177        instance_offset: &mut usize,
1178        viewport_size: Size<DevicePixels>,
1179        command_encoder: &metal::RenderCommandEncoderRef,
1180    ) -> bool {
1181        if underlines.is_empty() {
1182            return true;
1183        }
1184        align_offset(instance_offset);
1185
1186        command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
1187        command_encoder.set_vertex_buffer(
1188            UnderlineInputIndex::Vertices as u64,
1189            Some(&self.unit_vertices),
1190            0,
1191        );
1192        command_encoder.set_vertex_buffer(
1193            UnderlineInputIndex::Underlines as u64,
1194            Some(&instance_buffer.metal_buffer),
1195            *instance_offset as u64,
1196        );
1197        command_encoder.set_fragment_buffer(
1198            UnderlineInputIndex::Underlines as u64,
1199            Some(&instance_buffer.metal_buffer),
1200            *instance_offset as u64,
1201        );
1202
1203        command_encoder.set_vertex_bytes(
1204            UnderlineInputIndex::ViewportSize as u64,
1205            mem::size_of_val(&viewport_size) as u64,
1206            &viewport_size as *const Size<DevicePixels> as *const _,
1207        );
1208
1209        let underline_bytes_len = mem::size_of_val(underlines);
1210        let buffer_contents =
1211            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
1212
1213        let next_offset = *instance_offset + underline_bytes_len;
1214        if next_offset > instance_buffer.size {
1215            return false;
1216        }
1217
1218        unsafe {
1219            ptr::copy_nonoverlapping(
1220                underlines.as_ptr() as *const u8,
1221                buffer_contents,
1222                underline_bytes_len,
1223            );
1224        }
1225
1226        command_encoder.draw_primitives_instanced(
1227            metal::MTLPrimitiveType::Triangle,
1228            0,
1229            6,
1230            underlines.len() as u64,
1231        );
1232        *instance_offset = next_offset;
1233        true
1234    }
1235
1236    fn draw_monochrome_sprites(
1237        &self,
1238        texture_id: AtlasTextureId,
1239        sprites: &[MonochromeSprite],
1240        instance_buffer: &mut InstanceBuffer,
1241        instance_offset: &mut usize,
1242        viewport_size: Size<DevicePixels>,
1243        command_encoder: &metal::RenderCommandEncoderRef,
1244    ) -> bool {
1245        if sprites.is_empty() {
1246            return true;
1247        }
1248        align_offset(instance_offset);
1249
1250        let sprite_bytes_len = mem::size_of_val(sprites);
1251        let buffer_contents =
1252            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
1253
1254        let next_offset = *instance_offset + sprite_bytes_len;
1255        if next_offset > instance_buffer.size {
1256            return false;
1257        }
1258
1259        let texture = self.sprite_atlas.metal_texture(texture_id);
1260        let texture_size = size(
1261            DevicePixels(texture.width() as i32),
1262            DevicePixels(texture.height() as i32),
1263        );
1264        command_encoder.set_render_pipeline_state(&self.monochrome_sprites_pipeline_state);
1265        command_encoder.set_vertex_buffer(
1266            SpriteInputIndex::Vertices as u64,
1267            Some(&self.unit_vertices),
1268            0,
1269        );
1270        command_encoder.set_vertex_buffer(
1271            SpriteInputIndex::Sprites as u64,
1272            Some(&instance_buffer.metal_buffer),
1273            *instance_offset as u64,
1274        );
1275        command_encoder.set_vertex_bytes(
1276            SpriteInputIndex::ViewportSize as u64,
1277            mem::size_of_val(&viewport_size) as u64,
1278            &viewport_size as *const Size<DevicePixels> as *const _,
1279        );
1280        command_encoder.set_vertex_bytes(
1281            SpriteInputIndex::AtlasTextureSize as u64,
1282            mem::size_of_val(&texture_size) as u64,
1283            &texture_size as *const Size<DevicePixels> as *const _,
1284        );
1285        command_encoder.set_fragment_buffer(
1286            SpriteInputIndex::Sprites as u64,
1287            Some(&instance_buffer.metal_buffer),
1288            *instance_offset as u64,
1289        );
1290        command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
1291
1292        unsafe {
1293            ptr::copy_nonoverlapping(
1294                sprites.as_ptr() as *const u8,
1295                buffer_contents,
1296                sprite_bytes_len,
1297            );
1298        }
1299
1300        command_encoder.draw_primitives_instanced(
1301            metal::MTLPrimitiveType::Triangle,
1302            0,
1303            6,
1304            sprites.len() as u64,
1305        );
1306        *instance_offset = next_offset;
1307        true
1308    }
1309
1310    fn draw_polychrome_sprites(
1311        &self,
1312        texture_id: AtlasTextureId,
1313        sprites: &[PolychromeSprite],
1314        instance_buffer: &mut InstanceBuffer,
1315        instance_offset: &mut usize,
1316        viewport_size: Size<DevicePixels>,
1317        command_encoder: &metal::RenderCommandEncoderRef,
1318    ) -> bool {
1319        if sprites.is_empty() {
1320            return true;
1321        }
1322        align_offset(instance_offset);
1323
1324        let texture = self.sprite_atlas.metal_texture(texture_id);
1325        let texture_size = size(
1326            DevicePixels(texture.width() as i32),
1327            DevicePixels(texture.height() as i32),
1328        );
1329        command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state);
1330        command_encoder.set_vertex_buffer(
1331            SpriteInputIndex::Vertices as u64,
1332            Some(&self.unit_vertices),
1333            0,
1334        );
1335        command_encoder.set_vertex_buffer(
1336            SpriteInputIndex::Sprites as u64,
1337            Some(&instance_buffer.metal_buffer),
1338            *instance_offset as u64,
1339        );
1340        command_encoder.set_vertex_bytes(
1341            SpriteInputIndex::ViewportSize as u64,
1342            mem::size_of_val(&viewport_size) as u64,
1343            &viewport_size as *const Size<DevicePixels> as *const _,
1344        );
1345        command_encoder.set_vertex_bytes(
1346            SpriteInputIndex::AtlasTextureSize as u64,
1347            mem::size_of_val(&texture_size) as u64,
1348            &texture_size as *const Size<DevicePixels> as *const _,
1349        );
1350        command_encoder.set_fragment_buffer(
1351            SpriteInputIndex::Sprites as u64,
1352            Some(&instance_buffer.metal_buffer),
1353            *instance_offset as u64,
1354        );
1355        command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
1356
1357        let sprite_bytes_len = mem::size_of_val(sprites);
1358        let buffer_contents =
1359            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
1360
1361        let next_offset = *instance_offset + sprite_bytes_len;
1362        if next_offset > instance_buffer.size {
1363            return false;
1364        }
1365
1366        unsafe {
1367            ptr::copy_nonoverlapping(
1368                sprites.as_ptr() as *const u8,
1369                buffer_contents,
1370                sprite_bytes_len,
1371            );
1372        }
1373
1374        command_encoder.draw_primitives_instanced(
1375            metal::MTLPrimitiveType::Triangle,
1376            0,
1377            6,
1378            sprites.len() as u64,
1379        );
1380        *instance_offset = next_offset;
1381        true
1382    }
1383
1384    fn draw_surfaces(
1385        &mut self,
1386        surfaces: &[PaintSurface],
1387        instance_buffer: &mut InstanceBuffer,
1388        instance_offset: &mut usize,
1389        viewport_size: Size<DevicePixels>,
1390        command_encoder: &metal::RenderCommandEncoderRef,
1391    ) -> bool {
1392        command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state);
1393        command_encoder.set_vertex_buffer(
1394            SurfaceInputIndex::Vertices as u64,
1395            Some(&self.unit_vertices),
1396            0,
1397        );
1398        command_encoder.set_vertex_bytes(
1399            SurfaceInputIndex::ViewportSize as u64,
1400            mem::size_of_val(&viewport_size) as u64,
1401            &viewport_size as *const Size<DevicePixels> as *const _,
1402        );
1403
1404        for surface in surfaces {
1405            let texture_size = size(
1406                DevicePixels::from(surface.image_buffer.get_width() as i32),
1407                DevicePixels::from(surface.image_buffer.get_height() as i32),
1408            );
1409
1410            assert_eq!(
1411                surface.image_buffer.get_pixel_format(),
1412                kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
1413            );
1414
1415            let y_texture = self
1416                .core_video_texture_cache
1417                .create_texture_from_image(
1418                    surface.image_buffer.as_concrete_TypeRef(),
1419                    None,
1420                    MTLPixelFormat::R8Unorm,
1421                    surface.image_buffer.get_width_of_plane(0),
1422                    surface.image_buffer.get_height_of_plane(0),
1423                    0,
1424                )
1425                .unwrap();
1426            let cb_cr_texture = self
1427                .core_video_texture_cache
1428                .create_texture_from_image(
1429                    surface.image_buffer.as_concrete_TypeRef(),
1430                    None,
1431                    MTLPixelFormat::RG8Unorm,
1432                    surface.image_buffer.get_width_of_plane(1),
1433                    surface.image_buffer.get_height_of_plane(1),
1434                    1,
1435                )
1436                .unwrap();
1437
1438            align_offset(instance_offset);
1439            let next_offset = *instance_offset + mem::size_of::<Surface>();
1440            if next_offset > instance_buffer.size {
1441                return false;
1442            }
1443
1444            command_encoder.set_vertex_buffer(
1445                SurfaceInputIndex::Surfaces as u64,
1446                Some(&instance_buffer.metal_buffer),
1447                *instance_offset as u64,
1448            );
1449            command_encoder.set_vertex_bytes(
1450                SurfaceInputIndex::TextureSize as u64,
1451                mem::size_of_val(&texture_size) as u64,
1452                &texture_size as *const Size<DevicePixels> as *const _,
1453            );
1454            // let y_texture = y_texture.get_texture().unwrap().
1455            command_encoder.set_fragment_texture(SurfaceInputIndex::YTexture as u64, unsafe {
1456                let texture = CVMetalTextureGetTexture(y_texture.as_concrete_TypeRef());
1457                Some(metal::TextureRef::from_ptr(texture as *mut _))
1458            });
1459            command_encoder.set_fragment_texture(SurfaceInputIndex::CbCrTexture as u64, unsafe {
1460                let texture = CVMetalTextureGetTexture(cb_cr_texture.as_concrete_TypeRef());
1461                Some(metal::TextureRef::from_ptr(texture as *mut _))
1462            });
1463
1464            unsafe {
1465                let buffer_contents = (instance_buffer.metal_buffer.contents() as *mut u8)
1466                    .add(*instance_offset)
1467                    as *mut SurfaceBounds;
1468                ptr::write(
1469                    buffer_contents,
1470                    SurfaceBounds {
1471                        bounds: surface.bounds,
1472                        content_mask: surface.content_mask.clone(),
1473                    },
1474                );
1475            }
1476
1477            command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
1478            *instance_offset = next_offset;
1479        }
1480        true
1481    }
1482}
1483
1484fn new_command_encoder_for_texture<'a>(
1485    command_buffer: &'a metal::CommandBufferRef,
1486    texture: &'a metal::TextureRef,
1487    viewport_size: Size<DevicePixels>,
1488    configure_color_attachment: impl Fn(&RenderPassColorAttachmentDescriptorRef),
1489) -> &'a metal::RenderCommandEncoderRef {
1490    let render_pass_descriptor = metal::RenderPassDescriptor::new();
1491    let color_attachment = render_pass_descriptor
1492        .color_attachments()
1493        .object_at(0)
1494        .unwrap();
1495    color_attachment.set_texture(Some(texture));
1496    color_attachment.set_store_action(metal::MTLStoreAction::Store);
1497    configure_color_attachment(color_attachment);
1498
1499    let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
1500    command_encoder.set_viewport(metal::MTLViewport {
1501        originX: 0.0,
1502        originY: 0.0,
1503        width: i32::from(viewport_size.width) as f64,
1504        height: i32::from(viewport_size.height) as f64,
1505        znear: 0.0,
1506        zfar: 1.0,
1507    });
1508    command_encoder
1509}
1510
1511fn build_pipeline_state(
1512    device: &metal::DeviceRef,
1513    library: &metal::LibraryRef,
1514    label: &str,
1515    vertex_fn_name: &str,
1516    fragment_fn_name: &str,
1517    pixel_format: metal::MTLPixelFormat,
1518) -> metal::RenderPipelineState {
1519    let vertex_fn = library
1520        .get_function(vertex_fn_name, None)
1521        .expect("error locating vertex function");
1522    let fragment_fn = library
1523        .get_function(fragment_fn_name, None)
1524        .expect("error locating fragment function");
1525
1526    let descriptor = metal::RenderPipelineDescriptor::new();
1527    descriptor.set_label(label);
1528    descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
1529    descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
1530    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
1531    color_attachment.set_pixel_format(pixel_format);
1532    color_attachment.set_blending_enabled(true);
1533    color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
1534    color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
1535    color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
1536    color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
1537    color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1538    color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
1539
1540    device
1541        .new_render_pipeline_state(&descriptor)
1542        .expect("could not create render pipeline state")
1543}
1544
1545fn build_path_sprite_pipeline_state(
1546    device: &metal::DeviceRef,
1547    library: &metal::LibraryRef,
1548    label: &str,
1549    vertex_fn_name: &str,
1550    fragment_fn_name: &str,
1551    pixel_format: metal::MTLPixelFormat,
1552) -> metal::RenderPipelineState {
1553    let vertex_fn = library
1554        .get_function(vertex_fn_name, None)
1555        .expect("error locating vertex function");
1556    let fragment_fn = library
1557        .get_function(fragment_fn_name, None)
1558        .expect("error locating fragment function");
1559
1560    let descriptor = metal::RenderPipelineDescriptor::new();
1561    descriptor.set_label(label);
1562    descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
1563    descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
1564    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
1565    color_attachment.set_pixel_format(pixel_format);
1566    color_attachment.set_blending_enabled(true);
1567    color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
1568    color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
1569    color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
1570    color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
1571    color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1572    color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
1573
1574    device
1575        .new_render_pipeline_state(&descriptor)
1576        .expect("could not create render pipeline state")
1577}
1578
1579fn build_path_rasterization_pipeline_state(
1580    device: &metal::DeviceRef,
1581    library: &metal::LibraryRef,
1582    label: &str,
1583    vertex_fn_name: &str,
1584    fragment_fn_name: &str,
1585    pixel_format: metal::MTLPixelFormat,
1586    path_sample_count: u32,
1587) -> metal::RenderPipelineState {
1588    let vertex_fn = library
1589        .get_function(vertex_fn_name, None)
1590        .expect("error locating vertex function");
1591    let fragment_fn = library
1592        .get_function(fragment_fn_name, None)
1593        .expect("error locating fragment function");
1594
1595    let descriptor = metal::RenderPipelineDescriptor::new();
1596    descriptor.set_label(label);
1597    descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
1598    descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
1599    if path_sample_count > 1 {
1600        descriptor.set_raster_sample_count(path_sample_count as _);
1601        descriptor.set_alpha_to_coverage_enabled(false);
1602    }
1603    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
1604    color_attachment.set_pixel_format(pixel_format);
1605    color_attachment.set_blending_enabled(true);
1606    color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
1607    color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
1608    color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
1609    color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
1610    color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1611    color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1612
1613    device
1614        .new_render_pipeline_state(&descriptor)
1615        .expect("could not create render pipeline state")
1616}
1617
1618// Align to multiples of 256 make Metal happy.
1619fn align_offset(offset: &mut usize) {
1620    *offset = (*offset).div_ceil(256) * 256;
1621}
1622
1623#[repr(C)]
1624enum ShadowInputIndex {
1625    Vertices = 0,
1626    Shadows = 1,
1627    ViewportSize = 2,
1628}
1629
1630#[repr(C)]
1631enum QuadInputIndex {
1632    Vertices = 0,
1633    Quads = 1,
1634    ViewportSize = 2,
1635}
1636
1637#[repr(C)]
1638enum UnderlineInputIndex {
1639    Vertices = 0,
1640    Underlines = 1,
1641    ViewportSize = 2,
1642}
1643
1644#[repr(C)]
1645enum SpriteInputIndex {
1646    Vertices = 0,
1647    Sprites = 1,
1648    ViewportSize = 2,
1649    AtlasTextureSize = 3,
1650    AtlasTexture = 4,
1651}
1652
1653#[repr(C)]
1654enum SurfaceInputIndex {
1655    Vertices = 0,
1656    Surfaces = 1,
1657    ViewportSize = 2,
1658    TextureSize = 3,
1659    YTexture = 4,
1660    CbCrTexture = 5,
1661}
1662
1663#[repr(C)]
1664enum PathRasterizationInputIndex {
1665    Vertices = 0,
1666    ViewportSize = 1,
1667}
1668
1669#[derive(Clone, Debug, Eq, PartialEq)]
1670#[repr(C)]
1671pub struct PathSprite {
1672    pub bounds: Bounds<ScaledPixels>,
1673}
1674
1675#[derive(Clone, Debug, Eq, PartialEq)]
1676#[repr(C)]
1677pub struct SurfaceBounds {
1678    pub bounds: Bounds<ScaledPixels>,
1679    pub content_mask: ContentMask<ScaledPixels>,
1680}
1681
1682#[cfg(any(test, feature = "test-support"))]
1683pub struct MetalHeadlessRenderer {
1684    renderer: MetalRenderer,
1685}
1686
1687#[cfg(any(test, feature = "test-support"))]
1688impl MetalHeadlessRenderer {
1689    pub fn new() -> Self {
1690        let instance_buffer_pool = Arc::new(Mutex::new(InstanceBufferPool::default()));
1691        let renderer = MetalRenderer::new_headless(instance_buffer_pool);
1692        Self { renderer }
1693    }
1694}
1695
1696#[cfg(any(test, feature = "test-support"))]
1697impl gpui::PlatformHeadlessRenderer for MetalHeadlessRenderer {
1698    fn render_scene_to_image(
1699        &mut self,
1700        scene: &Scene,
1701        size: Size<DevicePixels>,
1702    ) -> anyhow::Result<image::RgbaImage> {
1703        self.renderer.render_scene_to_image(scene, size)
1704    }
1705
1706    fn sprite_atlas(&self) -> Arc<dyn gpui::PlatformAtlas> {
1707        self.renderer.sprite_atlas().clone()
1708    }
1709}