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 }