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
15Instance_Data :: struct #align(16) {
16 transform: glm.mat4,
17 color: glm.vec4,
18}
19
20NUM_INSTANCES :: 32
21
22Camera_Data :: struct {
23 perspective_transform: glm.mat4,
24 world_transform: glm.mat4,
25}
26
27build_shaders :: proc(device: ^MTL.Device) -> (library: ^MTL.Library, pso: ^MTL.RenderPipelineState, err: ^NS.Error) {
28 shader_src := `
29 #include <metal_stdlib>
30 using namespace metal;
31
32 struct v2f {
33 float4 position [[position]];
34 half3 color;
35 };
36
37 struct Vertex_Data {
38 packed_float3 position;
39 };
40
41 struct Instance_Data {
42 float4x4 transform;
43 float4 color;
44 };
45
46 struct Camera_Data {
47 float4x4 perspective_transform;
48 float4x4 world_transform;
49 };
50
51 v2f vertex vertex_main(device const Vertex_Data* vertex_data [[buffer(0)]],
52 device const Instance_Data* instance_data [[buffer(1)]],
53 device const Camera_Data& camera_data [[buffer(2)]],
54 uint vertex_id [[vertex_id]],
55 uint instance_id [[instance_id]]) {
56 v2f o;
57 float4 pos = float4(vertex_data[vertex_id].position, 1.0);
58 pos = instance_data[instance_id].transform * pos;
59 pos = camera_data.perspective_transform * camera_data.world_transform * pos;
60 o.position = pos;
61 o.color = half3(instance_data[instance_id].color.rgb);
62 return o;
63 }
64
65 half4 fragment fragment_main(v2f in [[stage_in]]) {
66 return half4(in.color, 1.0);
67 }
68 `
69 shader_src_str := NS.String.alloc()->initWithOdinString(shader_src)
70 defer shader_src_str->release()
71
72 library = device->newLibraryWithSource(shader_src_str, nil) or_return
73
74 vertex_function := library->newFunctionWithName(NS.AT("vertex_main"))
75 fragment_function := library->newFunctionWithName(NS.AT("fragment_main"))
76 defer vertex_function->release()
77 defer fragment_function->release()
78
79 desc := MTL.RenderPipelineDescriptor.alloc()->init()
80 defer desc->release()
81
82 desc->setVertexFunction(vertex_function)
83 desc->setFragmentFunction(fragment_function)
84 desc->colorAttachments()->object(0)->setPixelFormat(.BGRA8Unorm_sRGB)
85 desc->setDepthAttachmentPixelFormat(.Depth16Unorm)
86
87 pso = device->newRenderPipelineStateWithDescriptor(desc) or_return
88 return
89}
90
91build_buffers :: proc(device: ^MTL.Device) -> (vertex_buffer, index_buffer, instance_buffer: ^MTL.Buffer) {
92 s :: 0.5
93 positions := [][3]f32{
94 {-s, -s, +s},
95 {+s, -s, +s},
96 {+s, +s, +s},
97 {-s, +s, +s},
98
99 {-s, -s, -s},
100 {-s, +s, -s},
101 {+s, +s, -s},
102 {+s, -s, -s},
103 }
104 indices := []u16{
105 0, 1, 2,
106 2, 3, 0,
107
108 1, 7, 6,
109 6, 2, 1,
110
111 7, 4, 5,
112 5, 6, 7,
113
114 4, 0, 3,
115 3, 5, 4,
116
117 3, 2, 6,
118 6, 5, 3,
119
120 4, 7, 1,
121 1, 0, 4,
122 }
123
124 vertex_buffer = device->newBufferWithSlice(positions[:], {.StorageModeManaged})
125 index_buffer = device->newBufferWithSlice(indices[:], {.StorageModeManaged})
126 instance_buffer = device->newBuffer(NUM_INSTANCES*size_of(Instance_Data), {.StorageModeManaged})
127 return
128}
129
130metal_main :: proc() -> (err: ^NS.Error) {
131 SDL.SetHint(SDL.HINT_RENDER_DRIVER, "metal")
132 SDL.setenv("METAL_DEVICE_WRAPPER_TYPE", "1", 0)
133 SDL.Init({.VIDEO})
134 defer SDL.Quit()
135
136 window := SDL.CreateWindow("Metal in Odin - 05 perspective",
137 SDL.WINDOWPOS_CENTERED, SDL.WINDOWPOS_CENTERED,
138 854, 480,
139 {.ALLOW_HIGHDPI, .HIDDEN, .RESIZABLE},
140 )
141 defer SDL.DestroyWindow(window)
142
143 window_system_info: SDL.SysWMinfo
144 SDL.GetVersion(&window_system_info.version)
145 SDL.GetWindowWMInfo(window, &window_system_info)
146 assert(window_system_info.subsystem == .COCOA)
147
148 native_window := (^NS.Window)(window_system_info.info.cocoa.window)
149
150 device := MTL.CreateSystemDefaultDevice()
151 defer device->release()
152
153 fmt.println(device->name()->odinString())
154
155 swapchain := CA.MetalLayer.layer()
156 defer swapchain->release()
157
158 swapchain->setDevice(device)
159 swapchain->setPixelFormat(.BGRA8Unorm_sRGB)
160 swapchain->setFramebufferOnly(true)
161 swapchain->setFrame(native_window->frame())
162
163 native_window->contentView()->setLayer(swapchain)
164 native_window->setOpaque(true)
165 native_window->setBackgroundColor(nil)
166
167 library, pso := build_shaders(device) or_return
168 defer library->release()
169 defer pso->release()
170
171 vertex_buffer, index_buffer, instance_buffer := build_buffers(device)
172 defer vertex_buffer->release()
173 defer index_buffer->release()
174 defer instance_buffer->release()
175
176 camera_buffer := device->newBuffer(size_of(Camera_Data), {.StorageModeManaged})
177 defer camera_buffer->release()
178
179 depth_texture: ^MTL.Texture = nil
180
181 command_queue := device->newCommandQueue()
182 defer command_queue->release()
183
184 SDL.ShowWindow(window)
185 for quit := false; !quit; {
186 for e: SDL.Event; SDL.PollEvent(&e); {
187 #partial switch e.type {
188 case .QUIT:
189 quit = true
190 case .KEYDOWN:
191 if e.key.keysym.sym == .ESCAPE {
192 quit = true
193 }
194 }
195 }
196
197 w, h: i32
198 SDL.GetWindowSize(window, &w, &h)
199 aspect_ratio := f32(w)/max(f32(h), 1)
200
201 {
202 @static angle: f32
203 angle += 0.01
204
205 object_position := glm.vec3{0, 0, -5}
206 rt := glm.mat4Translate(object_position)
207 rr := glm.mat4Rotate({0, 1, 0}, -angle)
208 rt_inv := glm.mat4Translate(-object_position)
209 full_obj_rot := rt * rr * rt_inv
210
211 instance_data := instance_buffer->contentsAsSlice([]Instance_Data)[:NUM_INSTANCES]
212 for &instance, idx in instance_data {
213 scl :: 0.1
214
215 i := f32(idx) / NUM_INSTANCES
216 xoff := (i*2 - 1) + (1.0/NUM_INSTANCES)
217 yoff := math.sin((i + angle) * math.TAU)
218
219 scale := glm.mat4Scale({scl, scl, scl})
220 zrot := glm.mat4Rotate({0, 0, 1}, angle)
221 yrot := glm.mat4Rotate({0, 1, 0}, angle)
222 translate := glm.mat4Translate(object_position + {xoff, yoff, 0})
223
224 instance.transform = full_obj_rot * translate * yrot * zrot * scale
225 instance.color = {i, 1-i, math.sin(math.TAU * i), 1}
226 }
227 sz := NS.UInteger(len(instance_data)*size_of(instance_data[0]))
228 instance_buffer->didModifyRange(NS.Range_Make(0, sz))
229 }
230
231 {
232 camera_data := camera_buffer->contentsAsType(Camera_Data)
233 camera_data.perspective_transform = glm.mat4Perspective(glm.radians_f32(45), aspect_ratio, 0.03, 500)
234 camera_data.world_transform = 1
235
236 camera_buffer->didModifyRange(NS.Range_Make(0, size_of(Camera_Data)))
237
238 }
239
240 if depth_texture == nil ||
241 depth_texture->width() != NS.UInteger(w) ||
242 depth_texture->height() != NS.UInteger(h) {
243 desc := MTL.TextureDescriptor.texture2DDescriptorWithPixelFormat(
244 pixelFormat = .Depth16Unorm,
245 width = NS.UInteger(w),
246 height = NS.UInteger(h),
247 mipmapped = false,
248 )
249 defer desc->release()
250
251 desc->setUsage({.RenderTarget})
252 desc->setStorageMode(.Private)
253
254 if depth_texture != nil {
255 depth_texture->release()
256 }
257
258 depth_texture = device->newTextureWithDescriptor(desc)
259 }
260
261 drawable := swapchain->nextDrawable()
262 assert(drawable != nil)
263 defer drawable->release()
264
265 pass := MTL.RenderPassDescriptor.renderPassDescriptor()
266 defer pass->release()
267
268 color_attachment := pass->colorAttachments()->object(0)
269 assert(color_attachment != nil)
270 color_attachment->setClearColor(MTL.ClearColor{0.25, 0.5, 1.0, 1.0})
271 color_attachment->setLoadAction(.Clear)
272 color_attachment->setStoreAction(.Store)
273 color_attachment->setTexture(drawable->texture())
274
275 depth_attachment := pass->depthAttachment()
276 depth_attachment->setTexture(depth_texture)
277 depth_attachment->setClearDepth(1.0)
278 depth_attachment->setLoadAction(.Clear)
279 depth_attachment->setStoreAction(.Store)
280
281 command_buffer := command_queue->commandBuffer()
282 defer command_buffer->release()
283
284 render_encoder := command_buffer->renderCommandEncoderWithDescriptor(pass)
285 defer render_encoder->release()
286
287 render_encoder->setRenderPipelineState(pso)
288 render_encoder->setVertexBuffer(vertex_buffer, 0, 0)
289 render_encoder->setVertexBuffer(instance_buffer, 0, 1)
290 render_encoder->setVertexBuffer(camera_buffer, 0, 2)
291 render_encoder->drawIndexedPrimitivesWithInstanceCount(.Triangle, 6*6, .UInt16, index_buffer, 0, NUM_INSTANCES)
292
293 render_encoder->endEncoding()
294
295 command_buffer->presentDrawable(drawable)
296 command_buffer->commit()
297 }
298
299 return nil
300}
301
302main :: proc() {
303 err := metal_main()
304 if err != nil {
305 fmt.eprintln(err->localizedDescription()->odinString())
306 os.exit(1)
307 }
308}