mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +01:00
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:
parent
1d83df0c13
commit
c725937cc6
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user