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