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 ) -> (Self, Window) {
54 let transparent = window_attributes.transparent;
55 let window = event_loop
56 .create_window(window_attributes)
57 .expect("Could not create window with Metal context");
58
59 let device = MTLCreateSystemDefaultDevice().expect("No Metal-capable device found");
60
61 let size = window.inner_size();
62
63 let metal_layer = {
64 let layer = CAMetalLayer::new();
65 layer.setDevice(Some(&device));
66 layer.setPixelFormat(MTLPixelFormat::BGRA8Unorm);
67 layer.setPresentsWithTransaction(false);
68 layer.setFramebufferOnly(false);
71 layer.setDrawableSize(CGSize::new(size.width as f64, size.height as f64));
72
73 if transparent {
75 layer.setOpaque(false);
76 }
77
78 let raw_handle = window
79 .window_handle()
80 .expect("Could not get window handle")
81 .as_raw();
82
83 match raw_handle {
84 RawWindowHandle::AppKit(appkit) => {
85 let view = unsafe { (appkit.ns_view.as_ptr() as *mut NSView).as_ref() }
86 .expect("NSView pointer is null");
87
88 view.setWantsLayer(true);
89 view.setLayer(Some(&layer));
90 }
91 _ => panic!("Metal driver only supports AppKit (macOS) windows"),
92 };
93
94 layer
95 };
96
97 let command_queue = device
98 .newCommandQueue()
99 .expect("Could not create Metal command queue");
100
101 let backend = unsafe {
102 mtl::BackendContext::new(
103 Retained::as_ptr(&device) as mtl::Handle,
104 Retained::as_ptr(&command_queue) as mtl::Handle,
105 )
106 };
107
108 let gr_context =
109 direct_contexts::make_metal(&backend, None).expect("Could not create Metal context");
110
111 let driver = Self {
112 metal_layer,
113 command_queue,
114 gr_context,
115 };
116
117 (driver, window)
118 }
119
120 pub fn present(
121 &mut self,
122 _size: PhysicalSize<u32>,
123 window: &Window,
124 render: impl FnOnce(&mut SkiaSurface),
125 ) {
126 let Some(drawable) = self.metal_layer.nextDrawable() else {
127 return;
129 };
130
131 let (drawable_width, drawable_height) = {
132 let size = self.metal_layer.drawableSize();
133 (size.width as i32, size.height as i32)
134 };
135
136 let texture_info =
137 unsafe { mtl::TextureInfo::new(Retained::as_ptr(&drawable.texture()) as mtl::Handle) };
138
139 let backend_render_target =
140 backend_render_targets::make_mtl((drawable_width, drawable_height), &texture_info);
141
142 let mut surface = wrap_backend_render_target(
143 &mut self.gr_context,
144 &backend_render_target,
145 SurfaceOrigin::TopLeft,
146 ColorType::BGRA8888,
147 None,
148 None,
149 )
150 .expect("Could not create Skia surface from Metal texture");
151
152 render(&mut surface);
153
154 window.pre_present_notify();
155 self.gr_context.flush_and_submit();
156 drop(surface);
157
158 let command_buffer = self
159 .command_queue
160 .commandBuffer()
161 .expect("Could not get Metal command buffer");
162
163 let mtl_drawable: Retained<ProtocolObject<dyn MTLDrawable>> = (&drawable).into();
164 command_buffer.presentDrawable(&mtl_drawable);
165 command_buffer.commit();
166 }
167
168 pub fn resize(&mut self, size: PhysicalSize<u32>) {
169 self.metal_layer
170 .setDrawableSize(CGSize::new(size.width as f64, size.height as f64));
171 }
172}