hello_triangle/
hello_triangle.rs1extern crate cobin;
2
3use {
4 std::mem,
5 cobin::{
6 AutoReleaseContext,
7 Strong,
8 util,
9 runtime,
10 runtime::NSObjectBase,
11 app_kit::*,
12 foundation::*,
13 core_graphics,
14 metal::*,
15 core_animation
16 }
17};
18
19fn main() {
20 let width = 1280;
21 let height = 720;
22
23 let app = unsafe {
24 let app = NSApplication::shared_application().as_ref().unwrap();
25 app.set_activation_policy(NSApplicationActivationPolicy::Regular);
26 setup_top_menu(app);
27 app.finish_launching();
28
29 #[cfg(debug_assertions)]
30 app.activate_ignoring_other_apps(true);
31
32 app
33 };
34
35 let window = unsafe { create_window(width, height) };
36
37 let device = unsafe { util::mtl_create_system_default_device().as_mut().unwrap() };
38
39 let layer = unsafe {
40 let layer = core_animation::CAMetalLayer::new();
41 layer.set_device(&mut *device);
42 layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
43 layer.set_framebuffer_only(false);
44 layer
45 };
46
47 unsafe {
48 let view = window.content_view().as_ref().unwrap();
49 view.set_wants_layer(true);
50 view.set_layer(layer.as_mut_ptr() as *mut core_animation::CALayer);
51 }
52
53 let mut pipeline_state = unsafe {
54 let shader_source = include_str!("hello_triangle.metal");
55 let shader_library = compile_shader_lib(device, shader_source);
56 create_pipeline_state(device, &shader_library)
57 };
58
59 let command_queue = unsafe { device.new_command_queue() };
60
61 let mut position_buffer = unsafe {
62 let positions: [(f32, f32); 3] = [
63 (0.0, 0.75),
64 (0.75, -0.75),
65 (-0.75, -0.75),
66 ];
67
68 device.new_buffer_with_bytes_length_options(
69 positions.as_ptr() as *const _,
70 mem::size_of_val(&positions),
71 MTLResourceOptions::CPU_CACHE_MODE_WRITE_COMBINED | MTLResourceOptions::STORAGE_MODE_MANAGED
72 )
73 };
74
75 let mut termination_requested = false;
76 let mut frame_count = 0;
77
78 while !termination_requested {
79 unsafe {
80 let _context = AutoReleaseContext::new();
81
82 poll_events(&app, &mut termination_requested);
83
84 let cmd_buffer = command_queue.command_buffer().as_mut().unwrap();
85
86 let drawable = layer.next_drawable();
87 let output_texture = (&*drawable).texture().as_mut().unwrap();
88
89 let mut render_pass_descriptor = create_render_pass_descriptor(output_texture);
90
91 let encoder = cmd_buffer.render_command_encoder_with_descriptor(&mut *render_pass_descriptor).as_ref().unwrap();
92 encoder.set_render_pipeline_state(&mut *pipeline_state);
93
94 encoder.set_vertex_buffer_offset_at_index(&mut *position_buffer, 0, 0);
95
96 let displacement: (f32, f32) = {
97 let radius = 0.1;
98 let speed = 0.05;
99
100 (
101 radius * f32::cos(frame_count as f32 * speed),
102 radius * f32::sin(frame_count as f32 * speed)
103 )
104 };
105 encoder.set_vertex_bytes_offset_at_index(
106 &displacement as *const (f32, f32) as *const _,
107 mem::size_of_val(&displacement),
108 1
109 );
110
111 encoder.draw_primitives_vertex_start_vertex_count(
112 MTLPrimitiveType::Triangle,
113 0,
114 3
115 );
116
117 encoder.end_encoding();
118
119 frame_count += 1;
120
121 cmd_buffer.present_drawable(drawable as *mut MTLDrawable);
122 cmd_buffer.commit();
123 }
124 }
125
126 println!("Terminated gracefully.");
127}
128
129unsafe fn setup_top_menu(app: &NSApplication) {
130 let mut app_menu = NSMenu::new();
131 {
132 let mut title = util::string("Quit");
133 let action = cobin::util::selector("terminate:");
134 let mut key = util::string("q");
135 app_menu.add_item_with_title_action_key_equivalent(&mut *title, action, &mut *key);
136 }
137
138 let mut app_item = NSMenuItem::new();
139 app_item.set_submenu(&mut *app_menu);
140
141 let mut bar = NSMenu::new();
142 bar.add_item(&mut *app_item);
143
144 app.set_main_menu(&mut *bar);
145}
146
147unsafe fn create_window(width: usize, height: usize) -> Strong<NSWindow> {
148 let content_rect = NSRect {
149 origin: core_graphics::CGPoint { x: 0.0, y: 0.0 },
150 size: core_graphics::CGSize { width: width as f64, height: height as f64 }
151 };
152 let mut title = util::string("Hello Triangle");
153
154 let window = NSWindow::new_with_content_rect_style_mask_backing_defer(
155 content_rect,
156 NSWindowStyleMask::Titled,
157 NSBackingStoreType::Buffered,
158 false
159 );
160 window.set_accepts_mouse_moved_events(true);
161 window.center();
162 window.set_title(&mut *title);
163 window.make_key_and_order_front(runtime::NIL);
164 window
165}
166
167unsafe fn create_pipeline_state(device: &MTLDevice, shader_lib: &MTLLibrary) -> Strong<MTLRenderPipelineState> {
168
169 let mut vertex_shader = shader_lib.new_function_with_name(&mut *util::string("vs"));
170 let mut frag_shader = shader_lib.new_function_with_name(&mut *util::string("ps"));
171
172 let mut descriptor = MTLRenderPipelineDescriptor::new();
173 descriptor.set_vertex_function(&mut *vertex_shader);
174 descriptor.set_fragment_function(&mut *frag_shader);
175
176 let color_attachments = descriptor.color_attachments().as_ref().unwrap();
177
178 let color_attachment_descriptor = color_attachments.object_at_indexed_subscript(0).as_ref().unwrap();
179 color_attachment_descriptor.set_pixel_format(MTLPixelFormat::RGBA8Unorm);
180
181 let pipeline_state = device.new_render_pipeline_state_with_descriptor_error(&mut *descriptor, 0 as *mut NSError);
182 assert!(!pipeline_state.is_null());
185
186 pipeline_state
187}
188
189pub unsafe fn compile_shader_lib(device: &MTLDevice, source: &str) -> Strong<MTLLibrary> {
190 let mut shader_source = util::string(source);
191 let mut options = MTLCompileOptions::new();
192 let lib = device.new_library_with_source_options_error(&mut *shader_source, &mut *options, 0 as *mut NSError);
193 assert!(!lib.is_null());
196
197 lib
198}
199
200unsafe fn create_render_pass_descriptor(texture: &mut MTLTexture) -> Strong<MTLRenderPassDescriptor> {
201 let descriptor = MTLRenderPassDescriptor::new();
202 let color_attachments = descriptor.color_attachments().as_ref().unwrap();
203
204 {
205 let attachment = color_attachments.object_at_indexed_subscript(0).as_ref().unwrap();
206 attachment.set_texture(&mut *texture);
207 attachment.set_load_action(MTLLoadAction::Clear);
208 attachment.set_store_action(MTLStoreAction::Store);
209 }
210
211 descriptor
212}
213
214unsafe fn poll_events(app: &NSApplication, termination_requested: &mut bool) {
215 loop {
216 let event = app.next_event_matching_mask_until_date_in_mode_dequeue(
217 NSEventMask::Any,
218 NSDate::distant_past(),
219 util::ns_default_run_loop_mode(),
220 true
221 );
222
223 match event.as_mut() {
224 None => break,
225 Some(event) => {
226 match event.event_type() {
227 NSEventType::KeyDown => {
228 *termination_requested = true
229 },
230 _ => app.send_event(event)
231 }
232 }
233 }
234 }
235}