dmitri.shuralyov.com/gpu/mtl

add minimal API to support interactive rendering in a window dmitri.shuralyov.com/gpu/mtl#1

Mergeddmitshur opened this change 6 years ago
Collapse all
Commit Message
FileFile
@@ -0,0 +1,22 @@
1
Parent:     7a718d8 (Add PixelFormatBGRA8UNormSRGB, SetVertexBytes.)
2
Author:     Dmitri Shuralyov <dmitri@shuralyov.com>
3
AuthorDate: Sat Jun 23 01:07:53 2018 -0400
4
Commit:     Dmitri Shuralyov <dmitri@shuralyov.com>
5
CommitDate: Mon Oct 22 03:12:01 2018 -0400
6

7
Add minimal API to support interactive rendering in a window.
8

9
The goal of this change is to make it possible to use package mtl
10
to render to a window at interactive framerates (e.g., at 60 FPS,
11
assuming a 60 Hz display with vsync on). It adds the minimal API
12
that is needed.
13

14
A new movingtriangle example is added as a demonstration of this
15
functionality. It opens a window and renders a triangle that follows
16
the mouse cursor.
17

18
Much of the needed API comes from Core Animation, AppKit frameworks,
19
rather than Metal. Avoid adding that to mtl package; instead create
20
separate packages. For now, they are hidden in internal to avoid
21
committing to a public API and import path. After gaining more
22
confidence in the approach, they can be factored out and made public.
example/movingtriangle/internal/ca/ca.go
FileFile
@@ -0,0 +1,137 @@
1
// +build darwin
2

3
// Package ca provides access to Apple's Core Animation API (https://developer.apple.com/documentation/quartzcore).
4
//
5
// This package is in very early stages of development.
6
// It's a minimal implementation with scope limited to
7
// supporting the movingtriangle example.
8
package ca
9

10
import (
11
	"errors"
12
	"unsafe"
13

14
	"dmitri.shuralyov.com/gpu/mtl"
15
)
16

17
/*
18
#cgo LDFLAGS: -framework QuartzCore -framework Foundation
19
#include "ca.h"
20
*/
21
import "C"
22

23
// Layer is an object that manages image-based content and
24
// allows you to perform animations on that content.
25
//
26
// Reference: https://developer.apple.com/documentation/quartzcore/calayer.
27
type Layer interface {
28
	// Layer returns the underlying CALayer * pointer.
29
	Layer() unsafe.Pointer
30
}
31

32
// MetalLayer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables.
33
//
34
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
35
type MetalLayer struct {
36
	metalLayer unsafe.Pointer
37
}
38

39
// MakeMetalLayer creates a new Core Animation Metal layer.
40
//
41
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
42
func MakeMetalLayer() MetalLayer {
43
	return MetalLayer{C.MakeMetalLayer()}
44
}
45

46
// Layer implements the Layer interface.
47
func (ml MetalLayer) Layer() unsafe.Pointer { return ml.metalLayer }
48

49
// PixelFormat returns the pixel format of textures for rendering layer content.
50
//
51
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
52
func (ml MetalLayer) PixelFormat() mtl.PixelFormat {
53
	return mtl.PixelFormat(C.MetalLayer_PixelFormat(ml.metalLayer))
54
}
55

56
// SetDevice sets the Metal device responsible for the layer's drawable resources.
57
//
58
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device.
59
func (ml MetalLayer) SetDevice(device mtl.Device) {
60
	C.MetalLayer_SetDevice(ml.metalLayer, device.Device())
61
}
62

63
// SetPixelFormat controls the pixel format of textures for rendering layer content.
64
//
65
// The pixel format for a Metal layer must be PixelFormatBGRA8UNorm, PixelFormatBGRA8UNormSRGB,
66
// PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB.
67
// SetPixelFormat panics for other values.
68
//
69
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
70
func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
71
	e := C.MetalLayer_SetPixelFormat(ml.metalLayer, C.uint16_t(pf))
72
	if e != nil {
73
		panic(errors.New(C.GoString(e)))
74
	}
75
}
76

77
// SetMaximumDrawableCount controls the number of Metal drawables in the resource pool
78
// managed by Core Animation.
79
//
80
// It can set to 2 or 3 only. SetMaximumDrawableCount panics for other values.
81
//
82
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount.
83
func (ml MetalLayer) SetMaximumDrawableCount(count int) {
84
	e := C.MetalLayer_SetMaximumDrawableCount(ml.metalLayer, C.uint_t(count))
85
	if e != nil {
86
		panic(errors.New(C.GoString(e)))
87
	}
88
}
89

90
// SetDisplaySyncEnabled controls whether the Metal layer and its drawables
91
// are synchronized with the display's refresh rate.
92
//
93
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled.
94
func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
95
	switch enabled {
96
	case true:
97
		C.MetalLayer_SetDisplaySyncEnabled(ml.metalLayer, 1)
98
	case false:
99
		C.MetalLayer_SetDisplaySyncEnabled(ml.metalLayer, 0)
100
	}
101
}
102

103
// SetDrawableSize sets the size, in pixels, of textures for rendering layer content.
104
//
105
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize.
106
func (ml MetalLayer) SetDrawableSize(width, height int) {
107
	C.MetalLayer_SetDrawableSize(ml.metalLayer, C.double(width), C.double(height))
108
}
109

110
// NextDrawable returns a Metal drawable.
111
//
112
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable.
113
func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
114
	md := C.MetalLayer_NextDrawable(ml.metalLayer)
115
	if md == nil {
116
		return MetalDrawable{}, errors.New("nextDrawable returned nil")
117
	}
118

119
	return MetalDrawable{md}, nil
120
}
121

122
// MetalDrawable is a displayable resource that can be rendered or written to by Metal.
123
//
124
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable.
125
type MetalDrawable struct {
126
	metalDrawable unsafe.Pointer
127
}
128

129
// Drawable implements the mtl.Drawable interface.
130
func (md MetalDrawable) Drawable() unsafe.Pointer { return md.metalDrawable }
131

132
// Texture returns a Metal texture object representing the drawable object's content.
133
//
134
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture.
135
func (md MetalDrawable) Texture() mtl.Texture {
136
	return mtl.NewTexture(C.MetalDrawable_Texture(md.metalDrawable))
137
}
example/movingtriangle/internal/ca/ca.h
FileFile
@@ -0,0 +1,17 @@
1
// +build darwin
2

3
typedef signed char BOOL;
4
typedef unsigned long uint_t;
5
typedef unsigned short uint16_t;
6

7
void * MakeMetalLayer();
8

9
uint16_t     MetalLayer_PixelFormat(void * metalLayer);
10
void         MetalLayer_SetDevice(void * metalLayer, void * device);
11
const char * MetalLayer_SetPixelFormat(void * metalLayer, uint16_t pixelFormat);
12
const char * MetalLayer_SetMaximumDrawableCount(void * metalLayer, uint_t maximumDrawableCount);
13
void         MetalLayer_SetDisplaySyncEnabled(void * metalLayer, BOOL displaySyncEnabled);
14
void         MetalLayer_SetDrawableSize(void * metalLayer, double width, double height);
15
void *       MetalLayer_NextDrawable(void * metalLayer);
16

17
void * MetalDrawable_Texture(void * drawable);
example/movingtriangle/internal/ca/ca.m
FileFile
@@ -0,0 +1,54 @@
1
// +build darwin
2

3
#import <QuartzCore/QuartzCore.h>
4
#include "ca.h"
5

6
void * MakeMetalLayer() {
7
	return [[CAMetalLayer alloc] init];
8
}
9

10
uint16_t MetalLayer_PixelFormat(void * metalLayer) {
11
	return ((CAMetalLayer *)metalLayer).pixelFormat;
12
}
13

14
void MetalLayer_SetDevice(void * metalLayer, void * device) {
15
	((CAMetalLayer *)metalLayer).device = (id<MTLDevice>)device;
16
}
17

18
const char * MetalLayer_SetPixelFormat(void * metalLayer, uint16_t pixelFormat) {
19
	@try {
20
		((CAMetalLayer *)metalLayer).pixelFormat = (MTLPixelFormat)pixelFormat;
21
	}
22
	@catch (NSException * exception) {
23
		return exception.reason.UTF8String;
24
	}
25
	return NULL;
26
}
27

28
const char * MetalLayer_SetMaximumDrawableCount(void * metalLayer, uint_t maximumDrawableCount) {
29
	if (@available(macOS 10.13.2, *)) {
30
		@try {
31
			((CAMetalLayer *)metalLayer).maximumDrawableCount = (NSUInteger)maximumDrawableCount;
32
		}
33
		@catch (NSException * exception) {
34
			return exception.reason.UTF8String;
35
		}
36
	}
37
	return NULL;
38
}
39

40
void MetalLayer_SetDisplaySyncEnabled(void * metalLayer, BOOL displaySyncEnabled) {
41
	((CAMetalLayer *)metalLayer).displaySyncEnabled = displaySyncEnabled;
42
}
43

44
void MetalLayer_SetDrawableSize(void * metalLayer, double width, double height) {
45
	((CAMetalLayer *)metalLayer).drawableSize = (CGSize){width, height};
46
}
47

48
void * MetalLayer_NextDrawable(void * metalLayer) {
49
	return [(CAMetalLayer *)metalLayer nextDrawable];
50
}
51

52
void * MetalDrawable_Texture(void * metalDrawable) {
53
	return ((id<CAMetalDrawable>)metalDrawable).texture;
54
}
example/movingtriangle/internal/ns/ns.go
FileFile
@@ -0,0 +1,65 @@
1
// +build darwin
2

3
// Package ns provides access to Apple's AppKit API (https://developer.apple.com/documentation/appkit).
4
//
5
// This package is in very early stages of development.
6
// It's a minimal implementation with scope limited to
7
// supporting the movingtriangle example.
8
package ns
9

10
import (
11
	"unsafe"
12

13
	"dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ca"
14
)
15

16
/*
17
#include "ns.h"
18
*/
19
import "C"
20

21
// Window is a window that an app displays on the screen.
22
//
23
// Reference: https://developer.apple.com/documentation/appkit/nswindow.
24
type Window struct {
25
	window unsafe.Pointer
26
}
27

28
// NewWindow returns a Window that wraps an existing NSWindow * pointer.
29
func NewWindow(window unsafe.Pointer) Window {
30
	return Window{window}
31
}
32

33
// ContentView returns the window's content view, the highest accessible View
34
// in the window's view hierarchy.
35
//
36
// Reference: https://developer.apple.com/documentation/appkit/nswindow/1419160-contentview.
37
func (w Window) ContentView() View {
38
	return View{C.Window_ContentView(w.window)}
39
}
40

41
// View is the infrastructure for drawing, printing, and handling events in an app.
42
//
43
// Reference: https://developer.apple.com/documentation/appkit/nsview.
44
type View struct {
45
	view unsafe.Pointer
46
}
47

48
// SetLayer sets v.layer to l.
49
//
50
// Reference: https://developer.apple.com/documentation/appkit/nsview/1483298-layer.
51
func (v View) SetLayer(l ca.Layer) {
52
	C.View_SetLayer(v.view, l.Layer())
53
}
54

55
// SetWantsLayer sets v.wantsLayer to wantsLayer.
56
//
57
// Reference: https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer.
58
func (v View) SetWantsLayer(wantsLayer bool) {
59
	switch wantsLayer {
60
	case true:
61
		C.View_SetWantsLayer(v.view, 1)
62
	case false:
63
		C.View_SetWantsLayer(v.view, 0)
64
	}
65
}
example/movingtriangle/internal/ns/ns.h
FileFile
@@ -0,0 +1,8 @@
1
// +build darwin
2

3
typedef signed char BOOL;
4

5
void * Window_ContentView(void * window);
6

7
void View_SetLayer(void * view, void * layer);
8
void View_SetWantsLayer(void * view, BOOL wantsLayer);
example/movingtriangle/internal/ns/ns.m
FileFile
@@ -0,0 +1,16 @@
1
// +build darwin
2

3
#import <Cocoa/Cocoa.h>
4
#include "ns.h"
5

6
void * Window_ContentView(void * window) {
7
	return ((NSWindow *)window).contentView;
8
}
9

10
void View_SetLayer(void * view, void * layer) {
11
	((NSView *)view).layer = (CALayer *)layer;
12
}
13

14
void View_SetWantsLayer(void * view, BOOL wantsLayer) {
15
	((NSView *)view).wantsLayer = wantsLayer;
16
}
example/movingtriangle/main.go
FileFile
@@ -0,0 +1,198 @@
1
// +build darwin
2

3
// movingtriangle is an example Metal program that displays a moving triangle in a window.
4
// It opens a window and renders a triangle that follows the mouse cursor.
5
package main
6

7
import (
8
	"flag"
9
	"fmt"
10
	"log"
11
	"os"
12
	"runtime"
13
	"time"
14
	"unsafe"
15

16
	"dmitri.shuralyov.com/gpu/mtl"
17
	"dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ca"
18
	"dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/ns"
19
	"github.com/go-gl/glfw/v3.2/glfw"
20
	"golang.org/x/image/math/f32"
21
)
22

23
func init() {
24
	runtime.LockOSThread()
25
}
26

27
func main() {
28
	flag.Usage = func() {
29
		fmt.Fprintln(os.Stderr, "Usage: movingtriangle")
30
		flag.PrintDefaults()
31
	}
32
	flag.Parse()
33

34
	err := run()
35
	if err != nil {
36
		log.Fatalln(err)
37
	}
38
}
39

40
func run() error {
41
	device, err := mtl.CreateSystemDefaultDevice()
42
	if err != nil {
43
		return err
44
	}
45
	fmt.Println("Metal device:", device.Name)
46

47
	err = glfw.Init()
48
	if err != nil {
49
		return err
50
	}
51
	defer glfw.Terminate()
52

53
	glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI)
54
	window, err := glfw.CreateWindow(640, 480, "Metal Example", nil, nil)
55
	if err != nil {
56
		return err
57
	}
58
	defer window.Destroy()
59

60
	ml := ca.MakeMetalLayer()
61
	ml.SetDevice(device)
62
	ml.SetPixelFormat(mtl.PixelFormatBGRA8UNorm)
63
	ml.SetDrawableSize(window.GetFramebufferSize())
64
	ml.SetMaximumDrawableCount(3)
65
	ml.SetDisplaySyncEnabled(true)
66
	cocoaWindow := ns.NewWindow(unsafe.Pointer(window.GetCocoaWindow()))
67
	cocoaWindow.ContentView().SetLayer(ml)
68
	cocoaWindow.ContentView().SetWantsLayer(true)
69

70
	// Set callbacks.
71
	window.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) {
72
		ml.SetDrawableSize(width, height)
73
	})
74
	var windowSize = [2]int32{640, 480}
75
	window.SetSizeCallback(func(_ *glfw.Window, width, height int) {
76
		windowSize[0], windowSize[1] = int32(width), int32(height)
77
	})
78
	var pos [2]float32
79
	window.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) {
80
		pos[0], pos[1] = float32(x), float32(y)
81
	})
82

83
	// Create a render pipeline state.
84
	const source = `#include <metal_stdlib>
85

86
using namespace metal;
87

88
struct Vertex {
89
	float4 position [[position]];
90
	float4 color;
91
};
92

93
vertex Vertex VertexShader(
94
	uint vertexID [[vertex_id]],
95
	device Vertex * vertices [[buffer(0)]],
96
	constant int2 * windowSize [[buffer(1)]],
97
	constant float2 * pos [[buffer(2)]]
98
) {
99
	Vertex out = vertices[vertexID];
100
	out.position.xy += *pos;
101
	float2 viewportSize = float2(*windowSize);
102
	out.position.xy = float2(-1 + out.position.x / (0.5 * viewportSize.x),
103
	                          1 - out.position.y / (0.5 * viewportSize.y));
104
	return out;
105
}
106

107
fragment float4 FragmentShader(Vertex in [[stage_in]]) {
108
	return in.color;
109
}
110
`
111
	lib, err := device.MakeLibrary(source, mtl.CompileOptions{})
112
	if err != nil {
113
		return err
114
	}
115
	vs, err := lib.MakeFunction("VertexShader")
116
	if err != nil {
117
		return err
118
	}
119
	fs, err := lib.MakeFunction("FragmentShader")
120
	if err != nil {
121
		return err
122
	}
123
	var rpld mtl.RenderPipelineDescriptor
124
	rpld.VertexFunction = vs
125
	rpld.FragmentFunction = fs
126
	rpld.ColorAttachments[0].PixelFormat = ml.PixelFormat()
127
	rps, err := device.MakeRenderPipelineState(rpld)
128
	if err != nil {
129
		return err
130
	}
131

132
	// Create a vertex buffer.
133
	type Vertex struct {
134
		Position f32.Vec4
135
		Color    f32.Vec4
136
	}
137
	vertexData := [...]Vertex{
138
		{f32.Vec4{0, 0, 0, 1}, f32.Vec4{1, 0, 0, 1}},
139
		{f32.Vec4{300, 100, 0, 1}, f32.Vec4{0, 1, 0, 1}},
140
		{f32.Vec4{0, 100, 0, 1}, f32.Vec4{0, 0, 1, 1}},
141
	}
142
	vertexBuffer := device.MakeBuffer(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged)
143

144
	cq := device.MakeCommandQueue()
145

146
	frame := startFPSCounter()
147

148
	for !window.ShouldClose() {
149
		glfw.PollEvents()
150

151
		// Create a drawable to render into.
152
		drawable, err := ml.NextDrawable()
153
		if err != nil {
154
			return err
155
		}
156

157
		cb := cq.MakeCommandBuffer()
158

159
		// Encode all render commands.
160
		var rpd mtl.RenderPassDescriptor
161
		rpd.ColorAttachments[0].LoadAction = mtl.LoadActionClear
162
		rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
163
		rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{Red: 0.35, Green: 0.65, Blue: 0.85, Alpha: 1}
164
		rpd.ColorAttachments[0].Texture = drawable.Texture()
165
		rce := cb.MakeRenderCommandEncoder(rpd)
166
		rce.SetRenderPipelineState(rps)
167
		rce.SetVertexBuffer(vertexBuffer, 0, 0)
168
		rce.SetVertexBytes(unsafe.Pointer(&windowSize[0]), unsafe.Sizeof(windowSize), 1)
169
		rce.SetVertexBytes(unsafe.Pointer(&pos[0]), unsafe.Sizeof(pos), 2)
170
		rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3)
171
		rce.EndEncoding()
172

173
		cb.PresentDrawable(drawable)
174
		cb.Commit()
175

176
		frame <- struct{}{}
177
	}
178

179
	return nil
180
}
181

182
func startFPSCounter() chan struct{} {
183
	frame := make(chan struct{}, 4)
184
	go func() {
185
		second := time.Tick(time.Second)
186
		frames := 0
187
		for {
188
			select {
189
			case <-second:
190
				fmt.Println("fps:", frames)
191
				frames = 0
192
			case <-frame:
193
				frames++
194
			}
195
		}
196
	}()
197
	return frame
198
}
mtl.go
FileFile
@@ -191,10 +191,11 @@ const (
191191
// Resource represents a memory allocation for storing specialized data
192192
// that is accessible to the GPU.
193193
//
194194
// Reference: https://developer.apple.com/documentation/metal/mtlresource.
195195
type Resource interface {
196
	// resource returns the underlying id<MTLResource> pointer.
196197
	resource() unsafe.Pointer
197198
}
198199

199200
// RenderPipelineDescriptor configures new RenderPipelineState objects.
200201
//
@@ -325,10 +326,13 @@ func CopyAllDevices() []Device {
325326
		ds[i].Name = C.GoString(d.Name)
326327
	}
327328
	return ds
328329
}
329330

331
// Device returns the underlying id<MTLDevice> pointer.
332
func (d Device) Device() unsafe.Pointer { return d.device }
333

330334
// SupportsFeatureSet reports whether device d supports feature set fs.
331335
//
332336
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset.
333337
func (d Device) SupportsFeatureSet(fs FeatureSet) bool {
334338
	return C.Device_SupportsFeatureSet(d.device, C.uint16_t(fs)) != 0
@@ -403,10 +407,18 @@ func (d Device) MakeTexture(td TextureDescriptor) Texture {
403407
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions.
404408
type CompileOptions struct {
405409
	// TODO.
406410
}
407411

412
// Drawable is a displayable resource that can be rendered or written to.
413
//
414
// Reference: https://developer.apple.com/documentation/metal/mtldrawable.
415
type Drawable interface {
416
	// Drawable returns the underlying id<MTLDrawable> pointer.
417
	Drawable() unsafe.Pointer
418
}
419

408420
// CommandQueue is a queue that organizes the order
409421
// in which command buffers are executed by the GPU.
410422
//
411423
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue.
412424
type CommandQueue struct {
@@ -426,10 +438,17 @@ func (cq CommandQueue) MakeCommandBuffer() CommandBuffer {
426438
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer.
427439
type CommandBuffer struct {
428440
	commandBuffer unsafe.Pointer
429441
}
430442

443
// PresentDrawable registers a drawable presentation to occur as soon as possible.
444
//
445
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable.
446
func (cb CommandBuffer) PresentDrawable(d Drawable) {
447
	C.CommandBuffer_PresentDrawable(cb.commandBuffer, d.Drawable())
448
}
449

431450
// Commit commits this command buffer for execution as soon as possible.
432451
//
433452
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit.
434453
func (cb CommandBuffer) Commit() {
435454
	C.CommandBuffer_Commit(cb.commandBuffer)
@@ -562,17 +581,25 @@ func (l Library) MakeFunction(name string) (Function, error) {
562581
//
563582
// Reference: https://developer.apple.com/documentation/metal/mtltexture.
564583
type Texture struct {
565584
	texture unsafe.Pointer
566585

586
	// TODO: Change these fields into methods.
587

567588
	// Width is the width of the texture image for the base level mipmap, in pixels.
568589
	Width int
569590

570591
	// Height is the height of the texture image for the base level mipmap, in pixels.
571592
	Height int
572593
}
573594

595
// NewTexture returns a Texture that wraps an existing id<MTLTexture> pointer.
596
func NewTexture(texture unsafe.Pointer) Texture {
597
	return Texture{texture: texture}
598
}
599

600
// resource implements the Resource interface.
574601
func (t Texture) resource() unsafe.Pointer { return t.texture }
575602

576603
// GetBytes copies a block of pixels from the storage allocation of texture
577604
// slice zero into system memory at a specified address.
578605
//
mtl.h
FileFile
@@ -84,10 +84,11 @@ struct RenderPipelineState Device_MakeRenderPipelineState(void * device, struct
8484
void *                     Device_MakeBuffer(void * device, const void * bytes, size_t length, uint16_t options);
8585
void *                     Device_MakeTexture(void * device, struct TextureDescriptor descriptor);
8686

8787
void * CommandQueue_MakeCommandBuffer(void * commandQueue);
8888

89
void   CommandBuffer_PresentDrawable(void * commandBuffer, void * drawable);
8990
void   CommandBuffer_Commit(void * commandBuffer);
9091
void   CommandBuffer_WaitUntilCompleted(void * commandBuffer);
9192
void * CommandBuffer_MakeRenderCommandEncoder(void * commandBuffer, struct RenderPassDescriptor descriptor);
9293
void * CommandBuffer_MakeBlitCommandEncoder(void * commandBuffer);
9394

mtl.m
FileFile
@@ -98,10 +98,14 @@ void * Device_MakeTexture(void * device, struct TextureDescriptor descriptor) {
9898

9999
void * CommandQueue_MakeCommandBuffer(void * commandQueue) {
100100
	return [(id<MTLCommandQueue>)commandQueue commandBuffer];
101101
}
102102

103
void CommandBuffer_PresentDrawable(void * commandBuffer, void * drawable) {
104
	[(id<MTLCommandBuffer>)commandBuffer presentDrawable:(id<MTLDrawable>)drawable];
105
}
106

103107
void CommandBuffer_Commit(void * commandBuffer) {
104108
	[(id<MTLCommandBuffer>)commandBuffer commit];
105109
}
106110

107111
void CommandBuffer_WaitUntilCompleted(void * commandBuffer) {