restorable: Bug fix: topological sort is required to restore images correctly (#357)

This commit is contained in:
Hajime Hoshi 2017-06-01 10:44:28 +09:00
parent 967e737760
commit 1d66ebc854
3 changed files with 70 additions and 15 deletions

View File

@ -192,11 +192,8 @@ func (p *Image) makeStaleIfDependingOn(target *Image) {
if p.stale { if p.stale {
return return
} }
for _, c := range p.drawImageHistory { if p.dependsOn(target) {
if c.image == target { p.makeStale()
p.makeStale()
return
}
} }
} }
@ -222,6 +219,15 @@ func (p *Image) resolveStalePixels() error {
return p.readPixelsFromGPU(p.image) return p.readPixelsFromGPU(p.image)
} }
func (p *Image) dependsOn(target *Image) bool {
for _, c := range p.drawImageHistory {
if c.image == target {
return true
}
}
return false
}
func (p *Image) hasDependency() bool { func (p *Image) hasDependency() bool {
if p.stale { if p.stale {
return false return false

View File

@ -90,22 +90,40 @@ func (i *images) restore() error {
defer i.m.Unlock() defer i.m.Unlock()
// Framebuffers/textures cannot be disposed since framebuffers/textures that // Framebuffers/textures cannot be disposed since framebuffers/textures that
// don't belong to the current context. // don't belong to the current context.
imagesWithoutDependency := []*Image{}
imagesWithDependency := []*Image{} // Let's do topological sort based on dependencies of drawing history.
// There should not be a loop since cyclic drawing makes images stale.
current := map[*Image]struct{}{}
toBeDetermined := map[*Image]struct{}{}
sorted := []*Image{}
for img := range i.images { for img := range i.images {
if img.hasDependency() { if img.hasDependency() {
imagesWithDependency = append(imagesWithDependency, img) toBeDetermined[img] = struct{}{}
} else { continue
imagesWithoutDependency = append(imagesWithoutDependency, img)
} }
current[img] = struct{}{}
sorted = append(sorted, img)
} }
// Images depending on other images should be processed first. // TODO: How to confirm that there is no loop?
for _, img := range imagesWithoutDependency { for len(current) > 0 {
if err := img.restore(); err != nil { next := map[*Image]struct{}{}
return err for source := range current {
for target := range toBeDetermined {
if target.dependsOn(source) {
next[target] = struct{}{}
}
}
} }
for img := range next {
sorted = append(sorted, img)
delete(toBeDetermined, img)
}
current = next
} }
for _, img := range imagesWithDependency { if len(toBeDetermined) > 0 {
panic("not reached")
}
for _, img := range sorted {
if err := img.restore(); err != nil { if err := img.restore(); err != nil {
return err return err
} }

View File

@ -71,6 +71,37 @@ func vertices() []float32 {
} }
} }
func TestRestoreChain(t *testing.T) {
const num = 10
imgs := []*Image{}
for i := 0; i < num; i++ {
imgs = append(imgs, NewImage(1, 1, opengl.Nearest, false))
}
defer func() {
for _, img := range imgs {
img.Dispose()
}
}()
clr := color.RGBA{0x00, 0x00, 0x00, 0xff}
imgs[0].Fill(clr)
for i := 0; i < num-1; i++ {
imgs[i+1].DrawImage(imgs[i], vertices(), &affine.ColorM{}, opengl.CompositeModeSourceOver)
}
if err := ResolveStalePixels(); err != nil {
t.Fatal(err)
}
if err := Restore(); err != nil {
t.Fatal(err)
}
want := clr
for i, img := range imgs {
got := uint8SliceToColor(img.BasePixelsForTesting())
if got != want {
t.Errorf("%d: got %v, want %v", i, got, want)
}
}
}
func TestRestoreOverrideSource(t *testing.T) { func TestRestoreOverrideSource(t *testing.T) {
img0 := NewImage(1, 1, opengl.Nearest, false) img0 := NewImage(1, 1, opengl.Nearest, false)
img1 := NewImage(1, 1, opengl.Nearest, false) img1 := NewImage(1, 1, opengl.Nearest, false)