internal/restorable: simplify the implementation of rect

Allow overlapped regions at pixelsRecords.

This removes the members `last*` for caching. These were introduced
at 7e7751bd43, and apparently these
are no longer needed.
This commit is contained in:
Hajime Hoshi 2022-03-20 23:00:00 +09:00
parent ea81d4abf4
commit c1a0d83f8d
3 changed files with 169 additions and 81 deletions

View File

@ -25,38 +25,38 @@ import (
)
type Pixels struct {
rectToPixels *rectToPixels
pixelsRecords *pixelsRecords
}
// Apply applies the Pixels state to the given image especially for restoring.
func (p *Pixels) Apply(img *graphicscommand.Image) {
// Pixels doesn't clear the image. This is a caller's responsibility.
if p.rectToPixels == nil {
if p.pixelsRecords == nil {
return
}
p.rectToPixels.apply(img)
p.pixelsRecords.apply(img)
}
func (p *Pixels) AddOrReplace(pix []byte, x, y, width, height int) {
if p.rectToPixels == nil {
p.rectToPixels = &rectToPixels{}
if p.pixelsRecords == nil {
p.pixelsRecords = &pixelsRecords{}
}
p.rectToPixels.addOrReplace(pix, x, y, width, height)
p.pixelsRecords.addOrReplace(pix, x, y, width, height)
}
func (p *Pixels) Remove(x, y, width, height int) {
func (p *Pixels) Clear(x, y, width, height int) {
// Note that we don't care whether the region is actually removed or not here. There is an actual case that
// the region is allocated but nothing is rendered. See TestDisposeImmediately at shareable package.
if p.rectToPixels == nil {
if p.pixelsRecords == nil {
return
}
p.rectToPixels.remove(x, y, width, height)
p.pixelsRecords.clear(x, y, width, height)
}
func (p *Pixels) At(i, j int) (byte, byte, byte, byte) {
if p.rectToPixels != nil {
if r, g, b, a, ok := p.rectToPixels.at(i, j); ok {
if p.pixelsRecords != nil {
if r, g, b, a, ok := p.pixelsRecords.at(i, j); ok {
return r, g, b, a
}
}
@ -325,7 +325,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
copy(copiedPixels, pixels)
i.basePixels.AddOrReplace(copiedPixels, 0, 0, w, h)
} else {
i.basePixels.Remove(0, 0, w, h)
i.basePixels.Clear(0, 0, w, h)
}
i.clearDrawTrianglesHistory()
i.stale = false
@ -349,7 +349,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
copy(copiedPixels, pixels)
i.basePixels.AddOrReplace(copiedPixels, x, y, width, height)
} else {
i.basePixels.Remove(x, y, width, height)
i.basePixels.Clear(x, y, width, height)
}
}

View File

@ -918,3 +918,88 @@ func TestMutateSlices(t *testing.T) {
}
}
}
func TestOverlappedPixels(t *testing.T) {
dst := restorable.NewImage(3, 3)
pix0 := make([]byte, 4*2*2)
for j := 0; j < 2; j++ {
for i := 0; i < 2; i++ {
idx := 4 * (j*2 + i)
pix0[idx] = 0xff
pix0[idx+1] = 0
pix0[idx+2] = 0
pix0[idx+3] = 0xff
}
}
pix1 := make([]byte, 4*2*2)
for j := 0; j < 2; j++ {
for i := 0; i < 2; i++ {
idx := 4 * (j*2 + i)
pix1[idx] = 0
pix1[idx+1] = 0xff
pix1[idx+2] = 0
pix1[idx+3] = 0xff
}
}
dst.ReplacePixels(pix0, 0, 0, 2, 2)
dst.ReplacePixels(pix1, 1, 1, 2, 2)
wantColors := []color.RGBA{
{0xff, 0, 0, 0xff},
{0xff, 0, 0, 0xff},
{0, 0, 0, 0},
{0xff, 0, 0, 0xff},
{0, 0xff, 0, 0xff},
{0, 0xff, 0, 0xff},
{0, 0, 0, 0},
{0, 0xff, 0, 0xff},
{0, 0xff, 0, 0xff},
}
for j := 0; j < 3; j++ {
for i := 0; i < 3; i++ {
r, g, b, a, err := dst.At(ui.GraphicsDriverForTesting(), i, j)
if err != nil {
t.Fatal(err)
}
got := color.RGBA{r, g, b, a}
want := wantColors[3*j+i]
if got != want {
t.Errorf("color at (%d, %d): got %v, want: %v", i, j, got, want)
}
}
}
dst.ReplacePixels(nil, 1, 0, 2, 2)
wantColors = []color.RGBA{
{0xff, 0, 0, 0xff},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0xff, 0, 0, 0xff},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0xff, 0, 0xff},
{0, 0xff, 0, 0xff},
}
for j := 0; j < 3; j++ {
for i := 0; i < 3; i++ {
r, g, b, a, err := dst.At(ui.GraphicsDriverForTesting(), i, j)
if err != nil {
t.Fatal(err)
}
got := color.RGBA{r, g, b, a}
want := wantColors[3*j+i]
if got != want {
t.Errorf("color at (%d, %d): got %v, want: %v", i, j, got, want)
}
}
}
}

View File

@ -21,14 +21,32 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
)
type rectToPixels struct {
m map[image.Rectangle][]byte
lastR image.Rectangle
lastPix []byte
type pixelsRecord struct {
rect image.Rectangle
pix []byte
}
func (rtp *rectToPixels) addOrReplace(pixels []byte, x, y, width, height int) {
func (p *pixelsRecord) ClearIfOverlapped(rect image.Rectangle) {
r := p.rect.Intersect(rect)
ox := r.Min.X - p.rect.Min.X
oy := r.Min.Y - p.rect.Min.Y
w := p.rect.Dx()
for j := 0; j < r.Dy(); j++ {
for i := 0; i < r.Dx(); i++ {
idx := 4 * ((oy+j)*w + ox + i)
p.pix[idx] = 0
p.pix[idx+1] = 0
p.pix[idx+2] = 0
p.pix[idx+3] = 0
}
}
}
type pixelsRecords struct {
records []*pixelsRecord
}
func (pr *pixelsRecords) addOrReplace(pixels []byte, x, y, width, height int) {
if len(pixels) != 4*width*height {
msg := fmt.Sprintf("restorable: len(pixels) must be 4*%d*%d = %d but %d", width, height, 4*width*height, len(pixels))
if pixels == nil {
@ -37,83 +55,68 @@ func (rtp *rectToPixels) addOrReplace(pixels []byte, x, y, width, height int) {
panic(msg)
}
if rtp.m == nil {
rtp.m = map[image.Rectangle][]byte{}
}
newr := image.Rect(x, y, x+width, y+height)
for r := range rtp.m {
if r == newr {
// Replace the region.
rtp.m[r] = pixels
if r == rtp.lastR {
rtp.lastPix = pixels
}
return
}
if r.Overlaps(newr) {
panic(fmt.Sprintf("restorable: region (%#v) conflicted with the other region (%#v)", newr, r))
// Remove or update the duplicated records first.
rect := image.Rect(x, y, x+width, y+height)
var n int
for _, r := range pr.records {
if r.rect.In(rect) {
continue
}
pr.records[n] = r
n++
}
for i := n; i < len(pr.records); i++ {
pr.records[i] = nil
}
pr.records = pr.records[:n]
// Add the region.
rtp.m[newr] = pixels
if newr == rtp.lastR {
rtp.lastPix = pixels
}
// Add the new record.
pr.records = append(pr.records, &pixelsRecord{
rect: rect,
pix: pixels,
})
}
func (rtp *rectToPixels) remove(x, y, width, height int) {
if rtp.m == nil {
return
}
func (pr *pixelsRecords) clear(x, y, width, height int) {
newr := image.Rect(x, y, x+width, y+height)
for r := range rtp.m {
if r == newr {
delete(rtp.m, r)
return
var n int
for _, r := range pr.records {
if r.rect.In(newr) {
continue
}
r.ClearIfOverlapped(newr)
pr.records[n] = r
n++
}
for i := n; i < len(pr.records); i++ {
pr.records[i] = nil
}
pr.records = pr.records[:n]
}
func (rtp *rectToPixels) at(i, j int) (byte, byte, byte, byte, bool) {
if rtp.m == nil {
func (pr *pixelsRecords) at(i, j int) (byte, byte, byte, byte, bool) {
var record *pixelsRecord
pt := image.Pt(i, j)
// Traverse the slice in the reversed order.
for i := len(pr.records) - 1; i >= 0; i-- {
p := pr.records[i]
if pt.In(p.rect) {
record = p
break
}
}
if record == nil {
return 0, 0, 0, 0, false
}
var pix []byte
var r image.Rectangle
var found bool
if pt := image.Pt(i, j); pt.In(rtp.lastR) {
r = rtp.lastR
found = true
pix = rtp.lastPix
} else {
for rr := range rtp.m {
if pt.In(rr) {
r = rr
found = true
rtp.lastR = rr
pix = rtp.m[rr]
rtp.lastPix = pix
break
}
}
}
if !found {
return 0, 0, 0, 0, false
}
idx := 4 * ((j-r.Min.Y)*r.Dx() + (i - r.Min.X))
return pix[idx], pix[idx+1], pix[idx+2], pix[idx+3], true
idx := 4 * ((j-record.rect.Min.Y)*record.rect.Dx() + (i - record.rect.Min.X))
return record.pix[idx], record.pix[idx+1], record.pix[idx+2], record.pix[idx+3], true
}
func (rtp *rectToPixels) apply(img *graphicscommand.Image) {
func (pr *pixelsRecords) apply(img *graphicscommand.Image) {
// TODO: Isn't this too heavy? Can we merge the operations?
for r, pix := range rtp.m {
img.ReplacePixels(pix, r.Min.X, r.Min.Y, r.Dx(), r.Dy())
for _, r := range pr.records {
img.ReplacePixels(r.pix, r.rect.Min.X, r.rect.Min.Y, r.rect.Dx(), r.rect.Dy())
}
}