@@ -0,0 +1,137 @@ // +build darwin // Package ca provides access to Apple's Core Animation API (https://developer.apple.com/documentation/quartzcore). // // This package is in very early stages of development. // It's a minimal implementation with scope limited to // supporting the movingtriangle example. package ca import ( "errors" "unsafe" "dmitri.shuralyov.com/gpu/mtl" ) /* #cgo LDFLAGS: -framework QuartzCore -framework Foundation #include "ca.h" */ import "C" // Layer is an object that manages image-based content and // allows you to perform animations on that content. // // Reference: https://developer.apple.com/documentation/quartzcore/calayer. type Layer interface { // Layer returns the underlying CALayer * pointer. Layer() unsafe.Pointer } // MetalLayer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables. // // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer. type MetalLayer struct { metalLayer unsafe.Pointer } // MakeMetalLayer creates a new Core Animation Metal layer. // // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer. func MakeMetalLayer() MetalLayer { return MetalLayer{C.MakeMetalLayer()} } // Layer implements the Layer interface. func (ml MetalLayer) Layer() unsafe.Pointer { return ml.metalLayer } // PixelFormat returns the pixel format of textures for rendering layer content. // // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat. func (ml MetalLayer) PixelFormat() mtl.PixelFormat { return mtl.PixelFormat(C.MetalLayer_PixelFormat(ml.metalLayer)) } // SetDevice sets the Metal device responsible for the layer's drawable resources. // // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device. func (ml MetalLayer) SetDevice(device mtl.Device) { C.MetalLayer_SetDevice(ml.metalLayer, device.Device()) } // SetPixelFormat controls the pixel format of textures for rendering layer content. // // The pixel format for a Metal layer must be PixelFormatBGRA8UNorm, PixelFormatBGRA8UNormSRGB, // PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB. // SetPixelFormat panics for other values. // // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat. func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) { e := C.MetalLayer_SetPixelFormat(ml.metalLayer, C.uint16_t(pf)) if e != nil { panic(errors.New(C.GoString(e))) } } // SetMaximumDrawableCount controls the number of Metal drawables in the resource pool // managed by Core Animation. // // It can set to 2 or 3 only. SetMaximumDrawableCount panics for other values. // // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount. func (ml MetalLayer) SetMaximumDrawableCount(count int) { e := C.MetalLayer_SetMaximumDrawableCount(ml.metalLayer, C.uint_t(count)) if e != nil { panic(errors.New(C.GoString(e))) } } // SetDisplaySyncEnabled controls whether the Metal layer and its drawables // are synchronized with the display's refresh rate. // // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled. func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) { switch enabled { case true: C.MetalLayer_SetDisplaySyncEnabled(ml.metalLayer, 1) case false: C.MetalLayer_SetDisplaySyncEnabled(ml.metalLayer, 0) } } // SetDrawableSize sets the size, in pixels, of textures for rendering layer content. // // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize. func (ml MetalLayer) SetDrawableSize(width, height int) { C.MetalLayer_SetDrawableSize(ml.metalLayer, C.double(width), C.double(height)) } // NextDrawable returns a Metal drawable. // // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable. func (ml MetalLayer) NextDrawable() (MetalDrawable, error) { md := C.MetalLayer_NextDrawable(ml.metalLayer) if md == nil { return MetalDrawable{}, errors.New("nextDrawable returned nil") } return MetalDrawable{md}, nil } // MetalDrawable is a displayable resource that can be rendered or written to by Metal. // // Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable. type MetalDrawable struct { metalDrawable unsafe.Pointer } // Drawable implements the mtl.Drawable interface. func (md MetalDrawable) Drawable() unsafe.Pointer { return md.metalDrawable } // Texture returns a Metal texture object representing the drawable object's content. // // Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture. func (md MetalDrawable) Texture() mtl.Texture { return mtl.NewTexture(C.MetalDrawable_Texture(md.metalDrawable)) }
@@ -0,0 +1,17 @@ // +build darwin typedef signed char BOOL; typedef unsigned long uint_t; typedef unsigned short uint16_t; void * MakeMetalLayer(); uint16_t MetalLayer_PixelFormat(void * metalLayer); void MetalLayer_SetDevice(void * metalLayer, void * device); const char * MetalLayer_SetPixelFormat(void * metalLayer, uint16_t pixelFormat); const char * MetalLayer_SetMaximumDrawableCount(void * metalLayer, uint_t maximumDrawableCount); void MetalLayer_SetDisplaySyncEnabled(void * metalLayer, BOOL displaySyncEnabled); void MetalLayer_SetDrawableSize(void * metalLayer, double width, double height); void * MetalLayer_NextDrawable(void * metalLayer); void * MetalDrawable_Texture(void * drawable);
@@ -0,0 +1,54 @@ // +build darwin #import <QuartzCore/QuartzCore.h> #include "ca.h" void * MakeMetalLayer() { return [[CAMetalLayer alloc] init]; } uint16_t MetalLayer_PixelFormat(void * metalLayer) { return ((CAMetalLayer *)metalLayer).pixelFormat; } void MetalLayer_SetDevice(void * metalLayer, void * device) { ((CAMetalLayer *)metalLayer).device = (id<MTLDevice>)device; } const char * MetalLayer_SetPixelFormat(void * metalLayer, uint16_t pixelFormat) { @try { ((CAMetalLayer *)metalLayer).pixelFormat = (MTLPixelFormat)pixelFormat; } @catch (NSException * exception) { return exception.reason.UTF8String; } return NULL; } const char * MetalLayer_SetMaximumDrawableCount(void * metalLayer, uint_t maximumDrawableCount) { if (@available(macOS 10.13.2, *)) { @try { ((CAMetalLayer *)metalLayer).maximumDrawableCount = (NSUInteger)maximumDrawableCount; } @catch (NSException * exception) { return exception.reason.UTF8String; } } return NULL; } void MetalLayer_SetDisplaySyncEnabled(void * metalLayer, BOOL displaySyncEnabled) { ((CAMetalLayer *)metalLayer).displaySyncEnabled = displaySyncEnabled; } void MetalLayer_SetDrawableSize(void * metalLayer, double width, double height) { ((CAMetalLayer *)metalLayer).drawableSize = (CGSize){width, height}; } void * MetalLayer_NextDrawable(void * metalLayer) { return [(CAMetalLayer *)metalLayer nextDrawable]; } void * MetalDrawable_Texture(void * metalDrawable) { return ((id<CAMetalDrawable>)metalDrawable).texture; }
@@ -0,0 +1,65 @@ // +build darwin // Package ns provides access to Apple's AppKit API (https://developer.apple.com/documentation/appkit). // // This package is in very early stages of development. // It's a minimal implementation with scope limited to // supporting the movingtriangle example. package ns import ( "unsafe" "dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ca" ) /* #include "ns.h" */ import "C" // Window is a window that an app displays on the screen. // // Reference: https://developer.apple.com/documentation/appkit/nswindow. type Window struct { window unsafe.Pointer } // NewWindow returns a Window that wraps an existing NSWindow * pointer. func NewWindow(window unsafe.Pointer) Window { return Window{window} } // ContentView returns the window's content view, the highest accessible View // in the window's view hierarchy. // // Reference: https://developer.apple.com/documentation/appkit/nswindow/1419160-contentview. func (w Window) ContentView() View { return View{C.Window_ContentView(w.window)} } // View is the infrastructure for drawing, printing, and handling events in an app. // // Reference: https://developer.apple.com/documentation/appkit/nsview. type View struct { view unsafe.Pointer } // SetLayer sets v.layer to l. // // Reference: https://developer.apple.com/documentation/appkit/nsview/1483298-layer. func (v View) SetLayer(l ca.Layer) { C.View_SetLayer(v.view, l.Layer()) } // SetWantsLayer sets v.wantsLayer to wantsLayer. // // Reference: https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer. func (v View) SetWantsLayer(wantsLayer bool) { switch wantsLayer { case true: C.View_SetWantsLayer(v.view, 1) case false: C.View_SetWantsLayer(v.view, 0) } }
@@ -0,0 +1,8 @@ // +build darwin typedef signed char BOOL; void * Window_ContentView(void * window); void View_SetLayer(void * view, void * layer); void View_SetWantsLayer(void * view, BOOL wantsLayer);
@@ -0,0 +1,16 @@ // +build darwin #import <Cocoa/Cocoa.h> #include "ns.h" void * Window_ContentView(void * window) { return ((NSWindow *)window).contentView; } void View_SetLayer(void * view, void * layer) { ((NSView *)view).layer = (CALayer *)layer; } void View_SetWantsLayer(void * view, BOOL wantsLayer) { ((NSView *)view).wantsLayer = wantsLayer; }
@@ -0,0 +1,198 @@ // +build darwin // movingtriangle is an example Metal program that displays a moving triangle in a window. // It opens a window and renders a triangle that follows the mouse cursor. package main import ( "flag" "fmt" "log" "os" "runtime" "time" "unsafe" "dmitri.shuralyov.com/gpu/mtl" "dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ca" "dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ns" "github.com/go-gl/glfw/v3.2/glfw" "golang.org/x/image/math/f32" ) func init() { runtime.LockOSThread() } func main() { flag.Usage = func() { fmt.Fprintln(os.Stderr, "Usage: movingtriangle") flag.PrintDefaults() } flag.Parse() err := run() if err != nil { log.Fatalln(err) } } func run() error { device, err := mtl.CreateSystemDefaultDevice() if err != nil { return err } fmt.Println("Metal device:", device.Name) err = glfw.Init() if err != nil { return err } defer glfw.Terminate() glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI) window, err := glfw.CreateWindow(640, 480, "Metal Example", nil, nil) if err != nil { return err } defer window.Destroy() ml := ca.MakeMetalLayer() ml.SetDevice(device) ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm) ml.SetDrawableSize(window.GetFramebufferSize()) ml.SetMaximumDrawableCount(3) ml.SetDisplaySyncEnabled(true) cocoaWindow := ns.NewWindow(unsafe.Pointer(window.GetCocoaWindow())) cocoaWindow.ContentView().SetLayer(ml) cocoaWindow.ContentView().SetWantsLayer(true) // Set callbacks. window.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) { ml.SetDrawableSize(width, height) }) var windowSize = [2]int32{640, 480} window.SetSizeCallback(func(_ *glfw.Window, width, height int) { windowSize[0], windowSize[1] = int32(width), int32(height) }) var pos [2]float32 window.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) { pos[0], pos[1] = float32(x), float32(y) }) // Create a render pipeline state. const source = `#include <metal_stdlib> using namespace metal; struct Vertex { float4 position [[position]]; float4 color; }; vertex Vertex VertexShader( uint vertexID [[vertex_id]], device Vertex * vertices [[buffer(0)]], constant int2 * windowSize [[buffer(1)]], constant float2 * pos [[buffer(2)]] ) { Vertex out = vertices[vertexID]; out.position.xy += *pos; float2 viewportSize = float2(*windowSize); out.position.xy = float2(-1 + out.position.x / (0.5 * viewportSize.x), 1 - out.position.y / (0.5 * viewportSize.y)); return out; } fragment float4 FragmentShader(Vertex in [[stage_in]]) { return in.color; } ` lib, err := device.MakeLibrary(source, mtl.CompileOptions{}) if err != nil { return err } vs, err := lib.MakeFunction("VertexShader") if err != nil { return err } fs, err := lib.MakeFunction("FragmentShader") if err != nil { return err } var rpld mtl.RenderPipelineDescriptor rpld.VertexFunction = vs rpld.FragmentFunction = fs rpld.ColorAttachments[0].PixelFormat = ml.PixelFormat() rps, err := device.MakeRenderPipelineState(rpld) if err != nil { return err } // Create a vertex buffer. type Vertex struct { Position f32.Vec4 Color f32.Vec4 } vertexData := [...]Vertex{ {f32.Vec4{0, 0, 0, 1}, f32.Vec4{1, 0, 0, 1}}, {f32.Vec4{300, 100, 0, 1}, f32.Vec4{0, 1, 0, 1}}, {f32.Vec4{0, 100, 0, 1}, f32.Vec4{0, 0, 1, 1}}, } vertexBuffer := device.MakeBuffer(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged) cq := device.MakeCommandQueue() frame := startFPSCounter() for !window.ShouldClose() { glfw.PollEvents() // Create a drawable to render into. drawable, err := ml.NextDrawable() if err != nil { return err } cb := cq.MakeCommandBuffer() // Encode all render commands. var rpd mtl.RenderPassDescriptor rpd.ColorAttachments[0].LoadAction = mtl.LoadActionClear rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{Red: 0.35, Green: 0.65, Blue: 0.85, Alpha: 1} rpd.ColorAttachments[0].Texture = drawable.Texture() rce := cb.MakeRenderCommandEncoder(rpd) rce.SetRenderPipelineState(rps) rce.SetVertexBuffer(vertexBuffer, 0, 0) rce.SetVertexBytes(unsafe.Pointer(&windowSize[0]), unsafe.Sizeof(windowSize), 1) rce.SetVertexBytes(unsafe.Pointer(&pos[0]), unsafe.Sizeof(pos), 2) rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3) rce.EndEncoding() cb.PresentDrawable(drawable) cb.Commit() frame <- struct{}{} } return nil } func startFPSCounter() chan struct{} { frame := make(chan struct{}, 4) go func() { second := time.Tick(time.Second) frames := 0 for { select { case <-second: fmt.Println("fps:", frames) frames = 0 case <-frame: frames++ } } }() return frame }
@@ -191,10 +191,11 @@ const ( // Resource represents a memory allocation for storing specialized data // that is accessible to the GPU. // // Reference: https://developer.apple.com/documentation/metal/mtlresource. type Resource interface { // resource returns the underlying id<MTLResource> pointer. resource() unsafe.Pointer } // RenderPipelineDescriptor configures new RenderPipelineState objects. // @@ -325,10 +326,13 @@ func CopyAllDevices() []Device { ds[i].Name = C.GoString(d.Name) } return ds } // Device returns the underlying id<MTLDevice> pointer. func (d Device) Device() unsafe.Pointer { return d.device } // SupportsFeatureSet reports whether device d supports feature set fs. // // Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset. func (d Device) SupportsFeatureSet(fs FeatureSet) bool { return C.Device_SupportsFeatureSet(d.device, C.uint16_t(fs)) != 0 @@ -403,10 +407,18 @@ func (d Device) MakeTexture(td TextureDescriptor) Texture { // Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions. type CompileOptions struct { // TODO. } // Drawable is a displayable resource that can be rendered or written to. // // Reference: https://developer.apple.com/documentation/metal/mtldrawable. type Drawable interface { // Drawable returns the underlying id<MTLDrawable> pointer. Drawable() unsafe.Pointer } // CommandQueue is a queue that organizes the order // in which command buffers are executed by the GPU. // // Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue. type CommandQueue struct { @@ -426,10 +438,17 @@ func (cq CommandQueue) MakeCommandBuffer() CommandBuffer { // Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer. type CommandBuffer struct { commandBuffer unsafe.Pointer } // PresentDrawable registers a drawable presentation to occur as soon as possible. // // Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable. func (cb CommandBuffer) PresentDrawable(d Drawable) { C.CommandBuffer_PresentDrawable(cb.commandBuffer, d.Drawable()) } // Commit commits this command buffer for execution as soon as possible. // // Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit. func (cb CommandBuffer) Commit() { C.CommandBuffer_Commit(cb.commandBuffer) @@ -562,17 +581,25 @@ func (l Library) MakeFunction(name string) (Function, error) { // // Reference: https://developer.apple.com/documentation/metal/mtltexture. type Texture struct { texture unsafe.Pointer // TODO: Change these fields into methods. // Width is the width of the texture image for the base level mipmap, in pixels. Width int // Height is the height of the texture image for the base level mipmap, in pixels. Height int } // NewTexture returns a Texture that wraps an existing id<MTLTexture> pointer. func NewTexture(texture unsafe.Pointer) Texture { return Texture{texture: texture} } // resource implements the Resource interface. func (t Texture) resource() unsafe.Pointer { return t.texture } // GetBytes copies a block of pixels from the storage allocation of texture // slice zero into system memory at a specified address. //
@@ -84,10 +84,11 @@ struct RenderPipelineState Device_MakeRenderPipelineState(void * device, struct void * Device_MakeBuffer(void * device, const void * bytes, size_t length, uint16_t options); void * Device_MakeTexture(void * device, struct TextureDescriptor descriptor); void * CommandQueue_MakeCommandBuffer(void * commandQueue); void CommandBuffer_PresentDrawable(void * commandBuffer, void * drawable); void CommandBuffer_Commit(void * commandBuffer); void CommandBuffer_WaitUntilCompleted(void * commandBuffer); void * CommandBuffer_MakeRenderCommandEncoder(void * commandBuffer, struct RenderPassDescriptor descriptor); void * CommandBuffer_MakeBlitCommandEncoder(void * commandBuffer);
@@ -98,10 +98,14 @@ void * Device_MakeTexture(void * device, struct TextureDescriptor descriptor) { void * CommandQueue_MakeCommandBuffer(void * commandQueue) { return [(id<MTLCommandQueue>)commandQueue commandBuffer]; } void CommandBuffer_PresentDrawable(void * commandBuffer, void * drawable) { [(id<MTLCommandBuffer>)commandBuffer presentDrawable:(id<MTLDrawable>)drawable]; } void CommandBuffer_Commit(void * commandBuffer) { [(id<MTLCommandBuffer>)commandBuffer commit]; } void CommandBuffer_WaitUntilCompleted(void * commandBuffer) {