Files
gio-patched/gpu/internal/metal/metal_darwin.go
T
Elias Naur 8999747ad2 app,gpu,internal/vk: add Vulkan port for Wayland, X11, Android
This change implements a Vulkan port for the two renderers, old and
compute. Run with GIORENDERER=forcecompute to test the compute renderer.

To shake out bugs faster, it is also made the default on systems that
support it. To disable Vulkan and force the use of OpenGL, use the
`novulkan` tag:

$ go run -tags novulkan gioui.org/example/kitchen

Don't forget to file an issue describing the issue that prompted the use
of the tag.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2021-09-21 18:40:05 +02:00

1142 lines
33 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package metal
import (
"errors"
"fmt"
"image"
"unsafe"
"gioui.org/gpu/internal/driver"
"gioui.org/shader"
)
/*
#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
#cgo LDFLAGS: -framework CoreGraphics
@import Metal;
#include <CoreFoundation/CoreFoundation.h>
#include <Metal/Metal.h>
typedef struct {
void *addr;
NSUInteger size;
} slice;
static CFTypeRef queueNewBuffer(CFTypeRef queueRef) {
@autoreleasepool {
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
return CFBridgingRetain([queue commandBuffer]);
}
}
static void cmdBufferCommit(CFTypeRef cmdBufRef) {
@autoreleasepool {
id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef;
[cmdBuf commit];
}
}
static void cmdBufferWaitUntilCompleted(CFTypeRef cmdBufRef) {
@autoreleasepool {
id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef;
[cmdBuf waitUntilCompleted];
}
}
static CFTypeRef cmdBufferRenderEncoder(CFTypeRef cmdBufRef, CFTypeRef textureRef, MTLLoadAction act, float r, float g, float b, float a) {
@autoreleasepool {
id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef;
MTLRenderPassDescriptor *desc = [MTLRenderPassDescriptor new];
desc.colorAttachments[0].texture = (__bridge id<MTLTexture>)textureRef;
desc.colorAttachments[0].loadAction = act;
desc.colorAttachments[0].clearColor = MTLClearColorMake(r, g, b, a);
return CFBridgingRetain([cmdBuf renderCommandEncoderWithDescriptor:desc]);
}
}
static CFTypeRef cmdBufferComputeEncoder(CFTypeRef cmdBufRef) {
@autoreleasepool {
id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef;
return CFBridgingRetain([cmdBuf computeCommandEncoder]);
}
}
static CFTypeRef cmdBufferBlitEncoder(CFTypeRef cmdBufRef) {
@autoreleasepool {
id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef;
return CFBridgingRetain([cmdBuf blitCommandEncoder]);
}
}
static void renderEncEnd(CFTypeRef renderEncRef) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
[enc endEncoding];
}
}
static void renderEncViewport(CFTypeRef renderEncRef, MTLViewport viewport) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
[enc setViewport:viewport];
}
}
static void renderEncSetFragmentTexture(CFTypeRef renderEncRef, NSUInteger index, CFTypeRef texRef) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
id<MTLTexture> tex = (__bridge id<MTLTexture>)texRef;
[enc setFragmentTexture:tex atIndex:index];
}
}
static void renderEncSetFragmentSamplerState(CFTypeRef renderEncRef, NSUInteger index, CFTypeRef samplerRef) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
id<MTLSamplerState> sampler = (__bridge id<MTLSamplerState>)samplerRef;
[enc setFragmentSamplerState:sampler atIndex:index];
}
}
static void renderEncSetVertexBuffer(CFTypeRef renderEncRef, CFTypeRef bufRef, NSUInteger idx, NSUInteger offset) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef;
[enc setVertexBuffer:buf offset:offset atIndex:idx];
}
}
static void renderEncSetFragmentBuffer(CFTypeRef renderEncRef, CFTypeRef bufRef, NSUInteger idx, NSUInteger offset) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef;
[enc setFragmentBuffer:buf offset:offset atIndex:idx];
}
}
static void renderEncSetFragmentBytes(CFTypeRef renderEncRef, const void *bytes, NSUInteger length, NSUInteger idx) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
[enc setFragmentBytes:bytes length:length atIndex:idx];
}
}
static void renderEncSetVertexBytes(CFTypeRef renderEncRef, const void *bytes, NSUInteger length, NSUInteger idx) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
[enc setVertexBytes:bytes length:length atIndex:idx];
}
}
static void renderEncSetRenderPipelineState(CFTypeRef renderEncRef, CFTypeRef pipeRef) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
id<MTLRenderPipelineState> pipe = (__bridge id<MTLRenderPipelineState>)pipeRef;
[enc setRenderPipelineState:pipe];
}
}
static void renderEncDrawPrimitives(CFTypeRef renderEncRef, MTLPrimitiveType type, NSUInteger start, NSUInteger count) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
[enc drawPrimitives:type vertexStart:start vertexCount:count];
}
}
static void renderEncDrawIndexedPrimitives(CFTypeRef renderEncRef, MTLPrimitiveType type, CFTypeRef bufRef, NSUInteger offset, NSUInteger count) {
@autoreleasepool {
id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef;
id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef;
[enc drawIndexedPrimitives:type indexCount:count indexType:MTLIndexTypeUInt16 indexBuffer:buf indexBufferOffset:offset];
}
}
static void computeEncSetPipeline(CFTypeRef computeEncRef, CFTypeRef pipeRef) {
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
id<MTLComputePipelineState> pipe = (__bridge id<MTLComputePipelineState>)pipeRef;
[enc setComputePipelineState:pipe];
}
}
static void computeEncSetTexture(CFTypeRef computeEncRef, NSUInteger index, CFTypeRef texRef) {
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
id<MTLTexture> tex = (__bridge id<MTLTexture>)texRef;
[enc setTexture:tex atIndex:index];
}
}
static void computeEncEnd(CFTypeRef computeEncRef) {
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
[enc endEncoding];
}
}
static void computeEncSetBuffer(CFTypeRef computeEncRef, NSUInteger index, CFTypeRef bufRef) {
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef;
[enc setBuffer:buf offset:0 atIndex:index];
}
}
static void computeEncDispatch(CFTypeRef computeEncRef, MTLSize threadgroupsPerGrid, MTLSize threadsPerThreadgroup) {
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
[enc dispatchThreadgroups:threadgroupsPerGrid threadsPerThreadgroup:threadsPerThreadgroup];
}
}
static void computeEncSetBytes(CFTypeRef computeEncRef, const void *bytes, NSUInteger length, NSUInteger index) {
@autoreleasepool {
id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef;
[enc setBytes:bytes length:length atIndex:index];
}
}
static void blitEncEnd(CFTypeRef blitEncRef) {
@autoreleasepool {
id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
[enc endEncoding];
}
}
static void blitEncCopyFromTexture(CFTypeRef blitEncRef, CFTypeRef srcRef, MTLOrigin srcOrig, MTLSize srcSize, CFTypeRef dstRef, MTLOrigin dstOrig) {
@autoreleasepool {
id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
id<MTLTexture> src = (__bridge id<MTLTexture>)srcRef;
id<MTLTexture> dst = (__bridge id<MTLTexture>)dstRef;
[enc copyFromTexture:src
sourceSlice:0
sourceLevel:0
sourceOrigin:srcOrig
sourceSize:srcSize
toTexture:dst
destinationSlice:0
destinationLevel:0
destinationOrigin:dstOrig];
}
}
static void blitEncCopyBufferToTexture(CFTypeRef blitEncRef, CFTypeRef bufRef, CFTypeRef texRef, NSUInteger offset, NSUInteger stride, NSUInteger length, MTLSize dims, MTLOrigin orig) {
@autoreleasepool {
id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
id<MTLBuffer> src = (__bridge id<MTLBuffer>)bufRef;
id<MTLTexture> dst = (__bridge id<MTLTexture>)texRef;
[enc copyFromBuffer:src
sourceOffset:offset
sourceBytesPerRow:stride
sourceBytesPerImage:length
sourceSize:dims
toTexture:dst
destinationSlice:0
destinationLevel:0
destinationOrigin:orig];
}
}
static void blitEncCopyTextureToBuffer(CFTypeRef blitEncRef, CFTypeRef texRef, CFTypeRef bufRef, NSUInteger offset, NSUInteger stride, NSUInteger length, MTLSize dims, MTLOrigin orig) {
@autoreleasepool {
id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
id<MTLTexture> src = (__bridge id<MTLTexture>)texRef;
id<MTLBuffer> dst = (__bridge id<MTLBuffer>)bufRef;
[enc copyFromTexture:src
sourceSlice:0
sourceLevel:0
sourceOrigin:orig
sourceSize:dims
toBuffer:dst
destinationOffset:offset
destinationBytesPerRow:stride
destinationBytesPerImage:length];
}
}
static void blitEncCopyBufferToBuffer(CFTypeRef blitEncRef, CFTypeRef srcRef, CFTypeRef dstRef, NSUInteger srcOff, NSUInteger dstOff, NSUInteger size) {
@autoreleasepool {
id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
id<MTLBuffer> src = (__bridge id<MTLBuffer>)srcRef;
id<MTLBuffer> dst = (__bridge id<MTLBuffer>)dstRef;
[enc copyFromBuffer:src
sourceOffset:srcOff
toBuffer:dst
destinationOffset:dstOff
size:size];
}
}
static CFTypeRef newTexture(CFTypeRef devRef, NSUInteger width, NSUInteger height, MTLPixelFormat format, MTLTextureUsage usage) {
@autoreleasepool {
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
MTLTextureDescriptor *mtlDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: format
width: width
height: height
mipmapped: NO];
mtlDesc.usage = usage;
mtlDesc.storageMode = MTLStorageModePrivate;
return CFBridgingRetain([dev newTextureWithDescriptor:mtlDesc]);
}
}
static CFTypeRef newSampler(CFTypeRef devRef, MTLSamplerMinMagFilter minFilter, MTLSamplerMinMagFilter magFilter) {
@autoreleasepool {
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
MTLSamplerDescriptor *desc = [MTLSamplerDescriptor new];
desc.minFilter = minFilter;
desc.magFilter = magFilter;
return CFBridgingRetain([dev newSamplerStateWithDescriptor:desc]);
}
}
static CFTypeRef newBuffer(CFTypeRef devRef, NSUInteger size, MTLResourceOptions opts) {
@autoreleasepool {
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
id<MTLBuffer> buf = [dev newBufferWithLength:size
options:opts];
return CFBridgingRetain(buf);
}
}
static slice bufferContents(CFTypeRef bufRef) {
@autoreleasepool {
id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef;
slice s = {.addr = [buf contents], .size = [buf length]};
return s;
}
}
static CFTypeRef newLibrary(CFTypeRef devRef, char *name, void *mtllib, size_t size) {
@autoreleasepool {
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
dispatch_data_t data = dispatch_data_create(mtllib, size, DISPATCH_TARGET_QUEUE_DEFAULT, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
id<MTLLibrary> lib = [dev newLibraryWithData:data error:nil];
lib.label = [NSString stringWithUTF8String:name];
return CFBridgingRetain(lib);
}
}
static CFTypeRef libraryNewFunction(CFTypeRef libRef, char *funcName) {
@autoreleasepool {
id<MTLLibrary> lib = (__bridge id<MTLLibrary>)libRef;
NSString *name = [NSString stringWithUTF8String:funcName];
return CFBridgingRetain([lib newFunctionWithName:name]);
}
}
static CFTypeRef newComputePipeline(CFTypeRef devRef, CFTypeRef funcRef) {
@autoreleasepool {
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
id<MTLFunction> func = (__bridge id<MTLFunction>)funcRef;
return CFBridgingRetain([dev newComputePipelineStateWithFunction:func error:nil]);
}
}
static CFTypeRef newRenderPipeline(CFTypeRef devRef, CFTypeRef vertFunc, CFTypeRef fragFunc, MTLPixelFormat pixelFormat, NSUInteger bufIdx, NSUInteger nverts, MTLVertexFormat *fmts, NSUInteger *offsets, NSUInteger stride, int blend, MTLBlendFactor srcFactor, MTLBlendFactor dstFactor, NSUInteger nvertBufs, NSUInteger nfragBufs) {
@autoreleasepool {
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
id<MTLFunction> vfunc = (__bridge id<MTLFunction>)vertFunc;
id<MTLFunction> ffunc = (__bridge id<MTLFunction>)fragFunc;
MTLVertexDescriptor *vdesc = [MTLVertexDescriptor vertexDescriptor];
vdesc.layouts[bufIdx].stride = stride;
for (NSUInteger i = 0; i < nverts; i++) {
vdesc.attributes[i].format = fmts[i];
vdesc.attributes[i].offset = offsets[i];
vdesc.attributes[i].bufferIndex = bufIdx;
}
MTLRenderPipelineDescriptor *desc = [MTLRenderPipelineDescriptor new];
desc.vertexFunction = vfunc;
desc.fragmentFunction = ffunc;
desc.vertexDescriptor = vdesc;
for (NSUInteger i = 0; i < nvertBufs; i++) {
if (@available(iOS 11.0, *)) {
desc.vertexBuffers[i].mutability = MTLMutabilityImmutable;
}
}
for (NSUInteger i = 0; i < nfragBufs; i++) {
if (@available(iOS 11.0, *)) {
desc.fragmentBuffers[i].mutability = MTLMutabilityImmutable;
}
}
desc.colorAttachments[0].pixelFormat = pixelFormat;
desc.colorAttachments[0].blendingEnabled = blend ? YES : NO;
desc.colorAttachments[0].sourceAlphaBlendFactor = srcFactor;
desc.colorAttachments[0].sourceRGBBlendFactor = srcFactor;
desc.colorAttachments[0].destinationAlphaBlendFactor = dstFactor;
desc.colorAttachments[0].destinationRGBBlendFactor = dstFactor;
return CFBridgingRetain([dev newRenderPipelineStateWithDescriptor:desc
error:nil]);
}
}
*/
import "C"
type Backend struct {
dev C.CFTypeRef
queue C.CFTypeRef
pixelFmt C.MTLPixelFormat
cmdBuffer C.CFTypeRef
lastCmdBuffer C.CFTypeRef
renderEnc C.CFTypeRef
computeEnc C.CFTypeRef
blitEnc C.CFTypeRef
prog *Program
topology C.MTLPrimitiveType
stagingBuf C.CFTypeRef
stagingOff int
indexBuf *Buffer
// bufSizes is scratch space for filling out the spvBufferSizeConstants
// that spirv-cross generates for emulating buffer.length expressions in
// shaders.
bufSizes []uint32
}
type Texture struct {
backend *Backend
texture C.CFTypeRef
sampler C.CFTypeRef
width int
height int
foreign bool
}
type Shader struct {
function C.CFTypeRef
inputs []shader.InputLocation
}
type Program struct {
pipeline C.CFTypeRef
groupSize [3]int
}
type Pipeline struct {
pipeline C.CFTypeRef
topology C.MTLPrimitiveType
}
type Buffer struct {
backend *Backend
size int
buffer C.CFTypeRef
// store is the buffer contents For buffers not allocated on the GPU.
store []byte
}
const (
uniformBufferIndex = 0
attributeBufferIndex = 1
spvBufferSizeConstantsBinding = 25
)
const (
texUnits = 4
bufferUnits = 4
)
func init() {
driver.NewMetalDevice = newMetalDevice
}
func newMetalDevice(api driver.Metal) (driver.Device, error) {
dev := C.CFTypeRef(api.Device)
C.CFRetain(dev)
queue := C.CFTypeRef(api.Queue)
C.CFRetain(queue)
b := &Backend{
dev: dev,
queue: queue,
pixelFmt: C.MTLPixelFormat(api.PixelFormat),
bufSizes: make([]uint32, bufferUnits),
}
return b, nil
}
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
if b.lastCmdBuffer != 0 {
C.cmdBufferWaitUntilCompleted(b.lastCmdBuffer)
b.stagingOff = 0
}
if target == nil {
return nil
}
switch t := target.(type) {
case driver.MetalRenderTarget:
texture := C.CFTypeRef(t.Texture)
return &Texture{texture: texture, foreign: true}
case *Texture:
return t
default:
panic(fmt.Sprintf("metal: unsupported render target type: %T", t))
}
}
func (b *Backend) startBlit() C.CFTypeRef {
if b.blitEnc != 0 {
return b.blitEnc
}
b.endEncoder()
b.ensureCmdBuffer()
b.blitEnc = C.cmdBufferBlitEncoder(b.cmdBuffer)
if b.blitEnc == 0 {
panic("metal: [MTLCommandBuffer blitCommandEncoder:] failed")
}
return b.blitEnc
}
func (b *Backend) CopyTexture(dst driver.Texture, dorig image.Point, src driver.Texture, srect image.Rectangle) {
enc := b.startBlit()
dstTex := dst.(*Texture).texture
srcTex := src.(*Texture).texture
ssz := srect.Size()
C.blitEncCopyFromTexture(
enc,
srcTex,
C.MTLOrigin{
x: C.NSUInteger(srect.Min.X),
y: C.NSUInteger(srect.Min.Y),
},
C.MTLSize{
width: C.NSUInteger(ssz.X),
height: C.NSUInteger(ssz.Y),
depth: 1,
},
dstTex,
C.MTLOrigin{
x: C.NSUInteger(dorig.X),
y: C.NSUInteger(dorig.Y),
},
)
}
func (b *Backend) EndFrame() {
b.endCmdBuffer(false)
}
func (b *Backend) endCmdBuffer(wait bool) {
b.endEncoder()
if b.cmdBuffer == 0 {
return
}
C.cmdBufferCommit(b.cmdBuffer)
if wait {
C.cmdBufferWaitUntilCompleted(b.cmdBuffer)
}
if b.lastCmdBuffer != 0 {
C.CFRelease(b.lastCmdBuffer)
}
b.lastCmdBuffer = b.cmdBuffer
b.cmdBuffer = 0
}
func (b *Backend) Caps() driver.Caps {
return driver.Caps{
MaxTextureSize: 8192,
Features: driver.FeatureSRGB | driver.FeatureCompute | driver.FeatureFloatRenderTargets,
}
}
func (b *Backend) NewTimer() driver.Timer {
panic("timers not supported")
}
func (b *Backend) IsTimeContinuous() bool {
panic("timers not supported")
}
func (b *Backend) Release() {
if b.cmdBuffer != 0 {
C.CFRelease(b.cmdBuffer)
}
if b.lastCmdBuffer != 0 {
C.CFRelease(b.lastCmdBuffer)
}
if b.stagingBuf != 0 {
C.CFRelease(b.stagingBuf)
}
C.CFRelease(b.queue)
C.CFRelease(b.dev)
*b = Backend{}
}
func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, bindings driver.BufferBinding) (driver.Texture, error) {
mformat := pixelFormatFor(format)
var usage C.MTLTextureUsage
if bindings&(driver.BufferBindingTexture|driver.BufferBindingShaderStorageRead) != 0 {
usage |= C.MTLTextureUsageShaderRead
}
if bindings&driver.BufferBindingFramebuffer != 0 {
usage |= C.MTLTextureUsageRenderTarget
}
if bindings&driver.BufferBindingShaderStorageWrite != 0 {
usage |= C.MTLTextureUsageShaderWrite
}
tex := C.newTexture(b.dev, C.NSUInteger(width), C.NSUInteger(height), mformat, usage)
if tex == 0 {
return nil, errors.New("metal: [MTLDevice newTextureWithDescriptor:] failed")
}
min := samplerFilterFor(minFilter)
max := samplerFilterFor(magFilter)
s := C.newSampler(b.dev, min, max)
if s == 0 {
C.CFRelease(tex)
return nil, errors.New("metal: [MTLDevice newSamplerStateWithDescriptor:] failed")
}
return &Texture{backend: b, texture: tex, sampler: s, width: width, height: height}, nil
}
func samplerFilterFor(f driver.TextureFilter) C.MTLSamplerMinMagFilter {
switch f {
case driver.FilterNearest:
return C.MTLSamplerMinMagFilterNearest
case driver.FilterLinear:
return C.MTLSamplerMinMagFilterLinear
default:
panic("invalid texture filter")
}
}
func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) {
vsh, fsh := desc.VertexShader.(*Shader), desc.FragmentShader.(*Shader)
layout := desc.VertexLayout.Inputs
if got, exp := len(layout), len(vsh.inputs); got != exp {
return nil, fmt.Errorf("metal: number of input descriptors (%d) doesn't match number of inputs (%d)", got, exp)
}
formats := make([]C.MTLVertexFormat, len(layout))
offsets := make([]C.NSUInteger, len(layout))
for i, inp := range layout {
index := vsh.inputs[i].Location
formats[index] = vertFormatFor(vsh.inputs[i])
offsets[index] = C.NSUInteger(inp.Offset)
}
var (
fmtPtr *C.MTLVertexFormat
offPtr *C.NSUInteger
)
if len(layout) > 0 {
fmtPtr = &formats[0]
offPtr = &offsets[0]
}
srcFactor := blendFactorFor(desc.BlendDesc.SrcFactor)
dstFactor := blendFactorFor(desc.BlendDesc.DstFactor)
blend := C.int(0)
if desc.BlendDesc.Enable {
blend = 1
}
pf := b.pixelFmt
if f := desc.PixelFormat; f != driver.TextureFormatOutput {
pf = pixelFormatFor(f)
}
pipe := C.newRenderPipeline(
b.dev,
vsh.function,
fsh.function,
pf,
attributeBufferIndex,
C.NSUInteger(len(layout)), fmtPtr, offPtr,
C.NSUInteger(desc.VertexLayout.Stride),
blend, srcFactor, dstFactor,
2, // Number of vertex buffers.
1, // Number of fragment buffers.
)
if pipe == 0 {
return nil, errors.New("metal: pipeline construction failed")
}
return &Pipeline{pipeline: pipe, topology: primitiveFor(desc.Topology)}, nil
}
func dataTypeSize(d shader.DataType) int {
switch d {
case shader.DataTypeFloat:
return 4
default:
panic("unsupported data type")
}
}
func blendFactorFor(f driver.BlendFactor) C.MTLBlendFactor {
switch f {
case driver.BlendFactorZero:
return C.MTLBlendFactorZero
case driver.BlendFactorOne:
return C.MTLBlendFactorOne
case driver.BlendFactorOneMinusSrcAlpha:
return C.MTLBlendFactorOneMinusSourceAlpha
case driver.BlendFactorDstColor:
return C.MTLBlendFactorDestinationColor
default:
panic("unsupported blend factor")
}
}
func vertFormatFor(f shader.InputLocation) C.MTLVertexFormat {
t := f.Type
s := f.Size
switch {
case t == shader.DataTypeFloat && s == 1:
return C.MTLVertexFormatFloat
case t == shader.DataTypeFloat && s == 2:
return C.MTLVertexFormatFloat2
case t == shader.DataTypeFloat && s == 3:
return C.MTLVertexFormatFloat3
case t == shader.DataTypeFloat && s == 4:
return C.MTLVertexFormatFloat4
default:
panic("unsupported data type")
}
}
func pixelFormatFor(f driver.TextureFormat) C.MTLPixelFormat {
switch f {
case driver.TextureFormatFloat:
return C.MTLPixelFormatR16Float
case driver.TextureFormatRGBA8:
return C.MTLPixelFormatRGBA8Unorm
case driver.TextureFormatSRGBA:
return C.MTLPixelFormatRGBA8Unorm_sRGB
default:
panic("unsupported pixel format")
}
}
func (b *Backend) NewBuffer(typ driver.BufferBinding, size int) (driver.Buffer, error) {
// Transfer buffer contents in command encoders on every use for
// smaller buffers. The advantage is that buffer re-use during a frame
// won't occur a GPU wait.
// We can't do this for buffers written to by the GPU and read by the client,
// and Metal doesn't require a buffer for indexed draws.
if size <= 4096 && typ&(driver.BufferBindingShaderStorageWrite|driver.BufferBindingIndices) == 0 {
return &Buffer{size: size, store: make([]byte, size)}, nil
}
buf := C.newBuffer(b.dev, C.NSUInteger(size), C.MTLResourceStorageModePrivate)
return &Buffer{backend: b, size: size, buffer: buf}, nil
}
func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) {
buf, err := b.NewBuffer(typ, len(data))
if err != nil {
return nil, err
}
buf.Upload(data)
return buf, nil
}
func (b *Backend) NewComputeProgram(src shader.Sources) (driver.Program, error) {
sh, err := b.newShader(src)
if err != nil {
return nil, err
}
defer sh.Release()
pipe := C.newComputePipeline(b.dev, sh.function)
if pipe == 0 {
return nil, fmt.Errorf("metal: compute program %q load failed", src.Name)
}
return &Program{pipeline: pipe, groupSize: src.WorkgroupSize}, nil
}
func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) {
return b.newShader(src)
}
func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) {
return b.newShader(src)
}
func (b *Backend) newShader(src shader.Sources) (*Shader, error) {
vsrc := []byte(src.MetalLib)
cname := C.CString(src.Name)
defer C.free(unsafe.Pointer(cname))
vlib := C.newLibrary(b.dev, cname, unsafe.Pointer(&vsrc[0]), C.size_t(len(vsrc)))
if vlib == 0 {
return nil, fmt.Errorf("metal: vertex shader %q load failed", src.Name)
}
defer C.CFRelease(vlib)
funcName := C.CString("main0")
defer C.free(unsafe.Pointer(funcName))
f := C.libraryNewFunction(vlib, funcName)
if f == 0 {
return nil, fmt.Errorf("metal: main function not found in %q", src.Name)
}
return &Shader{function: f, inputs: src.Inputs}, nil
}
func (b *Backend) Viewport(x, y, width, height int) {
enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
C.renderEncViewport(enc, C.MTLViewport{
originX: C.double(x),
originY: C.double(y),
width: C.double(width),
height: C.double(height),
znear: 0.0,
zfar: 1.0,
})
}
func (b *Backend) DrawArrays(off, count int) {
enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
C.renderEncDrawPrimitives(enc, b.topology, C.NSUInteger(off), C.NSUInteger(count))
}
func (b *Backend) DrawElements(off, count int) {
enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
C.renderEncDrawIndexedPrimitives(enc, b.topology, b.indexBuf.buffer, C.NSUInteger(off), C.NSUInteger(count))
}
func primitiveFor(mode driver.Topology) C.MTLPrimitiveType {
switch mode {
case driver.TopologyTriangles:
return C.MTLPrimitiveTypeTriangle
case driver.TopologyTriangleStrip:
return C.MTLPrimitiveTypeTriangleStrip
default:
panic("metal: unknown draw mode")
}
}
func (b *Backend) BindImageTexture(unit int, tex driver.Texture) {
b.BindTexture(unit, tex)
}
func (b *Backend) BeginCompute() {
b.endEncoder()
b.ensureCmdBuffer()
for i := range b.bufSizes {
b.bufSizes[i] = 0
}
b.computeEnc = C.cmdBufferComputeEncoder(b.cmdBuffer)
if b.computeEnc == 0 {
panic("metal: [MTLCommandBuffer computeCommandEncoder:] failed")
}
}
func (b *Backend) EndCompute() {
if b.computeEnc == 0 {
panic("no active compute pass")
}
C.computeEncEnd(b.computeEnc)
C.CFRelease(b.computeEnc)
b.computeEnc = 0
}
func (b *Backend) DispatchCompute(x, y, z int) {
enc := b.computeEnc
if enc == 0 {
panic("no active compute pass")
}
C.computeEncSetBytes(enc, unsafe.Pointer(&b.bufSizes[0]), C.NSUInteger(len(b.bufSizes)*4), spvBufferSizeConstantsBinding)
threadgroupsPerGrid := C.MTLSize{
width: C.NSUInteger(x), height: C.NSUInteger(y), depth: C.NSUInteger(z),
}
sz := b.prog.groupSize
threadsPerThreadgroup := C.MTLSize{
width: C.NSUInteger(sz[0]), height: C.NSUInteger(sz[1]), depth: C.NSUInteger(sz[2]),
}
C.computeEncDispatch(enc, threadgroupsPerGrid, threadsPerThreadgroup)
}
func (b *Backend) stagingBuffer(size int) (C.CFTypeRef, int) {
if b.stagingBuf == 0 || b.stagingOff+size > len(bufferStore(b.stagingBuf)) {
if b.stagingBuf != 0 {
C.CFRelease(b.stagingBuf)
}
cap := 2 * (b.stagingOff + size)
b.stagingBuf = C.newBuffer(b.dev, C.NSUInteger(cap), C.MTLResourceStorageModeShared|C.MTLResourceCPUCacheModeWriteCombined)
if b.stagingBuf == 0 {
panic(fmt.Errorf("metal: failed to allocate %d bytes of staging buffer", cap))
}
b.stagingOff = 0
}
off := b.stagingOff
b.stagingOff += size
return b.stagingBuf, off
}
func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) {
if len(pixels) == 0 {
return
}
if stride == 0 {
stride = size.X * 4
}
dstStride := size.X * 4
n := size.Y * dstStride
buf, off := t.backend.stagingBuffer(n)
store := bufferSlice(buf, off, n)
var srcOff, dstOff int
for y := 0; y < size.Y; y++ {
srcRow := pixels[srcOff : srcOff+dstStride]
dstRow := store[dstOff : dstOff+dstStride]
copy(dstRow, srcRow)
dstOff += dstStride
srcOff += stride
}
enc := t.backend.startBlit()
orig := C.MTLOrigin{
x: C.NSUInteger(offset.X),
y: C.NSUInteger(offset.Y),
}
msize := C.MTLSize{
width: C.NSUInteger(size.X),
height: C.NSUInteger(size.Y),
depth: 1,
}
C.blitEncCopyBufferToTexture(enc, buf, t.texture, C.NSUInteger(off), C.NSUInteger(dstStride), C.NSUInteger(len(store)), msize, orig)
}
func (t *Texture) Release() {
if t.foreign {
panic("metal: release of external texture")
}
C.CFRelease(t.texture)
C.CFRelease(t.sampler)
*t = Texture{}
}
func (p *Pipeline) Release() {
C.CFRelease(p.pipeline)
*p = Pipeline{}
}
func (b *Backend) PrepareTexture(tex driver.Texture) {}
func (b *Backend) BindTexture(unit int, tex driver.Texture) {
t := tex.(*Texture)
if enc := b.renderEnc; enc != 0 {
C.renderEncSetFragmentTexture(enc, C.NSUInteger(unit), t.texture)
C.renderEncSetFragmentSamplerState(enc, C.NSUInteger(unit), t.sampler)
} else if enc := b.computeEnc; enc != 0 {
C.computeEncSetTexture(enc, C.NSUInteger(unit), t.texture)
} else {
panic("no active render nor compute pass")
}
}
func (b *Backend) ensureCmdBuffer() {
if b.cmdBuffer != 0 {
return
}
b.cmdBuffer = C.queueNewBuffer(b.queue)
if b.cmdBuffer == 0 {
panic("metal: [MTLCommandQueue cmdBuffer] failed")
}
}
func (b *Backend) BindPipeline(pipe driver.Pipeline) {
p := pipe.(*Pipeline)
enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
C.renderEncSetRenderPipelineState(enc, p.pipeline)
b.topology = p.topology
}
func (b *Backend) BindProgram(prog driver.Program) {
enc := b.computeEnc
if enc == 0 {
panic("no active compute pass")
}
p := prog.(*Program)
C.computeEncSetPipeline(enc, p.pipeline)
b.prog = p
}
func (s *Shader) Release() {
C.CFRelease(s.function)
*s = Shader{}
}
func (p *Program) Release() {
C.CFRelease(p.pipeline)
*p = Program{}
}
func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) {
buf := buffer.(*Buffer)
b.bufSizes[binding] = uint32(buf.size)
enc := b.computeEnc
if enc == 0 {
panic("no active compute pass")
}
if buf.buffer != 0 {
C.computeEncSetBuffer(enc, C.NSUInteger(binding), buf.buffer)
} else if buf.size > 0 {
C.computeEncSetBytes(enc, unsafe.Pointer(&buf.store[0]), C.NSUInteger(buf.size), C.NSUInteger(binding))
}
}
func (b *Backend) BindUniforms(buf driver.Buffer) {
bf := buf.(*Buffer)
enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
if bf.buffer != 0 {
C.renderEncSetVertexBuffer(enc, bf.buffer, uniformBufferIndex, 0)
C.renderEncSetFragmentBuffer(enc, bf.buffer, uniformBufferIndex, 0)
} else if bf.size > 0 {
C.renderEncSetVertexBytes(enc, unsafe.Pointer(&bf.store[0]), C.NSUInteger(bf.size), uniformBufferIndex)
C.renderEncSetFragmentBytes(enc, unsafe.Pointer(&bf.store[0]), C.NSUInteger(bf.size), uniformBufferIndex)
}
}
func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) {
bf := buf.(*Buffer)
enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
if bf.buffer != 0 {
C.renderEncSetVertexBuffer(enc, bf.buffer, attributeBufferIndex, C.NSUInteger(offset))
} else if n := bf.size - offset; n > 0 {
C.renderEncSetVertexBytes(enc, unsafe.Pointer(&bf.store[offset]), C.NSUInteger(n), attributeBufferIndex)
}
}
func (b *Backend) BindIndexBuffer(buf driver.Buffer) {
b.indexBuf = buf.(*Buffer)
}
func (b *Buffer) Download(data []byte) error {
if len(data) > b.size {
panic(fmt.Errorf("len(data) (%d) larger than len(content) (%d)", len(data), b.size))
}
buf, off := b.backend.stagingBuffer(len(data))
enc := b.backend.startBlit()
C.blitEncCopyBufferToBuffer(enc, b.buffer, buf, 0, C.NSUInteger(off), C.NSUInteger(len(data)))
b.backend.endCmdBuffer(true)
store := bufferSlice(buf, off, len(data))
copy(data, store)
return nil
}
func (b *Buffer) Upload(data []byte) {
if len(data) > b.size {
panic(fmt.Errorf("len(data) (%d) larger than len(content) (%d)", len(data), b.size))
}
if b.buffer == 0 {
copy(b.store, data)
return
}
buf, off := b.backend.stagingBuffer(len(data))
store := bufferSlice(buf, off, len(data))
copy(store, data)
enc := b.backend.startBlit()
C.blitEncCopyBufferToBuffer(enc, buf, b.buffer, C.NSUInteger(off), 0, C.NSUInteger(len(store)))
}
func bufferStore(buf C.CFTypeRef) []byte {
contents := C.bufferContents(buf)
return (*(*[1 << 30]byte)(contents.addr))[:contents.size:contents.size]
}
func bufferSlice(buf C.CFTypeRef, off, len int) []byte {
store := bufferStore(buf)
return store[off : off+len]
}
func (b *Buffer) Release() {
if b.buffer != 0 {
C.CFRelease(b.buffer)
}
*b = Buffer{}
}
func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
if len(pixels) == 0 {
return nil
}
sz := src.Size()
orig := C.MTLOrigin{
x: C.NSUInteger(src.Min.X),
y: C.NSUInteger(src.Min.Y),
}
msize := C.MTLSize{
width: C.NSUInteger(sz.X),
height: C.NSUInteger(sz.Y),
depth: 1,
}
stageStride := sz.X * 4
n := sz.Y * stageStride
buf, off := t.backend.stagingBuffer(n)
enc := t.backend.startBlit()
C.blitEncCopyTextureToBuffer(enc, t.texture, buf, C.NSUInteger(off), C.NSUInteger(stageStride), C.NSUInteger(n), msize, orig)
t.backend.endCmdBuffer(true)
store := bufferSlice(buf, off, n)
var srcOff, dstOff int
for y := 0; y < sz.Y; y++ {
dstRow := pixels[srcOff : srcOff+stageStride]
srcRow := store[dstOff : dstOff+stageStride]
copy(dstRow, srcRow)
dstOff += stageStride
srcOff += stride
}
return nil
}
func (b *Backend) BeginRenderPass(tex driver.Texture, d driver.LoadDesc) {
b.endEncoder()
b.ensureCmdBuffer()
f := tex.(*Texture)
col := d.ClearColor
var act C.MTLLoadAction
switch d.Action {
case driver.LoadActionKeep:
act = C.MTLLoadActionLoad
case driver.LoadActionClear:
act = C.MTLLoadActionClear
case driver.LoadActionInvalidate:
act = C.MTLLoadActionDontCare
}
b.renderEnc = C.cmdBufferRenderEncoder(b.cmdBuffer, f.texture, act, C.float(col.R), C.float(col.G), C.float(col.B), C.float(col.A))
if b.renderEnc == 0 {
panic("metal: [MTLCommandBuffer renderCommandEncoderWithDescriptor:] failed")
}
}
func (b *Backend) EndRenderPass() {
if b.renderEnc == 0 {
panic("no active render pass")
}
C.renderEncEnd(b.renderEnc)
C.CFRelease(b.renderEnc)
b.renderEnc = 0
}
func (b *Backend) endEncoder() {
if b.renderEnc != 0 {
panic("active render pass")
}
if b.computeEnc != 0 {
panic("active compute pass")
}
if b.blitEnc != 0 {
C.blitEncEnd(b.blitEnc)
C.CFRelease(b.blitEnc)
b.blitEnc = 0
}
}
func (f *Texture) ImplementsRenderTarget() {}