Skip to content

Commit

Permalink
Cocoa Metal example (no SDL).
Browse files Browse the repository at this point in the history
  • Loading branch information
yay committed Aug 27, 2024
1 parent 760b259 commit 7e69b2b
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 2 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Check everything
run: |
FLAGS="-vet -strict-style -vet-tabs -disallow-do -warnings-as-errors"
odin check by_example/dir_info $FLAGS
odin check by_example/dynamic_arrays $FLAGS
odin check by_example/hellope $FLAGS
Expand All @@ -47,6 +47,7 @@ jobs:
odin check learn_metal/00-window $FLAGS
odin check learn_metal/01-primitive $FLAGS
odin check learn_metal/02-argbuffers $FLAGS
odin check learn_metal/02-argbuffers-no-sdl $FLAGS
odin check learn_metal/03-animation $FLAGS
odin check learn_metal/04-instancing $FLAGS
odin check learn_metal/05-perspective $FLAGS
Expand All @@ -57,7 +58,7 @@ jobs:
odin check math/noise/draw_texture $FLAGS
odin check raylib/game_of_life $FLAGS
odin check raylib/game_of_life $FLAGS
odin check raylib/microui $FLAGS
odin check raylib/ports/shaders/shaders_mesh_instancing.odin -file $FLAGS
odin check raylib/ports/shapes/shapes_basic_shapes.odin -file $FLAGS
Expand Down
254 changes: 254 additions & 0 deletions learn_metal/02-argbuffers-no-sdl/02-argbuffers-no-sdl.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package main

import "base:intrinsics"

import "core:os"
import "core:math"
import "core:fmt"
import NS "core:sys/darwin/Foundation"

import MTL "vendor:darwin/Metal"
import CA "vendor:darwin/QuartzCore"

main :: proc() {
err := metal_main()
if err != nil {
fmt.eprintln(err->localizedDescription()->odinString())
os.exit(1)
}
}

metal_main :: proc() -> (err: ^NS.Error) {
NS.scoped_autoreleasepool()

app := NS.Application.sharedApplication()
app->setActivationPolicy(.Regular) // without this window is not brought to foreground on launch
defer app->release()

create_main_menu(app)

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.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)

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)

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()

frame_data_buffer := device->newBuffer(size_of(Frame_Data), {.StorageModeManaged})
defer frame_data_buffer->release()

command_queue := device->newCommandQueue()
defer command_queue->release()

for {
event := app->nextEventMatchingMask(NS.EventMaskAny, NS.Date.distantFuture(), NS.DefaultRunLoopMode, true)

@static angle: f32

event_type := event->type()
#partial switch event_type {
case .KeyDown, .KeyUp:
code := NS.kVK(event->keyCode())
#partial switch code {
case .LeftArrow:
angle -= 0.02
case .RightArrow:
angle += 0.02
}
fmt.println(event_type, code)
case:
fmt.println(event_type, event->locationInWindow(), event->modifierFlags())
}

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->sendEvent(event)
app->updateWindows()
}
}

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(app: ^NS.Application) {
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)

app->setMainMenu(main_menu)
}

get_main_screen_rect :: proc() -> NS.Rect {
the_screen: NS.Screen
main_screen := the_screen.mainScreen()

return main_screen->visibleFrame()
}

0 comments on commit 7e69b2b

Please sign in to comment.