1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159 |
- // 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 -fobjc-arc
- #cgo LDFLAGS: -framework CoreGraphics -framework Metal -framework Foundation
- #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 blitEncGenerateMipmapsForTexture(CFTypeRef blitEncRef, CFTypeRef texRef) {
- @autoreleasepool {
- id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef;
- id<MTLTexture> tex = (__bridge id<MTLTexture>)texRef;
- [enc generateMipmapsForTexture: tex];
- }
- }
- 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, int mipmapped) {
- @autoreleasepool {
- id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
- MTLTextureDescriptor *mtlDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: format
- width: width
- height: height
- mipmapped: mipmapped ? YES : NO];
- mtlDesc.usage = usage;
- mtlDesc.storageMode = MTLStorageModePrivate;
- return CFBridgingRetain([dev newTextureWithDescriptor:mtlDesc]);
- }
- }
- static CFTypeRef newSampler(CFTypeRef devRef, MTLSamplerMinMagFilter minFilter, MTLSamplerMinMagFilter magFilter, MTLSamplerMipFilter mipFilter) {
- @autoreleasepool {
- id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
- MTLSamplerDescriptor *desc = [MTLSamplerDescriptor new];
- desc.minFilter = minFilter;
- desc.magFilter = magFilter;
- desc.mipFilter = mipFilter;
- 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
- mipmap bool
- 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
- }
- min, mip := samplerFilterFor(minFilter)
- max, _ := samplerFilterFor(magFilter)
- mipmap := mip != C.MTLSamplerMipFilterNotMipmapped
- mipmapped := C.int(0)
- if mipmap {
- mipmapped = 1
- }
- tex := C.newTexture(b.dev, C.NSUInteger(width), C.NSUInteger(height), mformat, usage, mipmapped)
- if tex == 0 {
- return nil, errors.New("metal: [MTLDevice newTextureWithDescriptor:] failed")
- }
- s := C.newSampler(b.dev, min, max, mip)
- 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, mipmap: mipmap}, nil
- }
- func samplerFilterFor(f driver.TextureFilter) (C.MTLSamplerMinMagFilter, C.MTLSamplerMipFilter) {
- switch f {
- case driver.FilterNearest:
- return C.MTLSamplerMinMagFilterNearest, C.MTLSamplerMipFilterNotMipmapped
- case driver.FilterLinear:
- return C.MTLSamplerMinMagFilterLinear, C.MTLSamplerMipFilterNotMipmapped
- case driver.FilterLinearMipmapLinear:
- return C.MTLSamplerMinMagFilterLinear, C.MTLSamplerMipFilterLinear
- 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)
- if t.mipmap {
- C.blitEncGenerateMipmapsForTexture(enc, t.texture)
- }
- }
- 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() {}
|