metal/learn_metal/06-lighting macOS

Code

06-lighting.odin ¶
382 linesSource

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		// Positions      Normals
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, // front
154		 4,  5,  6,  6,  7,  4, // right
155		 8,  9, 10, 10, 11,  8, // back
156		12, 13, 14, 14, 15, 12, // left
157		16, 17, 18, 18, 19, 16, // top
158		20, 21, 22, 22, 23, 20, // bottom
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	// Build Depth Stencil State
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}

Declarations Used 49