gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/gpu/internal/metal/metal_darwin.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package metal 4 5 import ( 6 "errors" 7 "fmt" 8 "image" 9 "unsafe" 10 11 "gioui.org/gpu/internal/driver" 12 "gioui.org/shader" 13 ) 14 15 /* 16 #cgo CFLAGS: -Werror -xobjective-c -fobjc-arc 17 #cgo LDFLAGS: -framework CoreGraphics -framework Metal -framework Foundation 18 19 #include <CoreFoundation/CoreFoundation.h> 20 #include <Metal/Metal.h> 21 22 typedef struct { 23 void *addr; 24 NSUInteger size; 25 } slice; 26 27 static CFTypeRef queueNewBuffer(CFTypeRef queueRef) { 28 @autoreleasepool { 29 id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef; 30 return CFBridgingRetain([queue commandBuffer]); 31 } 32 } 33 34 static void cmdBufferCommit(CFTypeRef cmdBufRef) { 35 @autoreleasepool { 36 id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef; 37 [cmdBuf commit]; 38 } 39 } 40 41 static void cmdBufferWaitUntilCompleted(CFTypeRef cmdBufRef) { 42 @autoreleasepool { 43 id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef; 44 [cmdBuf waitUntilCompleted]; 45 } 46 } 47 48 static CFTypeRef cmdBufferRenderEncoder(CFTypeRef cmdBufRef, CFTypeRef textureRef, MTLLoadAction act, float r, float g, float b, float a) { 49 @autoreleasepool { 50 id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef; 51 MTLRenderPassDescriptor *desc = [MTLRenderPassDescriptor new]; 52 desc.colorAttachments[0].texture = (__bridge id<MTLTexture>)textureRef; 53 desc.colorAttachments[0].loadAction = act; 54 desc.colorAttachments[0].clearColor = MTLClearColorMake(r, g, b, a); 55 return CFBridgingRetain([cmdBuf renderCommandEncoderWithDescriptor:desc]); 56 } 57 } 58 59 static CFTypeRef cmdBufferComputeEncoder(CFTypeRef cmdBufRef) { 60 @autoreleasepool { 61 id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef; 62 return CFBridgingRetain([cmdBuf computeCommandEncoder]); 63 } 64 } 65 66 static CFTypeRef cmdBufferBlitEncoder(CFTypeRef cmdBufRef) { 67 @autoreleasepool { 68 id<MTLCommandBuffer> cmdBuf = (__bridge id<MTLCommandBuffer>)cmdBufRef; 69 return CFBridgingRetain([cmdBuf blitCommandEncoder]); 70 } 71 } 72 73 static void renderEncEnd(CFTypeRef renderEncRef) { 74 @autoreleasepool { 75 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 76 [enc endEncoding]; 77 } 78 } 79 80 static void renderEncViewport(CFTypeRef renderEncRef, MTLViewport viewport) { 81 @autoreleasepool { 82 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 83 [enc setViewport:viewport]; 84 } 85 } 86 87 static void renderEncSetFragmentTexture(CFTypeRef renderEncRef, NSUInteger index, CFTypeRef texRef) { 88 @autoreleasepool { 89 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 90 id<MTLTexture> tex = (__bridge id<MTLTexture>)texRef; 91 [enc setFragmentTexture:tex atIndex:index]; 92 } 93 } 94 95 static void renderEncSetFragmentSamplerState(CFTypeRef renderEncRef, NSUInteger index, CFTypeRef samplerRef) { 96 @autoreleasepool { 97 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 98 id<MTLSamplerState> sampler = (__bridge id<MTLSamplerState>)samplerRef; 99 [enc setFragmentSamplerState:sampler atIndex:index]; 100 } 101 } 102 103 static void renderEncSetVertexBuffer(CFTypeRef renderEncRef, CFTypeRef bufRef, NSUInteger idx, NSUInteger offset) { 104 @autoreleasepool { 105 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 106 id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef; 107 [enc setVertexBuffer:buf offset:offset atIndex:idx]; 108 } 109 } 110 111 static void renderEncSetFragmentBuffer(CFTypeRef renderEncRef, CFTypeRef bufRef, NSUInteger idx, NSUInteger offset) { 112 @autoreleasepool { 113 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 114 id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef; 115 [enc setFragmentBuffer:buf offset:offset atIndex:idx]; 116 } 117 } 118 119 static void renderEncSetFragmentBytes(CFTypeRef renderEncRef, const void *bytes, NSUInteger length, NSUInteger idx) { 120 @autoreleasepool { 121 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 122 [enc setFragmentBytes:bytes length:length atIndex:idx]; 123 } 124 } 125 126 static void renderEncSetVertexBytes(CFTypeRef renderEncRef, const void *bytes, NSUInteger length, NSUInteger idx) { 127 @autoreleasepool { 128 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 129 [enc setVertexBytes:bytes length:length atIndex:idx]; 130 } 131 } 132 133 static void renderEncSetRenderPipelineState(CFTypeRef renderEncRef, CFTypeRef pipeRef) { 134 @autoreleasepool { 135 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 136 id<MTLRenderPipelineState> pipe = (__bridge id<MTLRenderPipelineState>)pipeRef; 137 [enc setRenderPipelineState:pipe]; 138 } 139 } 140 141 static void renderEncDrawPrimitives(CFTypeRef renderEncRef, MTLPrimitiveType type, NSUInteger start, NSUInteger count) { 142 @autoreleasepool { 143 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 144 [enc drawPrimitives:type vertexStart:start vertexCount:count]; 145 } 146 } 147 148 static void renderEncDrawIndexedPrimitives(CFTypeRef renderEncRef, MTLPrimitiveType type, CFTypeRef bufRef, NSUInteger offset, NSUInteger count) { 149 @autoreleasepool { 150 id<MTLRenderCommandEncoder> enc = (__bridge id<MTLRenderCommandEncoder>)renderEncRef; 151 id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef; 152 [enc drawIndexedPrimitives:type indexCount:count indexType:MTLIndexTypeUInt16 indexBuffer:buf indexBufferOffset:offset]; 153 } 154 } 155 156 static void computeEncSetPipeline(CFTypeRef computeEncRef, CFTypeRef pipeRef) { 157 @autoreleasepool { 158 id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef; 159 id<MTLComputePipelineState> pipe = (__bridge id<MTLComputePipelineState>)pipeRef; 160 [enc setComputePipelineState:pipe]; 161 } 162 } 163 164 static void computeEncSetTexture(CFTypeRef computeEncRef, NSUInteger index, CFTypeRef texRef) { 165 @autoreleasepool { 166 id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef; 167 id<MTLTexture> tex = (__bridge id<MTLTexture>)texRef; 168 [enc setTexture:tex atIndex:index]; 169 } 170 } 171 172 static void computeEncEnd(CFTypeRef computeEncRef) { 173 @autoreleasepool { 174 id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef; 175 [enc endEncoding]; 176 } 177 } 178 179 static void computeEncSetBuffer(CFTypeRef computeEncRef, NSUInteger index, CFTypeRef bufRef) { 180 @autoreleasepool { 181 id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef; 182 id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef; 183 [enc setBuffer:buf offset:0 atIndex:index]; 184 } 185 } 186 187 static void computeEncDispatch(CFTypeRef computeEncRef, MTLSize threadgroupsPerGrid, MTLSize threadsPerThreadgroup) { 188 @autoreleasepool { 189 id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef; 190 [enc dispatchThreadgroups:threadgroupsPerGrid threadsPerThreadgroup:threadsPerThreadgroup]; 191 } 192 } 193 194 static void computeEncSetBytes(CFTypeRef computeEncRef, const void *bytes, NSUInteger length, NSUInteger index) { 195 @autoreleasepool { 196 id<MTLComputeCommandEncoder> enc = (__bridge id<MTLComputeCommandEncoder>)computeEncRef; 197 [enc setBytes:bytes length:length atIndex:index]; 198 } 199 } 200 201 static void blitEncEnd(CFTypeRef blitEncRef) { 202 @autoreleasepool { 203 id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef; 204 [enc endEncoding]; 205 } 206 } 207 208 static void blitEncCopyFromTexture(CFTypeRef blitEncRef, CFTypeRef srcRef, MTLOrigin srcOrig, MTLSize srcSize, CFTypeRef dstRef, MTLOrigin dstOrig) { 209 @autoreleasepool { 210 id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef; 211 id<MTLTexture> src = (__bridge id<MTLTexture>)srcRef; 212 id<MTLTexture> dst = (__bridge id<MTLTexture>)dstRef; 213 [enc copyFromTexture:src 214 sourceSlice:0 215 sourceLevel:0 216 sourceOrigin:srcOrig 217 sourceSize:srcSize 218 toTexture:dst 219 destinationSlice:0 220 destinationLevel:0 221 destinationOrigin:dstOrig]; 222 } 223 } 224 225 static void blitEncCopyBufferToTexture(CFTypeRef blitEncRef, CFTypeRef bufRef, CFTypeRef texRef, NSUInteger offset, NSUInteger stride, NSUInteger length, MTLSize dims, MTLOrigin orig) { 226 @autoreleasepool { 227 id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef; 228 id<MTLBuffer> src = (__bridge id<MTLBuffer>)bufRef; 229 id<MTLTexture> dst = (__bridge id<MTLTexture>)texRef; 230 [enc copyFromBuffer:src 231 sourceOffset:offset 232 sourceBytesPerRow:stride 233 sourceBytesPerImage:length 234 sourceSize:dims 235 toTexture:dst 236 destinationSlice:0 237 destinationLevel:0 238 destinationOrigin:orig]; 239 } 240 } 241 242 static void blitEncGenerateMipmapsForTexture(CFTypeRef blitEncRef, CFTypeRef texRef) { 243 @autoreleasepool { 244 id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef; 245 id<MTLTexture> tex = (__bridge id<MTLTexture>)texRef; 246 [enc generateMipmapsForTexture: tex]; 247 } 248 } 249 250 static void blitEncCopyTextureToBuffer(CFTypeRef blitEncRef, CFTypeRef texRef, CFTypeRef bufRef, NSUInteger offset, NSUInteger stride, NSUInteger length, MTLSize dims, MTLOrigin orig) { 251 @autoreleasepool { 252 id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef; 253 id<MTLTexture> src = (__bridge id<MTLTexture>)texRef; 254 id<MTLBuffer> dst = (__bridge id<MTLBuffer>)bufRef; 255 [enc copyFromTexture:src 256 sourceSlice:0 257 sourceLevel:0 258 sourceOrigin:orig 259 sourceSize:dims 260 toBuffer:dst 261 destinationOffset:offset 262 destinationBytesPerRow:stride 263 destinationBytesPerImage:length]; 264 } 265 } 266 267 static void blitEncCopyBufferToBuffer(CFTypeRef blitEncRef, CFTypeRef srcRef, CFTypeRef dstRef, NSUInteger srcOff, NSUInteger dstOff, NSUInteger size) { 268 @autoreleasepool { 269 id<MTLBlitCommandEncoder> enc = (__bridge id<MTLBlitCommandEncoder>)blitEncRef; 270 id<MTLBuffer> src = (__bridge id<MTLBuffer>)srcRef; 271 id<MTLBuffer> dst = (__bridge id<MTLBuffer>)dstRef; 272 [enc copyFromBuffer:src 273 sourceOffset:srcOff 274 toBuffer:dst 275 destinationOffset:dstOff 276 size:size]; 277 } 278 } 279 280 static CFTypeRef newTexture(CFTypeRef devRef, NSUInteger width, NSUInteger height, MTLPixelFormat format, MTLTextureUsage usage, int mipmapped) { 281 @autoreleasepool { 282 id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef; 283 MTLTextureDescriptor *mtlDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: format 284 width: width 285 height: height 286 mipmapped: mipmapped ? YES : NO]; 287 mtlDesc.usage = usage; 288 mtlDesc.storageMode = MTLStorageModePrivate; 289 return CFBridgingRetain([dev newTextureWithDescriptor:mtlDesc]); 290 } 291 } 292 293 static CFTypeRef newSampler(CFTypeRef devRef, MTLSamplerMinMagFilter minFilter, MTLSamplerMinMagFilter magFilter, MTLSamplerMipFilter mipFilter) { 294 @autoreleasepool { 295 id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef; 296 MTLSamplerDescriptor *desc = [MTLSamplerDescriptor new]; 297 desc.minFilter = minFilter; 298 desc.magFilter = magFilter; 299 desc.mipFilter = mipFilter; 300 return CFBridgingRetain([dev newSamplerStateWithDescriptor:desc]); 301 } 302 } 303 304 static CFTypeRef newBuffer(CFTypeRef devRef, NSUInteger size, MTLResourceOptions opts) { 305 @autoreleasepool { 306 id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef; 307 id<MTLBuffer> buf = [dev newBufferWithLength:size 308 options:opts]; 309 return CFBridgingRetain(buf); 310 } 311 } 312 313 static slice bufferContents(CFTypeRef bufRef) { 314 @autoreleasepool { 315 id<MTLBuffer> buf = (__bridge id<MTLBuffer>)bufRef; 316 slice s = {.addr = [buf contents], .size = [buf length]}; 317 return s; 318 } 319 } 320 321 static CFTypeRef newLibrary(CFTypeRef devRef, char *name, void *mtllib, size_t size) { 322 @autoreleasepool { 323 id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef; 324 dispatch_data_t data = dispatch_data_create(mtllib, size, DISPATCH_TARGET_QUEUE_DEFAULT, DISPATCH_DATA_DESTRUCTOR_DEFAULT); 325 id<MTLLibrary> lib = [dev newLibraryWithData:data error:nil]; 326 lib.label = [NSString stringWithUTF8String:name]; 327 return CFBridgingRetain(lib); 328 } 329 } 330 331 static CFTypeRef libraryNewFunction(CFTypeRef libRef, char *funcName) { 332 @autoreleasepool { 333 id<MTLLibrary> lib = (__bridge id<MTLLibrary>)libRef; 334 NSString *name = [NSString stringWithUTF8String:funcName]; 335 return CFBridgingRetain([lib newFunctionWithName:name]); 336 } 337 } 338 339 static CFTypeRef newComputePipeline(CFTypeRef devRef, CFTypeRef funcRef) { 340 @autoreleasepool { 341 id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef; 342 id<MTLFunction> func = (__bridge id<MTLFunction>)funcRef; 343 return CFBridgingRetain([dev newComputePipelineStateWithFunction:func error:nil]); 344 } 345 } 346 347 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) { 348 @autoreleasepool { 349 id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef; 350 id<MTLFunction> vfunc = (__bridge id<MTLFunction>)vertFunc; 351 id<MTLFunction> ffunc = (__bridge id<MTLFunction>)fragFunc; 352 MTLVertexDescriptor *vdesc = [MTLVertexDescriptor vertexDescriptor]; 353 vdesc.layouts[bufIdx].stride = stride; 354 for (NSUInteger i = 0; i < nverts; i++) { 355 vdesc.attributes[i].format = fmts[i]; 356 vdesc.attributes[i].offset = offsets[i]; 357 vdesc.attributes[i].bufferIndex = bufIdx; 358 } 359 MTLRenderPipelineDescriptor *desc = [MTLRenderPipelineDescriptor new]; 360 desc.vertexFunction = vfunc; 361 desc.fragmentFunction = ffunc; 362 desc.vertexDescriptor = vdesc; 363 for (NSUInteger i = 0; i < nvertBufs; i++) { 364 if (@available(iOS 11.0, *)) { 365 desc.vertexBuffers[i].mutability = MTLMutabilityImmutable; 366 } 367 } 368 for (NSUInteger i = 0; i < nfragBufs; i++) { 369 if (@available(iOS 11.0, *)) { 370 desc.fragmentBuffers[i].mutability = MTLMutabilityImmutable; 371 } 372 } 373 desc.colorAttachments[0].pixelFormat = pixelFormat; 374 desc.colorAttachments[0].blendingEnabled = blend ? YES : NO; 375 desc.colorAttachments[0].sourceAlphaBlendFactor = srcFactor; 376 desc.colorAttachments[0].sourceRGBBlendFactor = srcFactor; 377 desc.colorAttachments[0].destinationAlphaBlendFactor = dstFactor; 378 desc.colorAttachments[0].destinationRGBBlendFactor = dstFactor; 379 return CFBridgingRetain([dev newRenderPipelineStateWithDescriptor:desc 380 error:nil]); 381 } 382 } 383 */ 384 import "C" 385 386 type Backend struct { 387 dev C.CFTypeRef 388 queue C.CFTypeRef 389 pixelFmt C.MTLPixelFormat 390 391 cmdBuffer C.CFTypeRef 392 lastCmdBuffer C.CFTypeRef 393 renderEnc C.CFTypeRef 394 computeEnc C.CFTypeRef 395 blitEnc C.CFTypeRef 396 397 prog *Program 398 topology C.MTLPrimitiveType 399 400 stagingBuf C.CFTypeRef 401 stagingOff int 402 403 indexBuf *Buffer 404 405 // bufSizes is scratch space for filling out the spvBufferSizeConstants 406 // that spirv-cross generates for emulating buffer.length expressions in 407 // shaders. 408 bufSizes []uint32 409 } 410 411 type Texture struct { 412 backend *Backend 413 texture C.CFTypeRef 414 sampler C.CFTypeRef 415 width int 416 height int 417 mipmap bool 418 foreign bool 419 } 420 421 type Shader struct { 422 function C.CFTypeRef 423 inputs []shader.InputLocation 424 } 425 426 type Program struct { 427 pipeline C.CFTypeRef 428 groupSize [3]int 429 } 430 431 type Pipeline struct { 432 pipeline C.CFTypeRef 433 topology C.MTLPrimitiveType 434 } 435 436 type Buffer struct { 437 backend *Backend 438 size int 439 buffer C.CFTypeRef 440 441 // store is the buffer contents For buffers not allocated on the GPU. 442 store []byte 443 } 444 445 const ( 446 uniformBufferIndex = 0 447 attributeBufferIndex = 1 448 449 spvBufferSizeConstantsBinding = 25 450 ) 451 452 const ( 453 texUnits = 4 454 bufferUnits = 4 455 ) 456 457 func init() { 458 driver.NewMetalDevice = newMetalDevice 459 } 460 461 func newMetalDevice(api driver.Metal) (driver.Device, error) { 462 dev := C.CFTypeRef(api.Device) 463 C.CFRetain(dev) 464 queue := C.CFTypeRef(api.Queue) 465 C.CFRetain(queue) 466 b := &Backend{ 467 dev: dev, 468 queue: queue, 469 pixelFmt: C.MTLPixelFormat(api.PixelFormat), 470 bufSizes: make([]uint32, bufferUnits), 471 } 472 return b, nil 473 } 474 475 func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture { 476 if b.lastCmdBuffer != 0 { 477 C.cmdBufferWaitUntilCompleted(b.lastCmdBuffer) 478 b.stagingOff = 0 479 } 480 if target == nil { 481 return nil 482 } 483 switch t := target.(type) { 484 case driver.MetalRenderTarget: 485 texture := C.CFTypeRef(t.Texture) 486 return &Texture{texture: texture, foreign: true} 487 case *Texture: 488 return t 489 default: 490 panic(fmt.Sprintf("metal: unsupported render target type: %T", t)) 491 } 492 } 493 494 func (b *Backend) startBlit() C.CFTypeRef { 495 if b.blitEnc != 0 { 496 return b.blitEnc 497 } 498 b.endEncoder() 499 b.ensureCmdBuffer() 500 b.blitEnc = C.cmdBufferBlitEncoder(b.cmdBuffer) 501 if b.blitEnc == 0 { 502 panic("metal: [MTLCommandBuffer blitCommandEncoder:] failed") 503 } 504 return b.blitEnc 505 } 506 507 func (b *Backend) CopyTexture(dst driver.Texture, dorig image.Point, src driver.Texture, srect image.Rectangle) { 508 enc := b.startBlit() 509 dstTex := dst.(*Texture).texture 510 srcTex := src.(*Texture).texture 511 ssz := srect.Size() 512 C.blitEncCopyFromTexture( 513 enc, 514 srcTex, 515 C.MTLOrigin{ 516 x: C.NSUInteger(srect.Min.X), 517 y: C.NSUInteger(srect.Min.Y), 518 }, 519 C.MTLSize{ 520 width: C.NSUInteger(ssz.X), 521 height: C.NSUInteger(ssz.Y), 522 depth: 1, 523 }, 524 dstTex, 525 C.MTLOrigin{ 526 x: C.NSUInteger(dorig.X), 527 y: C.NSUInteger(dorig.Y), 528 }, 529 ) 530 } 531 532 func (b *Backend) EndFrame() { 533 b.endCmdBuffer(false) 534 } 535 536 func (b *Backend) endCmdBuffer(wait bool) { 537 b.endEncoder() 538 if b.cmdBuffer == 0 { 539 return 540 } 541 C.cmdBufferCommit(b.cmdBuffer) 542 if wait { 543 C.cmdBufferWaitUntilCompleted(b.cmdBuffer) 544 } 545 if b.lastCmdBuffer != 0 { 546 C.CFRelease(b.lastCmdBuffer) 547 } 548 b.lastCmdBuffer = b.cmdBuffer 549 b.cmdBuffer = 0 550 } 551 552 func (b *Backend) Caps() driver.Caps { 553 return driver.Caps{ 554 MaxTextureSize: 8192, 555 Features: driver.FeatureSRGB | driver.FeatureCompute | driver.FeatureFloatRenderTargets, 556 } 557 } 558 559 func (b *Backend) NewTimer() driver.Timer { 560 panic("timers not supported") 561 } 562 563 func (b *Backend) IsTimeContinuous() bool { 564 panic("timers not supported") 565 } 566 567 func (b *Backend) Release() { 568 if b.cmdBuffer != 0 { 569 C.CFRelease(b.cmdBuffer) 570 } 571 if b.lastCmdBuffer != 0 { 572 C.CFRelease(b.lastCmdBuffer) 573 } 574 if b.stagingBuf != 0 { 575 C.CFRelease(b.stagingBuf) 576 } 577 C.CFRelease(b.queue) 578 C.CFRelease(b.dev) 579 *b = Backend{} 580 } 581 582 func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, bindings driver.BufferBinding) (driver.Texture, error) { 583 mformat := pixelFormatFor(format) 584 var usage C.MTLTextureUsage 585 if bindings&(driver.BufferBindingTexture|driver.BufferBindingShaderStorageRead) != 0 { 586 usage |= C.MTLTextureUsageShaderRead 587 } 588 if bindings&driver.BufferBindingFramebuffer != 0 { 589 usage |= C.MTLTextureUsageRenderTarget 590 } 591 if bindings&driver.BufferBindingShaderStorageWrite != 0 { 592 usage |= C.MTLTextureUsageShaderWrite 593 } 594 min, mip := samplerFilterFor(minFilter) 595 max, _ := samplerFilterFor(magFilter) 596 mipmap := mip != C.MTLSamplerMipFilterNotMipmapped 597 mipmapped := C.int(0) 598 if mipmap { 599 mipmapped = 1 600 } 601 tex := C.newTexture(b.dev, C.NSUInteger(width), C.NSUInteger(height), mformat, usage, mipmapped) 602 if tex == 0 { 603 return nil, errors.New("metal: [MTLDevice newTextureWithDescriptor:] failed") 604 } 605 s := C.newSampler(b.dev, min, max, mip) 606 if s == 0 { 607 C.CFRelease(tex) 608 return nil, errors.New("metal: [MTLDevice newSamplerStateWithDescriptor:] failed") 609 } 610 return &Texture{backend: b, texture: tex, sampler: s, width: width, height: height, mipmap: mipmap}, nil 611 } 612 613 func samplerFilterFor(f driver.TextureFilter) (C.MTLSamplerMinMagFilter, C.MTLSamplerMipFilter) { 614 switch f { 615 case driver.FilterNearest: 616 return C.MTLSamplerMinMagFilterNearest, C.MTLSamplerMipFilterNotMipmapped 617 case driver.FilterLinear: 618 return C.MTLSamplerMinMagFilterLinear, C.MTLSamplerMipFilterNotMipmapped 619 case driver.FilterLinearMipmapLinear: 620 return C.MTLSamplerMinMagFilterLinear, C.MTLSamplerMipFilterLinear 621 default: 622 panic("invalid texture filter") 623 } 624 } 625 626 func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) { 627 vsh, fsh := desc.VertexShader.(*Shader), desc.FragmentShader.(*Shader) 628 layout := desc.VertexLayout.Inputs 629 if got, exp := len(layout), len(vsh.inputs); got != exp { 630 return nil, fmt.Errorf("metal: number of input descriptors (%d) doesn't match number of inputs (%d)", got, exp) 631 } 632 formats := make([]C.MTLVertexFormat, len(layout)) 633 offsets := make([]C.NSUInteger, len(layout)) 634 for i, inp := range layout { 635 index := vsh.inputs[i].Location 636 formats[index] = vertFormatFor(vsh.inputs[i]) 637 offsets[index] = C.NSUInteger(inp.Offset) 638 } 639 var ( 640 fmtPtr *C.MTLVertexFormat 641 offPtr *C.NSUInteger 642 ) 643 if len(layout) > 0 { 644 fmtPtr = &formats[0] 645 offPtr = &offsets[0] 646 } 647 srcFactor := blendFactorFor(desc.BlendDesc.SrcFactor) 648 dstFactor := blendFactorFor(desc.BlendDesc.DstFactor) 649 blend := C.int(0) 650 if desc.BlendDesc.Enable { 651 blend = 1 652 } 653 pf := b.pixelFmt 654 if f := desc.PixelFormat; f != driver.TextureFormatOutput { 655 pf = pixelFormatFor(f) 656 } 657 pipe := C.newRenderPipeline( 658 b.dev, 659 vsh.function, 660 fsh.function, 661 pf, 662 attributeBufferIndex, 663 C.NSUInteger(len(layout)), fmtPtr, offPtr, 664 C.NSUInteger(desc.VertexLayout.Stride), 665 blend, srcFactor, dstFactor, 666 2, // Number of vertex buffers. 667 1, // Number of fragment buffers. 668 ) 669 if pipe == 0 { 670 return nil, errors.New("metal: pipeline construction failed") 671 } 672 return &Pipeline{pipeline: pipe, topology: primitiveFor(desc.Topology)}, nil 673 } 674 675 func dataTypeSize(d shader.DataType) int { 676 switch d { 677 case shader.DataTypeFloat: 678 return 4 679 default: 680 panic("unsupported data type") 681 } 682 } 683 684 func blendFactorFor(f driver.BlendFactor) C.MTLBlendFactor { 685 switch f { 686 case driver.BlendFactorZero: 687 return C.MTLBlendFactorZero 688 case driver.BlendFactorOne: 689 return C.MTLBlendFactorOne 690 case driver.BlendFactorOneMinusSrcAlpha: 691 return C.MTLBlendFactorOneMinusSourceAlpha 692 case driver.BlendFactorDstColor: 693 return C.MTLBlendFactorDestinationColor 694 default: 695 panic("unsupported blend factor") 696 } 697 } 698 699 func vertFormatFor(f shader.InputLocation) C.MTLVertexFormat { 700 t := f.Type 701 s := f.Size 702 switch { 703 case t == shader.DataTypeFloat && s == 1: 704 return C.MTLVertexFormatFloat 705 case t == shader.DataTypeFloat && s == 2: 706 return C.MTLVertexFormatFloat2 707 case t == shader.DataTypeFloat && s == 3: 708 return C.MTLVertexFormatFloat3 709 case t == shader.DataTypeFloat && s == 4: 710 return C.MTLVertexFormatFloat4 711 default: 712 panic("unsupported data type") 713 } 714 } 715 716 func pixelFormatFor(f driver.TextureFormat) C.MTLPixelFormat { 717 switch f { 718 case driver.TextureFormatFloat: 719 return C.MTLPixelFormatR16Float 720 case driver.TextureFormatRGBA8: 721 return C.MTLPixelFormatRGBA8Unorm 722 case driver.TextureFormatSRGBA: 723 return C.MTLPixelFormatRGBA8Unorm_sRGB 724 default: 725 panic("unsupported pixel format") 726 } 727 } 728 729 func (b *Backend) NewBuffer(typ driver.BufferBinding, size int) (driver.Buffer, error) { 730 // Transfer buffer contents in command encoders on every use for 731 // smaller buffers. The advantage is that buffer re-use during a frame 732 // won't occur a GPU wait. 733 // We can't do this for buffers written to by the GPU and read by the client, 734 // and Metal doesn't require a buffer for indexed draws. 735 if size <= 4096 && typ&(driver.BufferBindingShaderStorageWrite|driver.BufferBindingIndices) == 0 { 736 return &Buffer{size: size, store: make([]byte, size)}, nil 737 } 738 buf := C.newBuffer(b.dev, C.NSUInteger(size), C.MTLResourceStorageModePrivate) 739 return &Buffer{backend: b, size: size, buffer: buf}, nil 740 } 741 742 func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) { 743 buf, err := b.NewBuffer(typ, len(data)) 744 if err != nil { 745 return nil, err 746 } 747 buf.Upload(data) 748 return buf, nil 749 } 750 751 func (b *Backend) NewComputeProgram(src shader.Sources) (driver.Program, error) { 752 sh, err := b.newShader(src) 753 if err != nil { 754 return nil, err 755 } 756 defer sh.Release() 757 pipe := C.newComputePipeline(b.dev, sh.function) 758 if pipe == 0 { 759 return nil, fmt.Errorf("metal: compute program %q load failed", src.Name) 760 } 761 return &Program{pipeline: pipe, groupSize: src.WorkgroupSize}, nil 762 } 763 764 func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) { 765 return b.newShader(src) 766 } 767 768 func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) { 769 return b.newShader(src) 770 } 771 772 func (b *Backend) newShader(src shader.Sources) (*Shader, error) { 773 vsrc := []byte(src.MetalLib) 774 cname := C.CString(src.Name) 775 defer C.free(unsafe.Pointer(cname)) 776 vlib := C.newLibrary(b.dev, cname, unsafe.Pointer(&vsrc[0]), C.size_t(len(vsrc))) 777 if vlib == 0 { 778 return nil, fmt.Errorf("metal: vertex shader %q load failed", src.Name) 779 } 780 defer C.CFRelease(vlib) 781 funcName := C.CString("main0") 782 defer C.free(unsafe.Pointer(funcName)) 783 f := C.libraryNewFunction(vlib, funcName) 784 if f == 0 { 785 return nil, fmt.Errorf("metal: main function not found in %q", src.Name) 786 } 787 return &Shader{function: f, inputs: src.Inputs}, nil 788 } 789 790 func (b *Backend) Viewport(x, y, width, height int) { 791 enc := b.renderEnc 792 if enc == 0 { 793 panic("no active render pass") 794 } 795 C.renderEncViewport(enc, C.MTLViewport{ 796 originX: C.double(x), 797 originY: C.double(y), 798 width: C.double(width), 799 height: C.double(height), 800 znear: 0.0, 801 zfar: 1.0, 802 }) 803 } 804 805 func (b *Backend) DrawArrays(off, count int) { 806 enc := b.renderEnc 807 if enc == 0 { 808 panic("no active render pass") 809 } 810 C.renderEncDrawPrimitives(enc, b.topology, C.NSUInteger(off), C.NSUInteger(count)) 811 } 812 813 func (b *Backend) DrawElements(off, count int) { 814 enc := b.renderEnc 815 if enc == 0 { 816 panic("no active render pass") 817 } 818 C.renderEncDrawIndexedPrimitives(enc, b.topology, b.indexBuf.buffer, C.NSUInteger(off), C.NSUInteger(count)) 819 } 820 821 func primitiveFor(mode driver.Topology) C.MTLPrimitiveType { 822 switch mode { 823 case driver.TopologyTriangles: 824 return C.MTLPrimitiveTypeTriangle 825 case driver.TopologyTriangleStrip: 826 return C.MTLPrimitiveTypeTriangleStrip 827 default: 828 panic("metal: unknown draw mode") 829 } 830 } 831 832 func (b *Backend) BindImageTexture(unit int, tex driver.Texture) { 833 b.BindTexture(unit, tex) 834 } 835 836 func (b *Backend) BeginCompute() { 837 b.endEncoder() 838 b.ensureCmdBuffer() 839 for i := range b.bufSizes { 840 b.bufSizes[i] = 0 841 } 842 b.computeEnc = C.cmdBufferComputeEncoder(b.cmdBuffer) 843 if b.computeEnc == 0 { 844 panic("metal: [MTLCommandBuffer computeCommandEncoder:] failed") 845 } 846 } 847 848 func (b *Backend) EndCompute() { 849 if b.computeEnc == 0 { 850 panic("no active compute pass") 851 } 852 C.computeEncEnd(b.computeEnc) 853 C.CFRelease(b.computeEnc) 854 b.computeEnc = 0 855 } 856 857 func (b *Backend) DispatchCompute(x, y, z int) { 858 enc := b.computeEnc 859 if enc == 0 { 860 panic("no active compute pass") 861 } 862 C.computeEncSetBytes(enc, unsafe.Pointer(&b.bufSizes[0]), C.NSUInteger(len(b.bufSizes)*4), spvBufferSizeConstantsBinding) 863 threadgroupsPerGrid := C.MTLSize{ 864 width: C.NSUInteger(x), height: C.NSUInteger(y), depth: C.NSUInteger(z), 865 } 866 sz := b.prog.groupSize 867 threadsPerThreadgroup := C.MTLSize{ 868 width: C.NSUInteger(sz[0]), height: C.NSUInteger(sz[1]), depth: C.NSUInteger(sz[2]), 869 } 870 C.computeEncDispatch(enc, threadgroupsPerGrid, threadsPerThreadgroup) 871 } 872 873 func (b *Backend) stagingBuffer(size int) (C.CFTypeRef, int) { 874 if b.stagingBuf == 0 || b.stagingOff+size > len(bufferStore(b.stagingBuf)) { 875 if b.stagingBuf != 0 { 876 C.CFRelease(b.stagingBuf) 877 } 878 cap := 2 * (b.stagingOff + size) 879 b.stagingBuf = C.newBuffer(b.dev, C.NSUInteger(cap), C.MTLResourceStorageModeShared|C.MTLResourceCPUCacheModeWriteCombined) 880 if b.stagingBuf == 0 { 881 panic(fmt.Errorf("metal: failed to allocate %d bytes of staging buffer", cap)) 882 } 883 b.stagingOff = 0 884 } 885 off := b.stagingOff 886 b.stagingOff += size 887 return b.stagingBuf, off 888 } 889 890 func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) { 891 if len(pixels) == 0 { 892 return 893 } 894 if stride == 0 { 895 stride = size.X * 4 896 } 897 dstStride := size.X * 4 898 n := size.Y * dstStride 899 buf, off := t.backend.stagingBuffer(n) 900 store := bufferSlice(buf, off, n) 901 var srcOff, dstOff int 902 for y := 0; y < size.Y; y++ { 903 srcRow := pixels[srcOff : srcOff+dstStride] 904 dstRow := store[dstOff : dstOff+dstStride] 905 copy(dstRow, srcRow) 906 dstOff += dstStride 907 srcOff += stride 908 } 909 enc := t.backend.startBlit() 910 orig := C.MTLOrigin{ 911 x: C.NSUInteger(offset.X), 912 y: C.NSUInteger(offset.Y), 913 } 914 msize := C.MTLSize{ 915 width: C.NSUInteger(size.X), 916 height: C.NSUInteger(size.Y), 917 depth: 1, 918 } 919 C.blitEncCopyBufferToTexture(enc, buf, t.texture, C.NSUInteger(off), C.NSUInteger(dstStride), C.NSUInteger(len(store)), msize, orig) 920 if t.mipmap { 921 C.blitEncGenerateMipmapsForTexture(enc, t.texture) 922 } 923 } 924 925 func (t *Texture) Release() { 926 if t.foreign { 927 panic("metal: release of external texture") 928 } 929 C.CFRelease(t.texture) 930 C.CFRelease(t.sampler) 931 *t = Texture{} 932 } 933 934 func (p *Pipeline) Release() { 935 C.CFRelease(p.pipeline) 936 *p = Pipeline{} 937 } 938 939 func (b *Backend) PrepareTexture(tex driver.Texture) {} 940 941 func (b *Backend) BindTexture(unit int, tex driver.Texture) { 942 t := tex.(*Texture) 943 if enc := b.renderEnc; enc != 0 { 944 C.renderEncSetFragmentTexture(enc, C.NSUInteger(unit), t.texture) 945 C.renderEncSetFragmentSamplerState(enc, C.NSUInteger(unit), t.sampler) 946 } else if enc := b.computeEnc; enc != 0 { 947 C.computeEncSetTexture(enc, C.NSUInteger(unit), t.texture) 948 } else { 949 panic("no active render nor compute pass") 950 } 951 } 952 953 func (b *Backend) ensureCmdBuffer() { 954 if b.cmdBuffer != 0 { 955 return 956 } 957 b.cmdBuffer = C.queueNewBuffer(b.queue) 958 if b.cmdBuffer == 0 { 959 panic("metal: [MTLCommandQueue cmdBuffer] failed") 960 } 961 } 962 963 func (b *Backend) BindPipeline(pipe driver.Pipeline) { 964 p := pipe.(*Pipeline) 965 enc := b.renderEnc 966 if enc == 0 { 967 panic("no active render pass") 968 } 969 C.renderEncSetRenderPipelineState(enc, p.pipeline) 970 b.topology = p.topology 971 } 972 973 func (b *Backend) BindProgram(prog driver.Program) { 974 enc := b.computeEnc 975 if enc == 0 { 976 panic("no active compute pass") 977 } 978 p := prog.(*Program) 979 C.computeEncSetPipeline(enc, p.pipeline) 980 b.prog = p 981 } 982 983 func (s *Shader) Release() { 984 C.CFRelease(s.function) 985 *s = Shader{} 986 } 987 988 func (p *Program) Release() { 989 C.CFRelease(p.pipeline) 990 *p = Program{} 991 } 992 993 func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) { 994 buf := buffer.(*Buffer) 995 b.bufSizes[binding] = uint32(buf.size) 996 enc := b.computeEnc 997 if enc == 0 { 998 panic("no active compute pass") 999 } 1000 if buf.buffer != 0 { 1001 C.computeEncSetBuffer(enc, C.NSUInteger(binding), buf.buffer) 1002 } else if buf.size > 0 { 1003 C.computeEncSetBytes(enc, unsafe.Pointer(&buf.store[0]), C.NSUInteger(buf.size), C.NSUInteger(binding)) 1004 } 1005 } 1006 1007 func (b *Backend) BindUniforms(buf driver.Buffer) { 1008 bf := buf.(*Buffer) 1009 enc := b.renderEnc 1010 if enc == 0 { 1011 panic("no active render pass") 1012 } 1013 if bf.buffer != 0 { 1014 C.renderEncSetVertexBuffer(enc, bf.buffer, uniformBufferIndex, 0) 1015 C.renderEncSetFragmentBuffer(enc, bf.buffer, uniformBufferIndex, 0) 1016 } else if bf.size > 0 { 1017 C.renderEncSetVertexBytes(enc, unsafe.Pointer(&bf.store[0]), C.NSUInteger(bf.size), uniformBufferIndex) 1018 C.renderEncSetFragmentBytes(enc, unsafe.Pointer(&bf.store[0]), C.NSUInteger(bf.size), uniformBufferIndex) 1019 } 1020 } 1021 1022 func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) { 1023 bf := buf.(*Buffer) 1024 enc := b.renderEnc 1025 if enc == 0 { 1026 panic("no active render pass") 1027 } 1028 if bf.buffer != 0 { 1029 C.renderEncSetVertexBuffer(enc, bf.buffer, attributeBufferIndex, C.NSUInteger(offset)) 1030 } else if n := bf.size - offset; n > 0 { 1031 C.renderEncSetVertexBytes(enc, unsafe.Pointer(&bf.store[offset]), C.NSUInteger(n), attributeBufferIndex) 1032 } 1033 } 1034 1035 func (b *Backend) BindIndexBuffer(buf driver.Buffer) { 1036 b.indexBuf = buf.(*Buffer) 1037 } 1038 1039 func (b *Buffer) Download(data []byte) error { 1040 if len(data) > b.size { 1041 panic(fmt.Errorf("len(data) (%d) larger than len(content) (%d)", len(data), b.size)) 1042 } 1043 buf, off := b.backend.stagingBuffer(len(data)) 1044 enc := b.backend.startBlit() 1045 C.blitEncCopyBufferToBuffer(enc, b.buffer, buf, 0, C.NSUInteger(off), C.NSUInteger(len(data))) 1046 b.backend.endCmdBuffer(true) 1047 store := bufferSlice(buf, off, len(data)) 1048 copy(data, store) 1049 return nil 1050 } 1051 1052 func (b *Buffer) Upload(data []byte) { 1053 if len(data) > b.size { 1054 panic(fmt.Errorf("len(data) (%d) larger than len(content) (%d)", len(data), b.size)) 1055 } 1056 if b.buffer == 0 { 1057 copy(b.store, data) 1058 return 1059 } 1060 buf, off := b.backend.stagingBuffer(len(data)) 1061 store := bufferSlice(buf, off, len(data)) 1062 copy(store, data) 1063 enc := b.backend.startBlit() 1064 C.blitEncCopyBufferToBuffer(enc, buf, b.buffer, C.NSUInteger(off), 0, C.NSUInteger(len(store))) 1065 } 1066 1067 func bufferStore(buf C.CFTypeRef) []byte { 1068 contents := C.bufferContents(buf) 1069 return (*(*[1 << 30]byte)(contents.addr))[:contents.size:contents.size] 1070 } 1071 1072 func bufferSlice(buf C.CFTypeRef, off, len int) []byte { 1073 store := bufferStore(buf) 1074 return store[off : off+len] 1075 } 1076 1077 func (b *Buffer) Release() { 1078 if b.buffer != 0 { 1079 C.CFRelease(b.buffer) 1080 } 1081 *b = Buffer{} 1082 } 1083 1084 func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error { 1085 if len(pixels) == 0 { 1086 return nil 1087 } 1088 sz := src.Size() 1089 orig := C.MTLOrigin{ 1090 x: C.NSUInteger(src.Min.X), 1091 y: C.NSUInteger(src.Min.Y), 1092 } 1093 msize := C.MTLSize{ 1094 width: C.NSUInteger(sz.X), 1095 height: C.NSUInteger(sz.Y), 1096 depth: 1, 1097 } 1098 stageStride := sz.X * 4 1099 n := sz.Y * stageStride 1100 buf, off := t.backend.stagingBuffer(n) 1101 enc := t.backend.startBlit() 1102 C.blitEncCopyTextureToBuffer(enc, t.texture, buf, C.NSUInteger(off), C.NSUInteger(stageStride), C.NSUInteger(n), msize, orig) 1103 t.backend.endCmdBuffer(true) 1104 store := bufferSlice(buf, off, n) 1105 var srcOff, dstOff int 1106 for y := 0; y < sz.Y; y++ { 1107 dstRow := pixels[srcOff : srcOff+stageStride] 1108 srcRow := store[dstOff : dstOff+stageStride] 1109 copy(dstRow, srcRow) 1110 dstOff += stageStride 1111 srcOff += stride 1112 } 1113 return nil 1114 } 1115 1116 func (b *Backend) BeginRenderPass(tex driver.Texture, d driver.LoadDesc) { 1117 b.endEncoder() 1118 b.ensureCmdBuffer() 1119 f := tex.(*Texture) 1120 col := d.ClearColor 1121 var act C.MTLLoadAction 1122 switch d.Action { 1123 case driver.LoadActionKeep: 1124 act = C.MTLLoadActionLoad 1125 case driver.LoadActionClear: 1126 act = C.MTLLoadActionClear 1127 case driver.LoadActionInvalidate: 1128 act = C.MTLLoadActionDontCare 1129 } 1130 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)) 1131 if b.renderEnc == 0 { 1132 panic("metal: [MTLCommandBuffer renderCommandEncoderWithDescriptor:] failed") 1133 } 1134 } 1135 1136 func (b *Backend) EndRenderPass() { 1137 if b.renderEnc == 0 { 1138 panic("no active render pass") 1139 } 1140 C.renderEncEnd(b.renderEnc) 1141 C.CFRelease(b.renderEnc) 1142 b.renderEnc = 0 1143 } 1144 1145 func (b *Backend) endEncoder() { 1146 if b.renderEnc != 0 { 1147 panic("active render pass") 1148 } 1149 if b.computeEnc != 0 { 1150 panic("active compute pass") 1151 } 1152 if b.blitEnc != 0 { 1153 C.blitEncEnd(b.blitEnc) 1154 C.CFRelease(b.blitEnc) 1155 b.blitEnc = 0 1156 } 1157 } 1158 1159 func (f *Texture) ImplementsRenderTarget() {}