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) {
|
if i.isSubImage() && !image.Pt(x, y).In(i.bounds) {
|
||||||
return color.RGBA{}
|
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}
|
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()
|
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.
|
// 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
|
// 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
|
// (BeginFrame). Before the game starts, the package shareable doesn't determine the minimum/maximum texture
|
||||||
// sizes (#879).
|
// sizes (#879).
|
||||||
delayedCommands []func()
|
delayedCommands []func() error
|
||||||
delayedCommandsM sync.Mutex
|
delayedCommandsM sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
func flushDelayedCommands() {
|
func flushDelayedCommands() error {
|
||||||
delayedCommandsM.Lock()
|
delayedCommandsM.Lock()
|
||||||
defer delayedCommandsM.Unlock()
|
defer delayedCommandsM.Unlock()
|
||||||
|
|
||||||
if !needsToDelayCommands {
|
if !needsToDelayCommands {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range delayedCommands {
|
for _, c := range delayedCommands {
|
||||||
c()
|
if err := c(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delayedCommands = delayedCommands[:0]
|
delayedCommands = delayedCommands[:0]
|
||||||
needsToDelayCommands = false
|
needsToDelayCommands = false
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,7 @@ func BeginFrame() error {
|
|||||||
if err := mipmap.BeginFrame(); err != nil {
|
if err := mipmap.BeginFrame(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
flushDelayedCommands()
|
return flushDelayedCommands()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func EndFrame() error {
|
func EndFrame() error {
|
||||||
@ -48,10 +47,11 @@ func NewImage(width, height int, volatile bool) *Image {
|
|||||||
i := &Image{}
|
i := &Image{}
|
||||||
delayedCommandsM.Lock()
|
delayedCommandsM.Lock()
|
||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.img = mipmap.New(width, height, volatile)
|
i.img = mipmap.New(width, height, volatile)
|
||||||
i.width = width
|
i.width = width
|
||||||
i.height = height
|
i.height = height
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
return i
|
return i
|
||||||
@ -68,10 +68,11 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
|||||||
i := &Image{}
|
i := &Image{}
|
||||||
delayedCommandsM.Lock()
|
delayedCommandsM.Lock()
|
||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.img = mipmap.NewScreenFramebufferMipmap(width, height)
|
i.img = mipmap.NewScreenFramebufferMipmap(width, height)
|
||||||
i.width = width
|
i.width = width
|
||||||
i.height = height
|
i.height = height
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
return i
|
return i
|
||||||
@ -104,8 +105,9 @@ func (i *Image) resolvePendingPixels(keepPendingPixels bool) {
|
|||||||
func (i *Image) MarkDisposed() {
|
func (i *Image) MarkDisposed() {
|
||||||
delayedCommandsM.Lock()
|
delayedCommandsM.Lock()
|
||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.img.MarkDisposed()
|
i.img.MarkDisposed()
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
return
|
return
|
||||||
@ -115,7 +117,7 @@ func (i *Image) MarkDisposed() {
|
|||||||
i.invalidatePendingPixels()
|
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()
|
delayedCommandsM.Lock()
|
||||||
defer delayedCommandsM.Unlock()
|
defer delayedCommandsM.Unlock()
|
||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
@ -126,28 +128,31 @@ func (i *Image) At(x, y int) (r, g, b, a byte) {
|
|||||||
return i.img.At(x, y)
|
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()
|
delayedCommandsM.Lock()
|
||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.set(x, y, r, g, b, a)
|
return i.set(x, y, r, g, b, a)
|
||||||
})
|
})
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
delayedCommandsM.Unlock()
|
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
|
w, h := img.width, img.height
|
||||||
if img.pixels == nil {
|
if img.pixels == nil {
|
||||||
pix := make([]byte, 4*w*h)
|
pix := make([]byte, 4*w*h)
|
||||||
idx := 0
|
idx := 0
|
||||||
for j := 0; j < h; j++ {
|
for j := 0; j < h; j++ {
|
||||||
for i := 0; i < w; i++ {
|
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] = r
|
||||||
pix[4*idx+1] = g
|
pix[4*idx+1] = g
|
||||||
pix[4*idx+2] = b
|
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)+2] = b
|
||||||
img.pixels[4*(x+y*w)+3] = a
|
img.pixels[4*(x+y*w)+3] = a
|
||||||
img.needsToResolvePixels = true
|
img.needsToResolvePixels = true
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Dump(name string) error {
|
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) {
|
func (i *Image) Fill(clr color.RGBA) {
|
||||||
delayedCommandsM.Lock()
|
delayedCommandsM.Lock()
|
||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.img.Fill(clr)
|
i.img.Fill(clr)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
return
|
return
|
||||||
@ -191,10 +198,11 @@ func (i *Image) Fill(clr color.RGBA) {
|
|||||||
func (i *Image) ReplacePixels(pix []byte) {
|
func (i *Image) ReplacePixels(pix []byte) {
|
||||||
delayedCommandsM.Lock()
|
delayedCommandsM.Lock()
|
||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
copied := make([]byte, len(pix))
|
copied := make([]byte, len(pix))
|
||||||
copy(copied, pix)
|
copy(copied, pix)
|
||||||
i.img.ReplacePixels(copied)
|
i.img.ReplacePixels(copied)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
return
|
return
|
||||||
@ -221,8 +229,9 @@ func (i *Image) DrawImage(src *Image, bounds image.Rectangle, a, b, c, d, tx, ty
|
|||||||
|
|
||||||
delayedCommandsM.Lock()
|
delayedCommandsM.Lock()
|
||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.drawImage(src, bounds, g, colorm, mode, filter)
|
i.drawImage(src, bounds, g, colorm, mode, filter)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
return
|
return
|
||||||
@ -245,8 +254,9 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16,
|
|||||||
|
|
||||||
delayedCommandsM.Lock()
|
delayedCommandsM.Lock()
|
||||||
if needsToDelayCommands {
|
if needsToDelayCommands {
|
||||||
delayedCommands = append(delayedCommands, func() {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.drawTriangles(src, vertices, indices, colorm, mode, filter, address)
|
i.drawTriangles(src, vertices, indices, colorm, mode, filter, address)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
delayedCommandsM.Unlock()
|
delayedCommandsM.Unlock()
|
||||||
return
|
return
|
||||||
|
@ -174,11 +174,7 @@ func fract(x float32) float32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flush flushes the command queue.
|
// Flush flushes the command queue.
|
||||||
func (q *commandQueue) Flush() {
|
func (q *commandQueue) Flush() error {
|
||||||
if q.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
es := q.indices
|
es := q.indices
|
||||||
vs := q.vertices
|
vs := q.vertices
|
||||||
if recordLog() {
|
if recordLog() {
|
||||||
@ -263,8 +259,7 @@ func (q *commandQueue) Flush() {
|
|||||||
indexOffset := 0
|
indexOffset := 0
|
||||||
for _, c := range cs[:nc] {
|
for _, c := range cs[:nc] {
|
||||||
if err := c.Exec(indexOffset); err != nil {
|
if err := c.Exec(indexOffset); err != nil {
|
||||||
q.err = err
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if recordLog() {
|
if recordLog() {
|
||||||
fmt.Printf("%s\n", c)
|
fmt.Printf("%s\n", c)
|
||||||
@ -282,16 +277,12 @@ func (q *commandQueue) Flush() {
|
|||||||
q.nindices = 0
|
q.nindices = 0
|
||||||
q.tmpNumIndices = 0
|
q.tmpNumIndices = 0
|
||||||
q.nextIndex = 0
|
q.nextIndex = 0
|
||||||
}
|
return nil
|
||||||
|
|
||||||
// Error returns an OpenGL error for the last command.
|
|
||||||
func Error() error {
|
|
||||||
return theCommandQueue.err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlushCommands flushes the command queue.
|
// FlushCommands flushes the command queue.
|
||||||
func FlushCommands() {
|
func FlushCommands() error {
|
||||||
theCommandQueue.Flush()
|
return theCommandQueue.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawTrianglesCommand represents a drawing command to draw an image on another image.
|
// 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 returns the image's pixels.
|
||||||
// Pixels might return nil when OpenGL error happens.
|
// Pixels might return nil when OpenGL error happens.
|
||||||
func (i *Image) Pixels() []byte {
|
func (i *Image) Pixels() ([]byte, error) {
|
||||||
i.resolveBufferedReplacePixels()
|
i.resolveBufferedReplacePixels()
|
||||||
c := &pixelsCommand{
|
c := &pixelsCommand{
|
||||||
result: nil,
|
result: nil,
|
||||||
img: i,
|
img: i,
|
||||||
}
|
}
|
||||||
theCommandQueue.Enqueue(c)
|
theCommandQueue.Enqueue(c)
|
||||||
theCommandQueue.Flush()
|
if err := theCommandQueue.Flush(); err != nil {
|
||||||
return c.result
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
|
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()
|
defer f.Close()
|
||||||
|
|
||||||
|
pix, err := i.Pixels()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := png.Encode(f, &image.RGBA{
|
if err := png.Encode(f, &image.RGBA{
|
||||||
Pix: i.Pixels(),
|
Pix: pix,
|
||||||
Stride: 4 * i.width,
|
Stride: 4 * i.width,
|
||||||
Rect: image.Rect(0, 0, i.width, i.height),
|
Rect: image.Rect(0, 0, i.width, i.height),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -61,7 +61,10 @@ func TestClear(t *testing.T) {
|
|||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeClear, driver.FilterNearest, driver.AddressClampToZero)
|
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 j := 0; j < h/2; j++ {
|
||||||
for i := 0; i < w/2; i++ {
|
for i := 0; i < w/2; i++ {
|
||||||
idx := 4 * (i + w*j)
|
idx := 4 * (i + w*j)
|
||||||
|
@ -92,7 +92,7 @@ func (m *Mipmap) ReplacePixels(pix []byte) {
|
|||||||
m.disposeMipmaps()
|
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)
|
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)
|
i.drawTrianglesHistory = append(i.drawTrianglesHistory, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) readPixelsFromGPUIfNeeded() {
|
func (i *Image) readPixelsFromGPUIfNeeded() error {
|
||||||
if len(i.drawTrianglesHistory) > 0 || i.stale {
|
if len(i.drawTrianglesHistory) > 0 || i.stale {
|
||||||
graphicscommand.FlushCommands()
|
if err := graphicscommand.FlushCommands(); err != nil {
|
||||||
i.readPixelsFromGPU()
|
return err
|
||||||
|
}
|
||||||
|
if err := i.readPixelsFromGPU(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
i.drawTrianglesHistory = nil
|
i.drawTrianglesHistory = nil
|
||||||
i.stale = false
|
i.stale = false
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// At returns a color value at (x, y).
|
// At returns a color value at (x, y).
|
||||||
//
|
//
|
||||||
// Note that this must not be called until context is available.
|
// 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 {
|
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.
|
// 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.
|
// 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 = 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.drawTrianglesHistory = nil
|
||||||
i.stale = false
|
i.stale = false
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveStale resolves the image's 'stale' state.
|
// resolveStale resolves the image's 'stale' state.
|
||||||
func (i *Image) resolveStale() {
|
func (i *Image) resolveStale() error {
|
||||||
if !needsRestoring() {
|
if !needsRestoring() {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.volatile {
|
if i.volatile {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if i.screen {
|
if i.screen {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if !i.stale {
|
if !i.stale {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
i.readPixelsFromGPU()
|
return i.readPixelsFromGPU()
|
||||||
}
|
}
|
||||||
|
|
||||||
// dependsOn returns a boolean value indicating whether the image depends on target.
|
// 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.
|
// 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
|
w, h := i.width, i.height
|
||||||
// Do not dispose the image here. The image should be already disposed.
|
// Do not dispose the image here. The image should be already disposed.
|
||||||
|
|
||||||
@ -499,12 +512,12 @@ func (i *Image) restore() {
|
|||||||
i.basePixels = Pixels{}
|
i.basePixels = Pixels{}
|
||||||
i.drawTrianglesHistory = nil
|
i.drawTrianglesHistory = nil
|
||||||
i.stale = false
|
i.stale = false
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if i.volatile {
|
if i.volatile {
|
||||||
i.image = graphicscommand.NewImage(w, h)
|
i.image = graphicscommand.NewImage(w, h)
|
||||||
fillImage(i.image, color.RGBA{})
|
fillImage(i.image, color.RGBA{})
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
if i.stale {
|
if i.stale {
|
||||||
panic("restorable: pixels must not be stale when restoring")
|
panic("restorable: pixels must not be stale when restoring")
|
||||||
@ -528,12 +541,17 @@ func (i *Image) restore() {
|
|||||||
|
|
||||||
if len(i.drawTrianglesHistory) > 0 {
|
if len(i.drawTrianglesHistory) > 0 {
|
||||||
i.basePixels = Pixels{}
|
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.image = gimg
|
||||||
i.drawTrianglesHistory = nil
|
i.drawTrianglesHistory = nil
|
||||||
i.stale = false
|
i.stale = false
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispose disposes the image.
|
// Dispose disposes the image.
|
||||||
@ -551,10 +569,12 @@ func (i *Image) Dispose() {
|
|||||||
// isInvalidated returns a boolean value indicating whether the image is invalidated.
|
// 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.
|
// 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.
|
// FlushCommands is required because c.offscreen.impl might not have an actual texture.
|
||||||
graphicscommand.FlushCommands()
|
if err := graphicscommand.FlushCommands(); err != nil {
|
||||||
return i.image.IsInvalidated()
|
return false, err
|
||||||
|
}
|
||||||
|
return i.image.IsInvalidated(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Dump(path string) error {
|
func (i *Image) Dump(path string) error {
|
||||||
|
@ -51,12 +51,14 @@ var theImages = &images{
|
|||||||
// all stale images.
|
// all stale images.
|
||||||
//
|
//
|
||||||
// ResolveStaleImages is intended to be called at the end of a frame.
|
// ResolveStaleImages is intended to be called at the end of a frame.
|
||||||
func ResolveStaleImages() {
|
func ResolveStaleImages() error {
|
||||||
graphicscommand.FlushCommands()
|
if err := graphicscommand.FlushCommands(); err != nil {
|
||||||
if !needsRestoring() {
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
theImages.resolveStaleImages()
|
if !needsRestoring() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return theImages.resolveStaleImages()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreIfNeeded restores the images.
|
// RestoreIfNeeded restores the images.
|
||||||
@ -76,7 +78,11 @@ func RestoreIfNeeded() error {
|
|||||||
if img.screen {
|
if img.screen {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r = img.isInvalidated()
|
var err error
|
||||||
|
r, err = img.isInvalidated()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !r {
|
if !r {
|
||||||
@ -87,8 +93,7 @@ func RestoreIfNeeded() error {
|
|||||||
if err := graphicscommand.ResetGraphicsDriverState(); err != nil {
|
if err := graphicscommand.ResetGraphicsDriverState(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
theImages.restore()
|
return theImages.restore()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DumpImages dumps all the current images to the specified directory.
|
// DumpImages dumps all the current images to the specified directory.
|
||||||
@ -115,11 +120,14 @@ func (i *images) remove(img *Image) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resolveStaleImages resolves stale images.
|
// resolveStaleImages resolves stale images.
|
||||||
func (i *images) resolveStaleImages() {
|
func (i *images) resolveStaleImages() error {
|
||||||
i.lastTarget = nil
|
i.lastTarget = nil
|
||||||
for img := range i.images {
|
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.
|
// makeStaleIfDependingOn makes all the images stale that depend on target.
|
||||||
@ -147,7 +155,7 @@ func (i *images) makeStaleIfDependingOnImpl(target *Image) {
|
|||||||
// restore restores the images.
|
// restore restores the images.
|
||||||
//
|
//
|
||||||
// Restoring means to make all *graphicscommand.Image objects have their textures and framebuffers.
|
// Restoring means to make all *graphicscommand.Image objects have their textures and framebuffers.
|
||||||
func (i *images) restore() {
|
func (i *images) restore() error {
|
||||||
if !needsRestoring() {
|
if !needsRestoring() {
|
||||||
panic("restorable: restore cannot be called when restoring is disabled")
|
panic("restorable: restore cannot be called when restoring is disabled")
|
||||||
}
|
}
|
||||||
@ -211,15 +219,14 @@ func (i *images) restore() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, img := range sorted {
|
for _, img := range sorted {
|
||||||
img.restore()
|
if err := img.restore(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeGraphicsDriverState initializes the graphics driver state.
|
// InitializeGraphicsDriverState initializes the graphics driver state.
|
||||||
func InitializeGraphicsDriverState() error {
|
func InitializeGraphicsDriverState() error {
|
||||||
return graphicscommand.ResetGraphicsDriverState()
|
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}
|
clr0 := color.RGBA{0x00, 0x00, 0x00, 0xff}
|
||||||
img0.ReplacePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, 0, 0, 1, 1)
|
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 {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
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.
|
// 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 {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -144,7 +148,9 @@ func TestRestoreChain(t *testing.T) {
|
|||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
imgs[i+1].DrawTriangles(imgs[i], vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero)
|
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 {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
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)
|
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 {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
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)
|
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)
|
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)
|
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 {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -320,7 +330,9 @@ func TestRestoreComplexGraph(t *testing.T) {
|
|||||||
img7.DrawTriangles(img2, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero)
|
img7.DrawTriangles(img2, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero)
|
||||||
vs = quadVertices(w, h, 2, 0)
|
vs = quadVertices(w, h, 2, 0)
|
||||||
img7.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero)
|
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 {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -411,7 +423,9 @@ func TestRestoreRecursive(t *testing.T) {
|
|||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
img1.DrawTriangles(img0, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero)
|
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)
|
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 {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -457,7 +471,10 @@ func TestReplacePixels(t *testing.T) {
|
|||||||
// Check the region (5, 7)-(9, 11). Outside state is indeterministic.
|
// Check the region (5, 7)-(9, 11). Outside state is indeterministic.
|
||||||
for j := 7; j < 11; j++ {
|
for j := 7; j < 11; j++ {
|
||||||
for i := 5; i < 9; i++ {
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||||
if got != want {
|
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 {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for j := 7; j < 11; j++ {
|
for j := 7; j < 11; j++ {
|
||||||
for i := 5; i < 9; i++ {
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||||
if got != want {
|
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.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)
|
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 {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||||
if !sameColors(got, want, 1) {
|
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)
|
img0.DrawTriangles(img1, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero)
|
||||||
img1.Dispose()
|
img1.Dispose()
|
||||||
|
|
||||||
ResolveStaleImages()
|
if err := ResolveStaleImages(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if err := RestoreIfNeeded(); err != nil {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||||
if !sameColors(got, want, 1) {
|
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 {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
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
|
// Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being
|
||||||
// stale.
|
// stale.
|
||||||
want := byte(0xff)
|
want := byte(0xff)
|
||||||
got, _, _, _ := dst.At(0, 0)
|
got, _, _, _, err := dst.At(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("got: %v, want: %v", 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 j := 0; j < h*2; j++ {
|
||||||
for i := 0; i < w*2; i++ {
|
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)
|
want := byte(0)
|
||||||
if i < w && j < h {
|
if i < w && j < h {
|
||||||
want = pixAt(i, j)
|
want = pixAt(i, j)
|
||||||
@ -773,13 +813,18 @@ func TestFill(t *testing.T) {
|
|||||||
const w, h = 16, 16
|
const w, h = 16, 16
|
||||||
img := NewImage(w, h, false)
|
img := NewImage(w, h, false)
|
||||||
img.Fill(color.RGBA{0xff, 0, 0, 0xff})
|
img.Fill(color.RGBA{0xff, 0, 0, 0xff})
|
||||||
ResolveStaleImages()
|
if err := ResolveStaleImages(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if err := RestoreIfNeeded(); err != nil {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for j := 0; j < h; j++ {
|
for j := 0; j < h; j++ {
|
||||||
for i := 0; i < w; i++ {
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
want := color.RGBA{0xff, 0, 0, 0xff}
|
want := color.RGBA{0xff, 0, 0, 0xff}
|
||||||
if got != want {
|
if got != want {
|
||||||
@ -812,16 +857,24 @@ func TestMutateSlices(t *testing.T) {
|
|||||||
for i := range is {
|
for i := range is {
|
||||||
is[i] = 0
|
is[i] = 0
|
||||||
}
|
}
|
||||||
ResolveStaleImages()
|
if err := ResolveStaleImages(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if err := RestoreIfNeeded(); err != nil {
|
if err := RestoreIfNeeded(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for j := 0; j < h; j++ {
|
for j := 0; j < h; j++ {
|
||||||
for i := 0; i < w; i++ {
|
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}
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
if !sameColors(got, want, 1) {
|
if !sameColors(got, want, 1) {
|
||||||
t.Errorf("(%d, %d): got %v, want %v", i, j, got, want)
|
t.Errorf("(%d, %d): got %v, want %v", i, j, got, want)
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
package shareable
|
package shareable
|
||||||
|
|
||||||
func MakeImagesSharedForTesting() {
|
func MakeImagesSharedForTesting() error {
|
||||||
makeImagesShared()
|
return makeImagesShared()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) IsSharedForTesting() bool {
|
func (i *Image) IsSharedForTesting() bool {
|
||||||
|
@ -52,8 +52,7 @@ func init() {
|
|||||||
defer backendsM.Unlock()
|
defer backendsM.Unlock()
|
||||||
|
|
||||||
resolveDeferred()
|
resolveDeferred()
|
||||||
makeImagesShared()
|
return makeImagesShared()
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,14 +72,17 @@ func resolveDeferred() {
|
|||||||
// This value is exported for testing.
|
// This value is exported for testing.
|
||||||
const MaxCountForShare = 10
|
const MaxCountForShare = 10
|
||||||
|
|
||||||
func makeImagesShared() {
|
func makeImagesShared() error {
|
||||||
for i := range imagesToMakeShared {
|
for i := range imagesToMakeShared {
|
||||||
i.nonUpdatedCount++
|
i.nonUpdatedCount++
|
||||||
if i.nonUpdatedCount >= MaxCountForShare {
|
if i.nonUpdatedCount >= MaxCountForShare {
|
||||||
i.makeShared()
|
if err := i.makeShared(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delete(imagesToMakeShared, i)
|
delete(imagesToMakeShared, i)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type backend struct {
|
type backend struct {
|
||||||
@ -220,14 +222,14 @@ func (i *Image) ensureNotShared() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) makeShared() {
|
func (i *Image) makeShared() error {
|
||||||
if i.backend == nil {
|
if i.backend == nil {
|
||||||
i.allocate(true)
|
i.allocate(true)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.isShared() {
|
if i.isShared() {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !i.shareable() {
|
if !i.shareable() {
|
||||||
@ -238,7 +240,10 @@ func (i *Image) makeShared() {
|
|||||||
pixels := make([]byte, 4*i.width*i.height)
|
pixels := make([]byte, 4*i.width*i.height)
|
||||||
for y := 0; y < i.height; y++ {
|
for y := 0; y < i.height; y++ {
|
||||||
for x := 0; x < i.width; x++ {
|
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)] = r
|
||||||
pixels[4*(x+i.width*y)+1] = g
|
pixels[4*(x+i.width*y)+1] = g
|
||||||
pixels[4*(x+i.width*y)+2] = b
|
pixels[4*(x+i.width*y)+2] = b
|
||||||
@ -248,6 +253,7 @@ func (i *Image) makeShared() {
|
|||||||
newI.replacePixels(pixels)
|
newI.replacePixels(pixels)
|
||||||
newI.moveTo(i)
|
newI.moveTo(i)
|
||||||
i.nonUpdatedCount = 0
|
i.nonUpdatedCount = 0
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) region() (x, y, width, height int) {
|
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)
|
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()
|
backendsM.Lock()
|
||||||
r, g, b, a := i.at(x, y)
|
r, g, b, a, err := i.at(x, y)
|
||||||
backendsM.Unlock()
|
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 {
|
if i.backend == nil {
|
||||||
return 0, 0, 0, 0
|
return 0, 0, 0, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ox, oy, w, h := i.region()
|
ox, oy, w, h := i.region()
|
||||||
if x < 0 || y < 0 || x >= w || y >= h {
|
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)
|
return i.backend.restorable.At(x+ox, y+oy)
|
||||||
@ -543,8 +549,7 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
|||||||
func EndFrame() error {
|
func EndFrame() error {
|
||||||
backendsM.Lock()
|
backendsM.Lock()
|
||||||
|
|
||||||
restorable.ResolveStaleImages()
|
return restorable.ResolveStaleImages()
|
||||||
return restorable.Error()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BeginFrame() error {
|
func BeginFrame() error {
|
||||||
|
@ -112,7 +112,10 @@ func TestEnsureNotShared(t *testing.T) {
|
|||||||
|
|
||||||
for j := 0; j < size; j++ {
|
for j := 0; j < size; j++ {
|
||||||
for i := 0; i < size; i++ {
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
var want color.RGBA
|
var want color.RGBA
|
||||||
if i < dx0 || dx1 <= i || j < dy0 || dy1 <= j {
|
if i < dx0 || dx1 <= i || j < dy0 || dy1 <= j {
|
||||||
@ -174,18 +177,25 @@ func TestReshared(t *testing.T) {
|
|||||||
|
|
||||||
// Use img1 as a render source.
|
// Use img1 as a render source.
|
||||||
for i := 0; i < MaxCountForShare; i++ {
|
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)
|
img0.DrawTriangles(img1, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero)
|
||||||
if got, want := img1.IsSharedForTesting(), false; got != want {
|
if got, want := img1.IsSharedForTesting(), false; got != want {
|
||||||
t.Errorf("got: %v, want: %v", 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 j := 0; j < size; j++ {
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
want := color.RGBA{byte(i + j), byte(i + j), byte(i + j), byte(i + j)}
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("got: %v, want: %v", 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 j := 0; j < size; j++ {
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
want := color.RGBA{byte(i + j), byte(i + j), byte(i + j), byte(i + j)}
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("got: %v, want: %v", 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.
|
// Use img3 as a render source. img3 never uses a shared texture.
|
||||||
for i := 0; i < MaxCountForShare*2; i++ {
|
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)
|
img0.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero)
|
||||||
if got, want := img3.IsSharedForTesting(), false; got != want {
|
if got, want := img3.IsSharedForTesting(), false; got != want {
|
||||||
t.Errorf("got: %v, want: %v", 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 j := 0; j < h0; j++ {
|
||||||
for i := 0; i < w0; i++ {
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
c := byte(i + w0*j)
|
c := byte(i + w0*j)
|
||||||
want := color.RGBA{c, c, c, c}
|
want := color.RGBA{c, c, c, c}
|
||||||
@ -261,7 +279,10 @@ func TestExtend(t *testing.T) {
|
|||||||
|
|
||||||
for j := 0; j < h1; j++ {
|
for j := 0; j < h1; j++ {
|
||||||
for i := 0; i < w1; i++ {
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
c := byte(i + w1*j)
|
c := byte(i + w1*j)
|
||||||
want := color.RGBA{c, c, c, c}
|
want := color.RGBA{c, c, c, c}
|
||||||
@ -298,7 +319,10 @@ func TestReplacePixelsAfterDrawTriangles(t *testing.T) {
|
|||||||
|
|
||||||
for j := 0; j < h; j++ {
|
for j := 0; j < h; j++ {
|
||||||
for i := 0; i < w; i++ {
|
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}
|
got := color.RGBA{r, g, b, a}
|
||||||
c := byte(i + w*j)
|
c := byte(i + w*j)
|
||||||
want := color.RGBA{c, c, c, c}
|
want := color.RGBA{c, c, c, c}
|
||||||
@ -332,7 +356,10 @@ func TestSmallImages(t *testing.T) {
|
|||||||
|
|
||||||
for j := 0; j < h; j++ {
|
for j := 0; j < h; j++ {
|
||||||
for i := 0; i < w; i++ {
|
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 {
|
if got, want := r, byte(0xff); got != want {
|
||||||
t.Errorf("At(%d, %d) red: got: %d, want: %d", i, j, 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 j := 0; j < h; j++ {
|
||||||
for i := 0; i < w*scale; i++ {
|
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 {
|
if got, want := r, byte(0xff); got != want {
|
||||||
t.Errorf("At(%d, %d) red: got: %d, want: %d", i, j, 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"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/internal/buffered"
|
"github.com/hajimehoshi/ebiten/internal/buffered"
|
||||||
"github.com/hajimehoshi/ebiten/internal/clock"
|
"github.com/hajimehoshi/ebiten/internal/clock"
|
||||||
@ -69,6 +70,8 @@ type uiContext struct {
|
|||||||
outsideWidth float64
|
outsideWidth float64
|
||||||
outsideHeight float64
|
outsideHeight float64
|
||||||
|
|
||||||
|
err atomic.Value
|
||||||
|
|
||||||
m sync.Mutex
|
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) {
|
func (c *uiContext) setScaleForWindow(scale float64) {
|
||||||
c.m.Lock()
|
c.m.Lock()
|
||||||
defer c.m.Unlock()
|
defer c.m.Unlock()
|
||||||
@ -233,6 +240,9 @@ func (c *uiContext) offsets() (float64, float64) {
|
|||||||
func (c *uiContext) Update(afterFrameUpdate func()) error {
|
func (c *uiContext) Update(afterFrameUpdate func()) error {
|
||||||
// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.
|
// 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 {
|
if err := buffered.BeginFrame(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user