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