zorldo

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

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 }