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