mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +01:00
internal/restorable: use clearImage to avoid allocations
Bytes from a pool in a command queue is now pretty hard to use correctly as the lifetime of a queue is not clear. Remove the byte pools once. Let's reconsider pool usages later. This change also removes imagesWithBuffers as this is no longer needed. imagesWithBuffers was necessary to ensure all the bytes from the pool of the command queue was used before the queue flushes the commands, as the command queue cleared the pool after flushing. The lifetimes were pretty ticky.
This commit is contained in:
parent
cc8cf688f4
commit
6e5361c328
@ -22,7 +22,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/packing"
|
"github.com/hajimehoshi/ebiten/v2/internal/packing"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
||||||
@ -483,13 +482,15 @@ func (i *Image) writePixels(pix []byte, region image.Rectangle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy pixels in the case when pix is modified before the graphics command is executed.
|
// Copy pixels in the case when pix is modified before the graphics command is executed.
|
||||||
pix2 := graphicscommand.AllocBytes(len(pix))
|
// TODO: Create byte slices from a pool.
|
||||||
|
pix2 := make([]byte, len(pix))
|
||||||
copy(pix2, pix)
|
copy(pix2, pix)
|
||||||
i.backend.restorable.WritePixels(pix2, region)
|
i.backend.restorable.WritePixels(pix2, region)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pixb := graphicscommand.AllocBytes(4 * r.Dx() * r.Dy())
|
// TODO: Create byte slices from a pool.
|
||||||
|
pixb := make([]byte, 4*r.Dx()*r.Dy())
|
||||||
|
|
||||||
// Clear the edges. pixb might not be zero-cleared.
|
// Clear the edges. pixb might not be zero-cleared.
|
||||||
// TODO: These loops assume that paddingSize is 1.
|
// TODO: These loops assume that paddingSize is 1.
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
// Copyright 2023 The Ebitengine Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package graphicscommand
|
|
||||||
|
|
||||||
type temporaryBytes struct {
|
|
||||||
pixels []byte
|
|
||||||
pos int
|
|
||||||
notFullyUsedTime int
|
|
||||||
}
|
|
||||||
|
|
||||||
func temporaryBytesSize(size int) int {
|
|
||||||
l := 16
|
|
||||||
for l < size {
|
|
||||||
l *= 2
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// alloc allocates the pixels and returns it.
|
|
||||||
//
|
|
||||||
// Be careful that the returned pixels might not be zero-cleared.
|
|
||||||
func (t *temporaryBytes) alloc(size int) []byte {
|
|
||||||
if len(t.pixels) < t.pos+size {
|
|
||||||
t.pixels = make([]byte, max(len(t.pixels)*2, temporaryBytesSize(size)))
|
|
||||||
t.pos = 0
|
|
||||||
}
|
|
||||||
pix := t.pixels[t.pos : t.pos+size]
|
|
||||||
t.pos += size
|
|
||||||
return pix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *temporaryBytes) reset() {
|
|
||||||
// reset is called in a render thread.
|
|
||||||
// When reset is called, a queue is being flushed in a render thread, and the queue is never used in the game thread.
|
|
||||||
// Thus, a mutex lock is not needed in alloc and reset.
|
|
||||||
|
|
||||||
const maxNotFullyUsedTime = 60
|
|
||||||
|
|
||||||
if temporaryBytesSize(t.pos) < len(t.pixels) {
|
|
||||||
if t.notFullyUsedTime < maxNotFullyUsedTime {
|
|
||||||
t.notFullyUsedTime++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.notFullyUsedTime = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let the pixels GCed if this is not used for a while.
|
|
||||||
if t.notFullyUsedTime == maxNotFullyUsedTime && len(t.pixels) > 0 {
|
|
||||||
t.pixels = nil
|
|
||||||
t.notFullyUsedTime = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the position and reuse the allocated bytes.
|
|
||||||
// t.pixels should already be sent to GPU, then this can be reused.
|
|
||||||
t.pos = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllocBytes allocates bytes from the cache.
|
|
||||||
//
|
|
||||||
// Be careful that the returned pixels might not be zero-cleared.
|
|
||||||
func AllocBytes(size int) []byte {
|
|
||||||
return theCommandQueueManager.allocBytes(size)
|
|
||||||
}
|
|
@ -30,7 +30,6 @@ import (
|
|||||||
// FlushCommands flushes the command queue and present the screen if needed.
|
// FlushCommands flushes the command queue and present the screen if needed.
|
||||||
// If endFrame is true, the current screen might be used to present.
|
// If endFrame is true, the current screen might be used to present.
|
||||||
func FlushCommands(graphicsDriver graphicsdriver.Graphics, endFrame bool, swapBuffersForGL func()) error {
|
func FlushCommands(graphicsDriver graphicsdriver.Graphics, endFrame bool, swapBuffersForGL func()) error {
|
||||||
flushImageBuffers()
|
|
||||||
if err := theCommandQueueManager.flush(graphicsDriver, endFrame, swapBuffersForGL); err != nil {
|
if err := theCommandQueueManager.flush(graphicsDriver, endFrame, swapBuffersForGL); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -52,8 +51,6 @@ type commandQueue struct {
|
|||||||
|
|
||||||
uint32sBuffer uint32sBuffer
|
uint32sBuffer uint32sBuffer
|
||||||
|
|
||||||
temporaryBytes temporaryBytes
|
|
||||||
|
|
||||||
err atomic.Value
|
err atomic.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +220,6 @@ func (q *commandQueue) flush(graphicsDriver graphicsdriver.Graphics, endFrame bo
|
|||||||
|
|
||||||
if endFrame {
|
if endFrame {
|
||||||
q.uint32sBuffer.reset()
|
q.uint32sBuffer.reset()
|
||||||
q.temporaryBytes.reset()
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -441,13 +437,6 @@ type commandQueueManager struct {
|
|||||||
|
|
||||||
var theCommandQueueManager commandQueueManager
|
var theCommandQueueManager commandQueueManager
|
||||||
|
|
||||||
func (c *commandQueueManager) allocBytes(size int) []byte {
|
|
||||||
if c.current == nil {
|
|
||||||
c.current, _ = c.pool.get()
|
|
||||||
}
|
|
||||||
return c.current.temporaryBytes.alloc(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *commandQueueManager) enqueueCommand(command command) {
|
func (c *commandQueueManager) enqueueCommand(command command) {
|
||||||
if c.current == nil {
|
if c.current == nil {
|
||||||
c.current, _ = c.pool.get()
|
c.current, _ = c.pool.get()
|
||||||
|
@ -54,31 +54,6 @@ func genNextID() int {
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
// imagesWithBuffers is the set of an image with buffers.
|
|
||||||
var imagesWithBuffers = map[*Image]struct{}{}
|
|
||||||
|
|
||||||
// addImageWithBuffer adds an image to the list of images with unflushed buffers.
|
|
||||||
func addImageWithBuffer(img *Image) {
|
|
||||||
imagesWithBuffers[img] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeImageWithBuffer removes an image from the list of images with unflushed buffers.
|
|
||||||
func removeImageWithBuffer(img *Image) {
|
|
||||||
delete(imagesWithBuffers, img)
|
|
||||||
}
|
|
||||||
|
|
||||||
// flushImageBuffers flushes all the image buffers and send to the command queue.
|
|
||||||
// flushImageBuffers should be called before flushing commands.
|
|
||||||
func flushImageBuffers() {
|
|
||||||
for img := range imagesWithBuffers {
|
|
||||||
img.flushBufferedWritePixels()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(imagesWithBuffers) != 0 {
|
|
||||||
panic("graphicscommand: len(imagesWithBuffers) must be empty after flushing")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewImage returns a new image.
|
// NewImage returns a new image.
|
||||||
//
|
//
|
||||||
// Note that the image is not initialized yet.
|
// Note that the image is not initialized yet.
|
||||||
@ -110,8 +85,6 @@ func (i *Image) flushBufferedWritePixels() {
|
|||||||
theCommandQueueManager.enqueueCommand(c)
|
theCommandQueueManager.enqueueCommand(c)
|
||||||
|
|
||||||
i.bufferedWritePixelsArgs = nil
|
i.bufferedWritePixelsArgs = nil
|
||||||
|
|
||||||
removeImageWithBuffer(i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Dispose() {
|
func (i *Image) Dispose() {
|
||||||
@ -120,8 +93,6 @@ func (i *Image) Dispose() {
|
|||||||
target: i,
|
target: i,
|
||||||
}
|
}
|
||||||
theCommandQueueManager.enqueueCommand(c)
|
theCommandQueueManager.enqueueCommand(c)
|
||||||
|
|
||||||
removeImageWithBuffer(i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) InternalSize() (int, int) {
|
func (i *Image) InternalSize() (int, int) {
|
||||||
@ -193,7 +164,6 @@ func (i *Image) WritePixels(pixels []byte, region image.Rectangle) {
|
|||||||
Pixels: pixels,
|
Pixels: pixels,
|
||||||
Region: region,
|
Region: region,
|
||||||
})
|
})
|
||||||
addImageWithBuffer(i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) IsInvalidated(graphicsDriver graphicsdriver.Graphics) (bool, error) {
|
func (i *Image) IsInvalidated(graphicsDriver graphicsdriver.Graphics) (bool, error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user