zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

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 }