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 }