metal/learn_metal/04-instancing macOS

Code

04-instancing.odin ¶
219 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"
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}

Declarations Used 35