Created
September 3, 2024 18:53
-
-
Save mbger/da25aceed721ec91930a32c4447883f0 to your computer and use it in GitHub Desktop.
Odin: Open window on macOS and draw w/ metal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import "base:intrinsics" | |
| import "core:os" | |
| import "core:strings" | |
| import "core:math" | |
| import "core:fmt" | |
| import NS "core:sys/darwin/Foundation" | |
| import MTL "vendor:darwin/Metal" | |
| import CA "vendor:darwin/QuartzCore" | |
| msgSend :: intrinsics.objc_send | |
| main :: proc() { | |
| err := metal_main() | |
| fmt.println("Returned from metal_main") | |
| if err != nil { | |
| fmt.eprintln(err->localizedDescription()->odinString()) | |
| os.exit(1) | |
| } | |
| } | |
| quit := false | |
| windowWillClose :: proc (^NS.Notification) | |
| { | |
| quit = true | |
| } | |
| USE_CUSTOM_WINDOW :: true | |
| metal_main :: proc() -> (err: ^NS.Error) { | |
| app := NS.Application.sharedApplication() | |
| defer app->release() | |
| app->setActivationPolicy(.Regular) // without this window is not brought to foreground on launch | |
| menu := create_main_menu() | |
| defer menu->release() | |
| app->setMainMenu(menu) | |
| screen_rect := get_main_screen_rect() | |
| window_size := NS.Size{800, 600} | |
| window_origin: NS.Point = { | |
| NS.Float(math.floor(f64(screen_rect.size.width - window_size.width) / 2)), | |
| NS.Float(math.floor(f64(screen_rect.size.height - window_size.height) / 2)), | |
| } | |
| window: ^NS.Window | |
| when USE_CUSTOM_WINDOW | |
| { | |
| customWindowClass := NS.objc_allocateClassPair(intrinsics.objc_find_class("NSWindow"), strings.clone_to_cstring("CustomWindow", context.temp_allocator), 0) | |
| keyDown :: proc "c" (event: ^NS.Event) { /* ignore key down events */ } | |
| assert(customWindowClass != nil) | |
| NS.class_addMethod(customWindowClass, intrinsics.objc_find_selector("keyDown:"), auto_cast keyDown, "v@:@") | |
| NS.objc_registerClassPair(customWindowClass) | |
| keyIgnoringWindow := NS.class_createInstance(customWindowClass, size_of(NS.Window)) | |
| window = cast(^NS.Window)keyIgnoringWindow | |
| } | |
| else | |
| { | |
| window = NS.Window.alloc() | |
| } | |
| defer window->release() | |
| window->initWithContentRect({window_origin, window_size}, { .Resizable, .Closable, .Titled, .Miniaturizable }, .Buffered, false) | |
| window->setTitle(NS.MakeConstantString("Use left/right arrow keys to rotate the triangle")) | |
| window->makeKeyAndOrderFront(nil) | |
| msgSend(nil, app, "finishLaunching") | |
| device := MTL.CreateSystemDefaultDevice() | |
| defer device->release() | |
| fmt.println("Metal device:", device->name()->odinString()) | |
| swapchain := CA.MetalLayer.layer() | |
| defer swapchain->release() | |
| swapchain->setDevice(device) | |
| swapchain->setPixelFormat(.BGRA8Unorm_sRGB) | |
| swapchain->setFramebufferOnly(true) | |
| swapchain->setFrame(window->frame()) | |
| window->contentView()->setLayer(swapchain) | |
| window->setOpaque(true) | |
| window->setBackgroundColor(nil) | |
| window->setDelegate(NS.window_delegate_register_and_alloc({ | |
| windowWillClose = windowWillClose | |
| }, "MainWindowDelegate", context)) | |
| library, pso := build_shaders(device) or_return | |
| defer library->release() | |
| defer pso->release() | |
| vertex_positions_buffer, vertex_colors_buffer, arg_buffer := build_buffers(device, library) | |
| defer arg_buffer->release() | |
| defer vertex_positions_buffer->release() | |
| defer vertex_colors_buffer->release() | |
| frame_data_buffer := device->newBuffer(size_of(Frame_Data), {.StorageModeManaged}) | |
| defer frame_data_buffer->release() | |
| command_queue := device->newCommandQueue() | |
| defer command_queue->release() | |
| angle: f32 = 0 | |
| // Main Loop | |
| for ; !quit; | |
| { | |
| // Poll all events from the queue | |
| { | |
| NS.scoped_autoreleasepool() | |
| for | |
| { | |
| e := app->nextEventMatchingMask(NS.EventMaskAny, nil, NS.DefaultRunLoopMode, true) | |
| if e == nil { break } | |
| #partial switch e->type() | |
| { | |
| case .KeyDown, .KeyUp: | |
| code := NS.kVK(e->keyCode()) | |
| #partial switch code { | |
| case .Escape: | |
| quit = true | |
| case .LeftArrow: | |
| angle -= 0.02 | |
| case .RightArrow: | |
| angle += 0.02 | |
| case: | |
| fmt.println(code) | |
| } | |
| case: | |
| fmt.println(e->locationInWindow(), e->modifierFlags()) | |
| } | |
| app->sendEvent(e) | |
| } | |
| } | |
| // Render frame | |
| frame_data := (^Frame_Data)(frame_data_buffer->contentsPointer()) | |
| frame_data.angle = angle | |
| frame_data_buffer->didModifyRange(NS.Range_Make(0, size_of(Frame_Data))) | |
| drawable := swapchain->nextDrawable() | |
| assert(drawable != nil) | |
| defer drawable->release() | |
| pass := MTL.RenderPassDescriptor.renderPassDescriptor() | |
| defer pass->release() | |
| color_attachment := pass->colorAttachments()->object(0) | |
| color_attachment->setClearColor(MTL.ClearColor{0.25, 0.5, 1.0, 1.0}) | |
| color_attachment->setLoadAction(.Clear) | |
| color_attachment->setStoreAction(.Store) | |
| color_attachment->setTexture(drawable->texture()) | |
| command_buffer := command_queue->commandBuffer() | |
| defer command_buffer->release() | |
| render_encoder := command_buffer->renderCommandEncoderWithDescriptor(pass) | |
| defer render_encoder->release() | |
| render_encoder->setRenderPipelineState(pso) | |
| render_encoder->setVertexBuffer(arg_buffer, 0, 0) | |
| render_encoder->setVertexBuffer(frame_data_buffer, 0, 1) | |
| render_encoder->useResource(vertex_positions_buffer, {.Read}) | |
| render_encoder->useResource(vertex_colors_buffer, {.Read}) | |
| render_encoder->drawPrimitives(.Triangle, 0, 3) | |
| render_encoder->endEncoding() | |
| command_buffer->presentDrawable(drawable) | |
| command_buffer->commit() | |
| app->updateWindows() | |
| } | |
| // Exit `metal_main` | |
| fmt.println("metal_main finished looping") | |
| return nil | |
| } | |
| Frame_Data :: struct { | |
| angle: f32, | |
| } | |
| build_shaders :: proc(device: ^MTL.Device) -> (library: ^MTL.Library, pso: ^MTL.RenderPipelineState, err: ^NS.Error) { | |
| shader_src := ` | |
| #include <metal_stdlib> | |
| using namespace metal; | |
| struct v2f { | |
| float4 position [[position]]; | |
| half3 color; | |
| }; | |
| struct Vertex_Data { | |
| device packed_float3* positions [[id(0)]]; | |
| device packed_float3* colors [[id(1)]]; | |
| }; | |
| struct Frame_Data { | |
| float angle; | |
| }; | |
| v2f vertex vertex_main(device const Vertex_Data* vertex_data [[buffer(0)]], | |
| device const Frame_Data* frame_data [[buffer(1)]], | |
| uint vertex_id [[vertex_id]]) { | |
| float a = frame_data->angle; | |
| float3x3 rotation_matrix = float3x3(sin(a), cos(a), 0.0, cos(a), -sin(a), 0.0, 0.0, 0.0, 1.0); | |
| float3 position = float3(vertex_data->positions[vertex_id]); | |
| v2f o; | |
| o.position = float4(rotation_matrix * position, 1.0); | |
| o.color = half3(vertex_data->colors[vertex_id]); | |
| return o; | |
| } | |
| half4 fragment fragment_main(v2f in [[stage_in]]) { | |
| return half4(in.color, 1.0); | |
| } | |
| ` | |
| shader_src_str := NS.String.alloc()->initWithOdinString(shader_src) | |
| defer shader_src_str->release() | |
| library = device->newLibraryWithSource(shader_src_str, nil) or_return | |
| vertex_function := library->newFunctionWithName(NS.AT("vertex_main")) | |
| fragment_function := library->newFunctionWithName(NS.AT("fragment_main")) | |
| defer vertex_function->release() | |
| defer fragment_function->release() | |
| desc := MTL.RenderPipelineDescriptor.alloc()->init() | |
| defer desc->release() | |
| desc->setVertexFunction(vertex_function) | |
| desc->setFragmentFunction(fragment_function) | |
| desc->colorAttachments()->object(0)->setPixelFormat(.BGRA8Unorm_sRGB) | |
| pso = device->newRenderPipelineStateWithDescriptor(desc) or_return | |
| return | |
| } | |
| build_buffers :: proc(device: ^MTL.Device, library: ^MTL.Library) -> (vertex_positions_buffer, vertex_colors_buffer, arg_buffer: ^MTL.Buffer) { | |
| NUM_VERTICES :: 3 | |
| positions := [NUM_VERTICES][3]f32{ | |
| {-0.8, 0.8, 0.0}, | |
| { 0.0, -0.8, 0.0}, | |
| {+0.8, 0.8, 0.0}, | |
| } | |
| colors := [NUM_VERTICES][3]f32{ | |
| {1.0, 0.3, 0.2}, | |
| {0.8, 1.0, 0.0}, | |
| {0.8, 0.0, 1.0}, | |
| } | |
| vertex_positions_buffer = device->newBufferWithSlice(positions[:], {.StorageModeManaged}) | |
| vertex_colors_buffer = device->newBufferWithSlice(colors[:], {.StorageModeManaged}) | |
| vertex_function := library->newFunctionWithName(NS.AT("vertex_main")) | |
| defer vertex_function->release() | |
| arg_encoder := vertex_function->newArgumentEncoder(0) | |
| defer arg_encoder->release() | |
| arg_buffer = device->newBuffer(arg_encoder->encodedLength(), {.StorageModeManaged}) | |
| arg_encoder->setArgumentBufferWithOffset(arg_buffer, 0) | |
| arg_encoder->setBuffer(vertex_positions_buffer, 0, 0) | |
| arg_encoder->setBuffer(vertex_colors_buffer, 0, 1) | |
| arg_buffer->didModifyRange(NS.Range_Make(0, arg_buffer->length())) | |
| return | |
| } | |
| create_main_menu :: proc() -> ^NS.Menu { | |
| main_menu := NS.Menu.alloc()->init() | |
| main_menu->addItem(NS.MenuItem.alloc()->init()) | |
| main_menu_app_item := main_menu->addItemWithTitle(NS.AT("Metal"), nil, NS.AT("")) | |
| app_menu := NS.Menu.alloc()->init() | |
| app_menu->addItemWithTitle(NS.AT("Quit"), intrinsics.objc_find_selector("terminate:"), NS.AT("q")) | |
| main_menu_app_item->setSubmenu(app_menu) | |
| main_menu_edit_item := main_menu->addItemWithTitle(NS.AT("Edit"), nil, NS.AT("")) | |
| edit_menu := NS.Menu.alloc()->init() | |
| main_menu_edit_item->setSubmenu(edit_menu) | |
| main_menu_view_item := main_menu->addItemWithTitle(NS.AT("View"), nil, NS.AT("")) | |
| view_menu := NS.Menu.alloc()->init() | |
| view_menu->addItemWithTitle(NS.AT("Enter Full Screen"), intrinsics.objc_find_selector("toggleFullScreen:"), NS.AT("f")) | |
| main_menu_view_item->setSubmenu(view_menu) | |
| return main_menu | |
| } | |
| get_main_screen_rect :: proc() -> NS.Rect { | |
| the_screen: NS.Screen | |
| main_screen := the_screen.mainScreen() | |
| return main_screen->visibleFrame() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment