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 {
return err
}
if r {
if err := c.restore(context); err != nil {
return err
}
if !r {
return nil
}
if err := c.restore(context); err != nil {
return err
}
return nil
}
@ -144,11 +146,6 @@ func (c *graphicsContext) UpdateAndDraw(context *opengl.Context, updateCount int
if err := c.drawToDefaultRenderTarget(context); err != nil {
return err
}
if 0 < updateCount {
if err := theImagesForRestoring.savePixels(context); err != nil {
return err
}
}
return nil
}

View File

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

View File

@ -29,16 +29,26 @@ import (
"github.com/hajimehoshi/ebiten/internal/ui"
)
type drawImageHistoryItem struct {
image *Image
vertices []int16
geom GeoM
colorm ColorM
mode opengl.CompositeMode
}
type imageImpl struct {
image *graphics.Image
disposed bool
width int
height int
filter Filter
pixels []uint8
volatile bool
screen bool
m sync.Mutex
image *graphics.Image
disposed bool
width int
height int
filter Filter
pixels []uint8
baseColor color.Color
drawImageHistory []*drawImageHistoryItem
volatile bool
screen bool
m sync.Mutex
}
func newImageImpl(width, height int, filter Filter, volatile bool) (*imageImpl, error) {
@ -52,6 +62,7 @@ func newImageImpl(width, height int, filter Filter, volatile bool) (*imageImpl,
height: height,
filter: filter,
volatile: volatile,
pixels: make([]uint8, width*height*4),
}
runtime.SetFinalizer(i, (*imageImpl).Dispose)
return i, nil
@ -100,6 +111,7 @@ func newScreenImageImpl(width, height int) (*imageImpl, error) {
height: height,
volatile: true,
screen: true,
pixels: make([]uint8, width*height*4),
}
runtime.SetFinalizer(i, (*imageImpl).Dispose)
return i, nil
@ -112,6 +124,8 @@ func (i *imageImpl) Fill(clr color.Color) error {
return errors.New("ebiten: image is already disposed")
}
i.pixels = nil
i.baseColor = clr
i.drawImageHistory = nil
return i.image.Fill(clr)
}
@ -125,6 +139,8 @@ func (i *imageImpl) clearIfVolatile() error {
return nil
}
i.pixels = nil
i.baseColor = nil
i.drawImageHistory = nil
return i.image.Fill(color.Transparent)
}
@ -159,11 +175,18 @@ func (i *imageImpl) DrawImage(image *Image, options *DrawImageOptions) error {
if i.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
colorm := &options.ColorM
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 nil
@ -178,42 +201,57 @@ func (i *imageImpl) At(x, y int) color.Color {
if i.disposed {
return color.Transparent
}
if i.pixels == nil {
if i.pixels == nil || i.drawImageHistory != nil {
var err error
i.pixels, err = i.image.Pixels(ui.GLContext())
if err != nil {
panic(err)
}
i.drawImageHistory = nil
}
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]
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()
defer i.m.Unlock()
if i.screen {
return nil
}
if i.volatile {
return nil
}
if i.disposed {
return nil
}
if i.pixels != nil {
if i.drawImageHistory == nil {
return nil
}
if !i.hasHistoryWith(target) {
return nil
}
var err error
i.pixels, err = i.image.Pixels(context)
i.pixels, err = i.image.Pixels(ui.GLContext())
if err != nil {
return err
return nil
}
i.baseColor = nil
i.drawImageHistory = 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()
defer i.m.Unlock()
if i.disposed {
@ -229,16 +267,43 @@ func (i *imageImpl) restore() error {
}
return nil
}
if i.pixels != nil {
if !i.volatile {
img := image.NewRGBA(image.Rect(0, 0, i.width, i.height))
for j := 0; j < i.height; j++ {
copy(img.Pix[j*img.Stride:], i.pixels[j*i.width*4:(j+1)*i.width*4])
if i.pixels != nil {
for j := 0; j < i.height; j++ {
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
i.image, err = graphics.NewImageFromImage(img, glFilter(i.filter))
if err != nil {
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
}
var err error
@ -263,6 +328,8 @@ func (i *imageImpl) Dispose() error {
i.image = nil
i.disposed = true
i.pixels = nil
i.baseColor = nil
i.drawImageHistory = nil
runtime.SetFinalizer(i, nil)
return nil
}
@ -274,9 +341,11 @@ func (i *imageImpl) ReplacePixels(p []uint8) error {
i.m.Lock()
defer i.m.Unlock()
if i.pixels == nil {
i.pixels = make([]uint8, len(p))
i.pixels = make([]uint8, i.width*i.height*4)
}
copy(i.pixels, p)
i.baseColor = nil
i.drawImageHistory = nil
if i.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) {
c.RunOnContextThread(func() error {
if !gl.IsProgram(uint32(p)) {
return nil
}
gl.DeleteProgram(uint32(p))
return nil
})

View File

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

View File

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