graphics.go (37617B)
1 // Copyright 2018 The Ebiten Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //go:build darwin 16 // +build darwin 17 18 package metal 19 20 import ( 21 "fmt" 22 "math" 23 "sort" 24 "strings" 25 "unsafe" 26 27 "github.com/hajimehoshi/ebiten/v2/internal/driver" 28 "github.com/hajimehoshi/ebiten/v2/internal/graphics" 29 "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca" 30 "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl" 31 "github.com/hajimehoshi/ebiten/v2/internal/shaderir" 32 ) 33 34 // #cgo CFLAGS: -x objective-c 35 // #cgo !ios CFLAGS: -mmacosx-version-min=10.12 36 // #cgo LDFLAGS: -framework Foundation 37 // 38 // #import <Foundation/Foundation.h> 39 // 40 // static void* allocAutoreleasePool() { 41 // return [[NSAutoreleasePool alloc] init]; 42 // } 43 // 44 // static void releaseAutoreleasePool(void* pool) { 45 // [(NSAutoreleasePool*)pool release]; 46 // } 47 import "C" 48 49 const source = `#include <metal_stdlib> 50 51 #define FILTER_NEAREST {{.FilterNearest}} 52 #define FILTER_LINEAR {{.FilterLinear}} 53 #define FILTER_SCREEN {{.FilterScreen}} 54 55 #define ADDRESS_CLAMP_TO_ZERO {{.AddressClampToZero}} 56 #define ADDRESS_REPEAT {{.AddressRepeat}} 57 #define ADDRESS_UNSAFE {{.AddressUnsafe}} 58 59 using namespace metal; 60 61 struct VertexIn { 62 packed_float2 position; 63 packed_float2 tex; 64 packed_float4 color; 65 }; 66 67 struct VertexOut { 68 float4 position [[position]]; 69 float2 tex; 70 float4 color; 71 }; 72 73 vertex VertexOut VertexShader( 74 uint vid [[vertex_id]], 75 const device VertexIn* vertices [[buffer(0)]], 76 constant float2& viewport_size [[buffer(1)]] 77 ) { 78 float4x4 projectionMatrix = float4x4( 79 float4(2.0 / viewport_size.x, 0, 0, 0), 80 float4(0, 2.0 / viewport_size.y, 0, 0), 81 float4(0, 0, 1, 0), 82 float4(-1, -1, 0, 1) 83 ); 84 85 VertexIn in = vertices[vid]; 86 VertexOut out = { 87 .position = projectionMatrix * float4(in.position, 0, 1), 88 .tex = in.tex, 89 .color = in.color, 90 }; 91 92 return out; 93 } 94 95 float FloorMod(float x, float y) { 96 if (x < 0.0) { 97 return y - (-x - y * floor(-x/y)); 98 } 99 return x - y * floor(x/y); 100 } 101 102 template<uint8_t address> 103 float2 AdjustTexelByAddress(float2 p, float4 source_region); 104 105 template<> 106 inline float2 AdjustTexelByAddress<ADDRESS_CLAMP_TO_ZERO>(float2 p, float4 source_region) { 107 return p; 108 } 109 110 template<> 111 inline float2 AdjustTexelByAddress<ADDRESS_REPEAT>(float2 p, float4 source_region) { 112 float2 o = float2(source_region[0], source_region[1]); 113 float2 size = float2(source_region[2] - source_region[0], source_region[3] - source_region[1]); 114 return float2(FloorMod((p.x - o.x), size.x) + o.x, FloorMod((p.y - o.y), size.y) + o.y); 115 } 116 117 template<uint8_t filter, uint8_t address> 118 struct ColorFromTexel; 119 120 constexpr sampler texture_sampler{filter::nearest}; 121 122 template<> 123 struct ColorFromTexel<FILTER_NEAREST, ADDRESS_UNSAFE> { 124 inline float4 Do(VertexOut v, texture2d<float> texture, constant float2& source_size, float scale, constant float4& source_region) { 125 float2 p = v.tex; 126 return texture.sample(texture_sampler, p); 127 } 128 }; 129 130 template<uint8_t address> 131 struct ColorFromTexel<FILTER_NEAREST, address> { 132 inline float4 Do(VertexOut v, texture2d<float> texture, constant float2& source_size, float scale, constant float4& source_region) { 133 float2 p = AdjustTexelByAddress<address>(v.tex, source_region); 134 if (source_region[0] <= p.x && 135 source_region[1] <= p.y && 136 p.x < source_region[2] && 137 p.y < source_region[3]) { 138 return texture.sample(texture_sampler, p); 139 } 140 return 0.0; 141 } 142 }; 143 144 template<> 145 struct ColorFromTexel<FILTER_LINEAR, ADDRESS_UNSAFE> { 146 inline float4 Do(VertexOut v, texture2d<float> texture, constant float2& source_size, float scale, constant float4& source_region) { 147 const float2 texel_size = 1 / source_size; 148 149 // Shift 1/512 [texel] to avoid the tie-breaking issue. 150 // As all the vertex positions are aligned to 1/16 [pixel], this shiting should work in most cases. 151 float2 p0 = v.tex - texel_size / 2.0 + (texel_size / 512.0); 152 float2 p1 = v.tex + texel_size / 2.0 + (texel_size / 512.0); 153 154 float4 c0 = texture.sample(texture_sampler, p0); 155 float4 c1 = texture.sample(texture_sampler, float2(p1.x, p0.y)); 156 float4 c2 = texture.sample(texture_sampler, float2(p0.x, p1.y)); 157 float4 c3 = texture.sample(texture_sampler, p1); 158 159 float2 rate = fract(p0 * source_size); 160 return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y); 161 } 162 }; 163 164 template<uint8_t address> 165 struct ColorFromTexel<FILTER_LINEAR, address> { 166 inline float4 Do(VertexOut v, texture2d<float> texture, constant float2& source_size, float scale, constant float4& source_region) { 167 const float2 texel_size = 1 / source_size; 168 169 // Shift 1/512 [texel] to avoid the tie-breaking issue. 170 // As all the vertex positions are aligned to 1/16 [pixel], this shiting should work in most cases. 171 float2 p0 = v.tex - texel_size / 2.0 + (texel_size / 512.0); 172 float2 p1 = v.tex + texel_size / 2.0 + (texel_size / 512.0); 173 p0 = AdjustTexelByAddress<address>(p0, source_region); 174 p1 = AdjustTexelByAddress<address>(p1, source_region); 175 176 float4 c0 = texture.sample(texture_sampler, p0); 177 float4 c1 = texture.sample(texture_sampler, float2(p1.x, p0.y)); 178 float4 c2 = texture.sample(texture_sampler, float2(p0.x, p1.y)); 179 float4 c3 = texture.sample(texture_sampler, p1); 180 181 if (p0.x < source_region[0]) { 182 c0 = 0; 183 c2 = 0; 184 } 185 if (p0.y < source_region[1]) { 186 c0 = 0; 187 c1 = 0; 188 } 189 if (source_region[2] <= p1.x) { 190 c1 = 0; 191 c3 = 0; 192 } 193 if (source_region[3] <= p1.y) { 194 c2 = 0; 195 c3 = 0; 196 } 197 198 float2 rate = fract(p0 * source_size); 199 return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y); 200 } 201 }; 202 203 template<uint8_t address> 204 struct ColorFromTexel<FILTER_SCREEN, address> { 205 inline float4 Do(VertexOut v, texture2d<float> texture, constant float2& source_size, float scale, constant float4& source_region) { 206 const float2 texel_size = 1 / source_size; 207 208 float2 p0 = v.tex - texel_size / 2.0 / scale + (texel_size / 512.0); 209 float2 p1 = v.tex + texel_size / 2.0 / scale + (texel_size / 512.0); 210 211 float4 c0 = texture.sample(texture_sampler, p0); 212 float4 c1 = texture.sample(texture_sampler, float2(p1.x, p0.y)); 213 float4 c2 = texture.sample(texture_sampler, float2(p0.x, p1.y)); 214 float4 c3 = texture.sample(texture_sampler, p1); 215 216 float2 rate_center = float2(1.0, 1.0) - texel_size / 2.0 / scale; 217 float2 rate = clamp(((fract(p0 * source_size) - rate_center) * scale) + rate_center, 0.0, 1.0); 218 return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y); 219 } 220 }; 221 222 template<bool useColorM, uint8_t filter, uint8_t address> 223 struct FragmentShaderImpl { 224 inline float4 Do( 225 VertexOut v, 226 texture2d<float> texture, 227 constant float2& source_size, 228 constant float4x4& color_matrix_body, 229 constant float4& color_matrix_translation, 230 constant float& scale, 231 constant float4& source_region) { 232 float4 c = ColorFromTexel<filter, address>().Do(v, texture, source_size, scale, source_region); 233 if (useColorM) { 234 c.rgb /= c.a + (1.0 - sign(c.a)); 235 c = (color_matrix_body * c) + color_matrix_translation; 236 c *= v.color; 237 c.rgb *= c.a; 238 } else { 239 float4 s = v.color; 240 c *= float4(s.r, s.g, s.b, 1.0) * s.a; 241 } 242 c = min(c, c.a); 243 return c; 244 } 245 }; 246 247 template<bool useColorM, uint8_t address> 248 struct FragmentShaderImpl<useColorM, FILTER_SCREEN, address> { 249 inline float4 Do( 250 VertexOut v, 251 texture2d<float> texture, 252 constant float2& source_size, 253 constant float4x4& color_matrix_body, 254 constant float4& color_matrix_translation, 255 constant float& scale, 256 constant float4& source_region) { 257 return ColorFromTexel<FILTER_SCREEN, address>().Do(v, texture, source_size, scale, source_region); 258 } 259 }; 260 261 // Define Foo and FooCp macros to force macro replacement. 262 // See "6.10.3.1 Argument substitution" in ISO/IEC 9899. 263 264 #define FragmentShaderFunc(useColorM, filter, address) \ 265 FragmentShaderFuncCp(useColorM, filter, address) 266 267 #define FragmentShaderFuncCp(useColorM, filter, address) \ 268 fragment float4 FragmentShader_##useColorM##_##filter##_##address( \ 269 VertexOut v [[stage_in]], \ 270 texture2d<float> texture [[texture(0)]], \ 271 constant float2& source_size [[buffer(2)]], \ 272 constant float4x4& color_matrix_body [[buffer(3)]], \ 273 constant float4& color_matrix_translation [[buffer(4)]], \ 274 constant float& scale [[buffer(5)]], \ 275 constant float4& source_region [[buffer(6)]]) { \ 276 return FragmentShaderImpl<useColorM, filter, address>().Do( \ 277 v, texture, source_size, color_matrix_body, color_matrix_translation, scale, source_region); \ 278 } 279 280 FragmentShaderFunc(0, FILTER_NEAREST, ADDRESS_CLAMP_TO_ZERO) 281 FragmentShaderFunc(0, FILTER_LINEAR, ADDRESS_CLAMP_TO_ZERO) 282 FragmentShaderFunc(0, FILTER_NEAREST, ADDRESS_REPEAT) 283 FragmentShaderFunc(0, FILTER_LINEAR, ADDRESS_REPEAT) 284 FragmentShaderFunc(0, FILTER_NEAREST, ADDRESS_UNSAFE) 285 FragmentShaderFunc(0, FILTER_LINEAR, ADDRESS_UNSAFE) 286 FragmentShaderFunc(1, FILTER_NEAREST, ADDRESS_CLAMP_TO_ZERO) 287 FragmentShaderFunc(1, FILTER_LINEAR, ADDRESS_CLAMP_TO_ZERO) 288 FragmentShaderFunc(1, FILTER_NEAREST, ADDRESS_REPEAT) 289 FragmentShaderFunc(1, FILTER_LINEAR, ADDRESS_REPEAT) 290 FragmentShaderFunc(1, FILTER_NEAREST, ADDRESS_UNSAFE) 291 FragmentShaderFunc(1, FILTER_LINEAR, ADDRESS_UNSAFE) 292 293 FragmentShaderFunc(0, FILTER_SCREEN, ADDRESS_UNSAFE) 294 295 #undef FragmentShaderFuncName 296 ` 297 298 type rpsKey struct { 299 useColorM bool 300 filter driver.Filter 301 address driver.Address 302 compositeMode driver.CompositeMode 303 stencilMode stencilMode 304 screen bool 305 } 306 307 type Graphics struct { 308 view view 309 310 screenRPS mtl.RenderPipelineState 311 rpss map[rpsKey]mtl.RenderPipelineState 312 cq mtl.CommandQueue 313 cb mtl.CommandBuffer 314 rce mtl.RenderCommandEncoder 315 dsss map[stencilMode]mtl.DepthStencilState 316 317 screenDrawable ca.MetalDrawable 318 319 buffers map[mtl.CommandBuffer][]mtl.Buffer 320 unusedBuffers map[mtl.Buffer]struct{} 321 322 lastDst *Image 323 lastStencilMode stencilMode 324 325 vb mtl.Buffer 326 ib mtl.Buffer 327 328 images map[driver.ImageID]*Image 329 nextImageID driver.ImageID 330 331 shaders map[driver.ShaderID]*Shader 332 nextShaderID driver.ShaderID 333 334 src *Image 335 dst *Image 336 337 transparent bool 338 maxImageSize int 339 tmpTextures []mtl.Texture 340 341 pool unsafe.Pointer 342 } 343 344 type stencilMode int 345 346 const ( 347 prepareStencil stencilMode = iota 348 drawWithStencil 349 noStencil 350 ) 351 352 var theGraphics Graphics 353 354 func Get() *Graphics { 355 return &theGraphics 356 } 357 358 func (g *Graphics) Begin() { 359 // NSAutoreleasePool is required to release drawable correctly (#847). 360 // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/Drawables.html 361 g.pool = C.allocAutoreleasePool() 362 } 363 364 func (g *Graphics) End() { 365 g.flushIfNeeded(true) 366 g.screenDrawable = ca.MetalDrawable{} 367 C.releaseAutoreleasePool(g.pool) 368 g.pool = nil 369 } 370 371 func (g *Graphics) SetWindow(window uintptr) { 372 // Note that [NSApp mainWindow] returns nil when the window is borderless. 373 // Then the window is needed to be given explicitly. 374 g.view.setWindow(window) 375 } 376 377 func (g *Graphics) SetUIView(uiview uintptr) { 378 // TODO: Should this be called on the main thread? 379 g.view.setUIView(uiview) 380 } 381 382 func pow2(x uintptr) uintptr { 383 var p2 uintptr = 1 384 for p2 < x { 385 p2 *= 2 386 } 387 return p2 388 } 389 390 func (g *Graphics) gcBuffers() { 391 for cb, bs := range g.buffers { 392 // If the command buffer still lives, the buffer must not be updated. 393 // TODO: Handle an error? 394 if cb.Status() != mtl.CommandBufferStatusCompleted { 395 continue 396 } 397 398 for _, b := range bs { 399 if g.unusedBuffers == nil { 400 g.unusedBuffers = map[mtl.Buffer]struct{}{} 401 } 402 g.unusedBuffers[b] = struct{}{} 403 } 404 delete(g.buffers, cb) 405 cb.Release() 406 } 407 408 const maxUnusedBuffers = 10 409 if len(g.unusedBuffers) > maxUnusedBuffers { 410 bufs := make([]mtl.Buffer, 0, len(g.unusedBuffers)) 411 for b := range g.unusedBuffers { 412 bufs = append(bufs, b) 413 } 414 sort.Slice(bufs, func(a, b int) bool { 415 return bufs[a].Length() > bufs[b].Length() 416 }) 417 for _, b := range bufs[maxUnusedBuffers:] { 418 delete(g.unusedBuffers, b) 419 b.Release() 420 } 421 } 422 } 423 424 func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer { 425 if g.cb == (mtl.CommandBuffer{}) { 426 g.cb = g.cq.MakeCommandBuffer() 427 } 428 429 var newBuf mtl.Buffer 430 for b := range g.unusedBuffers { 431 if b.Length() >= length { 432 newBuf = b 433 delete(g.unusedBuffers, b) 434 break 435 } 436 } 437 438 if newBuf == (mtl.Buffer{}) { 439 newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode) 440 } 441 442 if g.buffers == nil { 443 g.buffers = map[mtl.CommandBuffer][]mtl.Buffer{} 444 } 445 if _, ok := g.buffers[g.cb]; !ok { 446 g.cb.Retain() 447 } 448 g.buffers[g.cb] = append(g.buffers[g.cb], newBuf) 449 return newBuf 450 } 451 452 func (g *Graphics) SetVertices(vertices []float32, indices []uint16) { 453 vbSize := unsafe.Sizeof(vertices[0]) * uintptr(len(vertices)) 454 ibSize := unsafe.Sizeof(indices[0]) * uintptr(len(indices)) 455 456 g.vb = g.availableBuffer(vbSize) 457 g.vb.CopyToContents(unsafe.Pointer(&vertices[0]), vbSize) 458 459 g.ib = g.availableBuffer(ibSize) 460 g.ib.CopyToContents(unsafe.Pointer(&indices[0]), ibSize) 461 } 462 463 func (g *Graphics) flushIfNeeded(present bool) { 464 if g.cb == (mtl.CommandBuffer{}) { 465 return 466 } 467 g.flushRenderCommandEncoderIfNeeded() 468 469 if !g.view.presentsWithTransaction() && present && g.screenDrawable != (ca.MetalDrawable{}) { 470 g.cb.PresentDrawable(g.screenDrawable) 471 } 472 g.cb.Commit() 473 if g.view.presentsWithTransaction() && present && g.screenDrawable != (ca.MetalDrawable{}) { 474 g.cb.WaitUntilScheduled() 475 g.screenDrawable.Present() 476 } 477 478 for _, t := range g.tmpTextures { 479 t.Release() 480 } 481 g.tmpTextures = g.tmpTextures[:0] 482 483 g.cb = mtl.CommandBuffer{} 484 } 485 486 func (g *Graphics) checkSize(width, height int) { 487 if width < 1 { 488 panic(fmt.Sprintf("metal: width (%d) must be equal or more than %d", width, 1)) 489 } 490 if height < 1 { 491 panic(fmt.Sprintf("metal: height (%d) must be equal or more than %d", height, 1)) 492 } 493 m := g.MaxImageSize() 494 if width > m { 495 panic(fmt.Sprintf("metal: width (%d) must be less than or equal to %d", width, m)) 496 } 497 if height > m { 498 panic(fmt.Sprintf("metal: height (%d) must be less than or equal to %d", height, m)) 499 } 500 } 501 502 func (g *Graphics) genNextImageID() driver.ImageID { 503 g.nextImageID++ 504 return g.nextImageID 505 } 506 507 func (g *Graphics) genNextShaderID() driver.ShaderID { 508 g.nextShaderID++ 509 return g.nextShaderID 510 } 511 512 func (g *Graphics) NewImage(width, height int) (driver.Image, error) { 513 g.checkSize(width, height) 514 td := mtl.TextureDescriptor{ 515 TextureType: mtl.TextureType2D, 516 PixelFormat: mtl.PixelFormatRGBA8UNorm, 517 Width: graphics.InternalImageSize(width), 518 Height: graphics.InternalImageSize(height), 519 StorageMode: storageMode, 520 Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget, 521 } 522 t := g.view.getMTLDevice().MakeTexture(td) 523 i := &Image{ 524 id: g.genNextImageID(), 525 graphics: g, 526 width: width, 527 height: height, 528 texture: t, 529 } 530 g.addImage(i) 531 return i, nil 532 } 533 534 func (g *Graphics) NewScreenFramebufferImage(width, height int) (driver.Image, error) { 535 g.view.setDrawableSize(width, height) 536 i := &Image{ 537 id: g.genNextImageID(), 538 graphics: g, 539 width: width, 540 height: height, 541 screen: true, 542 } 543 g.addImage(i) 544 return i, nil 545 } 546 547 func (g *Graphics) addImage(img *Image) { 548 if g.images == nil { 549 g.images = map[driver.ImageID]*Image{} 550 } 551 if _, ok := g.images[img.id]; ok { 552 panic(fmt.Sprintf("opengl: image ID %d was already registered", img.id)) 553 } 554 g.images[img.id] = img 555 } 556 557 func (g *Graphics) removeImage(img *Image) { 558 delete(g.images, img.id) 559 } 560 561 func (g *Graphics) SetTransparent(transparent bool) { 562 g.transparent = transparent 563 } 564 565 func operationToBlendFactor(c driver.Operation) mtl.BlendFactor { 566 switch c { 567 case driver.Zero: 568 return mtl.BlendFactorZero 569 case driver.One: 570 return mtl.BlendFactorOne 571 case driver.SrcAlpha: 572 return mtl.BlendFactorSourceAlpha 573 case driver.DstAlpha: 574 return mtl.BlendFactorDestinationAlpha 575 case driver.OneMinusSrcAlpha: 576 return mtl.BlendFactorOneMinusSourceAlpha 577 case driver.OneMinusDstAlpha: 578 return mtl.BlendFactorOneMinusDestinationAlpha 579 case driver.DstColor: 580 return mtl.BlendFactorDestinationColor 581 default: 582 panic(fmt.Sprintf("metal: invalid operation: %d", c)) 583 } 584 } 585 586 func (g *Graphics) Initialize() error { 587 // Creating *State objects are expensive and reuse them whenever possible. 588 // See https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/Cmd-Submiss/Cmd-Submiss.html 589 590 // TODO: Release existing rpss 591 if g.rpss == nil { 592 g.rpss = map[rpsKey]mtl.RenderPipelineState{} 593 } 594 595 for _, dss := range g.dsss { 596 dss.Release() 597 } 598 if g.dsss == nil { 599 g.dsss = map[stencilMode]mtl.DepthStencilState{} 600 } 601 602 if err := g.view.reset(); err != nil { 603 return err 604 } 605 if g.transparent { 606 g.view.ml.SetOpaque(false) 607 } 608 609 replaces := map[string]string{ 610 "{{.FilterNearest}}": fmt.Sprintf("%d", driver.FilterNearest), 611 "{{.FilterLinear}}": fmt.Sprintf("%d", driver.FilterLinear), 612 "{{.FilterScreen}}": fmt.Sprintf("%d", driver.FilterScreen), 613 "{{.AddressClampToZero}}": fmt.Sprintf("%d", driver.AddressClampToZero), 614 "{{.AddressRepeat}}": fmt.Sprintf("%d", driver.AddressRepeat), 615 "{{.AddressUnsafe}}": fmt.Sprintf("%d", driver.AddressUnsafe), 616 } 617 src := source 618 for k, v := range replaces { 619 src = strings.Replace(src, k, v, -1) 620 } 621 622 lib, err := g.view.getMTLDevice().MakeLibrary(src, mtl.CompileOptions{}) 623 if err != nil { 624 return err 625 } 626 vs, err := lib.MakeFunction("VertexShader") 627 if err != nil { 628 return err 629 } 630 fs, err := lib.MakeFunction( 631 fmt.Sprintf("FragmentShader_%d_%d_%d", 0, driver.FilterScreen, driver.AddressUnsafe)) 632 if err != nil { 633 return err 634 } 635 rpld := mtl.RenderPipelineDescriptor{ 636 VertexFunction: vs, 637 FragmentFunction: fs, 638 } 639 rpld.ColorAttachments[0].PixelFormat = g.view.colorPixelFormat() 640 rpld.ColorAttachments[0].BlendingEnabled = true 641 rpld.ColorAttachments[0].DestinationAlphaBlendFactor = mtl.BlendFactorZero 642 rpld.ColorAttachments[0].DestinationRGBBlendFactor = mtl.BlendFactorZero 643 rpld.ColorAttachments[0].SourceAlphaBlendFactor = mtl.BlendFactorOne 644 rpld.ColorAttachments[0].SourceRGBBlendFactor = mtl.BlendFactorOne 645 rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll 646 rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld) 647 if err != nil { 648 return err 649 } 650 g.screenRPS = rps 651 652 for _, screen := range []bool{false, true} { 653 for _, cm := range []bool{false, true} { 654 for _, a := range []driver.Address{ 655 driver.AddressClampToZero, 656 driver.AddressRepeat, 657 driver.AddressUnsafe, 658 } { 659 for _, f := range []driver.Filter{ 660 driver.FilterNearest, 661 driver.FilterLinear, 662 } { 663 for c := driver.CompositeModeSourceOver; c <= driver.CompositeModeMax; c++ { 664 for _, stencil := range []stencilMode{ 665 prepareStencil, 666 drawWithStencil, 667 noStencil, 668 } { 669 cmi := 0 670 if cm { 671 cmi = 1 672 } 673 fs, err := lib.MakeFunction(fmt.Sprintf("FragmentShader_%d_%d_%d", cmi, f, a)) 674 if err != nil { 675 return err 676 } 677 rpld := mtl.RenderPipelineDescriptor{ 678 VertexFunction: vs, 679 FragmentFunction: fs, 680 } 681 if stencil != noStencil { 682 rpld.StencilAttachmentPixelFormat = mtl.PixelFormatStencil8 683 } 684 685 pix := mtl.PixelFormatRGBA8UNorm 686 if screen { 687 pix = g.view.colorPixelFormat() 688 } 689 rpld.ColorAttachments[0].PixelFormat = pix 690 rpld.ColorAttachments[0].BlendingEnabled = true 691 692 src, dst := c.Operations() 693 rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst) 694 rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst) 695 rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src) 696 rpld.ColorAttachments[0].SourceRGBBlendFactor = operationToBlendFactor(src) 697 if stencil == prepareStencil { 698 rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone 699 } else { 700 rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll 701 } 702 rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld) 703 if err != nil { 704 return err 705 } 706 g.rpss[rpsKey{ 707 screen: screen, 708 useColorM: cm, 709 filter: f, 710 address: a, 711 compositeMode: c, 712 stencilMode: stencil, 713 }] = rps 714 } 715 } 716 } 717 } 718 } 719 } 720 721 // The stencil reference value is always 0 (default). 722 g.dsss[prepareStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{ 723 BackFaceStencil: mtl.StencilDescriptor{ 724 StencilFailureOperation: mtl.StencilOperationKeep, 725 DepthFailureOperation: mtl.StencilOperationKeep, 726 DepthStencilPassOperation: mtl.StencilOperationInvert, 727 StencilCompareFunction: mtl.CompareFunctionAlways, 728 }, 729 FrontFaceStencil: mtl.StencilDescriptor{ 730 StencilFailureOperation: mtl.StencilOperationKeep, 731 DepthFailureOperation: mtl.StencilOperationKeep, 732 DepthStencilPassOperation: mtl.StencilOperationInvert, 733 StencilCompareFunction: mtl.CompareFunctionAlways, 734 }, 735 }) 736 g.dsss[drawWithStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{ 737 BackFaceStencil: mtl.StencilDescriptor{ 738 StencilFailureOperation: mtl.StencilOperationKeep, 739 DepthFailureOperation: mtl.StencilOperationKeep, 740 DepthStencilPassOperation: mtl.StencilOperationKeep, 741 StencilCompareFunction: mtl.CompareFunctionNotEqual, 742 }, 743 FrontFaceStencil: mtl.StencilDescriptor{ 744 StencilFailureOperation: mtl.StencilOperationKeep, 745 DepthFailureOperation: mtl.StencilOperationKeep, 746 DepthStencilPassOperation: mtl.StencilOperationKeep, 747 StencilCompareFunction: mtl.CompareFunctionNotEqual, 748 }, 749 }) 750 g.dsss[noStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{ 751 BackFaceStencil: mtl.StencilDescriptor{ 752 StencilFailureOperation: mtl.StencilOperationKeep, 753 DepthFailureOperation: mtl.StencilOperationKeep, 754 DepthStencilPassOperation: mtl.StencilOperationKeep, 755 StencilCompareFunction: mtl.CompareFunctionAlways, 756 }, 757 FrontFaceStencil: mtl.StencilDescriptor{ 758 StencilFailureOperation: mtl.StencilOperationKeep, 759 DepthFailureOperation: mtl.StencilOperationKeep, 760 DepthStencilPassOperation: mtl.StencilOperationKeep, 761 StencilCompareFunction: mtl.CompareFunctionAlways, 762 }, 763 }) 764 765 g.cq = g.view.getMTLDevice().MakeCommandQueue() 766 return nil 767 } 768 769 func (g *Graphics) flushRenderCommandEncoderIfNeeded() { 770 if g.rce == (mtl.RenderCommandEncoder{}) { 771 return 772 } 773 g.rce.EndEncoding() 774 g.rce = mtl.RenderCommandEncoder{} 775 g.lastDst = nil 776 } 777 778 func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion driver.Region, srcs [graphics.ShaderImageNum]*Image, indexLen int, indexOffset int, uniforms []interface{}, stencilMode stencilMode) error { 779 // When prepareing a stencil buffer, flush the current render command encoder 780 // to make sure the stencil buffer is cleared when loading. 781 // TODO: What about clearing the stencil buffer by vertices? 782 if g.lastDst != dst || (g.lastStencilMode == noStencil) != (stencilMode == noStencil) || stencilMode == prepareStencil { 783 g.flushRenderCommandEncoderIfNeeded() 784 } 785 g.lastDst = dst 786 g.lastStencilMode = stencilMode 787 788 if g.rce == (mtl.RenderCommandEncoder{}) { 789 rpd := mtl.RenderPassDescriptor{} 790 // Even though the destination pixels are not used, mtl.LoadActionDontCare might cause glitches 791 // (#1019). Always using mtl.LoadActionLoad is safe. 792 if dst.screen { 793 rpd.ColorAttachments[0].LoadAction = mtl.LoadActionClear 794 } else { 795 rpd.ColorAttachments[0].LoadAction = mtl.LoadActionLoad 796 } 797 798 // The store action should always be 'store' even for the screen (#1700). 799 rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore 800 801 t := dst.mtlTexture() 802 if t == (mtl.Texture{}) { 803 return nil 804 } 805 rpd.ColorAttachments[0].Texture = t 806 rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{} 807 808 if stencilMode == prepareStencil { 809 dst.ensureStencil() 810 rpd.StencilAttachment.LoadAction = mtl.LoadActionClear 811 rpd.StencilAttachment.StoreAction = mtl.StoreActionDontCare 812 rpd.StencilAttachment.Texture = dst.stencil 813 } 814 815 if g.cb == (mtl.CommandBuffer{}) { 816 g.cb = g.cq.MakeCommandBuffer() 817 } 818 g.rce = g.cb.MakeRenderCommandEncoder(rpd) 819 } 820 821 g.rce.SetRenderPipelineState(rps) 822 823 // In Metal, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't 824 // match. Then, the Y direction must be inverted. 825 w, h := dst.internalSize() 826 g.rce.SetViewport(mtl.Viewport{ 827 OriginX: 0, 828 OriginY: float64(h), 829 Width: float64(w), 830 Height: -float64(h), 831 ZNear: -1, 832 ZFar: 1, 833 }) 834 g.rce.SetScissorRect(mtl.ScissorRect{ 835 X: int(dstRegion.X), 836 Y: int(dstRegion.Y), 837 Width: int(dstRegion.Width), 838 Height: int(dstRegion.Height), 839 }) 840 g.rce.SetVertexBuffer(g.vb, 0, 0) 841 842 for i, u := range uniforms { 843 switch u := u.(type) { 844 case float32: 845 g.rce.SetVertexBytes(unsafe.Pointer(&u), unsafe.Sizeof(u), i+1) 846 g.rce.SetFragmentBytes(unsafe.Pointer(&u), unsafe.Sizeof(u), i+1) 847 case []float32: 848 g.rce.SetVertexBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1) 849 g.rce.SetFragmentBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1) 850 default: 851 return fmt.Errorf("metal: unexpected uniform value: %[1]v (type: %[1]T)", u) 852 } 853 } 854 855 for i, src := range srcs { 856 if src != nil { 857 g.rce.SetFragmentTexture(src.texture, i) 858 } else { 859 g.rce.SetFragmentTexture(mtl.Texture{}, i) 860 } 861 } 862 863 g.rce.SetDepthStencilState(g.dsss[stencilMode]) 864 865 g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, indexLen, mtl.IndexTypeUInt16, g.ib, indexOffset*2) 866 867 return nil 868 } 869 870 func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM driver.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}, evenOdd bool) error { 871 dst := g.images[dstID] 872 873 if dst.screen { 874 g.view.update() 875 } 876 877 var srcs [graphics.ShaderImageNum]*Image 878 for i, srcID := range srcIDs { 879 srcs[i] = g.images[srcID] 880 } 881 882 rpss := map[stencilMode]mtl.RenderPipelineState{} 883 var uniformVars []interface{} 884 if shaderID == driver.InvalidShaderID { 885 if dst.screen && filter == driver.FilterScreen { 886 rpss[noStencil] = g.screenRPS 887 } else { 888 for _, stencil := range []stencilMode{ 889 prepareStencil, 890 drawWithStencil, 891 noStencil, 892 } { 893 rpss[stencil] = g.rpss[rpsKey{ 894 screen: dst.screen, 895 useColorM: !colorM.IsIdentity(), 896 filter: filter, 897 address: address, 898 compositeMode: mode, 899 stencilMode: stencil, 900 }] 901 } 902 } 903 904 w, h := dst.internalSize() 905 sourceSize := []float32{0, 0} 906 if filter != driver.FilterNearest { 907 w, h := srcs[0].internalSize() 908 sourceSize[0] = float32(w) 909 sourceSize[1] = float32(h) 910 } 911 var esBody [16]float32 912 var esTranslate [4]float32 913 colorM.Elements(&esBody, &esTranslate) 914 scale := float32(0) 915 if filter == driver.FilterScreen { 916 scale = float32(dst.width) / float32(srcs[0].width) 917 } 918 uniformVars = []interface{}{ 919 []float32{float32(w), float32(h)}, 920 sourceSize, 921 esBody[:], 922 esTranslate[:], 923 scale, 924 []float32{ 925 srcRegion.X, 926 srcRegion.Y, 927 srcRegion.X + srcRegion.Width, 928 srcRegion.Y + srcRegion.Height, 929 }, 930 } 931 } else { 932 for _, stencil := range []stencilMode{ 933 prepareStencil, 934 drawWithStencil, 935 noStencil, 936 } { 937 var err error 938 rpss[stencil], err = g.shaders[shaderID].RenderPipelineState(g.view.getMTLDevice(), mode, stencil) 939 if err != nil { 940 return err 941 } 942 } 943 944 uniformVars = make([]interface{}, graphics.PreservedUniformVariablesNum+len(uniforms)) 945 946 // Set the destination texture size. 947 dw, dh := dst.internalSize() 948 uniformVars[graphics.DestinationTextureSizeUniformVariableIndex] = []float32{float32(dw), float32(dh)} 949 950 // Set the source texture sizes. 951 usizes := make([]float32, 2*len(srcs)) 952 for i, src := range srcs { 953 if src != nil { 954 w, h := src.internalSize() 955 usizes[2*i] = float32(w) 956 usizes[2*i+1] = float32(h) 957 } 958 } 959 uniformVars[graphics.TextureSizesUniformVariableIndex] = usizes 960 961 // Set the destination region's origin. 962 udorigin := []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)} 963 uniformVars[graphics.TextureDestinationRegionOriginUniformVariableIndex] = udorigin 964 965 // Set the destination region's size. 966 udsize := []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)} 967 uniformVars[graphics.TextureDestinationRegionSizeUniformVariableIndex] = udsize 968 969 // Set the source offsets. 970 uoffsets := make([]float32, 2*len(offsets)) 971 for i, offset := range offsets { 972 uoffsets[2*i] = offset[0] 973 uoffsets[2*i+1] = offset[1] 974 } 975 uniformVars[graphics.TextureSourceOffsetsUniformVariableIndex] = uoffsets 976 977 // Set the source region's origin of texture0. 978 usorigin := []float32{float32(srcRegion.X), float32(srcRegion.Y)} 979 uniformVars[graphics.TextureSourceRegionOriginUniformVariableIndex] = usorigin 980 981 // Set the source region's size of texture0. 982 ussize := []float32{float32(srcRegion.Width), float32(srcRegion.Height)} 983 uniformVars[graphics.TextureSourceRegionSizeUniformVariableIndex] = ussize 984 985 // Set the additional uniform variables. 986 for i, v := range uniforms { 987 const offset = graphics.PreservedUniformVariablesNum 988 uniformVars[offset+i] = v 989 } 990 } 991 992 if evenOdd { 993 if err := g.draw(rpss[prepareStencil], dst, dstRegion, srcs, indexLen, indexOffset, uniformVars, prepareStencil); err != nil { 994 return err 995 } 996 if err := g.draw(rpss[drawWithStencil], dst, dstRegion, srcs, indexLen, indexOffset, uniformVars, drawWithStencil); err != nil { 997 return err 998 } 999 } else { 1000 if err := g.draw(rpss[noStencil], dst, dstRegion, srcs, indexLen, indexOffset, uniformVars, noStencil); err != nil { 1001 return err 1002 } 1003 } 1004 1005 return nil 1006 } 1007 1008 func (g *Graphics) SetVsyncEnabled(enabled bool) { 1009 g.view.setDisplaySyncEnabled(enabled) 1010 } 1011 1012 func (g *Graphics) SetFullscreen(fullscreen bool) { 1013 g.view.setFullscreen(fullscreen) 1014 } 1015 1016 func (g *Graphics) FramebufferYDirection() driver.YDirection { 1017 return driver.Downward 1018 } 1019 1020 func (g *Graphics) NeedsRestoring() bool { 1021 return false 1022 } 1023 1024 func (g *Graphics) NeedsClearingScreen() bool { 1025 return false 1026 } 1027 1028 func (g *Graphics) IsGL() bool { 1029 return false 1030 } 1031 1032 func (g *Graphics) HasHighPrecisionFloat() bool { 1033 return true 1034 } 1035 1036 func (g *Graphics) MaxImageSize() int { 1037 if g.maxImageSize != 0 { 1038 return g.maxImageSize 1039 } 1040 1041 g.maxImageSize = 4096 1042 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf 1043 switch { 1044 case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily5_v1): 1045 g.maxImageSize = 16384 1046 case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily4_v1): 1047 g.maxImageSize = 16384 1048 case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily3_v1): 1049 g.maxImageSize = 16384 1050 case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v2): 1051 g.maxImageSize = 8192 1052 case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v1): 1053 g.maxImageSize = 4096 1054 case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v2): 1055 g.maxImageSize = 8192 1056 case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v1): 1057 g.maxImageSize = 4096 1058 case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily2_v1): 1059 g.maxImageSize = 16384 1060 case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily1_v1): 1061 g.maxImageSize = 8192 1062 case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_macOS_GPUFamily1_v1): 1063 g.maxImageSize = 16384 1064 default: 1065 panic("metal: there is no supported feature set") 1066 } 1067 return g.maxImageSize 1068 } 1069 1070 func (g *Graphics) NewShader(program *shaderir.Program) (driver.Shader, error) { 1071 s, err := newShader(g.view.getMTLDevice(), g.genNextShaderID(), program) 1072 if err != nil { 1073 return nil, err 1074 } 1075 g.addShader(s) 1076 return s, nil 1077 } 1078 1079 func (g *Graphics) addShader(shader *Shader) { 1080 if g.shaders == nil { 1081 g.shaders = map[driver.ShaderID]*Shader{} 1082 } 1083 if _, ok := g.shaders[shader.id]; ok { 1084 panic(fmt.Sprintf("metal: shader ID %d was already registered", shader.id)) 1085 } 1086 g.shaders[shader.id] = shader 1087 } 1088 1089 func (g *Graphics) removeShader(shader *Shader) { 1090 delete(g.shaders, shader.id) 1091 } 1092 1093 type Image struct { 1094 id driver.ImageID 1095 graphics *Graphics 1096 width int 1097 height int 1098 screen bool 1099 texture mtl.Texture 1100 stencil mtl.Texture 1101 } 1102 1103 func (i *Image) ID() driver.ImageID { 1104 return i.id 1105 } 1106 1107 func (i *Image) internalSize() (int, int) { 1108 if i.screen { 1109 return i.width, i.height 1110 } 1111 return graphics.InternalImageSize(i.width), graphics.InternalImageSize(i.height) 1112 } 1113 1114 func (i *Image) Dispose() { 1115 if i.stencil != (mtl.Texture{}) { 1116 i.stencil.Release() 1117 i.stencil = mtl.Texture{} 1118 } 1119 if i.texture != (mtl.Texture{}) { 1120 i.texture.Release() 1121 i.texture = mtl.Texture{} 1122 } 1123 i.graphics.removeImage(i) 1124 } 1125 1126 func (i *Image) IsInvalidated() bool { 1127 // TODO: Does Metal cause context lost? 1128 // https://developer.apple.com/documentation/metal/mtlresource/1515898-setpurgeablestate 1129 // https://developer.apple.com/documentation/metal/mtldevicenotificationhandler 1130 return false 1131 } 1132 1133 func (i *Image) syncTexture() { 1134 i.graphics.flushRenderCommandEncoderIfNeeded() 1135 1136 // Calling SynchronizeTexture is ignored on iOS (see mtl.m), but it looks like committing BlitCommandEncoder 1137 // is necessary (#1337). 1138 if i.graphics.cb != (mtl.CommandBuffer{}) { 1139 panic("metal: command buffer must be empty at syncTexture: flushIfNeeded is not called yet?") 1140 } 1141 1142 cb := i.graphics.cq.MakeCommandBuffer() 1143 bce := cb.MakeBlitCommandEncoder() 1144 bce.SynchronizeTexture(i.texture, 0, 0) 1145 bce.EndEncoding() 1146 1147 cb.Commit() 1148 cb.WaitUntilCompleted() 1149 } 1150 1151 func (i *Image) Pixels() ([]byte, error) { 1152 i.graphics.flushIfNeeded(false) 1153 i.syncTexture() 1154 1155 b := make([]byte, 4*i.width*i.height) 1156 i.texture.GetBytes(&b[0], uintptr(4*i.width), mtl.Region{ 1157 Size: mtl.Size{Width: i.width, Height: i.height, Depth: 1}, 1158 }, 0) 1159 return b, nil 1160 } 1161 1162 func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) { 1163 g := i.graphics 1164 1165 g.flushRenderCommandEncoderIfNeeded() 1166 1167 // Calculate the smallest texture size to include all the values in args. 1168 minX := math.MaxInt32 1169 minY := math.MaxInt32 1170 maxX := 0 1171 maxY := 0 1172 for _, a := range args { 1173 if minX > a.X { 1174 minX = a.X 1175 } 1176 if maxX < a.X+a.Width { 1177 maxX = a.X + a.Width 1178 } 1179 if minY > a.Y { 1180 minY = a.Y 1181 } 1182 if maxY < a.Y+a.Height { 1183 maxY = a.Y + a.Height 1184 } 1185 } 1186 w := maxX - minX 1187 h := maxY - minY 1188 1189 // Use a temporary texture to send pixels asynchrounsly, whichever the memory is shared (e.g., iOS) or 1190 // managed (e.g., macOS). A temporary texture is needed since ReplaceRegion tries to sync the pixel 1191 // data between CPU and GPU, and doing it on the existing texture is inefficient (#1418). 1192 // The texture cannot be reused until sending the pixels finishes, then create new ones for each call. 1193 td := mtl.TextureDescriptor{ 1194 TextureType: mtl.TextureType2D, 1195 PixelFormat: mtl.PixelFormatRGBA8UNorm, 1196 Width: w, 1197 Height: h, 1198 StorageMode: storageMode, 1199 Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget, 1200 } 1201 t := g.view.getMTLDevice().MakeTexture(td) 1202 g.tmpTextures = append(g.tmpTextures, t) 1203 1204 for _, a := range args { 1205 t.ReplaceRegion(mtl.Region{ 1206 Origin: mtl.Origin{X: a.X - minX, Y: a.Y - minY, Z: 0}, 1207 Size: mtl.Size{Width: a.Width, Height: a.Height, Depth: 1}, 1208 }, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Width) 1209 } 1210 1211 if g.cb == (mtl.CommandBuffer{}) { 1212 g.cb = i.graphics.cq.MakeCommandBuffer() 1213 } 1214 bce := g.cb.MakeBlitCommandEncoder() 1215 for _, a := range args { 1216 so := mtl.Origin{X: a.X - minX, Y: a.Y - minY, Z: 0} 1217 ss := mtl.Size{Width: a.Width, Height: a.Height, Depth: 1} 1218 do := mtl.Origin{X: a.X, Y: a.Y, Z: 0} 1219 bce.CopyFromTexture(t, 0, 0, so, ss, i.texture, 0, 0, do) 1220 } 1221 bce.EndEncoding() 1222 } 1223 1224 func (i *Image) mtlTexture() mtl.Texture { 1225 if i.screen { 1226 g := i.graphics 1227 if g.screenDrawable == (ca.MetalDrawable{}) { 1228 drawable := g.view.nextDrawable() 1229 if drawable == (ca.MetalDrawable{}) { 1230 return mtl.Texture{} 1231 } 1232 g.screenDrawable = drawable 1233 // After nextDrawable, it is expected some command buffers are completed. 1234 g.gcBuffers() 1235 } 1236 return g.screenDrawable.Texture() 1237 } 1238 return i.texture 1239 } 1240 1241 func (i *Image) ensureStencil() { 1242 if i.stencil != (mtl.Texture{}) { 1243 return 1244 } 1245 1246 td := mtl.TextureDescriptor{ 1247 TextureType: mtl.TextureType2D, 1248 PixelFormat: mtl.PixelFormatStencil8, 1249 Width: graphics.InternalImageSize(i.width), 1250 Height: graphics.InternalImageSize(i.height), 1251 StorageMode: mtl.StorageModePrivate, 1252 Usage: mtl.TextureUsageRenderTarget, 1253 } 1254 i.stencil = i.graphics.view.getMTLDevice().MakeTexture(td) 1255 }