image.go (16042B)
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 shareable 16 17 import ( 18 "fmt" 19 "image/color" 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 graphicsDriver driver.Graphics 38 39 func SetGraphicsDriver(graphics driver.Graphics) { 40 graphicsDriver = graphics 41 } 42 43 var ( 44 minSize = 0 45 maxSize = 0 46 ) 47 48 func max(a, b int) int { 49 if a > b { 50 return a 51 } 52 return b 53 } 54 55 func init() { 56 hooks.AppendHookOnBeforeUpdate(func() error { 57 backendsM.Lock() 58 defer backendsM.Unlock() 59 60 resolveDeferred() 61 return makeImagesShared() 62 }) 63 } 64 65 func resolveDeferred() { 66 deferredM.Lock() 67 fs := deferred 68 deferred = nil 69 deferredM.Unlock() 70 71 for _, f := range fs { 72 f() 73 } 74 } 75 76 // MaxCountForShare represents the time duration when the image can become shared. 77 // 78 // This value is exported for testing. 79 const MaxCountForShare = 10 80 81 func makeImagesShared() error { 82 for i := range imagesToMakeShared { 83 i.nonUpdatedCount++ 84 if i.nonUpdatedCount >= MaxCountForShare { 85 if err := i.makeShared(); err != nil { 86 return err 87 } 88 } 89 delete(imagesToMakeShared, i) 90 } 91 return nil 92 } 93 94 type backend struct { 95 restorable *restorable.Image 96 97 // If page is nil, the backend is not shared. 98 page *packing.Page 99 } 100 101 func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) { 102 // If the region is allocated without any extension, that's fine. 103 if n := b.page.Alloc(width, height); n != nil { 104 return n, true 105 } 106 107 nExtended := 1 108 var n *packing.Node 109 for { 110 if !b.page.Extend(nExtended) { 111 // The page can't be extended any more. Return as failure. 112 return nil, false 113 } 114 nExtended++ 115 n = b.page.Alloc(width, height) 116 if n != nil { 117 b.page.CommitExtension() 118 break 119 } 120 b.page.RollbackExtension() 121 } 122 123 s := b.page.Size() 124 b.restorable = b.restorable.Extend(s, s) 125 126 if n == nil { 127 panic("shareable: Alloc result must not be nil at TryAlloc") 128 } 129 return n, true 130 } 131 132 var ( 133 // backendsM is a mutex for critical sections of the backend and packing.Node objects. 134 backendsM sync.Mutex 135 136 initOnce sync.Once 137 138 // theBackends is a set of actually shared images. 139 theBackends = []*backend{} 140 141 imagesToMakeShared = map[*Image]struct{}{} 142 143 deferred []func() 144 145 // deferredM is a mutext for the slice operations. This must not be used for other usages. 146 deferredM sync.Mutex 147 ) 148 149 func init() { 150 // Lock the mutex before a frame begins. 151 // 152 // In each frame, restoring images and resolving images happen respectively: 153 // 154 // [Restore -> Resolve] -> [Restore -> Resolve] -> ... 155 // 156 // Between each frame, any image operations are not permitted, or stale images would remain when restoring 157 // (#913). 158 backendsM.Lock() 159 } 160 161 type Image struct { 162 width int 163 height int 164 disposed bool 165 volatile bool 166 screen bool 167 168 backend *backend 169 170 node *packing.Node 171 172 // nonUpdatedCount represents how long the image is kept not modified with DrawTriangles. 173 // In the current implementation, if an image is being modified by DrawTriangles, the image is separated from 174 // a shared (restorable) image by ensureNotShared. 175 // 176 // nonUpdatedCount is increased every frame if the image is not modified, or set to 0 if the image is 177 // modified. 178 // 179 // ReplacePixels doesn't affect this value since ReplacePixels can be done on shared images. 180 nonUpdatedCount int 181 } 182 183 func (i *Image) moveTo(dst *Image) { 184 dst.dispose(false) 185 *dst = *i 186 187 // i is no longer available but Dispose must not be called 188 // since i and dst have the same values like node. 189 runtime.SetFinalizer(i, nil) 190 } 191 192 func (i *Image) isShared() bool { 193 return i.node != nil 194 } 195 196 func (i *Image) ensureNotShared() { 197 if i.backend == nil { 198 i.allocate(false) 199 return 200 } 201 202 if !i.isShared() { 203 return 204 } 205 206 ox, oy, w, h := i.regionWithPadding() 207 dx0 := float32(0) 208 dy0 := float32(0) 209 dx1 := float32(w) 210 dy1 := float32(h) 211 sx0 := float32(ox) 212 sy0 := float32(oy) 213 sx1 := float32(ox + w) 214 sy1 := float32(oy + h) 215 newImg := restorable.NewImage(w, h) 216 newImg.SetVolatile(i.volatile) 217 vs := []float32{ 218 dx0, dy0, sx0, sy0, 1, 1, 1, 1, 219 dx1, dy0, sx1, sy0, 1, 1, 1, 1, 220 dx0, dy1, sx0, sy1, 1, 1, 1, 1, 221 dx1, dy1, sx1, sy1, 1, 1, 1, 1, 222 } 223 is := graphics.QuadIndices() 224 srcs := [graphics.ShaderImageNum]*restorable.Image{i.backend.restorable} 225 var offsets [graphics.ShaderImageNum - 1][2]float32 226 newImg.DrawTriangles(srcs, offsets, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil) 227 228 i.dispose(false) 229 i.backend = &backend{ 230 restorable: newImg, 231 } 232 } 233 234 func (i *Image) makeShared() error { 235 if i.backend == nil { 236 i.allocate(true) 237 return nil 238 } 239 240 if i.isShared() { 241 return nil 242 } 243 244 if !i.shareable() { 245 panic("shareable: makeShared cannot be called on a non-shareable image") 246 } 247 248 newI := NewImage(i.width, i.height) 249 newI.SetVolatile(i.volatile) 250 pixels := make([]byte, 4*i.width*i.height) 251 for y := 0; y < i.height; y++ { 252 for x := 0; x < i.width; x++ { 253 r, g, b, a, err := i.at(x+paddingSize, y+paddingSize) 254 if err != nil { 255 return err 256 } 257 pixels[4*(i.width*y+x)] = r 258 pixels[4*(i.width*y+x)+1] = g 259 pixels[4*(i.width*y+x)+2] = b 260 pixels[4*(i.width*y+x)+3] = a 261 } 262 } 263 newI.replacePixels(pixels) 264 newI.moveTo(i) 265 i.nonUpdatedCount = 0 266 return nil 267 } 268 269 func (i *Image) regionWithPadding() (x, y, width, height int) { 270 if i.backend == nil { 271 panic("shareable: backend must not be nil: not allocated yet?") 272 } 273 if !i.isShared() { 274 return 0, 0, i.width + 2*paddingSize, i.height + 2*paddingSize 275 } 276 return i.node.Region() 277 } 278 279 func (i *Image) processSrc(src *Image) { 280 if src == nil { 281 return 282 } 283 if src.disposed { 284 panic("shareable: the drawing source image must not be disposed (DrawTriangles)") 285 } 286 if src.backend == nil { 287 src.allocate(true) 288 } 289 290 // Compare i and source images after ensuring i is not shared, or 291 // i and a source image might share the same texture even though i != src. 292 if i.backend.restorable == src.backend.restorable { 293 panic("shareable: Image.DrawTriangles: source must be different from the receiver") 294 } 295 } 296 297 // DrawTriangles draws triangles with the given image. 298 // 299 // The vertex floats are: 300 // 301 // 0: Destination X in pixels 302 // 1: Destination Y in pixels 303 // 2: Source X in pixels (the upper-left is (0, 0)) 304 // 3: Source Y in pixels 305 // 4: Color R [0.0-1.0] 306 // 5: Color G 307 // 6: Color B 308 // 7: Color Y 309 func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}) { 310 backendsM.Lock() 311 // Do not use defer for performance. 312 313 if i.disposed { 314 panic("shareable: the drawing target image must not be disposed (DrawTriangles)") 315 } 316 i.ensureNotShared() 317 318 for _, src := range srcs { 319 i.processSrc(src) 320 } 321 322 var dx, dy float32 323 // A screen image doesn't have its padding. 324 if !i.screen { 325 dx = paddingSize 326 dy = paddingSize 327 } 328 329 var oxf, oyf float32 330 if srcs[0] != nil { 331 ox, oy, _, _ := srcs[0].regionWithPadding() 332 ox += paddingSize 333 oy += paddingSize 334 oxf, oyf = float32(ox), float32(oy) 335 n := len(vertices) / graphics.VertexFloatNum 336 for i := 0; i < n; i++ { 337 vertices[i*graphics.VertexFloatNum+0] += dx 338 vertices[i*graphics.VertexFloatNum+1] += dy 339 vertices[i*graphics.VertexFloatNum+2] += oxf 340 vertices[i*graphics.VertexFloatNum+3] += oyf 341 } 342 // sourceRegion can be delibarately empty when this is not needed in order to avoid unexpected 343 // performance issue (#1293). 344 if sourceRegion.Width != 0 && sourceRegion.Height != 0 { 345 sourceRegion.X += oxf 346 sourceRegion.Y += oyf 347 } 348 } else { 349 n := len(vertices) / graphics.VertexFloatNum 350 for i := 0; i < n; i++ { 351 vertices[i*graphics.VertexFloatNum+0] += dx 352 vertices[i*graphics.VertexFloatNum+1] += dy 353 } 354 } 355 356 var offsets [graphics.ShaderImageNum - 1][2]float32 357 var s *restorable.Shader 358 var imgs [graphics.ShaderImageNum]*restorable.Image 359 if shader == nil { 360 // Fast path for rendering without a shader (#1355). 361 imgs[0] = srcs[0].backend.restorable 362 } else { 363 for i, subimageOffset := range subimageOffsets { 364 src := srcs[i+1] 365 if src == nil { 366 continue 367 } 368 ox, oy, _, _ := src.regionWithPadding() 369 offsets[i][0] = float32(ox) + paddingSize - oxf + subimageOffset[0] 370 offsets[i][1] = float32(oy) + paddingSize - oyf + subimageOffset[1] 371 } 372 s = shader.shader 373 for i, src := range srcs { 374 if src == nil { 375 continue 376 } 377 imgs[i] = src.backend.restorable 378 } 379 } 380 381 i.backend.restorable.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms) 382 383 i.nonUpdatedCount = 0 384 delete(imagesToMakeShared, i) 385 386 for _, src := range srcs { 387 if src == nil { 388 continue 389 } 390 if !src.isShared() && src.shareable() { 391 imagesToMakeShared[src] = struct{}{} 392 } 393 } 394 395 backendsM.Unlock() 396 } 397 398 func (i *Image) Fill(clr color.RGBA) { 399 backendsM.Lock() 400 defer backendsM.Unlock() 401 402 if i.disposed { 403 panic("shareable: the drawing target image must not be disposed (Fill)") 404 } 405 if i.backend == nil { 406 if _, _, _, a := clr.RGBA(); a == 0 { 407 return 408 } 409 } 410 411 i.ensureNotShared() 412 413 // As *restorable.Image is an independent image, it is fine to fill the entire image. 414 i.backend.restorable.Fill(clr) 415 416 i.nonUpdatedCount = 0 417 delete(imagesToMakeShared, i) 418 } 419 420 func (i *Image) ReplacePixels(pix []byte) { 421 backendsM.Lock() 422 defer backendsM.Unlock() 423 i.replacePixels(pix) 424 } 425 426 func (i *Image) replacePixels(pix []byte) { 427 if i.disposed { 428 panic("shareable: the image must not be disposed at replacePixels") 429 } 430 if i.backend == nil { 431 if pix == nil { 432 return 433 } 434 i.allocate(true) 435 } 436 437 x, y, w, h := i.regionWithPadding() 438 if pix == nil { 439 i.backend.restorable.ReplacePixels(nil, x, y, w, h) 440 return 441 } 442 443 ow, oh := w-2*paddingSize, h-2*paddingSize 444 if l := 4 * ow * oh; len(pix) != l { 445 panic(fmt.Sprintf("shareable: len(p) must be %d but %d", l, len(pix))) 446 } 447 448 // Add a padding around the image. 449 pixb := make([]byte, 4*w*h) 450 for j := 0; j < oh; j++ { 451 copy(pixb[4*((j+paddingSize)*w+paddingSize):], pix[4*j*ow:4*(j+1)*ow]) 452 } 453 454 i.backend.restorable.ReplacePixels(pixb, x, y, w, h) 455 } 456 457 func (img *Image) Pixels(x, y, width, height int) ([]byte, error) { 458 backendsM.Lock() 459 defer backendsM.Unlock() 460 461 x += paddingSize 462 y += paddingSize 463 464 bs := make([]byte, 4*width*height) 465 idx := 0 466 for j := y; j < y+height; j++ { 467 for i := x; i < x+width; i++ { 468 r, g, b, a, err := img.at(i, j) 469 if err != nil { 470 return nil, err 471 } 472 bs[4*idx] = r 473 bs[4*idx+1] = g 474 bs[4*idx+2] = b 475 bs[4*idx+3] = a 476 idx++ 477 } 478 } 479 return bs, nil 480 } 481 482 func (i *Image) at(x, y int) (byte, byte, byte, byte, error) { 483 if i.backend == nil { 484 return 0, 0, 0, 0, nil 485 } 486 487 ox, oy, w, h := i.regionWithPadding() 488 if x < 0 || y < 0 || x >= w || y >= h { 489 return 0, 0, 0, 0, nil 490 } 491 492 return i.backend.restorable.At(x+ox, y+oy) 493 } 494 495 // MarkDisposed marks the image as disposed. The actual operation is deferred. 496 // MarkDisposed can be called from finalizers. 497 // 498 // A function from finalizer must not be blocked, but disposing operation can be blocked. 499 // Defer this operation until it becomes safe. (#913) 500 func (i *Image) MarkDisposed() { 501 deferredM.Lock() 502 deferred = append(deferred, func() { 503 i.dispose(true) 504 }) 505 deferredM.Unlock() 506 } 507 508 func (i *Image) dispose(markDisposed bool) { 509 defer func() { 510 if markDisposed { 511 i.disposed = true 512 } 513 i.backend = nil 514 i.node = nil 515 if markDisposed { 516 runtime.SetFinalizer(i, nil) 517 } 518 }() 519 520 if i.disposed { 521 return 522 } 523 524 if i.backend == nil { 525 // Not allocated yet. 526 return 527 } 528 529 if !i.isShared() { 530 i.backend.restorable.Dispose() 531 return 532 } 533 534 i.backend.page.Free(i.node) 535 if !i.backend.page.IsEmpty() { 536 // As this part can be reused, this should be cleared explicitly. 537 i.backend.restorable.ClearPixels(i.regionWithPadding()) 538 return 539 } 540 541 i.backend.restorable.Dispose() 542 index := -1 543 for idx, sh := range theBackends { 544 if sh == i.backend { 545 index = idx 546 break 547 } 548 } 549 if index == -1 { 550 panic("shareable: backend not found at an image being disposed") 551 } 552 theBackends = append(theBackends[:index], theBackends[index+1:]...) 553 } 554 555 func NewImage(width, height int) *Image { 556 // Actual allocation is done lazily, and the lock is not needed. 557 return &Image{ 558 width: width, 559 height: height, 560 } 561 } 562 563 func (i *Image) SetVolatile(volatile bool) { 564 i.volatile = volatile 565 if i.backend == nil { 566 return 567 } 568 if i.volatile { 569 i.ensureNotShared() 570 } 571 i.backend.restorable.SetVolatile(i.volatile) 572 } 573 574 func (i *Image) shareable() bool { 575 if minSize == 0 || maxSize == 0 { 576 panic("shareable: minSize or maxSize must be initialized") 577 } 578 if i.volatile { 579 return false 580 } 581 if i.screen { 582 return false 583 } 584 return i.width+2*paddingSize <= maxSize && i.height+2*paddingSize <= maxSize 585 } 586 587 func (i *Image) allocate(shareable bool) { 588 if i.backend != nil { 589 panic("shareable: the image is already allocated") 590 } 591 592 runtime.SetFinalizer(i, (*Image).MarkDisposed) 593 594 if i.screen { 595 // A screen image doesn't have a padding. 596 i.backend = &backend{ 597 restorable: restorable.NewScreenFramebufferImage(i.width, i.height), 598 } 599 return 600 } 601 602 if !shareable || !i.shareable() { 603 i.backend = &backend{ 604 restorable: restorable.NewImage(i.width+2*paddingSize, i.height+2*paddingSize), 605 } 606 i.backend.restorable.SetVolatile(i.volatile) 607 return 608 } 609 610 for _, b := range theBackends { 611 if n, ok := b.TryAlloc(i.width+2*paddingSize, i.height+2*paddingSize); ok { 612 i.backend = b 613 i.node = n 614 return 615 } 616 } 617 size := minSize 618 for i.width+2*paddingSize > size || i.height+2*paddingSize > size { 619 if size == maxSize { 620 panic(fmt.Sprintf("shareable: the image being shared is too big: width: %d, height: %d", i.width, i.height)) 621 } 622 size *= 2 623 } 624 625 b := &backend{ 626 restorable: restorable.NewImage(size, size), 627 page: packing.NewPage(size, maxSize), 628 } 629 b.restorable.SetVolatile(i.volatile) 630 theBackends = append(theBackends, b) 631 632 n := b.page.Alloc(i.width+2*paddingSize, i.height+2*paddingSize) 633 if n == nil { 634 panic("shareable: Alloc result must not be nil at allocate") 635 } 636 i.backend = b 637 i.node = n 638 } 639 640 func (i *Image) Dump(path string, blackbg bool) error { 641 backendsM.Lock() 642 defer backendsM.Unlock() 643 644 return i.backend.restorable.Dump(path, blackbg) 645 } 646 647 func NewScreenFramebufferImage(width, height int) *Image { 648 // Actual allocation is done lazily. 649 i := &Image{ 650 width: width, 651 height: height, 652 screen: true, 653 } 654 return i 655 } 656 657 func EndFrame() error { 658 backendsM.Lock() 659 660 return restorable.ResolveStaleImages() 661 } 662 663 func BeginFrame() error { 664 defer backendsM.Unlock() 665 666 var err error 667 initOnce.Do(func() { 668 err = restorable.InitializeGraphicsDriverState() 669 if err != nil { 670 return 671 } 672 if len(theBackends) != 0 { 673 panic("shareable: all the images must be not-shared before the game starts") 674 } 675 minSize = 1024 676 maxSize = max(minSize, graphicsDriver.MaxImageSize()) 677 }) 678 if err != nil { 679 return err 680 } 681 682 return restorable.RestoreIfNeeded() 683 } 684 685 func DumpImages(dir string) error { 686 backendsM.Lock() 687 defer backendsM.Unlock() 688 return restorable.DumpImages(dir) 689 }