restorable: Unify locks with shareable's backendsM and fix the deadlock

This change should make Image operations cuncurrent safe.

Updates #913
Fixes #915
This commit is contained in:
Hajime Hoshi 2019-08-13 00:18:49 +09:00
parent 14ce0c1bcb
commit 4373bd8b89
4 changed files with 26 additions and 69 deletions

View File

@ -121,9 +121,6 @@ func init() {
// //
// Note that Dispose is not called automatically. // Note that Dispose is not called automatically.
func NewImage(width, height int) *Image { func NewImage(width, height int) *Image {
theImages.m.Lock()
defer theImages.m.Unlock()
i := &Image{ i := &Image{
image: graphicscommand.NewImage(width, height), image: graphicscommand.NewImage(width, height),
} }
@ -175,9 +172,6 @@ func (i *Image) MakeVolatile() {
// //
// Note that Dispose is not called automatically. // Note that Dispose is not called automatically.
func NewScreenFramebufferImage(width, height int) *Image { func NewScreenFramebufferImage(width, height int) *Image {
theImages.m.Lock()
defer theImages.m.Unlock()
i := &Image{ i := &Image{
image: graphicscommand.NewScreenFramebufferImage(width, height), image: graphicscommand.NewScreenFramebufferImage(width, height),
screen: true, screen: true,
@ -188,8 +182,6 @@ func NewScreenFramebufferImage(width, height int) *Image {
} }
func (i *Image) Clear() { func (i *Image) Clear() {
theImages.m.Lock()
defer theImages.m.Unlock()
theImages.makeStaleIfDependingOn(i) theImages.makeStaleIfDependingOn(i)
i.clear() i.clear()
} }
@ -300,9 +292,6 @@ func (i *Image) ClearPixels(x, y, width, height int) {
// //
// ReplacePixels for a part is forbidden if the image is rendered with DrawTriangles or Fill. // ReplacePixels for a part is forbidden if the image is rendered with DrawTriangles or Fill.
func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
theImages.m.Lock()
defer theImages.m.Unlock()
w, h := i.image.Size() w, h := i.image.Size()
if width <= 0 || height <= 0 { if width <= 0 || height <= 0 {
panic("restorable: width/height must be positive") panic("restorable: width/height must be positive")
@ -356,9 +345,6 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
// DrawTriangles draws a given image img to the image. // DrawTriangles draws a given image img to the image.
func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) { func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) {
theImages.m.Lock()
defer theImages.m.Unlock()
if i.priority { if i.priority {
panic("restorable: DrawTriangles cannot be called on a priority image") panic("restorable: DrawTriangles cannot be called on a priority image")
} }
@ -413,9 +399,6 @@ func (i *Image) readPixelsFromGPUIfNeeded() {
// //
// Note that this must not be called until context is available. // Note that this must not be called until context is available.
func (i *Image) At(x, y int) (byte, byte, byte, byte) { func (i *Image) At(x, y int) (byte, byte, byte, byte) {
theImages.m.Lock()
defer theImages.m.Unlock()
w, h := i.image.Size() w, h := i.image.Size()
if x < 0 || y < 0 || w <= x || h <= y { if x < 0 || y < 0 || w <= x || h <= y {
return 0, 0, 0, 0 return 0, 0, 0, 0
@ -541,9 +524,6 @@ func (i *Image) restore() {
// //
// After disposing, calling the function of the image causes unexpected results. // After disposing, calling the function of the image causes unexpected results.
func (i *Image) Dispose() { func (i *Image) Dispose() {
theImages.m.Lock()
defer theImages.m.Unlock()
theImages.remove(i) theImages.remove(i)
i.image.Dispose() i.image.Dispose()
i.image = nil i.image = nil

View File

@ -16,7 +16,6 @@ package restorable
import ( import (
"path/filepath" "path/filepath"
"sync"
"github.com/hajimehoshi/ebiten/internal/graphicscommand" "github.com/hajimehoshi/ebiten/internal/graphicscommand"
) )
@ -41,9 +40,6 @@ func EnableRestoringForTesting() {
type images struct { type images struct {
images map[*Image]struct{} images map[*Image]struct{}
lastTarget *Image lastTarget *Image
m sync.Mutex
once sync.Once
} }
// theImages represents the images for the current process. // theImages represents the images for the current process.
@ -56,9 +52,6 @@ var theImages = &images{
// //
// ResolveStaleImages is intended to be called at the end of a frame. // ResolveStaleImages is intended to be called at the end of a frame.
func ResolveStaleImages() { func ResolveStaleImages() {
// Until the begin o the frame (by RestoreIfNeeded, any operations are locked.
theImages.m.Lock()
graphicscommand.FlushCommands() graphicscommand.FlushCommands()
if !needsRestoring() { if !needsRestoring() {
return return
@ -70,24 +63,6 @@ func ResolveStaleImages() {
// //
// Restoring means to make all *graphicscommand.Image objects have their textures and framebuffers. // Restoring means to make all *graphicscommand.Image objects have their textures and framebuffers.
func RestoreIfNeeded() error { func RestoreIfNeeded() error {
// Unlock except for the first time.
//
// In each frame, restoring images and resolving images happen respectively:
//
// [Restore -> Resolve] -> [Restore -> Resolve] -> ...
//
// Between each frame, any image operations are not permitted, or stale images would remain when restoring
// (#913).
defer func() {
firsttime := false
theImages.once.Do(func() {
firsttime = true
})
if !firsttime {
theImages.m.Unlock()
}
}()
if !needsRestoring() { if !needsRestoring() {
return nil return nil
} }

View File

@ -143,6 +143,8 @@ var (
// backendsM is a mutex for critical sections of the backend and packing.Node objects. // backendsM is a mutex for critical sections of the backend and packing.Node objects.
backendsM sync.Mutex backendsM sync.Mutex
backendsOnce sync.Once
// theBackends is a set of actually shared images. // theBackends is a set of actually shared images.
theBackends = []*backend{} theBackends = []*backend{}
@ -570,15 +572,30 @@ func InitializeGraphicsDriverState() error {
return restorable.InitializeGraphicsDriverState() return restorable.InitializeGraphicsDriverState()
} }
func ResolveStaleImages() { func EndFrame() error {
backendsM.Lock() backendsM.Lock()
defer backendsM.Unlock()
restorable.ResolveStaleImages() restorable.ResolveStaleImages()
return restorable.Error()
} }
func RestoreIfNeeded() error { func BeginFrame() error {
backendsM.Lock() // Unlock except for the first time.
defer backendsM.Unlock() //
// In each frame, restoring images and resolving images happen respectively:
//
// [Restore -> Resolve] -> [Restore -> Resolve] -> ...
//
// Between each frame, any image operations are not permitted, or stale images would remain when restoring
// (#913).
defer func() {
firsttime := false
backendsOnce.Do(func() {
firsttime = true
})
if !firsttime {
backendsM.Unlock()
}
}()
return restorable.RestoreIfNeeded() return restorable.RestoreIfNeeded()
} }
@ -587,9 +604,3 @@ func DumpImages(dir string) error {
defer backendsM.Unlock() defer backendsM.Unlock()
return restorable.DumpImages(dir) return restorable.DumpImages(dir)
} }
func Error() error {
backendsM.Lock()
defer backendsM.Unlock()
return restorable.Error()
}

View File

@ -81,9 +81,6 @@ func (c *uiContext) initializeIfNeeded() error {
} }
c.initialized = true c.initialized = true
} }
if err := c.restoreIfNeeded(); err != nil {
return err
}
return nil return nil
} }
@ -96,6 +93,9 @@ func (c *uiContext) Update(afterFrameUpdate func()) error {
if err := c.initializeIfNeeded(); err != nil { if err := c.initializeIfNeeded(); err != nil {
return err return err
} }
if err := shareable.BeginFrame(); err != nil {
return err
}
for i := 0; i < updateCount; i++ { for i := 0; i < updateCount; i++ {
c.offscreen.Clear() c.offscreen.Clear()
// Mipmap images should be disposed by fill. // Mipmap images should be disposed by fill.
@ -141,16 +141,7 @@ func (c *uiContext) Update(afterFrameUpdate func()) error {
} }
_ = c.screen.DrawImage(c.offscreen, op) _ = c.screen.DrawImage(c.offscreen, op)
shareable.ResolveStaleImages() if err := shareable.EndFrame(); err != nil {
if err := shareable.Error(); err != nil {
return err
}
return nil
}
func (c *uiContext) restoreIfNeeded() error {
if err := shareable.RestoreIfNeeded(); err != nil {
return err return err
} }
return nil return nil