/
main.go
147 lines (126 loc) · 3.41 KB
/
main.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
// +build darwin
// hellotriangle is an example Metal program that renders a single frame with a triangle.
// It writes the frame to a triangle.png file in current working directory.
package main
import (
"flag"
"fmt"
"image"
"image/png"
"log"
"os"
"unsafe"
"dmitri.shuralyov.com/gpu/mtl"
"golang.org/x/image/math/f32"
)
func usage() {
fmt.Fprintln(os.Stderr, "Usage: hellotriangle")
flag.PrintDefaults()
}
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
}
// 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 {
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 = mtl.PixelFormatRGBA8UNorm
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.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.
img := 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(&img.Pix[0], uintptr(bytesPerRow), region, 0)
// Write output image to a PNG file.
err = writePNG("triangle.png", img)
return err
}
// writePNG encodes the image m to a named file, in PNG format.
func writePNG(name string, m image.Image) error {
f, err := os.Create(name)
if err != nil {
return err
}
defer f.Close()
err = png.Encode(f, m)
return err
}