metal/learn_metal/02-argbuffers-no-sdl macOS

Code

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

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

Declarations Used 42