1 use cocoa::{appkit::NSView, base::id as cocoa_id};
2
3 use metal::*;
4 use objc::{rc::autoreleasepool, runtime::YES};
5 use std::mem;
6 use winit::platform::macos::WindowExtMacOS;
7
8 use winit::{
9 event::{Event, WindowEvent},
10 event_loop::ControlFlow,
11 };
12
13 struct App {
14 pub device: Device,
15 pub command_queue: CommandQueue,
16 pub layer: MetalLayer,
17 pub image_fill_cps: ComputePipelineState,
18 pub width: u32,
19 pub height: u32,
20 }
21
select_device() -> Option<Device>22 fn select_device() -> Option<Device> {
23 let devices = Device::all();
24 for device in devices {
25 if device.supports_dynamic_libraries() {
26 return Some(device);
27 }
28 }
29
30 None
31 }
32
33 impl App {
new(window: &winit::window::Window) -> Self34 fn new(window: &winit::window::Window) -> Self {
35 let device = select_device().expect("no device found that supports dynamic libraries");
36 let command_queue = device.new_command_queue();
37
38 let layer = MetalLayer::new();
39 layer.set_device(&device);
40 layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
41 layer.set_presents_with_transaction(false);
42 layer.set_framebuffer_only(false);
43 unsafe {
44 let view = window.ns_view() as cocoa_id;
45 view.setWantsLayer(YES);
46 view.setLayer(mem::transmute(layer.as_ref()));
47 }
48 let draw_size = window.inner_size();
49 layer.set_drawable_size(CGSize::new(draw_size.width as f64, draw_size.height as f64));
50
51 // compile dynamic lib shader
52 let dylib_src_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
53 .join("examples/shader-dylib/test_dylib.metal");
54 let install_path =
55 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target/test_dylib.metallib");
56
57 let dylib_src = std::fs::read_to_string(dylib_src_path).expect("bad shit");
58 let opts = metal::CompileOptions::new();
59 opts.set_library_type(MTLLibraryType::Dynamic);
60 opts.set_install_name(install_path.to_str().unwrap());
61
62 let lib = device
63 .new_library_with_source(dylib_src.as_str(), &opts)
64 .unwrap();
65
66 // create dylib
67 let dylib = device.new_dynamic_library(&lib).unwrap();
68 dylib.set_label("test_dylib");
69
70 // optional: serialize binary blob that can be loaded later
71 let blob_url = String::from("file://") + install_path.to_str().unwrap();
72 let url = URL::new_with_string(&blob_url);
73 dylib.serialize_to_url(&url).unwrap();
74
75 // create shader that links with dylib
76 let shader_src_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
77 .join("examples/shader-dylib/test_shader.metal");
78
79 let shader_src = std::fs::read_to_string(shader_src_path).expect("bad shit");
80 let opts = metal::CompileOptions::new();
81 // add dynamic library to link with
82 let libraries = [dylib.as_ref()];
83 opts.set_libraries(&libraries);
84
85 // compile
86 let shader_lib = device
87 .new_library_with_source(shader_src.as_str(), &opts)
88 .unwrap();
89
90 let func = shader_lib.get_function("test_kernel", None).unwrap();
91
92 // create pipeline state
93 // linking occurs here
94 let image_fill_cps = device
95 .new_compute_pipeline_state_with_function(&func)
96 .unwrap();
97
98 Self {
99 device,
100 command_queue,
101 layer,
102 image_fill_cps,
103 width: draw_size.width,
104 height: draw_size.height,
105 }
106 }
107
resize(&mut self, width: u32, height: u32)108 fn resize(&mut self, width: u32, height: u32) {
109 self.layer
110 .set_drawable_size(CGSize::new(width as f64, height as f64));
111 self.width = width;
112 self.height = height;
113 }
114
draw(&self)115 fn draw(&self) {
116 let drawable = match self.layer.next_drawable() {
117 Some(drawable) => drawable,
118 None => return,
119 };
120
121 let w = self.image_fill_cps.thread_execution_width();
122 let h = self.image_fill_cps.max_total_threads_per_threadgroup() / w;
123 let threads_per_threadgroup = MTLSize::new(w, h, 1);
124 let threads_per_grid = MTLSize::new(self.width as _, self.height as _, 1);
125
126 let command_buffer = self.command_queue.new_command_buffer();
127
128 {
129 let encoder = command_buffer.new_compute_command_encoder();
130 encoder.set_compute_pipeline_state(&self.image_fill_cps);
131 encoder.set_texture(0, Some(&drawable.texture()));
132 encoder.dispatch_threads(threads_per_grid, threads_per_threadgroup);
133 encoder.end_encoding();
134 }
135
136 command_buffer.present_drawable(&drawable);
137 command_buffer.commit();
138 }
139 }
140
main()141 fn main() {
142 let events_loop = winit::event_loop::EventLoop::new();
143 let size = winit::dpi::LogicalSize::new(800, 600);
144
145 let window = winit::window::WindowBuilder::new()
146 .with_inner_size(size)
147 .with_title("Metal Shader Dylib Example".to_string())
148 .build(&events_loop)
149 .unwrap();
150
151 let mut app = App::new(&window);
152
153 events_loop.run(move |event, _, control_flow| {
154 autoreleasepool(|| {
155 *control_flow = ControlFlow::Poll;
156
157 match event {
158 Event::WindowEvent { event, .. } => match event {
159 WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
160 WindowEvent::Resized(size) => {
161 app.resize(size.width, size.height);
162 }
163 _ => (),
164 },
165 Event::MainEventsCleared => {
166 window.request_redraw();
167 }
168 Event::RedrawRequested(_) => {
169 app.draw();
170 }
171 _ => {}
172 }
173 });
174 });
175 }
176