1package main
2
3import "base:intrinsics"
4
5import "core:os"
6import "core:math"
7import "core:fmt"
8import NS "core:sys/darwin/Foundation"
9
10import MTL "vendor:darwin/Metal"
11import CA "vendor:darwin/QuartzCore"
12
13main :: proc() {
14 err := metal_main()
15 if err != nil {
16 fmt.eprintln(err->localizedDescription()->odinString())
17 os.exit(1)
18 }
19}
20
21metal_main :: proc() -> (err: ^NS.Error) {
22 @static quit := false
23
24 app := NS.Application.sharedApplication()
25 defer app->release()
26 app->setActivationPolicy(.Regular)
27 app->finishLaunching()
28
29 create_main_menu(app)
30
31 screen_rect := NS.Screen.mainScreen()->visibleFrame()
32 window_size := NS.Size{800, 600}
33 window_origin: NS.Point = {
34 NS.Float(math.floor(f64(screen_rect.size.width - window_size.width) / 2)),
35 NS.Float(math.floor(f64(screen_rect.size.height - window_size.height) / 2)),
36 }
37
38 CustomWindowClass := NS.objc_allocateClassPair(intrinsics.objc_find_class("NSWindow"), "CustomWindow", 0)
39 assert(CustomWindowClass != nil)
40 keyDown :: proc "c" (self: NS.id, sel: NS.SEL, event: ^NS.Event) { }
41 NS.class_addMethod(CustomWindowClass, intrinsics.objc_find_selector("keyDown:"), NS.IMP(keyDown), "v@:@")
42 NS.objc_registerClassPair(CustomWindowClass)
43 window := cast(^NS.Window)(NS.class_createInstance(CustomWindowClass, size_of(NS.Window)))
44 defer window->release()
45 window->initWithContentRect({window_origin, window_size}, { .Resizable, .Closable, .Titled, .Miniaturizable }, .Buffered, NS.NO)
46 window->setDelegate(NS.window_delegate_register_and_alloc({
47 windowWillClose = proc(^NS.Notification) {
48 quit = true
49 },
50 }, "CustomWindowDelegate", context))
51 window->setTitle(NS.MakeConstantString("Use left/right arrow keys to rotate the triangle"))
52 window->setIsVisible(true)
53
54 app->activateIgnoringOtherApps(true)
55
56
57 device := MTL.CreateSystemDefaultDevice()
58 defer device->release()
59
60 fmt.println("Metal device:", device->name()->odinString())
61
62 swapchain := CA.MetalLayer.layer()
63 defer swapchain->release()
64
65 swapchain->setDevice(device)
66 swapchain->setPixelFormat(.BGRA8Unorm_sRGB)
67 swapchain->setFramebufferOnly(true)
68 swapchain->setFrame(window->frame())
69
70 window->contentView()->setLayer(swapchain)
71 window->setOpaque(true)
72 window->setBackgroundColor(nil)
73
74 library, pso := build_shaders(device) or_return
75 defer library->release()
76 defer pso->release()
77
78 vertex_positions_buffer, vertex_colors_buffer, arg_buffer := build_buffers(device, library)
79 defer arg_buffer->release()
80
81 frame_data_buffer := device->newBuffer(size_of(Frame_Data), {.StorageModeManaged})
82 defer frame_data_buffer->release()
83
84 command_queue := device->newCommandQueue()
85 defer command_queue->release()
86
87 @static angle: f32
88
89 for !quit {
90 {
91 NS.scoped_autoreleasepool()
92
93 event := app->nextEventMatchingMask(NS.EventMaskAny, NS.Date.distantFuture(), NS.DefaultRunLoopMode, true)
94 for event != nil {
95 event_type := event->type()
96 #partial switch event_type {
97 case .KeyDown, .KeyUp:
98 code := NS.kVK(event->keyCode())
99 #partial switch code {
100 case .Escape:
101 quit = true
102 case .LeftArrow:
103 angle -= 0.02
104 case .RightArrow:
105 angle += 0.02
106 }
107 fmt.println(event_type, code, event->modifierFlags())
108 case:
109 fmt.println(event_type, event->locationInWindow(), event->modifierFlags())
110 }
111 app->sendEvent(event)
112
113
114 event = app->nextEventMatchingMask(NS.EventMaskAny, nil, NS.DefaultRunLoopMode, true)
115 }
116 app->updateWindows()
117 }
118
119 frame_data := (^Frame_Data)(frame_data_buffer->contentsPointer())
120 frame_data.angle = angle
121 frame_data_buffer->didModifyRange(NS.Range_Make(0, size_of(Frame_Data)))
122
123 drawable := swapchain->nextDrawable()
124 assert(drawable != nil)
125 defer drawable->release()
126
127 pass := MTL.RenderPassDescriptor.renderPassDescriptor()
128 defer pass->release()
129
130 color_attachment := pass->colorAttachments()->object(0)
131 color_attachment->setClearColor(MTL.ClearColor{0.25, 0.5, 1.0, 1.0})
132 color_attachment->setLoadAction(.Clear)
133 color_attachment->setStoreAction(.Store)
134 color_attachment->setTexture(drawable->texture())
135
136 command_buffer := command_queue->commandBuffer()
137 defer command_buffer->release()
138
139 render_encoder := command_buffer->renderCommandEncoderWithDescriptor(pass)
140 defer render_encoder->release()
141
142 render_encoder->setRenderPipelineState(pso)
143 render_encoder->setVertexBuffer(arg_buffer, 0, 0)
144 render_encoder->setVertexBuffer(frame_data_buffer, 0, 1)
145 render_encoder->useResource(vertex_positions_buffer, {.Read})
146 render_encoder->useResource(vertex_colors_buffer, {.Read})
147 render_encoder->drawPrimitives(.Triangle, 0, 3)
148
149 render_encoder->endEncoding()
150
151 command_buffer->presentDrawable(drawable)
152 command_buffer->commit()
153 }
154
155 return nil
156}
157
158Frame_Data :: struct {
159 angle: f32,
160}
161
162build_shaders :: proc(device: ^MTL.Device) -> (library: ^MTL.Library, pso: ^MTL.RenderPipelineState, err: ^NS.Error) {
163 shader_src := `
164 #include <metal_stdlib>
165 using namespace metal;
166
167 struct v2f {
168 float4 position [[position]];
169 half3 color;
170 };
171
172 struct Vertex_Data {
173 device packed_float3* positions [[id(0)]];
174 device packed_float3* colors [[id(1)]];
175 };
176
177 struct Frame_Data {
178 float angle;
179 };
180
181 v2f vertex vertex_main(device const Vertex_Data* vertex_data [[buffer(0)]],
182 device const Frame_Data* frame_data [[buffer(1)]],
183 uint vertex_id [[vertex_id]]) {
184 float a = frame_data->angle;
185 float3x3 rotation_matrix = float3x3(sin(a), cos(a), 0.0, cos(a), -sin(a), 0.0, 0.0, 0.0, 1.0);
186 float3 position = float3(vertex_data->positions[vertex_id]);
187 v2f o;
188 o.position = float4(rotation_matrix * position, 1.0);
189 o.color = half3(vertex_data->colors[vertex_id]);
190 return o;
191 }
192
193 half4 fragment fragment_main(v2f in [[stage_in]]) {
194 return half4(in.color, 1.0);
195 }
196 `
197 shader_src_str := NS.String.alloc()->initWithOdinString(shader_src)
198 defer shader_src_str->release()
199
200 library = device->newLibraryWithSource(shader_src_str, nil) or_return
201
202 vertex_function := library->newFunctionWithName(NS.AT("vertex_main"))
203 fragment_function := library->newFunctionWithName(NS.AT("fragment_main"))
204 defer vertex_function->release()
205 defer fragment_function->release()
206
207 desc := MTL.RenderPipelineDescriptor.alloc()->init()
208 defer desc->release()
209
210 desc->setVertexFunction(vertex_function)
211 desc->setFragmentFunction(fragment_function)
212 desc->colorAttachments()->object(0)->setPixelFormat(.BGRA8Unorm_sRGB)
213
214 pso = device->newRenderPipelineStateWithDescriptor(desc) or_return
215 return
216}
217
218build_buffers :: proc(device: ^MTL.Device, library: ^MTL.Library) -> (vertex_positions_buffer, vertex_colors_buffer, arg_buffer: ^MTL.Buffer) {
219 NUM_VERTICES :: 3
220 positions := [NUM_VERTICES][3]f32{
221 {-0.8, 0.8, 0.0},
222 { 0.0, -0.8, 0.0},
223 {+0.8, 0.8, 0.0},
224 }
225 colors := [NUM_VERTICES][3]f32{
226 {1.0, 0.3, 0.2},
227 {0.8, 1.0, 0.0},
228 {0.8, 0.0, 1.0},
229 }
230
231 vertex_positions_buffer = device->newBufferWithSlice(positions[:], {.StorageModeManaged})
232 vertex_colors_buffer = device->newBufferWithSlice(colors[:], {.StorageModeManaged})
233
234 vertex_function := library->newFunctionWithName(NS.AT("vertex_main"))
235 defer vertex_function->release()
236
237 arg_encoder := vertex_function->newArgumentEncoder(0)
238 defer arg_encoder->release()
239
240 arg_buffer = device->newBuffer(arg_encoder->encodedLength(), {.StorageModeManaged})
241 arg_encoder->setArgumentBufferWithOffset(arg_buffer, 0)
242 arg_encoder->setBuffer(vertex_positions_buffer, 0, 0)
243 arg_encoder->setBuffer(vertex_colors_buffer, 0, 1)
244 arg_buffer->didModifyRange(NS.Range_Make(0, arg_buffer->length()))
245
246 return
247}
248
249create_main_menu :: proc(app: ^NS.Application) {
250 main_menu := NS.Menu.alloc()->init()
251 main_menu->addItem(NS.MenuItem.alloc()->init())
252
253 main_menu_app_item := main_menu->addItemWithTitle(NS.AT("Metal"), nil, NS.AT(""))
254 app_menu := NS.Menu.alloc()->init()
255 app_menu->addItemWithTitle(NS.AT("Quit"), intrinsics.objc_find_selector("terminate:"), NS.AT("q"))
256 main_menu_app_item->setSubmenu(app_menu)
257
258 main_menu_edit_item := main_menu->addItemWithTitle(NS.AT("Edit"), nil, NS.AT(""))
259 edit_menu := NS.Menu.alloc()->init()
260 main_menu_edit_item->setSubmenu(edit_menu)
261
262 main_menu_view_item := main_menu->addItemWithTitle(NS.AT("View"), nil, NS.AT(""))
263 view_menu := NS.Menu.alloc()->init()
264 view_menu->addItemWithTitle(NS.AT("Enter Full Screen"), intrinsics.objc_find_selector("toggleFullScreen:"), NS.AT("f"))
265 main_menu_view_item->setSubmenu(view_menu)
266
267 app->setMainMenu(main_menu)
268}