Skip to content

Instantly share code, notes, and snippets.

@gingerBill
Last active October 5, 2024 20:52
Show Gist options
  • Save gingerBill/e1270f60a1739c266934599c2bee46f5 to your computer and use it in GitHub Desktop.
Save gingerBill/e1270f60a1739c266934599c2bee46f5 to your computer and use it in GitHub Desktop.
Metal in Odin Natively
package objc_test
import NS "vendor:darwin/Foundation"
import MTL "vendor:darwin/Metal"
import CA "vendor:darwin/QuartzCore"
import SDL "vendor:sdl2"
import "core:fmt"
import "core:os"
metal_main :: proc() -> (err: ^NS.Error) {
SDL.SetHint(SDL.HINT_RENDER_DRIVER, "metal")
SDL.setenv("METAL_DEVICE_WRAPPER_TYPE", "1", 0)
SDL.Init({.VIDEO})
defer SDL.Quit()
window := SDL.CreateWindow("Metal in Odin",
SDL.WINDOWPOS_CENTERED, SDL.WINDOWPOS_CENTERED,
854, 480,
{.ALLOW_HIGHDPI, .HIDDEN, .RESIZABLE},
)
defer SDL.DestroyWindow(window)
window_system_info: SDL.SysWMinfo
SDL.GetVersion(&window_system_info.version)
SDL.GetWindowWMInfo(window, &window_system_info)
assert(window_system_info.subsystem == .COCOA)
native_window := (^NS.Window)(window_system_info.info.cocoa.window)
device := MTL.CreateSystemDefaultDevice()
fmt.println(device->name()->odinString())
swapchain := CA.MetalLayer.layer()
swapchain->setDevice(device)
swapchain->setPixelFormat(.BGRA8Unorm_sRGB)
swapchain->setFramebufferOnly(true)
swapchain->setFrame(native_window->frame())
native_window->contentView()->setLayer(swapchain)
native_window->setOpaque(true)
native_window->setBackgroundColor(nil)
command_queue := device->newCommandQueue()
compile_options := NS.new(MTL.CompileOptions)
defer compile_options->release()
program_source :: `
using namespace metal;
struct ColoredVertex {
float4 position [[position]];
float4 color;
};
vertex ColoredVertex vertex_main(constant float4 *position [[buffer(0)]],
constant float4 *color [[buffer(1)]],
uint vid [[vertex_id]]) {
ColoredVertex vert;
vert.position = position[vid];
vert.color = color[vid];
return vert;
}
fragment float4 fragment_main(ColoredVertex vert [[stage_in]]) {
return vert.color;
}
`
program_library := device->newLibraryWithSource(NS.AT(program_source), compile_options) or_return
vertex_program := program_library->newFunctionWithName(NS.AT("vertex_main"))
fragment_program := program_library->newFunctionWithName(NS.AT("fragment_main"))
assert(vertex_program != nil)
assert(fragment_program != nil)
pipeline_state_descriptor := NS.new(MTL.RenderPipelineDescriptor)
pipeline_state_descriptor->colorAttachments()->object(0)->setPixelFormat(.BGRA8Unorm_sRGB)
pipeline_state_descriptor->setVertexFunction(vertex_program)
pipeline_state_descriptor->setFragmentFunction(fragment_program)
pipeline_state := device->newRenderPipelineState(pipeline_state_descriptor) or_return
positions := [?][4]f32{
{ 0.0, 0.5, 0, 1},
{-0.5, -0.5, 0, 1},
{ 0.5, -0.5, 0, 1},
}
colors := [?][4]f32{
{1, 0, 0, 1},
{0, 1, 0, 1},
{0, 0, 1, 1},
}
position_buffer := device->newBufferWithSlice(positions[:], {})
color_buffer := device->newBufferWithSlice(colors[:], {})
SDL.ShowWindow(window)
for quit := false; !quit; {
for e: SDL.Event; SDL.PollEvent(&e) != 0; {
#partial switch e.type {
case .QUIT:
quit = true
case .KEYDOWN:
if e.key.keysym.sym == .ESCAPE {
quit = true
}
}
}
NS.scoped_autoreleasepool()
drawable := swapchain->nextDrawable()
assert(drawable != nil)
pass := MTL.RenderPassDescriptor.renderPassDescriptor()
color_attachment := pass->colorAttachments()->object(0)
assert(color_attachment != nil)
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()
render_encoder := command_buffer->renderCommandEncoderWithDescriptor(pass)
render_encoder->setRenderPipelineState(pipeline_state)
render_encoder->setVertexBuffer(position_buffer, 0, 0)
render_encoder->setVertexBuffer(color_buffer, 0, 1)
render_encoder->drawPrimitivesWithInstanceCount(.Triangle, 0, 3, 1)
render_encoder->endEncoding()
command_buffer->presentDrawable(drawable)
command_buffer->commit()
}
return nil
}
main :: proc() {
err := metal_main()
if err != nil {
fmt.eprintln(err->localizedDescription()->odinString())
os.exit(1)
}
}
@RenatoCesarF
Copy link

The foundation import was changed:

import NS "core:sys/darwin/Foundation"

@pixls
Copy link

pixls commented Sep 7, 2024

@gingerBill , I'm new to Odin (started playing with it about half an hour ago) and considering it for a Metal project, but the experience has been frustrating. After installing Odin with brew (odin version dev-2024-08:1a16585b1) and fixing the Foundation import issue thanks to @RenatoCesarF, I hit another comp issue:

/metal_odin_example/main.odin(105:42) Error: Cannot convert '0' to 'b32' from 'untyped integer', got 0
	for e: SDL.Event; SDL.PollEvent(&e) != 0; {
	

This kind of basic example should always work. You could run it automatically on GitHub so with each new push to Odin the examples get run ensuring nothing ever stops working.

@DonnieTD
Copy link

DonnieTD commented Oct 5, 2024

@pixls

just change the zero to false and it works

https://odin-lang.org/docs/overview/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment