internal/atlas: refactoring: remove SetVolatile and SetIsolate

Pass an image type to NewImage instead.
This commit is contained in:
Hajime Hoshi 2022-06-07 23:57:56 +09:00
parent b8e8d72377
commit 81f91658ff
9 changed files with 95 additions and 205 deletions

View File

@ -15,6 +15,7 @@
package ebiten package ebiten
import ( import (
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/ui" "github.com/hajimehoshi/ebiten/v2/internal/ui"
) )
@ -34,7 +35,17 @@ func (c *gameForUI) NewOffscreenImage(width, height int) *ui.Image {
c.offscreen.Dispose() c.offscreen.Dispose()
c.offscreen = nil c.offscreen = nil
} }
c.offscreen = NewImage(width, height)
// Keep the offscreen an isolated image from an atlas (#1938).
// The shader program for the screen is special and doesn't work well with an image on an atlas.
// An image on an atlas is surrounded by a transparent edge,
// and the shader program unexpectedly picks the pixel on the edges.
imageType := atlas.ImageTypeIsolated
if ui.IsScreenClearedEveryFrame() {
// A violatile image is also always isolated.
imageType = atlas.ImageTypeVolatile
}
c.offscreen = newImage(width, height, imageType)
return c.offscreen.image return c.offscreen.image
} }

View File

@ -20,6 +20,7 @@ import (
"image/color" "image/color"
"github.com/hajimehoshi/ebiten/v2/internal/affine" "github.com/hajimehoshi/ebiten/v2/internal/affine"
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/ui" "github.com/hajimehoshi/ebiten/v2/internal/ui"
@ -787,6 +788,10 @@ func (i *Image) ReplacePixels(pixels []byte) {
// //
// NewImage panics if RunGame already finishes. // NewImage panics if RunGame already finishes.
func NewImage(width, height int) *Image { func NewImage(width, height int) *Image {
return newImage(width, height, atlas.ImageTypeRegular)
}
func newImage(width, height int, imageType atlas.ImageType) *Image {
if isRunGameEnded() { if isRunGameEnded() {
panic(fmt.Sprintf("ebiten: NewImage cannot be called after RunGame finishes")) panic(fmt.Sprintf("ebiten: NewImage cannot be called after RunGame finishes"))
} }
@ -797,7 +802,7 @@ func NewImage(width, height int) *Image {
panic(fmt.Sprintf("ebiten: height at NewImage must be positive but %d", height)) panic(fmt.Sprintf("ebiten: height at NewImage must be positive but %d", height))
} }
i := &Image{ i := &Image{
image: ui.NewImage(width, height), image: ui.NewImage(width, height, imageType),
bounds: image.Rect(0, 0, width, height), bounds: image.Rect(0, 0, width, height),
} }
i.addr = i i.addr = i
@ -839,7 +844,7 @@ func NewImageFromImage(source image.Image) *Image {
} }
i := &Image{ i := &Image{
image: ui.NewImage(width, height), image: ui.NewImage(width, height, atlas.ImageTypeRegular),
bounds: image.Rect(0, 0, width, height), bounds: image.Rect(0, 0, width, height),
} }
i.addr = i i.addr = i

View File

@ -206,14 +206,21 @@ func init() {
backendsM.Lock() backendsM.Lock()
} }
type ImageType int
const (
ImageTypeRegular ImageType = iota
ImageTypeScreen
ImageTypeVolatile
ImageTypeIsolated
)
// Image is a rectangle pixel set that might be on an atlas. // Image is a rectangle pixel set that might be on an atlas.
type Image struct { type Image struct {
width int width int
height int height int
imageType ImageType
disposed bool disposed bool
isolated bool
volatile bool
screen bool
backend *backend backend *backend
@ -284,7 +291,7 @@ func (i *Image) ensureIsolated() {
sx1 /= float32(sw) sx1 /= float32(sw)
sy1 /= float32(sh) sy1 /= float32(sh)
typ := restorable.ImageTypeRegular typ := restorable.ImageTypeRegular
if i.volatile { if i.imageType == ImageTypeVolatile {
typ = restorable.ImageTypeVolatile typ = restorable.ImageTypeVolatile
} }
newImg := restorable.NewImage(w, h, typ) newImg := restorable.NewImage(w, h, typ)
@ -326,11 +333,8 @@ func (i *Image) putOnAtlas(graphicsDriver graphicsdriver.Graphics) error {
if !i.canBePutOnAtlas() { if !i.canBePutOnAtlas() {
panic("atlas: putOnAtlas cannot be called on a image that cannot be on an atlas") panic("atlas: putOnAtlas cannot be called on a image that cannot be on an atlas")
} }
if i.volatile {
panic("atlas: a volatile image cannot be put on an atlas")
}
newI := NewImage(i.width, i.height) newI := NewImage(i.width, i.height, i.imageType)
if restorable.NeedsRestoring() { if restorable.NeedsRestoring() {
// If the underlying graphics driver requires restoring from the context lost, the pixel data is // If the underlying graphics driver requires restoring from the context lost, the pixel data is
@ -433,7 +437,7 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
var dx, dy float32 var dx, dy float32
// A screen image doesn't have its padding. // A screen image doesn't have its padding.
if !i.screen { if i.imageType != ImageTypeScreen {
x, y, _, _ := i.regionWithPadding() x, y, _, _ := i.regionWithPadding()
dx = float32(x) + paddingSize dx = float32(x) + paddingSize
dy = float32(y) + paddingSize dy = float32(y) + paddingSize
@ -708,43 +712,20 @@ func (i *Image) dispose(markDisposed bool) {
theBackends = append(theBackends[:index], theBackends[index+1:]...) theBackends = append(theBackends[:index], theBackends[index+1:]...)
} }
func NewImage(width, height int) *Image { func NewImage(width, height int, imageType ImageType) *Image {
// Actual allocation is done lazily, and the lock is not needed. // Actual allocation is done lazily, and the lock is not needed.
return &Image{ return &Image{
width: width, width: width,
height: height, height: height,
imageType: imageType,
} }
} }
func (i *Image) SetIsolated(isolated bool) {
if i.backend != nil {
panic("atlas: SetIsolated must be called before its backend is allocated")
}
i.isolated = isolated
}
func (i *Image) SetVolatile(volatile bool) {
i.volatile = volatile
if i.backend == nil {
return
}
if i.volatile {
i.ensureIsolated()
}
i.backend.restorable.SetVolatile(i.volatile)
}
func (i *Image) canBePutOnAtlas() bool { func (i *Image) canBePutOnAtlas() bool {
if minSize == 0 || maxSize == 0 { if minSize == 0 || maxSize == 0 {
panic("atlas: minSize or maxSize must be initialized") panic("atlas: minSize or maxSize must be initialized")
} }
if i.isolated { if i.imageType != ImageTypeRegular {
return false
}
if i.volatile {
return false
}
if i.screen {
return false return false
} }
return i.width+2*paddingSize <= maxSize && i.height+2*paddingSize <= maxSize return i.width+2*paddingSize <= maxSize && i.height+2*paddingSize <= maxSize
@ -757,7 +738,7 @@ func (i *Image) allocate(putOnAtlas bool) {
runtime.SetFinalizer(i, (*Image).MarkDisposed) runtime.SetFinalizer(i, (*Image).MarkDisposed)
if i.screen { if i.imageType == ImageTypeScreen {
// A screen image doesn't have a padding. // A screen image doesn't have a padding.
i.backend = &backend{ i.backend = &backend{
restorable: restorable.NewImage(i.width, i.height, restorable.ImageTypeScreen), restorable: restorable.NewImage(i.width, i.height, restorable.ImageTypeScreen),
@ -767,7 +748,7 @@ func (i *Image) allocate(putOnAtlas bool) {
if !putOnAtlas || !i.canBePutOnAtlas() { if !putOnAtlas || !i.canBePutOnAtlas() {
typ := restorable.ImageTypeRegular typ := restorable.ImageTypeRegular
if i.volatile { if i.imageType == ImageTypeVolatile {
typ = restorable.ImageTypeVolatile typ = restorable.ImageTypeVolatile
} }
i.backend = &backend{ i.backend = &backend{
@ -792,7 +773,7 @@ func (i *Image) allocate(putOnAtlas bool) {
} }
typ := restorable.ImageTypeRegular typ := restorable.ImageTypeRegular
if i.volatile { if i.imageType == ImageTypeVolatile {
typ = restorable.ImageTypeVolatile typ = restorable.ImageTypeVolatile
} }
b := &backend{ b := &backend{
@ -816,16 +797,6 @@ func (i *Image) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, path stri
return i.backend.restorable.Dump(graphicsDriver, path, blackbg, image.Rect(paddingSize, paddingSize, paddingSize+i.width, paddingSize+i.height)) return i.backend.restorable.Dump(graphicsDriver, path, blackbg, image.Rect(paddingSize, paddingSize, paddingSize+i.width, paddingSize+i.height))
} }
func NewScreenFramebufferImage(width, height int) *Image {
// Actual allocation is done lazily.
i := &Image{
width: width,
height: height,
screen: true,
}
return i
}
func EndFrame(graphicsDriver graphicsdriver.Graphics) error { func EndFrame(graphicsDriver graphicsdriver.Graphics) error {
backendsM.Lock() backendsM.Lock()

View File

@ -60,25 +60,25 @@ const bigSize = 2049
func TestEnsureIsolated(t *testing.T) { func TestEnsureIsolated(t *testing.T) {
// Create img1 and img2 with this size so that the next images are allocated // Create img1 and img2 with this size so that the next images are allocated
// with non-upper-left location. // with non-upper-left location.
img1 := atlas.NewImage(bigSize, 100) img1 := atlas.NewImage(bigSize, 100, atlas.ImageTypeRegular)
defer img1.MarkDisposed() defer img1.MarkDisposed()
// Ensure img1's region is allocated. // Ensure img1's region is allocated.
img1.ReplacePixels(make([]byte, 4*bigSize*100), nil) img1.ReplacePixels(make([]byte, 4*bigSize*100), nil)
img2 := atlas.NewImage(100, bigSize) img2 := atlas.NewImage(100, bigSize, atlas.ImageTypeRegular)
defer img2.MarkDisposed() defer img2.MarkDisposed()
img2.ReplacePixels(make([]byte, 4*100*bigSize), nil) img2.ReplacePixels(make([]byte, 4*100*bigSize), nil)
const size = 32 const size = 32
img3 := atlas.NewImage(size/2, size/2) img3 := atlas.NewImage(size/2, size/2, atlas.ImageTypeRegular)
defer img3.MarkDisposed() defer img3.MarkDisposed()
img3.ReplacePixels(make([]byte, (size/2)*(size/2)*4), nil) img3.ReplacePixels(make([]byte, (size/2)*(size/2)*4), nil)
img4 := atlas.NewImage(size, size) img4 := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer img4.MarkDisposed() defer img4.MarkDisposed()
img5 := atlas.NewImage(size/2, size/2) img5 := atlas.NewImage(size/2, size/2, atlas.ImageTypeRegular)
defer img3.MarkDisposed() defer img3.MarkDisposed()
pix := make([]byte, size*size*4) pix := make([]byte, size*size*4)
@ -155,18 +155,18 @@ func TestEnsureIsolated(t *testing.T) {
func TestReputOnAtlas(t *testing.T) { func TestReputOnAtlas(t *testing.T) {
const size = 16 const size = 16
img0 := atlas.NewImage(size, size) img0 := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer img0.MarkDisposed() defer img0.MarkDisposed()
img0.ReplacePixels(make([]byte, 4*size*size), nil) img0.ReplacePixels(make([]byte, 4*size*size), nil)
img1 := atlas.NewImage(size, size) img1 := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer img1.MarkDisposed() defer img1.MarkDisposed()
img1.ReplacePixels(make([]byte, 4*size*size), nil) img1.ReplacePixels(make([]byte, 4*size*size), nil)
if got, want := img1.IsOnAtlasForTesting(), true; got != want { if got, want := img1.IsOnAtlasForTesting(), true; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
img2 := atlas.NewImage(size, size) img2 := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer img2.MarkDisposed() defer img2.MarkDisposed()
pix := make([]byte, 4*size*size) pix := make([]byte, 4*size*size)
for j := 0; j < size; j++ { for j := 0; j < size; j++ {
@ -179,8 +179,8 @@ func TestReputOnAtlas(t *testing.T) {
} }
img2.ReplacePixels(pix, nil) img2.ReplacePixels(pix, nil)
img3 := atlas.NewImage(size, size) // Create a volatile image. This should always be isolated.
img3.SetVolatile(true) img3 := atlas.NewImage(size, size, atlas.ImageTypeVolatile)
defer img3.MarkDisposed() defer img3.MarkDisposed()
img1.ReplacePixels(make([]byte, 4*size*size), nil) img1.ReplacePixels(make([]byte, 4*size*size), nil)
if got, want := img3.IsOnAtlasForTesting(), false; got != want { if got, want := img3.IsOnAtlasForTesting(), false; got != want {
@ -303,7 +303,7 @@ func TestReputOnAtlas(t *testing.T) {
func TestExtend(t *testing.T) { func TestExtend(t *testing.T) {
const w0, h0 = 100, 100 const w0, h0 = 100, 100
img0 := atlas.NewImage(w0, h0) img0 := atlas.NewImage(w0, h0, atlas.ImageTypeRegular)
defer img0.MarkDisposed() defer img0.MarkDisposed()
p0 := make([]byte, 4*w0*h0) p0 := make([]byte, 4*w0*h0)
@ -316,7 +316,7 @@ func TestExtend(t *testing.T) {
img0.ReplacePixels(p0, nil) img0.ReplacePixels(p0, nil)
const w1, h1 = minImageSizeForTesting + 1, 100 const w1, h1 = minImageSizeForTesting + 1, 100
img1 := atlas.NewImage(w1, h1) img1 := atlas.NewImage(w1, h1, atlas.ImageTypeRegular)
defer img1.MarkDisposed() defer img1.MarkDisposed()
p1 := make([]byte, 4*w1*h1) p1 := make([]byte, 4*w1*h1)
@ -370,9 +370,9 @@ func TestExtend(t *testing.T) {
func TestReplacePixelsAfterDrawTriangles(t *testing.T) { func TestReplacePixelsAfterDrawTriangles(t *testing.T) {
const w, h = 256, 256 const w, h = 256, 256
src := atlas.NewImage(w, h) src := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.MarkDisposed()
dst := atlas.NewImage(w, h) dst := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer dst.MarkDisposed() defer dst.MarkDisposed()
pix := make([]byte, 4*w*h) pix := make([]byte, 4*w*h)
@ -418,9 +418,9 @@ func TestReplacePixelsAfterDrawTriangles(t *testing.T) {
// Issue #887 // Issue #887
func TestSmallImages(t *testing.T) { func TestSmallImages(t *testing.T) {
const w, h = 4, 8 const w, h = 4, 8
src := atlas.NewImage(w, h) src := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.MarkDisposed()
dst := atlas.NewImage(w, h) dst := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer dst.MarkDisposed() defer dst.MarkDisposed()
pix := make([]byte, 4*w*h) pix := make([]byte, 4*w*h)
@ -463,11 +463,11 @@ func TestSmallImages(t *testing.T) {
// Issue #887 // Issue #887
func TestLongImages(t *testing.T) { func TestLongImages(t *testing.T) {
const w, h = 1, 6 const w, h = 1, 6
src := atlas.NewImage(w, h) src := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.MarkDisposed()
const dstW, dstH = 256, 256 const dstW, dstH = 256, 256
dst := atlas.NewImage(dstW, dstH) dst := atlas.NewImage(dstW, dstH, atlas.ImageTypeRegular)
defer dst.MarkDisposed() defer dst.MarkDisposed()
pix := make([]byte, 4*w*h) pix := make([]byte, 4*w*h)
@ -511,11 +511,11 @@ func TestLongImages(t *testing.T) {
func TestDisposeImmediately(t *testing.T) { func TestDisposeImmediately(t *testing.T) {
// This tests restorable.Image.ClearPixels is called but ReplacePixels is not called. // This tests restorable.Image.ClearPixels is called but ReplacePixels is not called.
img0 := atlas.NewImage(16, 16) img0 := atlas.NewImage(16, 16, atlas.ImageTypeRegular)
img0.EnsureIsolatedForTesting() img0.EnsureIsolatedForTesting()
defer img0.MarkDisposed() defer img0.MarkDisposed()
img1 := atlas.NewImage(16, 16) img1 := atlas.NewImage(16, 16, atlas.ImageTypeRegular)
img1.EnsureIsolatedForTesting() img1.EnsureIsolatedForTesting()
defer img1.MarkDisposed() defer img1.MarkDisposed()
@ -524,12 +524,12 @@ func TestDisposeImmediately(t *testing.T) {
// Issue #1028 // Issue #1028
func TestExtendWithBigImage(t *testing.T) { func TestExtendWithBigImage(t *testing.T) {
img0 := atlas.NewImage(1, 1) img0 := atlas.NewImage(1, 1, atlas.ImageTypeRegular)
defer img0.MarkDisposed() defer img0.MarkDisposed()
img0.ReplacePixels(make([]byte, 4*1*1), nil) img0.ReplacePixels(make([]byte, 4*1*1), nil)
img1 := atlas.NewImage(minImageSizeForTesting+1, minImageSizeForTesting+1) img1 := atlas.NewImage(minImageSizeForTesting+1, minImageSizeForTesting+1, atlas.ImageTypeRegular)
defer img1.MarkDisposed() defer img1.MarkDisposed()
img1.ReplacePixels(make([]byte, 4*(minImageSizeForTesting+1)*(minImageSizeForTesting+1)), nil) img1.ReplacePixels(make([]byte, 4*(minImageSizeForTesting+1)*(minImageSizeForTesting+1)), nil)
@ -539,7 +539,7 @@ func TestExtendWithBigImage(t *testing.T) {
func TestMaxImageSize(t *testing.T) { func TestMaxImageSize(t *testing.T) {
// This tests that a too-big image is allocated correctly. // This tests that a too-big image is allocated correctly.
s := maxImageSizeForTesting - 2*atlas.PaddingSize s := maxImageSizeForTesting - 2*atlas.PaddingSize
img := atlas.NewImage(s, s) img := atlas.NewImage(s, s, atlas.ImageTypeRegular)
defer img.MarkDisposed() defer img.MarkDisposed()
img.ReplacePixels(make([]byte, 4*s*s), nil) img.ReplacePixels(make([]byte, 4*s*s), nil)
} }
@ -552,7 +552,7 @@ func Disable_TestMinImageSize(t *testing.T) {
// This tests that extending a backend works correctly. // This tests that extending a backend works correctly.
// Though the image size is minimum size of the backend, extending the backend happens due to the paddings. // Though the image size is minimum size of the backend, extending the backend happens due to the paddings.
s := minImageSizeForTesting s := minImageSizeForTesting
img := atlas.NewImage(s, s) img := atlas.NewImage(s, s, atlas.ImageTypeRegular)
defer img.MarkDisposed() defer img.MarkDisposed()
img.ReplacePixels(make([]byte, 4*s*s), nil) img.ReplacePixels(make([]byte, 4*s*s), nil)
} }
@ -561,11 +561,11 @@ func Disable_TestMinImageSize(t *testing.T) {
func TestDisposedAndReputOnAtlas(t *testing.T) { func TestDisposedAndReputOnAtlas(t *testing.T) {
const size = 16 const size = 16
src := atlas.NewImage(size, size) src := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.MarkDisposed()
src2 := atlas.NewImage(size, size) src2 := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer src2.MarkDisposed() defer src2.MarkDisposed()
dst := atlas.NewImage(size, size) dst := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer dst.MarkDisposed() defer dst.MarkDisposed()
// Use src as a render target so that src is not on an atlas. // Use src as a render target so that src is not on an atlas.
@ -609,11 +609,11 @@ func TestDisposedAndReputOnAtlas(t *testing.T) {
func TestImageIsNotReputOnAtlasWithoutUsingAsSource(t *testing.T) { func TestImageIsNotReputOnAtlasWithoutUsingAsSource(t *testing.T) {
const size = 16 const size = 16
src := atlas.NewImage(size, size) src := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.MarkDisposed()
src2 := atlas.NewImage(size, size) src2 := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer src2.MarkDisposed() defer src2.MarkDisposed()
dst := atlas.NewImage(size, size) dst := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer dst.MarkDisposed() defer dst.MarkDisposed()
// Use src as a render target so that src is not on an atlas. // Use src as a render target so that src is not on an atlas.

View File

@ -29,7 +29,7 @@ import (
func TestShaderFillTwice(t *testing.T) { func TestShaderFillTwice(t *testing.T) {
const w, h = 1, 1 const w, h = 1, 1
dst := atlas.NewImage(w, h) dst := atlas.NewImage(w, h, atlas.ImageTypeRegular)
vs := quadVertices(w, h, 0, 0, 1) vs := quadVertices(w, h, 0, 0, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
@ -60,10 +60,10 @@ func TestShaderFillTwice(t *testing.T) {
func TestImageDrawTwice(t *testing.T) { func TestImageDrawTwice(t *testing.T) {
const w, h = 1, 1 const w, h = 1, 1
dst := atlas.NewImage(w, h) dst := atlas.NewImage(w, h, atlas.ImageTypeRegular)
src0 := atlas.NewImage(w, h) src0 := atlas.NewImage(w, h, atlas.ImageTypeRegular)
src0.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff}, nil) src0.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff}, nil)
src1 := atlas.NewImage(w, h) src1 := atlas.NewImage(w, h, atlas.ImageTypeRegular)
src1.ReplacePixels([]byte{0x80, 0x80, 0x80, 0xff}, nil) src1.ReplacePixels([]byte{0x80, 0x80, 0x80, 0xff}, nil)
vs := quadVertices(w, h, 0, 0, 1) vs := quadVertices(w, h, 0, 0, 1)

View File

@ -45,70 +45,25 @@ func EndFrame(graphicsDriver graphicsdriver.Graphics) error {
return atlas.EndFrame(graphicsDriver) return atlas.EndFrame(graphicsDriver)
} }
func NewImage(width, height int) *Image { func NewImage(width, height int, imageType atlas.ImageType) *Image {
i := &Image{ i := &Image{
width: width, width: width,
height: height, height: height,
} }
i.initialize() i.initialize(imageType)
return i return i
} }
func (i *Image) initialize() { func (i *Image) initialize(imageType atlas.ImageType) {
if maybeCanAddDelayedCommand() { if maybeCanAddDelayedCommand() {
if tryAddDelayedCommand(func() error { if tryAddDelayedCommand(func() error {
i.initialize() i.initialize(imageType)
return nil return nil
}) { }) {
return return
} }
} }
i.img = atlas.NewImage(i.width, i.height) i.img = atlas.NewImage(i.width, i.height, imageType)
}
func (i *Image) SetIsolated(isolated bool) {
if maybeCanAddDelayedCommand() {
if tryAddDelayedCommand(func() error {
i.SetIsolated(isolated)
return nil
}) {
return
}
}
i.img.SetIsolated(isolated)
}
func (i *Image) SetVolatile(volatile bool) {
if maybeCanAddDelayedCommand() {
if tryAddDelayedCommand(func() error {
i.SetVolatile(volatile)
return nil
}) {
return
}
}
i.img.SetVolatile(volatile)
}
func NewScreenFramebufferImage(width, height int) *Image {
i := &Image{}
i.initializeAsScreenFramebuffer(width, height)
return i
}
func (i *Image) initializeAsScreenFramebuffer(width, height int) {
if maybeCanAddDelayedCommand() {
if tryAddDelayedCommand(func() error {
i.initializeAsScreenFramebuffer(width, height)
return nil
}) {
return
}
}
i.img = atlas.NewScreenFramebufferImage(width, height)
i.width = width
i.height = height
} }
func (i *Image) invalidatePixels() { func (i *Image) invalidatePixels() {

View File

@ -19,6 +19,7 @@ import (
"math" "math"
"github.com/hajimehoshi/ebiten/v2/internal/affine" "github.com/hajimehoshi/ebiten/v2/internal/affine"
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/buffered" "github.com/hajimehoshi/ebiten/v2/internal/buffered"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
@ -35,38 +36,15 @@ type Mipmap struct {
imgs map[int]*buffered.Image imgs map[int]*buffered.Image
} }
func New(width, height int) *Mipmap { func New(width, height int, imageType atlas.ImageType) *Mipmap {
return &Mipmap{ return &Mipmap{
width: width, width: width,
height: height, height: height,
orig: buffered.NewImage(width, height), orig: buffered.NewImage(width, height, imageType),
volatile: imageType == atlas.ImageTypeVolatile,
} }
} }
func NewScreenFramebufferMipmap(width, height int) *Mipmap {
return &Mipmap{
width: width,
height: height,
orig: buffered.NewScreenFramebufferImage(width, height),
}
}
func (m *Mipmap) SetIsolated(isolated bool) {
m.orig.SetIsolated(isolated)
}
func (m *Mipmap) SetVolatile(volatile bool) {
if m.volatile == volatile {
return
}
m.volatile = volatile
if m.volatile {
m.disposeMipmaps()
}
m.orig.SetVolatile(volatile)
}
func (m *Mipmap) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, name string, blackbg bool) error { func (m *Mipmap) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, name string, blackbg bool) error {
return m.orig.DumpScreenshot(graphicsDriver, name, blackbg) return m.orig.DumpScreenshot(graphicsDriver, name, blackbg)
} }
@ -203,8 +181,7 @@ func (m *Mipmap) level(level int) *buffered.Image {
m.setImg(level, nil) m.setImg(level, nil)
return nil return nil
} }
s := buffered.NewImage(w2, h2) s := buffered.NewImage(w2, h2, atlas.ImageTypeVolatile)
s.SetVolatile(m.volatile)
dstRegion := graphicsdriver.Region{ dstRegion := graphicsdriver.Region{
X: 0, X: 0,

View File

@ -21,6 +21,7 @@ import (
"sync/atomic" "sync/atomic"
"github.com/hajimehoshi/ebiten/v2/internal/affine" "github.com/hajimehoshi/ebiten/v2/internal/affine"
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/buffered" "github.com/hajimehoshi/ebiten/v2/internal/buffered"
"github.com/hajimehoshi/ebiten/v2/internal/clock" "github.com/hajimehoshi/ebiten/v2/internal/clock"
"github.com/hajimehoshi/ebiten/v2/internal/debug" "github.com/hajimehoshi/ebiten/v2/internal/debug"
@ -149,13 +150,6 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics) {
w, h := c.offscreen.width, c.offscreen.height w, h := c.offscreen.width, c.offscreen.height
c.offscreen.MarkDisposed() c.offscreen.MarkDisposed()
c.offscreen = c.game.NewOffscreenImage(w, h) c.offscreen = c.game.NewOffscreenImage(w, h)
// TODO: Give volatile/isolated property to the constructor.
if theGlobalState.isScreenClearedEveryFrame() {
c.offscreen.setVolatile(true)
} else {
c.offscreen.mipmap.SetIsolated(true)
}
} }
// Even though updateCount == 0, the offscreen is cleared and Draw is called. // Even though updateCount == 0, the offscreen is cleared and Draw is called.
@ -244,7 +238,7 @@ func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFac
} }
} }
if c.screen == nil { if c.screen == nil {
c.screen = newScreenFramebufferImage(sw, sh) c.screen = NewImage(sw, sh, atlas.ImageTypeScreen)
} }
if c.offscreen != nil { if c.offscreen != nil {
@ -255,17 +249,6 @@ func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFac
} }
if c.offscreen == nil { if c.offscreen == nil {
c.offscreen = c.game.NewOffscreenImage(ow, oh) c.offscreen = c.game.NewOffscreenImage(ow, oh)
// TODO: Give volatile/isolated property to the constructor.
if theGlobalState.isScreenClearedEveryFrame() {
c.offscreen.setVolatile(true)
} else {
// Keep the offscreen an isolated image from an atlas (#1938).
// The shader program for the screen is special and doesn't work well with an image on an atlas.
// An image on an atlas is surrounded by a transparent edge,
// and the shader program unexpectedly picks the pixel on the edges.
c.offscreen.mipmap.SetIsolated(true)
}
} }
return ow, oh return ow, oh

View File

@ -16,6 +16,7 @@ package ui
import ( import (
"github.com/hajimehoshi/ebiten/v2/internal/affine" "github.com/hajimehoshi/ebiten/v2/internal/affine"
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/mipmap" "github.com/hajimehoshi/ebiten/v2/internal/mipmap"
@ -36,27 +37,14 @@ type Image struct {
volatile bool volatile bool
} }
func NewImage(width, height int) *Image { func NewImage(width, height int, imageType atlas.ImageType) *Image {
return &Image{ return &Image{
mipmap: mipmap.New(width, height), mipmap: mipmap.New(width, height, imageType),
width: width, width: width,
height: height, height: height,
} }
} }
func newScreenFramebufferImage(width, height int) *Image {
return &Image{
mipmap: mipmap.NewScreenFramebufferMipmap(width, height),
width: width,
height: height,
}
}
func (i *Image) setVolatile(volatile bool) {
i.volatile = volatile
i.mipmap.SetVolatile(volatile)
}
func (i *Image) MarkDisposed() { func (i *Image) MarkDisposed() {
if i.mipmap == nil { if i.mipmap == nil {
return return
@ -112,7 +100,7 @@ func DumpImages(dir string) error {
} }
var ( var (
emptyImage = NewImage(3, 3) emptyImage = NewImage(3, 3, atlas.ImageTypeRegular)
) )
func init() { func init() {