1package main
2
3import NS "core:sys/darwin/Foundation"
4import MTL "vendor:darwin/Metal"
5import CA "vendor:darwin/QuartzCore"
6
7import SDL "vendor:sdl2"
8
9import "core:fmt"
10import "core:os"
11import "core:math"
12import glm "core:math/linalg/glsl"
13
14
15Vertex_Data :: struct {
16 position: glm.vec3,
17 normal: glm.vec3,
18}
19
20Instance_Data :: struct #align(16) {
21 transform: glm.mat4,
22 color: glm.vec4,
23 normal_transform: glm.mat3,
24}
25
26INSTANCE_WIDTH :: 10
27INSTANCE_HEIGHT :: 10
28INSTANCE_DEPTH :: 10
29NUM_INSTANCES :: INSTANCE_WIDTH*INSTANCE_HEIGHT*INSTANCE_DEPTH
30
31Camera_Data :: struct #align(16) {
32 perspective_transform: glm.mat4,
33 world_transform: glm.mat4,
34 world_normal_transform: glm.mat3,
35}
36
37build_shaders :: proc(device: ^MTL.Device) -> (library: ^MTL.Library, pso: ^MTL.RenderPipelineState, err: ^NS.Error) {
38 shader_src := `
39 #include <metal_stdlib>
40 using namespace metal;
41
42 struct v2f {
43 float4 position [[position]];
44 float3 normal;
45 half3 color;
46 };
47
48 struct Vertex_Data {
49 packed_float3 position;
50 packed_float3 normal;
51 };
52
53 struct Instance_Data {
54 float4x4 transform;
55 float4 color;
56 float3x3 normal_transform;
57 };
58
59 struct Camera_Data {
60 float4x4 perspective_transform;
61 float4x4 world_transform;
62 float3x3 world_normal_transform;
63 };
64
65 v2f vertex vertex_main(device const Vertex_Data* vertex_data [[buffer(0)]],
66 device const Instance_Data* instance_data [[buffer(1)]],
67 device const Camera_Data& camera_data [[buffer(2)]],
68 uint vertex_id [[vertex_id]],
69 uint instance_id [[instance_id]]) {
70 v2f o;
71
72 const device Vertex_Data& vd = vertex_data[vertex_id];
73 const device Instance_Data& id = instance_data[instance_id];
74
75 float4 pos = float4(vd.position, 1.0);
76 pos = id.transform * pos;
77 pos = camera_data.perspective_transform * camera_data.world_transform * pos;
78 o.position = pos;
79
80 float3 normal = id.normal_transform * float3(vd.normal);
81 normal = camera_data.world_normal_transform * normal;
82 o.normal = normal;
83
84 o.color = half3(id.color.rgb);
85 return o;
86 }
87
88 half4 fragment fragment_main(v2f in [[stage_in]]) {
89 // assume light coming from front-top-right
90 float3 l = normalize(float3(1.0, 1.0, 0.8));
91 float3 n = normalize(in.normal);
92
93 float ndotl = saturate(dot(n, l));
94
95 return half4(in.color * 0.1 + in.color * ndotl, 1.0);
96 }
97 `
98 shader_src_str := NS.String.alloc()->initWithOdinString(shader_src)
99 defer shader_src_str->release()
100
101 library = device->newLibraryWithSource(shader_src_str, nil) or_return
102
103 vertex_function := library->newFunctionWithName(NS.AT("vertex_main"))
104 fragment_function := library->newFunctionWithName(NS.AT("fragment_main"))
105 defer vertex_function->release()
106 defer fragment_function->release()
107
108 desc := MTL.RenderPipelineDescriptor.alloc()->init()
109 defer desc->release()
110
111 desc->setVertexFunction(vertex_function)
112 desc->setFragmentFunction(fragment_function)
113 desc->colorAttachments()->object(0)->setPixelFormat(.BGRA8Unorm_sRGB)
114 desc->setDepthAttachmentPixelFormat(.Depth16Unorm)
115
116 pso = device->newRenderPipelineStateWithDescriptor(desc) or_return
117 return
118}
119
120build_buffers :: proc(device: ^MTL.Device) -> (vertex_buffer, index_buffer, instance_buffer: ^MTL.Buffer) {
121 s :: 0.5
122 positions := []Vertex_Data{
123
124 {{-s, -s, +s}, {0, 0, 1}},
125 {{+s, -s, +s}, {0, 0, 1}},
126 {{+s, +s, +s}, {0, 0, 1}},
127 {{-s, +s, +s}, {0, 0, 1}},
128 {{+s, -s, +s}, {1, 0, 0}},
129 {{+s, -s, -s}, {1, 0, 0}},
130 {{+s, +s, -s}, {1, 0, 0}},
131 {{+s, +s, +s}, {1, 0, 0}},
132 {{+s, -s, -s}, {0, 0, -1}},
133 {{-s, -s, -s}, {0, 0, -1}},
134 {{-s, +s, -s}, {0, 0, -1}},
135 {{+s, +s, -s}, {0, 0, -1}},
136
137 {{-s, -s, -s}, {-1, 0, 0}},
138 {{-s, -s, +s}, {-1, 0, 0}},
139 {{-s, +s, +s}, {-1, 0, 0}},
140 {{-s, +s, -s}, {-1, 0, 0}},
141
142 {{-s, +s, +s}, {0, 1, 0}},
143 {{+s, +s, +s}, {0, 1, 0}},
144 {{+s, +s, -s}, {0, 1, 0}},
145 {{-s, +s, -s}, {0, 1, 0}},
146
147 {{-s, -s, -s}, {0, -1, 0}},
148 {{+s, -s, -s}, {0, -1, 0}},
149 {{+s, -s, +s}, {0, -1, 0}},
150 {{-s, -s, +s}, {0, -1, 0}},
151 }
152 indices := []u16{
153 0, 1, 2, 2, 3, 0,
154 4, 5, 6, 6, 7, 4,
155 8, 9, 10, 10, 11, 8,
156 12, 13, 14, 14, 15, 12,
157 16, 17, 18, 18, 19, 16,
158 20, 21, 22, 22, 23, 20,
159 }
160
161 vertex_buffer = device->newBufferWithSlice(positions[:], {.StorageModeManaged})
162 index_buffer = device->newBufferWithSlice(indices[:], {.StorageModeManaged})
163 instance_buffer = device->newBuffer(NUM_INSTANCES*size_of(Instance_Data), {.StorageModeManaged})
164 return
165}
166
167metal_main :: proc() -> (err: ^NS.Error) {
168 SDL.SetHint(SDL.HINT_RENDER_DRIVER, "metal")
169 SDL.setenv("METAL_DEVICE_WRAPPER_TYPE", "1", 0)
170 SDL.Init({.VIDEO})
171 defer SDL.Quit()
172
173 window := SDL.CreateWindow("Metal in Odin - 06 lighting",
174 SDL.WINDOWPOS_CENTERED, SDL.WINDOWPOS_CENTERED,
175 1024, 1024,
176 {.ALLOW_HIGHDPI, .HIDDEN, .RESIZABLE},
177 )
178 defer SDL.DestroyWindow(window)
179
180 window_system_info: SDL.SysWMinfo
181 SDL.GetVersion(&window_system_info.version)
182 SDL.GetWindowWMInfo(window, &window_system_info)
183 assert(window_system_info.subsystem == .COCOA)
184
185 native_window := (^NS.Window)(window_system_info.info.cocoa.window)
186
187 device := MTL.CreateSystemDefaultDevice()
188 defer device->release()
189
190 fmt.println(device->name()->odinString())
191
192 swapchain := CA.MetalLayer.layer()
193 defer swapchain->release()
194
195 swapchain->setDevice(device)
196 swapchain->setPixelFormat(.BGRA8Unorm_sRGB)
197 swapchain->setFramebufferOnly(true)
198 swapchain->setFrame(native_window->frame())
199
200 native_window->contentView()->setLayer(swapchain)
201 native_window->setOpaque(true)
202 native_window->setBackgroundColor(nil)
203
204 library, pso := build_shaders(device) or_return
205 defer library->release()
206 defer pso->release()
207
208
209 depth_stencil_state: ^MTL.DepthStencilState
210 depth_desc := MTL.DepthStencilDescriptor.alloc()->init()
211 depth_desc->setDepthCompareFunction(.Less)
212 depth_desc->setDepthWriteEnabled(true)
213 depth_stencil_state = device->newDepthStencilState(depth_desc)
214 depth_desc->release()
215
216 vertex_buffer, index_buffer, instance_buffer := build_buffers(device)
217 defer vertex_buffer->release()
218 defer index_buffer->release()
219 defer instance_buffer->release()
220
221 camera_buffer := device->newBuffer(size_of(Camera_Data), {.StorageModeManaged})
222 defer camera_buffer->release()
223
224 depth_texture: ^MTL.Texture = nil
225 defer if depth_texture != nil { depth_texture->release() }
226
227 command_queue := device->newCommandQueue()
228 defer command_queue->release()
229
230 SDL.ShowWindow(window)
231 for quit := false; !quit; {
232 for e: SDL.Event; SDL.PollEvent(&e); {
233 #partial switch e.type {
234 case .QUIT:
235 quit = true
236 case .KEYDOWN:
237 if e.key.keysym.sym == .ESCAPE {
238 quit = true
239 }
240 }
241 }
242
243 w, h: i32
244 SDL.GetWindowSize(window, &w, &h)
245 aspect_ratio := f32(w)/max(f32(h), 1)
246
247
248 {
249 @static angle: f32
250 angle += 0.002
251
252 object_position := glm.vec3{0, 0, -10}
253 rt := glm.mat4Translate(object_position)
254 rr1 := glm.mat4Rotate({0, 1, 0}, -angle)
255 rr0 := glm.mat4Rotate({1, 0, 0}, angle*0.5)
256 rt_inv := glm.mat4Translate(-object_position)
257 full_obj_rot := rt * rr1 * rr0 * rt_inv
258
259
260 ix, iy, iz := 0, 0, 0
261
262 instance_data := instance_buffer->contentsAsSlice([]Instance_Data)[:NUM_INSTANCES]
263 for &instance, idx in instance_data {
264 if ix == INSTANCE_WIDTH {
265 ix = 0
266 iy += 1
267 }
268 if iy == INSTANCE_HEIGHT {
269 iy = 0
270 iz += 1
271 }
272 defer ix += 1
273
274 scl :: 0.2
275
276 scale := glm.mat4Scale({scl, scl, scl})
277 zrot := glm.mat4Rotate({0, 0, 1}, angle * math.sin(f32(ix)))
278 yrot := glm.mat4Rotate({0, 1, 0}, angle * math.cos(f32(iy)))
279
280 pos := glm.vec3{
281 (f32(ix) - INSTANCE_WIDTH * 0.5) * 2*scl + scl,
282 (f32(iy) - INSTANCE_HEIGHT* 0.5) * 2*scl + scl,
283 (f32(iz) - INSTANCE_DEPTH * 0.5) * 2*scl,
284 }
285
286 translate := glm.mat4Translate(object_position + pos)
287
288 instance.transform = full_obj_rot * translate * yrot * zrot * scale
289 instance.normal_transform = glm.mat3(instance.transform)
290
291 r := f32(idx) / NUM_INSTANCES
292 instance.color = {r, 1-r, math.sin(math.TAU * r), 1}
293
294 }
295 sz := NS.UInteger(len(instance_data)*size_of(instance_data[0]))
296 instance_buffer->didModifyRange(NS.Range_Make(0, sz))
297 }
298
299 {
300 camera_data := camera_buffer->contentsAsType(Camera_Data)
301 camera_data.perspective_transform = glm.mat4Perspective(glm.radians_f32(45), aspect_ratio, 0.03, 500)
302 camera_data.world_transform = 1
303 camera_data.world_normal_transform = glm.mat3(camera_data.world_transform)
304
305 camera_buffer->didModifyRange(NS.Range_Make(0, size_of(Camera_Data)))
306 }
307
308 if depth_texture == nil ||
309 depth_texture->width() != NS.UInteger(w) ||
310 depth_texture->height() != NS.UInteger(h) {
311 desc := MTL.TextureDescriptor.texture2DDescriptorWithPixelFormat(
312 pixelFormat = .Depth16Unorm,
313 width = NS.UInteger(w),
314 height = NS.UInteger(h),
315 mipmapped = false,
316 )
317 defer desc->release()
318
319 desc->setUsage({.RenderTarget})
320 desc->setStorageMode(.Private)
321
322 if depth_texture != nil {
323 depth_texture->release()
324 }
325
326 depth_texture = device->newTextureWithDescriptor(desc)
327 }
328
329
330 drawable := swapchain->nextDrawable()
331 assert(drawable != nil)
332 defer drawable->release()
333
334 pass := MTL.RenderPassDescriptor.renderPassDescriptor()
335 defer pass->release()
336
337 color_attachment := pass->colorAttachments()->object(0)
338 assert(color_attachment != nil)
339 color_attachment->setClearColor(MTL.ClearColor{0.1, 0.1, 0.1, 1.0})
340 color_attachment->setLoadAction(.Clear)
341 color_attachment->setStoreAction(.Store)
342 color_attachment->setTexture(drawable->texture())
343
344 depth_attachment := pass->depthAttachment()
345 depth_attachment->setTexture(depth_texture)
346 depth_attachment->setClearDepth(1.0)
347 depth_attachment->setLoadAction(.Clear)
348 depth_attachment->setStoreAction(.Store)
349
350 command_buffer := command_queue->commandBuffer()
351 defer command_buffer->release()
352
353 render_encoder := command_buffer->renderCommandEncoderWithDescriptor(pass)
354 defer render_encoder->release()
355
356 render_encoder->setRenderPipelineState(pso)
357 render_encoder->setDepthStencilState(depth_stencil_state)
358
359 render_encoder->setVertexBuffer(buffer=vertex_buffer, offset=0, index=0)
360 render_encoder->setVertexBuffer(buffer=instance_buffer, offset=0, index=1)
361 render_encoder->setVertexBuffer(buffer=camera_buffer, offset=0, index=2)
362
363 render_encoder->setCullMode(.Back)
364 render_encoder->setFrontFacingWinding(.CounterClockwise)
365 render_encoder->drawIndexedPrimitivesWithInstanceCount(.Triangle, 6*6, .UInt16, index_buffer, 0, NUM_INSTANCES)
366
367 render_encoder->endEncoding()
368
369 command_buffer->presentDrawable(drawable)
370 command_buffer->commit()
371 }
372
373 return nil
374}
375
376main :: proc() {
377 err := metal_main()
378 if err != nil {
379 fmt.eprintln(err->localizedDescription()->odinString())
380 os.exit(1)
381 }
382}