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"
12
13
14Instance_Data :: struct #align(16) {
15 transform: matrix[4, 4]f32,
16 color: [4]f32,
17}
18
19NUM_INSTANCES :: 32
20
21build_shaders :: proc(device: ^MTL.Device) -> (library: ^MTL.Library, pso: ^MTL.RenderPipelineState, err: ^NS.Error) {
22 shader_src := `
23 #include <metal_stdlib>
24 using namespace metal;
25
26 struct v2f {
27 float4 position [[position]];
28 half3 color;
29 };
30
31 struct Vertex_Data {
32 packed_float3 position;
33 };
34
35 struct Instance_Data {
36 float4x4 transform;
37 float4 color;
38 };
39
40 v2f vertex vertex_main(device const Vertex_Data* vertex_data [[buffer(0)]],
41 device const Instance_Data* instance_data [[buffer(1)]],
42 uint vertex_id [[vertex_id]],
43 uint instance_id [[instance_id]]) {
44 v2f o;
45 float4 pos = float4(vertex_data[vertex_id].position, 1.0);
46 o.position = instance_data[instance_id].transform * pos;
47 o.color = half3(instance_data[instance_id].color.rgb);
48 return o;
49 }
50
51 half4 fragment fragment_main(v2f in [[stage_in]]) {
52 return half4(in.color, 1.0);
53 }
54 `
55 shader_src_str := NS.String.alloc()->initWithOdinString(shader_src)
56 defer shader_src_str->release()
57
58 library = device->newLibraryWithSource(shader_src_str, nil) or_return
59
60 vertex_function := library->newFunctionWithName(NS.AT("vertex_main"))
61 fragment_function := library->newFunctionWithName(NS.AT("fragment_main"))
62 defer vertex_function->release()
63 defer fragment_function->release()
64
65 desc := MTL.RenderPipelineDescriptor.alloc()->init()
66 defer desc->release()
67
68 desc->setVertexFunction(vertex_function)
69 desc->setFragmentFunction(fragment_function)
70 desc->colorAttachments()->object(0)->setPixelFormat(.BGRA8Unorm_sRGB)
71
72 pso = device->newRenderPipelineStateWithDescriptor(desc) or_return
73 return
74}
75
76build_buffers :: proc(device: ^MTL.Device) -> (vertex_buffer, index_buffer, instance_buffer: ^MTL.Buffer) {
77 s :: 0.5
78 positions := [][3]f32{
79 {-s, -s, +s},
80 {+s, -s, +s},
81 {+s, +s, +s},
82 {-s, +s, +s},
83 }
84 indices := []u16{
85 0, 1, 2,
86 2, 3, 0,
87 }
88
89 vertex_buffer = device->newBufferWithSlice(positions[:], {.StorageModeManaged})
90 index_buffer = device->newBufferWithSlice(indices[:], {.StorageModeManaged})
91 instance_buffer = device->newBuffer(NUM_INSTANCES*size_of(Instance_Data), {.StorageModeManaged})
92 return
93}
94
95metal_main :: proc() -> (err: ^NS.Error) {
96 SDL.SetHint(SDL.HINT_RENDER_DRIVER, "metal")
97 SDL.setenv("METAL_DEVICE_WRAPPER_TYPE", "1", 0)
98 SDL.Init({.VIDEO})
99 defer SDL.Quit()
100
101 window := SDL.CreateWindow("Metal in Odin - 04 instancing",
102 SDL.WINDOWPOS_CENTERED, SDL.WINDOWPOS_CENTERED,
103 854, 480,
104 {.ALLOW_HIGHDPI, .HIDDEN, .RESIZABLE},
105 )
106 defer SDL.DestroyWindow(window)
107
108 window_system_info: SDL.SysWMinfo
109 SDL.GetVersion(&window_system_info.version)
110 SDL.GetWindowWMInfo(window, &window_system_info)
111 assert(window_system_info.subsystem == .COCOA)
112
113 native_window := (^NS.Window)(window_system_info.info.cocoa.window)
114
115 device := MTL.CreateSystemDefaultDevice()
116 defer device->release()
117
118 fmt.println(device->name()->odinString())
119
120 swapchain := CA.MetalLayer.layer()
121 defer swapchain->release()
122
123 swapchain->setDevice(device)
124 swapchain->setPixelFormat(.BGRA8Unorm_sRGB)
125 swapchain->setFramebufferOnly(true)
126 swapchain->setFrame(native_window->frame())
127
128 native_window->contentView()->setLayer(swapchain)
129 native_window->setOpaque(true)
130 native_window->setBackgroundColor(nil)
131
132 library, pso := build_shaders(device) or_return
133 defer library->release()
134 defer pso->release()
135
136 vertex_buffer, index_buffer, instance_buffer := build_buffers(device)
137 defer vertex_buffer->release()
138 defer index_buffer->release()
139 defer instance_buffer->release()
140
141 command_queue := device->newCommandQueue()
142 defer command_queue->release()
143
144 SDL.ShowWindow(window)
145 for quit := false; !quit; {
146 for e: SDL.Event; SDL.PollEvent(&e); {
147 #partial switch e.type {
148 case .QUIT:
149 quit = true
150 case .KEYDOWN:
151 if e.key.keysym.sym == .ESCAPE {
152 quit = true
153 }
154 }
155 }
156
157 {
158 @static angle: f32
159 angle += 0.01
160 instance_data := instance_buffer->contentsAsSlice([]Instance_Data)[:NUM_INSTANCES]
161 for &instance, idx in instance_data {
162 scl :: 0.1
163
164 i := f32(idx) / NUM_INSTANCES
165 xoff := (i*2 - 1) + (1.0/NUM_INSTANCES)
166 yoff := math.sin((i + angle) * math.TAU)
167 instance.transform = matrix[4, 4]f32{
168 scl * math.sin(angle), scl * math.cos(angle), 0, xoff,
169 scl * math.cos(angle), -scl * math.sin(angle), 0, yoff,
170 0, 0, 0, 0,
171 0, 0, 0, 1,
172 }
173 instance.color = {i, 1-i, math.sin(math.TAU * i), 1}
174 }
175 sz := NS.UInteger(len(instance_data)*size_of(instance_data[0]))
176 instance_buffer->didModifyRange(NS.Range_Make(0, sz))
177 }
178
179 drawable := swapchain->nextDrawable()
180 assert(drawable != nil)
181 defer drawable->release()
182
183 pass := MTL.RenderPassDescriptor.renderPassDescriptor()
184 defer pass->release()
185
186 color_attachment := pass->colorAttachments()->object(0)
187 assert(color_attachment != nil)
188 color_attachment->setClearColor(MTL.ClearColor{0.25, 0.5, 1.0, 1.0})
189 color_attachment->setLoadAction(.Clear)
190 color_attachment->setStoreAction(.Store)
191 color_attachment->setTexture(drawable->texture())
192
193 command_buffer := command_queue->commandBuffer()
194 defer command_buffer->release()
195
196 render_encoder := command_buffer->renderCommandEncoderWithDescriptor(pass)
197 defer render_encoder->release()
198
199 render_encoder->setRenderPipelineState(pso)
200 render_encoder->setVertexBuffer(vertex_buffer, 0, 0)
201 render_encoder->setVertexBuffer(instance_buffer, 0, 1)
202 render_encoder->drawIndexedPrimitivesWithInstanceCount(.Triangle, 6, .UInt16, index_buffer, 0, NUM_INSTANCES)
203
204 render_encoder->endEncoding()
205
206 command_buffer->presentDrawable(drawable)
207 command_buffer->commit()
208 }
209
210 return nil
211}
212
213main :: proc() {
214 err := metal_main()
215 if err != nil {
216 fmt.eprintln(err->localizedDescription()->odinString())
217 os.exit(1)
218 }
219}