use super::metal_atlas::MetalAtlas;use crate::{ point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,};use anyhow::{anyhow, Result};use block::ConcreteBlock;use cocoa::{ base::{NO, YES}, foundation::{NSSize, NSUInteger}, quartzcore::AutoresizingMask,};use collections::HashMap;use core_foundation::base::TCFType;use foreign_types::ForeignType;use media::core_video::CVMetalTextureCache;use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};use objc::{self, msg_send, sel, sel_impl};use parking_lot::Mutex;use smallvec::SmallVec;use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc}; // Exported to metalpub(crate) type PointF = crate::Point<f32>; #[cfg(not(feature = "runtime_shaders"))]const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));#[cfg(feature = "runtime_shaders")]const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));// Use 4x MSAA, all devices support it.// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecountconst PATH_SAMPLE_COUNT: u32 = 4; pub type Context = Arc<Mutex<InstanceBufferPool>>;pub type Renderer = MetalRenderer; pub unsafe fn new_renderer( context: self::Context, _native_window: *mut c_void, _native_view: *mut c_void, _bounds: crate::Size<f32>, _transparent: bool,) -> Renderer { MetalRenderer::new(context)} pub(crate) struct InstanceBufferPool { buffer_size: usize, buffers: Vec<metal::Buffer>,} impl Default for InstanceBufferPool { fn default() -> Self { Self { buffer_size: 2 * 1024 * 1024, buffers: Vec::new(), } }} pub(crate) struct InstanceBuffer { metal_buffer: metal::Buffer, size: usize,} impl InstanceBufferPool { pub(crate) fn reset(&mut self, buffer_size: usize) { self.buffer_size = buffer_size; self.buffers.clear(); } pub(crate) fn acquire(&mut self, device: &metal::Device) -> InstanceBuffer { let buffer = self.buffers.pop().unwrap_or_else(|| { device.new_buffer( self.buffer_size as u64, MTLResourceOptions::StorageModeManaged, ) }); InstanceBuffer { metal_buffer: buffer, size: self.buffer_size, } } pub(crate) fn release(&mut self, buffer: InstanceBuffer) { if buffer.size == self.buffer_size { self.buffers.push(buffer.metal_buffer) } }} pub(crate) struct MetalRenderer { device: metal::Device, layer: metal::MetalLayer, presents_with_transaction: bool, command_queue: CommandQueue, paths_rasterization_pipeline_state: metal::RenderPipelineState, path_sprites_pipeline_state: metal::RenderPipelineState, shadows_pipeline_state: metal::RenderPipelineState, quads_pipeline_state: metal::RenderPipelineState, underlines_pipeline_state: metal::RenderPipelineState, monochrome_sprites_pipeline_state: metal::RenderPipelineState, polychrome_sprites_pipeline_state: metal::RenderPipelineState, surfaces_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, #[allow(clippy::arc_with_non_send_sync)] instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>, sprite_atlas: Arc<MetalAtlas>, core_video_texture_cache: CVMetalTextureCache,} impl MetalRenderer { pub fn new(instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>) -> Self { // Prefer low‐power integrated GPUs on Intel Mac. On Apple // Silicon, there is only ever one GPU, so this is equivalent to // `metal::Device::system_default()`. let mut devices = metal::Device::all(); devices.sort_by_key(|device| (device.is_removable(), device.is_low_power())); let Some(device) = devices.pop() else { log::error!("unable to access a compatible graphics device"); std::process::exit(1); }; let layer = metal::MetalLayer::new(); layer.set_device(&device); layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm); layer.set_opaque(false); layer.set_maximum_drawable_count(3); unsafe { let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO]; let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES]; let _: () = msg_send![ &*layer, setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE | AutoresizingMask::HEIGHT_SIZABLE ]; } #[cfg(feature = "runtime_shaders")] let library = device .new_library_with_source(&SHADERS_SOURCE_FILE, &metal::CompileOptions::new()) .expect("error building metal library"); #[cfg(not(feature = "runtime_shaders"))] let library = device .new_library_with_data(SHADERS_METALLIB) .expect("error building metal library"); fn to_float2_bits(point: PointF) -> u64 { let mut output = point.y.to_bits() as u64; output <<= 32; output |= point.x.to_bits() as u64; output } let unit_vertices = [ to_float2_bits(point(0., 0.)), to_float2_bits(point(1., 0.)), to_float2_bits(point(0., 1.)), to_float2_bits(point(0., 1.)), to_float2_bits(point(1., 0.)), to_float2_bits(point(1., 1.)), ]; let unit_vertices = device.new_buffer_with_data( unit_vertices.as_ptr() as *const c_void, mem::size_of_val(&unit_vertices) as u64, MTLResourceOptions::StorageModeManaged, ); let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state( &device, &library, "paths_rasterization", "path_rasterization_vertex", "path_rasterization_fragment", MTLPixelFormat::R16Float, PATH_SAMPLE_COUNT, ); let path_sprites_pipeline_state = build_pipeline_state( &device, &library, "path_sprites", "path_sprite_vertex", "path_sprite_fragment", MTLPixelFormat::BGRA8Unorm, ); let shadows_pipeline_state = build_pipeline_state( &device, &library, "shadows", "shadow_vertex", "shadow_fragment", MTLPixelFormat::BGRA8Unorm, ); let quads_pipeline_state = build_pipeline_state( &device, &library, "quads", "quad_vertex", "quad_fragment", MTLPixelFormat::BGRA8Unorm, ); let underlines_pipeline_state = build_pipeline_state( &device, &library, "underlines", "underline_vertex", "underline_fragment", MTLPixelFormat::BGRA8Unorm, ); let monochrome_sprites_pipeline_state = build_pipeline_state( &device, &library, "monochrome_sprites", "monochrome_sprite_vertex", "monochrome_sprite_fragment", MTLPixelFormat::BGRA8Unorm, ); let polychrome_sprites_pipeline_state = build_pipeline_state( &device, &library, "polychrome_sprites", "polychrome_sprite_vertex", "polychrome_sprite_fragment", MTLPixelFormat::BGRA8Unorm, ); let surfaces_pipeline_state = build_pipeline_state( &device, &library, "surfaces", "surface_vertex", "surface_fragment", MTLPixelFormat::BGRA8Unorm, ); let command_queue = device.new_command_queue(); let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), PATH_SAMPLE_COUNT)); let core_video_texture_cache = unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() }; Self { device, layer, presents_with_transaction: false, command_queue, paths_rasterization_pipeline_state, path_sprites_pipeline_state, shadows_pipeline_state, quads_pipeline_state, underlines_pipeline_state, monochrome_sprites_pipeline_state, polychrome_sprites_pipeline_state, surfaces_pipeline_state, unit_vertices, instance_buffer_pool, sprite_atlas, core_video_texture_cache, } } pub fn layer(&self) -> &metal::MetalLayerRef { &self.layer } pub fn layer_ptr(&self) -> *mut CAMetalLayer { self.layer.as_ptr() } pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> { &self.sprite_atlas } pub fn set_presents_with_transaction(&mut self, presents_with_transaction: bool) { self.presents_with_transaction = presents_with_transaction; self.layer .set_presents_with_transaction(presents_with_transaction); } pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) { let size = NSSize { width: size.width.0 as f64, height: size.height.0 as f64, }; unsafe { let _: () = msg_send![ self.layer(), setDrawableSize: size ]; } } pub fn update_transparency(&self, _transparent: bool) { // todo(mac)? } pub fn destroy(&self) { // nothing to do } pub fn draw(&mut self, scene: &Scene) { let layer = self.layer.clone(); let viewport_size = layer.drawable_size(); let viewport_size: Size<DevicePixels> = size( (viewport_size.width.ceil() as i32).into(), (viewport_size.height.ceil() as i32).into(), ); let drawable = if let Some(drawable) = layer.next_drawable() { drawable } else { log::error!( "failed to retrieve next drawable, drawable size: {:?}", viewport_size ); return; }; loop { let mut instance_buffer = self.instance_buffer_pool.lock().acquire(&self.device); let command_buffer = self.draw_primitives(scene, &mut instance_buffer, drawable, viewport_size); match command_buffer { Ok(command_buffer) => { let instance_buffer_pool = self.instance_buffer_pool.clone(); let instance_buffer = Cell::new(Some(instance_buffer)); let block = ConcreteBlock::new(move |_| { if let Some(instance_buffer) = instance_buffer.take() { instance_buffer_pool.lock().release(instance_buffer); } }); let block = block.copy(); command_buffer.add_completed_handler(&block); if self.presents_with_transaction { command_buffer.commit(); command_buffer.wait_until_scheduled(); drawable.present(); } else { command_buffer.present_drawable(drawable); command_buffer.commit(); } return; } Err(err) => { log::error!( "failed to render: {}. retrying with larger instance buffer size", err ); let mut instance_buffer_pool = self.instance_buffer_pool.lock(); let buffer_size = instance_buffer_pool.buffer_size; if buffer_size >= 256 * 1024 * 1024 { log::error!("instance buffer size grew too large: {}", buffer_size); break; } instance_buffer_pool.reset(buffer_size * 2); log::info!( "increased instance buffer size to {}", instance_buffer_pool.buffer_size ); } } } } fn draw_primitives( &mut self, scene: &Scene, instance_buffer: &mut InstanceBuffer, drawable: &metal::MetalDrawableRef, viewport_size: Size<DevicePixels>, ) -> Result<metal::CommandBuffer> { let command_queue = self.command_queue.clone(); let command_buffer = command_queue.new_command_buffer(); let mut instance_offset = 0; let Some(path_tiles) = self.rasterize_paths( scene.paths(), instance_buffer, &mut instance_offset, command_buffer, ) else { return Err(anyhow!("failed to rasterize {} paths", scene.paths().len())); }; let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor .color_attachments() .object_at(0) .unwrap(); color_attachment.set_texture(Some(drawable.texture())); color_attachment.set_load_action(metal::MTLLoadAction::Clear); color_attachment.set_store_action(metal::MTLStoreAction::Store); let alpha = if self.layer.is_opaque() { 1. } else { 0. }; color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha)); let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor); command_encoder.set_viewport(metal::MTLViewport { originX: 0.0, originY: 0.0, width: i32::from(viewport_size.width) as f64, height: i32::from(viewport_size.height) as f64, znear: 0.0, zfar: 1.0, }); for batch in scene.batches() { let ok = match batch { PrimitiveBatch::Shadows(shadows) => self.draw_shadows( shadows, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::Quads(quads) => self.draw_quads( quads, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::Paths(paths) => self.draw_paths( paths, &path_tiles, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::Underlines(underlines) => self.draw_underlines( underlines, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::MonochromeSprites { texture_id, sprites, } => self.draw_monochrome_sprites( texture_id, sprites, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::PolychromeSprites { texture_id, sprites, } => self.draw_polychrome_sprites( texture_id, sprites, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces( surfaces, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), }; if !ok { command_encoder.end_encoding(); return Err(anyhow!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces", scene.paths.len(), scene.shadows.len(), scene.quads.len(), scene.underlines.len(), scene.monochrome_sprites.len(), scene.polychrome_sprites.len(), scene.surfaces.len(), )); } } command_encoder.end_encoding(); instance_buffer.metal_buffer.did_modify_range(NSRange { location: 0, length: instance_offset as NSUInteger, }); Ok(command_buffer.to_owned()) } fn rasterize_paths( &self, paths: &[Path<ScaledPixels>], instance_buffer: &mut InstanceBuffer, instance_offset: &mut usize, command_buffer: &metal::CommandBufferRef, ) -> Option<HashMap<PathId, AtlasTile>> { self.sprite_atlas.clear_textures(AtlasTextureKind::Path); Lines 1–500 of 1273. Load more
use super::metal_atlas::MetalAtlas; use crate::{ point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, }; use anyhow::{anyhow, Result}; use block::ConcreteBlock; use cocoa::{ base::{NO, YES}, foundation::{NSSize, NSUInteger}, quartzcore::AutoresizingMask, }; use collections::HashMap; use core_foundation::base::TCFType; use foreign_types::ForeignType; use media::core_video::CVMetalTextureCache; use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange}; use objc::{self, msg_send, sel, sel_impl}; use parking_lot::Mutex; use smallvec::SmallVec; use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc}; // Exported to metal pub(crate) type PointF = crate::Point<f32>; #[cfg(not(feature = "runtime_shaders"))] const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); #[cfg(feature = "runtime_shaders")] const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal")); // Use 4x MSAA, all devices support it. // https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount const PATH_SAMPLE_COUNT: u32 = 4; pub type Context = Arc<Mutex<InstanceBufferPool>>; pub type Renderer = MetalRenderer; pub unsafe fn new_renderer( context: self::Context, _native_window: *mut c_void, _native_view: *mut c_void, _bounds: crate::Size<f32>, _transparent: bool, ) -> Renderer { MetalRenderer::new(context) } pub(crate) struct InstanceBufferPool { buffer_size: usize, buffers: Vec<metal::Buffer>, } impl Default for InstanceBufferPool { fn default() -> Self { Self { buffer_size: 2 * 1024 * 1024, buffers: Vec::new(), } } } pub(crate) struct InstanceBuffer { metal_buffer: metal::Buffer, size: usize, } impl InstanceBufferPool { pub(crate) fn reset(&mut self, buffer_size: usize) { self.buffer_size = buffer_size; self.buffers.clear(); } pub(crate) fn acquire(&mut self, device: &metal::Device) -> InstanceBuffer { let buffer = self.buffers.pop().unwrap_or_else(|| { device.new_buffer( self.buffer_size as u64, MTLResourceOptions::StorageModeManaged, ) }); InstanceBuffer { metal_buffer: buffer, size: self.buffer_size, } } pub(crate) fn release(&mut self, buffer: InstanceBuffer) { if buffer.size == self.buffer_size { self.buffers.push(buffer.metal_buffer) } } } pub(crate) struct MetalRenderer { device: metal::Device, layer: metal::MetalLayer, presents_with_transaction: bool, command_queue: CommandQueue, paths_rasterization_pipeline_state: metal::RenderPipelineState, path_sprites_pipeline_state: metal::RenderPipelineState, shadows_pipeline_state: metal::RenderPipelineState, quads_pipeline_state: metal::RenderPipelineState, underlines_pipeline_state: metal::RenderPipelineState, monochrome_sprites_pipeline_state: metal::RenderPipelineState, polychrome_sprites_pipeline_state: metal::RenderPipelineState, surfaces_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, #[allow(clippy::arc_with_non_send_sync)] instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>, sprite_atlas: Arc<MetalAtlas>, core_video_texture_cache: CVMetalTextureCache, } impl MetalRenderer { pub fn new(instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>) -> Self { // Prefer low‐power integrated GPUs on Intel Mac. On Apple // Silicon, there is only ever one GPU, so this is equivalent to // `metal::Device::system_default()`. let mut devices = metal::Device::all(); devices.sort_by_key(|device| (device.is_removable(), device.is_low_power())); let Some(device) = devices.pop() else { log::error!("unable to access a compatible graphics device"); std::process::exit(1); }; let layer = metal::MetalLayer::new(); layer.set_device(&device); layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm); layer.set_opaque(false); layer.set_maximum_drawable_count(3); unsafe { let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO]; let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES]; let _: () = msg_send![ &*layer, setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE | AutoresizingMask::HEIGHT_SIZABLE ]; } #[cfg(feature = "runtime_shaders")] let library = device .new_library_with_source(&SHADERS_SOURCE_FILE, &metal::CompileOptions::new()) .expect("error building metal library"); #[cfg(not(feature = "runtime_shaders"))] let library = device .new_library_with_data(SHADERS_METALLIB) .expect("error building metal library"); fn to_float2_bits(point: PointF) -> u64 { let mut output = point.y.to_bits() as u64; output <<= 32; output |= point.x.to_bits() as u64; output } let unit_vertices = [ to_float2_bits(point(0., 0.)), to_float2_bits(point(1., 0.)), to_float2_bits(point(0., 1.)), to_float2_bits(point(0., 1.)), to_float2_bits(point(1., 0.)), to_float2_bits(point(1., 1.)), ]; let unit_vertices = device.new_buffer_with_data( unit_vertices.as_ptr() as *const c_void, mem::size_of_val(&unit_vertices) as u64, MTLResourceOptions::StorageModeManaged, ); let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state( &device, &library, "paths_rasterization", "path_rasterization_vertex", "path_rasterization_fragment", MTLPixelFormat::R16Float, PATH_SAMPLE_COUNT, ); let path_sprites_pipeline_state = build_pipeline_state( &device, &library, "path_sprites", "path_sprite_vertex", "path_sprite_fragment", MTLPixelFormat::BGRA8Unorm, ); let shadows_pipeline_state = build_pipeline_state( &device, &library, "shadows", "shadow_vertex", "shadow_fragment", MTLPixelFormat::BGRA8Unorm, ); let quads_pipeline_state = build_pipeline_state( &device, &library, "quads", "quad_vertex", "quad_fragment", MTLPixelFormat::BGRA8Unorm, ); let underlines_pipeline_state = build_pipeline_state( &device, &library, "underlines", "underline_vertex", "underline_fragment", MTLPixelFormat::BGRA8Unorm, ); let monochrome_sprites_pipeline_state = build_pipeline_state( &device, &library, "monochrome_sprites", "monochrome_sprite_vertex", "monochrome_sprite_fragment", MTLPixelFormat::BGRA8Unorm, ); let polychrome_sprites_pipeline_state = build_pipeline_state( &device, &library, "polychrome_sprites", "polychrome_sprite_vertex", "polychrome_sprite_fragment", MTLPixelFormat::BGRA8Unorm, ); let surfaces_pipeline_state = build_pipeline_state( &device, &library, "surfaces", "surface_vertex", "surface_fragment", MTLPixelFormat::BGRA8Unorm, ); let command_queue = device.new_command_queue(); let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), PATH_SAMPLE_COUNT)); let core_video_texture_cache = unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() }; Self { device, layer, presents_with_transaction: false, command_queue, paths_rasterization_pipeline_state, path_sprites_pipeline_state, shadows_pipeline_state, quads_pipeline_state, underlines_pipeline_state, monochrome_sprites_pipeline_state, polychrome_sprites_pipeline_state, surfaces_pipeline_state, unit_vertices, instance_buffer_pool, sprite_atlas, core_video_texture_cache, } } pub fn layer(&self) -> &metal::MetalLayerRef { &self.layer } pub fn layer_ptr(&self) -> *mut CAMetalLayer { self.layer.as_ptr() } pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> { &self.sprite_atlas } pub fn set_presents_with_transaction(&mut self, presents_with_transaction: bool) { self.presents_with_transaction = presents_with_transaction; self.layer .set_presents_with_transaction(presents_with_transaction); } pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) { let size = NSSize { width: size.width.0 as f64, height: size.height.0 as f64, }; unsafe { let _: () = msg_send![ self.layer(), setDrawableSize: size ]; } } pub fn update_transparency(&self, _transparent: bool) { // todo(mac)? } pub fn destroy(&self) { // nothing to do } pub fn draw(&mut self, scene: &Scene) { let layer = self.layer.clone(); let viewport_size = layer.drawable_size(); let viewport_size: Size<DevicePixels> = size( (viewport_size.width.ceil() as i32).into(), (viewport_size.height.ceil() as i32).into(), ); let drawable = if let Some(drawable) = layer.next_drawable() { drawable } else { log::error!( "failed to retrieve next drawable, drawable size: {:?}", viewport_size ); return; }; loop { let mut instance_buffer = self.instance_buffer_pool.lock().acquire(&self.device); let command_buffer = self.draw_primitives(scene, &mut instance_buffer, drawable, viewport_size); match command_buffer { Ok(command_buffer) => { let instance_buffer_pool = self.instance_buffer_pool.clone(); let instance_buffer = Cell::new(Some(instance_buffer)); let block = ConcreteBlock::new(move |_| { if let Some(instance_buffer) = instance_buffer.take() { instance_buffer_pool.lock().release(instance_buffer); } }); let block = block.copy(); command_buffer.add_completed_handler(&block); if self.presents_with_transaction { command_buffer.commit(); command_buffer.wait_until_scheduled(); drawable.present(); } else { command_buffer.present_drawable(drawable); command_buffer.commit(); } return; } Err(err) => { log::error!( "failed to render: {}. retrying with larger instance buffer size", err ); let mut instance_buffer_pool = self.instance_buffer_pool.lock(); let buffer_size = instance_buffer_pool.buffer_size; if buffer_size >= 256 * 1024 * 1024 { log::error!("instance buffer size grew too large: {}", buffer_size); break; } instance_buffer_pool.reset(buffer_size * 2); log::info!( "increased instance buffer size to {}", instance_buffer_pool.buffer_size ); } } } } fn draw_primitives( &mut self, scene: &Scene, instance_buffer: &mut InstanceBuffer, drawable: &metal::MetalDrawableRef, viewport_size: Size<DevicePixels>, ) -> Result<metal::CommandBuffer> { let command_queue = self.command_queue.clone(); let command_buffer = command_queue.new_command_buffer(); let mut instance_offset = 0; let Some(path_tiles) = self.rasterize_paths( scene.paths(), instance_buffer, &mut instance_offset, command_buffer, ) else { return Err(anyhow!("failed to rasterize {} paths", scene.paths().len())); }; let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor .color_attachments() .object_at(0) .unwrap(); color_attachment.set_texture(Some(drawable.texture())); color_attachment.set_load_action(metal::MTLLoadAction::Clear); color_attachment.set_store_action(metal::MTLStoreAction::Store); let alpha = if self.layer.is_opaque() { 1. } else { 0. }; color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha)); let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor); command_encoder.set_viewport(metal::MTLViewport { originX: 0.0, originY: 0.0, width: i32::from(viewport_size.width) as f64, height: i32::from(viewport_size.height) as f64, znear: 0.0, zfar: 1.0, }); for batch in scene.batches() { let ok = match batch { PrimitiveBatch::Shadows(shadows) => self.draw_shadows( shadows, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::Quads(quads) => self.draw_quads( quads, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::Paths(paths) => self.draw_paths( paths, &path_tiles, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::Underlines(underlines) => self.draw_underlines( underlines, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::MonochromeSprites { texture_id, sprites, } => self.draw_monochrome_sprites( texture_id, sprites, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::PolychromeSprites { texture_id, sprites, } => self.draw_polychrome_sprites( texture_id, sprites, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces( surfaces, instance_buffer, &mut instance_offset, viewport_size, command_encoder, ), }; if !ok { command_encoder.end_encoding(); return Err(anyhow!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces", scene.paths.len(), scene.shadows.len(), scene.quads.len(), scene.underlines.len(), scene.monochrome_sprites.len(), scene.polychrome_sprites.len(), scene.surfaces.len(), )); } } command_encoder.end_encoding(); instance_buffer.metal_buffer.did_modify_range(NSRange { location: 0, length: instance_offset as NSUInteger, }); Ok(command_buffer.to_owned()) } fn rasterize_paths( &self, paths: &[Path<ScaledPixels>], instance_buffer: &mut InstanceBuffer, instance_offset: &mut usize, command_buffer: &metal::CommandBufferRef, ) -> Option<HashMap<PathId, AtlasTile>> { self.sprite_atlas.clear_textures(AtlasTextureKind::Path);