freya_winit/drivers/
metal.rs1use freya_engine::prelude::{
2 ColorType,
3 DirectContext,
4 Surface as SkiaSurface,
5 SurfaceOrigin,
6 backend_render_targets,
7 direct_contexts,
8 mtl,
9 wrap_backend_render_target,
10};
11use objc2::{
12 rc::Retained,
13 runtime::ProtocolObject,
14};
15use objc2_app_kit::NSView;
16use objc2_core_foundation::CGSize;
17use objc2_metal::{
18 MTLCommandBuffer,
19 MTLCommandQueue,
20 MTLCreateSystemDefaultDevice,
21 MTLDevice,
22 MTLDrawable,
23 MTLPixelFormat,
24};
25use objc2_quartz_core::{
26 CAMetalDrawable,
27 CAMetalLayer,
28};
29use raw_window_handle::{
30 HasWindowHandle,
31 RawWindowHandle,
32};
33use winit::{
34 dpi::PhysicalSize,
35 event_loop::ActiveEventLoop,
36 window::{
37 Window,
38 WindowAttributes,
39 },
40};
41
42pub struct MetalDriver {
44 metal_layer: Retained<CAMetalLayer>,
45 command_queue: Retained<ProtocolObject<dyn MTLCommandQueue>>,
46 gr_context: DirectContext,
47}
48
49impl MetalDriver {
50 pub fn new(
51 event_loop: &ActiveEventLoop,
52 window_attributes: WindowAttributes,
53 gpu_resource_cache_limit: usize,
54 ) -> (Self, Window) {
55 let transparent = window_attributes.transparent;
56 let window = event_loop
57 .create_window(window_attributes)
58 .expect("Could not create window with Metal context");
59
60 let device = MTLCreateSystemDefaultDevice().expect("No Metal-capable device found");
61
62 let size = window.inner_size();
63
64 let metal_layer = {
65 let layer = CAMetalLayer::new();
66 layer.setDevice(Some(&device));
67 layer.setPixelFormat(MTLPixelFormat::BGRA8Unorm);
68 layer.setPresentsWithTransaction(false);
69 layer.setFramebufferOnly(false);
72 layer.setDrawableSize(CGSize::new(size.width as f64, size.height as f64));
73
74 if transparent {
76 layer.setOpaque(false);
77 }
78
79 let raw_handle = window
80 .window_handle()
81 .expect("Could not get window handle")
82 .as_raw();
83
84 match raw_handle {
85 RawWindowHandle::AppKit(appkit) => {
86 let view = unsafe { (appkit.ns_view.as_ptr() as *mut NSView).as_ref() }
87 .expect("NSView pointer is null");
88
89 view.setWantsLayer(true);
90 view.setLayer(Some(&layer));
91 }
92 _ => panic!("Metal driver only supports AppKit (macOS) windows"),
93 };
94
95 layer
96 };
97
98 let command_queue = device
99 .newCommandQueue()
100 .expect("Could not create Metal command queue");
101
102 let backend = unsafe {
103 mtl::BackendContext::new(
104 Retained::as_ptr(&device) as mtl::Handle,
105 Retained::as_ptr(&command_queue) as mtl::Handle,
106 )
107 };
108
109 let mut gr_context =
110 direct_contexts::make_metal(&backend, None).expect("Could not create Metal context");
111
112 gr_context.set_resource_cache_limit(gpu_resource_cache_limit);
113
114 let driver = Self {
115 metal_layer,
116 command_queue,
117 gr_context,
118 };
119
120 (driver, window)
121 }
122
123 pub fn present(
124 &mut self,
125 _size: PhysicalSize<u32>,
126 window: &Window,
127 render: impl FnOnce(&mut SkiaSurface),
128 ) {
129 let Some(drawable) = self.metal_layer.nextDrawable() else {
130 return;
132 };
133
134 let (drawable_width, drawable_height) = {
135 let size = self.metal_layer.drawableSize();
136 (size.width as i32, size.height as i32)
137 };
138
139 let texture_info =
140 unsafe { mtl::TextureInfo::new(Retained::as_ptr(&drawable.texture()) as mtl::Handle) };
141
142 let backend_render_target =
143 backend_render_targets::make_mtl((drawable_width, drawable_height), &texture_info);
144
145 let mut surface = wrap_backend_render_target(
146 &mut self.gr_context,
147 &backend_render_target,
148 SurfaceOrigin::TopLeft,
149 ColorType::BGRA8888,
150 None,
151 None,
152 )
153 .expect("Could not create Skia surface from Metal texture");
154
155 render(&mut surface);
156
157 window.pre_present_notify();
158 self.gr_context.flush_and_submit();
159 drop(surface);
160
161 let command_buffer = self
162 .command_queue
163 .commandBuffer()
164 .expect("Could not get Metal command buffer");
165
166 let mtl_drawable: Retained<ProtocolObject<dyn MTLDrawable>> = (&drawable).into();
167 command_buffer.presentDrawable(&mtl_drawable);
168 command_buffer.commit();
169 }
170
171 pub fn resize(&mut self, size: PhysicalSize<u32>) {
172 self.metal_layer
173 .setDrawableSize(CGSize::new(size.width as f64, size.height as f64));
174 }
175}