graphics: Restoring by recording drawing-images history

This commit is contained in:
Hajime Hoshi 2016-07-13 02:07:35 +09:00
parent ebe7f10af1
commit 1627176d59
6 changed files with 151 additions and 39 deletions

View File

@ -89,11 +89,13 @@ func (c *graphicsContext) initializeIfNeeded(context *opengl.Context) error {
if err != nil { if err != nil {
return err return err
} }
if r { if !r {
return nil
}
if err := c.restore(context); err != nil { if err := c.restore(context); err != nil {
return err return err
} }
}
return nil return nil
} }
@ -144,11 +146,6 @@ func (c *graphicsContext) UpdateAndDraw(context *opengl.Context, updateCount int
if err := c.drawToDefaultRenderTarget(context); err != nil { if err := c.drawToDefaultRenderTarget(context); err != nil {
return err return err
} }
if 0 < updateCount {
if err := theImagesForRestoring.savePixels(context); err != nil {
return err
}
}
return nil return nil
} }

View File

@ -42,17 +42,20 @@ func (i *images) add(img *imageImpl) (*Image, error) {
} }
func (i *images) remove(img *Image) { func (i *images) remove(img *Image) {
if err := img.Dispose(); err != nil {
panic(err)
}
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
delete(i.images, img.impl) delete(i.images, img.impl)
runtime.SetFinalizer(img, nil) runtime.SetFinalizer(img, nil)
} }
func (i *images) savePixels(context *opengl.Context) error { func (i *images) resetHistoryIfNeeded(target *Image) error {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
for img := range i.images { for img := range i.images {
if err := img.savePixels(context); err != nil { if err := img.resetHistoryIfNeeded(target); err != nil {
return err return err
} }
} }
@ -73,8 +76,24 @@ func (i *images) restore(context *opengl.Context) error {
return err return err
} }
} }
imagesWithoutHistory := []*imageImpl{}
imagesWithHistory := []*imageImpl{}
for img := range i.images { for img := range i.images {
if err := img.restore(); err != nil { if img.hasHistory() {
imagesWithHistory = append(imagesWithHistory, img)
} else {
imagesWithoutHistory = append(imagesWithoutHistory, img)
}
}
// Images with history can depend on other images. Let's process images without history
// first.
for _, img := range imagesWithoutHistory {
if err := img.restore(context); err != nil {
return err
}
}
for _, img := range imagesWithHistory {
if err := img.restore(context); err != nil {
return err return err
} }
} }
@ -110,6 +129,9 @@ func (i *Image) Size() (width, height int) {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func (i *Image) Clear() error { func (i *Image) Clear() error {
if err := theImagesForRestoring.resetHistoryIfNeeded(i); err != nil {
return err
}
return i.impl.Fill(color.Transparent) return i.impl.Fill(color.Transparent)
} }
@ -117,6 +139,9 @@ func (i *Image) Clear() error {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func (i *Image) Fill(clr color.Color) error { func (i *Image) Fill(clr color.Color) error {
if err := theImagesForRestoring.resetHistoryIfNeeded(i); err != nil {
return err
}
return i.impl.Fill(clr) return i.impl.Fill(clr)
} }
@ -137,6 +162,9 @@ func (i *Image) Fill(clr color.Color) error {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func (i *Image) DrawImage(image *Image, options *DrawImageOptions) error { func (i *Image) DrawImage(image *Image, options *DrawImageOptions) error {
if err := theImagesForRestoring.resetHistoryIfNeeded(i); err != nil {
return err
}
return i.impl.DrawImage(image, options) return i.impl.DrawImage(image, options)
} }
@ -172,6 +200,12 @@ func (i *Image) At(x, y int) color.Color {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func (i *Image) Dispose() error { func (i *Image) Dispose() error {
if err := theImagesForRestoring.resetHistoryIfNeeded(i); err != nil {
return err
}
if i.impl.isDisposed() {
return nil
}
return i.impl.Dispose() return i.impl.Dispose()
} }
@ -183,6 +217,9 @@ func (i *Image) Dispose() error {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func (i *Image) ReplacePixels(p []uint8) error { func (i *Image) ReplacePixels(p []uint8) error {
if err := theImagesForRestoring.resetHistoryIfNeeded(i); err != nil {
return err
}
return i.impl.ReplacePixels(p) return i.impl.ReplacePixels(p)
} }

View File

@ -29,6 +29,14 @@ import (
"github.com/hajimehoshi/ebiten/internal/ui" "github.com/hajimehoshi/ebiten/internal/ui"
) )
type drawImageHistoryItem struct {
image *Image
vertices []int16
geom GeoM
colorm ColorM
mode opengl.CompositeMode
}
type imageImpl struct { type imageImpl struct {
image *graphics.Image image *graphics.Image
disposed bool disposed bool
@ -36,6 +44,8 @@ type imageImpl struct {
height int height int
filter Filter filter Filter
pixels []uint8 pixels []uint8
baseColor color.Color
drawImageHistory []*drawImageHistoryItem
volatile bool volatile bool
screen bool screen bool
m sync.Mutex m sync.Mutex
@ -52,6 +62,7 @@ func newImageImpl(width, height int, filter Filter, volatile bool) (*imageImpl,
height: height, height: height,
filter: filter, filter: filter,
volatile: volatile, volatile: volatile,
pixels: make([]uint8, width*height*4),
} }
runtime.SetFinalizer(i, (*imageImpl).Dispose) runtime.SetFinalizer(i, (*imageImpl).Dispose)
return i, nil return i, nil
@ -100,6 +111,7 @@ func newScreenImageImpl(width, height int) (*imageImpl, error) {
height: height, height: height,
volatile: true, volatile: true,
screen: true, screen: true,
pixels: make([]uint8, width*height*4),
} }
runtime.SetFinalizer(i, (*imageImpl).Dispose) runtime.SetFinalizer(i, (*imageImpl).Dispose)
return i, nil return i, nil
@ -112,6 +124,8 @@ func (i *imageImpl) Fill(clr color.Color) error {
return errors.New("ebiten: image is already disposed") return errors.New("ebiten: image is already disposed")
} }
i.pixels = nil i.pixels = nil
i.baseColor = clr
i.drawImageHistory = nil
return i.image.Fill(clr) return i.image.Fill(clr)
} }
@ -125,6 +139,8 @@ func (i *imageImpl) clearIfVolatile() error {
return nil return nil
} }
i.pixels = nil i.pixels = nil
i.baseColor = nil
i.drawImageHistory = nil
return i.image.Fill(color.Transparent) return i.image.Fill(color.Transparent)
} }
@ -159,11 +175,18 @@ func (i *imageImpl) DrawImage(image *Image, options *DrawImageOptions) error {
if i.disposed { if i.disposed {
return errors.New("ebiten: image is already disposed") return errors.New("ebiten: image is already disposed")
} }
i.pixels = nil c := &drawImageHistoryItem{
image: image,
vertices: vertices,
geom: options.GeoM,
colorm: options.ColorM,
mode: opengl.CompositeMode(options.CompositeMode),
}
i.drawImageHistory = append(i.drawImageHistory, c)
geom := &options.GeoM geom := &options.GeoM
colorm := &options.ColorM colorm := &options.ColorM
mode := opengl.CompositeMode(options.CompositeMode) mode := opengl.CompositeMode(options.CompositeMode)
if err := i.image.DrawImage(image.impl.image, vertices[:16*n], geom, colorm, mode); err != nil { if err := i.image.DrawImage(image.impl.image, vertices, geom, colorm, mode); err != nil {
return err return err
} }
return nil return nil
@ -178,42 +201,57 @@ func (i *imageImpl) At(x, y int) color.Color {
if i.disposed { if i.disposed {
return color.Transparent return color.Transparent
} }
if i.pixels == nil { if i.pixels == nil || i.drawImageHistory != nil {
var err error var err error
i.pixels, err = i.image.Pixels(ui.GLContext()) i.pixels, err = i.image.Pixels(ui.GLContext())
if err != nil { if err != nil {
panic(err) panic(err)
} }
i.drawImageHistory = nil
} }
idx := 4*x + 4*y*i.width idx := 4*x + 4*y*i.width
r, g, b, a := i.pixels[idx], i.pixels[idx+1], i.pixels[idx+2], i.pixels[idx+3] r, g, b, a := i.pixels[idx], i.pixels[idx+1], i.pixels[idx+2], i.pixels[idx+3]
return color.RGBA{r, g, b, a} return color.RGBA{r, g, b, a}
} }
func (i *imageImpl) savePixels(context *opengl.Context) error { func (i *imageImpl) hasHistoryWith(target *Image) bool {
for _, c := range i.drawImageHistory {
if c.image == target {
return true
}
}
return false
}
func (i *imageImpl) resetHistoryIfNeeded(target *Image) error {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
if i.screen {
return nil
}
if i.volatile {
return nil
}
if i.disposed { if i.disposed {
return nil return nil
} }
if i.pixels != nil { if i.drawImageHistory == nil {
return nil
}
if !i.hasHistoryWith(target) {
return nil return nil
} }
var err error var err error
i.pixels, err = i.image.Pixels(context) i.pixels, err = i.image.Pixels(ui.GLContext())
if err != nil { if err != nil {
return err return nil
} }
i.baseColor = nil
i.drawImageHistory = nil
return nil return nil
} }
func (i *imageImpl) restore() error { func (i *imageImpl) hasHistory() bool {
i.m.Lock()
defer i.m.Unlock()
return i.drawImageHistory != nil
}
func (i *imageImpl) restore(context *opengl.Context) error {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
if i.disposed { if i.disposed {
@ -229,16 +267,43 @@ func (i *imageImpl) restore() error {
} }
return nil return nil
} }
if i.pixels != nil { if !i.volatile {
img := image.NewRGBA(image.Rect(0, 0, i.width, i.height)) img := image.NewRGBA(image.Rect(0, 0, i.width, i.height))
if i.pixels != nil {
for j := 0; j < i.height; j++ { for j := 0; j < i.height; j++ {
copy(img.Pix[j*img.Stride:], i.pixels[j*i.width*4:(j+1)*i.width*4]) copy(img.Pix[j*img.Stride:], i.pixels[j*i.width*4:(j+1)*i.width*4])
} }
} else if i.baseColor != nil {
r32, g32, b32, a32 := i.baseColor.RGBA()
r, g, b, a := uint8(r32), uint8(g32), uint8(b32), uint8(a32)
for idx := 0; idx < len(img.Pix)/4; idx++ {
img.Pix[4*idx] = r
img.Pix[4*idx+1] = g
img.Pix[4*idx+2] = b
img.Pix[4*idx+3] = a
}
}
var err error var err error
i.image, err = graphics.NewImageFromImage(img, glFilter(i.filter)) i.image, err = graphics.NewImageFromImage(img, glFilter(i.filter))
if err != nil { if err != nil {
return err return err
} }
for _, c := range i.drawImageHistory {
if c.image.impl.hasHistory() {
panic("not reach")
}
if err := i.image.DrawImage(c.image.impl.image, c.vertices, &c.geom, &c.colorm, c.mode); err != nil {
return err
}
}
if 0 < len(i.drawImageHistory) {
i.pixels, err = i.image.Pixels(context)
if err != nil {
return err
}
}
i.baseColor = nil
i.drawImageHistory = nil
return nil return nil
} }
var err error var err error
@ -263,6 +328,8 @@ func (i *imageImpl) Dispose() error {
i.image = nil i.image = nil
i.disposed = true i.disposed = true
i.pixels = nil i.pixels = nil
i.baseColor = nil
i.drawImageHistory = nil
runtime.SetFinalizer(i, nil) runtime.SetFinalizer(i, nil)
return nil return nil
} }
@ -274,9 +341,11 @@ func (i *imageImpl) ReplacePixels(p []uint8) error {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
if i.pixels == nil { if i.pixels == nil {
i.pixels = make([]uint8, len(p)) i.pixels = make([]uint8, i.width*i.height*4)
} }
copy(i.pixels, p) copy(i.pixels, p)
i.baseColor = nil
i.drawImageHistory = nil
if i.disposed { if i.disposed {
return errors.New("ebiten: image is already disposed") return errors.New("ebiten: image is already disposed")
} }

View File

@ -394,6 +394,9 @@ func (c *Context) UseProgram(p Program) {
func (c *Context) DeleteProgram(p Program) { func (c *Context) DeleteProgram(p Program) {
c.RunOnContextThread(func() error { c.RunOnContextThread(func() error {
if !gl.IsProgram(uint32(p)) {
return nil
}
gl.DeleteProgram(uint32(p)) gl.DeleteProgram(uint32(p))
return nil return nil
}) })

View File

@ -319,6 +319,9 @@ func (c *Context) UseProgram(p Program) {
func (c *Context) DeleteProgram(p Program) { func (c *Context) DeleteProgram(p Program) {
gl := c.gl gl := c.gl
if !gl.IsProgram(p.Object) {
return
}
gl.DeleteProgram(p.Object) gl.DeleteProgram(p.Object)
} }

View File

@ -298,6 +298,9 @@ func (c *Context) UseProgram(p Program) {
func (c *Context) DeleteProgram(p Program) { func (c *Context) DeleteProgram(p Program) {
gl := c.gl gl := c.gl
if !gl.IsProgram(mgl.Program(p)) {
return
}
gl.DeleteProgram(mgl.Program(p)) gl.DeleteProgram(mgl.Program(p))
} }