@@ -15,39 +15,39 @@ type Service struct { // Reactions, if not nil, is temporarily used as a place to store reactions. Reactions reactions.Service } var s = struct { changes []struct { changes map[string][]struct { change.Change Timeline []interface{} Commits []change.Commit Diffs map[string][]byte // Key is commit SHA. "all" is diff of all commits combined. } }{ changes: []struct { changes: map[string][]struct { change.Change Timeline []interface{} Commits []change.Commit Diffs map[string][]byte }{ { "dmitri.shuralyov.com/font/woff2": {{ Change: change.Change{ ID: 1, State: change.MergedState, Title: "Initial implementation of woff2.", Labels: nil, Author: shurcool, Author: dmitshur, CreatedAt: time.Date(2018, 2, 12, 0, 9, 19, 621031866, time.UTC), Replies: 1, Commits: 3, ChangedFiles: 5, }, Timeline: []interface{}{ change.Comment{ ID: "0", User: shurcool, User: dmitshur, CreatedAt: time.Date(2018, 2, 12, 0, 9, 19, 621031866, time.UTC), Body: `Add initial parser implementation. This is an initial implementation of a parser for the WOFF2 font packaging format. @@ -71,18 +71,18 @@ Helps https://github.com/ConradIrwin/font/issues/1. For convenience, a ` + "`" + `godoc` + "`" + ` view of this change can be seen [here](https://redpen.io/rk9a75c358f45654a8).`, }, change.Review{ ID: "1", User: shurcool, User: dmitshur, 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: shurcool, 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", RefName: "master", @@ -110,36 +110,87 @@ from the Go font source .ttf files located at https://go.googlesource.com/image/+/master/font/gofont/ttfs/. Add basic test coverage. Helps https://github.com/ConradIrwin/font/issues/1.`, Author: shurcool, Author: dmitshur, AuthorTime: time.Date(2018, 2, 11, 20, 10, 28, 0, time.UTC), }, { SHA: "61339d441b319cd6ca35d952522f86cc42ad4b6e", Message: `Update test for new API. The API had changed earlier, the test wasn't updated for it. This change fixes that, allowing tests to pass.`, Author: shurcool, Author: dmitshur, AuthorTime: time.Date(2018, 2, 12, 20, 22, 29, 674711268, time.UTC), }, { SHA: "3b528c98b05508322be465a207f5ffd8258b8a96", Message: `Add comment describing null-padding check. Also add a TODO comment to improve this check. It's not compliant with spec as is. Addressing this will be a part of future changes.`, Author: shurcool, Author: dmitshur, AuthorTime: time.Date(2018, 2, 20, 21, 39, 17, 660912242, time.UTC), }}, Diffs: map[string][]byte{ "all": []byte(diffAll), "d2568fb6f10921b2d0c84d58bad14b2fadb88aa7": []byte(diffCommit1), "61339d441b319cd6ca35d952522f86cc42ad4b6e": []byte(diffCommit2), "3b528c98b05508322be465a207f5ffd8258b8a96": []byte(diffCommit3), }, }, }}, "dmitri.shuralyov.com/gpu/mtl": {{ Change: change.Change{ ID: 1, State: change.OpenState, Title: "WIP: Add minimal API to support rendering to a window at 60 FPS.", Labels: nil, Author: dmitshur, CreatedAt: time.Date(2018, 10, 17, 2, 9, 9, 583606000, time.UTC), Replies: 0, Commits: 1, ChangedFiles: 4, }, Timeline: []interface{}{ change.Comment{ ID: "0", User: dmitshur, CreatedAt: time.Date(2018, 10, 17, 2, 9, 9, 583606000, 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. A new movingtriangle example is added as a demonstration of this 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.`, }, }, Commits: []change.Commit{{ SHA: "fc76fa8984fb4a28ff383895e55e635e06bd32f0", Message: `WIP: Add minimal API to support rendering to a window at 60 FPS. 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. A new movingtriangle example is added as a demonstration of this 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), }}, Diffs: map[string][]byte{ "all": []byte(diffMtlAll), "fc76fa8984fb4a28ff383895e55e635e06bd32f0": []byte(diffMtlCommit1), }, }}, }, } // List changes. func (*Service) List(ctx context.Context, repo string, opt change.ListOptions) ([]change.Change, error) { @@ -153,15 +204,12 @@ func (*Service) List(ctx context.Context, repo string, opt change.ListOptions) ( counts = func(s change.State) bool { return true } default: // TODO: Map to 400 Bad Request HTTP error. return nil, fmt.Errorf("invalid change.ListOptions.Filter value: %q", opt.Filter) } if repo != "dmitri.shuralyov.com/font/woff2" { return nil, nil } var cs []change.Change for _, c := range s.changes { for _, c := range s.changes[repo] { if !counts(c.State) { continue } cs = append(cs, c.Change) } @@ -180,81 +228,87 @@ func (*Service) Count(ctx context.Context, repo string, opt change.ListOptions) counts = func(s change.State) bool { return true } default: // TODO: Map to 400 Bad Request HTTP error. return 0, fmt.Errorf("invalid change.ListOptions.Filter value: %q", opt.Filter) } if repo != "dmitri.shuralyov.com/font/woff2" { return 0, nil } var count uint64 for _, c := range s.changes { for _, c := range s.changes[repo] { if !counts(c.State) { continue } count++ } return count, nil } // Get a change. func (*Service) Get(ctx context.Context, repo string, id uint64) (change.Change, error) { if repo != "dmitri.shuralyov.com/font/woff2" || id != 1 { if !hasChange(repo, id) { return change.Change{}, os.ErrNotExist } return s.changes[0].Change, nil return s.changes[repo][id-1].Change, nil } // ListTimeline lists timeline items (change.Comment, change.Review, change.TimelineItem) for specified change id. func (svc *Service) ListTimeline(ctx context.Context, repo string, id uint64, opt *change.ListTimelineOptions) ([]interface{}, error) { if repo != "dmitri.shuralyov.com/font/woff2" || id != 1 { if !hasChange(repo, id) { return nil, os.ErrNotExist } if svc.Reactions == nil { return s.changes[0].Timeline, nil return s.changes[repo][id-1].Timeline, nil } reactions, err := svc.Reactions.List(ctx, repo) if err != nil { return nil, fmt.Errorf("ListTimeline: Reactions.List: %v", err) } timeline := make([]interface{}, len(s.changes[0].Timeline)) copy(timeline, s.changes[0].Timeline) { t := timeline[0].(change.Comment) t.Reactions = reactions[t.ID] timeline[0] = t } { t := timeline[1].(change.Review) t.Reactions = reactions[t.ID] timeline[1] = t timeline := make([]interface{}, len(s.changes[repo][id-1].Timeline)) copy(timeline, s.changes[repo][id-1].Timeline) switch { case repo == "dmitri.shuralyov.com/font/woff2" && id == 1: { t := timeline[0].(change.Comment) t.Reactions = reactions[t.ID] timeline[0] = t } { t := timeline[1].(change.Review) t.Reactions = reactions[t.ID] timeline[1] = t } case repo == "dmitri.shuralyov.com/gpu/mtl" && id == 1: { t := timeline[0].(change.Comment) t.Reactions = reactions[t.ID] timeline[0] = t } } return timeline, nil } // ListCommits lists change commits. func (*Service) ListCommits(ctx context.Context, repo string, id uint64) ([]change.Commit, error) { if repo != "dmitri.shuralyov.com/font/woff2" || id != 1 { if !hasChange(repo, id) { return nil, os.ErrNotExist } return s.changes[0].Commits, nil return s.changes[repo][id-1].Commits, nil } // Get a change diff. func (*Service) GetDiff(ctx context.Context, repo string, id uint64, opt *change.GetDiffOptions) ([]byte, error) { if repo != "dmitri.shuralyov.com/font/woff2" || id != 1 { if !hasChange(repo, id) { return nil, os.ErrNotExist } switch opt { case nil: return s.changes[0].Diffs["all"], nil return s.changes[repo][id-1].Diffs["all"], nil default: return s.changes[0].Diffs[opt.Commit], nil return s.changes[repo][id-1].Diffs[opt.Commit], nil } } func (s *Service) EditComment(ctx context.Context, repo string, id uint64, cr change.CommentRequest) (change.Comment, error) { if repo != "dmitri.shuralyov.com/font/woff2" || id != 1 { if !hasChange(repo, id) { return change.Comment{}, os.ErrNotExist } if s.Reactions == nil { return change.Comment{}, fmt.Errorf("no place on backend to store reactions") } @@ -267,22 +321,26 @@ func (s *Service) EditComment(ctx context.Context, repo string, id uint64, cr ch comment.Reactions = reactions } return comment, nil } func hasChange(repo string, id uint64) bool { return 1 <= id && id <= uint64(len(s.changes[repo])) } // threadType is the notifications thread type for this service. const threadType = "Change" // ThreadType returns the notifications thread type for this service. func (*Service) ThreadType(repo string) string { return threadType } var shurcool = users.User{ var dmitshur = users.User{ UserSpec: users.UserSpec{ ID: 1924134, Domain: "github.com", }, Login: "shurcooL", Login: "dmitshur", Name: "Dmitri Shuralyov", Email: "dmitri@shuralyov.com", AvatarURL: "https://dmitri.shuralyov.com/avatar.jpg", HTMLURL: "https://dmitri.shuralyov.com", SiteAdmin: true, @@ -1696,5 +1754,595 @@ index 498a4a8..0004d27 100644 + // and made more precise (i.e., the beginning of those blocks must be 4-byte aligned, etc.). n, err := io.Copy(discardZeroes{}, r) if err != nil { return File{}, fmt.Errorf("Parse: %v", err) ` const diffMtlAll = `diff --git a/Commit Message b/Commit Message new file mode 100644 index 0000000..dfb31fe --- /dev/null +++ b/Commit Message @@ -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 + +WIP: Add minimal API to support rendering to a window at 60 FPS. + +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. + +A new movingtriangle example is added as a demonstration of this +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. ` + diffMtlCommit1 const diffMtlCommit1 = `commit fc76fa8984fb4a28ff383895e55e635e06bd32f0 Author: Dmitri Shuralyov <dmitri@shuralyov.com> Date: Sat Jun 23 01:07:53 2018 -0400 WIP: Add minimal API to support rendering to a window at 60 FPS. 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. A new movingtriangle example is added as a demonstration of this 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. 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) { `