metal_renderer.rs

   1use super::metal_atlas::MetalAtlas;
   2use crate::{
   3    AtlasTextureId, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface,
   4    Path, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
   5    Surface, Underline, point, size,
   6};
   7use anyhow::Result;
   8use block::ConcreteBlock;
   9use cocoa::{
  10    base::{NO, YES},
  11    foundation::{NSSize, NSUInteger},
  12    quartzcore::AutoresizingMask,
  13};
  14use core_foundation::base::TCFType;
  15use core_video::{
  16    metal_texture::CVMetalTextureGetTexture, metal_texture_cache::CVMetalTextureCache,
  17    pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
  18};
  19use foreign_types::{ForeignType, ForeignTypeRef};
  20use metal::{
  21    CAMetalLayer, CommandQueue, MTLDrawPrimitivesIndirectArguments, MTLPixelFormat,
  22    MTLResourceOptions, NSRange,
  23};
  24use objc::{self, msg_send, sel, sel_impl};
  25use parking_lot::Mutex;
  26use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc};
  27
  28// Exported to metal
  29pub(crate) type PointF = crate::Point<f32>;
  30
  31#[cfg(not(feature = "runtime_shaders"))]
  32const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
  33#[cfg(feature = "runtime_shaders")]
  34const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
  35
  36pub type Context = Arc<Mutex<InstanceBufferPool>>;
  37pub type Renderer = MetalRenderer;
  38
  39pub unsafe fn new_renderer(
  40    context: self::Context,
  41    _native_window: *mut c_void,
  42    _native_view: *mut c_void,
  43    _bounds: crate::Size<f32>,
  44    _transparent: bool,
  45) -> Renderer {
  46    MetalRenderer::new(context)
  47}
  48
  49pub(crate) struct InstanceBufferPool {
  50    buffer_size: usize,
  51    buffers: Vec<metal::Buffer>,
  52}
  53
  54impl Default for InstanceBufferPool {
  55    fn default() -> Self {
  56        Self {
  57            buffer_size: 2 * 1024 * 1024,
  58            buffers: Vec::new(),
  59        }
  60    }
  61}
  62
  63pub(crate) struct InstanceBuffer {
  64    metal_buffer: metal::Buffer,
  65    size: usize,
  66}
  67
  68impl InstanceBufferPool {
  69    pub(crate) fn reset(&mut self, buffer_size: usize) {
  70        self.buffer_size = buffer_size;
  71        self.buffers.clear();
  72    }
  73
  74    pub(crate) fn acquire(&mut self, device: &metal::Device) -> InstanceBuffer {
  75        let buffer = self.buffers.pop().unwrap_or_else(|| {
  76            device.new_buffer(
  77                self.buffer_size as u64,
  78                MTLResourceOptions::StorageModeManaged,
  79            )
  80        });
  81        InstanceBuffer {
  82            metal_buffer: buffer,
  83            size: self.buffer_size,
  84        }
  85    }
  86
  87    pub(crate) fn release(&mut self, buffer: InstanceBuffer) {
  88        if buffer.size == self.buffer_size {
  89            self.buffers.push(buffer.metal_buffer)
  90        }
  91    }
  92}
  93
  94pub(crate) struct MetalRenderer {
  95    device: metal::Device,
  96    layer: metal::MetalLayer,
  97    presents_with_transaction: bool,
  98    command_queue: CommandQueue,
  99    path_pipeline_state: metal::RenderPipelineState,
 100    shadows_pipeline_state: metal::RenderPipelineState,
 101    quads_pipeline_state: metal::RenderPipelineState,
 102    underlines_pipeline_state: metal::RenderPipelineState,
 103    monochrome_sprites_pipeline_state: metal::RenderPipelineState,
 104    polychrome_sprites_pipeline_state: metal::RenderPipelineState,
 105    surfaces_pipeline_state: metal::RenderPipelineState,
 106    unit_vertices: metal::Buffer,
 107    #[allow(clippy::arc_with_non_send_sync)]
 108    instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
 109    sprite_atlas: Arc<MetalAtlas>,
 110    core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache,
 111    sample_count: u64,
 112    msaa_texture: Option<metal::Texture>,
 113}
 114
 115impl MetalRenderer {
 116    pub fn new(instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>) -> Self {
 117        // Prefer low‐power integrated GPUs on Intel Mac. On Apple
 118        // Silicon, there is only ever one GPU, so this is equivalent to
 119        // `metal::Device::system_default()`.
 120        let mut devices = metal::Device::all();
 121        devices.sort_by_key(|device| (device.is_removable(), device.is_low_power()));
 122        let Some(device) = devices.pop() else {
 123            log::error!("unable to access a compatible graphics device");
 124            std::process::exit(1);
 125        };
 126
 127        let layer = metal::MetalLayer::new();
 128        layer.set_device(&device);
 129        layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
 130        layer.set_opaque(false);
 131        layer.set_maximum_drawable_count(3);
 132        unsafe {
 133            let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
 134            let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
 135            let _: () = msg_send![
 136                &*layer,
 137                setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
 138                    | AutoresizingMask::HEIGHT_SIZABLE
 139            ];
 140        }
 141        #[cfg(feature = "runtime_shaders")]
 142        let library = device
 143            .new_library_with_source(&SHADERS_SOURCE_FILE, &metal::CompileOptions::new())
 144            .expect("error building metal library");
 145        #[cfg(not(feature = "runtime_shaders"))]
 146        let library = device
 147            .new_library_with_data(SHADERS_METALLIB)
 148            .expect("error building metal library");
 149
 150        fn to_float2_bits(point: PointF) -> u64 {
 151            let mut output = point.y.to_bits() as u64;
 152            output <<= 32;
 153            output |= point.x.to_bits() as u64;
 154            output
 155        }
 156
 157        let unit_vertices = [
 158            to_float2_bits(point(0., 0.)),
 159            to_float2_bits(point(1., 0.)),
 160            to_float2_bits(point(0., 1.)),
 161            to_float2_bits(point(0., 1.)),
 162            to_float2_bits(point(1., 0.)),
 163            to_float2_bits(point(1., 1.)),
 164        ];
 165        let unit_vertices = device.new_buffer_with_data(
 166            unit_vertices.as_ptr() as *const c_void,
 167            mem::size_of_val(&unit_vertices) as u64,
 168            MTLResourceOptions::StorageModeManaged,
 169        );
 170
 171        let sample_count = [4, 2, 1]
 172            .into_iter()
 173            .find(|count| device.supports_texture_sample_count(*count))
 174            .unwrap_or(1);
 175
 176        let path_pipeline_state = build_pipeline_state(
 177            &device,
 178            &library,
 179            "paths",
 180            "path_vertex",
 181            "path_fragment",
 182            MTLPixelFormat::BGRA8Unorm,
 183            sample_count,
 184        );
 185        let shadows_pipeline_state = build_pipeline_state(
 186            &device,
 187            &library,
 188            "shadows",
 189            "shadow_vertex",
 190            "shadow_fragment",
 191            MTLPixelFormat::BGRA8Unorm,
 192            sample_count,
 193        );
 194        let quads_pipeline_state = build_pipeline_state(
 195            &device,
 196            &library,
 197            "quads",
 198            "quad_vertex",
 199            "quad_fragment",
 200            MTLPixelFormat::BGRA8Unorm,
 201            sample_count,
 202        );
 203        let underlines_pipeline_state = build_pipeline_state(
 204            &device,
 205            &library,
 206            "underlines",
 207            "underline_vertex",
 208            "underline_fragment",
 209            MTLPixelFormat::BGRA8Unorm,
 210            sample_count,
 211        );
 212        let monochrome_sprites_pipeline_state = build_pipeline_state(
 213            &device,
 214            &library,
 215            "monochrome_sprites",
 216            "monochrome_sprite_vertex",
 217            "monochrome_sprite_fragment",
 218            MTLPixelFormat::BGRA8Unorm,
 219            sample_count,
 220        );
 221        let polychrome_sprites_pipeline_state = build_pipeline_state(
 222            &device,
 223            &library,
 224            "polychrome_sprites",
 225            "polychrome_sprite_vertex",
 226            "polychrome_sprite_fragment",
 227            MTLPixelFormat::BGRA8Unorm,
 228            sample_count,
 229        );
 230        let surfaces_pipeline_state = build_pipeline_state(
 231            &device,
 232            &library,
 233            "surfaces",
 234            "surface_vertex",
 235            "surface_fragment",
 236            MTLPixelFormat::BGRA8Unorm,
 237            sample_count,
 238        );
 239
 240        let command_queue = device.new_command_queue();
 241        let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
 242        let core_video_texture_cache =
 243            CVMetalTextureCache::new(None, device.clone(), None).unwrap();
 244        let msaa_texture = create_msaa_texture(&device, &layer, sample_count);
 245
 246        Self {
 247            device,
 248            layer,
 249            presents_with_transaction: false,
 250            command_queue,
 251            path_pipeline_state,
 252            shadows_pipeline_state,
 253            quads_pipeline_state,
 254            underlines_pipeline_state,
 255            monochrome_sprites_pipeline_state,
 256            polychrome_sprites_pipeline_state,
 257            surfaces_pipeline_state,
 258            unit_vertices,
 259            instance_buffer_pool,
 260            sprite_atlas,
 261            core_video_texture_cache,
 262            sample_count,
 263            msaa_texture,
 264        }
 265    }
 266
 267    pub fn layer(&self) -> &metal::MetalLayerRef {
 268        &self.layer
 269    }
 270
 271    pub fn layer_ptr(&self) -> *mut CAMetalLayer {
 272        self.layer.as_ptr()
 273    }
 274
 275    pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
 276        &self.sprite_atlas
 277    }
 278
 279    pub fn set_presents_with_transaction(&mut self, presents_with_transaction: bool) {
 280        self.presents_with_transaction = presents_with_transaction;
 281        self.layer
 282            .set_presents_with_transaction(presents_with_transaction);
 283    }
 284
 285    pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
 286        let size = NSSize {
 287            width: size.width.0 as f64,
 288            height: size.height.0 as f64,
 289        };
 290        unsafe {
 291            let _: () = msg_send![
 292                self.layer(),
 293                setDrawableSize: size
 294            ];
 295        }
 296
 297        self.msaa_texture = create_msaa_texture(&self.device, &self.layer, self.sample_count);
 298    }
 299
 300    pub fn update_transparency(&self, _transparent: bool) {
 301        // todo(mac)?
 302    }
 303
 304    pub fn destroy(&self) {
 305        // nothing to do
 306    }
 307
 308    pub fn draw(&mut self, scene: &Scene) {
 309        let layer = self.layer.clone();
 310        let viewport_size = layer.drawable_size();
 311        let viewport_size: Size<DevicePixels> = size(
 312            (viewport_size.width.ceil() as i32).into(),
 313            (viewport_size.height.ceil() as i32).into(),
 314        );
 315        let drawable = if let Some(drawable) = layer.next_drawable() {
 316            drawable
 317        } else {
 318            log::error!(
 319                "failed to retrieve next drawable, drawable size: {:?}",
 320                viewport_size
 321            );
 322            return;
 323        };
 324
 325        loop {
 326            let mut instance_buffer = self.instance_buffer_pool.lock().acquire(&self.device);
 327
 328            let command_buffer =
 329                self.draw_primitives(scene, &mut instance_buffer, drawable, viewport_size);
 330
 331            match command_buffer {
 332                Ok(command_buffer) => {
 333                    let instance_buffer_pool = self.instance_buffer_pool.clone();
 334                    let instance_buffer = Cell::new(Some(instance_buffer));
 335                    let block = ConcreteBlock::new(move |_| {
 336                        if let Some(instance_buffer) = instance_buffer.take() {
 337                            instance_buffer_pool.lock().release(instance_buffer);
 338                        }
 339                    });
 340                    let block = block.copy();
 341                    command_buffer.add_completed_handler(&block);
 342
 343                    if self.presents_with_transaction {
 344                        command_buffer.commit();
 345                        command_buffer.wait_until_scheduled();
 346                        drawable.present();
 347                    } else {
 348                        command_buffer.present_drawable(drawable);
 349                        command_buffer.commit();
 350                    }
 351                    return;
 352                }
 353                Err(err) => {
 354                    log::error!(
 355                        "failed to render: {}. retrying with larger instance buffer size",
 356                        err
 357                    );
 358                    let mut instance_buffer_pool = self.instance_buffer_pool.lock();
 359                    let buffer_size = instance_buffer_pool.buffer_size;
 360                    if buffer_size >= 256 * 1024 * 1024 {
 361                        log::error!("instance buffer size grew too large: {}", buffer_size);
 362                        break;
 363                    }
 364                    instance_buffer_pool.reset(buffer_size * 2);
 365                    log::info!(
 366                        "increased instance buffer size to {}",
 367                        instance_buffer_pool.buffer_size
 368                    );
 369                }
 370            }
 371        }
 372    }
 373
 374    fn draw_primitives(
 375        &mut self,
 376        scene: &Scene,
 377        instance_buffer: &mut InstanceBuffer,
 378        drawable: &metal::MetalDrawableRef,
 379        viewport_size: Size<DevicePixels>,
 380    ) -> Result<metal::CommandBuffer> {
 381        let command_queue = self.command_queue.clone();
 382        let command_buffer = command_queue.new_command_buffer();
 383        let mut instance_offset = 0;
 384        let render_pass_descriptor = metal::RenderPassDescriptor::new();
 385        let color_attachment = render_pass_descriptor
 386            .color_attachments()
 387            .object_at(0)
 388            .unwrap();
 389
 390        if let Some(msaa_texture_ref) = self.msaa_texture.as_deref() {
 391            color_attachment.set_texture(Some(msaa_texture_ref));
 392            color_attachment.set_load_action(metal::MTLLoadAction::Clear);
 393            color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
 394            color_attachment.set_resolve_texture(Some(drawable.texture()));
 395        } else {
 396            color_attachment.set_load_action(metal::MTLLoadAction::Clear);
 397            color_attachment.set_texture(Some(drawable.texture()));
 398            color_attachment.set_store_action(metal::MTLStoreAction::Store);
 399        }
 400
 401        let alpha = if self.layer.is_opaque() { 1. } else { 0. };
 402        color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
 403        let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
 404
 405        command_encoder.set_viewport(metal::MTLViewport {
 406            originX: 0.0,
 407            originY: 0.0,
 408            width: i32::from(viewport_size.width) as f64,
 409            height: i32::from(viewport_size.height) as f64,
 410            znear: 0.0,
 411            zfar: 1.0,
 412        });
 413
 414        for batch in scene.batches() {
 415            let ok = match batch {
 416                PrimitiveBatch::Shadows(shadows) => self.draw_shadows(
 417                    shadows,
 418                    instance_buffer,
 419                    &mut instance_offset,
 420                    viewport_size,
 421                    command_encoder,
 422                ),
 423                PrimitiveBatch::Quads(quads) => self.draw_quads(
 424                    quads,
 425                    instance_buffer,
 426                    &mut instance_offset,
 427                    viewport_size,
 428                    command_encoder,
 429                ),
 430                PrimitiveBatch::Paths(paths) => self.draw_paths(
 431                    paths,
 432                    instance_buffer,
 433                    &mut instance_offset,
 434                    viewport_size,
 435                    command_encoder,
 436                ),
 437                PrimitiveBatch::Underlines(underlines) => self.draw_underlines(
 438                    underlines,
 439                    instance_buffer,
 440                    &mut instance_offset,
 441                    viewport_size,
 442                    command_encoder,
 443                ),
 444                PrimitiveBatch::MonochromeSprites {
 445                    texture_id,
 446                    sprites,
 447                } => self.draw_monochrome_sprites(
 448                    texture_id,
 449                    sprites,
 450                    instance_buffer,
 451                    &mut instance_offset,
 452                    viewport_size,
 453                    command_encoder,
 454                ),
 455                PrimitiveBatch::PolychromeSprites {
 456                    texture_id,
 457                    sprites,
 458                } => self.draw_polychrome_sprites(
 459                    texture_id,
 460                    sprites,
 461                    instance_buffer,
 462                    &mut instance_offset,
 463                    viewport_size,
 464                    command_encoder,
 465                ),
 466                PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(
 467                    surfaces,
 468                    instance_buffer,
 469                    &mut instance_offset,
 470                    viewport_size,
 471                    command_encoder,
 472                ),
 473            };
 474
 475            if !ok {
 476                command_encoder.end_encoding();
 477                anyhow::bail!(
 478                    "scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
 479                    scene.paths.len(),
 480                    scene.shadows.len(),
 481                    scene.quads.len(),
 482                    scene.underlines.len(),
 483                    scene.monochrome_sprites.len(),
 484                    scene.polychrome_sprites.len(),
 485                    scene.surfaces.len(),
 486                );
 487            }
 488        }
 489
 490        command_encoder.end_encoding();
 491
 492        instance_buffer.metal_buffer.did_modify_range(NSRange {
 493            location: 0,
 494            length: instance_offset as NSUInteger,
 495        });
 496        Ok(command_buffer.to_owned())
 497    }
 498
 499    fn draw_shadows(
 500        &self,
 501        shadows: &[Shadow],
 502        instance_buffer: &mut InstanceBuffer,
 503        instance_offset: &mut usize,
 504        viewport_size: Size<DevicePixels>,
 505        command_encoder: &metal::RenderCommandEncoderRef,
 506    ) -> bool {
 507        if shadows.is_empty() {
 508            return true;
 509        }
 510        align_offset(instance_offset);
 511
 512        command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
 513        command_encoder.set_vertex_buffer(
 514            ShadowInputIndex::Vertices as u64,
 515            Some(&self.unit_vertices),
 516            0,
 517        );
 518        command_encoder.set_vertex_buffer(
 519            ShadowInputIndex::Shadows as u64,
 520            Some(&instance_buffer.metal_buffer),
 521            *instance_offset as u64,
 522        );
 523        command_encoder.set_fragment_buffer(
 524            ShadowInputIndex::Shadows as u64,
 525            Some(&instance_buffer.metal_buffer),
 526            *instance_offset as u64,
 527        );
 528
 529        command_encoder.set_vertex_bytes(
 530            ShadowInputIndex::ViewportSize as u64,
 531            mem::size_of_val(&viewport_size) as u64,
 532            &viewport_size as *const Size<DevicePixels> as *const _,
 533        );
 534
 535        let shadow_bytes_len = mem::size_of_val(shadows);
 536        let buffer_contents =
 537            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
 538
 539        let next_offset = *instance_offset + shadow_bytes_len;
 540        if next_offset > instance_buffer.size {
 541            return false;
 542        }
 543
 544        unsafe {
 545            ptr::copy_nonoverlapping(
 546                shadows.as_ptr() as *const u8,
 547                buffer_contents,
 548                shadow_bytes_len,
 549            );
 550        }
 551
 552        command_encoder.draw_primitives_instanced(
 553            metal::MTLPrimitiveType::Triangle,
 554            0,
 555            6,
 556            shadows.len() as u64,
 557        );
 558        *instance_offset = next_offset;
 559        true
 560    }
 561
 562    fn draw_quads(
 563        &self,
 564        quads: &[Quad],
 565        instance_buffer: &mut InstanceBuffer,
 566        instance_offset: &mut usize,
 567        viewport_size: Size<DevicePixels>,
 568        command_encoder: &metal::RenderCommandEncoderRef,
 569    ) -> bool {
 570        if quads.is_empty() {
 571            return true;
 572        }
 573        align_offset(instance_offset);
 574
 575        command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
 576        command_encoder.set_vertex_buffer(
 577            QuadInputIndex::Vertices as u64,
 578            Some(&self.unit_vertices),
 579            0,
 580        );
 581        command_encoder.set_vertex_buffer(
 582            QuadInputIndex::Quads as u64,
 583            Some(&instance_buffer.metal_buffer),
 584            *instance_offset as u64,
 585        );
 586        command_encoder.set_fragment_buffer(
 587            QuadInputIndex::Quads as u64,
 588            Some(&instance_buffer.metal_buffer),
 589            *instance_offset as u64,
 590        );
 591
 592        command_encoder.set_vertex_bytes(
 593            QuadInputIndex::ViewportSize as u64,
 594            mem::size_of_val(&viewport_size) as u64,
 595            &viewport_size as *const Size<DevicePixels> as *const _,
 596        );
 597
 598        let quad_bytes_len = mem::size_of_val(quads);
 599        let buffer_contents =
 600            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
 601
 602        let next_offset = *instance_offset + quad_bytes_len;
 603        if next_offset > instance_buffer.size {
 604            return false;
 605        }
 606
 607        unsafe {
 608            ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
 609        }
 610
 611        command_encoder.draw_primitives_instanced(
 612            metal::MTLPrimitiveType::Triangle,
 613            0,
 614            6,
 615            quads.len() as u64,
 616        );
 617        *instance_offset = next_offset;
 618        true
 619    }
 620
 621    fn draw_paths(
 622        &self,
 623        paths: &[Path<ScaledPixels>],
 624        instance_buffer: &mut InstanceBuffer,
 625        instance_offset: &mut usize,
 626        viewport_size: Size<DevicePixels>,
 627        command_encoder: &metal::RenderCommandEncoderRef,
 628    ) -> bool {
 629        if paths.is_empty() {
 630            return true;
 631        }
 632
 633        command_encoder.set_render_pipeline_state(&self.path_pipeline_state);
 634
 635        unsafe {
 636            let base_addr = instance_buffer.metal_buffer.contents();
 637            let mut p = (base_addr as *mut u8).add(*instance_offset);
 638            let mut draw_indirect_commands = Vec::with_capacity(paths.len());
 639
 640            // copy vertices
 641            let vertices_offset = (p as usize) - (base_addr as usize);
 642            let mut first_vertex = 0;
 643            for (i, path) in paths.iter().enumerate() {
 644                if (p as usize) - (base_addr as usize)
 645                    + (mem::size_of::<PathVertex<ScaledPixels>>() * path.vertices.len())
 646                    > instance_buffer.size
 647                {
 648                    return false;
 649                }
 650
 651                for v in &path.vertices {
 652                    *(p as *mut PathVertex<ScaledPixels>) = PathVertex {
 653                        xy_position: v.xy_position,
 654                        content_mask: ContentMask {
 655                            bounds: path.content_mask.bounds,
 656                        },
 657                    };
 658                    p = p.add(mem::size_of::<PathVertex<ScaledPixels>>());
 659                }
 660
 661                draw_indirect_commands.push(MTLDrawPrimitivesIndirectArguments {
 662                    vertexCount: path.vertices.len() as u32,
 663                    instanceCount: 1,
 664                    vertexStart: first_vertex,
 665                    baseInstance: i as u32,
 666                });
 667                first_vertex += path.vertices.len() as u32;
 668            }
 669
 670            // copy sprites
 671            let sprites_offset = (p as u64) - (base_addr as u64);
 672            if (p as usize) - (base_addr as usize) + (mem::size_of::<PathSprite>() * paths.len())
 673                > instance_buffer.size
 674            {
 675                return false;
 676            }
 677            for path in paths {
 678                *(p as *mut PathSprite) = PathSprite {
 679                    bounds: path.bounds,
 680                    color: path.color,
 681                };
 682                p = p.add(mem::size_of::<PathSprite>());
 683            }
 684
 685            // copy indirect commands
 686            let icb_bytes_len = mem::size_of_val(draw_indirect_commands.as_slice());
 687            let icb_offset = (p as u64) - (base_addr as u64);
 688            if (p as usize) - (base_addr as usize) + icb_bytes_len > instance_buffer.size {
 689                return false;
 690            }
 691            ptr::copy_nonoverlapping(
 692                draw_indirect_commands.as_ptr() as *const u8,
 693                p,
 694                icb_bytes_len,
 695            );
 696            p = p.add(icb_bytes_len);
 697
 698            // draw path
 699            command_encoder.set_vertex_buffer(
 700                PathInputIndex::Vertices as u64,
 701                Some(&instance_buffer.metal_buffer),
 702                vertices_offset as u64,
 703            );
 704
 705            command_encoder.set_vertex_bytes(
 706                PathInputIndex::ViewportSize as u64,
 707                mem::size_of_val(&viewport_size) as u64,
 708                &viewport_size as *const Size<DevicePixels> as *const _,
 709            );
 710
 711            command_encoder.set_vertex_buffer(
 712                PathInputIndex::Sprites as u64,
 713                Some(&instance_buffer.metal_buffer),
 714                sprites_offset,
 715            );
 716
 717            command_encoder.set_fragment_buffer(
 718                PathInputIndex::Sprites as u64,
 719                Some(&instance_buffer.metal_buffer),
 720                sprites_offset,
 721            );
 722
 723            for i in 0..paths.len() {
 724                command_encoder.draw_primitives_indirect(
 725                    metal::MTLPrimitiveType::Triangle,
 726                    &instance_buffer.metal_buffer,
 727                    icb_offset
 728                        + (i * std::mem::size_of::<MTLDrawPrimitivesIndirectArguments>()) as u64,
 729                );
 730            }
 731
 732            *instance_offset = (p as usize) - (base_addr as usize);
 733        }
 734
 735        true
 736    }
 737
 738    fn draw_underlines(
 739        &self,
 740        underlines: &[Underline],
 741        instance_buffer: &mut InstanceBuffer,
 742        instance_offset: &mut usize,
 743        viewport_size: Size<DevicePixels>,
 744        command_encoder: &metal::RenderCommandEncoderRef,
 745    ) -> bool {
 746        if underlines.is_empty() {
 747            return true;
 748        }
 749        align_offset(instance_offset);
 750
 751        command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
 752        command_encoder.set_vertex_buffer(
 753            UnderlineInputIndex::Vertices as u64,
 754            Some(&self.unit_vertices),
 755            0,
 756        );
 757        command_encoder.set_vertex_buffer(
 758            UnderlineInputIndex::Underlines as u64,
 759            Some(&instance_buffer.metal_buffer),
 760            *instance_offset as u64,
 761        );
 762        command_encoder.set_fragment_buffer(
 763            UnderlineInputIndex::Underlines as u64,
 764            Some(&instance_buffer.metal_buffer),
 765            *instance_offset as u64,
 766        );
 767
 768        command_encoder.set_vertex_bytes(
 769            UnderlineInputIndex::ViewportSize as u64,
 770            mem::size_of_val(&viewport_size) as u64,
 771            &viewport_size as *const Size<DevicePixels> as *const _,
 772        );
 773
 774        let underline_bytes_len = mem::size_of_val(underlines);
 775        let buffer_contents =
 776            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
 777
 778        let next_offset = *instance_offset + underline_bytes_len;
 779        if next_offset > instance_buffer.size {
 780            return false;
 781        }
 782
 783        unsafe {
 784            ptr::copy_nonoverlapping(
 785                underlines.as_ptr() as *const u8,
 786                buffer_contents,
 787                underline_bytes_len,
 788            );
 789        }
 790
 791        command_encoder.draw_primitives_instanced(
 792            metal::MTLPrimitiveType::Triangle,
 793            0,
 794            6,
 795            underlines.len() as u64,
 796        );
 797        *instance_offset = next_offset;
 798        true
 799    }
 800
 801    fn draw_monochrome_sprites(
 802        &self,
 803        texture_id: AtlasTextureId,
 804        sprites: &[MonochromeSprite],
 805        instance_buffer: &mut InstanceBuffer,
 806        instance_offset: &mut usize,
 807        viewport_size: Size<DevicePixels>,
 808        command_encoder: &metal::RenderCommandEncoderRef,
 809    ) -> bool {
 810        if sprites.is_empty() {
 811            return true;
 812        }
 813        align_offset(instance_offset);
 814
 815        let sprite_bytes_len = mem::size_of_val(sprites);
 816        let buffer_contents =
 817            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
 818
 819        let next_offset = *instance_offset + sprite_bytes_len;
 820        if next_offset > instance_buffer.size {
 821            return false;
 822        }
 823
 824        let texture = self.sprite_atlas.metal_texture(texture_id);
 825        let texture_size = size(
 826            DevicePixels(texture.width() as i32),
 827            DevicePixels(texture.height() as i32),
 828        );
 829        command_encoder.set_render_pipeline_state(&self.monochrome_sprites_pipeline_state);
 830        command_encoder.set_vertex_buffer(
 831            SpriteInputIndex::Vertices as u64,
 832            Some(&self.unit_vertices),
 833            0,
 834        );
 835        command_encoder.set_vertex_buffer(
 836            SpriteInputIndex::Sprites as u64,
 837            Some(&instance_buffer.metal_buffer),
 838            *instance_offset as u64,
 839        );
 840        command_encoder.set_vertex_bytes(
 841            SpriteInputIndex::ViewportSize as u64,
 842            mem::size_of_val(&viewport_size) as u64,
 843            &viewport_size as *const Size<DevicePixels> as *const _,
 844        );
 845        command_encoder.set_vertex_bytes(
 846            SpriteInputIndex::AtlasTextureSize as u64,
 847            mem::size_of_val(&texture_size) as u64,
 848            &texture_size as *const Size<DevicePixels> as *const _,
 849        );
 850        command_encoder.set_fragment_buffer(
 851            SpriteInputIndex::Sprites as u64,
 852            Some(&instance_buffer.metal_buffer),
 853            *instance_offset as u64,
 854        );
 855        command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
 856
 857        unsafe {
 858            ptr::copy_nonoverlapping(
 859                sprites.as_ptr() as *const u8,
 860                buffer_contents,
 861                sprite_bytes_len,
 862            );
 863        }
 864
 865        command_encoder.draw_primitives_instanced(
 866            metal::MTLPrimitiveType::Triangle,
 867            0,
 868            6,
 869            sprites.len() as u64,
 870        );
 871        *instance_offset = next_offset;
 872        true
 873    }
 874
 875    fn draw_polychrome_sprites(
 876        &self,
 877        texture_id: AtlasTextureId,
 878        sprites: &[PolychromeSprite],
 879        instance_buffer: &mut InstanceBuffer,
 880        instance_offset: &mut usize,
 881        viewport_size: Size<DevicePixels>,
 882        command_encoder: &metal::RenderCommandEncoderRef,
 883    ) -> bool {
 884        if sprites.is_empty() {
 885            return true;
 886        }
 887        align_offset(instance_offset);
 888
 889        let texture = self.sprite_atlas.metal_texture(texture_id);
 890        let texture_size = size(
 891            DevicePixels(texture.width() as i32),
 892            DevicePixels(texture.height() as i32),
 893        );
 894        command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state);
 895        command_encoder.set_vertex_buffer(
 896            SpriteInputIndex::Vertices as u64,
 897            Some(&self.unit_vertices),
 898            0,
 899        );
 900        command_encoder.set_vertex_buffer(
 901            SpriteInputIndex::Sprites as u64,
 902            Some(&instance_buffer.metal_buffer),
 903            *instance_offset as u64,
 904        );
 905        command_encoder.set_vertex_bytes(
 906            SpriteInputIndex::ViewportSize as u64,
 907            mem::size_of_val(&viewport_size) as u64,
 908            &viewport_size as *const Size<DevicePixels> as *const _,
 909        );
 910        command_encoder.set_vertex_bytes(
 911            SpriteInputIndex::AtlasTextureSize as u64,
 912            mem::size_of_val(&texture_size) as u64,
 913            &texture_size as *const Size<DevicePixels> as *const _,
 914        );
 915        command_encoder.set_fragment_buffer(
 916            SpriteInputIndex::Sprites as u64,
 917            Some(&instance_buffer.metal_buffer),
 918            *instance_offset as u64,
 919        );
 920        command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
 921
 922        let sprite_bytes_len = mem::size_of_val(sprites);
 923        let buffer_contents =
 924            unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) };
 925
 926        let next_offset = *instance_offset + sprite_bytes_len;
 927        if next_offset > instance_buffer.size {
 928            return false;
 929        }
 930
 931        unsafe {
 932            ptr::copy_nonoverlapping(
 933                sprites.as_ptr() as *const u8,
 934                buffer_contents,
 935                sprite_bytes_len,
 936            );
 937        }
 938
 939        command_encoder.draw_primitives_instanced(
 940            metal::MTLPrimitiveType::Triangle,
 941            0,
 942            6,
 943            sprites.len() as u64,
 944        );
 945        *instance_offset = next_offset;
 946        true
 947    }
 948
 949    fn draw_surfaces(
 950        &mut self,
 951        surfaces: &[PaintSurface],
 952        instance_buffer: &mut InstanceBuffer,
 953        instance_offset: &mut usize,
 954        viewport_size: Size<DevicePixels>,
 955        command_encoder: &metal::RenderCommandEncoderRef,
 956    ) -> bool {
 957        command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state);
 958        command_encoder.set_vertex_buffer(
 959            SurfaceInputIndex::Vertices as u64,
 960            Some(&self.unit_vertices),
 961            0,
 962        );
 963        command_encoder.set_vertex_bytes(
 964            SurfaceInputIndex::ViewportSize as u64,
 965            mem::size_of_val(&viewport_size) as u64,
 966            &viewport_size as *const Size<DevicePixels> as *const _,
 967        );
 968
 969        for surface in surfaces {
 970            let texture_size = size(
 971                DevicePixels::from(surface.image_buffer.get_width() as i32),
 972                DevicePixels::from(surface.image_buffer.get_height() as i32),
 973            );
 974
 975            assert_eq!(
 976                surface.image_buffer.get_pixel_format(),
 977                kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
 978            );
 979
 980            let y_texture = self
 981                .core_video_texture_cache
 982                .create_texture_from_image(
 983                    surface.image_buffer.as_concrete_TypeRef(),
 984                    None,
 985                    MTLPixelFormat::R8Unorm,
 986                    surface.image_buffer.get_width_of_plane(0),
 987                    surface.image_buffer.get_height_of_plane(0),
 988                    0,
 989                )
 990                .unwrap();
 991            let cb_cr_texture = self
 992                .core_video_texture_cache
 993                .create_texture_from_image(
 994                    surface.image_buffer.as_concrete_TypeRef(),
 995                    None,
 996                    MTLPixelFormat::RG8Unorm,
 997                    surface.image_buffer.get_width_of_plane(1),
 998                    surface.image_buffer.get_height_of_plane(1),
 999                    1,
1000                )
1001                .unwrap();
1002
1003            align_offset(instance_offset);
1004            let next_offset = *instance_offset + mem::size_of::<Surface>();
1005            if next_offset > instance_buffer.size {
1006                return false;
1007            }
1008
1009            command_encoder.set_vertex_buffer(
1010                SurfaceInputIndex::Surfaces as u64,
1011                Some(&instance_buffer.metal_buffer),
1012                *instance_offset as u64,
1013            );
1014            command_encoder.set_vertex_bytes(
1015                SurfaceInputIndex::TextureSize as u64,
1016                mem::size_of_val(&texture_size) as u64,
1017                &texture_size as *const Size<DevicePixels> as *const _,
1018            );
1019            // let y_texture = y_texture.get_texture().unwrap().
1020            command_encoder.set_fragment_texture(SurfaceInputIndex::YTexture as u64, unsafe {
1021                let texture = CVMetalTextureGetTexture(y_texture.as_concrete_TypeRef());
1022                Some(metal::TextureRef::from_ptr(texture as *mut _))
1023            });
1024            command_encoder.set_fragment_texture(SurfaceInputIndex::CbCrTexture as u64, unsafe {
1025                let texture = CVMetalTextureGetTexture(cb_cr_texture.as_concrete_TypeRef());
1026                Some(metal::TextureRef::from_ptr(texture as *mut _))
1027            });
1028
1029            unsafe {
1030                let buffer_contents = (instance_buffer.metal_buffer.contents() as *mut u8)
1031                    .add(*instance_offset)
1032                    as *mut SurfaceBounds;
1033                ptr::write(
1034                    buffer_contents,
1035                    SurfaceBounds {
1036                        bounds: surface.bounds,
1037                        content_mask: surface.content_mask.clone(),
1038                    },
1039                );
1040            }
1041
1042            command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
1043            *instance_offset = next_offset;
1044        }
1045        true
1046    }
1047}
1048
1049fn build_pipeline_state(
1050    device: &metal::DeviceRef,
1051    library: &metal::LibraryRef,
1052    label: &str,
1053    vertex_fn_name: &str,
1054    fragment_fn_name: &str,
1055    pixel_format: metal::MTLPixelFormat,
1056    sample_count: u64,
1057) -> metal::RenderPipelineState {
1058    let vertex_fn = library
1059        .get_function(vertex_fn_name, None)
1060        .expect("error locating vertex function");
1061    let fragment_fn = library
1062        .get_function(fragment_fn_name, None)
1063        .expect("error locating fragment function");
1064
1065    let descriptor = metal::RenderPipelineDescriptor::new();
1066    descriptor.set_label(label);
1067    descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
1068    descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
1069    descriptor.set_sample_count(sample_count);
1070    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
1071    color_attachment.set_pixel_format(pixel_format);
1072    color_attachment.set_blending_enabled(true);
1073    color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
1074    color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
1075    color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
1076    color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
1077    color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
1078    color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
1079
1080    device
1081        .new_render_pipeline_state(&descriptor)
1082        .expect("could not create render pipeline state")
1083}
1084
1085// Align to multiples of 256 make Metal happy.
1086fn align_offset(offset: &mut usize) {
1087    *offset = (*offset).div_ceil(256) * 256;
1088}
1089
1090fn create_msaa_texture(
1091    device: &metal::Device,
1092    layer: &metal::MetalLayer,
1093    sample_count: u64,
1094) -> Option<metal::Texture> {
1095    let viewport_size = layer.drawable_size();
1096    let width = viewport_size.width.ceil() as u64;
1097    let height = viewport_size.height.ceil() as u64;
1098
1099    if width == 0 || height == 0 {
1100        return None;
1101    }
1102
1103    if sample_count <= 1 {
1104        return None;
1105    }
1106
1107    let texture_descriptor = metal::TextureDescriptor::new();
1108    texture_descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
1109
1110    // MTLStorageMode default is `shared` only for Apple silicon GPUs. Use `private` for Apple and Intel GPUs both.
1111    // Reference: https://developer.apple.com/documentation/metal/choosing-a-resource-storage-mode-for-apple-gpus
1112    texture_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
1113
1114    texture_descriptor.set_width(width);
1115    texture_descriptor.set_height(height);
1116    texture_descriptor.set_pixel_format(layer.pixel_format());
1117    texture_descriptor.set_usage(metal::MTLTextureUsage::RenderTarget);
1118    texture_descriptor.set_sample_count(sample_count);
1119
1120    let metal_texture = device.new_texture(&texture_descriptor);
1121    Some(metal_texture)
1122}
1123
1124#[repr(C)]
1125enum ShadowInputIndex {
1126    Vertices = 0,
1127    Shadows = 1,
1128    ViewportSize = 2,
1129}
1130
1131#[repr(C)]
1132enum QuadInputIndex {
1133    Vertices = 0,
1134    Quads = 1,
1135    ViewportSize = 2,
1136}
1137
1138#[repr(C)]
1139enum UnderlineInputIndex {
1140    Vertices = 0,
1141    Underlines = 1,
1142    ViewportSize = 2,
1143}
1144
1145#[repr(C)]
1146enum SpriteInputIndex {
1147    Vertices = 0,
1148    Sprites = 1,
1149    ViewportSize = 2,
1150    AtlasTextureSize = 3,
1151    AtlasTexture = 4,
1152}
1153
1154#[repr(C)]
1155enum SurfaceInputIndex {
1156    Vertices = 0,
1157    Surfaces = 1,
1158    ViewportSize = 2,
1159    TextureSize = 3,
1160    YTexture = 4,
1161    CbCrTexture = 5,
1162}
1163
1164#[repr(C)]
1165enum PathInputIndex {
1166    Vertices = 0,
1167    ViewportSize = 1,
1168    Sprites = 2,
1169}
1170
1171#[derive(Clone, Debug, Eq, PartialEq)]
1172#[repr(C)]
1173pub struct PathSprite {
1174    pub bounds: Bounds<ScaledPixels>,
1175    pub color: Background,
1176}
1177
1178#[derive(Clone, Debug, Eq, PartialEq)]
1179#[repr(C)]
1180pub struct SurfaceBounds {
1181    pub bounds: Bounds<ScaledPixels>,
1182    pub content_mask: ContentMask<ScaledPixels>,
1183}