dmitri.shuralyov.com/service/change/fs

Finish and merge the mtl change.

Still manually crafted in order to prototype and learn
about this space. It's still not completely clear how to
best integrate patch sets vs multiple commits, the commit
messages, and how git client fits into the picture.

The end result can be seen at
https://dmitri.shuralyov.com/gpu/mtl/...$changes/1.
dmitshur committed 6 years ago commit a85b471d5412b6580fdd6bdbaedc2365efe16812
Showing partial commit. Full Commit
Collapse all
fs/fs.go
@@ -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"
 )
`