@@ -77,11 +77,10 @@ For convenience, a ` + "`" + `godoc` + "`" + ` view of this change can be seen [ CreatedAt: time.Date(2018, 2, 20, 21, 49, 35, 536092503, time.UTC), State: change.Approved, Body: "There have been some out-of-band review comments that I've addressed. This will do for the initial version.\n\nLGTM.", }, change.TimelineItem{ ID: "2", Actor: dmitshur, CreatedAt: time.Date(2018, 2, 20, 21, 57, 47, 537746502, time.UTC), Payload: change.MergedEvent{ CommitID: "957792cbbdabb084d484a7dcfd1e5b1a739a0ced", CommitHTMLURL: "https://dmitri.shuralyov.com/font/woff2/...$commit/957792cbbdabb084d484a7dcfd1e5b1a739a0ced", @@ -139,44 +138,75 @@ spec as is. Addressing this will be a part of future changes.`, }, }}, "dmitri.shuralyov.com/gpu/mtl": {{ Change: change.Change{ ID: 1, State: change.OpenState, Title: "WIP: Add minimal API to support interactive rendering in a window.", State: change.MergedState, Title: "Add minimal API to support interactive rendering in a window.", Labels: nil, Author: dmitshur, CreatedAt: time.Date(2018, 10, 17, 2, 9, 9, 583606000, time.UTC), Replies: 0, Commits: 1, ChangedFiles: 4, Replies: 1, Commits: 4, ChangedFiles: 10, }, Timeline: []interface{}{ change.Comment{ ID: "0", User: dmitshur, CreatedAt: time.Date(2018, 10, 17, 2, 9, 9, 583606000, time.UTC), Edited: &change.Edited{ By: dmitshur, At: time.Date(2018, 10, 21, 17, 25, 56, 868164000, time.UTC), }, Body: `The goal of this change is to make it possible to use package mtl to render to a window at 60 FPS. It tries to add the minimum viable API that is needed. to render to a window at interactive framerates (e.g., at 60 FPS, assuming a 60 Hz display with vsync on). It adds the minimal API that is needed. A new movingtriangle example is added as a demonstration of this functionality. It renders a triangle that follows the mouse cursor. functionality. It opens a window and renders a triangle that follows the mouse cursor. **TODO:** A lot of the newly added API comes from Core Animation, AppKit frameworks, rather than Metal. As a result, they likely do not belong in this package and should be factored out.`, Much of the needed API comes from Core Animation, AppKit frameworks, rather than Metal. Avoid adding that to mtl package; instead create separate packages. For now, they are hidden in internal to avoid committing to a public API and import path. After gaining more confidence in the approach, they can be factored out and made public.`, }, change.TimelineItem{ ID: "1", Actor: dmitshur, CreatedAt: time.Date(2018, 10, 18, 0, 54, 37, 790845000, time.UTC), Payload: change.RenamedEvent{ From: "WIP: Add minimal API to support rendering to a window at 60 FPS.", To: "WIP: Add minimal API to support interactive rendering in a window.", }, }, change.TimelineItem{ Actor: dmitshur, CreatedAt: time.Date(2018, 10, 21, 17, 25, 56, 868164000, time.UTC), Payload: change.RenamedEvent{ From: "WIP: Add minimal API to support interactive rendering in a window.", To: "Add minimal API to support interactive rendering in a window.", }, }, change.Review{ ID: "1", User: hajimehoshi, CreatedAt: time.Date(2018, 10, 23, 3, 22, 57, 249312000, time.UTC), State: change.Approved, Body: "I did a rough review and could not find a critical issue. 🙂", }, change.TimelineItem{ Actor: dmitshur, CreatedAt: time.Date(2018, 10, 23, 3, 32, 2, 951463000, time.UTC), Payload: change.MergedEvent{ CommitID: "c4eb07ba2d711bc78bcd2606dd587d9267a61aa5", CommitHTMLURL: "https://dmitri.shuralyov.com/gpu/mtl/...$commit/c4eb07ba2d711bc78bcd2606dd587d9267a61aa5", RefName: "master", }, }, }, Commits: []change.Commit{{ SHA: "fc76fa8984fb4a28ff383895e55e635e06bd32f0", Message: `WIP: Add minimal API to support rendering to a window at 60 FPS. @@ -190,14 +220,34 @@ functionality. It renders a triangle that follows the mouse cursor. TODO: A lot of the newly added API comes from Core Animation, AppKit frameworks, rather than Metal. As a result, they likely do not belong in this package and should be factored out.`, Author: dmitshur, AuthorTime: time.Date(2018, 10, 17, 2, 9, 9, 583606000, time.UTC), }, { SHA: "da15ef360afe80e10274aecb1b3e1390144fde3c", Message: "Add missing reference links.", Author: dmitshur, AuthorTime: time.Date(2018, 10, 21, 3, 16, 43, 311370000, time.UTC), }, { SHA: "d146c0ceb29d388d838337b3951f16dca31602e1", Message: "Factor out Core Animation, Cocoa APIs into separate internal packages.", Author: dmitshur, AuthorTime: time.Date(2018, 10, 21, 17, 22, 10, 566116000, time.UTC), }, { SHA: "c4eb07ba2d711bc78bcd2606dd587d9267a61aa5", Message: `Move internal/{ca,ns} into example/movingtriangle. Also make minor tweaks to the documentation to make it more accurate.`, Author: dmitshur, AuthorTime: time.Date(2018, 10, 23, 3, 20, 25, 631677000, time.UTC), }}, Diffs: map[string][]byte{ "all": []byte(diffMtlAll), "fc76fa8984fb4a28ff383895e55e635e06bd32f0": []byte(diffMtlCommit1), "da15ef360afe80e10274aecb1b3e1390144fde3c": []byte(diffMtlCommit2), "d146c0ceb29d388d838337b3951f16dca31602e1": []byte(diffMtlCommit3), "c4eb07ba2d711bc78bcd2606dd587d9267a61aa5": []byte(diffMtlCommit4), }, }}, }, } @@ -287,10 +337,15 @@ func (svc *Service) ListTimeline(ctx context.Context, repo string, id uint64, op { t := timeline[0].(change.Comment) t.Reactions = reactions[t.ID] timeline[0] = t } { t := timeline[3].(change.Review) t.Reactions = reactions[t.ID] timeline[3] = t } } return timeline, nil } // ListCommits lists change commits. @@ -340,22 +395,34 @@ func hasChange(repo string, id uint64) bool { const threadType = "Change" // ThreadType returns the notifications thread type for this service. func (*Service) ThreadType(repo string) string { return threadType } var dmitshur = users.User{ UserSpec: users.UserSpec{ ID: 1924134, Domain: "github.com", }, Login: "dmitshur", Name: "Dmitri Shuralyov", Email: "dmitri@shuralyov.com", AvatarURL: "https://dmitri.shuralyov.com/avatar.jpg", HTMLURL: "https://dmitri.shuralyov.com", SiteAdmin: true, } var ( dmitshur = users.User{ UserSpec: users.UserSpec{ ID: 1924134, Domain: "github.com", }, Login: "dmitshur", Name: "Dmitri Shuralyov", Email: "dmitri@shuralyov.com", AvatarURL: "https://dmitri.shuralyov.com/avatar.jpg", HTMLURL: "https://dmitri.shuralyov.com", SiteAdmin: true, } hajimehoshi = users.User{ UserSpec: users.UserSpec{ ID: 16950, Domain: "github.com", }, Login: "hajimehoshi", AvatarURL: "https://avatars2.githubusercontent.com/u/16950?v=4", HTMLURL: "https://github.com/hajimehoshi", } ) const diffAll = `diff --git a/Commit Message b/Commit Message new file mode 100644 index 0000000..dfb31fe --- /dev/null @@ -1774,35 +1841,371 @@ index 0000000..dfb31fe @@ -0,0 +1,27 @@ +Parent: 0cf138a8 (cmd/mtlinfo: Add a tool to list all Metal devices, supported feature sets.) +Author: Dmitri Shuralyov <dmitri@shuralyov.com> +AuthorDate: Sat Jun 23 01:07:53 2018 -0400 +Commit: Dmitri Shuralyov <dmitri@shuralyov.com> +CommitDate: Tue Oct 16 21:39:22 2018 -0400 +CommitDate: Sat Oct 20 23:15:25 2018 -0400 + +WIP: Add minimal API to support rendering to a window at 60 FPS. +Add minimal API to support interactive rendering in a window. + +The goal of this change is to make it possible to use package mtl +to render to a window at 60 FPS. It tries to add the minimum viable +API that is needed. +to render to a window at interactive framerates (e.g., at 60 FPS, +assuming a 60 Hz display with vsync on). It adds the minimal API +that is needed. + +A new movingtriangle example is added as a demonstration of this +functionality. It renders a triangle that follows the mouse cursor. +functionality. It opens a window and renders a triangle that follows +the mouse cursor. + +Much of the needed API comes from Core Animation, AppKit frameworks, +rather than Metal. Avoid adding that to mtl package; instead create +separate packages. For now, they are hidden in internal to avoid +committing to a public API and import path. After gaining more +confidence in the approach, they can be factored out and made public. diff --git a/example/movingtriangle/internal/ca/ca.go b/example/movingtriangle/internal/ca/ca.go new file mode 100644 index 0000000..d2ff39d --- /dev/null +++ b/example/movingtriangle/internal/ca/ca.go @@ -0,0 +1,137 @@ +// +build darwin + +TODO: A lot of the newly added API comes from Core Animation, AppKit +frameworks, rather than Metal. As a result, they likely do not belong +in this package and should be factored out. ` + diffMtlCommit1 const diffMtlCommit1 = `diff --git a/example/movingtriangle/main.go b/example/movingtriangle/main.go +// 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)) +} diff --git a/example/movingtriangle/internal/ca/ca.h b/example/movingtriangle/internal/ca/ca.h new file mode 100644 index 0000000..18b5e03 index 0000000..809898b --- /dev/null +++ b/example/movingtriangle/internal/ca/ca.h @@ -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); diff --git a/example/movingtriangle/internal/ca/ca.m b/example/movingtriangle/internal/ca/ca.m new file mode 100644 index 0000000..45d14f7 --- /dev/null +++ b/example/movingtriangle/internal/ca/ca.m @@ -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; +} diff --git a/example/movingtriangle/internal/ns/ns.go b/example/movingtriangle/internal/ns/ns.go new file mode 100644 index 0000000..e8d2993 --- /dev/null +++ b/example/movingtriangle/internal/ns/ns.go @@ -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) + } +} diff --git a/example/movingtriangle/internal/ns/ns.h b/example/movingtriangle/internal/ns/ns.h new file mode 100644 index 0000000..42ceb6a --- /dev/null +++ b/example/movingtriangle/internal/ns/ns.h @@ -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); diff --git a/example/movingtriangle/internal/ns/ns.m b/example/movingtriangle/internal/ns/ns.m new file mode 100644 index 0000000..937836d --- /dev/null +++ b/example/movingtriangle/internal/ns/ns.m @@ -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; +} diff --git a/example/movingtriangle/main.go b/example/movingtriangle/main.go new file mode 100644 index 0000000..cf2aa35 --- /dev/null +++ b/example/movingtriangle/main.go @@ -0,0 +1,196 @@ @@ -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" @@ -1811,25 +2214,25 @@ index 0000000..18b5e03 + "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 usage() { + fmt.Fprintln(os.Stderr, "Usage: movingtriangle") + flag.PrintDefaults() +} + +func init() { + runtime.LockOSThread() +} + +func main() { + flag.Usage = usage + flag.Usage = func() { + fmt.Fprintln(os.Stderr, "Usage: movingtriangle") + flag.PrintDefaults() + } + flag.Parse() + + err := run() + if err != nil { + log.Fatalln(err) @@ -1854,22 +2257,23 @@ index 0000000..18b5e03 + if err != nil { + return err + } + defer window.Destroy() + + layer := mtl.MakeLayer() + layer.SetDevice(device) + layer.SetPixelFormat(mtl.PixelFormatBGRA8UNorm) + layer.SetDrawableSize(window.GetFramebufferSize()) + layer.SetMaximumDrawableCount(3) + layer.SetDisplaySyncEnabled(true) + mtl.SetWindowContentViewLayer(window.GetCocoaWindow(), layer) + mtl.SetWindowContentViewWantsLayer(window.GetCocoaWindow(), true) + 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) { + layer.SetDrawableSize(width, height) + 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) + }) @@ -1919,11 +2323,11 @@ index 0000000..18b5e03 + return err + } + var rpld mtl.RenderPipelineDescriptor + rpld.VertexFunction = vs + rpld.FragmentFunction = fs + rpld.ColorAttachments[0].PixelFormat = layer.PixelFormat() + rpld.ColorAttachments[0].PixelFormat = ml.PixelFormat() + rps, err := device.MakeRenderPipelineState(rpld) + if err != nil { + return err + } + @@ -1945,11 +2349,11 @@ index 0000000..18b5e03 + + for !window.ShouldClose() { + glfw.PollEvents() + + // Create a drawable to render into. + drawable, err := layer.NextDrawable() + drawable, err := ml.NextDrawable() + if err != nil { + return err + } + + cb := cq.MakeCommandBuffer() @@ -1966,11 +2370,11 @@ index 0000000..18b5e03 + 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.Present(drawable) + cb.PresentDrawable(drawable) + cb.Commit() + + frame <- struct{}{} + } + @@ -1993,148 +2397,22 @@ index 0000000..18b5e03 + } + }() + return frame +} diff --git a/mtl.go b/mtl.go index feff4bb..5ff54c5 100644 index feff4bb..9c66681 100644 --- a/mtl.go +++ b/mtl.go @@ -16,7 +16,7 @@ import ( @@ -15,7 +15,6 @@ import ( ) /* #cgo CFLAGS: -x objective-c -#cgo LDFLAGS: -framework Metal -framework Foundation +#cgo LDFLAGS: -framework Metal -framework QuartzCore -framework Foundation -#cgo CFLAGS: -x objective-c #cgo LDFLAGS: -framework Metal -framework Foundation #include <stdlib.h> #include "mtl.h" struct Library Go_Device_MakeLibrary(void * device, _GoString_ source) { @@ -25,6 +25,124 @@ struct Library Go_Device_MakeLibrary(void * device, _GoString_ source) { */ import "C" +// Layer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer. +type Layer struct { + layer unsafe.Pointer +} + +// MakeLayer creates a new Core Animation Metal layer. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer. +func MakeLayer() Layer { + return Layer{C.MakeLayer()} +} + +// PixelFormat returns the pixel format of textures for rendering layer content. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat. +func (l Layer) PixelFormat() PixelFormat { + return PixelFormat(C.Layer_PixelFormat(l.layer)) +} + +// SetDevice sets the Metal device responsible for the layer's drawable resources. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device. +func (l Layer) SetDevice(device Device) { + C.Layer_SetDevice(l.layer, 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. +func (l Layer) SetPixelFormat(pf PixelFormat) { + e := C.Layer_SetPixelFormat(l.layer, 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. +func (l Layer) SetMaximumDrawableCount(count int) { + e := C.Layer_SetMaximumDrawableCount(l.layer, 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. +func (l Layer) SetDisplaySyncEnabled(enabled bool) { + switch enabled { + case true: + C.Layer_SetDisplaySyncEnabled(l.layer, 1) + case false: + C.Layer_SetDisplaySyncEnabled(l.layer, 0) + } +} + +// SetDrawableSize sets the size, in pixels, of textures for rendering layer content. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize. +func (l Layer) SetDrawableSize(width, height int) { + C.Layer_SetDrawableSize(l.layer, C.double(width), C.double(height)) +} + +// NextDrawable returns a Metal drawable. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable. +func (l Layer) NextDrawable() (Drawable, error) { + d := C.Layer_NextDrawable(l.layer) + if d == nil { + return Drawable{}, errors.New("nextDrawable returned nil") + } + + return Drawable{d}, nil +} + +// Drawable is a displayable resource that can be rendered or written to. +// +// Reference: https://developer.apple.com/documentation/metal/mtldrawable. +type Drawable struct { + drawable unsafe.Pointer +} + +// Texture returns a Metal texture object representing the drawable object's content. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture. +func (d Drawable) Texture() Texture { + return Texture{ + texture: C.Drawable_Texture(d.drawable), + Width: 0, // TODO: Fetch dimensions of actually created texture. + Height: 0, // TODO: Fetch dimensions of actually created texture. + } +} + +// SetWindowContentViewLayer sets cocoaWindow's contentView's layer to layer. +// +// Reference: https://developer.apple.com/documentation/appkit/nsview/1483298-layer. +func SetWindowContentViewLayer(cocoaWindow uintptr, l Layer) { + C.SetWindowContentViewLayer(unsafe.Pointer(cocoaWindow), l.layer) +} + +// SetWindowContentViewWantsLayer sets cocoaWindow's contentView's wantsLayer to wantsLayer. +// +// Reference: https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer. +func SetWindowContentViewWantsLayer(cocoaWindow uintptr, wantsLayer bool) { + switch wantsLayer { + case true: + C.SetWindowContentViewWantsLayer(unsafe.Pointer(cocoaWindow), 1) + case false: + C.SetWindowContentViewWantsLayer(unsafe.Pointer(cocoaWindow), 0) + } +} + // FeatureSet defines a specific platform, hardware, and software configuration. // // Reference: https://developer.apple.com/documentation/metal/mtlfeatureset. @@ -49,8 +167,9 @@ type PixelFormat uint8 @@ -49,8 +48,9 @@ type PixelFormat uint8 // The data formats that describe the organization and characteristics // of individual pixels in a texture. const ( - PixelFormatRGBA8UNorm PixelFormat = 70 // Ordinary format with four 8-bit normalized unsigned integer components in RGBA order. - PixelFormatBGRA8UNorm PixelFormat = 80 // Ordinary format with four 8-bit normalized unsigned integer components in BGRA order. @@ -2142,34 +2420,67 @@ index feff4bb..5ff54c5 100644 + PixelFormatBGRA8UNorm PixelFormat = 80 // Ordinary format with four 8-bit normalized unsigned integer components in BGRA order. + PixelFormatBGRA8UNormSRGB PixelFormat = 81 // Ordinary format with four 8-bit normalized unsigned integer components in BGRA order with conversion between sRGB and linear space. ) // PrimitiveType defines geometric primitive types for drawing commands. @@ -215,7 +334,7 @@ type RenderPipelineDescriptor struct { @@ -193,6 +193,7 @@ const ( // // Reference: https://developer.apple.com/documentation/metal/mtlresource. type Resource interface { + // resource returns the underlying id<MTLResource> pointer. resource() unsafe.Pointer } @@ -215,7 +216,7 @@ type RenderPipelineDescriptor struct { // // Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor. type RenderPipelineColorAttachmentDescriptor struct { - // PixelFormat is the pixel format of the color attachment’s texture. + // PixelFormat is the pixel format of the color attachment's texture. PixelFormat PixelFormat } @@ -428,6 +547,13 @@ type CommandBuffer struct { @@ -327,6 +328,9 @@ func CopyAllDevices() []Device { 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. @@ -405,6 +409,14 @@ 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. // @@ -428,6 +440,13 @@ type CommandBuffer struct { commandBuffer unsafe.Pointer } +// Present registers a drawable presentation to occur as soon as possible. +// PresentDrawable registers a drawable presentation to occur as soon as possible. +// +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable. +func (cb CommandBuffer) Present(d Drawable) { + C.CommandBuffer_Present(cb.commandBuffer, d.drawable) +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. @@ -507,6 +633,13 @@ func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) { @@ -507,6 +526,13 @@ func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) { C.RenderCommandEncoder_SetVertexBuffer(rce.commandEncoder, buf.buffer, C.uint_t(offset), C.uint_t(index)) } +// SetVertexBytes sets a block of data for the vertex function. +// @@ -2179,141 +2490,77 @@ index feff4bb..5ff54c5 100644 +} + // DrawPrimitives renders one instance of primitives using vertex data // in contiguous array elements. // diff --git a/mtl.h b/mtl.h index 6ac8b18..e8924ab 100644 --- a/mtl.h +++ b/mtl.h @@ -74,6 +74,21 @@ struct Region { struct Size Size; }; @@ -557,6 +583,8 @@ func (l Library) MakeFunction(name string) (Function, error) { type Texture struct { texture unsafe.Pointer +void * MakeLayer(); + +uint16_t Layer_PixelFormat(void * layer); +void Layer_SetDevice(void * layer, void * device); +const char * Layer_SetPixelFormat(void * layer, uint16_t pixelFormat); +const char * Layer_SetMaximumDrawableCount(void * layer, uint_t maximumDrawableCount); +void Layer_SetDisplaySyncEnabled(void * layer, BOOL displaySyncEnabled); +void Layer_SetDrawableSize(void * layer, double width, double height); +void * Layer_NextDrawable(void * layer); + +void * Drawable_Texture(void * drawable); + // TODO: Change these fields into methods. + +void SetWindowContentViewLayer(void * cocoaWindow, void * layer); +void SetWindowContentViewWantsLayer(void * cocoaWindow, BOOL wantsLayer); // Width is the width of the texture image for the base level mipmap, in pixels. Width int @@ -564,6 +592,12 @@ type Texture struct { Height int } +// NewTexture returns a Texture that wraps an existing id<MTLTexture> pointer. +func NewTexture(texture unsafe.Pointer) Texture { + return Texture{texture: texture} +} + struct Device CreateSystemDefaultDevice(); struct Devices CopyAllDevices(); +// resource implements the Resource interface. func (t Texture) resource() unsafe.Pointer { return t.texture } @@ -86,6 +101,7 @@ void * Device_MakeTexture(void * device, struct TextureDescr // GetBytes copies a block of pixels from the storage allocation of texture diff --git a/mtl.h b/mtl.h index 6ac8b18..f7c4c67 100644 --- a/mtl.h +++ b/mtl.h @@ -86,6 +86,7 @@ void * Device_MakeTexture(void * device, struct TextureDescr void * CommandQueue_MakeCommandBuffer(void * commandQueue); +void CommandBuffer_Present(void * commandBuffer, void * drawable); +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); @@ -95,6 +111,7 @@ void CommandEncoder_EndEncoding(void * commandEncoder); @@ -95,6 +96,7 @@ void CommandEncoder_EndEncoding(void * commandEncoder); void RenderCommandEncoder_SetRenderPipelineState(void * renderCommandEncoder, void * renderPipelineState); void RenderCommandEncoder_SetVertexBuffer(void * renderCommandEncoder, void * buffer, uint_t offset, uint_t index); +void RenderCommandEncoder_SetVertexBytes(void * renderCommandEncoder, const void * bytes, size_t length, uint_t index); void RenderCommandEncoder_DrawPrimitives(void * renderCommandEncoder, uint8_t primitiveType, uint_t vertexStart, uint_t vertexCount); void BlitCommandEncoder_Synchronize(void * blitCommandEncoder, void * resource); diff --git a/mtl.m b/mtl.m index b3126d6..7b0fd96 100644 index b3126d6..4296744 100644 --- a/mtl.m +++ b/mtl.m @@ -1,9 +1,71 @@ @@ -1,7 +1,7 @@ // +build darwin -#include <stdlib.h> #import <Metal/Metal.h> +#import <QuartzCore/QuartzCore.h> +#import <Cocoa/Cocoa.h> + +#include <stdlib.h> + #include "mtl.h" +void * MakeLayer() { + return [[CAMetalLayer alloc] init]; +} + +uint16_t Layer_PixelFormat(void * layer) { + return ((CAMetalLayer *)layer).pixelFormat; +} + +void Layer_SetDevice(void * layer, void * device) { + ((CAMetalLayer *)layer).device = (id<MTLDevice>)device; +} + +const char * Layer_SetPixelFormat(void * layer, uint16_t pixelFormat) { + @try { + ((CAMetalLayer *)layer).pixelFormat = (MTLPixelFormat)pixelFormat; + } + @catch (NSException * exception) { + return exception.reason.UTF8String; + } + return NULL; +} + +const char * Layer_SetMaximumDrawableCount(void * layer, uint_t maximumDrawableCount) { + if (@available(macOS 10.13.2, *)) { + @try { + ((CAMetalLayer *)layer).maximumDrawableCount = (NSUInteger)maximumDrawableCount; + } + @catch (NSException * exception) { + return exception.reason.UTF8String; + } + } + return NULL; +} + +void Layer_SetDisplaySyncEnabled(void * layer, BOOL displaySyncEnabled) { + ((CAMetalLayer *)layer).displaySyncEnabled = displaySyncEnabled; +} + +void Layer_SetDrawableSize(void * layer, double width, double height) { + ((CAMetalLayer *)layer).drawableSize = (CGSize){width, height}; +} + +void * Layer_NextDrawable(void * layer) { + return [(CAMetalLayer *)layer nextDrawable]; +} + +void * Drawable_Texture(void * drawable) { + return ((id<CAMetalDrawable>)drawable).texture; +} + +void SetWindowContentViewLayer(void * cocoaWindow, void * layer) { + ((NSWindow *)cocoaWindow).contentView.layer = (CAMetalLayer *)layer; +} + +void SetWindowContentViewWantsLayer(void * cocoaWindow, BOOL wantsLayer) { + ((NSWindow *)cocoaWindow).contentView.wantsLayer = wantsLayer; +} + struct Device CreateSystemDefaultDevice() { id<MTLDevice> device = MTLCreateSystemDefaultDevice(); if (!device) { @@ -100,6 +162,10 @@ struct RenderPipelineState Device_MakeRenderPipelineState(void * device, struct @@ -100,6 +100,10 @@ struct RenderPipelineState Device_MakeRenderPipelineState(void * device, struct return [(id<MTLCommandQueue>)commandQueue commandBuffer]; } +void CommandBuffer_Present(void * commandBuffer, void * drawable) { + [(id<MTLCommandBuffer>)commandBuffer presentDrawable:(id<CAMetalDrawable>)drawable]; +void CommandBuffer_PresentDrawable(void * commandBuffer, void * drawable) { + [(id<MTLCommandBuffer>)commandBuffer presentDrawable:(id<MTLDrawable>)drawable]; +} + void CommandBuffer_Commit(void * commandBuffer) { [(id<MTLCommandBuffer>)commandBuffer commit]; } @@ -136,14 +202,20 @@ void RenderCommandEncoder_SetRenderPipelineState(void * renderCommandEncoder, vo @@ -136,14 +140,20 @@ void RenderCommandEncoder_SetRenderPipelineState(void * renderCommandEncoder, vo void RenderCommandEncoder_SetVertexBuffer(void * renderCommandEncoder, void * buffer, uint_t offset, uint_t index) { [(id<MTLRenderCommandEncoder>)renderCommandEncoder setVertexBuffer:(id<MTLBuffer>)buffer - offset:offset - atIndex:index]; @@ -2336,5 +2583,1429 @@ index b3126d6..7b0fd96 100644 + vertexCount:(NSUInteger)vertexCount]; } void BlitCommandEncoder_Synchronize(void * blitCommandEncoder, void * resource) { ` const diffMtlCommit1 = `diff --git a/example/movingtriangle/main.go b/example/movingtriangle/main.go new file mode 100644 index 0000000..18b5e03 --- /dev/null +++ b/example/movingtriangle/main.go @@ -0,0 +1,196 @@ +// +build darwin + +// movingtriangle is an example Metal program that displays a moving triangle in a window. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "runtime" + "time" + "unsafe" + + "dmitri.shuralyov.com/gpu/mtl" + "github.com/go-gl/glfw/v3.2/glfw" + "golang.org/x/image/math/f32" +) + +func usage() { + fmt.Fprintln(os.Stderr, "Usage: movingtriangle") + flag.PrintDefaults() +} + +func init() { + runtime.LockOSThread() +} + +func main() { + flag.Usage = usage + 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() + + layer := mtl.MakeLayer() + layer.SetDevice(device) + layer.SetPixelFormat(mtl.PixelFormatBGRA8UNorm) + layer.SetDrawableSize(window.GetFramebufferSize()) + layer.SetMaximumDrawableCount(3) + layer.SetDisplaySyncEnabled(true) + mtl.SetWindowContentViewLayer(window.GetCocoaWindow(), layer) + mtl.SetWindowContentViewWantsLayer(window.GetCocoaWindow(), true) + + // Set callbacks. + window.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) { + layer.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 = layer.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 := layer.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.Present(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 +} diff --git a/mtl.go b/mtl.go index feff4bb..5ff54c5 100644 --- a/mtl.go +++ b/mtl.go @@ -16,7 +16,7 @@ import ( /* #cgo CFLAGS: -x objective-c -#cgo LDFLAGS: -framework Metal -framework Foundation +#cgo LDFLAGS: -framework Metal -framework QuartzCore -framework Foundation #include <stdlib.h> #include "mtl.h" struct Library Go_Device_MakeLibrary(void * device, _GoString_ source) { @@ -25,6 +25,124 @@ struct Library Go_Device_MakeLibrary(void * device, _GoString_ source) { */ import "C" +// Layer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer. +type Layer struct { + layer unsafe.Pointer +} + +// MakeLayer creates a new Core Animation Metal layer. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer. +func MakeLayer() Layer { + return Layer{C.MakeLayer()} +} + +// PixelFormat returns the pixel format of textures for rendering layer content. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat. +func (l Layer) PixelFormat() PixelFormat { + return PixelFormat(C.Layer_PixelFormat(l.layer)) +} + +// SetDevice sets the Metal device responsible for the layer's drawable resources. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device. +func (l Layer) SetDevice(device Device) { + C.Layer_SetDevice(l.layer, 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. +func (l Layer) SetPixelFormat(pf PixelFormat) { + e := C.Layer_SetPixelFormat(l.layer, 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. +func (l Layer) SetMaximumDrawableCount(count int) { + e := C.Layer_SetMaximumDrawableCount(l.layer, 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. +func (l Layer) SetDisplaySyncEnabled(enabled bool) { + switch enabled { + case true: + C.Layer_SetDisplaySyncEnabled(l.layer, 1) + case false: + C.Layer_SetDisplaySyncEnabled(l.layer, 0) + } +} + +// SetDrawableSize sets the size, in pixels, of textures for rendering layer content. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize. +func (l Layer) SetDrawableSize(width, height int) { + C.Layer_SetDrawableSize(l.layer, C.double(width), C.double(height)) +} + +// NextDrawable returns a Metal drawable. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable. +func (l Layer) NextDrawable() (Drawable, error) { + d := C.Layer_NextDrawable(l.layer) + if d == nil { + return Drawable{}, errors.New("nextDrawable returned nil") + } + + return Drawable{d}, nil +} + +// Drawable is a displayable resource that can be rendered or written to. +// +// Reference: https://developer.apple.com/documentation/metal/mtldrawable. +type Drawable struct { + drawable unsafe.Pointer +} + +// Texture returns a Metal texture object representing the drawable object's content. +// +// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture. +func (d Drawable) Texture() Texture { + return Texture{ + texture: C.Drawable_Texture(d.drawable), + Width: 0, // TODO: Fetch dimensions of actually created texture. + Height: 0, // TODO: Fetch dimensions of actually created texture. + } +} + +// SetWindowContentViewLayer sets cocoaWindow's contentView's layer to layer. +// +// Reference: https://developer.apple.com/documentation/appkit/nsview/1483298-layer. +func SetWindowContentViewLayer(cocoaWindow uintptr, l Layer) { + C.SetWindowContentViewLayer(unsafe.Pointer(cocoaWindow), l.layer) +} + +// SetWindowContentViewWantsLayer sets cocoaWindow's contentView's wantsLayer to wantsLayer. +// +// Reference: https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer. +func SetWindowContentViewWantsLayer(cocoaWindow uintptr, wantsLayer bool) { + switch wantsLayer { + case true: + C.SetWindowContentViewWantsLayer(unsafe.Pointer(cocoaWindow), 1) + case false: + C.SetWindowContentViewWantsLayer(unsafe.Pointer(cocoaWindow), 0) + } +} + // FeatureSet defines a specific platform, hardware, and software configuration. // // Reference: https://developer.apple.com/documentation/metal/mtlfeatureset. @@ -49,8 +167,9 @@ type PixelFormat uint8 // The data formats that describe the organization and characteristics // of individual pixels in a texture. const ( - PixelFormatRGBA8UNorm PixelFormat = 70 // Ordinary format with four 8-bit normalized unsigned integer components in RGBA order. - PixelFormatBGRA8UNorm PixelFormat = 80 // Ordinary format with four 8-bit normalized unsigned integer components in BGRA order. + PixelFormatRGBA8UNorm PixelFormat = 70 // Ordinary format with four 8-bit normalized unsigned integer components in RGBA order. + PixelFormatBGRA8UNorm PixelFormat = 80 // Ordinary format with four 8-bit normalized unsigned integer components in BGRA order. + PixelFormatBGRA8UNormSRGB PixelFormat = 81 // Ordinary format with four 8-bit normalized unsigned integer components in BGRA order with conversion between sRGB and linear space. ) // PrimitiveType defines geometric primitive types for drawing commands. @@ -215,7 +334,7 @@ type RenderPipelineDescriptor struct { // // Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor. type RenderPipelineColorAttachmentDescriptor struct { - // PixelFormat is the pixel format of the color attachment’s texture. + // PixelFormat is the pixel format of the color attachment's texture. PixelFormat PixelFormat } @@ -428,6 +547,13 @@ type CommandBuffer struct { commandBuffer unsafe.Pointer } +// Present registers a drawable presentation to occur as soon as possible. +// +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable. +func (cb CommandBuffer) Present(d Drawable) { + C.CommandBuffer_Present(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. @@ -507,6 +633,13 @@ func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) { C.RenderCommandEncoder_SetVertexBuffer(rce.commandEncoder, buf.buffer, C.uint_t(offset), C.uint_t(index)) } +// SetVertexBytes sets a block of data for the vertex function. +// +// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes. +func (rce RenderCommandEncoder) SetVertexBytes(bytes unsafe.Pointer, length uintptr, index int) { + C.RenderCommandEncoder_SetVertexBytes(rce.commandEncoder, bytes, C.size_t(length), C.uint_t(index)) +} + // DrawPrimitives renders one instance of primitives using vertex data // in contiguous array elements. // diff --git a/mtl.h b/mtl.h index 6ac8b18..e8924ab 100644 --- a/mtl.h +++ b/mtl.h @@ -74,6 +74,21 @@ struct Region { struct Size Size; }; +void * MakeLayer(); + +uint16_t Layer_PixelFormat(void * layer); +void Layer_SetDevice(void * layer, void * device); +const char * Layer_SetPixelFormat(void * layer, uint16_t pixelFormat); +const char * Layer_SetMaximumDrawableCount(void * layer, uint_t maximumDrawableCount); +void Layer_SetDisplaySyncEnabled(void * layer, BOOL displaySyncEnabled); +void Layer_SetDrawableSize(void * layer, double width, double height); +void * Layer_NextDrawable(void * layer); + +void * Drawable_Texture(void * drawable); + +void SetWindowContentViewLayer(void * cocoaWindow, void * layer); +void SetWindowContentViewWantsLayer(void * cocoaWindow, BOOL wantsLayer); + struct Device CreateSystemDefaultDevice(); struct Devices CopyAllDevices(); @@ -86,6 +101,7 @@ void * Device_MakeTexture(void * device, struct TextureDescr void * CommandQueue_MakeCommandBuffer(void * commandQueue); +void CommandBuffer_Present(void * commandBuffer, void * drawable); void CommandBuffer_Commit(void * commandBuffer); void CommandBuffer_WaitUntilCompleted(void * commandBuffer); void * CommandBuffer_MakeRenderCommandEncoder(void * commandBuffer, struct RenderPassDescriptor descriptor); @@ -95,6 +111,7 @@ void CommandEncoder_EndEncoding(void * commandEncoder); void RenderCommandEncoder_SetRenderPipelineState(void * renderCommandEncoder, void * renderPipelineState); void RenderCommandEncoder_SetVertexBuffer(void * renderCommandEncoder, void * buffer, uint_t offset, uint_t index); +void RenderCommandEncoder_SetVertexBytes(void * renderCommandEncoder, const void * bytes, size_t length, uint_t index); void RenderCommandEncoder_DrawPrimitives(void * renderCommandEncoder, uint8_t primitiveType, uint_t vertexStart, uint_t vertexCount); void BlitCommandEncoder_Synchronize(void * blitCommandEncoder, void * resource); diff --git a/mtl.m b/mtl.m index b3126d6..7b0fd96 100644 --- a/mtl.m +++ b/mtl.m @@ -1,9 +1,71 @@ // +build darwin -#include <stdlib.h> #import <Metal/Metal.h> +#import <QuartzCore/QuartzCore.h> +#import <Cocoa/Cocoa.h> + +#include <stdlib.h> + #include "mtl.h" +void * MakeLayer() { + return [[CAMetalLayer alloc] init]; +} + +uint16_t Layer_PixelFormat(void * layer) { + return ((CAMetalLayer *)layer).pixelFormat; +} + +void Layer_SetDevice(void * layer, void * device) { + ((CAMetalLayer *)layer).device = (id<MTLDevice>)device; +} + +const char * Layer_SetPixelFormat(void * layer, uint16_t pixelFormat) { + @try { + ((CAMetalLayer *)layer).pixelFormat = (MTLPixelFormat)pixelFormat; + } + @catch (NSException * exception) { + return exception.reason.UTF8String; + } + return NULL; +} + +const char * Layer_SetMaximumDrawableCount(void * layer, uint_t maximumDrawableCount) { + if (@available(macOS 10.13.2, *)) { + @try { + ((CAMetalLayer *)layer).maximumDrawableCount = (NSUInteger)maximumDrawableCount; + } + @catch (NSException * exception) { + return exception.reason.UTF8String; + } + } + return NULL; +} + +void Layer_SetDisplaySyncEnabled(void * layer, BOOL displaySyncEnabled) { + ((CAMetalLayer *)layer).displaySyncEnabled = displaySyncEnabled; +} + +void Layer_SetDrawableSize(void * layer, double width, double height) { + ((CAMetalLayer *)layer).drawableSize = (CGSize){width, height}; +} + +void * Layer_NextDrawable(void * layer) { + return [(CAMetalLayer *)layer nextDrawable]; +} + +void * Drawable_Texture(void * drawable) { + return ((id<CAMetalDrawable>)drawable).texture; +} + +void SetWindowContentViewLayer(void * cocoaWindow, void * layer) { + ((NSWindow *)cocoaWindow).contentView.layer = (CAMetalLayer *)layer; +} + +void SetWindowContentViewWantsLayer(void * cocoaWindow, BOOL wantsLayer) { + ((NSWindow *)cocoaWindow).contentView.wantsLayer = wantsLayer; +} + struct Device CreateSystemDefaultDevice() { id<MTLDevice> device = MTLCreateSystemDefaultDevice(); if (!device) { @@ -100,6 +162,10 @@ struct RenderPipelineState Device_MakeRenderPipelineState(void * device, struct return [(id<MTLCommandQueue>)commandQueue commandBuffer]; } +void CommandBuffer_Present(void * commandBuffer, void * drawable) { + [(id<MTLCommandBuffer>)commandBuffer presentDrawable:(id<CAMetalDrawable>)drawable]; +} + void CommandBuffer_Commit(void * commandBuffer) { [(id<MTLCommandBuffer>)commandBuffer commit]; } @@ -136,14 +202,20 @@ void RenderCommandEncoder_SetRenderPipelineState(void * renderCommandEncoder, vo void RenderCommandEncoder_SetVertexBuffer(void * renderCommandEncoder, void * buffer, uint_t offset, uint_t index) { [(id<MTLRenderCommandEncoder>)renderCommandEncoder setVertexBuffer:(id<MTLBuffer>)buffer - offset:offset - atIndex:index]; + offset:(NSUInteger)offset + atIndex:(NSUInteger)index]; +} + +void RenderCommandEncoder_SetVertexBytes(void * renderCommandEncoder, const void * bytes, size_t length, uint_t index) { + [(id<MTLRenderCommandEncoder>)renderCommandEncoder setVertexBytes:bytes + length:(NSUInteger)length + atIndex:(NSUInteger)index]; } void RenderCommandEncoder_DrawPrimitives(void * renderCommandEncoder, uint8_t primitiveType, uint_t vertexStart, uint_t vertexCount) { - [(id<MTLRenderCommandEncoder>)renderCommandEncoder drawPrimitives:primitiveType - vertexStart:vertexStart - vertexCount:vertexCount]; + [(id<MTLRenderCommandEncoder>)renderCommandEncoder drawPrimitives:(MTLPrimitiveType)primitiveType + vertexStart:(NSUInteger)vertexStart + vertexCount:(NSUInteger)vertexCount]; } void BlitCommandEncoder_Synchronize(void * blitCommandEncoder, void * resource) { ` const diffMtlCommit2 = `diff --git a/mtl.go b/mtl.go index 5ff54c5..4e3b9a7 100644 --- a/mtl.go +++ b/mtl.go @@ -58,6 +58,8 @@ func (l Layer) SetDevice(device Device) { // 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 (l Layer) SetPixelFormat(pf PixelFormat) { e := C.Layer_SetPixelFormat(l.layer, C.uint16_t(pf)) if e != nil { @@ -69,6 +71,8 @@ func (l Layer) SetPixelFormat(pf PixelFormat) { // 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 (l Layer) SetMaximumDrawableCount(count int) { e := C.Layer_SetMaximumDrawableCount(l.layer, C.uint_t(count)) if e != nil { @@ -78,6 +82,8 @@ func (l Layer) SetMaximumDrawableCount(count int) { // 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 (l Layer) SetDisplaySyncEnabled(enabled bool) { switch enabled { case true: ` const diffMtlCommit3 = `diff --git a/example/movingtriangle/main.go b/example/movingtriangle/main.go index 18b5e03..b09a63d 100644 --- a/example/movingtriangle/main.go +++ b/example/movingtriangle/main.go @@ -13,21 +13,21 @@ import ( "unsafe" "dmitri.shuralyov.com/gpu/mtl" + "dmitri.shuralyov.com/gpu/mtl/internal/ca" + "dmitri.shuralyov.com/gpu/mtl/internal/ns" "github.com/go-gl/glfw/v3.2/glfw" "golang.org/x/image/math/f32" ) -func usage() { - fmt.Fprintln(os.Stderr, "Usage: movingtriangle") - flag.PrintDefaults() -} - func init() { runtime.LockOSThread() } func main() { - flag.Usage = usage + flag.Usage = func() { + fmt.Fprintln(os.Stderr, "Usage: movingtriangle") + flag.PrintDefaults() + } flag.Parse() err := run() @@ -56,18 +56,19 @@ func run() error { } defer window.Destroy() - layer := mtl.MakeLayer() - layer.SetDevice(device) - layer.SetPixelFormat(mtl.PixelFormatBGRA8UNorm) - layer.SetDrawableSize(window.GetFramebufferSize()) - layer.SetMaximumDrawableCount(3) - layer.SetDisplaySyncEnabled(true) - mtl.SetWindowContentViewLayer(window.GetCocoaWindow(), layer) - mtl.SetWindowContentViewWantsLayer(window.GetCocoaWindow(), true) + 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) { - layer.SetDrawableSize(width, height) + ml.SetDrawableSize(width, height) }) var windowSize = [2]int32{640, 480} window.SetSizeCallback(func(_ *glfw.Window, width, height int) { @@ -121,7 +122,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { var rpld mtl.RenderPipelineDescriptor rpld.VertexFunction = vs rpld.FragmentFunction = fs - rpld.ColorAttachments[0].PixelFormat = layer.PixelFormat() + rpld.ColorAttachments[0].PixelFormat = ml.PixelFormat() rps, err := device.MakeRenderPipelineState(rpld) if err != nil { return err @@ -147,7 +148,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { glfw.PollEvents() // Create a drawable to render into. - drawable, err := layer.NextDrawable() + drawable, err := ml.NextDrawable() if err != nil { return err } @@ -168,7 +169,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3) rce.EndEncoding() - cb.Present(drawable) + cb.PresentDrawable(drawable) cb.Commit() frame <- struct{}{} diff --git a/internal/ca/ca.go b/internal/ca/ca.go new file mode 100644 index 0000000..87afcc6 --- /dev/null +++ b/internal/ca/ca.go @@ -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 ../../example/movingtriangle command. +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)) +} diff --git a/internal/ca/ca.h b/internal/ca/ca.h new file mode 100644 index 0000000..809898b --- /dev/null +++ b/internal/ca/ca.h @@ -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); diff --git a/internal/ca/ca.m b/internal/ca/ca.m new file mode 100644 index 0000000..45d14f7 --- /dev/null +++ b/internal/ca/ca.m @@ -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; +} diff --git a/internal/ns/ns.go b/internal/ns/ns.go new file mode 100644 index 0000000..b81157d --- /dev/null +++ b/internal/ns/ns.go @@ -0,0 +1,65 @@ +// +build darwin + +// Package ns provides access to Apple's Cocoa 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 ../../example/movingtriangle command. +package ns + +import ( + "unsafe" + + "dmitri.shuralyov.com/gpu/mtl/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) + } +} diff --git a/internal/ns/ns.h b/internal/ns/ns.h new file mode 100644 index 0000000..42ceb6a --- /dev/null +++ b/internal/ns/ns.h @@ -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); diff --git a/internal/ns/ns.m b/internal/ns/ns.m new file mode 100644 index 0000000..937836d --- /dev/null +++ b/internal/ns/ns.m @@ -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; +} diff --git a/mtl.go b/mtl.go index 4e3b9a7..9c66681 100644 --- a/mtl.go +++ b/mtl.go @@ -15,8 +15,7 @@ import ( ) /* -#cgo CFLAGS: -x objective-c -#cgo LDFLAGS: -framework Metal -framework QuartzCore -framework Foundation +#cgo LDFLAGS: -framework Metal -framework Foundation #include <stdlib.h> #include "mtl.h" struct Library Go_Device_MakeLibrary(void * device, _GoString_ source) { @@ -25,130 +24,6 @@ struct Library Go_Device_MakeLibrary(void * device, _GoString_ source) { */ import "C" -// Layer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables. -// -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer. -type Layer struct { - layer unsafe.Pointer -} - -// MakeLayer creates a new Core Animation Metal layer. -// -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer. -func MakeLayer() Layer { - return Layer{C.MakeLayer()} -} - -// PixelFormat returns the pixel format of textures for rendering layer content. -// -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat. -func (l Layer) PixelFormat() PixelFormat { - return PixelFormat(C.Layer_PixelFormat(l.layer)) -} - -// SetDevice sets the Metal device responsible for the layer's drawable resources. -// -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device. -func (l Layer) SetDevice(device Device) { - C.Layer_SetDevice(l.layer, 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 (l Layer) SetPixelFormat(pf PixelFormat) { - e := C.Layer_SetPixelFormat(l.layer, 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 (l Layer) SetMaximumDrawableCount(count int) { - e := C.Layer_SetMaximumDrawableCount(l.layer, 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 (l Layer) SetDisplaySyncEnabled(enabled bool) { - switch enabled { - case true: - C.Layer_SetDisplaySyncEnabled(l.layer, 1) - case false: - C.Layer_SetDisplaySyncEnabled(l.layer, 0) - } -} - -// SetDrawableSize sets the size, in pixels, of textures for rendering layer content. -// -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize. -func (l Layer) SetDrawableSize(width, height int) { - C.Layer_SetDrawableSize(l.layer, C.double(width), C.double(height)) -} - -// NextDrawable returns a Metal drawable. -// -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable. -func (l Layer) NextDrawable() (Drawable, error) { - d := C.Layer_NextDrawable(l.layer) - if d == nil { - return Drawable{}, errors.New("nextDrawable returned nil") - } - - return Drawable{d}, nil -} - -// Drawable is a displayable resource that can be rendered or written to. -// -// Reference: https://developer.apple.com/documentation/metal/mtldrawable. -type Drawable struct { - drawable unsafe.Pointer -} - -// Texture returns a Metal texture object representing the drawable object's content. -// -// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture. -func (d Drawable) Texture() Texture { - return Texture{ - texture: C.Drawable_Texture(d.drawable), - Width: 0, // TODO: Fetch dimensions of actually created texture. - Height: 0, // TODO: Fetch dimensions of actually created texture. - } -} - -// SetWindowContentViewLayer sets cocoaWindow's contentView's layer to layer. -// -// Reference: https://developer.apple.com/documentation/appkit/nsview/1483298-layer. -func SetWindowContentViewLayer(cocoaWindow uintptr, l Layer) { - C.SetWindowContentViewLayer(unsafe.Pointer(cocoaWindow), l.layer) -} - -// SetWindowContentViewWantsLayer sets cocoaWindow's contentView's wantsLayer to wantsLayer. -// -// Reference: https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer. -func SetWindowContentViewWantsLayer(cocoaWindow uintptr, wantsLayer bool) { - switch wantsLayer { - case true: - C.SetWindowContentViewWantsLayer(unsafe.Pointer(cocoaWindow), 1) - case false: - C.SetWindowContentViewWantsLayer(unsafe.Pointer(cocoaWindow), 0) - } -} - // FeatureSet defines a specific platform, hardware, and software configuration. // // Reference: https://developer.apple.com/documentation/metal/mtlfeatureset. @@ -318,6 +193,7 @@ const ( // // Reference: https://developer.apple.com/documentation/metal/mtlresource. type Resource interface { + // resource returns the underlying id<MTLResource> pointer. resource() unsafe.Pointer } @@ -452,6 +328,9 @@ func CopyAllDevices() []Device { 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. @@ -530,6 +409,14 @@ 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. // @@ -553,11 +440,11 @@ type CommandBuffer struct { commandBuffer unsafe.Pointer } -// Present registers a drawable presentation to occur as soon as possible. +// PresentDrawable registers a drawable presentation to occur as soon as possible. // // Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable. -func (cb CommandBuffer) Present(d Drawable) { - C.CommandBuffer_Present(cb.commandBuffer, d.drawable) +func (cb CommandBuffer) PresentDrawable(d Drawable) { + C.CommandBuffer_PresentDrawable(cb.commandBuffer, d.Drawable()) } // Commit commits this command buffer for execution as soon as possible. @@ -696,6 +583,8 @@ func (l Library) MakeFunction(name string) (Function, error) { 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 @@ -703,6 +592,12 @@ type Texture struct { 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 diff --git a/mtl.h b/mtl.h index e8924ab..f7c4c67 100644 --- a/mtl.h +++ b/mtl.h @@ -74,21 +74,6 @@ struct Region { struct Size Size; }; -void * MakeLayer(); - -uint16_t Layer_PixelFormat(void * layer); -void Layer_SetDevice(void * layer, void * device); -const char * Layer_SetPixelFormat(void * layer, uint16_t pixelFormat); -const char * Layer_SetMaximumDrawableCount(void * layer, uint_t maximumDrawableCount); -void Layer_SetDisplaySyncEnabled(void * layer, BOOL displaySyncEnabled); -void Layer_SetDrawableSize(void * layer, double width, double height); -void * Layer_NextDrawable(void * layer); - -void * Drawable_Texture(void * drawable); - -void SetWindowContentViewLayer(void * cocoaWindow, void * layer); -void SetWindowContentViewWantsLayer(void * cocoaWindow, BOOL wantsLayer); - struct Device CreateSystemDefaultDevice(); struct Devices CopyAllDevices(); @@ -101,7 +86,7 @@ void * Device_MakeTexture(void * device, struct TextureDescr void * CommandQueue_MakeCommandBuffer(void * commandQueue); -void CommandBuffer_Present(void * commandBuffer, void * drawable); +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); diff --git a/mtl.m b/mtl.m index 7b0fd96..4296744 100644 --- a/mtl.m +++ b/mtl.m @@ -1,71 +1,9 @@ // +build darwin #import <Metal/Metal.h> -#import <QuartzCore/QuartzCore.h> -#import <Cocoa/Cocoa.h> - #include <stdlib.h> - #include "mtl.h" -void * MakeLayer() { - return [[CAMetalLayer alloc] init]; -} - -uint16_t Layer_PixelFormat(void * layer) { - return ((CAMetalLayer *)layer).pixelFormat; -} - -void Layer_SetDevice(void * layer, void * device) { - ((CAMetalLayer *)layer).device = (id<MTLDevice>)device; -} - -const char * Layer_SetPixelFormat(void * layer, uint16_t pixelFormat) { - @try { - ((CAMetalLayer *)layer).pixelFormat = (MTLPixelFormat)pixelFormat; - } - @catch (NSException * exception) { - return exception.reason.UTF8String; - } - return NULL; -} - -const char * Layer_SetMaximumDrawableCount(void * layer, uint_t maximumDrawableCount) { - if (@available(macOS 10.13.2, *)) { - @try { - ((CAMetalLayer *)layer).maximumDrawableCount = (NSUInteger)maximumDrawableCount; - } - @catch (NSException * exception) { - return exception.reason.UTF8String; - } - } - return NULL; -} - -void Layer_SetDisplaySyncEnabled(void * layer, BOOL displaySyncEnabled) { - ((CAMetalLayer *)layer).displaySyncEnabled = displaySyncEnabled; -} - -void Layer_SetDrawableSize(void * layer, double width, double height) { - ((CAMetalLayer *)layer).drawableSize = (CGSize){width, height}; -} - -void * Layer_NextDrawable(void * layer) { - return [(CAMetalLayer *)layer nextDrawable]; -} - -void * Drawable_Texture(void * drawable) { - return ((id<CAMetalDrawable>)drawable).texture; -} - -void SetWindowContentViewLayer(void * cocoaWindow, void * layer) { - ((NSWindow *)cocoaWindow).contentView.layer = (CAMetalLayer *)layer; -} - -void SetWindowContentViewWantsLayer(void * cocoaWindow, BOOL wantsLayer) { - ((NSWindow *)cocoaWindow).contentView.wantsLayer = wantsLayer; -} - struct Device CreateSystemDefaultDevice() { id<MTLDevice> device = MTLCreateSystemDefaultDevice(); if (!device) { @@ -162,8 +100,8 @@ struct RenderPipelineState Device_MakeRenderPipelineState(void * device, struct return [(id<MTLCommandQueue>)commandQueue commandBuffer]; } -void CommandBuffer_Present(void * commandBuffer, void * drawable) { - [(id<MTLCommandBuffer>)commandBuffer presentDrawable:(id<CAMetalDrawable>)drawable]; +void CommandBuffer_PresentDrawable(void * commandBuffer, void * drawable) { + [(id<MTLCommandBuffer>)commandBuffer presentDrawable:(id<MTLDrawable>)drawable]; } void CommandBuffer_Commit(void * commandBuffer) { ` const diffMtlCommit4 = `diff --git a/internal/ca/ca.go b/example/movingtriangle/internal/ca/ca.go similarity index 98% rename from internal/ca/ca.go rename to example/movingtriangle/internal/ca/ca.go index 87afcc6..d2ff39d 100644 --- a/internal/ca/ca.go +++ b/example/movingtriangle/internal/ca/ca.go @@ -4,7 +4,7 @@ // // This package is in very early stages of development. // It's a minimal implementation with scope limited to -// supporting the ../../example/movingtriangle command. +// supporting the movingtriangle example. package ca import ( diff --git a/internal/ca/ca.h b/example/movingtriangle/internal/ca/ca.h similarity index 100% rename from internal/ca/ca.h rename to example/movingtriangle/internal/ca/ca.h diff --git a/internal/ca/ca.m b/example/movingtriangle/internal/ca/ca.m similarity index 100% rename from internal/ca/ca.m rename to example/movingtriangle/internal/ca/ca.m diff --git a/internal/ns/ns.go b/example/movingtriangle/internal/ns/ns.go similarity index 87% rename from internal/ns/ns.go rename to example/movingtriangle/internal/ns/ns.go index b81157d..e8d2993 100644 --- a/internal/ns/ns.go +++ b/example/movingtriangle/internal/ns/ns.go @@ -1,16 +1,16 @@ // +build darwin -// Package ns provides access to Apple's Cocoa API (https://developer.apple.com/documentation/appkit). +// 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 ../../example/movingtriangle command. +// supporting the movingtriangle example. package ns import ( "unsafe" - "dmitri.shuralyov.com/gpu/mtl/internal/ca" + "dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ca" ) /* diff --git a/internal/ns/ns.h b/example/movingtriangle/internal/ns/ns.h similarity index 100% rename from internal/ns/ns.h rename to example/movingtriangle/internal/ns/ns.h diff --git a/internal/ns/ns.m b/example/movingtriangle/internal/ns/ns.m similarity index 100% rename from internal/ns/ns.m rename to example/movingtriangle/internal/ns/ns.m diff --git a/example/movingtriangle/main.go b/example/movingtriangle/main.go index b09a63d..cf2aa35 100644 --- a/example/movingtriangle/main.go +++ b/example/movingtriangle/main.go @@ -1,6 +1,7 @@ // +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 ( @@ -13,8 +14,8 @@ import ( "unsafe" "dmitri.shuralyov.com/gpu/mtl" - "dmitri.shuralyov.com/gpu/mtl/internal/ca" - "dmitri.shuralyov.com/gpu/mtl/internal/ns" + "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" ) `