Rename internal/shareable -> internal/atlas

Also the terms are renamed:

 * shared -> on an atlas
 * not shared -> isolated

Closes #1529
This commit is contained in:
Hajime Hoshi 2021-03-11 23:13:24 +09:00
parent d2be9beb2d
commit ec677a258f
6 changed files with 131 additions and 128 deletions

View File

@ -23,7 +23,7 @@ import (
"os" "os"
"time" "time"
"github.com/hajimehoshi/ebiten/v2/internal/shareable" "github.com/hajimehoshi/ebiten/v2/internal/atlas"
) )
// availableFilename returns a filename that is valid as a new file or directory. // availableFilename returns a filename that is valid as a new file or directory.
@ -73,7 +73,7 @@ func dumpInternalImages() error {
return err return err
} }
if err := shareable.DumpImages(dir); err != nil { if err := atlas.DumpImages(dir); err != nil {
return err return err
} }

View File

@ -12,14 +12,14 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package shareable package atlas
const ( const (
BaseCountForShare = baseCountForShare BaseCountToPutOnAtlas = baseCountToPutOnAtlas
) )
func MakeImagesSharedForTesting() error { func PutImagesOnAtlasForTesting() error {
return makeImagesShared() return putImagesOnAtlas()
} }
var ( var (
@ -45,16 +45,16 @@ func ResetBackendsForTesting() {
theBackends = nil theBackends = nil
} }
func (i *Image) IsSharedForTesting() bool { func (i *Image) IsOnAtlasForTesting() bool {
backendsM.Lock() backendsM.Lock()
defer backendsM.Unlock() defer backendsM.Unlock()
return i.isShared() return i.isOnAtlas()
} }
func (i *Image) EnsureNotSharedForTesting() { func (i *Image) EnsureIsolatedForTesting() {
backendsM.Lock() backendsM.Lock()
defer backendsM.Unlock() defer backendsM.Unlock()
i.ensureNotShared() i.ensureIsolated()
} }
func ResolveDeferredForTesting() { func ResolveDeferredForTesting() {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package shareable package atlas
import ( import (
"fmt" "fmt"
@ -51,7 +51,7 @@ func init() {
defer backendsM.Unlock() defer backendsM.Unlock()
resolveDeferred() resolveDeferred()
return makeImagesShared() return putImagesOnAtlas()
}) })
} }
@ -66,9 +66,9 @@ func resolveDeferred() {
} }
} }
// baseCountForShare represents the base time duration when the image can become shared. // baseCountToPutOnAtlas represents the base time duration when the image can be put onto an atlas.
// Actual time duration is increased in an exponential way for each usages as a rendering target. // Actual time duration is increased in an exponential way for each usages as a rendering target.
const baseCountForShare = 10 const baseCountToPutOnAtlas = 10
func min(a, b uint) uint { func min(a, b uint) uint {
if a < b { if a < b {
@ -77,33 +77,35 @@ func min(a, b uint) uint {
return b return b
} }
func makeImagesShared() error { func putImagesOnAtlas() error {
for i := range imagesToMakeShared { for i := range imagesToPutOnAtlas {
i.usedAsSourceCount++ i.usedAsSourceCount++
if i.usedAsSourceCount >= baseCountForShare*(1<<min(uint(i.notSharedCount), 31)) { if i.usedAsSourceCount >= baseCountToPutOnAtlas*(1<<min(uint(i.isolatedCount), 31)) {
if err := i.makeShared(); err != nil { if err := i.putOnAtlas(); err != nil {
return err return err
} }
i.usedAsSourceCount = 0 i.usedAsSourceCount = 0
delete(imagesToMakeShared, i) delete(imagesToPutOnAtlas, i)
} }
} }
// Reset the images. The images will be registered again when it is used as a rendering source. // Reset the images. The images will be registered again when it is used as a rendering source.
for k := range imagesToMakeShared { for k := range imagesToPutOnAtlas {
delete(imagesToMakeShared, k) delete(imagesToPutOnAtlas, k)
} }
return nil return nil
} }
type backend struct { type backend struct {
// restorable is an atlas on which there might be multiple images.
restorable *restorable.Image restorable *restorable.Image
// If page is nil, the backend is not shared. // page is an atlas map. Each part is called a node.
// If page is nil, the backend's image is isolated and not on an atlas.
page *packing.Page page *packing.Page
} }
func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) { func (b *backend) tryAlloc(width, height int) (*packing.Node, bool) {
// If the region is allocated without any extension, that's fine. // If the region is allocated without any extension, that's fine.
if n := b.page.Alloc(width, height); n != nil { if n := b.page.Alloc(width, height); n != nil {
return n, true return n, true
@ -129,7 +131,7 @@ func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) {
b.restorable = b.restorable.Extend(s, s) b.restorable = b.restorable.Extend(s, s)
if n == nil { if n == nil {
panic("shareable: Alloc result must not be nil at TryAlloc") panic("atlas: Alloc result must not be nil at TryAlloc")
} }
return n, true return n, true
} }
@ -140,10 +142,10 @@ var (
initOnce sync.Once initOnce sync.Once
// theBackends is a set of actually shared images. // theBackends is a set of atlases.
theBackends = []*backend{} theBackends = []*backend{}
imagesToMakeShared = map[*Image]struct{}{} imagesToPutOnAtlas = map[*Image]struct{}{}
deferred []func() deferred []func()
@ -163,6 +165,7 @@ func init() {
backendsM.Lock() backendsM.Lock()
} }
// Image is a renctangle pixel set that might be on an atlas.
type Image struct { type Image struct {
width int width int
height int height int
@ -177,17 +180,17 @@ type Image struct {
// usedAsSourceCount represents how long the image is used as a rendering source and kept not modified with // usedAsSourceCount represents how long the image is used as a rendering source and kept not modified with
// DrawTriangles. // DrawTriangles.
// In the current implementation, if an image is being modified by DrawTriangles, the image is separated from // In the current implementation, if an image is being modified by DrawTriangles, the image is separated from
// a shared (restorable) image by ensureNotShared. // a restorable image on an atlas by ensureIsolated.
// //
// usedAsSourceCount is increased if the image is used as a rendering source, or set to 0 if the image is // usedAsSourceCount is increased if the image is used as a rendering source, or set to 0 if the image is
// modified. // modified.
// //
// ReplacePixels doesn't affect this value since ReplacePixels can be done on shared images. // ReplacePixels doesn't affect this value since ReplacePixels can be done on images on an atlas.
usedAsSourceCount int usedAsSourceCount int
// notSharedCount represents how many times the image on a texture atlas is changed into an isolated image. // isolatedCount represents how many times the image on a texture atlas is changed into an isolated image.
// notSharedCount affects the calculation when to make the image onto a texture atlas again. // isolatedCount affects the calculation when to put the image onto a texture atlas again.
notSharedCount int isolatedCount int
} }
func (i *Image) moveTo(dst *Image) { func (i *Image) moveTo(dst *Image) {
@ -199,16 +202,16 @@ func (i *Image) moveTo(dst *Image) {
runtime.SetFinalizer(i, nil) runtime.SetFinalizer(i, nil)
} }
func (i *Image) isShared() bool { func (i *Image) isOnAtlas() bool {
return i.node != nil return i.node != nil
} }
func (i *Image) resetUsedAsSourceCount() { func (i *Image) resetUsedAsSourceCount() {
i.usedAsSourceCount = 0 i.usedAsSourceCount = 0
delete(imagesToMakeShared, i) delete(imagesToPutOnAtlas, i)
} }
func (i *Image) ensureNotShared() { func (i *Image) ensureIsolated() {
i.resetUsedAsSourceCount() i.resetUsedAsSourceCount()
if i.backend == nil { if i.backend == nil {
@ -216,7 +219,7 @@ func (i *Image) ensureNotShared() {
return return
} }
if !i.isShared() { if !i.isOnAtlas() {
return return
} }
@ -253,21 +256,21 @@ func (i *Image) ensureNotShared() {
restorable: newImg, restorable: newImg,
} }
i.notSharedCount++ i.isolatedCount++
} }
func (i *Image) makeShared() error { func (i *Image) putOnAtlas() error {
if i.backend == nil { if i.backend == nil {
i.allocate(true) i.allocate(true)
return nil return nil
} }
if i.isShared() { if i.isOnAtlas() {
return nil return nil
} }
if !i.shareable() { if !i.canBePutOnAtlas() {
panic("shareable: makeShared cannot be called on a non-shareable image") panic("atlas: putOnAtlas cannot be called on a image that cannot be on an atlas")
} }
newI := NewImage(i.width, i.height) newI := NewImage(i.width, i.height)
@ -275,7 +278,7 @@ func (i *Image) makeShared() error {
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
// needed. A shared image must have its complete pixel data in this case. // needed. A image on an atlas must have its complete pixel data in this case.
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++ {
@ -312,9 +315,9 @@ func (i *Image) makeShared() error {
func (i *Image) regionWithPadding() (x, y, width, height int) { func (i *Image) regionWithPadding() (x, y, width, height int) {
if i.backend == nil { if i.backend == nil {
panic("shareable: backend must not be nil: not allocated yet?") panic("atlas: backend must not be nil: not allocated yet?")
} }
if !i.isShared() { if !i.isOnAtlas() {
return 0, 0, i.width + 2*paddingSize, i.height + 2*paddingSize return 0, 0, i.width + 2*paddingSize, i.height + 2*paddingSize
} }
return i.node.Region() return i.node.Region()
@ -325,16 +328,16 @@ func (i *Image) processSrc(src *Image) {
return return
} }
if src.disposed { if src.disposed {
panic("shareable: the drawing source image must not be disposed (DrawTriangles)") panic("atlas: the drawing source image must not be disposed (DrawTriangles)")
} }
if src.backend == nil { if src.backend == nil {
src.allocate(true) src.allocate(true)
} }
// Compare i and source images after ensuring i is not shared, or // Compare i and source images after ensuring i is not on an atlas, or
// i and a source image might share the same texture even though i != src. // i and a source image might share the same atlas even though i != src.
if i.backend.restorable == src.backend.restorable { if i.backend.restorable == src.backend.restorable {
panic("shareable: Image.DrawTriangles: source must be different from the receiver") panic("atlas: Image.DrawTriangles: source must be different from the receiver")
} }
} }
@ -356,16 +359,16 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, false) i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, false)
} }
func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, keepShared bool) { func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, keepOnAtlas bool) {
if i.disposed { if i.disposed {
panic("shareable: the drawing target image must not be disposed (DrawTriangles)") panic("atlas: the drawing target image must not be disposed (DrawTriangles)")
} }
if keepShared { if keepOnAtlas {
if i.backend == nil { if i.backend == nil {
i.allocate(true) i.allocate(true)
} }
} else { } else {
i.ensureNotShared() i.ensureIsolated()
} }
for _, src := range srcs { for _, src := range srcs {
@ -441,9 +444,9 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
if src == nil { if src == nil {
continue continue
} }
if !src.isShared() && src.shareable() { if !src.isOnAtlas() && src.canBePutOnAtlas() {
// src might already registered, but assiging it again is not harmful. // src might already registered, but assiging it again is not harmful.
imagesToMakeShared[src] = struct{}{} imagesToPutOnAtlas[src] = struct{}{}
} }
} }
} }
@ -456,7 +459,7 @@ func (i *Image) ReplacePixels(pix []byte) {
func (i *Image) replacePixels(pix []byte) { func (i *Image) replacePixels(pix []byte) {
if i.disposed { if i.disposed {
panic("shareable: the image must not be disposed at replacePixels") panic("atlas: the image must not be disposed at replacePixels")
} }
i.resetUsedAsSourceCount() i.resetUsedAsSourceCount()
@ -476,7 +479,7 @@ func (i *Image) replacePixels(pix []byte) {
ow, oh := w-2*paddingSize, h-2*paddingSize ow, oh := w-2*paddingSize, h-2*paddingSize
if l := 4 * ow * oh; len(pix) != l { if l := 4 * ow * oh; len(pix) != l {
panic(fmt.Sprintf("shareable: len(p) must be %d but %d", l, len(pix))) panic(fmt.Sprintf("atlas: len(p) must be %d but %d", l, len(pix)))
} }
// Add a padding around the image. // Add a padding around the image.
@ -562,7 +565,7 @@ func (i *Image) dispose(markDisposed bool) {
return return
} }
if !i.isShared() { if !i.isOnAtlas() {
i.backend.restorable.Dispose() i.backend.restorable.Dispose()
return return
} }
@ -583,7 +586,7 @@ func (i *Image) dispose(markDisposed bool) {
} }
} }
if index == -1 { if index == -1 {
panic("shareable: backend not found at an image being disposed") panic("atlas: backend not found at an image being disposed")
} }
theBackends = append(theBackends[:index], theBackends[index+1:]...) theBackends = append(theBackends[:index], theBackends[index+1:]...)
} }
@ -602,14 +605,14 @@ func (i *Image) SetVolatile(volatile bool) {
return return
} }
if i.volatile { if i.volatile {
i.ensureNotShared() i.ensureIsolated()
} }
i.backend.restorable.SetVolatile(i.volatile) i.backend.restorable.SetVolatile(i.volatile)
} }
func (i *Image) shareable() bool { func (i *Image) canBePutOnAtlas() bool {
if minSize == 0 || maxSize == 0 { if minSize == 0 || maxSize == 0 {
panic("shareable: minSize or maxSize must be initialized") panic("atlas: minSize or maxSize must be initialized")
} }
if i.volatile { if i.volatile {
return false return false
@ -620,9 +623,9 @@ func (i *Image) shareable() bool {
return i.width+2*paddingSize <= maxSize && i.height+2*paddingSize <= maxSize return i.width+2*paddingSize <= maxSize && i.height+2*paddingSize <= maxSize
} }
func (i *Image) allocate(shareable bool) { func (i *Image) allocate(putOnAtlas bool) {
if i.backend != nil { if i.backend != nil {
panic("shareable: the image is already allocated") panic("atlas: the image is already allocated")
} }
runtime.SetFinalizer(i, (*Image).MarkDisposed) runtime.SetFinalizer(i, (*Image).MarkDisposed)
@ -635,7 +638,7 @@ func (i *Image) allocate(shareable bool) {
return return
} }
if !shareable || !i.shareable() { if !putOnAtlas || !i.canBePutOnAtlas() {
i.backend = &backend{ i.backend = &backend{
restorable: restorable.NewImage(i.width+2*paddingSize, i.height+2*paddingSize), restorable: restorable.NewImage(i.width+2*paddingSize, i.height+2*paddingSize),
} }
@ -644,7 +647,7 @@ func (i *Image) allocate(shareable bool) {
} }
for _, b := range theBackends { for _, b := range theBackends {
if n, ok := b.TryAlloc(i.width+2*paddingSize, i.height+2*paddingSize); ok { if n, ok := b.tryAlloc(i.width+2*paddingSize, i.height+2*paddingSize); ok {
i.backend = b i.backend = b
i.node = n i.node = n
return return
@ -653,7 +656,7 @@ func (i *Image) allocate(shareable bool) {
size := minSize size := minSize
for i.width+2*paddingSize > size || i.height+2*paddingSize > size { for i.width+2*paddingSize > size || i.height+2*paddingSize > size {
if size == maxSize { if size == maxSize {
panic(fmt.Sprintf("shareable: the image being shared is too big: width: %d, height: %d", i.width, i.height)) panic(fmt.Sprintf("atlas: the image being put on an atlas is too big: width: %d, height: %d", i.width, i.height))
} }
size *= 2 size *= 2
} }
@ -667,7 +670,7 @@ func (i *Image) allocate(shareable bool) {
n := b.page.Alloc(i.width+2*paddingSize, i.height+2*paddingSize) n := b.page.Alloc(i.width+2*paddingSize, i.height+2*paddingSize)
if n == nil { if n == nil {
panic("shareable: Alloc result must not be nil at allocate") panic("atlas: Alloc result must not be nil at allocate")
} }
i.backend = b i.backend = b
i.node = n i.node = n
@ -706,7 +709,7 @@ func BeginFrame() error {
return return
} }
if len(theBackends) != 0 { if len(theBackends) != 0 {
panic("shareable: all the images must be not-shared before the game starts") panic("atlas: all the images must be not on an atlas before the game starts")
} }
minSize = 1024 minSize = 1024
maxSize = max(minSize, restorable.MaxImageSize()) maxSize = max(minSize, restorable.MaxImageSize())

View File

@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package shareable_test package atlas_test
import ( import (
"image/color" "image/color"
"runtime" "runtime"
"testing" "testing"
. "github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
. "github.com/hajimehoshi/ebiten/v2/internal/shareable"
t "github.com/hajimehoshi/ebiten/v2/internal/testing" t "github.com/hajimehoshi/ebiten/v2/internal/testing"
) )
@ -55,7 +55,7 @@ func quadVertices(sw, sh, x, y int, scalex float32) []float32 {
const bigSize = 2049 const bigSize = 2049
func TestEnsureNotShared(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 := NewImage(bigSize, 100) img1 := NewImage(bigSize, 100)
@ -93,7 +93,7 @@ func TestEnsureNotShared(t *testing.T) {
dx1 = size * 3 / 4 dx1 = size * 3 / 4
dy1 = size * 3 / 4 dy1 = size * 3 / 4
) )
// img4.ensureNotShared() should be called. // img4.EnsureIsolated() should be called.
vs := quadVertices(size/2, size/2, size/4, size/4, 1) vs := quadVertices(size/2, size/2, size/4, size/4, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := driver.Region{ dr := driver.Region{
@ -104,7 +104,7 @@ func TestEnsureNotShared(t *testing.T) {
} }
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
want := false want := false
if got := img4.IsSharedForTesting(); got != want { if got := img4.IsOnAtlasForTesting(); got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -135,7 +135,7 @@ func TestEnsureNotShared(t *testing.T) {
img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) img4.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
} }
func TestReshared(t *testing.T) { func TestReputOnAtlas(t *testing.T) {
const size = 16 const size = 16
img0 := NewImage(size, size) img0 := NewImage(size, size)
@ -145,7 +145,7 @@ func TestReshared(t *testing.T) {
img1 := NewImage(size, size) img1 := NewImage(size, size)
defer img1.MarkDisposed() defer img1.MarkDisposed()
img1.ReplacePixels(make([]byte, 4*size*size)) img1.ReplacePixels(make([]byte, 4*size*size))
if got, want := img1.IsSharedForTesting(), 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)
} }
@ -166,7 +166,7 @@ func TestReshared(t *testing.T) {
img3.SetVolatile(true) img3.SetVolatile(true)
defer img3.MarkDisposed() defer img3.MarkDisposed()
img1.ReplacePixels(make([]byte, 4*size*size)) img1.ReplacePixels(make([]byte, 4*size*size))
if got, want := img3.IsSharedForTesting(), false; got != want { if got, want := img3.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -180,23 +180,23 @@ func TestReshared(t *testing.T) {
Height: size, Height: size,
} }
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := img1.IsSharedForTesting(), false; got != want { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
// Use img1 as a render source. // Use img1 as a render source.
// Use the doubled count since img1 was on a texture atlas and became an isolated image once. // Use the doubled count since img1 was on a texture atlas and became an isolated image once.
// Then, img1 requires longer time to recover to be on a textur atlas again. // Then, img1 requires longer time to recover to be on a textur atlas again.
for i := 0; i < BaseCountForShare*2; i++ { for i := 0; i < BaseCountToPutOnAtlas*2; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := img1.IsSharedForTesting(), false; got != want { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
} }
if err := MakeImagesSharedForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -218,9 +218,9 @@ func TestReshared(t *testing.T) {
} }
} }
// img1 is on a shared image again. // img1 is on an atlas again.
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := img1.IsSharedForTesting(), 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)
} }
@ -244,39 +244,39 @@ func TestReshared(t *testing.T) {
// Use img1 as a render target again. // Use img1 as a render target again.
img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) img1.DrawTriangles([graphics.ShaderImageNum]*Image{img2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := img1.IsSharedForTesting(), false; got != want { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
// Use img1 as a render source, but call ReplacePixels. // Use img1 as a render source, but call ReplacePixels.
// Now use 4x count as img1 became an isolated image again. // Now use 4x count as img1 became an isolated image again.
for i := 0; i < BaseCountForShare*4; i++ { for i := 0; i < BaseCountToPutOnAtlas*4; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
img1.ReplacePixels(make([]byte, 4*size*size)) img1.ReplacePixels(make([]byte, 4*size*size))
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := img1.IsSharedForTesting(), false; got != want { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
} }
if err := MakeImagesSharedForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// img1 is not on a shared image due to ReplacePixels. // img1 is not on an atlas due to ReplacePixels.
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img1}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := img1.IsSharedForTesting(), false; got != want { if got, want := img1.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
// Use img3 as a render source. As img3 is volatile, img3 never uses a shared texture. // Use img3 as a render source. As img3 is volatile, img3 is never on an atlas.
for i := 0; i < BaseCountForShare*2; i++ { for i := 0; i < BaseCountToPutOnAtlas*2; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
img0.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) img0.DrawTriangles([graphics.ShaderImageNum]*Image{img3}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := img3.IsSharedForTesting(), false; got != want { if got, want := img3.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
} }
@ -495,11 +495,11 @@ 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 := NewImage(16, 16) img0 := NewImage(16, 16)
img0.EnsureNotSharedForTesting() img0.EnsureIsolatedForTesting()
defer img0.MarkDisposed() defer img0.MarkDisposed()
img1 := NewImage(16, 16) img1 := NewImage(16, 16)
img1.EnsureNotSharedForTesting() img1.EnsureIsolatedForTesting()
defer img1.MarkDisposed() defer img1.MarkDisposed()
// img0 and img1 should share the same backend in 99.9999% possibility. // img0 and img1 should share the same backend in 99.9999% possibility.
@ -540,7 +540,7 @@ func TestMinImageSize(t *testing.T) {
} }
// Issue #1421 // Issue #1421
func TestDisposedAndReshared(t *testing.T) { func TestDisposedAndReputOnAtlas(t *testing.T) {
const size = 16 const size = 16
src := NewImage(size, size) src := NewImage(size, size)
@ -550,7 +550,7 @@ func TestDisposedAndReshared(t *testing.T) {
dst := NewImage(size, size) dst := NewImage(size, size)
defer dst.MarkDisposed() defer dst.MarkDisposed()
// Use src as a render target so that src is not on the shared image. // Use src as a render target so that src is not on an atlas.
vs := quadVertices(size, size, 0, 0, 1) vs := quadVertices(size, size, 0, 0, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := driver.Region{ dr := driver.Region{
@ -560,35 +560,35 @@ func TestDisposedAndReshared(t *testing.T) {
Height: size, Height: size,
} }
src.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) src.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := src.IsSharedForTesting(), false; got != want { if got, want := src.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
// Use src as a render source. // Use src as a render source.
for i := 0; i < BaseCountForShare/2; i++ { for i := 0; i < BaseCountToPutOnAtlas/2; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := src.IsSharedForTesting(), false; got != want { if got, want := src.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
} }
// Before MakeImagesSharedForTesting, dispose the image. // Before PutImaegsOnAtlasForTesting, dispose the image.
src.MarkDisposed() src.MarkDisposed()
// Force to dispose the image. // Force to dispose the image.
ResolveDeferredForTesting() ResolveDeferredForTesting()
// Confirm that MakeImagesSharedForTesting doesn't panic. // Confirm that PutImagesOnAtlasForTesting doesn't panic.
if err := MakeImagesSharedForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
// Issue #1456 // Issue #1456
func TestImageIsNotResharedWithoutUsingAsSource(t *testing.T) { func TestImageIsNotReputOnAtlasWithoutUsingAsSource(t *testing.T) {
const size = 16 const size = 16
src := NewImage(size, size) src := NewImage(size, size)
@ -598,7 +598,7 @@ func TestImageIsNotResharedWithoutUsingAsSource(t *testing.T) {
dst := NewImage(size, size) dst := NewImage(size, size)
defer dst.MarkDisposed() defer dst.MarkDisposed()
// Use src as a render target so that src is not on the shared image. // Use src as a render target so that src is not on an atlas.
vs := quadVertices(size, size, 0, 0, 1) vs := quadVertices(size, size, 0, 0, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := driver.Region{ dr := driver.Region{
@ -610,38 +610,38 @@ func TestImageIsNotResharedWithoutUsingAsSource(t *testing.T) {
// Use src2 as a rendering target, and make src2 an independent image. // Use src2 as a rendering target, and make src2 an independent image.
src2.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) src2.DrawTriangles([graphics.ShaderImageNum]*Image{src}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := src2.IsSharedForTesting(), false; got != want { if got, want := src2.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
// Update the count without using src2 as a rendering source. // Update the count without using src2 as a rendering source.
// This should not affect whether src2 is on a shareable image or not. // This should not affect whether src2 is on an atlas or not.
for i := 0; i < BaseCountForShare; i++ { for i := 0; i < BaseCountToPutOnAtlas; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if got, want := src2.IsSharedForTesting(), false; got != want { if got, want := src2.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
} }
// Update the count with using src2 as a rendering source. // Update the count with using src2 as a rendering source.
for i := 0; i < BaseCountForShare; i++ { for i := 0; i < BaseCountToPutOnAtlas; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil) dst.DrawTriangles([graphics.ShaderImageNum]*Image{src2}, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil)
if got, want := src2.IsSharedForTesting(), false; got != want { if got, want := src2.IsOnAtlasForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
} }
if err := MakeImagesSharedForTesting(); err != nil { if err := PutImagesOnAtlasForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if got, want := src2.IsSharedForTesting(), true; got != want { if got, want := src2.IsOnAtlasForTesting(), true; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
} }
// TODO: Add tests to extend shareable image out of the main loop // TODO: Add tests to extend image on an atlas out of the main loop

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package shareable package atlas
import ( import (
"runtime" "runtime"

View File

@ -19,14 +19,14 @@ import (
"image" "image"
"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/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir" "github.com/hajimehoshi/ebiten/v2/internal/shaderir"
"github.com/hajimehoshi/ebiten/v2/internal/shareable"
) )
type Image struct { type Image struct {
img *shareable.Image img *atlas.Image
width int width int
height int height int
@ -35,14 +35,14 @@ type Image struct {
} }
func BeginFrame() error { func BeginFrame() error {
if err := shareable.BeginFrame(); err != nil { if err := atlas.BeginFrame(); err != nil {
return err return err
} }
return flushDelayedCommands() return flushDelayedCommands()
} }
func EndFrame() error { func EndFrame() error {
return shareable.EndFrame() return atlas.EndFrame()
} }
func NewImage(width, height int) *Image { func NewImage(width, height int) *Image {
@ -60,7 +60,7 @@ func (i *Image) initialize(width, height int) {
return return
} }
} }
i.img = shareable.NewImage(width, height) i.img = atlas.NewImage(width, height)
i.width = width i.width = width
i.height = height i.height = height
} }
@ -93,7 +93,7 @@ func (i *Image) initializeAsScreenFramebuffer(width, height int) {
} }
} }
i.img = shareable.NewScreenFramebufferImage(width, height) i.img = atlas.NewScreenFramebufferImage(width, height)
i.width = width i.width = width
i.height = height i.height = height
} }
@ -219,8 +219,8 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
} }
} }
var s *shareable.Shader var s *atlas.Shader
var imgs [graphics.ShaderImageNum]*shareable.Image var imgs [graphics.ShaderImageNum]*atlas.Image
if shader == nil { if shader == nil {
// Fast path for rendering without a shader (#1355). // Fast path for rendering without a shader (#1355).
img := srcs[0] img := srcs[0]
@ -243,12 +243,12 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
} }
type Shader struct { type Shader struct {
shader *shareable.Shader shader *atlas.Shader
} }
func NewShader(program *shaderir.Program) *Shader { func NewShader(program *shaderir.Program) *Shader {
return &Shader{ return &Shader{
shader: shareable.NewShader(program), shader: atlas.NewShader(program),
} }
} }