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