mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 02:38:53 +01:00
internal/restorable: optimize removeDuplicatedRegions from O((n+m)^2) to O(n*m + m^2) (#2631)
This is achieved by replacing the function by one that only adds a single new region, and only considers duplicates between the previously existing region and the one newly added one, thereby removing previously redundant checking of each previously existing region against each other. This speeds up AAAAXY loading on a Moto G7 Play from 52.27 seconds to 8.15 seconds. Closes #2626
This commit is contained in:
parent
ce71c31a27
commit
cc24796270
@ -18,6 +18,6 @@ import (
|
||||
"image"
|
||||
)
|
||||
|
||||
func RemoveDuplicatedRegions(regions []image.Rectangle) int {
|
||||
return removeDuplicatedRegions(regions)
|
||||
func AppendRegionRemovingDuplicates(regions *[]image.Rectangle, region image.Rectangle) {
|
||||
appendRegionRemovingDuplicates(regions, region)
|
||||
}
|
||||
|
@ -240,26 +240,26 @@ func (i *Image) makeStale(rect image.Rectangle) {
|
||||
return
|
||||
}
|
||||
|
||||
origNum := len(i.staleRegions)
|
||||
i.staleRegions = i.appendRegionsForDrawTriangles(i.staleRegions)
|
||||
var addedRegions []image.Rectangle
|
||||
i.appendRegionsForDrawTriangles(&addedRegions)
|
||||
if !rect.Empty() {
|
||||
i.staleRegions = append(i.staleRegions, rect)
|
||||
appendRegionRemovingDuplicates(&addedRegions, rect)
|
||||
}
|
||||
|
||||
for _, rect := range addedRegions {
|
||||
appendRegionRemovingDuplicates(&i.staleRegions, rect)
|
||||
}
|
||||
|
||||
i.clearDrawTrianglesHistory()
|
||||
|
||||
// Clear pixels to save memory.
|
||||
for _, r := range i.staleRegions[origNum:] {
|
||||
for _, r := range addedRegions {
|
||||
if r.Empty() {
|
||||
continue
|
||||
}
|
||||
i.basePixels.Clear(r.Min.X, r.Min.Y, r.Dx(), r.Dy())
|
||||
}
|
||||
|
||||
// Remove duplicated regions to avoid unnecessary reading pixels from GPU.
|
||||
n := removeDuplicatedRegions(i.staleRegions)
|
||||
i.staleRegions = i.staleRegions[:n]
|
||||
|
||||
// Don't have to call makeStale recursively here.
|
||||
// Restoring is done after topological sorting is done.
|
||||
// If an image depends on another stale image, this means that
|
||||
@ -483,12 +483,11 @@ func (i *Image) readPixelsFromGPU(graphicsDriver graphicsdriver.Graphics) error
|
||||
if i.stale {
|
||||
rs = i.staleRegions
|
||||
} else {
|
||||
i.regionsCache = i.appendRegionsForDrawTriangles(i.regionsCache)
|
||||
i.appendRegionsForDrawTriangles(&i.regionsCache)
|
||||
defer func() {
|
||||
i.regionsCache = i.regionsCache[:0]
|
||||
}()
|
||||
n := removeDuplicatedRegions(i.regionsCache)
|
||||
rs = i.regionsCache[:n]
|
||||
rs = i.regionsCache
|
||||
}
|
||||
|
||||
for _, r := range rs {
|
||||
@ -625,14 +624,12 @@ func (i *Image) restore(graphicsDriver graphicsdriver.Graphics) error {
|
||||
|
||||
// In order to clear the draw-triangles history, read pixels from GPU.
|
||||
if len(i.drawTrianglesHistory) > 0 {
|
||||
i.regionsCache = i.appendRegionsForDrawTriangles(i.regionsCache)
|
||||
i.appendRegionsForDrawTriangles(&i.regionsCache)
|
||||
defer func() {
|
||||
i.regionsCache = i.regionsCache[:0]
|
||||
}()
|
||||
n := removeDuplicatedRegions(i.regionsCache)
|
||||
rs := i.regionsCache[:n]
|
||||
|
||||
for _, r := range rs {
|
||||
for _, r := range i.regionsCache {
|
||||
if r.Empty() {
|
||||
continue
|
||||
}
|
||||
@ -698,18 +695,14 @@ func (i *Image) InternalSize() (int, int) {
|
||||
return i.image.InternalSize()
|
||||
}
|
||||
|
||||
func (i *Image) appendRegionsForDrawTriangles(regions []image.Rectangle) []image.Rectangle {
|
||||
n := len(regions)
|
||||
func (i *Image) appendRegionsForDrawTriangles(regions *[]image.Rectangle) {
|
||||
for _, d := range i.drawTrianglesHistory {
|
||||
r := regionToRectangle(d.dstRegion)
|
||||
if r.Empty() {
|
||||
continue
|
||||
}
|
||||
regions = append(regions, r)
|
||||
appendRegionRemovingDuplicates(regions, r)
|
||||
}
|
||||
|
||||
nn := removeDuplicatedRegions(regions[n:])
|
||||
return regions[:n+nn]
|
||||
}
|
||||
|
||||
func regionToRectangle(region graphicsdriver.Region) image.Rectangle {
|
||||
@ -720,34 +713,29 @@ func regionToRectangle(region graphicsdriver.Region) image.Rectangle {
|
||||
int(math.Ceil(float64(region.Y+region.Height))))
|
||||
}
|
||||
|
||||
// removeDuplicatedRegions removes duplicated regions and returns the new size of the slice.
|
||||
// If a region covers other regions, the covered regions are removed.
|
||||
func removeDuplicatedRegions(regions []image.Rectangle) int {
|
||||
for i, r := range regions {
|
||||
if r.Empty() {
|
||||
continue
|
||||
}
|
||||
for j, rr := range regions {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
if rr.Empty() {
|
||||
continue
|
||||
}
|
||||
if rr.In(r) {
|
||||
regions[j] = image.Rectangle{}
|
||||
}
|
||||
// appendRegionRemovingDuplicates adds a region to a given list of regions,
|
||||
// but removes any duplicate between the newly added region and any existing regions.
|
||||
//
|
||||
// In case the newly added region is fully contained in any pre-existing region, this function does nothing.
|
||||
// Otherwise, any pre-existing regions that are fully contained in the newly added region are removed.
|
||||
//
|
||||
// This is done to avoid unnecessary reading pixels from GPU.
|
||||
func appendRegionRemovingDuplicates(regions *[]image.Rectangle, region image.Rectangle) {
|
||||
for _, r := range *regions {
|
||||
if region.In(r) {
|
||||
// The newly added rectangle is fully contained in one of the input regions.
|
||||
// Nothing to add.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Separate loop, as regions must not get mutated before above return.
|
||||
n := 0
|
||||
for _, r := range regions {
|
||||
if r.Empty() {
|
||||
for _, r := range *regions {
|
||||
if r.In(region) {
|
||||
continue
|
||||
}
|
||||
regions[n] = r
|
||||
(*regions)[n] = r
|
||||
n++
|
||||
}
|
||||
|
||||
return n
|
||||
*regions = append((*regions)[:n], region)
|
||||
}
|
||||
|
@ -37,94 +37,129 @@ func areEqualRectangles(a, b []image.Rectangle) bool {
|
||||
|
||||
func TestRemoveDuplicatedRegions(t *testing.T) {
|
||||
cases := []struct {
|
||||
In []image.Rectangle
|
||||
Out []image.Rectangle
|
||||
Regions []image.Rectangle
|
||||
NewRegions []image.Rectangle
|
||||
Expected []image.Rectangle
|
||||
}{
|
||||
{
|
||||
In: nil,
|
||||
Out: nil,
|
||||
Regions: nil,
|
||||
NewRegions: nil,
|
||||
Expected: nil,
|
||||
},
|
||||
{
|
||||
In: []image.Rectangle{
|
||||
NewRegions: []image.Rectangle{
|
||||
image.Rect(0, 0, 2, 2),
|
||||
},
|
||||
Out: []image.Rectangle{
|
||||
Expected: []image.Rectangle{
|
||||
image.Rect(0, 0, 2, 2),
|
||||
},
|
||||
},
|
||||
{
|
||||
In: []image.Rectangle{
|
||||
NewRegions: []image.Rectangle{
|
||||
image.Rect(0, 0, 2, 2),
|
||||
image.Rect(0, 0, 1, 1),
|
||||
},
|
||||
Out: []image.Rectangle{
|
||||
Expected: []image.Rectangle{
|
||||
image.Rect(0, 0, 2, 2),
|
||||
},
|
||||
},
|
||||
{
|
||||
In: []image.Rectangle{
|
||||
NewRegions: []image.Rectangle{
|
||||
image.Rect(0, 0, 1, 1),
|
||||
image.Rect(0, 0, 2, 2),
|
||||
},
|
||||
Out: []image.Rectangle{
|
||||
Expected: []image.Rectangle{
|
||||
image.Rect(0, 0, 2, 2),
|
||||
},
|
||||
},
|
||||
{
|
||||
In: []image.Rectangle{
|
||||
NewRegions: []image.Rectangle{
|
||||
image.Rect(0, 0, 1, 3),
|
||||
image.Rect(0, 0, 2, 2),
|
||||
image.Rect(0, 0, 3, 1),
|
||||
},
|
||||
Out: []image.Rectangle{
|
||||
Expected: []image.Rectangle{
|
||||
image.Rect(0, 0, 1, 3),
|
||||
image.Rect(0, 0, 2, 2),
|
||||
image.Rect(0, 0, 3, 1),
|
||||
},
|
||||
},
|
||||
{
|
||||
In: []image.Rectangle{
|
||||
NewRegions: []image.Rectangle{
|
||||
image.Rect(0, 0, 1, 3),
|
||||
image.Rect(0, 0, 2, 2),
|
||||
image.Rect(0, 0, 3, 1),
|
||||
image.Rect(0, 0, 4, 4),
|
||||
},
|
||||
Out: []image.Rectangle{
|
||||
Expected: []image.Rectangle{
|
||||
image.Rect(0, 0, 4, 4),
|
||||
},
|
||||
},
|
||||
{
|
||||
In: []image.Rectangle{
|
||||
NewRegions: []image.Rectangle{
|
||||
image.Rect(0, 0, 1, 3),
|
||||
image.Rect(0, 0, 2, 2),
|
||||
image.Rect(0, 0, 3, 1),
|
||||
image.Rect(0, 0, 4, 4),
|
||||
image.Rect(1, 1, 2, 2),
|
||||
},
|
||||
Out: []image.Rectangle{
|
||||
Expected: []image.Rectangle{
|
||||
image.Rect(0, 0, 4, 4),
|
||||
},
|
||||
},
|
||||
{
|
||||
In: []image.Rectangle{
|
||||
NewRegions: []image.Rectangle{
|
||||
image.Rect(0, 0, 1, 3),
|
||||
image.Rect(0, 0, 2, 2),
|
||||
image.Rect(0, 0, 3, 1),
|
||||
image.Rect(0, 0, 4, 4),
|
||||
image.Rect(0, 0, 5, 5),
|
||||
},
|
||||
Out: []image.Rectangle{
|
||||
Expected: []image.Rectangle{
|
||||
image.Rect(0, 0, 5, 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
Regions: []image.Rectangle{
|
||||
image.Rect(0, 0, 1, 3),
|
||||
image.Rect(0, 0, 2, 2),
|
||||
image.Rect(0, 0, 3, 1),
|
||||
image.Rect(0, 0, 4, 4),
|
||||
},
|
||||
NewRegions: []image.Rectangle{
|
||||
image.Rect(0, 0, 5, 5),
|
||||
},
|
||||
Expected: []image.Rectangle{
|
||||
image.Rect(0, 0, 5, 5),
|
||||
},
|
||||
},
|
||||
{
|
||||
Regions: []image.Rectangle{
|
||||
image.Rect(0, 0, 2, 2),
|
||||
image.Rect(0, 0, 3, 1),
|
||||
image.Rect(0, 0, 4, 4),
|
||||
image.Rect(0, 0, 5, 5),
|
||||
},
|
||||
NewRegions: []image.Rectangle{
|
||||
image.Rect(0, 0, 1, 3),
|
||||
},
|
||||
Expected: []image.Rectangle{
|
||||
image.Rect(0, 0, 2, 2),
|
||||
image.Rect(0, 0, 3, 1),
|
||||
image.Rect(0, 0, 4, 4),
|
||||
image.Rect(0, 0, 5, 5),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
n := restorable.RemoveDuplicatedRegions(c.In)
|
||||
got := c.In[:n]
|
||||
want := c.Out
|
||||
got := c.Regions
|
||||
for _, r := range c.NewRegions {
|
||||
restorable.AppendRegionRemovingDuplicates(&got, r)
|
||||
}
|
||||
want := c.Expected
|
||||
if !areEqualRectangles(got, want) {
|
||||
t.Errorf("restorable.RemoveDuplicatedRegions(%#v): got: %#v, want: %#v", c.In, got, want)
|
||||
t.Errorf("restorable.RemoveDuplicatedRegions(%#v): got: %#v, want: %#v", c.NewRegions, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user