-
Notifications
You must be signed in to change notification settings - Fork 0
/
mtl_test.go
163 lines (141 loc) · 4.24 KB
/
mtl_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// +build darwin
package mtl_test
import (
"fmt"
"image"
"image/color"
"image/png"
"os"
"path/filepath"
"testing"
"unsafe"
"dmitri.shuralyov.com/gpu/mtl"
"golang.org/x/image/math/f32"
)
func TestRenderTriangle(t *testing.T) {
device, err := mtl.CreateSystemDefaultDevice()
if err != nil {
t.Fatal(err)
}
// 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)]]
) {
return vertices[vertexID];
}
fragment float4 FragmentShader(Vertex in [[stage_in]]) {
return in.color;
}
`
lib, err := device.MakeLibrary(source, mtl.CompileOptions{})
if err != nil {
t.Fatal(err)
}
vs, err := lib.MakeFunction("VertexShader")
if err != nil {
t.Fatal(err)
}
fs, err := lib.MakeFunction("FragmentShader")
if err != nil {
t.Fatal(err)
}
var rpld mtl.RenderPipelineDescriptor
rpld.VertexFunction = vs
rpld.FragmentFunction = fs
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
rps, err := device.MakeRenderPipelineState(rpld)
if err != nil {
t.Fatal(err)
}
// Create a vertex buffer.
type Vertex struct {
Position f32.Vec4
Color f32.Vec4
}
vertexData := [...]Vertex{
{f32.Vec4{+0.00, +0.75, 0, 1}, f32.Vec4{1, 0, 0, 1}},
{f32.Vec4{-0.75, -0.75, 0, 1}, f32.Vec4{0, 1, 0, 1}},
{f32.Vec4{+0.75, -0.75, 0, 1}, f32.Vec4{0, 0, 1, 1}},
}
vertexBuffer := device.MakeBuffer(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged)
// Create an output texture to render into.
td := mtl.TextureDescriptor{
PixelFormat: mtl.PixelFormatRGBA8UNorm,
Width: 512,
Height: 512,
StorageMode: mtl.StorageModeManaged,
}
texture := device.MakeTexture(td)
cq := device.MakeCommandQueue()
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 = texture
rce := cb.MakeRenderCommandEncoder(rpd)
rce.SetRenderPipelineState(rps)
rce.SetVertexBuffer(vertexBuffer, 0, 0)
rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3)
rce.EndEncoding()
// Encode all blit commands.
bce := cb.MakeBlitCommandEncoder()
bce.Synchronize(texture)
bce.EndEncoding()
cb.Commit()
cb.WaitUntilCompleted()
// Read pixels from output texture into an image.
got := image.NewNRGBA(image.Rect(0, 0, texture.Width, texture.Height))
bytesPerRow := 4 * texture.Width
region := mtl.RegionMake2D(0, 0, texture.Width, texture.Height)
texture.GetBytes(&got.Pix[0], uintptr(bytesPerRow), region, 0)
want, err := readPNG(filepath.Join("testdata", "triangle.png"))
if err != nil {
t.Fatal(err)
}
if err := imageEq(got, want); err != nil {
t.Errorf("got image != want: %v", err)
}
}
func readPNG(name string) (image.Image, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return png.Decode(f)
}
// imageEq reports whether images m, n are considered equivalent. Two images are considered
// equivalent if they have same bounds, and all pixel colors are within a small margin.
func imageEq(m, n image.Image) error {
if m.Bounds() != n.Bounds() {
return fmt.Errorf("bounds don't match: %v != %v", m.Bounds(), n.Bounds())
}
for y := m.Bounds().Min.Y; y < m.Bounds().Max.Y; y++ {
for x := m.Bounds().Min.X; x < m.Bounds().Max.X; x++ {
c := color.NRGBAModel.Convert(m.At(x, y)).(color.NRGBA)
d := color.NRGBAModel.Convert(n.At(x, y)).(color.NRGBA)
if !colorEq(c, d) {
return fmt.Errorf("pixel (%v, %v) doesn't match: %+v != %+v", x, y, c, d)
}
}
}
return nil
}
// colorEq reports whether colors c, d are considered equivalent, i.e., within a small margin.
func colorEq(c, d color.NRGBA) bool {
return eqEpsilon(c.R, d.R) && eqEpsilon(c.G, d.G) && eqEpsilon(c.B, d.B) && eqEpsilon(c.A, d.A)
}
// eqEpsilon reports whether a and b are within epsilon of each other.
func eqEpsilon(a, b uint8) bool {
const epsilon = 1
return uint16(a)-uint16(b) <= epsilon || uint16(b)-uint16(a) <= epsilon
}