mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 11:18:54 +01:00
shareable: Lock before BeginFrame
This make package shareable more consistent. The mutex is lock after EndFrame and before BeginFrame, and the similar rule will be applied at launching (BeginFrame unlocks the lock in any cases). Instead, package ebiten queues image operations if BeginFrame and doesn't create provisional non-shared images. This should improve performance at launching since this reduces the number of draw calls, especifally for creating new images. Updates #879. Updates #921.
This commit is contained in:
parent
7907bb43ce
commit
d2312f1450
87
image.go
87
image.go
@ -19,6 +19,7 @@ import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/internal/driver"
|
||||
@ -26,6 +27,31 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/internal/shareable"
|
||||
)
|
||||
|
||||
var (
|
||||
// imageQueue 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).
|
||||
// Instead of accessing the package shareable, defer the image operations until the game starts (#921).
|
||||
imageQueue []func()
|
||||
imageQueueM sync.Mutex
|
||||
)
|
||||
|
||||
func enqueueImageOp(f func()) {
|
||||
imageQueueM.Lock()
|
||||
defer imageQueueM.Unlock()
|
||||
|
||||
imageQueue = append(imageQueue, f)
|
||||
}
|
||||
|
||||
func flushImageOps() {
|
||||
imageQueueM.Lock()
|
||||
defer imageQueueM.Unlock()
|
||||
|
||||
for _, f := range imageQueue {
|
||||
f()
|
||||
}
|
||||
imageQueue = nil
|
||||
}
|
||||
|
||||
// Image represents a rectangle set of pixels.
|
||||
// The pixel format is alpha-premultiplied RGBA.
|
||||
// Image implements image.Image and draw.Image.
|
||||
@ -85,6 +111,20 @@ func (i *Image) Clear() error {
|
||||
// Fill always returns nil as of 1.5.0-alpha.
|
||||
func (i *Image) Fill(clr color.Color) error {
|
||||
i.copyCheck()
|
||||
|
||||
if atomic.LoadInt32(&isImageAvailable) == 0 {
|
||||
enqueueImageOp(func() {
|
||||
r, g, b, a := clr.RGBA()
|
||||
i.Fill(color.RGBA64{
|
||||
R: uint16(r),
|
||||
G: uint16(g),
|
||||
B: uint16(b),
|
||||
A: uint16(a),
|
||||
})
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.isDisposed() {
|
||||
return nil
|
||||
}
|
||||
@ -147,6 +187,15 @@ func (i *Image) disposeMipmaps() {
|
||||
// DrawImage always returns nil as of 1.5.0-alpha.
|
||||
func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error {
|
||||
i.copyCheck()
|
||||
|
||||
if atomic.LoadInt32(&isImageAvailable) == 0 {
|
||||
enqueueImageOp(func() {
|
||||
op := *options
|
||||
i.DrawImage(img, &op)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
if img.isDisposed() {
|
||||
panic("ebiten: the given image to DrawImage must not be disposed")
|
||||
}
|
||||
@ -357,6 +406,19 @@ const MaxIndicesNum = graphics.IndicesNum
|
||||
// Note that this API is experimental.
|
||||
func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, options *DrawTrianglesOptions) {
|
||||
i.copyCheck()
|
||||
|
||||
if atomic.LoadInt32(&isImageAvailable) == 0 {
|
||||
enqueueImageOp(func() {
|
||||
vs := make([]Vertex, len(vertices))
|
||||
copy(vs, vertices)
|
||||
is := make([]uint16, len(indices))
|
||||
copy(is, indices)
|
||||
op := *options
|
||||
i.DrawTriangles(vs, is, img, &op)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if i.isDisposed() {
|
||||
return
|
||||
}
|
||||
@ -556,6 +618,14 @@ func (i *Image) resolvePendingPixels(draw bool) {
|
||||
// Dipose always return nil as of 1.5.0-alpha.
|
||||
func (i *Image) Dispose() error {
|
||||
i.copyCheck()
|
||||
|
||||
if atomic.LoadInt32(&isImageAvailable) == 0 {
|
||||
enqueueImageOp(func() {
|
||||
i.Dispose()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.isDisposed() {
|
||||
return nil
|
||||
}
|
||||
@ -580,6 +650,16 @@ func (i *Image) Dispose() error {
|
||||
// ReplacePixels always returns nil as of 1.5.0-alpha.
|
||||
func (i *Image) ReplacePixels(p []byte) error {
|
||||
i.copyCheck()
|
||||
|
||||
if atomic.LoadInt32(&isImageAvailable) == 0 {
|
||||
enqueueImageOp(func() {
|
||||
px := make([]byte, len(p))
|
||||
copy(px, p)
|
||||
i.ReplacePixels(px)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.isDisposed() {
|
||||
return nil
|
||||
}
|
||||
@ -663,6 +743,13 @@ func NewImage(width, height int, filter Filter) (*Image, error) {
|
||||
//
|
||||
// When the image is disposed, makeVolatile does nothing.
|
||||
func (i *Image) makeVolatile() {
|
||||
if atomic.LoadInt32(&isImageAvailable) == 0 {
|
||||
enqueueImageOp(func() {
|
||||
i.makeVolatile()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if i.isDisposed() {
|
||||
return
|
||||
}
|
||||
|
@ -128,8 +128,7 @@ var (
|
||||
// backendsM is a mutex for critical sections of the backend and packing.Node objects.
|
||||
backendsM sync.Mutex
|
||||
|
||||
backendsOnce sync.Once
|
||||
initOnce sync.Once
|
||||
initOnce sync.Once
|
||||
|
||||
// theBackends is a set of actually shared images.
|
||||
theBackends = []*backend{}
|
||||
@ -139,13 +138,8 @@ var (
|
||||
deferred []func()
|
||||
)
|
||||
|
||||
// isShareable reports whether the new allocation can use the shareable backends.
|
||||
//
|
||||
// isShareable retruns false before the graphics driver is available.
|
||||
// After the graphics driver is available, read-only images will be automatically on the shareable backends by
|
||||
// (*Image).makeShared().
|
||||
func isShareable() bool {
|
||||
return minSize > 0 && maxSize > 0
|
||||
func init() {
|
||||
backendsM.Lock()
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
@ -472,7 +466,7 @@ func (i *Image) IsVolatile() bool {
|
||||
}
|
||||
|
||||
func NewImage(width, height int) *Image {
|
||||
// Actual allocation is done lazily.
|
||||
// Actual allocation is done lazily, and the lock is not needed.
|
||||
return &Image{
|
||||
width: width,
|
||||
height: height,
|
||||
@ -480,8 +474,8 @@ func NewImage(width, height int) *Image {
|
||||
}
|
||||
|
||||
func (i *Image) shareable() bool {
|
||||
if !isShareable() {
|
||||
return false
|
||||
if minSize == 0 || maxSize == 0 {
|
||||
panic("shareable: minSize or maxSize must be initialized")
|
||||
}
|
||||
if i.neverShared {
|
||||
return false
|
||||
@ -575,8 +569,6 @@ func EndFrame() error {
|
||||
}
|
||||
|
||||
func BeginFrame() error {
|
||||
// Unlock except for the first time.
|
||||
//
|
||||
// In each frame, restoring images and resolving images happen respectively:
|
||||
//
|
||||
// [Restore -> Resolve] -> [Restore -> Resolve] -> ...
|
||||
@ -584,13 +576,7 @@ func BeginFrame() error {
|
||||
// Between each frame, any image operations are not permitted, or stale images would remain when restoring
|
||||
// (#913).
|
||||
defer func() {
|
||||
firsttime := false
|
||||
backendsOnce.Do(func() {
|
||||
firsttime = true
|
||||
})
|
||||
if !firsttime {
|
||||
backendsM.Unlock()
|
||||
}
|
||||
backendsM.Unlock()
|
||||
}()
|
||||
|
||||
var err error
|
||||
|
@ -86,6 +86,7 @@ func (c *uiContext) Update(afterFrameUpdate func()) error {
|
||||
|
||||
// Images are available after shareable is initialized.
|
||||
atomic.StoreInt32(&isImageAvailable, 1)
|
||||
flushImageOps()
|
||||
|
||||
for i := 0; i < updateCount; i++ {
|
||||
c.offscreen.Clear()
|
||||
|
Loading…
Reference in New Issue
Block a user