mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-27 03:02:49 +01:00
buffered: Remove mutex and use sync/atomic for performance
This change also enables to remove the optimization at (*buffered.Image).ReplacePixels. // This commit w/ the optimization BenchmarkImageDrawOver-8 60225 19241 ns/op // This commit w/o the optimization BenchmarkImageDrawOver-8 66567 17700 ns/op // The previous w/ the optimization BenchmarkImageDrawOver-8 62355 19580 ns/op // The previous w/o the optimization BenchmarkImageDrawOver-8 54460 22768 ns/op Updates #1137
This commit is contained in:
parent
137663c0df
commit
96fa0565e4
@ -2056,3 +2056,12 @@ func TestImageDrawTrianglesDisposedImage(t *testing.T) {
|
|||||||
is := []uint16{0, 1, 2, 1, 2, 3}
|
is := []uint16{0, 1, 2, 1, 2, 3}
|
||||||
dst.DrawTriangles(vs, is, src, nil)
|
dst.DrawTriangles(vs, is, src, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #1137
|
||||||
|
func BenchmarkImageDrawOver(b *testing.B) {
|
||||||
|
dst, _ := NewImage(16, 16, FilterDefault)
|
||||||
|
src := image.NewUniform(color.Black)
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
draw.Draw(dst, dst.Bounds(), src, image.ZP, draw.Over)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,17 +15,16 @@
|
|||||||
package buffered
|
package buffered
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
needsToDelayCommands = true
|
needsToDelayCommandsV int32 = 1
|
||||||
|
|
||||||
// delayedCommands represents a queue for image operations that are ordered before the game starts
|
// delayedCommands represents a queue for image operations that are ordered before the game starts
|
||||||
// (BeginFrame). Before the game starts, the package shareable doesn't determine the minimum/maximum texture
|
// (BeginFrame). Before the game starts, the package shareable doesn't determine the minimum/maximum texture
|
||||||
// sizes (#879).
|
// sizes (#879).
|
||||||
delayedCommands []func() error
|
delayedCommands []func() error
|
||||||
delayedCommandsM sync.Mutex
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func flushDelayedCommands() error {
|
func flushDelayedCommands() error {
|
||||||
@ -40,12 +39,14 @@ func flushDelayedCommands() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getDelayedFuncsAndClear() []func() error {
|
func getDelayedFuncsAndClear() []func() error {
|
||||||
delayedCommandsM.Lock()
|
atomic.StoreInt32(&needsToDelayCommandsV, 0)
|
||||||
defer delayedCommandsM.Unlock()
|
|
||||||
|
|
||||||
fs := make([]func() error, len(delayedCommands))
|
fs := make([]func() error, len(delayedCommands))
|
||||||
copy(fs, delayedCommands)
|
copy(fs, delayedCommands)
|
||||||
delayedCommands = nil
|
delayedCommands = nil
|
||||||
needsToDelayCommands = false
|
|
||||||
return fs
|
return fs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func needsToDelayCommands() bool {
|
||||||
|
return atomic.LoadInt32(&needsToDelayCommandsV) != 0
|
||||||
|
}
|
||||||
|
@ -55,10 +55,7 @@ func NewImage(width, height int, volatile bool) *Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) initialize(width, height int, volatile bool) {
|
func (i *Image) initialize(width, height int, volatile bool) {
|
||||||
delayedCommandsM.Lock()
|
if needsToDelayCommands() {
|
||||||
defer delayedCommandsM.Unlock()
|
|
||||||
|
|
||||||
if needsToDelayCommands {
|
|
||||||
delayedCommands = append(delayedCommands, func() error {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.initialize(width, height, volatile)
|
i.initialize(width, height, volatile)
|
||||||
return nil
|
return nil
|
||||||
@ -77,10 +74,7 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) initializeAsScreenFramebuffer(width, height int) {
|
func (i *Image) initializeAsScreenFramebuffer(width, height int) {
|
||||||
delayedCommandsM.Lock()
|
if needsToDelayCommands() {
|
||||||
defer delayedCommandsM.Unlock()
|
|
||||||
|
|
||||||
if needsToDelayCommands {
|
|
||||||
delayedCommands = append(delayedCommands, func() error {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.initializeAsScreenFramebuffer(width, height)
|
i.initializeAsScreenFramebuffer(width, height)
|
||||||
return nil
|
return nil
|
||||||
@ -122,10 +116,7 @@ func (i *Image) resolvePendingFill() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) MarkDisposed() {
|
func (i *Image) MarkDisposed() {
|
||||||
delayedCommandsM.Lock()
|
if needsToDelayCommands() {
|
||||||
defer delayedCommandsM.Unlock()
|
|
||||||
|
|
||||||
if needsToDelayCommands {
|
|
||||||
delayedCommands = append(delayedCommands, func() error {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.MarkDisposed()
|
i.MarkDisposed()
|
||||||
return nil
|
return nil
|
||||||
@ -137,9 +128,7 @@ func (i *Image) MarkDisposed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) {
|
func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) {
|
||||||
delayedCommandsM.Lock()
|
if needsToDelayCommands() {
|
||||||
defer delayedCommandsM.Unlock()
|
|
||||||
if needsToDelayCommands {
|
|
||||||
panic("buffered: the command queue is not available yet at At")
|
panic("buffered: the command queue is not available yet at At")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,19 +165,14 @@ func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Dump(name string, blackbg bool) error {
|
func (i *Image) Dump(name string, blackbg bool) error {
|
||||||
delayedCommandsM.Lock()
|
if needsToDelayCommands() {
|
||||||
defer delayedCommandsM.Unlock()
|
|
||||||
if needsToDelayCommands {
|
|
||||||
panic("buffered: the command queue is not available yet at Dump")
|
panic("buffered: the command queue is not available yet at Dump")
|
||||||
}
|
}
|
||||||
return i.img.Dump(name, blackbg)
|
return i.img.Dump(name, blackbg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Fill(clr color.RGBA) {
|
func (i *Image) Fill(clr color.RGBA) {
|
||||||
delayedCommandsM.Lock()
|
if needsToDelayCommands() {
|
||||||
defer delayedCommandsM.Unlock()
|
|
||||||
|
|
||||||
if needsToDelayCommands {
|
|
||||||
delayedCommands = append(delayedCommands, func() error {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.Fill(clr)
|
i.Fill(clr)
|
||||||
return nil
|
return nil
|
||||||
@ -207,23 +191,7 @@ func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) error {
|
|||||||
panic(fmt.Sprintf("buffered: len(pix) was %d but must be %d", len(pix), l))
|
panic(fmt.Sprintf("buffered: len(pix) was %d but must be %d", len(pix), l))
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is an optimization to avoid mutex for the case when ReplacePixels is called very often (e.g., Set).
|
if needsToDelayCommands() {
|
||||||
// If i.pixels is not nil, delayed commands have already been flushed.
|
|
||||||
// needsToDelayCommands should be false, but we don't check it because this is out of the mutex lock.
|
|
||||||
// (#1137)
|
|
||||||
if i.pixels != nil {
|
|
||||||
// If the region is the whole image, don't use this optimization, or more memory is consumed by
|
|
||||||
// keeping pixels.
|
|
||||||
if !(x == 0 && y == 0 && width == i.width && height == i.height) {
|
|
||||||
i.replacePendingPixels(pix, x, y, width, height)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delayedCommandsM.Lock()
|
|
||||||
defer delayedCommandsM.Unlock()
|
|
||||||
|
|
||||||
if needsToDelayCommands {
|
|
||||||
copied := make([]byte, len(pix))
|
copied := make([]byte, len(pix))
|
||||||
copy(copied, pix)
|
copy(copied, pix)
|
||||||
delayedCommands = append(delayedCommands, func() error {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
@ -274,10 +242,7 @@ func (i *Image) DrawImage(src *Image, bounds image.Rectangle, a, b, c, d, tx, ty
|
|||||||
Ty: ty,
|
Ty: ty,
|
||||||
}
|
}
|
||||||
|
|
||||||
delayedCommandsM.Lock()
|
if needsToDelayCommands() {
|
||||||
defer delayedCommandsM.Unlock()
|
|
||||||
|
|
||||||
if needsToDelayCommands {
|
|
||||||
delayedCommands = append(delayedCommands, func() error {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
i.drawImage(src, bounds, g, colorm, mode, filter)
|
i.drawImage(src, bounds, g, colorm, mode, filter)
|
||||||
return nil
|
return nil
|
||||||
@ -314,10 +279,7 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delayedCommandsM.Lock()
|
if needsToDelayCommands() {
|
||||||
defer delayedCommandsM.Unlock()
|
|
||||||
|
|
||||||
if needsToDelayCommands {
|
|
||||||
delayedCommands = append(delayedCommands, func() error {
|
delayedCommands = append(delayedCommands, func() error {
|
||||||
// Arguments are not copied. Copying is the caller's responsibility.
|
// Arguments are not copied. Copying is the caller's responsibility.
|
||||||
i.DrawTriangles(src, vertices, indices, colorm, mode, filter, address, shader, uniforms)
|
i.DrawTriangles(src, vertices, indices, colorm, mode, filter, address, shader, uniforms)
|
||||||
|
Loading…
Reference in New Issue
Block a user