metal/learn_metal/07-texturing macOS

Code

07-texturing.odin ¶
430 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	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		//                                         Texture
134		//   Positions           Normals         Coordinates
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, // front
167		 4,  5,  6,  6,  7,  4, // right
168		 8,  9, 10, 10, 11,  8, // back
169		12, 13, 14, 14, 15, 12, // left
170		16, 17, 18, 18, 19, 16, // top
171		20, 21, 22, 22, 23, 20, // bottom
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	// Build Depth Stencil State
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}

Declarations Used 51