dmitri.shuralyov.com/service/change/...

fs: Add an open change for mtl package.

Still hard-coded mock data for now.

Update shurcool user to dmitshur.
dmitshur committed 6 years ago commit f0c4aee46f480a95d2b38fca1cde98f16466e592
Collapse all
fs/fs.go
@@ -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) {
`