Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:46:52.684Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

metal_renderer.rs

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