image.go (19851B)
1 // Copyright 2016 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 restorable 16 17 import ( 18 "fmt" 19 "image" 20 21 "github.com/hajimehoshi/ebiten/v2/internal/affine" 22 "github.com/hajimehoshi/ebiten/v2/internal/driver" 23 "github.com/hajimehoshi/ebiten/v2/internal/graphics" 24 "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" 25 ) 26 27 type Pixels struct { 28 rectToPixels *rectToPixels 29 } 30 31 // Apply applies the Pixels state to the given image especially for restoring. 32 func (p *Pixels) Apply(img *graphicscommand.Image) { 33 // Pixels doesn't clear the image. This is a caller's responsibility. 34 35 if p.rectToPixels == nil { 36 return 37 } 38 p.rectToPixels.apply(img) 39 } 40 41 func (p *Pixels) AddOrReplace(pix []byte, x, y, width, height int) { 42 if p.rectToPixels == nil { 43 p.rectToPixels = &rectToPixels{} 44 } 45 p.rectToPixels.addOrReplace(pix, x, y, width, height) 46 } 47 48 func (p *Pixels) Remove(x, y, width, height int) { 49 // Note that we don't care whether the region is actually removed or not here. There is an actual case that 50 // the region is allocated but nothing is rendered. See TestDisposeImmediately at shareable package. 51 if p.rectToPixels == nil { 52 return 53 } 54 p.rectToPixels.remove(x, y, width, height) 55 } 56 57 func (p *Pixels) At(i, j int) (byte, byte, byte, byte) { 58 if p.rectToPixels != nil { 59 if r, g, b, a, ok := p.rectToPixels.at(i, j); ok { 60 return r, g, b, a 61 } 62 } 63 return 0, 0, 0, 0 64 } 65 66 // drawTrianglesHistoryItem is an item for history of draw-image commands. 67 type drawTrianglesHistoryItem struct { 68 images [graphics.ShaderImageNum]*Image 69 offsets [graphics.ShaderImageNum - 1][2]float32 70 vertices []float32 71 indices []uint16 72 colorm affine.ColorM 73 mode driver.CompositeMode 74 filter driver.Filter 75 address driver.Address 76 dstRegion driver.Region 77 srcRegion driver.Region 78 shader *Shader 79 uniforms []interface{} 80 evenOdd bool 81 } 82 83 // Image represents an image that can be restored when GL context is lost. 84 type Image struct { 85 image *graphicscommand.Image 86 87 width int 88 height int 89 90 basePixels Pixels 91 92 // drawTrianglesHistory is a set of draw-image commands. 93 // TODO: This should be merged with the similar command queue in package graphics (#433). 94 drawTrianglesHistory []*drawTrianglesHistoryItem 95 96 // stale indicates whether the image needs to be synced with GPU as soon as possible. 97 stale bool 98 99 // volatile indicates whether the image is cleared whenever a frame starts. 100 volatile bool 101 102 // screen indicates whether the image is used as an actual screen. 103 screen bool 104 105 // priority indicates whether the image is restored in high priority when context-lost happens. 106 priority bool 107 } 108 109 var emptyImage *Image 110 111 func ensureEmptyImage() *Image { 112 if emptyImage != nil { 113 return emptyImage 114 } 115 116 // Initialize the empty image lazily. Some functions like NeedsRestoring might not work at the initial phase. 117 118 // w and h are the empty image's size. They indicate the 1x1 image with 1px padding around. 119 const w, h = 3, 3 120 emptyImage = &Image{ 121 image: graphicscommand.NewImage(w, h), 122 width: w, 123 height: h, 124 priority: true, 125 } 126 pix := make([]byte, 4*w*h) 127 for i := range pix { 128 pix[i] = 0xff 129 } 130 131 // As emptyImage is the source at clearImage, initialize this with ReplacePixels, not clearImage. 132 // This operation is also important when restoring emptyImage. 133 emptyImage.ReplacePixels(pix, 0, 0, w, h) 134 theImages.add(emptyImage) 135 return emptyImage 136 } 137 138 // NewImage creates an empty image with the given size. 139 // 140 // The returned image is cleared. 141 // 142 // Note that Dispose is not called automatically. 143 func NewImage(width, height int) *Image { 144 if !graphicsDriverInitialized { 145 panic("restorable: graphics driver must be ready at NewImage but not") 146 } 147 148 i := &Image{ 149 image: graphicscommand.NewImage(width, height), 150 width: width, 151 height: height, 152 } 153 clearImage(i.image) 154 theImages.add(i) 155 return i 156 } 157 158 // SetVolatile sets the volatile state of the image. 159 // 160 // Regular non-volatile images need to record drawing history or read its pixels from GPU if necessary so that all 161 // the images can be restored automatically from the context lost. However, such recording the drawing history or 162 // reading pixels from GPU are expensive operations. Volatile images can skip such oprations, but the image content 163 // is cleared every frame instead. 164 func (i *Image) SetVolatile(volatile bool) { 165 changed := i.volatile != volatile 166 i.volatile = volatile 167 if changed { 168 i.makeStale() 169 } 170 } 171 172 // Extend extends the image by the given size. 173 // Extend creates a new image with the given size and copies the pixels of the given source image. 174 // Extend disposes itself after its call. 175 // 176 // If the given size (width and height) is smaller than the source image, ExtendImage panics. 177 // 178 // The image must be ReplacePixels-only image. Extend panics when Fill or DrawTriangles are applied on the image. 179 // 180 // Extend panics when the image is stale. 181 func (i *Image) Extend(width, height int) *Image { 182 if i.width > width || i.height > height { 183 panic(fmt.Sprintf("restorable: the original size (%d, %d) cannot be extended to (%d, %d)", i.width, i.height, width, height)) 184 } 185 186 newImg := NewImage(width, height) 187 newImg.SetVolatile(i.volatile) 188 189 // Use DrawTriangles instead of ReplacePixels because the image i might be stale and not have its pixels 190 // information. 191 srcs := [graphics.ShaderImageNum]*Image{i} 192 var offsets [graphics.ShaderImageNum - 1][2]float32 193 sw, sh := i.image.InternalSize() 194 vs := quadVertices(0, 0, float32(sw), float32(sh), 0, 0, float32(sw), float32(sh), 1, 1, 1, 1) 195 is := graphics.QuadIndices() 196 dr := driver.Region{ 197 X: 0, 198 Y: 0, 199 Width: float32(sw), 200 Height: float32(sh), 201 } 202 newImg.DrawTriangles(srcs, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil, false) 203 204 // Overwrite the history as if the image newImg is created only by ReplacePixels. Now drawTrianglesHistory 205 // and basePixels cannot be mixed. 206 newImg.clearDrawTrianglesHistory() 207 newImg.basePixels = i.basePixels 208 newImg.stale = i.stale 209 210 i.Dispose() 211 212 return newImg 213 } 214 215 // NewScreenFramebufferImage creates a special image that framebuffer is one for the screen. 216 // 217 // The returned image is cleared. 218 // 219 // Note that Dispose is not called automatically. 220 func NewScreenFramebufferImage(width, height int) *Image { 221 i := &Image{ 222 image: graphicscommand.NewScreenFramebufferImage(width, height), 223 width: width, 224 height: height, 225 screen: true, 226 } 227 clearImage(i.image) 228 theImages.add(i) 229 return i 230 } 231 232 // quadVertices returns vertices to render a quad. These values are passed to graphicscommand.Image. 233 func quadVertices(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, cr, cg, cb, ca float32) []float32 { 234 return []float32{ 235 dx0, dy0, sx0, sy0, cr, cg, cb, ca, 236 dx1, dy0, sx1, sy0, cr, cg, cb, ca, 237 dx0, dy1, sx0, sy1, cr, cg, cb, ca, 238 dx1, dy1, sx1, sy1, cr, cg, cb, ca, 239 } 240 } 241 242 func clearImage(i *graphicscommand.Image) { 243 emptyImage := ensureEmptyImage() 244 245 if i == emptyImage.image { 246 panic("restorable: fillImage cannot be called on emptyImage") 247 } 248 249 // This needs to use 'InternalSize' to render the whole region, or edges are unexpectedly cleared on some 250 // devices. 251 // 252 // TODO: Can we unexport InternalSize()? 253 dw, dh := i.InternalSize() 254 sw, sh := emptyImage.width, emptyImage.height 255 vs := quadVertices(0, 0, float32(dw), float32(dh), 1, 1, float32(sw-1), float32(sh-1), 0, 0, 0, 0) 256 is := graphics.QuadIndices() 257 srcs := [graphics.ShaderImageNum]*graphicscommand.Image{emptyImage.image} 258 var offsets [graphics.ShaderImageNum - 1][2]float32 259 dstRegion := driver.Region{ 260 X: 0, 261 Y: 0, 262 Width: float32(dw), 263 Height: float32(dh), 264 } 265 i.DrawTriangles(srcs, offsets, vs, is, affine.ColorMIdentity{}, driver.CompositeModeClear, driver.FilterNearest, driver.AddressUnsafe, dstRegion, driver.Region{}, nil, nil, false) 266 } 267 268 // BasePixelsForTesting returns the image's basePixels for testing. 269 func (i *Image) BasePixelsForTesting() *Pixels { 270 return &i.basePixels 271 } 272 273 // makeStale makes the image stale. 274 func (i *Image) makeStale() { 275 i.basePixels = Pixels{} 276 i.clearDrawTrianglesHistory() 277 i.stale = true 278 279 // Don't have to call makeStale recursively here. 280 // Restoring is done after topological sorting is done. 281 // If an image depends on another stale image, this means that 282 // the former image can be restored from the latest state of the latter image. 283 } 284 285 // ClearPixels clears the specified region by ReplacePixels. 286 func (i *Image) ClearPixels(x, y, width, height int) { 287 i.ReplacePixels(nil, x, y, width, height) 288 } 289 290 // ReplacePixels replaces the image pixels with the given pixels slice. 291 // 292 // ReplacePixels for a part is forbidden if the image is rendered with DrawTriangles or Fill. 293 func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { 294 if width <= 0 || height <= 0 { 295 panic("restorable: width/height must be positive") 296 } 297 w, h := i.width, i.height 298 if x < 0 || y < 0 || w <= x || h <= y || x+width <= 0 || y+height <= 0 || w < x+width || h < y+height { 299 panic(fmt.Sprintf("restorable: out of range x: %d, y: %d, width: %d, height: %d", x, y, width, height)) 300 } 301 302 // TODO: Avoid making other images stale if possible. (#514) 303 // For this purpuse, images should remember which part of that is used for DrawTriangles. 304 theImages.makeStaleIfDependingOn(i) 305 306 if pixels != nil { 307 i.image.ReplacePixels(pixels, x, y, width, height) 308 } else { 309 // TODO: When pixels == nil, we don't have to care the pixel state there. In such cases, the image 310 // accepts only ReplacePixels and not Fill or DrawTriangles. 311 // TODO: Separate Image struct into two: images for only-ReplacePixels, and the others. 312 i.image.ReplacePixels(make([]byte, 4*width*height), x, y, width, height) 313 } 314 315 if !NeedsRestoring() || i.screen || i.volatile { 316 i.makeStale() 317 return 318 } 319 320 if x == 0 && y == 0 && width == w && height == h { 321 if pixels != nil { 322 // pixels can point to a shared region. 323 // This function is responsible to copy this. 324 copiedPixels := make([]byte, len(pixels)) 325 copy(copiedPixels, pixels) 326 i.basePixels.AddOrReplace(copiedPixels, 0, 0, w, h) 327 } else { 328 i.basePixels.Remove(0, 0, w, h) 329 } 330 i.clearDrawTrianglesHistory() 331 i.stale = false 332 return 333 } 334 335 // drawTrianglesHistory and basePixels cannot be mixed. 336 if len(i.drawTrianglesHistory) > 0 { 337 panic("restorable: ReplacePixels for a part after DrawTriangles is forbidden") 338 } 339 340 if i.stale { 341 // TODO: panic here? 342 return 343 } 344 345 if pixels != nil { 346 // pixels can point to a shared region. 347 // This function is responsible to copy this. 348 copiedPixels := make([]byte, len(pixels)) 349 copy(copiedPixels, pixels) 350 i.basePixels.AddOrReplace(copiedPixels, x, y, width, height) 351 } else { 352 i.basePixels.Remove(x, y, width, height) 353 } 354 } 355 356 // DrawTriangles draws triangles with the given image. 357 // 358 // The vertex floats are: 359 // 360 // 0: Destination X in pixels 361 // 1: Destination Y in pixels 362 // 2: Source X in pixels (not texels!) 363 // 3: Source Y in pixels 364 // 4: Color R [0.0-1.0] 365 // 5: Color G 366 // 6: Color B 367 // 7: Color Y 368 func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) { 369 if i.priority { 370 panic("restorable: DrawTriangles cannot be called on a priority image") 371 } 372 if len(vertices) == 0 { 373 return 374 } 375 theImages.makeStaleIfDependingOn(i) 376 377 // TODO: Add tests to confirm this logic. 378 var srcstale bool 379 for _, src := range srcs { 380 if src == nil { 381 continue 382 } 383 if src.stale || src.volatile { 384 srcstale = true 385 break 386 } 387 } 388 389 if srcstale || i.screen || !NeedsRestoring() || i.volatile { 390 i.makeStale() 391 } else { 392 i.appendDrawTrianglesHistory(srcs, offsets, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, shader, uniforms, evenOdd) 393 } 394 395 var s *graphicscommand.Shader 396 var imgs [graphics.ShaderImageNum]*graphicscommand.Image 397 if shader == nil { 398 // Fast path for rendering without a shader (#1355). 399 imgs[0] = srcs[0].image 400 } else { 401 for i, src := range srcs { 402 if src == nil { 403 continue 404 } 405 imgs[i] = src.image 406 } 407 s = shader.shader 408 } 409 i.image.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, s, uniforms, evenOdd) 410 } 411 412 // appendDrawTrianglesHistory appends a draw-image history item to the image. 413 func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) { 414 if i.stale || i.volatile || i.screen { 415 return 416 } 417 // TODO: Would it be possible to merge draw image history items? 418 const maxDrawTrianglesHistoryNum = 1024 419 if len(i.drawTrianglesHistory)+1 > maxDrawTrianglesHistoryNum { 420 i.makeStale() 421 return 422 } 423 // All images must be resolved and not stale each after frame. 424 // So we don't have to care if image is stale or not here. 425 426 vs := make([]float32, len(vertices)) 427 copy(vs, vertices) 428 429 is := make([]uint16, len(indices)) 430 copy(is, indices) 431 432 item := &drawTrianglesHistoryItem{ 433 images: srcs, 434 offsets: offsets, 435 vertices: vs, 436 indices: is, 437 colorm: colorm, 438 mode: mode, 439 filter: filter, 440 address: address, 441 dstRegion: dstRegion, 442 srcRegion: srcRegion, 443 shader: shader, 444 uniforms: uniforms, 445 evenOdd: evenOdd, 446 } 447 i.drawTrianglesHistory = append(i.drawTrianglesHistory, item) 448 } 449 450 func (i *Image) readPixelsFromGPUIfNeeded() error { 451 if len(i.drawTrianglesHistory) > 0 || i.stale { 452 if err := graphicscommand.FlushCommands(); err != nil { 453 return err 454 } 455 if err := i.readPixelsFromGPU(); err != nil { 456 return err 457 } 458 } 459 return nil 460 } 461 462 // At returns a color value at (x, y). 463 // 464 // Note that this must not be called until context is available. 465 func (i *Image) At(x, y int) (byte, byte, byte, byte, error) { 466 if x < 0 || y < 0 || i.width <= x || i.height <= y { 467 return 0, 0, 0, 0, nil 468 } 469 470 if err := i.readPixelsFromGPUIfNeeded(); err != nil { 471 return 0, 0, 0, 0, err 472 } 473 474 r, g, b, a := i.basePixels.At(x, y) 475 return r, g, b, a, nil 476 } 477 478 // makeStaleIfDependingOn makes the image stale if the image depends on target. 479 func (i *Image) makeStaleIfDependingOn(target *Image) { 480 if i.stale { 481 return 482 } 483 if i.dependsOn(target) { 484 i.makeStale() 485 } 486 } 487 488 // makeStaleIfDependingOnShader makes the image stale if the image depends on shader. 489 func (i *Image) makeStaleIfDependingOnShader(shader *Shader) { 490 if i.stale { 491 return 492 } 493 if i.dependsOnShader(shader) { 494 i.makeStale() 495 } 496 } 497 498 // readPixelsFromGPU reads the pixels from GPU and resolves the image's 'stale' state. 499 func (i *Image) readPixelsFromGPU() error { 500 pix, err := i.image.Pixels() 501 if err != nil { 502 return err 503 } 504 i.basePixels = Pixels{} 505 i.basePixels.AddOrReplace(pix, 0, 0, i.width, i.height) 506 i.clearDrawTrianglesHistory() 507 i.stale = false 508 return nil 509 } 510 511 // resolveStale resolves the image's 'stale' state. 512 func (i *Image) resolveStale() error { 513 if !NeedsRestoring() { 514 return nil 515 } 516 517 if i.volatile { 518 return nil 519 } 520 if i.screen { 521 return nil 522 } 523 if !i.stale { 524 return nil 525 } 526 return i.readPixelsFromGPU() 527 } 528 529 // dependsOn reports whether the image depends on target. 530 func (i *Image) dependsOn(target *Image) bool { 531 for _, c := range i.drawTrianglesHistory { 532 for _, img := range c.images { 533 if img == nil { 534 continue 535 } 536 if img == target { 537 return true 538 } 539 } 540 } 541 return false 542 } 543 544 // dependsOnShader reports whether the image depends on shader. 545 func (i *Image) dependsOnShader(shader *Shader) bool { 546 for _, c := range i.drawTrianglesHistory { 547 if c.shader == shader { 548 return true 549 } 550 } 551 return false 552 } 553 554 // dependingImages returns all images that is depended by the image. 555 func (i *Image) dependingImages() map[*Image]struct{} { 556 r := map[*Image]struct{}{} 557 for _, c := range i.drawTrianglesHistory { 558 for _, img := range c.images { 559 if img == nil { 560 continue 561 } 562 r[img] = struct{}{} 563 } 564 } 565 return r 566 } 567 568 // hasDependency returns a boolean value indicating whether the image depends on another image. 569 func (i *Image) hasDependency() bool { 570 if i.stale { 571 return false 572 } 573 return len(i.drawTrianglesHistory) > 0 574 } 575 576 // Restore restores *graphicscommand.Image from the pixels using its state. 577 func (i *Image) restore() error { 578 w, h := i.width, i.height 579 // Do not dispose the image here. The image should be already disposed. 580 581 if i.screen { 582 // The screen image should also be recreated because framebuffer might 583 // be changed. 584 i.image = graphicscommand.NewScreenFramebufferImage(w, h) 585 i.basePixels = Pixels{} 586 i.clearDrawTrianglesHistory() 587 i.stale = false 588 return nil 589 } 590 if i.volatile { 591 i.image = graphicscommand.NewImage(w, h) 592 clearImage(i.image) 593 return nil 594 } 595 if i.stale { 596 panic("restorable: pixels must not be stale when restoring") 597 } 598 599 gimg := graphicscommand.NewImage(w, h) 600 // Clear the image explicitly. 601 if i != ensureEmptyImage() { 602 // As clearImage uses emptyImage, clearImage cannot be called on emptyImage. 603 // It is OK to skip this since emptyImage has its entire pixel information. 604 clearImage(gimg) 605 } 606 i.basePixels.Apply(gimg) 607 608 for _, c := range i.drawTrianglesHistory { 609 var s *graphicscommand.Shader 610 if c.shader != nil { 611 s = c.shader.shader 612 } 613 614 var imgs [graphics.ShaderImageNum]*graphicscommand.Image 615 for i, img := range c.images { 616 if img == nil { 617 continue 618 } 619 if img.hasDependency() { 620 panic("restorable: all dependencies must be already resolved but not") 621 } 622 imgs[i] = img.image 623 } 624 gimg.DrawTriangles(imgs, c.offsets, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address, c.dstRegion, c.srcRegion, s, c.uniforms, c.evenOdd) 625 } 626 627 if len(i.drawTrianglesHistory) > 0 { 628 i.basePixels = Pixels{} 629 pix, err := gimg.Pixels() 630 if err != nil { 631 return err 632 } 633 i.basePixels.AddOrReplace(pix, 0, 0, w, h) 634 } 635 636 i.image = gimg 637 i.clearDrawTrianglesHistory() 638 i.stale = false 639 return nil 640 } 641 642 // Dispose disposes the image. 643 // 644 // After disposing, calling the function of the image causes unexpected results. 645 func (i *Image) Dispose() { 646 theImages.remove(i) 647 i.image.Dispose() 648 i.image = nil 649 i.basePixels = Pixels{} 650 i.clearDrawTrianglesHistory() 651 i.stale = false 652 } 653 654 // isInvalidated returns a boolean value indicating whether the image is invalidated. 655 // 656 // If an image is invalidated, GL context is lost and all the images should be restored asap. 657 func (i *Image) isInvalidated() (bool, error) { 658 // FlushCommands is required because c.offscreen.impl might not have an actual texture. 659 if err := graphicscommand.FlushCommands(); err != nil { 660 return false, err 661 } 662 return i.image.IsInvalidated(), nil 663 } 664 665 func (i *Image) Dump(path string, blackbg bool, rect image.Rectangle) error { 666 return i.image.Dump(path, blackbg, rect) 667 } 668 669 func (i *Image) clearDrawTrianglesHistory() { 670 // Clear the items explicitly, or the references might remain (#1803). 671 for idx := range i.drawTrianglesHistory { 672 i.drawTrianglesHistory[idx] = nil 673 } 674 i.drawTrianglesHistory = i.drawTrianglesHistory[:0] 675 }