zorldo

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

images.go (7580B)


      1 // Copyright 2017 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 	"image"
     19 	"path/filepath"
     20 
     21 	"github.com/hajimehoshi/ebiten/v2/internal/debug"
     22 	"github.com/hajimehoshi/ebiten/v2/internal/driver"
     23 	"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
     24 )
     25 
     26 // forceRestoring reports whether restoring forcely happens or not.
     27 var forceRestoring = false
     28 
     29 // NeedsRestoring reports whether restoring process works or not.
     30 func NeedsRestoring() bool {
     31 	if forceRestoring {
     32 		return true
     33 	}
     34 	return graphicscommand.NeedsRestoring()
     35 }
     36 
     37 // EnableRestoringForTesting forces to enable restoring for testing.
     38 func EnableRestoringForTesting() {
     39 	forceRestoring = true
     40 }
     41 
     42 // images is a set of Image objects.
     43 type images struct {
     44 	images      map[*Image]struct{}
     45 	shaders     map[*Shader]struct{}
     46 	lastTarget  *Image
     47 	contextLost bool
     48 }
     49 
     50 // theImages represents the images for the current process.
     51 var theImages = &images{
     52 	images:  map[*Image]struct{}{},
     53 	shaders: map[*Shader]struct{}{},
     54 }
     55 
     56 // ResolveStaleImages flushes the queued draw commands and resolves
     57 // all stale images.
     58 //
     59 // ResolveStaleImages is intended to be called at the end of a frame.
     60 func ResolveStaleImages() error {
     61 	if debug.IsDebug {
     62 		debug.Logf("Internal image sizes:\n")
     63 		imgs := make([]*graphicscommand.Image, 0, len(theImages.images))
     64 		for i := range theImages.images {
     65 			imgs = append(imgs, i.image)
     66 		}
     67 		graphicscommand.LogImagesInfo(imgs)
     68 	}
     69 
     70 	if err := graphicscommand.FlushCommands(); err != nil {
     71 		return err
     72 	}
     73 	if !NeedsRestoring() {
     74 		return nil
     75 	}
     76 	return theImages.resolveStaleImages()
     77 }
     78 
     79 // RestoreIfNeeded restores the images.
     80 //
     81 // Restoring means to make all *graphicscommand.Image objects have their textures and framebuffers.
     82 func RestoreIfNeeded() error {
     83 	if !NeedsRestoring() {
     84 		return nil
     85 	}
     86 
     87 	if !forceRestoring {
     88 		var r bool
     89 
     90 		if canDetectContextLostExplicitly {
     91 			r = theImages.contextLost
     92 		} else {
     93 			// As isInvalidated() is expensive, call this only for one image.
     94 			// This assumes that if there is one image that is invalidated, all images are invalidated.
     95 			for img := range theImages.images {
     96 				// The screen image might not have a texture. Skip this.
     97 				if img.screen {
     98 					continue
     99 				}
    100 				var err error
    101 				r, err = img.isInvalidated()
    102 				if err != nil {
    103 					return err
    104 				}
    105 				break
    106 			}
    107 		}
    108 
    109 		if !r {
    110 			return nil
    111 		}
    112 	}
    113 
    114 	err := graphicscommand.ResetGraphicsDriverState()
    115 	if err == driver.GraphicsNotReady {
    116 		return nil
    117 	}
    118 	if err != nil {
    119 		return err
    120 	}
    121 	return theImages.restore()
    122 }
    123 
    124 // DumpImages dumps all the current images to the specified directory.
    125 //
    126 // This is for testing usage.
    127 func DumpImages(dir string) error {
    128 	for img := range theImages.images {
    129 		if err := img.Dump(filepath.Join(dir, "*.png"), false, image.Rect(0, 0, img.width, img.height)); err != nil {
    130 			return err
    131 		}
    132 	}
    133 	return nil
    134 }
    135 
    136 // add adds img to the images.
    137 func (i *images) add(img *Image) {
    138 	i.images[img] = struct{}{}
    139 }
    140 
    141 func (i *images) addShader(shader *Shader) {
    142 	i.shaders[shader] = struct{}{}
    143 }
    144 
    145 // remove removes img from the images.
    146 func (i *images) remove(img *Image) {
    147 	i.makeStaleIfDependingOn(img)
    148 	delete(i.images, img)
    149 }
    150 
    151 func (i *images) removeShader(shader *Shader) {
    152 	i.makeStaleIfDependingOnShader(shader)
    153 	delete(i.shaders, shader)
    154 }
    155 
    156 // resolveStaleImages resolves stale images.
    157 func (i *images) resolveStaleImages() error {
    158 	i.lastTarget = nil
    159 	for img := range i.images {
    160 		if err := img.resolveStale(); err != nil {
    161 			return err
    162 		}
    163 	}
    164 	return nil
    165 }
    166 
    167 // makeStaleIfDependingOn makes all the images stale that depend on target.
    168 //
    169 // When target is modified, all images depending on target can't be restored with target.
    170 // makeStaleIfDependingOn is called in such situation.
    171 func (i *images) makeStaleIfDependingOn(target *Image) {
    172 	if target == nil {
    173 		panic("restorable: target must not be nil at makeStaleIfDependingOn")
    174 	}
    175 	if i.lastTarget == target {
    176 		return
    177 	}
    178 	i.lastTarget = target
    179 	for img := range i.images {
    180 		img.makeStaleIfDependingOn(target)
    181 	}
    182 }
    183 
    184 // makeStaleIfDependingOn makes all the images stale that depend on shader.
    185 func (i *images) makeStaleIfDependingOnShader(shader *Shader) {
    186 	if shader == nil {
    187 		panic("restorable: shader must not be nil at makeStaleIfDependingOnShader")
    188 	}
    189 	for img := range i.images {
    190 		img.makeStaleIfDependingOnShader(shader)
    191 	}
    192 }
    193 
    194 // restore restores the images.
    195 //
    196 // Restoring means to make all *graphicscommand.Image objects have their textures and framebuffers.
    197 func (i *images) restore() error {
    198 	if !NeedsRestoring() {
    199 		panic("restorable: restore cannot be called when restoring is disabled")
    200 	}
    201 
    202 	// Dispose all the shaders ahead of restoring. A current shader ID and a new shader ID can be duplicated.
    203 	for s := range i.shaders {
    204 		if needsDisposingWhenRestoring {
    205 			s.shader.Dispose()
    206 		}
    207 		s.shader = nil
    208 	}
    209 	for s := range i.shaders {
    210 		s.restore()
    211 	}
    212 
    213 	// Dispose all the images ahead of restoring. A current texture ID and a new texture ID can be duplicated.
    214 	// TODO: Write a test to confirm that ID duplication never happens.
    215 	for i := range i.images {
    216 		if needsDisposingWhenRestoring {
    217 			i.image.Dispose()
    218 		}
    219 		i.image = nil
    220 	}
    221 
    222 	// Let's do topological sort based on dependencies of drawing history.
    223 	// It is assured that there are not loops since cyclic drawing makes images stale.
    224 	type edge struct {
    225 		source *Image
    226 		target *Image
    227 	}
    228 	images := map[*Image]struct{}{}
    229 	for i := range i.images {
    230 		if !i.priority {
    231 			images[i] = struct{}{}
    232 		}
    233 	}
    234 	edges := map[edge]struct{}{}
    235 	for t := range images {
    236 		for s := range t.dependingImages() {
    237 			edges[edge{source: s, target: t}] = struct{}{}
    238 		}
    239 	}
    240 
    241 	sorted := []*Image{}
    242 	for i := range i.images {
    243 		if i.priority {
    244 			sorted = append(sorted, i)
    245 		}
    246 	}
    247 	for len(images) > 0 {
    248 		// current repesents images that have no incoming edges.
    249 		current := map[*Image]struct{}{}
    250 		for i := range images {
    251 			current[i] = struct{}{}
    252 		}
    253 		for e := range edges {
    254 			if _, ok := current[e.target]; ok {
    255 				delete(current, e.target)
    256 			}
    257 		}
    258 		for i := range current {
    259 			delete(images, i)
    260 			sorted = append(sorted, i)
    261 		}
    262 		removed := []edge{}
    263 		for e := range edges {
    264 			if _, ok := current[e.source]; ok {
    265 				removed = append(removed, e)
    266 			}
    267 		}
    268 		for _, e := range removed {
    269 			delete(edges, e)
    270 		}
    271 	}
    272 
    273 	for _, img := range sorted {
    274 		if err := img.restore(); err != nil {
    275 			return err
    276 		}
    277 	}
    278 
    279 	i.contextLost = false
    280 
    281 	return nil
    282 }
    283 
    284 var graphicsDriverInitialized bool
    285 
    286 // InitializeGraphicsDriverState initializes the graphics driver state.
    287 func InitializeGraphicsDriverState() error {
    288 	graphicsDriverInitialized = true
    289 	return graphicscommand.InitializeGraphicsDriverState()
    290 }
    291 
    292 // MaxImageSize returns the maximum size of an image.
    293 func MaxImageSize() int {
    294 	return graphicscommand.MaxImageSize()
    295 }
    296 
    297 // OnContextLost is called when the context lost is detected in an explicit way.
    298 func OnContextLost() {
    299 	if !canDetectContextLostExplicitly {
    300 		panic("restorable: OnContextLost cannot be called in this environment")
    301 	}
    302 	theImages.contextLost = true
    303 }