internal/shareable: Use an exponential way to determine the image sharing

When an image was used in both ways source and destination, Ebiten
worked ineffectively since Ebiten tried to make the image on a texture
atlas after the image was used as a source for a while, which tried to
create a new internal texture every time.

This fix mitigates this issue by using an exponential way to determine
whether the image should be on a texture atlas again or not, so that
an image often used as a destination will require much longer time to
become on a texture atlas again.

Updates #1464
This commit is contained in:
Hajime Hoshi 2021-03-10 02:29:56 +09:00
parent 2c0108e178
commit df3e74533b
3 changed files with 27 additions and 10 deletions

View File

@ -15,7 +15,7 @@
package shareable package shareable
const ( const (
MaxCountForShare = maxCountForShare BaseCountForShare = baseCountForShare
) )
func MakeImagesSharedForTesting() error { func MakeImagesSharedForTesting() error {

View File

@ -66,13 +66,21 @@ func resolveDeferred() {
} }
} }
// maxCountForShare represents the time duration when the image can become shared. // baseCountForShare represents the base time duration when the image can become shared.
const maxCountForShare = 10 // Actual time duration is increased in an exponential way for each usages as a rendering target.
const baseCountForShare = 10
func min(a, b uint) uint {
if a < b {
return a
}
return b
}
func makeImagesShared() error { func makeImagesShared() error {
for i := range imagesToMakeShared { for i := range imagesToMakeShared {
i.usedAsSourceCount++ i.usedAsSourceCount++
if i.usedAsSourceCount >= maxCountForShare { if i.usedAsSourceCount >= baseCountForShare*(1<<min(uint(i.notSharedCount), 31)) {
if err := i.makeShared(); err != nil { if err := i.makeShared(); err != nil {
return err return err
} }
@ -176,6 +184,10 @@ type Image struct {
// //
// 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 shared images.
usedAsSourceCount int usedAsSourceCount int
// notSharedCount 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.
notSharedCount int
} }
func (i *Image) moveTo(dst *Image) { func (i *Image) moveTo(dst *Image) {
@ -240,6 +252,8 @@ func (i *Image) ensureNotShared() {
i.backend = &backend{ i.backend = &backend{
restorable: newImg, restorable: newImg,
} }
i.notSharedCount++
} }
func (i *Image) makeShared() error { func (i *Image) makeShared() error {

View File

@ -185,7 +185,9 @@ func TestReshared(t *testing.T) {
} }
// Use img1 as a render source. // Use img1 as a render source.
for i := 0; i < MaxCountForShare; i++ { // 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.
for i := 0; i < BaseCountForShare*2; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := MakeImagesSharedForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -247,7 +249,8 @@ func TestReshared(t *testing.T) {
} }
// Use img1 as a render source, but call ReplacePixels. // Use img1 as a render source, but call ReplacePixels.
for i := 0; i < MaxCountForShare; i++ { // Now use 4x count as img1 became an isolated image again.
for i := 0; i < BaseCountForShare*4; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := MakeImagesSharedForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -268,7 +271,7 @@ func TestReshared(t *testing.T) {
} }
// 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 never uses a shared texture.
for i := 0; i < MaxCountForShare*2; i++ { for i := 0; i < BaseCountForShare*2; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := MakeImagesSharedForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -562,7 +565,7 @@ func TestDisposedAndReshared(t *testing.T) {
} }
// Use src as a render source. // Use src as a render source.
for i := 0; i < MaxCountForShare/2; i++ { for i := 0; i < BaseCountForShare/2; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := MakeImagesSharedForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -613,7 +616,7 @@ func TestImageIsNotResharedWithoutUsingAsSource(t *testing.T) {
// 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 a shareable image or not.
for i := 0; i < MaxCountForShare; i++ { for i := 0; i < BaseCountForShare; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := MakeImagesSharedForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -623,7 +626,7 @@ func TestImageIsNotResharedWithoutUsingAsSource(t *testing.T) {
} }
// Update the count with using src2 as a rendering source. // Update the count with using src2 as a rendering source.
for i := 0; i < MaxCountForShare; i++ { for i := 0; i < BaseCountForShare; i++ {
if err := MakeImagesSharedForTesting(); err != nil { if err := MakeImagesSharedForTesting(); err != nil {
t.Fatal(err) t.Fatal(err)
} }