zorldo

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

image.go (20447B)


      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 package atlas
     16 
     17 import (
     18 	"fmt"
     19 	"image"
     20 	"runtime"
     21 	"sync"
     22 
     23 	"github.com/hajimehoshi/ebiten/v2/internal/affine"
     24 	"github.com/hajimehoshi/ebiten/v2/internal/driver"
     25 	"github.com/hajimehoshi/ebiten/v2/internal/graphics"
     26 	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
     27 	"github.com/hajimehoshi/ebiten/v2/internal/packing"
     28 	"github.com/hajimehoshi/ebiten/v2/internal/restorable"
     29 )
     30 
     31 const (
     32 	// paddingSize represents the size of padding around an image.
     33 	// Every image or node except for a screen image has its padding.
     34 	paddingSize = 1
     35 )
     36 
     37 var (
     38 	minSize = 0
     39 	maxSize = 0
     40 )
     41 
     42 type temporaryPixels struct {
     43 	pixels           []byte
     44 	pos              int
     45 	notFullyUsedTime int
     46 }
     47 
     48 var theTemporaryPixels temporaryPixels
     49 
     50 func temporaryPixelsByteSize(size int) int {
     51 	l := 16
     52 	for l < size {
     53 		l *= 2
     54 	}
     55 	return l
     56 }
     57 
     58 // alloc allocates the pixels and reutrns it.
     59 // Be careful that the returned pixels might not be zero-cleared.
     60 func (t *temporaryPixels) alloc(size int) []byte {
     61 	if len(t.pixels) < t.pos+size {
     62 		t.pixels = make([]byte, temporaryPixelsByteSize(t.pos+size))
     63 		t.pos = 0
     64 	}
     65 	pix := t.pixels[t.pos : t.pos+size]
     66 	t.pos += size
     67 	return pix
     68 }
     69 
     70 func (t *temporaryPixels) resetAtFrameEnd() {
     71 	const maxNotFullyUsedTime = 60
     72 
     73 	if temporaryPixelsByteSize(t.pos) < len(t.pixels) {
     74 		if t.notFullyUsedTime < maxNotFullyUsedTime {
     75 			t.notFullyUsedTime++
     76 		}
     77 	} else {
     78 		t.notFullyUsedTime = 0
     79 	}
     80 
     81 	// Let the pixels GCed if this is not used for a while.
     82 	if t.notFullyUsedTime == maxNotFullyUsedTime && len(t.pixels) > 0 {
     83 		t.pixels = nil
     84 	}
     85 
     86 	// Reset the position and reuse the allocated bytes.
     87 	// t.pixels should already be sent to GPU, then this can be reused.
     88 	t.pos = 0
     89 }
     90 
     91 func max(a, b int) int {
     92 	if a > b {
     93 		return a
     94 	}
     95 	return b
     96 }
     97 
     98 func min(a, b int) int {
     99 	if a < b {
    100 		return a
    101 	}
    102 	return b
    103 }
    104 
    105 func init() {
    106 	hooks.AppendHookOnBeforeUpdate(func() error {
    107 		backendsM.Lock()
    108 		defer backendsM.Unlock()
    109 
    110 		resolveDeferred()
    111 		return putImagesOnAtlas()
    112 	})
    113 }
    114 
    115 func resolveDeferred() {
    116 	deferredM.Lock()
    117 	fs := deferred
    118 	deferred = nil
    119 	deferredM.Unlock()
    120 
    121 	for _, f := range fs {
    122 		f()
    123 	}
    124 }
    125 
    126 // baseCountToPutOnAtlas represents the base time duration when the image can be put onto an atlas.
    127 // Actual time duration is increased in an exponential way for each usages as a rendering target.
    128 const baseCountToPutOnAtlas = 10
    129 
    130 func putImagesOnAtlas() error {
    131 	for i := range imagesToPutOnAtlas {
    132 		i.usedAsSourceCount++
    133 		if i.usedAsSourceCount >= baseCountToPutOnAtlas*(1<<uint(min(i.isolatedCount, 31))) {
    134 			if err := i.putOnAtlas(); err != nil {
    135 				return err
    136 			}
    137 			i.usedAsSourceCount = 0
    138 			delete(imagesToPutOnAtlas, i)
    139 		}
    140 	}
    141 
    142 	// Reset the images. The images will be registered again when it is used as a rendering source.
    143 	for k := range imagesToPutOnAtlas {
    144 		delete(imagesToPutOnAtlas, k)
    145 	}
    146 	return nil
    147 }
    148 
    149 type backend struct {
    150 	// restorable is an atlas on which there might be multiple images.
    151 	restorable *restorable.Image
    152 
    153 	// page is an atlas map. Each part is called a node.
    154 	// If page is nil, the backend's image is isolated and not on an atlas.
    155 	page *packing.Page
    156 }
    157 
    158 func (b *backend) tryAlloc(width, height int) (*packing.Node, bool) {
    159 	// If the region is allocated without any extension, that's fine.
    160 	if n := b.page.Alloc(width, height); n != nil {
    161 		return n, true
    162 	}
    163 
    164 	nExtended := 1
    165 	var n *packing.Node
    166 	for {
    167 		if !b.page.Extend(nExtended) {
    168 			// The page can't be extended any more. Return as failure.
    169 			return nil, false
    170 		}
    171 		nExtended++
    172 		n = b.page.Alloc(width, height)
    173 		if n != nil {
    174 			b.page.CommitExtension()
    175 			break
    176 		}
    177 		b.page.RollbackExtension()
    178 	}
    179 
    180 	s := b.page.Size()
    181 	b.restorable = b.restorable.Extend(s, s)
    182 
    183 	if n == nil {
    184 		panic("atlas: Alloc result must not be nil at TryAlloc")
    185 	}
    186 	return n, true
    187 }
    188 
    189 var (
    190 	// backendsM is a mutex for critical sections of the backend and packing.Node objects.
    191 	backendsM sync.Mutex
    192 
    193 	initOnce sync.Once
    194 
    195 	// theBackends is a set of atlases.
    196 	theBackends = []*backend{}
    197 
    198 	imagesToPutOnAtlas = map[*Image]struct{}{}
    199 
    200 	deferred []func()
    201 
    202 	// deferredM is a mutext for the slice operations. This must not be used for other usages.
    203 	deferredM sync.Mutex
    204 )
    205 
    206 func init() {
    207 	// Lock the mutex before a frame begins.
    208 	//
    209 	// In each frame, restoring images and resolving images happen respectively:
    210 	//
    211 	//   [Restore -> Resolve] -> [Restore -> Resolve] -> ...
    212 	//
    213 	// Between each frame, any image operations are not permitted, or stale images would remain when restoring
    214 	// (#913).
    215 	backendsM.Lock()
    216 }
    217 
    218 // Image is a renctangle pixel set that might be on an atlas.
    219 type Image struct {
    220 	width    int
    221 	height   int
    222 	disposed bool
    223 	volatile bool
    224 	screen   bool
    225 
    226 	backend *backend
    227 
    228 	node *packing.Node
    229 
    230 	// usedAsSourceCount represents how long the image is used as a rendering source and kept not modified with
    231 	// DrawTriangles.
    232 	// In the current implementation, if an image is being modified by DrawTriangles, the image is separated from
    233 	// a restorable image on an atlas by ensureIsolated.
    234 	//
    235 	// usedAsSourceCount is increased if the image is used as a rendering source, or set to 0 if the image is
    236 	// modified.
    237 	//
    238 	// ReplacePixels doesn't affect this value since ReplacePixels can be done on images on an atlas.
    239 	usedAsSourceCount int
    240 
    241 	// isolatedCount represents how many times the image on a texture atlas is changed into an isolated image.
    242 	// isolatedCount affects the calculation when to put the image onto a texture atlas again.
    243 	isolatedCount int
    244 }
    245 
    246 // moveTo moves its content to the given image dst.
    247 // After moveTo is called, the image i is no longer available.
    248 //
    249 // moveTo is smilar to C++'s move semantics.
    250 func (i *Image) moveTo(dst *Image) {
    251 	dst.dispose(false)
    252 	*dst = *i
    253 
    254 	// i is no longer available but Dispose must not be called
    255 	// since i and dst have the same values like node.
    256 	runtime.SetFinalizer(i, nil)
    257 }
    258 
    259 func (i *Image) isOnAtlas() bool {
    260 	return i.node != nil
    261 }
    262 
    263 func (i *Image) resetUsedAsSourceCount() {
    264 	i.usedAsSourceCount = 0
    265 	delete(imagesToPutOnAtlas, i)
    266 }
    267 
    268 func (i *Image) ensureIsolated() {
    269 	i.resetUsedAsSourceCount()
    270 
    271 	if i.backend == nil {
    272 		i.allocate(false)
    273 		return
    274 	}
    275 
    276 	if !i.isOnAtlas() {
    277 		return
    278 	}
    279 
    280 	ox, oy, w, h := i.regionWithPadding()
    281 	dx0 := float32(0)
    282 	dy0 := float32(0)
    283 	dx1 := float32(w)
    284 	dy1 := float32(h)
    285 	sx0 := float32(ox)
    286 	sy0 := float32(oy)
    287 	sx1 := float32(ox + w)
    288 	sy1 := float32(oy + h)
    289 	newImg := restorable.NewImage(w, h)
    290 	newImg.SetVolatile(i.volatile)
    291 	vs := []float32{
    292 		dx0, dy0, sx0, sy0, 1, 1, 1, 1,
    293 		dx1, dy0, sx1, sy0, 1, 1, 1, 1,
    294 		dx0, dy1, sx0, sy1, 1, 1, 1, 1,
    295 		dx1, dy1, sx1, sy1, 1, 1, 1, 1,
    296 	}
    297 	is := graphics.QuadIndices()
    298 	srcs := [graphics.ShaderImageNum]*restorable.Image{i.backend.restorable}
    299 	var offsets [graphics.ShaderImageNum - 1][2]float32
    300 	dstRegion := driver.Region{
    301 		X:      paddingSize,
    302 		Y:      paddingSize,
    303 		Width:  float32(w - 2*paddingSize),
    304 		Height: float32(h - 2*paddingSize),
    305 	}
    306 	newImg.DrawTriangles(srcs, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dstRegion, driver.Region{}, nil, nil, false)
    307 
    308 	i.dispose(false)
    309 	i.backend = &backend{
    310 		restorable: newImg,
    311 	}
    312 
    313 	i.isolatedCount++
    314 }
    315 
    316 func (i *Image) putOnAtlas() error {
    317 	if i.backend == nil {
    318 		i.allocate(true)
    319 		return nil
    320 	}
    321 
    322 	if i.isOnAtlas() {
    323 		return nil
    324 	}
    325 
    326 	if !i.canBePutOnAtlas() {
    327 		panic("atlas: putOnAtlas cannot be called on a image that cannot be on an atlas")
    328 	}
    329 
    330 	newI := NewImage(i.width, i.height)
    331 	newI.SetVolatile(i.volatile)
    332 
    333 	if restorable.NeedsRestoring() {
    334 		// If the underlying graphics driver requires restoring from the context lost, the pixel data is
    335 		// needed. A image on an atlas must have its complete pixel data in this case.
    336 		pixels := make([]byte, 4*i.width*i.height)
    337 		for y := 0; y < i.height; y++ {
    338 			for x := 0; x < i.width; x++ {
    339 				r, g, b, a, err := i.at(x+paddingSize, y+paddingSize)
    340 				if err != nil {
    341 					return err
    342 				}
    343 				pixels[4*(i.width*y+x)] = r
    344 				pixels[4*(i.width*y+x)+1] = g
    345 				pixels[4*(i.width*y+x)+2] = b
    346 				pixels[4*(i.width*y+x)+3] = a
    347 			}
    348 		}
    349 		newI.replacePixels(pixels)
    350 	} else {
    351 		// If the underlying graphics driver doesn't require restoring from the context lost, just a regular
    352 		// rendering works.
    353 		w, h := float32(i.width), float32(i.height)
    354 		vs := graphics.QuadVertices(0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
    355 		is := graphics.QuadIndices()
    356 		dr := driver.Region{
    357 			X:      0,
    358 			Y:      0,
    359 			Width:  w,
    360 			Height: h,
    361 		}
    362 		newI.drawTriangles([graphics.ShaderImageNum]*Image{i}, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false, true)
    363 	}
    364 
    365 	newI.moveTo(i)
    366 	i.usedAsSourceCount = 0
    367 	return nil
    368 }
    369 
    370 func (i *Image) regionWithPadding() (x, y, width, height int) {
    371 	if i.backend == nil {
    372 		panic("atlas: backend must not be nil: not allocated yet?")
    373 	}
    374 	if !i.isOnAtlas() {
    375 		return 0, 0, i.width + 2*paddingSize, i.height + 2*paddingSize
    376 	}
    377 	return i.node.Region()
    378 }
    379 
    380 func (i *Image) processSrc(src *Image) {
    381 	if src == nil {
    382 		return
    383 	}
    384 	if src.disposed {
    385 		panic("atlas: the drawing source image must not be disposed (DrawTriangles)")
    386 	}
    387 	if src.backend == nil {
    388 		src.allocate(true)
    389 	}
    390 
    391 	// Compare i and source images after ensuring i is not on an atlas, or
    392 	// i and a source image might share the same atlas even though i != src.
    393 	if i.backend.restorable == src.backend.restorable {
    394 		panic("atlas: Image.DrawTriangles: source must be different from the receiver")
    395 	}
    396 }
    397 
    398 // DrawTriangles draws triangles with the given image.
    399 //
    400 // The vertex floats are:
    401 //
    402 //   0: Destination X in pixels
    403 //   1: Destination Y in pixels
    404 //   2: Source X in pixels (the upper-left is (0, 0))
    405 //   3: Source Y in pixels
    406 //   4: Color R [0.0-1.0]
    407 //   5: Color G
    408 //   6: Color B
    409 //   7: Color Y
    410 func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool) {
    411 	backendsM.Lock()
    412 	defer backendsM.Unlock()
    413 	i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, false)
    414 }
    415 
    416 func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool, keepOnAtlas bool) {
    417 	if i.disposed {
    418 		panic("atlas: the drawing target image must not be disposed (DrawTriangles)")
    419 	}
    420 	if keepOnAtlas {
    421 		if i.backend == nil {
    422 			i.allocate(true)
    423 		}
    424 	} else {
    425 		i.ensureIsolated()
    426 	}
    427 
    428 	for _, src := range srcs {
    429 		i.processSrc(src)
    430 	}
    431 
    432 	cr := float32(1)
    433 	cg := float32(1)
    434 	cb := float32(1)
    435 	ca := float32(1)
    436 	if !colorm.IsIdentity() && colorm.ScaleOnly() {
    437 		cr = colorm.At(0, 0)
    438 		cg = colorm.At(1, 1)
    439 		cb = colorm.At(2, 2)
    440 		ca = colorm.At(3, 3)
    441 		colorm = affine.ColorMIdentity{}
    442 	}
    443 
    444 	var dx, dy float32
    445 	// A screen image doesn't have its padding.
    446 	if !i.screen {
    447 		x, y, _, _ := i.regionWithPadding()
    448 		dx = float32(x) + paddingSize
    449 		dy = float32(y) + paddingSize
    450 		// TODO: Check if dstRegion does not to violate the region.
    451 	}
    452 	dstRegion.X += dx
    453 	dstRegion.Y += dy
    454 
    455 	var oxf, oyf float32
    456 	if srcs[0] != nil {
    457 		ox, oy, _, _ := srcs[0].regionWithPadding()
    458 		ox += paddingSize
    459 		oy += paddingSize
    460 		oxf, oyf = float32(ox), float32(oy)
    461 		n := len(vertices)
    462 		for i := 0; i < n; i += graphics.VertexFloatNum {
    463 			vertices[i] += dx
    464 			vertices[i+1] += dy
    465 			vertices[i+2] += oxf
    466 			vertices[i+3] += oyf
    467 			vertices[i+4] *= cr
    468 			vertices[i+5] *= cg
    469 			vertices[i+6] *= cb
    470 			vertices[i+7] *= ca
    471 		}
    472 		// srcRegion can be delibarately empty when this is not needed in order to avoid unexpected
    473 		// performance issue (#1293).
    474 		if srcRegion.Width != 0 && srcRegion.Height != 0 {
    475 			srcRegion.X += oxf
    476 			srcRegion.Y += oyf
    477 		}
    478 	} else {
    479 		n := len(vertices)
    480 		for i := 0; i < n; i += graphics.VertexFloatNum {
    481 			vertices[i] += dx
    482 			vertices[i+1] += dy
    483 			vertices[i+4] *= cr
    484 			vertices[i+5] *= cg
    485 			vertices[i+6] *= cb
    486 			vertices[i+7] *= ca
    487 		}
    488 	}
    489 
    490 	var offsets [graphics.ShaderImageNum - 1][2]float32
    491 	var s *restorable.Shader
    492 	var imgs [graphics.ShaderImageNum]*restorable.Image
    493 	if shader == nil {
    494 		// Fast path for rendering without a shader (#1355).
    495 		imgs[0] = srcs[0].backend.restorable
    496 	} else {
    497 		for i, subimageOffset := range subimageOffsets {
    498 			src := srcs[i+1]
    499 			if src == nil {
    500 				continue
    501 			}
    502 			ox, oy, _, _ := src.regionWithPadding()
    503 			offsets[i][0] = float32(ox) + paddingSize - oxf + subimageOffset[0]
    504 			offsets[i][1] = float32(oy) + paddingSize - oyf + subimageOffset[1]
    505 		}
    506 		s = shader.shader
    507 		for i, src := range srcs {
    508 			if src == nil {
    509 				continue
    510 			}
    511 			imgs[i] = src.backend.restorable
    512 		}
    513 	}
    514 
    515 	i.backend.restorable.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, s, uniforms, evenOdd)
    516 
    517 	for _, src := range srcs {
    518 		if src == nil {
    519 			continue
    520 		}
    521 		if !src.isOnAtlas() && src.canBePutOnAtlas() {
    522 			// src might already registered, but assiging it again is not harmful.
    523 			imagesToPutOnAtlas[src] = struct{}{}
    524 		}
    525 	}
    526 }
    527 
    528 func (i *Image) ReplacePixels(pix []byte) {
    529 	backendsM.Lock()
    530 	defer backendsM.Unlock()
    531 	i.replacePixels(pix)
    532 }
    533 
    534 func (i *Image) replacePixels(pix []byte) {
    535 	if i.disposed {
    536 		panic("atlas: the image must not be disposed at replacePixels")
    537 	}
    538 
    539 	i.resetUsedAsSourceCount()
    540 
    541 	if i.backend == nil {
    542 		if pix == nil {
    543 			return
    544 		}
    545 		i.allocate(true)
    546 	}
    547 
    548 	x, y, w, h := i.regionWithPadding()
    549 	if pix == nil {
    550 		i.backend.restorable.ReplacePixels(nil, x, y, w, h)
    551 		return
    552 	}
    553 
    554 	ow, oh := w-2*paddingSize, h-2*paddingSize
    555 	if l := 4 * ow * oh; len(pix) != l {
    556 		panic(fmt.Sprintf("atlas: len(p) must be %d but %d", l, len(pix)))
    557 	}
    558 
    559 	pixb := theTemporaryPixels.alloc(4 * w * h)
    560 
    561 	// Clear the edges. pixb might not be zero-cleared.
    562 	rowPixels := 4 * w
    563 	for i := 0; i < rowPixels; i++ {
    564 		pixb[i] = 0
    565 	}
    566 	for j := 1; j < h-1; j++ {
    567 		pixb[rowPixels*j] = 0
    568 		pixb[rowPixels*j+1] = 0
    569 		pixb[rowPixels*j+2] = 0
    570 		pixb[rowPixels*j+3] = 0
    571 		pixb[rowPixels*(j+1)-4] = 0
    572 		pixb[rowPixels*(j+1)-3] = 0
    573 		pixb[rowPixels*(j+1)-2] = 0
    574 		pixb[rowPixels*(j+1)-1] = 0
    575 	}
    576 	for i := 0; i < rowPixels; i++ {
    577 		pixb[rowPixels*(h-1)+i] = 0
    578 	}
    579 
    580 	// Copy the content.
    581 	for j := 0; j < oh; j++ {
    582 		copy(pixb[4*((j+paddingSize)*w+paddingSize):], pix[4*j*ow:4*(j+1)*ow])
    583 	}
    584 
    585 	i.backend.restorable.ReplacePixels(pixb, x, y, w, h)
    586 }
    587 
    588 func (img *Image) Pixels(x, y, width, height int) ([]byte, error) {
    589 	backendsM.Lock()
    590 	defer backendsM.Unlock()
    591 
    592 	x += paddingSize
    593 	y += paddingSize
    594 
    595 	bs := make([]byte, 4*width*height)
    596 	idx := 0
    597 	for j := y; j < y+height; j++ {
    598 		for i := x; i < x+width; i++ {
    599 			r, g, b, a, err := img.at(i, j)
    600 			if err != nil {
    601 				return nil, err
    602 			}
    603 			bs[4*idx] = r
    604 			bs[4*idx+1] = g
    605 			bs[4*idx+2] = b
    606 			bs[4*idx+3] = a
    607 			idx++
    608 		}
    609 	}
    610 	return bs, nil
    611 }
    612 
    613 func (i *Image) at(x, y int) (byte, byte, byte, byte, error) {
    614 	if i.backend == nil {
    615 		return 0, 0, 0, 0, nil
    616 	}
    617 
    618 	ox, oy, w, h := i.regionWithPadding()
    619 	if x < 0 || y < 0 || x >= w || y >= h {
    620 		return 0, 0, 0, 0, nil
    621 	}
    622 
    623 	return i.backend.restorable.At(x+ox, y+oy)
    624 }
    625 
    626 // MarkDisposed marks the image as disposed. The actual operation is deferred.
    627 // MarkDisposed can be called from finalizers.
    628 //
    629 // A function from finalizer must not be blocked, but disposing operation can be blocked.
    630 // Defer this operation until it becomes safe. (#913)
    631 func (i *Image) MarkDisposed() {
    632 	deferredM.Lock()
    633 	deferred = append(deferred, func() {
    634 		i.dispose(true)
    635 	})
    636 	deferredM.Unlock()
    637 }
    638 
    639 func (i *Image) dispose(markDisposed bool) {
    640 	defer func() {
    641 		if markDisposed {
    642 			i.disposed = true
    643 		}
    644 		i.backend = nil
    645 		i.node = nil
    646 		if markDisposed {
    647 			runtime.SetFinalizer(i, nil)
    648 		}
    649 	}()
    650 
    651 	i.resetUsedAsSourceCount()
    652 
    653 	if i.disposed {
    654 		return
    655 	}
    656 
    657 	if i.backend == nil {
    658 		// Not allocated yet.
    659 		return
    660 	}
    661 
    662 	if !i.isOnAtlas() {
    663 		i.backend.restorable.Dispose()
    664 		return
    665 	}
    666 
    667 	i.backend.page.Free(i.node)
    668 	if !i.backend.page.IsEmpty() {
    669 		// As this part can be reused, this should be cleared explicitly.
    670 		i.backend.restorable.ClearPixels(i.regionWithPadding())
    671 		return
    672 	}
    673 
    674 	i.backend.restorable.Dispose()
    675 	index := -1
    676 	for idx, sh := range theBackends {
    677 		if sh == i.backend {
    678 			index = idx
    679 			break
    680 		}
    681 	}
    682 	if index == -1 {
    683 		panic("atlas: backend not found at an image being disposed")
    684 	}
    685 	theBackends = append(theBackends[:index], theBackends[index+1:]...)
    686 }
    687 
    688 func NewImage(width, height int) *Image {
    689 	// Actual allocation is done lazily, and the lock is not needed.
    690 	return &Image{
    691 		width:  width,
    692 		height: height,
    693 	}
    694 }
    695 
    696 func (i *Image) SetVolatile(volatile bool) {
    697 	i.volatile = volatile
    698 	if i.backend == nil {
    699 		return
    700 	}
    701 	if i.volatile {
    702 		i.ensureIsolated()
    703 	}
    704 	i.backend.restorable.SetVolatile(i.volatile)
    705 }
    706 
    707 func (i *Image) canBePutOnAtlas() bool {
    708 	if minSize == 0 || maxSize == 0 {
    709 		panic("atlas: minSize or maxSize must be initialized")
    710 	}
    711 	if i.volatile {
    712 		return false
    713 	}
    714 	if i.screen {
    715 		return false
    716 	}
    717 	return i.width+2*paddingSize <= maxSize && i.height+2*paddingSize <= maxSize
    718 }
    719 
    720 func (i *Image) allocate(putOnAtlas bool) {
    721 	if i.backend != nil {
    722 		panic("atlas: the image is already allocated")
    723 	}
    724 
    725 	runtime.SetFinalizer(i, (*Image).MarkDisposed)
    726 
    727 	if i.screen {
    728 		// A screen image doesn't have a padding.
    729 		i.backend = &backend{
    730 			restorable: restorable.NewScreenFramebufferImage(i.width, i.height),
    731 		}
    732 		return
    733 	}
    734 
    735 	if !putOnAtlas || !i.canBePutOnAtlas() {
    736 		i.backend = &backend{
    737 			restorable: restorable.NewImage(i.width+2*paddingSize, i.height+2*paddingSize),
    738 		}
    739 		i.backend.restorable.SetVolatile(i.volatile)
    740 		return
    741 	}
    742 
    743 	for _, b := range theBackends {
    744 		if n, ok := b.tryAlloc(i.width+2*paddingSize, i.height+2*paddingSize); ok {
    745 			i.backend = b
    746 			i.node = n
    747 			return
    748 		}
    749 	}
    750 	size := minSize
    751 	for i.width+2*paddingSize > size || i.height+2*paddingSize > size {
    752 		if size == maxSize {
    753 			panic(fmt.Sprintf("atlas: the image being put on an atlas is too big: width: %d, height: %d", i.width, i.height))
    754 		}
    755 		size *= 2
    756 	}
    757 
    758 	b := &backend{
    759 		restorable: restorable.NewImage(size, size),
    760 		page:       packing.NewPage(size, maxSize),
    761 	}
    762 	b.restorable.SetVolatile(i.volatile)
    763 	theBackends = append(theBackends, b)
    764 
    765 	n := b.page.Alloc(i.width+2*paddingSize, i.height+2*paddingSize)
    766 	if n == nil {
    767 		panic("atlas: Alloc result must not be nil at allocate")
    768 	}
    769 	i.backend = b
    770 	i.node = n
    771 }
    772 
    773 func (i *Image) DumpScreenshot(path string, blackbg bool) error {
    774 	backendsM.Lock()
    775 	defer backendsM.Unlock()
    776 
    777 	return i.backend.restorable.Dump(path, blackbg, image.Rect(paddingSize, paddingSize, paddingSize+i.width, paddingSize+i.height))
    778 }
    779 
    780 func NewScreenFramebufferImage(width, height int) *Image {
    781 	// Actual allocation is done lazily.
    782 	i := &Image{
    783 		width:  width,
    784 		height: height,
    785 		screen: true,
    786 	}
    787 	return i
    788 }
    789 
    790 func EndFrame() error {
    791 	backendsM.Lock()
    792 
    793 	theTemporaryPixels.resetAtFrameEnd()
    794 
    795 	return restorable.ResolveStaleImages()
    796 }
    797 
    798 func BeginFrame() error {
    799 	defer backendsM.Unlock()
    800 
    801 	var err error
    802 	initOnce.Do(func() {
    803 		err = restorable.InitializeGraphicsDriverState()
    804 		if err != nil {
    805 			return
    806 		}
    807 		if len(theBackends) != 0 {
    808 			panic("atlas: all the images must be not on an atlas before the game starts")
    809 		}
    810 		minSize = 1024
    811 		maxSize = restorable.MaxImageSize()
    812 	})
    813 	if err != nil {
    814 		return err
    815 	}
    816 
    817 	return restorable.RestoreIfNeeded()
    818 }
    819 
    820 func DumpImages(dir string) error {
    821 	backendsM.Lock()
    822 	defer backendsM.Unlock()
    823 	return restorable.DumpImages(dir)
    824 }