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 {
if !c.initialized {
if err := restorable.ResetGLState(); err != nil {
if err := restorable.InitializeGLState(); err != nil {
return err
}
c.initialized = true
@ -113,7 +113,7 @@ func (c *graphicsContext) Update(updateCount int) error {
_ = c.screen.Clear()
drawWithFittingScale(c.screen, c.offscreen2)
if err := restorable.FlushAndResolveStalePixels(); err != nil {
if err := restorable.ResolveStaleImages(); err != nil {
return err
}
return nil

View File

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

View File

@ -20,7 +20,9 @@ import (
)
// 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
// restoring process works or not.
@ -38,23 +40,29 @@ func EnableRestoringForTesting() {
// images is a set of Image objects.
type images struct {
images map[*Image]struct{}
lastChecked *Image
lastTarget *Image
m sync.Mutex
}
// theImages represents the images for the current process.
var theImages = &images{
images: map[*Image]struct{}{},
}
// FlushAndResolveStalePixels flushes the queued draw commands and resolves
// ResolveStaleImages flushes the queued draw commands and resolves
// 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 {
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 {
if err := graphics.ResetGLState(); err != nil {
return err
@ -62,35 +70,45 @@ func Restore() error {
return theImages.restore()
}
// ClearVolatileImages clears volatile images.
//
// ClearVolatileImages is intended to be called at the start of a frame.
func ClearVolatileImages() {
theImages.clearVolatileImages()
}
// add adds img to the images.
func (i *images) add(img *Image) {
i.m.Lock()
defer i.m.Unlock()
i.images[img] = struct{}{}
}
// remove removes img from the images.
func (i *images) remove(img *Image) {
i.m.Lock()
defer i.m.Unlock()
delete(i.images, img)
}
func (i *images) resolveStalePixels() error {
// resolveStaleImages resolves stale images.
func (i *images) resolveStaleImages() error {
i.m.Lock()
defer i.m.Unlock()
i.lastChecked = nil
i.lastTarget = nil
for img := range i.images {
if err := img.resolveStalePixels(); err != nil {
if err := img.resolveStale(); err != nil {
return err
}
}
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
i.m.Lock()
if target == nil {
@ -98,11 +116,11 @@ func (i *images) resetPixelsIfDependingOn(target *Image) {
i.m.Unlock()
return
}
if i.lastChecked == target {
if i.lastTarget == target {
i.m.Unlock()
return
}
i.lastChecked = target
i.lastTarget = target
for img := range i.images {
// TODO: This seems not enough: What if img becomes stale but what about
// other images depend on img? (#357)
@ -111,17 +129,21 @@ func (i *images) resetPixelsIfDependingOn(target *Image) {
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 {
i.m.Lock()
defer i.m.Unlock()
if !IsRestoringEnabled() {
panic("not reached")
}
// Framebuffers/textures cannot be disposed since framebuffers/textures that
// don't belong to the current context.
// 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 {
source *Image
target *Image
@ -170,6 +192,7 @@ func (i *images) restore() error {
return nil
}
// clearVolatileImages clears the volatile images.
func (i *images) clearVolatileImages() {
i.m.Lock()
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()
}