hello_triangle/
hello_triangle.rs

1extern 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  // TODO: Check error.
183
184  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  // TODO: Check error.
194
195  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}