internal/buffered: move buffering to internal/atlas

Closes #2814
This commit is contained in:
Hajime Hoshi 2023-10-18 23:18:08 +09:00
parent da979a3ab2
commit 7d517bfb63
3 changed files with 49 additions and 126 deletions

View File

@ -120,6 +120,10 @@ var (
// backendsM is a mutex for critical sections of the backend and packing.Node objects.
backendsM sync.Mutex
// inFrame indicates whether the current state is in between BeginFrame and EndFrame or not.
// If inFrame is false, function calls on an image should be deferred until the next BeginFrame.
inFrame bool
initOnce sync.Once
// theBackends is a set of atlases.
@ -135,18 +139,6 @@ var (
deferredM sync.Mutex
)
func init() {
// Lock the mutex before a frame begins.
//
// In each frame, restoring images and resolving images happen respectively:
//
// [Restore -> Resolve] -> [Restore -> Resolve] -> ...
//
// Between each frame, any image operations are not permitted, or stale images would remain when restoring
// (#913).
backendsM.Lock()
}
type ImageType int
const (
@ -341,6 +333,23 @@ func (i *Image) regionWithPadding() image.Rectangle {
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint16, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, evenOdd bool) {
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
vs := make([]float32, len(vertices))
copy(vs, vertices)
is := make([]uint16, len(indices))
copy(is, indices)
us := make([]uint32, len(uniforms))
copy(us, uniforms)
deferredM.Lock()
deferred = append(deferred, func() {
i.drawTriangles(srcs, vs, is, blend, dstRegion, srcRegions, shader, us, evenOdd, false)
})
deferredM.Unlock()
return
}
i.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, evenOdd, false)
}
@ -449,6 +458,19 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
func (i *Image) WritePixels(pix []byte, region image.Rectangle) {
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
copied := make([]byte, len(pix))
copy(copied, pix)
deferredM.Lock()
deferred = append(deferred, func() {
i.writePixels(copied, region)
})
deferredM.Unlock()
return
}
i.writePixels(pix, region)
}
@ -521,6 +543,10 @@ func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
panic("atlas: ReadPixels must be called in between BeginFrame and EndFrame")
}
// In the tests, BeginFrame might not be called often and then images might not be disposed (#2292).
// To prevent memory leaks, flush the deferred functions here.
flushDeferred()
@ -715,12 +741,19 @@ func (i *Image) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, path stri
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
panic("atlas: ReadPixels must be called in between BeginFrame and EndFrame")
}
return i.backend.restorable.Dump(graphicsDriver, path, blackbg, image.Rect(0, 0, i.width, i.height))
}
func EndFrame(graphicsDriver graphicsdriver.Graphics, swapBuffersForGL func()) error {
// Flushing draw commands starts. Stop queuing new graphics commands until the next frame.
backendsM.Lock()
defer backendsM.Unlock()
defer func() {
inFrame = true
}()
if err := restorable.EndFrame(graphicsDriver, swapBuffersForGL); err != nil {
return err
@ -745,7 +778,9 @@ func floorPowerOf2(x int) int {
}
func BeginFrame(graphicsDriver graphicsdriver.Graphics) error {
backendsM.Lock()
defer backendsM.Unlock()
inFrame = true
var err error
initOnce.Do(func() {

View File

@ -1,75 +0,0 @@
// Copyright 2019 The Ebiten 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 buffered
import (
"sync"
"sync/atomic"
)
var (
// 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(){}
delayedCommandsM sync.Mutex
delayedCommandsFlushed uint32
)
func flushDelayedCommands() {
if atomic.LoadUint32(&delayedCommandsFlushed) == 0 {
// Outline the slow-path to expect the fast-path is inlined.
flushDelayedCommandsSlow()
}
}
func flushDelayedCommandsSlow() {
delayedCommandsM.Lock()
defer delayedCommandsM.Unlock()
if delayedCommandsFlushed == 0 {
for _, f := range delayedCommands {
f()
}
delayedCommands = nil
delayedCommandsFlushed = 1
}
}
// maybeCanAddDelayedCommand returns false if the delayed commands cannot be added.
// Otherwise, maybeCanAddDelayedCommand's returning value is not determined.
// For example, maybeCanAddDelayedCommand can return true even when flushing is being processed.
func maybeCanAddDelayedCommand() bool {
return atomic.LoadUint32(&delayedCommandsFlushed) == 0
}
func tryAddDelayedCommand(f func()) bool {
delayedCommandsM.Lock()
defer delayedCommandsM.Unlock()
if delayedCommandsFlushed == 0 {
delayedCommands = append(delayedCommands, f)
return true
}
return false
}
func checkDelayedCommandsFlushed(fname string) {
if atomic.LoadUint32(&delayedCommandsFlushed) == 0 {
panic("buffered: the command queue is not available yet at " + fname)
}
}

View File

@ -34,11 +34,7 @@ type Image struct {
}
func BeginFrame(graphicsDriver graphicsdriver.Graphics) error {
if err := atlas.BeginFrame(graphicsDriver); err != nil {
return err
}
flushDelayedCommands()
return nil
return atlas.BeginFrame(graphicsDriver)
}
func EndFrame(graphicsDriver graphicsdriver.Graphics, swapBuffersForGL func()) error {
@ -62,8 +58,6 @@ func (i *Image) MarkDisposed() {
}
func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error {
checkDelayedCommandsFlushed("ReadPixels")
// If restorable.AlwaysReadPixelsFromGPU() returns false, the pixel data is cached in the restorable package.
if !restorable.AlwaysReadPixelsFromGPU() {
if err := i.img.ReadPixels(graphicsDriver, pixels, region); err != nil {
@ -90,7 +84,6 @@ func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte
}
func (i *Image) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, name string, blackbg bool) (string, error) {
checkDelayedCommandsFlushed("Dump")
return i.img.DumpScreenshot(graphicsDriver, name, blackbg)
}
@ -101,19 +94,6 @@ func (i *Image) WritePixels(pix []byte, region image.Rectangle) {
}
i.invalidatePixels()
if maybeCanAddDelayedCommand() {
copied := make([]byte, len(pix))
copy(copied, pix)
if tryAddDelayedCommand(func() {
i.writePixelsImpl(copied, region)
}) {
return
}
}
i.writePixelsImpl(pix, region)
}
func (i *Image) writePixelsImpl(pix []byte, region image.Rectangle) {
i.img.WritePixels(pix, region)
}
@ -128,23 +108,6 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
}
i.invalidatePixels()
if maybeCanAddDelayedCommand() {
vs := make([]float32, len(vertices))
copy(vs, vertices)
is := make([]uint16, len(indices))
copy(is, indices)
us := make([]uint32, len(uniforms))
copy(us, uniforms)
if tryAddDelayedCommand(func() {
i.drawTrianglesImpl(srcs, vs, is, blend, dstRegion, srcRegions, shader, us, evenOdd)
}) {
return
}
}
i.drawTrianglesImpl(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, evenOdd)
}
func (i *Image) drawTrianglesImpl(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint16, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, evenOdd bool) {
var imgs [graphics.ShaderImageCount]*atlas.Image
for i, img := range srcs {
if img == nil {