restorable: Add comments

This commit is contained in:
Hajime Hoshi 2017-09-15 00:24:18 +09:00
parent b51d93a707
commit 80940f9070
5 changed files with 77 additions and 24 deletions

View File

@ -75,7 +75,7 @@ func (c *graphicsContext) SetSize(screenWidth, screenHeight int, screenScale flo
func (c *graphicsContext) initializeIfNeeded() error { func (c *graphicsContext) initializeIfNeeded() error {
if !c.initialized { if !c.initialized {
if err := restorable.ResetGLState(); err != nil { if err := restorable.InitializeGLState(); err != nil {
return err return err
} }
c.initialized = true c.initialized = true
@ -113,7 +113,7 @@ func (c *graphicsContext) Update(updateCount int) error {
_ = c.screen.Clear() _ = c.screen.Clear()
drawWithFittingScale(c.screen, c.offscreen2) drawWithFittingScale(c.screen, c.offscreen2)
if err := restorable.FlushAndResolveStalePixels(); err != nil { if err := restorable.ResolveStaleImages(); err != nil {
return err return err
} }
return nil return nil

View File

@ -28,7 +28,7 @@ import (
// Basically CopyImage just calls draw.Draw. // Basically CopyImage just calls draw.Draw.
// If origImg is a paletted image, an optimized copying method is used. // If origImg is a paletted image, an optimized copying method is used.
// //
// CopyImage is used only from ebiten package but defined in restorable package, // CopyImage is used only from ebiten package but defined in restorable package
// because this function needs to be tested but cannot be exposed to Ebiten users. // because this function needs to be tested but cannot be exposed to Ebiten users.
func CopyImage(origImg image.Image) *image.RGBA { func CopyImage(origImg image.Image) *image.RGBA {
size := origImg.Bounds().Size() size := origImg.Bounds().Size()

View File

@ -47,7 +47,8 @@
// After any of the drawing functions is called, the target image can't be depended on by // After any of the drawing functions is called, the target image can't be depended on by
// any other images. For example, if an image A depends on an image B, and B is changed // any other images. For example, if an image A depends on an image B, and B is changed
// by a Fill call after that, the image A can't depend on the image B any more. // by a Fill call after that, the image A can't depend on the image B any more.
// In this case, as the image A is no longer relaiable, the image A becomes 'stale'. // In this case, as the image B can no longer be used to restore the image A,
// the image A becomes 'stale'.
// As all the stale images are resolved before context lost happens, // As all the stale images are resolved before context lost happens,
// draw image history items are kept as they are // draw image history items are kept as they are
// (even if an image C depends on the stale image A, it is still fine). // (even if an image C depends on the stale image A, it is still fine).

View File

@ -26,12 +26,15 @@ import (
"github.com/hajimehoshi/ebiten/internal/opengl" "github.com/hajimehoshi/ebiten/internal/opengl"
) )
// MaxImageSize represents the maximum width/height of an image.
const MaxImageSize = graphics.MaxImageSize const MaxImageSize = graphics.MaxImageSize
// QuadVertexSizeInBytes returns the byte size of vertices for a quadrilateral.
func QuadVertexSizeInBytes() int { func QuadVertexSizeInBytes() int {
return graphics.QuadVertexSizeInBytes() return graphics.QuadVertexSizeInBytes()
} }
// drawImageHistoryItem is an item for history of draw-image commands.
type drawImageHistoryItem struct { type drawImageHistoryItem struct {
image *Image image *Image
vertices []float32 vertices []float32
@ -39,6 +42,8 @@ type drawImageHistoryItem struct {
mode opengl.CompositeMode mode opengl.CompositeMode
} }
// canMerge returns a boolean value indicating whether the drawImageHistoryItem d
// can be merged with the given conditions.
func (d *drawImageHistoryItem) canMerge(image *Image, colorm *affine.ColorM, mode opengl.CompositeMode) bool { func (d *drawImageHistoryItem) canMerge(image *Image, colorm *affine.ColorM, mode opengl.CompositeMode) bool {
if d.image != image { if d.image != image {
return false return false
@ -75,6 +80,7 @@ type Image struct {
offsetY float64 offsetY float64
} }
// NewImage creates an empty image with the given size and filter.
func NewImage(width, height int, filter opengl.Filter, volatile bool) *Image { func NewImage(width, height int, filter opengl.Filter, volatile bool) *Image {
i := &Image{ i := &Image{
image: graphics.NewImage(width, height, filter), image: graphics.NewImage(width, height, filter),
@ -86,6 +92,7 @@ func NewImage(width, height int, filter opengl.Filter, volatile bool) *Image {
return i return i
} }
// NewImageFromImage creates an image with source image.
func NewImageFromImage(source *image.RGBA, width, height int, filter opengl.Filter) *Image { func NewImageFromImage(source *image.RGBA, width, height int, filter opengl.Filter) *Image {
w2, h2 := math.NextPowerOf2Int(width), math.NextPowerOf2Int(height) w2, h2 := math.NextPowerOf2Int(width), math.NextPowerOf2Int(height)
p := make([]uint8, 4*w2*h2) p := make([]uint8, 4*w2*h2)
@ -102,6 +109,7 @@ func NewImageFromImage(source *image.RGBA, width, height int, filter opengl.Filt
return i return i
} }
// NewScreenFramebufferImage creates a special image that framebuffer is one for the screen.
func NewScreenFramebufferImage(width, height int, offsetX, offsetY float64) *Image { func NewScreenFramebufferImage(width, height int, offsetX, offsetY float64) *Image {
i := &Image{ i := &Image{
image: graphics.NewScreenFramebufferImage(width, height, offsetX, offsetY), image: graphics.NewScreenFramebufferImage(width, height, offsetX, offsetY),
@ -115,14 +123,17 @@ func NewScreenFramebufferImage(width, height int, offsetX, offsetY float64) *Ima
return i return i
} }
// BasePixelsForTesting returns the image's basePixels for testing.
func (p *Image) BasePixelsForTesting() []uint8 { func (p *Image) BasePixelsForTesting() []uint8 {
return p.basePixels return p.basePixels
} }
// Size returns the image's size.
func (p *Image) Size() (int, int) { func (p *Image) Size() (int, int) {
return p.image.Size() return p.image.Size()
} }
// makeStale makes the image stale.
func (p *Image) makeStale() { func (p *Image) makeStale() {
p.basePixels = nil p.basePixels = nil
p.baseColor = color.RGBA{} p.baseColor = color.RGBA{}
@ -130,6 +141,7 @@ func (p *Image) makeStale() {
p.stale = true p.stale = true
} }
// clearIfVolatile clears the image if the image is volatile.
func (p *Image) clearIfVolatile() { func (p *Image) clearIfVolatile() {
if !p.volatile { if !p.volatile {
return return
@ -144,8 +156,9 @@ func (p *Image) clearIfVolatile() {
p.image.Fill(0, 0, 0, 0) p.image.Fill(0, 0, 0, 0)
} }
// Fill fills the image with the given color.
func (p *Image) Fill(r, g, b, a uint8) { func (p *Image) Fill(r, g, b, a uint8) {
theImages.resetPixelsIfDependingOn(p) theImages.makeStaleIfDependingOn(p)
p.basePixels = nil p.basePixels = nil
p.baseColor = color.RGBA{r, g, b, a} p.baseColor = color.RGBA{r, g, b, a}
p.drawImageHistory = nil p.drawImageHistory = nil
@ -153,8 +166,9 @@ func (p *Image) Fill(r, g, b, a uint8) {
p.image.Fill(r, g, b, a) p.image.Fill(r, g, b, a)
} }
// ReplacePixels replaces the image pixels with the given pixels slice.
func (p *Image) ReplacePixels(pixels []uint8) { func (p *Image) ReplacePixels(pixels []uint8) {
theImages.resetPixelsIfDependingOn(p) theImages.makeStaleIfDependingOn(p)
p.image.ReplacePixels(pixels) p.image.ReplacePixels(pixels)
p.basePixels = pixels p.basePixels = pixels
p.baseColor = color.RGBA{} p.baseColor = color.RGBA{}
@ -162,8 +176,9 @@ func (p *Image) ReplacePixels(pixels []uint8) {
p.stale = false p.stale = false
} }
// DrawImage draws a given image img to the image.
func (p *Image) DrawImage(img *Image, vertices []float32, colorm *affine.ColorM, mode opengl.CompositeMode) { func (p *Image) DrawImage(img *Image, vertices []float32, colorm *affine.ColorM, mode opengl.CompositeMode) {
theImages.resetPixelsIfDependingOn(p) theImages.makeStaleIfDependingOn(p)
if img.stale || img.volatile || !IsRestoringEnabled() { if img.stale || img.volatile || !IsRestoringEnabled() {
p.makeStale() p.makeStale()
} else { } else {
@ -172,6 +187,7 @@ func (p *Image) DrawImage(img *Image, vertices []float32, colorm *affine.ColorM,
p.image.DrawImage(img.image, vertices, colorm, mode) p.image.DrawImage(img.image, vertices, colorm, mode)
} }
// appendDrawImageHistory appends a draw-image history item to the image.
func (p *Image) appendDrawImageHistory(image *Image, vertices []float32, colorm *affine.ColorM, mode opengl.CompositeMode) { func (p *Image) appendDrawImageHistory(image *Image, vertices []float32, colorm *affine.ColorM, mode opengl.CompositeMode) {
if p.stale || p.volatile { if p.stale || p.volatile {
return return
@ -218,6 +234,7 @@ func (p *Image) At(x, y int) (color.RGBA, error) {
return color.RGBA{r, g, b, a}, nil return color.RGBA{r, g, b, a}, nil
} }
// makeStaleIfDependingOn makes the image stale if the image depends on target.
func (p *Image) makeStaleIfDependingOn(target *Image) { func (p *Image) makeStaleIfDependingOn(target *Image) {
if p.stale { if p.stale {
return return
@ -227,6 +244,7 @@ func (p *Image) makeStaleIfDependingOn(target *Image) {
} }
} }
// readPixelsFromGPU reads the pixels from GPU and resolves the image's 'stale' state.
func (p *Image) readPixelsFromGPU(image *graphics.Image) error { func (p *Image) readPixelsFromGPU(image *graphics.Image) error {
var err error var err error
p.basePixels, err = image.Pixels() p.basePixels, err = image.Pixels()
@ -239,7 +257,8 @@ func (p *Image) readPixelsFromGPU(image *graphics.Image) error {
return nil return nil
} }
func (p *Image) resolveStalePixels() error { // resolveStale resolves the image's 'stale' state.
func (p *Image) resolveStale() error {
if !IsRestoringEnabled() { if !IsRestoringEnabled() {
return nil return nil
} }
@ -252,6 +271,7 @@ func (p *Image) resolveStalePixels() error {
return p.readPixelsFromGPU(p.image) return p.readPixelsFromGPU(p.image)
} }
// dependsOn returns a boolean value indicating whether the image depends on target.
func (p *Image) dependsOn(target *Image) bool { func (p *Image) dependsOn(target *Image) bool {
for _, c := range p.drawImageHistory { for _, c := range p.drawImageHistory {
if c.image == target { if c.image == target {
@ -261,6 +281,7 @@ func (p *Image) dependsOn(target *Image) bool {
return false return false
} }
// dependingImages returns all images that is depended by the image.
func (p *Image) dependingImages() map[*Image]struct{} { func (p *Image) dependingImages() map[*Image]struct{} {
r := map[*Image]struct{}{} r := map[*Image]struct{}{}
for _, c := range p.drawImageHistory { for _, c := range p.drawImageHistory {
@ -269,6 +290,7 @@ func (p *Image) dependingImages() map[*Image]struct{} {
return r return r
} }
// hasDependency returns a boolean value indicating whether the image depends on another image.
func (p *Image) hasDependency() bool { func (p *Image) hasDependency() bool {
if p.stale { if p.stale {
return false return false
@ -335,8 +357,11 @@ func (p *Image) restore() error {
return nil return nil
} }
// Dispose disposes the image.
//
// After disposing, calling the funciton of the image causes unexpected results.
func (p *Image) Dispose() { func (p *Image) Dispose() {
theImages.resetPixelsIfDependingOn(p) theImages.makeStaleIfDependingOn(p)
p.image.Dispose() p.image.Dispose()
p.image = nil p.image = nil
p.basePixels = nil p.basePixels = nil
@ -347,6 +372,9 @@ func (p *Image) Dispose() {
runtime.SetFinalizer(p, nil) runtime.SetFinalizer(p, nil)
} }
// IsInvalidated returns a boolean value indicating whether the image is invalidated.
//
// If an image is invalidated, GL context is lost and all the images should be restored asap.
func (p *Image) IsInvalidated() (bool, error) { func (p *Image) IsInvalidated() (bool, error) {
// FlushCommands is required because c.offscreen.impl might not have an actual texture. // FlushCommands is required because c.offscreen.impl might not have an actual texture.
if err := graphics.FlushCommands(); err != nil { if err := graphics.FlushCommands(); err != nil {

View File

@ -20,7 +20,9 @@ import (
) )
// restoringEnabled indicates if restoring happens or not. // restoringEnabled indicates if restoring happens or not.
var restoringEnabled = true // This value is overridden at enabled_*.go. //
// This value is overridden at enabled_*.go.
var restoringEnabled = true
// IsRestoringEnabled returns a boolean value indicating whether // IsRestoringEnabled returns a boolean value indicating whether
// restoring process works or not. // restoring process works or not.
@ -37,24 +39,30 @@ func EnableRestoringForTesting() {
// images is a set of Image objects. // images is a set of Image objects.
type images struct { type images struct {
images map[*Image]struct{} images map[*Image]struct{}
lastChecked *Image lastTarget *Image
m sync.Mutex m sync.Mutex
} }
// theImages represents the images for the current process.
var theImages = &images{ var theImages = &images{
images: map[*Image]struct{}{}, images: map[*Image]struct{}{},
} }
// FlushAndResolveStalePixels flushes the queued draw commands and resolves // ResolveStaleImages flushes the queued draw commands and resolves
// all stale images. // all stale images.
func FlushAndResolveStalePixels() error { //
// ResolveStaleImages is intended to be called at the end of a frame.
func ResolveStaleImages() error {
if err := graphics.FlushCommands(); err != nil { if err := graphics.FlushCommands(); err != nil {
return err return err
} }
return theImages.resolveStalePixels() return theImages.resolveStaleImages()
} }
// Restore restores the images.
//
// Restoring means to make all *graphics.Image objects have their textures and framebuffers.
func Restore() error { func Restore() error {
if err := graphics.ResetGLState(); err != nil { if err := graphics.ResetGLState(); err != nil {
return err return err
@ -62,35 +70,45 @@ func Restore() error {
return theImages.restore() return theImages.restore()
} }
// ClearVolatileImages clears volatile images.
//
// ClearVolatileImages is intended to be called at the start of a frame.
func ClearVolatileImages() { func ClearVolatileImages() {
theImages.clearVolatileImages() theImages.clearVolatileImages()
} }
// add adds img to the images.
func (i *images) add(img *Image) { func (i *images) add(img *Image) {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
i.images[img] = struct{}{} i.images[img] = struct{}{}
} }
// remove removes img from the images.
func (i *images) remove(img *Image) { func (i *images) remove(img *Image) {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
delete(i.images, img) delete(i.images, img)
} }
func (i *images) resolveStalePixels() error { // resolveStaleImages resolves stale images.
func (i *images) resolveStaleImages() error {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
i.lastChecked = nil i.lastTarget = nil
for img := range i.images { for img := range i.images {
if err := img.resolveStalePixels(); err != nil { if err := img.resolveStale(); err != nil {
return err return err
} }
} }
return nil return nil
} }
func (i *images) resetPixelsIfDependingOn(target *Image) { // makeStaleIfDependingOn makes all the images stale that depend on target.
//
// When target is changed, all images depending on target can't be restored with target.
// makeStaleIfDependingOn is called in such situation.
func (i *images) makeStaleIfDependingOn(target *Image) {
// Avoid defer for performance // Avoid defer for performance
i.m.Lock() i.m.Lock()
if target == nil { if target == nil {
@ -98,11 +116,11 @@ func (i *images) resetPixelsIfDependingOn(target *Image) {
i.m.Unlock() i.m.Unlock()
return return
} }
if i.lastChecked == target { if i.lastTarget == target {
i.m.Unlock() i.m.Unlock()
return return
} }
i.lastChecked = target i.lastTarget = target
for img := range i.images { for img := range i.images {
// TODO: This seems not enough: What if img becomes stale but what about // TODO: This seems not enough: What if img becomes stale but what about
// other images depend on img? (#357) // other images depend on img? (#357)
@ -111,17 +129,21 @@ func (i *images) resetPixelsIfDependingOn(target *Image) {
i.m.Unlock() i.m.Unlock()
} }
// restore restores the images.
//
// Restoring means to make all *graphics.Image objects have their textures and framebuffers.
func (i *images) restore() error { func (i *images) restore() error {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
if !IsRestoringEnabled() { if !IsRestoringEnabled() {
panic("not reached") panic("not reached")
} }
// 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.
// Let's do topological sort based on dependencies of drawing history. // Let's do topological sort based on dependencies of drawing history.
// There should not be a loop since cyclic drawing makes images stale. // It is assured that there are not loops since cyclic drawing makes images stale.
type edge struct { type edge struct {
source *Image source *Image
target *Image target *Image
@ -170,6 +192,7 @@ func (i *images) restore() error {
return nil return nil
} }
// clearVolatileImages clears the volatile images.
func (i *images) clearVolatileImages() { func (i *images) clearVolatileImages() {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
@ -178,6 +201,7 @@ func (i *images) clearVolatileImages() {
} }
} }
func ResetGLState() error { // InitializeGLState initializes the GL state.
func InitializeGLState() error {
return graphics.ResetGLState() return graphics.ResetGLState()
} }