ebiten: Remove copying pixels from ReplacePixels and copyImage (renamed to imageToBytes)

This optimization utilizes the fact that copying happens in the
'shareable' package to add paddings.

Updates #1222
This commit is contained in:
Hajime Hoshi 2020-07-03 02:56:37 +09:00
parent f7757ae025
commit 02ef92f4cd
5 changed files with 45 additions and 31 deletions

View File

@ -15,5 +15,5 @@
package ebiten
var (
CopyImage = copyImage
ImageToBytes = imageToBytes
)

View File

@ -540,7 +540,7 @@ func (i *Image) Dispose() error {
// When the image is disposed, ReplacePixels does nothing.
//
// ReplacePixels always returns nil as of 1.5.0.
func (i *Image) ReplacePixels(pix []byte) error {
func (i *Image) ReplacePixels(pixels []byte) error {
i.copyCheck()
if i.isDisposed() {
@ -548,12 +548,10 @@ func (i *Image) ReplacePixels(pix []byte) error {
}
r := i.Bounds()
// Copy the pixels as restorable package might reuse the pixels later.
// TODO: Would it be possible to avoid copying? (#1222)
copied := make([]byte, len(pix))
copy(copied, pix)
if err := i.buffered.ReplacePixels(copied, r.Min.X, r.Min.Y, r.Dx(), r.Dy()); err != nil {
// Do not need to copy pixels here.
// * In internal/buffered, pixels are copied when necessary.
// * In internal/shareable, pixels are copied to make its paddings.
if err := i.buffered.ReplacePixels(pixels, r.Min.X, r.Min.Y, r.Dx(), r.Dy()); err != nil {
theUIContext.setError(err)
}
return nil
@ -637,10 +635,7 @@ func NewImageFromImage(source image.Image, filter Filter) (*Image, error) {
}
i.addr = i
// Call (*buffered.Image).ReplacePixels directly to avoid copying.
if err := i.buffered.ReplacePixels(copyImage(source), 0, 0, width, height); err != nil {
theUIContext.setError(err)
}
i.ReplacePixels(imageToBytes(source))
return i, nil
}

View File

@ -20,17 +20,20 @@ import (
"image/draw"
)
// copyImage copies img to a new RGBA image.
// imageToBytes gets RGBA bytes from img.
//
// Basically copyImage just calls draw.Draw.
// Basically imageToBytes just calls draw.Draw.
// If img is a paletted image, an optimized copying method is used.
func copyImage(img image.Image) []byte {
//
// If img is *image.RGBA and its length is same as 4*width*height, imageToBytes returns its Pix.
func imageToBytes(img image.Image) []byte {
size := img.Bounds().Size()
w, h := size.X, size.Y
bs := make([]byte, 4*w*h)
switch img := img.(type) {
case *image.Paletted:
bs := make([]byte, 4*w*h)
b := img.Bounds()
x0 := b.Min.X
y0 := b.Min.Y
@ -61,13 +64,27 @@ func copyImage(img image.Image) []byte {
}
idx0 += d
}
default:
dstImg := &image.RGBA{
Pix: bs,
Stride: 4 * w,
Rect: image.Rect(0, 0, w, h),
return bs
case *image.RGBA:
if len(img.Pix) == 4*w*h {
return img.Pix
}
draw.Draw(dstImg, image.Rect(0, 0, w, h), img, img.Bounds().Min, draw.Src)
return imageToBytesSlow(img)
default:
return imageToBytesSlow(img)
}
}
func imageToBytesSlow(img image.Image) []byte {
size := img.Bounds().Size()
w, h := size.X, size.Y
bs := make([]byte, 4*w*h)
dstImg := &image.RGBA{
Pix: bs,
Stride: 4 * w,
Rect: image.Rect(0, 0, w, h),
}
draw.Draw(dstImg, image.Rect(0, 0, w, h), img, img.Bounds().Min, draw.Src)
return bs
}

View File

@ -24,7 +24,7 @@ import (
. "github.com/hajimehoshi/ebiten"
)
func TestCopyImage(t *testing.T) {
func TestImageToBytes(t *testing.T) {
pal := make(color.Palette, 256)
for i := range pal {
pal[i] = color.White
@ -93,7 +93,7 @@ func TestCopyImage(t *testing.T) {
},
}
for i, c := range cases {
got := CopyImage(c.In)
got := ImageToBytes(c.In)
want := c.Out
if !bytes.Equal(got, want) {
t.Errorf("Test %d: got: %v, want: %v", i, got, want)
@ -101,26 +101,26 @@ func TestCopyImage(t *testing.T) {
}
}
func BenchmarkCopyImageRGBA(b *testing.B) {
func BenchmarkImageToBytesRGBA(b *testing.B) {
img := image.NewRGBA(image.Rect(0, 0, 4096, 4096))
b.ResetTimer()
for i := 0; i < b.N; i++ {
CopyImage(img)
ImageToBytes(img)
}
}
func BenchmarkCopyImageNRGBA(b *testing.B) {
func BenchmarkImageToBytesNRGBA(b *testing.B) {
img := image.NewNRGBA(image.Rect(0, 0, 4096, 4096))
b.ResetTimer()
for i := 0; i < b.N; i++ {
CopyImage(img)
ImageToBytes(img)
}
}
func BenchmarkCopyImagePaletted(b *testing.B) {
func BenchmarkImageToBytesPaletted(b *testing.B) {
img := image.NewPaletted(image.Rect(0, 0, 4096, 4096), palette.Plan9)
b.ResetTimer()
for i := 0; i < b.N; i++ {
CopyImage(img)
ImageToBytes(img)
}
}

View File

@ -192,8 +192,10 @@ func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) error {
}
if maybeCanAddDelayedCommand() {
copied := make([]byte, len(pix))
copy(copied, pix)
if tryAddDelayedCommand(func() error {
i.ReplacePixels(pix, x, y, width, height)
i.ReplacePixels(copied, x, y, width, height)
return nil
}) {
return nil