metal/learn_metal/02-argbuffers-no-sdl

Code

02-argbuffers-no-sdl.odin ¶
269 linesSource

1package main
2
3import "base:intrinsics"
4
5import "core:os"
6import "core:math"
7import "core:fmt"
8import NS "vendor:darwin/AppKit"
9import ObjC "core:sys/darwin/ObjectiveC"
10
11import MTL "vendor:darwin/Metal"
12import CA "vendor:darwin/QuartzCore"
13
14main :: proc() {
15	err := metal_main()
16	if err != nil {
17		fmt.eprintln(err->localizedDescription()->odinString())
18		os.exit(1)
19	}
20}
21
22metal_main :: proc() -> (err: ^NS.Error) {
23	@static quit := false
24
25	app := NS.Application.sharedApplication()
26	defer app->release()
27	app->setActivationPolicy(.Regular) // without this window is not brought to foreground on launch
28	app->finishLaunching()
29
30	create_main_menu(app)
31
32	screen_rect := NS.Screen.mainScreen()->visibleFrame()
33	window_size := NS.Size{800, 600}
34	window_origin: NS.Point =  {
35		NS.Float(math.floor(f64(screen_rect.size.width - window_size.width) / 2)),
36		NS.Float(math.floor(f64(screen_rect.size.height - window_size.height) / 2)),
37	}
38
39	CustomWindowClass := ObjC.objc_allocateClassPair(intrinsics.objc_find_class("NSWindow"), "CustomWindow", 0)
40	assert(CustomWindowClass != nil)
41	keyDown :: proc "c" (self: NS.id, sel: NS.SEL, event: ^NS.Event) { /* ignore key down events */ }
42	ObjC.class_addMethod(CustomWindowClass, intrinsics.objc_find_selector("keyDown:"), ObjC.IMP(keyDown), "v@:@")
43	ObjC.objc_registerClassPair(CustomWindowClass)
44	window := cast(^NS.Window)(ObjC.class_createInstance(CustomWindowClass, size_of(NS.Window)))
45	defer window->release()
46	window->initWithContentRect({window_origin, window_size}, { .Resizable, .Closable, .Titled, .Miniaturizable }, .Buffered, NS.NO)
47	window->setDelegate(NS.window_delegate_register_and_alloc({
48		windowWillClose = proc(^NS.Notification) {
49			quit = true
50		},
51	}, "CustomWindowDelegate", context))
52	window->setTitle(NS.MakeConstantString("Use left/right arrow keys to rotate the triangle"))
53	window->setIsVisible(true)
54
55	app->activateIgnoringOtherApps(true)
56
57
58	device := MTL.CreateSystemDefaultDevice()
59	defer device->release()
60
61	fmt.println("Metal device:", device->name()->odinString())
62
63	swapchain := CA.MetalLayer.layer()
64	defer swapchain->release()
65
66	swapchain->setDevice(device)
67	swapchain->setPixelFormat(.BGRA8Unorm_sRGB)
68	swapchain->setFramebufferOnly(true)
69	swapchain->setFrame(window->frame())
70
71	window->contentView()->setLayer(swapchain)
72	window->setOpaque(true)
73	window->setBackgroundColor(nil)
74
75	library, pso := build_shaders(device) or_return
76	defer library->release()
77	defer pso->release()
78
79	vertex_positions_buffer, vertex_colors_buffer, arg_buffer := build_buffers(device, library)
80	defer arg_buffer->release()
81
82	frame_data_buffer := device->newBufferWithLength(size_of(Frame_Data), {.StorageModeManaged})
83	defer frame_data_buffer->release()
84
85	command_queue := device->newCommandQueue()
86	defer command_queue->release()
87
88	@static angle: f32
89
90	for !quit {
91		{
92			NS.scoped_autoreleasepool()
93			// Use `NS.Date.distantFuture()` for `expiration` param, so that we idle without wasting any CPU while waiting for events.
94			event := app->nextEventMatchingMask(NS.EventMaskAny, NS.Date.distantFuture(), NS.DefaultRunLoopMode, true)
95			for event != nil {
96				event_type := event->type()
97				#partial switch event_type {
98				case .KeyDown, .KeyUp:
99					code := NS.kVK(event->keyCode())
100					#partial switch code {
101					case .Escape:
102						quit = true
103					case .LeftArrow:
104						angle -= 0.02
105					case .RightArrow:
106						angle += 0.02
107					}
108					fmt.println(event_type, code, event->modifierFlags())
109				case:
110					fmt.println(event_type, event->locationInWindow(), event->modifierFlags())
111				}
112				app->sendEvent(event)
113
114				// Once we wake up from idle, process all events in the queue before we can idle again.
115				event = app->nextEventMatchingMask(NS.EventMaskAny, nil, NS.DefaultRunLoopMode, true)
116			}
117			app->updateWindows()
118		}
119
120		frame_data := (^Frame_Data)(frame_data_buffer->contents())
121		frame_data.angle = angle
122		frame_data_buffer->didModifyRange(NS.Range_Make(0, size_of(Frame_Data)))
123
124		drawable := swapchain->nextDrawable()
125		assert(drawable != nil)
126		defer drawable->release()
127
128		pass := MTL.RenderPassDescriptor.renderPassDescriptor()
129		defer pass->release()
130
131		color_attachment := pass->colorAttachments()->object(0)
132		color_attachment->setClearColor(MTL.ClearColor{0.25, 0.5, 1.0, 1.0})
133		color_attachment->setLoadAction(.Clear)
134		color_attachment->setStoreAction(.Store)
135		color_attachment->setTexture(drawable->texture())
136
137		command_buffer := command_queue->commandBuffer()
138		defer command_buffer->release()
139
140		render_encoder := command_buffer->renderCommandEncoderWithDescriptor(pass)
141		defer render_encoder->release()
142
143		render_encoder->setRenderPipelineState(pso)
144		render_encoder->setVertexBuffer(arg_buffer,        0, 0)
145		render_encoder->setVertexBuffer(frame_data_buffer, 0, 1)
146		render_encoder->useResource(vertex_positions_buffer, {.Read})
147		render_encoder->useResource(vertex_colors_buffer, {.Read})
148		render_encoder->drawPrimitives(.Triangle, 0, 3)
149
150		render_encoder->endEncoding()
151
152		command_buffer->presentDrawable(drawable)
153		command_buffer->commit()
154	}
155
156	return nil
157}
158
159Frame_Data :: struct {
160	angle: f32,
161}
162
163build_shaders :: proc(device: ^MTL.Device) -> (library: ^MTL.Library, pso: ^MTL.RenderPipelineState, err: ^NS.Error) {
164	shader_src := `
165	#include <metal_stdlib>
166	using namespace metal;
167
168	struct v2f {
169		float4 position [[position]];
170		half3 color;
171	};
172
173	struct Vertex_Data {
174		device packed_float3* positions [[id(0)]];
175		device packed_float3* colors    [[id(1)]];
176	};
177
178	struct Frame_Data {
179		float angle;
180	};
181
182	v2f vertex vertex_main(device const Vertex_Data* vertex_data [[buffer(0)]],
183						   device const Frame_Data*  frame_data  [[buffer(1)]],
184						   uint vertex_id                        [[vertex_id]]) {
185		float a = frame_data->angle;
186		float3x3 rotation_matrix = float3x3(sin(a), cos(a), 0.0, cos(a), -sin(a), 0.0, 0.0, 0.0, 1.0);
187		float3 position = float3(vertex_data->positions[vertex_id]);
188		v2f o;
189		o.position = float4(rotation_matrix * position, 1.0);
190		o.color = half3(vertex_data->colors[vertex_id]);
191		return o;
192	}
193
194	half4 fragment fragment_main(v2f in [[stage_in]]) {
195		return half4(in.color, 1.0);
196	}
197	`
198	shader_src_str := NS.String.alloc()->initWithOdinString(shader_src)
199	defer shader_src_str->release()
200
201	library = device->newLibraryWithSource(shader_src_str, nil) or_return
202
203	vertex_function   := library->newFunctionWithName(NS.AT("vertex_main"))
204	fragment_function := library->newFunctionWithName(NS.AT("fragment_main"))
205	defer vertex_function->release()
206	defer fragment_function->release()
207
208	desc := MTL.RenderPipelineDescriptor.alloc()->init()
209	defer desc->release()
210
211	desc->setVertexFunction(vertex_function)
212	desc->setFragmentFunction(fragment_function)
213	desc->colorAttachments()->object(0)->setPixelFormat(.BGRA8Unorm_sRGB)
214
215	pso = device->newRenderPipelineStateWithDescriptor(desc) or_return
216	return
217}
218
219build_buffers :: proc(device: ^MTL.Device, library: ^MTL.Library) -> (vertex_positions_buffer, vertex_colors_buffer, arg_buffer: ^MTL.Buffer) {
220	NUM_VERTICES :: 3
221	positions := [NUM_VERTICES][3]f32{
222		{-0.8,  0.8, 0.0},
223		{ 0.0, -0.8, 0.0},
224		{+0.8,  0.8, 0.0},
225	}
226	colors := [NUM_VERTICES][3]f32{
227		{1.0, 0.3, 0.2},
228		{0.8, 1.0, 0.0},
229		{0.8, 0.0, 1.0},
230	}
231
232	vertex_positions_buffer = device->newBufferWithSlice(positions[:], {.StorageModeManaged})
233	vertex_colors_buffer    = device->newBufferWithSlice(colors[:],    {.StorageModeManaged})
234
235	vertex_function := library->newFunctionWithName(NS.AT("vertex_main"))
236	defer vertex_function->release()
237
238	arg_encoder := vertex_function->newArgumentEncoderWithBufferIndex(0)
239	defer arg_encoder->release()
240
241	arg_buffer = device->newBufferWithLength(arg_encoder->encodedLength(), {.StorageModeManaged})
242	arg_encoder->setArgumentBuffer(arg_buffer, 0)
243	arg_encoder->setBuffer(vertex_positions_buffer, 0, 0)
244	arg_encoder->setBuffer(vertex_colors_buffer,    0, 1)
245	arg_buffer->didModifyRange(NS.Range_Make(0, arg_buffer->length()))
246
247	return
248}
249
250create_main_menu :: proc(app: ^NS.Application) {
251	main_menu := NS.Menu.alloc()->init()
252	main_menu->addItem(NS.MenuItem.alloc()->init())
253
254	main_menu_app_item := main_menu->addItemWithTitle(NS.AT("Metal"), nil, NS.AT(""))
255	app_menu := NS.Menu.alloc()->init()
256	app_menu->addItemWithTitle(NS.AT("Quit"), intrinsics.objc_find_selector("terminate:"), NS.AT("q"))
257	main_menu_app_item->setSubmenu(app_menu)
258
259	main_menu_edit_item := main_menu->addItemWithTitle(NS.AT("Edit"), nil, NS.AT(""))
260	edit_menu := NS.Menu.alloc()->init()
261	main_menu_edit_item->setSubmenu(edit_menu)
262
263	main_menu_view_item := main_menu->addItemWithTitle(NS.AT("View"), nil, NS.AT(""))
264	view_menu := NS.Menu.alloc()->init()
265	view_menu->addItemWithTitle(NS.AT("Enter Full Screen"), intrinsics.objc_find_selector("toggleFullScreen:"), NS.AT("f"))
266	main_menu_view_item->setSubmenu(view_menu)
267
268	app->setMainMenu(main_menu)
269}

Declarations Used 4