diff --git a/internal/buffered/command.go b/internal/buffered/command.go index 59fb80908..cf4f39bf5 100644 --- a/internal/buffered/command.go +++ b/internal/buffered/command.go @@ -15,16 +15,18 @@ package buffered import ( + "sync" "sync/atomic" ) var ( - needsToDelayCommandsV int32 = 1 - // 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 // sizes (#879). - delayedCommands []func() error + delayedCommands = []func() error{} + + delayedCommandsM sync.Mutex + delayedCommandsFlushed uint32 ) func flushDelayedCommands() error { @@ -39,14 +41,56 @@ func flushDelayedCommands() error { } func getDelayedFuncsAndClear() []func() error { - atomic.StoreInt32(&needsToDelayCommandsV, 0) - - fs := make([]func() error, len(delayedCommands)) - copy(fs, delayedCommands) - delayedCommands = nil - return fs + if atomic.LoadUint32(&delayedCommandsFlushed) == 0 { + // Outline the slow-path to expect the fast-path is inlined. + return getDelayedFuncsAndClearSlow() + } + return nil } -func needsToDelayCommands() bool { - return atomic.LoadInt32(&needsToDelayCommandsV) != 0 +func getDelayedFuncsAndClearSlow() []func() error { + delayedCommandsM.Lock() + defer delayedCommandsM.Unlock() + + if delayedCommandsFlushed == 0 { + defer atomic.StoreUint32(&delayedCommandsFlushed, 1) + + fs := make([]func() error, len(delayedCommands)) + copy(fs, delayedCommands) + delayedCommands = nil + return fs + } + + return nil +} + +func tryAddDelayedCommand(f func(obj interface{}) error, ondelayed func() interface{}) bool { + if atomic.LoadUint32(&delayedCommandsFlushed) == 0 { + // Outline the slow-path to expect the fast-path is inlined. + tryAddDelayedCommandSlow(f, ondelayed) + return true + } + + return false +} + +func tryAddDelayedCommandSlow(f func(obj interface{}) error, ondelayed func() interface{}) { + delayedCommandsM.Lock() + defer delayedCommandsM.Unlock() + + if delayedCommandsFlushed == 0 { + var obj interface{} + if ondelayed != nil { + obj = ondelayed() + } + delayedCommands = append(delayedCommands, func() error { + return f(obj) + }) + } +} + +func checkDelayedCommandsNil(fname string) { + if delayedCommands != nil { + panic("buffered: the command queue is not available yet at " + fname) + } } diff --git a/internal/buffered/image.go b/internal/buffered/image.go index d797e5364..bb0ec1d37 100644 --- a/internal/buffered/image.go +++ b/internal/buffered/image.go @@ -55,11 +55,10 @@ func NewImage(width, height int, volatile bool) *Image { } func (i *Image) initialize(width, height int, volatile bool) { - if needsToDelayCommands() { - delayedCommands = append(delayedCommands, func() error { - i.initialize(width, height, volatile) - return nil - }) + if tryAddDelayedCommand(func(obj interface{}) error { + i.initialize(width, height, volatile) + return nil + }, nil) { return } i.img = mipmap.New(width, height, volatile) @@ -74,11 +73,10 @@ func NewScreenFramebufferImage(width, height int) *Image { } func (i *Image) initializeAsScreenFramebuffer(width, height int) { - if needsToDelayCommands() { - delayedCommands = append(delayedCommands, func() error { - i.initializeAsScreenFramebuffer(width, height) - return nil - }) + if tryAddDelayedCommand(func(obj interface{}) error { + i.initializeAsScreenFramebuffer(width, height) + return nil + }, nil) { return } @@ -116,11 +114,10 @@ func (i *Image) resolvePendingFill() { } func (i *Image) MarkDisposed() { - if needsToDelayCommands() { - delayedCommands = append(delayedCommands, func() error { - i.MarkDisposed() - return nil - }) + if tryAddDelayedCommand(func(obj interface{}) error { + i.MarkDisposed() + return nil + }, nil) { return } i.invalidatePendingPixels() @@ -128,9 +125,7 @@ func (i *Image) MarkDisposed() { } func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) { - if needsToDelayCommands() { - panic("buffered: the command queue is not available yet at At") - } + checkDelayedCommandsNil("Pixels") if !image.Rect(x, y, x+width, y+height).In(image.Rect(0, 0, img.width, img.height)) { return nil, fmt.Errorf("buffered: out of range") @@ -165,18 +160,15 @@ func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) { } func (i *Image) Dump(name string, blackbg bool) error { - if needsToDelayCommands() { - panic("buffered: the command queue is not available yet at Dump") - } + checkDelayedCommandsNil("Dump") return i.img.Dump(name, blackbg) } func (i *Image) Fill(clr color.RGBA) { - if needsToDelayCommands() { - delayedCommands = append(delayedCommands, func() error { - i.Fill(clr) - return nil - }) + if tryAddDelayedCommand(func(obj interface{}) error { + i.Fill(clr) + return nil + }, nil) { return } @@ -191,13 +183,14 @@ 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)) } - if needsToDelayCommands() { + if tryAddDelayedCommand(func(copied interface{}) error { + i.ReplacePixels(copied.([]byte), x, y, width, height) + return nil + }, func() interface{} { copied := make([]byte, len(pix)) copy(copied, pix) - delayedCommands = append(delayedCommands, func() error { - i.ReplacePixels(copied, x, y, width, height) - return nil - }) + return copied + }) { return nil } @@ -237,11 +230,10 @@ func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) { } func (i *Image) CopyPixels(img *Image, x, y, width, height int) error { - if needsToDelayCommands() { - delayedCommands = append(delayedCommands, func() error { - i.CopyPixels(img, x, y, width, height) - return nil - }) + if tryAddDelayedCommand(func(obj interface{}) error { + i.CopyPixels(img, x, y, width, height) + return nil + }, nil) { return nil } @@ -269,11 +261,10 @@ func (i *Image) DrawImage(src *Image, bounds image.Rectangle, a, b, c, d, tx, ty Ty: ty, } - if needsToDelayCommands() { - delayedCommands = append(delayedCommands, func() error { - i.drawImage(src, bounds, g, colorm, mode, filter) - return nil - }) + if tryAddDelayedCommand(func(obj interface{}) error { + i.drawImage(src, bounds, g, colorm, mode, filter) + return nil + }, nil) { return } @@ -306,12 +297,11 @@ func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16, } } - if needsToDelayCommands() { - delayedCommands = append(delayedCommands, func() error { - // Arguments are not copied. Copying is the caller's responsibility. - i.DrawTriangles(src, vertices, indices, colorm, mode, filter, address, shader, uniforms) - return nil - }) + if tryAddDelayedCommand(func(obj interface{}) error { + // Arguments are not copied. Copying is the caller's responsibility. + i.DrawTriangles(src, vertices, indices, colorm, mode, filter, address, shader, uniforms) + return nil + }, nil) { return }