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
Patch Set 1
dmitshur committed 6 years ago commit 10618a0ec1a82037a197f586773eddace272daf4
Collapse all
Commit Message
FileFile
@@ -0,0 +1,18 @@
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: Tue Oct 16 22:09:09 2018 -0400
6

7
WIP: Add minimal API to support rendering to a window at 60 FPS.
8

9
The goal of this change is to make it possible to use package mtl
10
to render to a window at 60 FPS. It tries to add the minimum viable
11
API that is needed.
12

13
A new movingtriangle example is added as a demonstration of this
14
functionality. It renders a triangle that follows the mouse cursor.
15

16
TODO: A lot of the newly added API comes from Core Animation, AppKit
17
frameworks, rather than Metal. As a result, they likely do not belong
18
in this package and should be factored out.
example/movingtriangle/main.go
FileFile
@@ -0,0 +1,196 @@
1
// +build darwin
2

3
// movingtriangle is an example Metal program that displays a moving triangle in a window.
4
package main
5

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

15
	"dmitri.shuralyov.com/gpu/mtl"
16
	"github.com/go-gl/glfw/v3.2/glfw"
17
	"golang.org/x/image/math/f32"
18
)
19

20
func usage() {
21
	fmt.Fprintln(os.Stderr, "Usage: movingtriangle")
22
	flag.PrintDefaults()
23
}
24

25
func init() {
26
	runtime.LockOSThread()
27
}
28

29
func main() {
30
	flag.Usage = usage
31
	flag.Parse()
32

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

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

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

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

59
	layer := mtl.MakeLayer()
60
	layer.SetDevice(device)
61
	layer.SetPixelFormat(mtl.PixelFormatBGRA8UNorm)
62
	layer.SetDrawableSize(window.GetFramebufferSize())
63
	layer.SetMaximumDrawableCount(3)
64
	layer.SetDisplaySyncEnabled(true)
65
	mtl.SetWindowContentViewLayer(window.GetCocoaWindow(), layer)
66
	mtl.SetWindowContentViewWantsLayer(window.GetCocoaWindow(), true)
67

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

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

84
using namespace metal;
85

86
struct Vertex {
87
	float4 position [[position]];
88
	float4 color;
89
};
90

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

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

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

142
	cq := device.MakeCommandQueue()
143

144
	frame := startFPSCounter()
145

146
	for !window.ShouldClose() {
147
		glfw.PollEvents()
148

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

155
		cb := cq.MakeCommandBuffer()
156

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

171
		cb.Present(drawable)
172
		cb.Commit()
173

174
		frame <- struct{}{}
175
	}
176

177
	return nil
178
}
179

180
func startFPSCounter() chan struct{} {
181
	frame := make(chan struct{}, 4)
182
	go func() {
183
		second := time.Tick(time.Second)
184
		frames := 0
185
		for {
186
			select {
187
			case <-second:
188
				fmt.Println("fps:", frames)
189
				frames = 0
190
			case <-frame:
191
				frames++
192
			}
193
		}
194
	}()
195
	return frame
196
}
mtl.go
FileFile
@@ -13,19 +13,137 @@ import (
1313
	"fmt"
1414
	"unsafe"
1515
)
1616

1717
/*
18
#cgo LDFLAGS: -framework Metal -framework Foundation
18
#cgo LDFLAGS: -framework Metal -framework QuartzCore -framework Foundation
1919
#include <stdlib.h>
2020
#include "mtl.h"
2121
struct Library Go_Device_MakeLibrary(void * device, _GoString_ source) {
2222
	return Device_MakeLibrary(device, _GoStringPtr(source), _GoStringLen(source));
2323
}
2424
*/
2525
import "C"
2626

27
// Layer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables.
28
//
29
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
30
type Layer struct {
31
	layer unsafe.Pointer
32
}
33

34
// MakeLayer creates a new Core Animation Metal layer.
35
//
36
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
37
func MakeLayer() Layer {
38
	return Layer{C.MakeLayer()}
39
}
40

41
// PixelFormat returns the pixel format of textures for rendering layer content.
42
//
43
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
44
func (l Layer) PixelFormat() PixelFormat {
45
	return PixelFormat(C.Layer_PixelFormat(l.layer))
46
}
47

48
// SetDevice sets the Metal device responsible for the layer's drawable resources.
49
//
50
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device.
51
func (l Layer) SetDevice(device Device) {
52
	C.Layer_SetDevice(l.layer, device.device)
53
}
54

55
// SetPixelFormat controls the pixel format of textures for rendering layer content.
56
//
57
// The pixel format for a Metal layer must be PixelFormatBGRA8UNorm, PixelFormatBGRA8UNormSRGB,
58
// PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB.
59
// SetPixelFormat panics for other values.
60
func (l Layer) SetPixelFormat(pf PixelFormat) {
61
	e := C.Layer_SetPixelFormat(l.layer, C.uint16_t(pf))
62
	if e != nil {
63
		panic(errors.New(C.GoString(e)))
64
	}
65
}
66

67
// SetMaximumDrawableCount controls the number of Metal drawables in the resource pool
68
// managed by Core Animation.
69
//
70
// It can set to 2 or 3 only. SetMaximumDrawableCount panics for other values.
71
func (l Layer) SetMaximumDrawableCount(count int) {
72
	e := C.Layer_SetMaximumDrawableCount(l.layer, C.uint_t(count))
73
	if e != nil {
74
		panic(errors.New(C.GoString(e)))
75
	}
76
}
77

78
// SetDisplaySyncEnabled controls whether the Metal layer and its drawables
79
// are synchronized with the display's refresh rate.
80
func (l Layer) SetDisplaySyncEnabled(enabled bool) {
81
	switch enabled {
82
	case true:
83
		C.Layer_SetDisplaySyncEnabled(l.layer, 1)
84
	case false:
85
		C.Layer_SetDisplaySyncEnabled(l.layer, 0)
86
	}
87
}
88

89
// SetDrawableSize sets the size, in pixels, of textures for rendering layer content.
90
//
91
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize.
92
func (l Layer) SetDrawableSize(width, height int) {
93
	C.Layer_SetDrawableSize(l.layer, C.double(width), C.double(height))
94
}
95

96
// NextDrawable returns a Metal drawable.
97
//
98
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable.
99
func (l Layer) NextDrawable() (Drawable, error) {
100
	d := C.Layer_NextDrawable(l.layer)
101
	if d == nil {
102
		return Drawable{}, errors.New("nextDrawable returned nil")
103
	}
104

105
	return Drawable{d}, nil
106
}
107

108
// Drawable is a displayable resource that can be rendered or written to.
109
//
110
// Reference: https://developer.apple.com/documentation/metal/mtldrawable.
111
type Drawable struct {
112
	drawable unsafe.Pointer
113
}
114

115
// Texture returns a Metal texture object representing the drawable object's content.
116
//
117
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture.
118
func (d Drawable) Texture() Texture {
119
	return Texture{
120
		texture: C.Drawable_Texture(d.drawable),
121
		Width:   0, // TODO: Fetch dimensions of actually created texture.
122
		Height:  0, // TODO: Fetch dimensions of actually created texture.
123
	}
124
}
125

126
// SetWindowContentViewLayer sets cocoaWindow's contentView's layer to layer.
127
//
128
// Reference: https://developer.apple.com/documentation/appkit/nsview/1483298-layer.
129
func SetWindowContentViewLayer(cocoaWindow uintptr, l Layer) {
130
	C.SetWindowContentViewLayer(unsafe.Pointer(cocoaWindow), l.layer)
131
}
132

133
// SetWindowContentViewWantsLayer sets cocoaWindow's contentView's wantsLayer to wantsLayer.
134
//
135
// Reference: https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer.
136
func SetWindowContentViewWantsLayer(cocoaWindow uintptr, wantsLayer bool) {
137
	switch wantsLayer {
138
	case true:
139
		C.SetWindowContentViewWantsLayer(unsafe.Pointer(cocoaWindow), 1)
140
	case false:
141
		C.SetWindowContentViewWantsLayer(unsafe.Pointer(cocoaWindow), 0)
142
	}
143
}
144

27145
// FeatureSet defines a specific platform, hardware, and software configuration.
28146
//
29147
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset.
30148
type FeatureSet uint16
31149

@@ -426,10 +544,17 @@ func (cq CommandQueue) MakeCommandBuffer() CommandBuffer {
426544
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer.
427545
type CommandBuffer struct {
428546
	commandBuffer unsafe.Pointer
429547
}
430548

549
// Present registers a drawable presentation to occur as soon as possible.
550
//
551
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable.
552
func (cb CommandBuffer) Present(d Drawable) {
553
	C.CommandBuffer_Present(cb.commandBuffer, d.drawable)
554
}
555

431556
// Commit commits this command buffer for execution as soon as possible.
432557
//
433558
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit.
434559
func (cb CommandBuffer) Commit() {
435560
	C.CommandBuffer_Commit(cb.commandBuffer)
mtl.h
FileFile
@@ -72,10 +72,25 @@ struct Size {
7272
struct Region {
7373
	struct Origin Origin;
7474
	struct Size   Size;
7575
};
7676

77
void * MakeLayer();
78

79
uint16_t     Layer_PixelFormat(void * layer);
80
void         Layer_SetDevice(void * layer, void * device);
81
const char * Layer_SetPixelFormat(void * layer, uint16_t pixelFormat);
82
const char * Layer_SetMaximumDrawableCount(void * layer, uint_t maximumDrawableCount);
83
void         Layer_SetDisplaySyncEnabled(void * layer, BOOL displaySyncEnabled);
84
void         Layer_SetDrawableSize(void * layer, double width, double height);
85
void *       Layer_NextDrawable(void * layer);
86

87
void * Drawable_Texture(void * drawable);
88

89
void SetWindowContentViewLayer(void * cocoaWindow, void * layer);
90
void SetWindowContentViewWantsLayer(void * cocoaWindow, BOOL wantsLayer);
91

7792
struct Device CreateSystemDefaultDevice();
7893
struct Devices CopyAllDevices();
7994

8095
BOOL                       Device_SupportsFeatureSet(void * device, uint16_t featureSet);
8196
void *                     Device_MakeCommandQueue(void * device);
@@ -84,10 +99,11 @@ struct RenderPipelineState Device_MakeRenderPipelineState(void * device, struct
8499
void *                     Device_MakeBuffer(void * device, const void * bytes, size_t length, uint16_t options);
85100
void *                     Device_MakeTexture(void * device, struct TextureDescriptor descriptor);
86101

87102
void * CommandQueue_MakeCommandBuffer(void * commandQueue);
88103

104
void   CommandBuffer_Present(void * commandBuffer, void * drawable);
89105
void   CommandBuffer_Commit(void * commandBuffer);
90106
void   CommandBuffer_WaitUntilCompleted(void * commandBuffer);
91107
void * CommandBuffer_MakeRenderCommandEncoder(void * commandBuffer, struct RenderPassDescriptor descriptor);
92108
void * CommandBuffer_MakeBlitCommandEncoder(void * commandBuffer);
93109

mtl.m
FileFile
@@ -1,11 +1,73 @@
11
// +build darwin
22

33
#import <Metal/Metal.h>
4
#import <QuartzCore/QuartzCore.h>
5
#import <Cocoa/Cocoa.h>
6

47
#include <stdlib.h>
8

59
#include "mtl.h"
610

11
void * MakeLayer() {
12
	return [[CAMetalLayer alloc] init];
13
}
14

15
uint16_t Layer_PixelFormat(void * layer) {
16
	return ((CAMetalLayer *)layer).pixelFormat;
17
}
18

19
void Layer_SetDevice(void * layer, void * device) {
20
	((CAMetalLayer *)layer).device = (id<MTLDevice>)device;
21
}
22

23
const char * Layer_SetPixelFormat(void * layer, uint16_t pixelFormat) {
24
	@try {
25
		((CAMetalLayer *)layer).pixelFormat = (MTLPixelFormat)pixelFormat;
26
	}
27
	@catch (NSException * exception) {
28
		return exception.reason.UTF8String;
29
	}
30
	return NULL;
31
}
32

33
const char * Layer_SetMaximumDrawableCount(void * layer, uint_t maximumDrawableCount) {
34
	if (@available(macOS 10.13.2, *)) {
35
		@try {
36
			((CAMetalLayer *)layer).maximumDrawableCount = (NSUInteger)maximumDrawableCount;
37
		}
38
		@catch (NSException * exception) {
39
			return exception.reason.UTF8String;
40
		}
41
	}
42
	return NULL;
43
}
44

45
void Layer_SetDisplaySyncEnabled(void * layer, BOOL displaySyncEnabled) {
46
	((CAMetalLayer *)layer).displaySyncEnabled = displaySyncEnabled;
47
}
48

49
void Layer_SetDrawableSize(void * layer, double width, double height) {
50
	((CAMetalLayer *)layer).drawableSize = (CGSize){width, height};
51
}
52

53
void * Layer_NextDrawable(void * layer) {
54
	return [(CAMetalLayer *)layer nextDrawable];
55
}
56

57
void * Drawable_Texture(void * drawable) {
58
	return ((id<CAMetalDrawable>)drawable).texture;
59
}
60

61
void SetWindowContentViewLayer(void * cocoaWindow, void * layer) {
62
	((NSWindow *)cocoaWindow).contentView.layer = (CAMetalLayer *)layer;
63
}
64

65
void SetWindowContentViewWantsLayer(void * cocoaWindow, BOOL wantsLayer) {
66
	((NSWindow *)cocoaWindow).contentView.wantsLayer = wantsLayer;
67
}
68

769
struct Device CreateSystemDefaultDevice() {
870
	id<MTLDevice> device = MTLCreateSystemDefaultDevice();
971
	if (!device) {
1072
		struct Device d;
1173
		d.Device = NULL;
@@ -98,10 +160,14 @@ void * Device_MakeTexture(void * device, struct TextureDescriptor descriptor) {
98160

99161
void * CommandQueue_MakeCommandBuffer(void * commandQueue) {
100162
	return [(id<MTLCommandQueue>)commandQueue commandBuffer];
101163
}
102164

165
void CommandBuffer_Present(void * commandBuffer, void * drawable) {
166
	[(id<MTLCommandBuffer>)commandBuffer presentDrawable:(id<CAMetalDrawable>)drawable];
167
}
168

103169
void CommandBuffer_Commit(void * commandBuffer) {
104170
	[(id<MTLCommandBuffer>)commandBuffer commit];
105171
}
106172

107173
void CommandBuffer_WaitUntilCompleted(void * commandBuffer) {