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