From 8cf3c31cf6da12f03576a58bc5e7b4fc7c0ba708 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Thu, 6 Oct 2022 12:11:43 +0900 Subject: [PATCH] internal/restorable: reuse bytes instead of allocations Updates #2375 --- internal/restorable/image.go | 64 ++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/internal/restorable/image.go b/internal/restorable/image.go index 3951eefc1..68d10b0ec 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -70,6 +70,33 @@ func (p *Pixels) Region() image.Rectangle { return p.pixelsRecords.region() } +// ReturnReusableBytesAndReset tries to return a require size of a byte slice and resets its state. +// The returned byte slice is not zero-cleared. +// ReturnReusableBytesAndReset might return nil if p doesn't have a big enough size of a slice. +func (p *Pixels) ReturnReusableBytesAndReset(n int) []byte { + if p.pixelsRecords == nil { + return nil + } + + defer func() { + p.pixelsRecords = nil + }() + + if n == 0 { + return nil + } + + // Search for a reusable byte slice and reuse it to reduce allocations (#2375). + var pix []byte + for _, r := range p.pixelsRecords.records { + if len(r.pix) >= n { + pix = r.pix[:n] + break + } + } + return pix +} + // drawTrianglesHistoryItem is an item for history of draw-image commands. type drawTrianglesHistoryItem struct { images [graphics.ShaderImageCount]*Image @@ -317,11 +344,14 @@ func (i *Image) WritePixels(pixels []byte, x, y, width, height int) { if x == 0 && y == 0 && width == w && height == h { if pixels != nil { - // pixels can point to a shared region. - // This function is responsible to copy this. - copiedPixels := make([]byte, len(pixels)) - copy(copiedPixels, pixels) - i.basePixels.AddOrReplace(copiedPixels, 0, 0, w, h) + // pixels can point to a shared region. This function is responsible to copy this. + // As the bytePixels will be reset by writing the entire region, an existing byte slice is reusable. + newPixels := i.basePixels.ReturnReusableBytesAndReset(len(pixels)) + if newPixels == nil { + newPixels = make([]byte, len(pixels)) + } + copy(newPixels, pixels) + i.basePixels.AddOrReplace(newPixels, 0, 0, w, h) } else { i.basePixels.Clear(0, 0, w, h) } @@ -338,11 +368,10 @@ func (i *Image) WritePixels(pixels []byte, x, y, width, height int) { } if pixels != nil { - // pixels can point to a shared region. - // This function is responsible to copy this. - copiedPixels := make([]byte, len(pixels)) - copy(copiedPixels, pixels) - i.basePixels.AddOrReplace(copiedPixels, x, y, width, height) + // pixels can point to a shared region. This function is responsible to copy this. + newPixels := make([]byte, len(pixels)) + copy(newPixels, pixels) + i.basePixels.AddOrReplace(newPixels, x, y, width, height) } else { i.basePixels.Clear(x, y, width, height) } @@ -475,18 +504,23 @@ func (i *Image) makeStaleIfDependingOnShader(shader *Shader) { // readPixelsFromGPU reads the pixels from GPU and resolves the image's 'stale' state. func (i *Image) readPixelsFromGPU(graphicsDriver graphicsdriver.Graphics) error { - i.basePixels = Pixels{} r := i.staleRegion if len(i.drawTrianglesHistory) > 0 { r = image.Rect(0, 0, i.width, i.height) } + + req := 4 * r.Dx() * r.Dy() + pix := i.basePixels.ReturnReusableBytesAndReset(req) if !r.Empty() { - pix := make([]byte, 4*r.Dx()*r.Dy()) + if pix == nil { + pix = make([]byte, req) + } if err := i.image.ReadPixels(graphicsDriver, pix, r.Min.X, r.Min.Y, r.Dx(), r.Dy()); err != nil { return err } i.basePixels.AddOrReplace(pix, r.Min.X, r.Min.Y, r.Dx(), r.Dy()) } + i.clearDrawTrianglesHistory() i.stale = false i.staleRegion = image.Rectangle{} @@ -603,8 +637,10 @@ func (i *Image) restore(graphicsDriver graphicsdriver.Graphics) error { } if len(i.drawTrianglesHistory) > 0 { - i.basePixels = Pixels{} - pix := make([]byte, 4*w*h) + pix := i.basePixels.ReturnReusableBytesAndReset(4 * w * h) + if pix == nil { + pix = make([]byte, 4*w*h) + } if err := gimg.ReadPixels(graphicsDriver, pix, 0, 0, w, h); err != nil { return err }