metal/learn_metal/05-perspective macOS

Code

05-perspective.odin ¶
308 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
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, // front
106		2, 3, 0,
107
108		1, 7, 6, // right
109		6, 2, 1,
110
111		7, 4, 5, // back
112		5, 6, 7,
113
114		4, 0, 3, // left
115		3, 5, 4,
116
117		3, 2, 6, // top
118		6, 5, 3,
119
120		4, 7, 1, // bottom
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}

Declarations Used 45