mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
restorable: Add comments
This commit is contained in:
parent
b51d93a707
commit
80940f9070
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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).
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
@ -37,24 +39,30 @@ func EnableRestoringForTesting() {
|
||||
|
||||
// images is a set of Image objects.
|
||||
type images struct {
|
||||
images map[*Image]struct{}
|
||||
lastChecked *Image
|
||||
m sync.Mutex
|
||||
images map[*Image]struct{}
|
||||
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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user