mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 10:48:53 +01:00
graphicscommand: Return the error immediately (#1060)
Now grpahicscommand saves the error and shows the error after a while. This was good to simplify the API but was the cause to hide some issues. This change fixes all the errors to be returned immediately, and buffer this in the ebiten package instead. Fixes #971
This commit is contained in:
parent
5c285de3db
commit
b3bdf51905
9
image.go
9
image.go
@ -401,7 +401,10 @@ func (i *Image) At(x, y int) color.Color {
|
||||
if i.isSubImage() && !image.Pt(x, y).In(i.bounds) {
|
||||
return color.RGBA{}
|
||||
}
|
||||
r, g, b, a := i.buffered.At(x, y)
|
||||
r, g, b, a, err := i.buffered.At(x, y)
|
||||
if err != nil {
|
||||
theUIContext.setError(err)
|
||||
}
|
||||
return color.RGBA{r, g, b, a}
|
||||
}
|
||||
|
||||
@ -425,7 +428,9 @@ func (i *Image) Set(x, y int, clr color.Color) {
|
||||
}
|
||||
|
||||
r, g, b, a := clr.RGBA()
|
||||
i.buffered.Set(x, y, byte(r>>8), byte(g>>8), byte(b>>8), byte(a>>8))
|
||||
if err := i.buffered.Set(x, y, byte(r>>8), byte(g>>8), byte(b>>8), byte(a>>8)); err != nil {
|
||||
theUIContext.setError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Dispose disposes the image data. After disposing, most of image functions do nothing and returns meaningless values.
|
||||
|
@ -24,21 +24,24 @@ var (
|
||||
// delayedCommands represents a queue for image operations that are ordered before the game starts
|
||||
// (BeginFrame). Before the game starts, the package shareable doesn't determine the minimum/maximum texture
|
||||
// sizes (#879).
|
||||
delayedCommands []func()
|
||||
delayedCommands []func() error
|
||||
delayedCommandsM sync.Mutex
|
||||
)
|
||||
|
||||
func flushDelayedCommands() {
|
||||
func flushDelayedCommands() error {
|
||||
delayedCommandsM.Lock()
|
||||
defer delayedCommandsM.Unlock()
|
||||
|
||||
if !needsToDelayCommands {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, c := range delayedCommands {
|
||||
c()
|
||||
if err := c(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
delayedCommands = delayedCommands[:0]
|
||||
needsToDelayCommands = false
|
||||
return nil
|
||||
}
|
||||
|
@ -36,8 +36,7 @@ func BeginFrame() error {
|
||||
if err := mipmap.BeginFrame(); err != nil {
|
||||
return err
|
||||
}
|
||||
flushDelayedCommands()
|
||||
return nil
|
||||
return flushDelayedCommands()
|
||||
}
|
||||
|
||||
func EndFrame() error {
|
||||
@ -48,10 +47,11 @@ func NewImage(width, height int, volatile bool) *Image {
|
||||
i := &Image{}
|
||||
delayedCommandsM.Lock()
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
i.img = mipmap.New(width, height, volatile)
|
||||
i.width = width
|
||||
i.height = height
|
||||
return nil
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return i
|
||||
@ -68,10 +68,11 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
||||
i := &Image{}
|
||||
delayedCommandsM.Lock()
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
i.img = mipmap.NewScreenFramebufferMipmap(width, height)
|
||||
i.width = width
|
||||
i.height = height
|
||||
return nil
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return i
|
||||
@ -104,8 +105,9 @@ func (i *Image) resolvePendingPixels(keepPendingPixels bool) {
|
||||
func (i *Image) MarkDisposed() {
|
||||
delayedCommandsM.Lock()
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
i.img.MarkDisposed()
|
||||
return nil
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return
|
||||
@ -115,7 +117,7 @@ func (i *Image) MarkDisposed() {
|
||||
i.invalidatePendingPixels()
|
||||
}
|
||||
|
||||
func (i *Image) At(x, y int) (r, g, b, a byte) {
|
||||
func (i *Image) At(x, y int) (r, g, b, a byte, err error) {
|
||||
delayedCommandsM.Lock()
|
||||
defer delayedCommandsM.Unlock()
|
||||
if needsToDelayCommands {
|
||||
@ -126,28 +128,31 @@ func (i *Image) At(x, y int) (r, g, b, a byte) {
|
||||
return i.img.At(x, y)
|
||||
}
|
||||
|
||||
func (i *Image) Set(x, y int, r, g, b, a byte) {
|
||||
func (i *Image) Set(x, y int, r, g, b, a byte) error {
|
||||
delayedCommandsM.Lock()
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
i.set(x, y, r, g, b, a)
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
return i.set(x, y, r, g, b, a)
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return
|
||||
return nil
|
||||
}
|
||||
delayedCommandsM.Unlock()
|
||||
|
||||
i.set(x, y, r, g, b, a)
|
||||
return i.set(x, y, r, g, b, a)
|
||||
}
|
||||
|
||||
func (img *Image) set(x, y int, r, g, b, a byte) {
|
||||
func (img *Image) set(x, y int, r, g, b, a byte) error {
|
||||
w, h := img.width, img.height
|
||||
if img.pixels == nil {
|
||||
pix := make([]byte, 4*w*h)
|
||||
idx := 0
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
r, g, b, a := img.img.At(i, j)
|
||||
r, g, b, a, err := img.img.At(i, j)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pix[4*idx] = r
|
||||
pix[4*idx+1] = g
|
||||
pix[4*idx+2] = b
|
||||
@ -162,6 +167,7 @@ func (img *Image) set(x, y int, r, g, b, a byte) {
|
||||
img.pixels[4*(x+y*w)+2] = b
|
||||
img.pixels[4*(x+y*w)+3] = a
|
||||
img.needsToResolvePixels = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Image) Dump(name string) error {
|
||||
@ -176,8 +182,9 @@ func (i *Image) Dump(name string) error {
|
||||
func (i *Image) Fill(clr color.RGBA) {
|
||||
delayedCommandsM.Lock()
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
i.img.Fill(clr)
|
||||
return nil
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return
|
||||
@ -191,10 +198,11 @@ func (i *Image) Fill(clr color.RGBA) {
|
||||
func (i *Image) ReplacePixels(pix []byte) {
|
||||
delayedCommandsM.Lock()
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
copied := make([]byte, len(pix))
|
||||
copy(copied, pix)
|
||||
i.img.ReplacePixels(copied)
|
||||
return nil
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return
|
||||
@ -221,8 +229,9 @@ func (i *Image) DrawImage(src *Image, bounds image.Rectangle, a, b, c, d, tx, ty
|
||||
|
||||
delayedCommandsM.Lock()
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
i.drawImage(src, bounds, g, colorm, mode, filter)
|
||||
return nil
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return
|
||||
@ -245,8 +254,9 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16,
|
||||
|
||||
delayedCommandsM.Lock()
|
||||
if needsToDelayCommands {
|
||||
delayedCommands = append(delayedCommands, func() {
|
||||
delayedCommands = append(delayedCommands, func() error {
|
||||
i.drawTriangles(src, vertices, indices, colorm, mode, filter, address)
|
||||
return nil
|
||||
})
|
||||
delayedCommandsM.Unlock()
|
||||
return
|
||||
|
@ -174,11 +174,7 @@ func fract(x float32) float32 {
|
||||
}
|
||||
|
||||
// Flush flushes the command queue.
|
||||
func (q *commandQueue) Flush() {
|
||||
if q.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
func (q *commandQueue) Flush() error {
|
||||
es := q.indices
|
||||
vs := q.vertices
|
||||
if recordLog() {
|
||||
@ -263,8 +259,7 @@ func (q *commandQueue) Flush() {
|
||||
indexOffset := 0
|
||||
for _, c := range cs[:nc] {
|
||||
if err := c.Exec(indexOffset); err != nil {
|
||||
q.err = err
|
||||
return
|
||||
return err
|
||||
}
|
||||
if recordLog() {
|
||||
fmt.Printf("%s\n", c)
|
||||
@ -282,16 +277,12 @@ func (q *commandQueue) Flush() {
|
||||
q.nindices = 0
|
||||
q.tmpNumIndices = 0
|
||||
q.nextIndex = 0
|
||||
}
|
||||
|
||||
// Error returns an OpenGL error for the last command.
|
||||
func Error() error {
|
||||
return theCommandQueue.err
|
||||
return nil
|
||||
}
|
||||
|
||||
// FlushCommands flushes the command queue.
|
||||
func FlushCommands() {
|
||||
theCommandQueue.Flush()
|
||||
func FlushCommands() error {
|
||||
return theCommandQueue.Flush()
|
||||
}
|
||||
|
||||
// drawTrianglesCommand represents a drawing command to draw an image on another image.
|
||||
|
@ -162,15 +162,17 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16,
|
||||
|
||||
// Pixels returns the image's pixels.
|
||||
// Pixels might return nil when OpenGL error happens.
|
||||
func (i *Image) Pixels() []byte {
|
||||
func (i *Image) Pixels() ([]byte, error) {
|
||||
i.resolveBufferedReplacePixels()
|
||||
c := &pixelsCommand{
|
||||
result: nil,
|
||||
img: i,
|
||||
}
|
||||
theCommandQueue.Enqueue(c)
|
||||
theCommandQueue.Flush()
|
||||
return c.result
|
||||
if err := theCommandQueue.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.result, nil
|
||||
}
|
||||
|
||||
func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
|
||||
@ -221,8 +223,12 @@ func (i *Image) Dump(path string) error {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
pix, err := i.Pixels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := png.Encode(f, &image.RGBA{
|
||||
Pix: i.Pixels(),
|
||||
Pix: pix,
|
||||
Stride: 4 * i.width,
|
||||
Rect: image.Rect(0, 0, i.width, i.height),
|
||||
}); err != nil {
|
||||
|
@ -61,7 +61,10 @@ func TestClear(t *testing.T) {
|
||||
is := graphics.QuadIndices()
|
||||
dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressClampToZero)
|
||||
|
||||
pix := dst.Pixels()
|
||||
pix, err := dst.Pixels()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for j := 0; j < h/2; j++ {
|
||||
for i := 0; i < w/2; i++ {
|
||||
idx := 4 * (i + w*j)
|
||||
|
@ -92,7 +92,7 @@ func (m *Mipmap) ReplacePixels(pix []byte) {
|
||||
m.disposeMipmaps()
|
||||
}
|
||||
|
||||
func (m *Mipmap) At(x, y int) (r, g, b, a byte) {
|
||||
func (m *Mipmap) At(x, y int) (r, g, b, a byte, err error) {
|
||||
return m.orig.At(x, y)
|
||||
}
|
||||
|
||||
|
@ -402,26 +402,34 @@ func (i *Image) appendDrawTrianglesHistory(image *Image, vertices []float32, ind
|
||||
i.drawTrianglesHistory = append(i.drawTrianglesHistory, item)
|
||||
}
|
||||
|
||||
func (i *Image) readPixelsFromGPUIfNeeded() {
|
||||
func (i *Image) readPixelsFromGPUIfNeeded() error {
|
||||
if len(i.drawTrianglesHistory) > 0 || i.stale {
|
||||
graphicscommand.FlushCommands()
|
||||
i.readPixelsFromGPU()
|
||||
if err := graphicscommand.FlushCommands(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := i.readPixelsFromGPU(); err != nil {
|
||||
return err
|
||||
}
|
||||
i.drawTrianglesHistory = nil
|
||||
i.stale = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// At returns a color value at (x, y).
|
||||
//
|
||||
// Note that this must not be called until context is available.
|
||||
func (i *Image) At(x, y int) (byte, byte, byte, byte) {
|
||||
func (i *Image) At(x, y int) (byte, byte, byte, byte, error) {
|
||||
if x < 0 || y < 0 || i.width <= x || i.height <= y {
|
||||
return 0, 0, 0, 0
|
||||
return 0, 0, 0, 0, nil
|
||||
}
|
||||
|
||||
i.readPixelsFromGPUIfNeeded()
|
||||
if err := i.readPixelsFromGPUIfNeeded(); err != nil {
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
|
||||
return i.basePixels.At(x, y)
|
||||
r, g, b, a := i.basePixels.At(x, y)
|
||||
return r, g, b, a, nil
|
||||
}
|
||||
|
||||
// makeStaleIfDependingOn makes the image stale if the image depends on target.
|
||||
@ -435,29 +443,34 @@ func (i *Image) makeStaleIfDependingOn(target *Image) {
|
||||
}
|
||||
|
||||
// readPixelsFromGPU reads the pixels from GPU and resolves the image's 'stale' state.
|
||||
func (i *Image) readPixelsFromGPU() {
|
||||
func (i *Image) readPixelsFromGPU() error {
|
||||
pix, err := i.image.Pixels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.basePixels = Pixels{}
|
||||
i.basePixels.AddOrReplace(i.image.Pixels(), 0, 0, i.width, i.height)
|
||||
i.basePixels.AddOrReplace(pix, 0, 0, i.width, i.height)
|
||||
i.drawTrianglesHistory = nil
|
||||
i.stale = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveStale resolves the image's 'stale' state.
|
||||
func (i *Image) resolveStale() {
|
||||
func (i *Image) resolveStale() error {
|
||||
if !needsRestoring() {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.volatile {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
if i.screen {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
if !i.stale {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
i.readPixelsFromGPU()
|
||||
return i.readPixelsFromGPU()
|
||||
}
|
||||
|
||||
// dependsOn returns a boolean value indicating whether the image depends on target.
|
||||
@ -488,7 +501,7 @@ func (i *Image) hasDependency() bool {
|
||||
}
|
||||
|
||||
// Restore restores *graphicscommand.Image from the pixels using its state.
|
||||
func (i *Image) restore() {
|
||||
func (i *Image) restore() error {
|
||||
w, h := i.width, i.height
|
||||
// Do not dispose the image here. The image should be already disposed.
|
||||
|
||||
@ -499,12 +512,12 @@ func (i *Image) restore() {
|
||||
i.basePixels = Pixels{}
|
||||
i.drawTrianglesHistory = nil
|
||||
i.stale = false
|
||||
return
|
||||
return nil
|
||||
}
|
||||
if i.volatile {
|
||||
i.image = graphicscommand.NewImage(w, h)
|
||||
fillImage(i.image, color.RGBA{})
|
||||
return
|
||||
return nil
|
||||
}
|
||||
if i.stale {
|
||||
panic("restorable: pixels must not be stale when restoring")
|
||||
@ -528,12 +541,17 @@ func (i *Image) restore() {
|
||||
|
||||
if len(i.drawTrianglesHistory) > 0 {
|
||||
i.basePixels = Pixels{}
|
||||
i.basePixels.AddOrReplace(gimg.Pixels(), 0, 0, w, h)
|
||||
pix, err := gimg.Pixels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.basePixels.AddOrReplace(pix, 0, 0, w, h)
|
||||
}
|
||||
|
||||
i.image = gimg
|
||||
i.drawTrianglesHistory = nil
|
||||
i.stale = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dispose disposes the image.
|
||||
@ -551,10 +569,12 @@ func (i *Image) Dispose() {
|
||||
// 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 (i *Image) isInvalidated() bool {
|
||||
func (i *Image) isInvalidated() (bool, error) {
|
||||
// FlushCommands is required because c.offscreen.impl might not have an actual texture.
|
||||
graphicscommand.FlushCommands()
|
||||
return i.image.IsInvalidated()
|
||||
if err := graphicscommand.FlushCommands(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return i.image.IsInvalidated(), nil
|
||||
}
|
||||
|
||||
func (i *Image) Dump(path string) error {
|
||||
|
@ -51,12 +51,14 @@ var theImages = &images{
|
||||
// all stale images.
|
||||
//
|
||||
// ResolveStaleImages is intended to be called at the end of a frame.
|
||||
func ResolveStaleImages() {
|
||||
graphicscommand.FlushCommands()
|
||||
if !needsRestoring() {
|
||||
return
|
||||
func ResolveStaleImages() error {
|
||||
if err := graphicscommand.FlushCommands(); err != nil {
|
||||
return err
|
||||
}
|
||||
theImages.resolveStaleImages()
|
||||
if !needsRestoring() {
|
||||
return nil
|
||||
}
|
||||
return theImages.resolveStaleImages()
|
||||
}
|
||||
|
||||
// RestoreIfNeeded restores the images.
|
||||
@ -76,7 +78,11 @@ func RestoreIfNeeded() error {
|
||||
if img.screen {
|
||||
continue
|
||||
}
|
||||
r = img.isInvalidated()
|
||||
var err error
|
||||
r, err = img.isInvalidated()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
if !r {
|
||||
@ -87,8 +93,7 @@ func RestoreIfNeeded() error {
|
||||
if err := graphicscommand.ResetGraphicsDriverState(); err != nil {
|
||||
return err
|
||||
}
|
||||
theImages.restore()
|
||||
return nil
|
||||
return theImages.restore()
|
||||
}
|
||||
|
||||
// DumpImages dumps all the current images to the specified directory.
|
||||
@ -115,11 +120,14 @@ func (i *images) remove(img *Image) {
|
||||
}
|
||||
|
||||
// resolveStaleImages resolves stale images.
|
||||
func (i *images) resolveStaleImages() {
|
||||
func (i *images) resolveStaleImages() error {
|
||||
i.lastTarget = nil
|
||||
for img := range i.images {
|
||||
img.resolveStale()
|
||||
if err := img.resolveStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeStaleIfDependingOn makes all the images stale that depend on target.
|
||||
@ -147,7 +155,7 @@ func (i *images) makeStaleIfDependingOnImpl(target *Image) {
|
||||
// restore restores the images.
|
||||
//
|
||||
// Restoring means to make all *graphicscommand.Image objects have their textures and framebuffers.
|
||||
func (i *images) restore() {
|
||||
func (i *images) restore() error {
|
||||
if !needsRestoring() {
|
||||
panic("restorable: restore cannot be called when restoring is disabled")
|
||||
}
|
||||
@ -211,15 +219,14 @@ func (i *images) restore() {
|
||||
}
|
||||
|
||||
for _, img := range sorted {
|
||||
img.restore()
|
||||
if err := img.restore(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitializeGraphicsDriverState initializes the graphics driver state.
|
||||
func InitializeGraphicsDriverState() error {
|
||||
return graphicscommand.ResetGraphicsDriverState()
|
||||
}
|
||||
|
||||
func Error() error {
|
||||
return graphicscommand.Error()
|
||||
}
|
||||
|
@ -75,7 +75,9 @@ func TestRestore(t *testing.T) {
|
||||
|
||||
clr0 := color.RGBA{0x00, 0x00, 0x00, 0xff}
|
||||
img0.ReplacePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, 0, 0, 1, 1)
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -92,7 +94,9 @@ func TestRestoreWithoutDraw(t *testing.T) {
|
||||
|
||||
// If there is no drawing command on img0, img0 is cleared when restored.
|
||||
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -144,7 +148,9 @@ func TestRestoreChain(t *testing.T) {
|
||||
is := graphics.QuadIndices()
|
||||
imgs[i+1].DrawTriangles(imgs[i], vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero)
|
||||
}
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -188,7 +194,9 @@ func TestRestoreChain2(t *testing.T) {
|
||||
imgs[i+1].DrawTriangles(imgs[i], quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero)
|
||||
}
|
||||
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -227,7 +235,9 @@ func TestRestoreOverrideSource(t *testing.T) {
|
||||
img3.DrawTriangles(img2, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero)
|
||||
img0.ReplacePixels([]byte{clr1.R, clr1.G, clr1.B, clr1.A}, 0, 0, w, h)
|
||||
img1.DrawTriangles(img0, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero)
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -320,7 +330,9 @@ func TestRestoreComplexGraph(t *testing.T) {
|
||||
img7.DrawTriangles(img2, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero)
|
||||
vs = quadVertices(w, h, 2, 0)
|
||||
img7.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero)
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -411,7 +423,9 @@ func TestRestoreRecursive(t *testing.T) {
|
||||
is := graphics.QuadIndices()
|
||||
img1.DrawTriangles(img0, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero)
|
||||
img0.DrawTriangles(img1, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero)
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -457,7 +471,10 @@ func TestReplacePixels(t *testing.T) {
|
||||
// Check the region (5, 7)-(9, 11). Outside state is indeterministic.
|
||||
for j := 7; j < 11; j++ {
|
||||
for i := 5; i < 9; i++ {
|
||||
r, g, b, a := img.At(i, j)
|
||||
r, g, b, a, err := img.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||
if got != want {
|
||||
@ -465,13 +482,18 @@ func TestReplacePixels(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for j := 7; j < 11; j++ {
|
||||
for i := 5; i < 9; i++ {
|
||||
r, g, b, a := img.At(i, j)
|
||||
r, g, b, a, err := img.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||
if got != want {
|
||||
@ -497,11 +519,16 @@ func TestDrawTrianglesAndReplacePixels(t *testing.T) {
|
||||
img1.DrawTriangles(img0, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero)
|
||||
img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0, 2, 1)
|
||||
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, g, b, a := img1.At(0, 0)
|
||||
r, g, b, a, err := img1.At(0, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||
if !sameColors(got, want, 1) {
|
||||
@ -530,11 +557,16 @@ func TestDispose(t *testing.T) {
|
||||
img0.DrawTriangles(img1, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero)
|
||||
img1.Dispose()
|
||||
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, g, b, a := img0.At(0, 0)
|
||||
r, g, b, a, err := img0.At(0, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||
if !sameColors(got, want, 1) {
|
||||
@ -651,7 +683,9 @@ func TestReplacePixelsOnly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -686,7 +720,10 @@ func TestReadPixelsFromVolatileImage(t *testing.T) {
|
||||
// Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being
|
||||
// stale.
|
||||
want := byte(0xff)
|
||||
got, _, _, _ := dst.At(0, 0)
|
||||
got, _, _, _, err := dst.At(0, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -745,7 +782,10 @@ func TestExtend(t *testing.T) {
|
||||
|
||||
for j := 0; j < h*2; j++ {
|
||||
for i := 0; i < w*2; i++ {
|
||||
got, _, _, _ := extended.At(i, j)
|
||||
got, _, _, _, err := extended.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := byte(0)
|
||||
if i < w && j < h {
|
||||
want = pixAt(i, j)
|
||||
@ -773,13 +813,18 @@ func TestFill(t *testing.T) {
|
||||
const w, h = 16, 16
|
||||
img := NewImage(w, h, false)
|
||||
img.Fill(color.RGBA{0xff, 0, 0, 0xff})
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
r, g, b, a := img.At(i, j)
|
||||
r, g, b, a, err := img.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
want := color.RGBA{0xff, 0, 0, 0xff}
|
||||
if got != want {
|
||||
@ -812,16 +857,24 @@ func TestMutateSlices(t *testing.T) {
|
||||
for i := range is {
|
||||
is[i] = 0
|
||||
}
|
||||
ResolveStaleImages()
|
||||
if err := ResolveStaleImages(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := RestoreIfNeeded(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
r, g, b, a := src.At(i, j)
|
||||
r, g, b, a, err := src.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := color.RGBA{r, g, b, a}
|
||||
r, g, b, a = dst.At(i, j)
|
||||
r, g, b, a, err = dst.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
if !sameColors(got, want, 1) {
|
||||
t.Errorf("(%d, %d): got %v, want %v", i, j, got, want)
|
||||
|
@ -14,8 +14,8 @@
|
||||
|
||||
package shareable
|
||||
|
||||
func MakeImagesSharedForTesting() {
|
||||
makeImagesShared()
|
||||
func MakeImagesSharedForTesting() error {
|
||||
return makeImagesShared()
|
||||
}
|
||||
|
||||
func (i *Image) IsSharedForTesting() bool {
|
||||
|
@ -52,8 +52,7 @@ func init() {
|
||||
defer backendsM.Unlock()
|
||||
|
||||
resolveDeferred()
|
||||
makeImagesShared()
|
||||
return nil
|
||||
return makeImagesShared()
|
||||
})
|
||||
}
|
||||
|
||||
@ -73,14 +72,17 @@ func resolveDeferred() {
|
||||
// This value is exported for testing.
|
||||
const MaxCountForShare = 10
|
||||
|
||||
func makeImagesShared() {
|
||||
func makeImagesShared() error {
|
||||
for i := range imagesToMakeShared {
|
||||
i.nonUpdatedCount++
|
||||
if i.nonUpdatedCount >= MaxCountForShare {
|
||||
i.makeShared()
|
||||
if err := i.makeShared(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
delete(imagesToMakeShared, i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
@ -220,14 +222,14 @@ func (i *Image) ensureNotShared() {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Image) makeShared() {
|
||||
func (i *Image) makeShared() error {
|
||||
if i.backend == nil {
|
||||
i.allocate(true)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.isShared() {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
if !i.shareable() {
|
||||
@ -238,7 +240,10 @@ func (i *Image) makeShared() {
|
||||
pixels := make([]byte, 4*i.width*i.height)
|
||||
for y := 0; y < i.height; y++ {
|
||||
for x := 0; x < i.width; x++ {
|
||||
r, g, b, a := i.at(x, y)
|
||||
r, g, b, a, err := i.at(x, y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pixels[4*(x+i.width*y)] = r
|
||||
pixels[4*(x+i.width*y)+1] = g
|
||||
pixels[4*(x+i.width*y)+2] = b
|
||||
@ -248,6 +253,7 @@ func (i *Image) makeShared() {
|
||||
newI.replacePixels(pixels)
|
||||
newI.moveTo(i)
|
||||
i.nonUpdatedCount = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Image) region() (x, y, width, height int) {
|
||||
@ -370,21 +376,21 @@ func (i *Image) replacePixels(p []byte) {
|
||||
i.backend.restorable.ReplacePixels(p, x, y, w, h)
|
||||
}
|
||||
|
||||
func (i *Image) At(x, y int) (byte, byte, byte, byte) {
|
||||
func (i *Image) At(x, y int) (byte, byte, byte, byte, error) {
|
||||
backendsM.Lock()
|
||||
r, g, b, a := i.at(x, y)
|
||||
r, g, b, a, err := i.at(x, y)
|
||||
backendsM.Unlock()
|
||||
return r, g, b, a
|
||||
return r, g, b, a, err
|
||||
}
|
||||
|
||||
func (i *Image) at(x, y int) (byte, byte, byte, byte) {
|
||||
func (i *Image) at(x, y int) (byte, byte, byte, byte, error) {
|
||||
if i.backend == nil {
|
||||
return 0, 0, 0, 0
|
||||
return 0, 0, 0, 0, nil
|
||||
}
|
||||
|
||||
ox, oy, w, h := i.region()
|
||||
if x < 0 || y < 0 || x >= w || y >= h {
|
||||
return 0, 0, 0, 0
|
||||
return 0, 0, 0, 0, nil
|
||||
}
|
||||
|
||||
return i.backend.restorable.At(x+ox, y+oy)
|
||||
@ -543,8 +549,7 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
||||
func EndFrame() error {
|
||||
backendsM.Lock()
|
||||
|
||||
restorable.ResolveStaleImages()
|
||||
return restorable.Error()
|
||||
return restorable.ResolveStaleImages()
|
||||
}
|
||||
|
||||
func BeginFrame() error {
|
||||
|
@ -112,7 +112,10 @@ func TestEnsureNotShared(t *testing.T) {
|
||||
|
||||
for j := 0; j < size; j++ {
|
||||
for i := 0; i < size; i++ {
|
||||
r, g, b, a := img4.At(i, j)
|
||||
r, g, b, a, err := img4.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
var want color.RGBA
|
||||
if i < dx0 || dx1 <= i || j < dy0 || dy1 <= j {
|
||||
@ -174,18 +177,25 @@ func TestReshared(t *testing.T) {
|
||||
|
||||
// Use img1 as a render source.
|
||||
for i := 0; i < MaxCountForShare; i++ {
|
||||
MakeImagesSharedForTesting()
|
||||
if err := MakeImagesSharedForTesting(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img0.DrawTriangles(img1, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero)
|
||||
if got, want := img1.IsSharedForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
}
|
||||
MakeImagesSharedForTesting()
|
||||
if err := MakeImagesSharedForTesting(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for j := 0; j < size; j++ {
|
||||
for i := 0; i < size; i++ {
|
||||
want := color.RGBA{byte(i + j), byte(i + j), byte(i + j), byte(i + j)}
|
||||
r, g, b, a := img1.At(i, j)
|
||||
r, g, b, a, err := img1.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
@ -201,7 +211,10 @@ func TestReshared(t *testing.T) {
|
||||
for j := 0; j < size; j++ {
|
||||
for i := 0; i < size; i++ {
|
||||
want := color.RGBA{byte(i + j), byte(i + j), byte(i + j), byte(i + j)}
|
||||
r, g, b, a := img1.At(i, j)
|
||||
r, g, b, a, err := img1.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
if got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
@ -211,7 +224,9 @@ func TestReshared(t *testing.T) {
|
||||
|
||||
// Use img3 as a render source. img3 never uses a shared texture.
|
||||
for i := 0; i < MaxCountForShare*2; i++ {
|
||||
MakeImagesSharedForTesting()
|
||||
if err := MakeImagesSharedForTesting(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img0.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero)
|
||||
if got, want := img3.IsSharedForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
@ -249,7 +264,10 @@ func TestExtend(t *testing.T) {
|
||||
|
||||
for j := 0; j < h0; j++ {
|
||||
for i := 0; i < w0; i++ {
|
||||
r, g, b, a := img0.At(i, j)
|
||||
r, g, b, a, err := img0.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
c := byte(i + w0*j)
|
||||
want := color.RGBA{c, c, c, c}
|
||||
@ -261,7 +279,10 @@ func TestExtend(t *testing.T) {
|
||||
|
||||
for j := 0; j < h1; j++ {
|
||||
for i := 0; i < w1; i++ {
|
||||
r, g, b, a := img1.At(i, j)
|
||||
r, g, b, a, err := img1.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
c := byte(i + w1*j)
|
||||
want := color.RGBA{c, c, c, c}
|
||||
@ -298,7 +319,10 @@ func TestReplacePixelsAfterDrawTriangles(t *testing.T) {
|
||||
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
r, g, b, a := dst.At(i, j)
|
||||
r, g, b, a, err := dst.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := color.RGBA{r, g, b, a}
|
||||
c := byte(i + w*j)
|
||||
want := color.RGBA{c, c, c, c}
|
||||
@ -332,7 +356,10 @@ func TestSmallImages(t *testing.T) {
|
||||
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
r, _, _, a := dst.At(i, j)
|
||||
r, _, _, a, err := dst.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := r, byte(0xff); got != want {
|
||||
t.Errorf("At(%d, %d) red: got: %d, want: %d", i, j, got, want)
|
||||
}
|
||||
@ -367,7 +394,10 @@ func TestLongImages(t *testing.T) {
|
||||
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w*scale; i++ {
|
||||
r, _, _, a := dst.At(i, j)
|
||||
r, _, _, a, err := dst.At(i, j)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := r, byte(0xff); got != want {
|
||||
t.Errorf("At(%d, %d) red: got: %d, want: %d", i, j, got, want)
|
||||
}
|
||||
|
10
uicontext.go
10
uicontext.go
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/internal/buffered"
|
||||
"github.com/hajimehoshi/ebiten/internal/clock"
|
||||
@ -69,6 +70,8 @@ type uiContext struct {
|
||||
outsideWidth float64
|
||||
outsideHeight float64
|
||||
|
||||
err atomic.Value
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
@ -85,6 +88,10 @@ func (c *uiContext) set(game Game, scaleForWindow float64) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *uiContext) setError(err error) {
|
||||
c.err.Store(err)
|
||||
}
|
||||
|
||||
func (c *uiContext) setScaleForWindow(scale float64) {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
@ -233,6 +240,9 @@ func (c *uiContext) offsets() (float64, float64) {
|
||||
func (c *uiContext) Update(afterFrameUpdate func()) error {
|
||||
// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.
|
||||
|
||||
if err, ok := c.err.Load().(error); ok && err != nil {
|
||||
return err
|
||||
}
|
||||
if err := buffered.BeginFrame(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user