internal/graphics: Reuse the vertices backend

This is basically a reland of 9cb631e30f.
This change locks the vertices backend at the end-frame phase to
protect from vertices usages by other goroutines.

Updates #1546
Closes #1681
This commit is contained in:
Hajime Hoshi 2021-06-27 02:21:51 +09:00
parent 1d83df0c13
commit c725937cc6
4 changed files with 117 additions and 22 deletions

View File

@ -57,32 +57,61 @@ var (
theVerticesBackend = &verticesBackend{} theVerticesBackend = &verticesBackend{}
) )
// TODO: The logic is very similar to atlas.temporaryPixels. Unify them.
type verticesBackend struct { type verticesBackend struct {
backend []float32 backend []float32
head int pos int
notFullyUsedTime int
m sync.Mutex m sync.Mutex
} }
func verticesBackendFloat32Size(size int) int {
l := 1024
for l < size {
l *= 2
}
return l
}
func (v *verticesBackend) slice(n int) []float32 { func (v *verticesBackend) slice(n int) []float32 {
v.m.Lock() v.m.Lock()
defer v.m.Unlock() defer v.m.Unlock()
need := n * VertexFloatNum need := n * VertexFloatNum
if v.head+need > len(v.backend) { if len(v.backend) < v.pos+need {
v.backend = nil v.backend = make([]float32, verticesBackendFloat32Size(v.pos+need))
v.head = 0 v.pos = 0
} }
if v.backend == nil { s := v.backend[v.pos : v.pos+need]
l := 1024 v.pos += need
if n > l { return s
l = n }
}
v.backend = make([]float32, VertexFloatNum*l) func (v *verticesBackend) lockAndReset(f func() error) error {
v.m.Lock()
defer v.m.Unlock()
if err := f(); err != nil {
return err
} }
s := v.backend[v.head : v.head+need] const maxNotFullyUsedTime = 60
v.head += need if verticesBackendFloat32Size(v.pos) < len(v.backend) {
return s if v.notFullyUsedTime < maxNotFullyUsedTime {
v.notFullyUsedTime++
}
} else {
v.notFullyUsedTime = 0
}
if v.notFullyUsedTime == maxNotFullyUsedTime && len(v.backend) > 0 {
v.backend = nil
}
v.pos = 0
return nil
} }
// Vertices returns a float32 slice for n vertices. // Vertices returns a float32 slice for n vertices.
@ -92,6 +121,10 @@ func Vertices(n int) []float32 {
return theVerticesBackend.slice(n) return theVerticesBackend.slice(n)
} }
func LockAndResetVertices(f func() error) error {
return theVerticesBackend.lockAndReset(f)
}
// QuadVertices returns a float32 slice for a quadrangle. // QuadVertices returns a float32 slice for a quadrangle.
// QuadVertices returns a slice that never overlaps with other slices returned this function, // QuadVertices returns a slice that never overlaps with other slices returned this function,
// and users can do optimization based on this fact. // and users can do optimization based on this fact.

View File

@ -408,8 +408,8 @@ func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image,
// All images must be resolved and not stale each after frame. // All images must be resolved and not stale each after frame.
// So we don't have to care if image is stale or not here. // So we don't have to care if image is stale or not here.
// vertices is generated at ebiten package and doesn't have to be copied so far. vs := make([]float32, len(vertices))
// This depends on the implementation of graphics.QuadVertices. copy(vs, vertices)
is := make([]uint16, len(indices)) is := make([]uint16, len(indices))
copy(is, indices) copy(is, indices)
@ -417,7 +417,7 @@ func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image,
item := &drawTrianglesHistoryItem{ item := &drawTrianglesHistoryItem{
images: srcs, images: srcs,
offsets: offsets, offsets: offsets,
vertices: vertices, vertices: vs,
indices: is, indices: is,
colorm: colorm, colorm: colorm,
mode: mode, mode: mode,

View File

@ -861,3 +861,58 @@ func TestClearPixels(t *testing.T) {
// After clearing, the regions will be available again. // After clearing, the regions will be available again.
img.ReplacePixels(make([]byte, 4*8*4), 0, 0, 8, 4) img.ReplacePixels(make([]byte, 4*8*4), 0, 0, 8, 4)
} }
func TestMutateSlices(t *testing.T) {
const w, h = 16, 16
dst := NewImage(w, h)
src := NewImage(w, h)
pix := make([]byte, 4*w*h)
for i := 0; i < w*h; i++ {
pix[4*i] = byte(i)
pix[4*i+1] = byte(i)
pix[4*i+2] = byte(i)
pix[4*i+3] = 0xff
}
src.ReplacePixels(pix, 0, 0, w, h)
vs := quadVertices(w, h, 0, 0)
is := make([]uint16, len(graphics.QuadIndices()))
copy(is, graphics.QuadIndices())
dr := driver.Region{
X: 0,
Y: 0,
Width: w,
Height: h,
}
dst.DrawTriangles([graphics.ShaderImageNum]*Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressUnsafe, dr, driver.Region{}, nil, nil)
for i := range vs {
vs[i] = 0
}
for i := range is {
is[i] = 0
}
if err := ResolveStaleImages(); err != nil {
t.Fatal(err)
}
if err := RestoreIfNeeded(); err != nil {
t.Fatal(err)
}
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
r, g, b, a, err := src.At(i, j)
if err != nil {
t.Fatal(err)
}
want := color.RGBA{r, g, b, a}
r, g, b, a, err = dst.At(i, j)
if err != nil {
t.Fatal(err)
}
got := color.RGBA{r, g, b, a}
if !sameColors(got, want, 1) {
t.Errorf("(%d, %d): got %v, want %v", i, j, got, want)
}
}
}
}

View File

@ -24,6 +24,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/clock" "github.com/hajimehoshi/ebiten/v2/internal/clock"
"github.com/hajimehoshi/ebiten/v2/internal/debug" "github.com/hajimehoshi/ebiten/v2/internal/debug"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/hooks" "github.com/hajimehoshi/ebiten/v2/internal/hooks"
) )
@ -158,16 +159,22 @@ func (c *uiContext) update(updateCount int) error {
if err, ok := c.err.Load().(error); ok && err != nil { if err, ok := c.err.Load().(error); ok && err != nil {
return err return err
} }
if err := buffered.BeginFrame(); err != nil { if err := buffered.BeginFrame(); err != nil {
return err return err
} }
if err := c.updateImpl(updateCount); err != nil { if err := c.updateImpl(updateCount); err != nil {
return err return err
} }
// All the vertices data are consumed at the end of the frame, and the data backend can be
// available after that. Until then, lock the vertices backend.
return graphics.LockAndResetVertices(func() error {
if err := buffered.EndFrame(); err != nil { if err := buffered.EndFrame(); err != nil {
return err return err
} }
return nil return nil
})
} }
func (c *uiContext) updateImpl(updateCount int) error { func (c *uiContext) updateImpl(updateCount int) error {