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