From 9cb631e30f3cf7471d53ed9aa76afb42846b0918 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sun, 29 Sep 2019 01:31:01 +0900 Subject: [PATCH] Reland: graphics: Reuse vertices backend and reduce allocating This is a reland of 74f1e5519f7d10d0cba56cfcd6f05d2dc4e9f0e0. The vertex slice is now copied before saving as the drawing history items. --- internal/restorable/image.go | 5 +++-- mipmap.go | 23 ++++++++++++++++------- vertex.go | 36 +++++++++++++++++++----------------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/internal/restorable/image.go b/internal/restorable/image.go index e901ae1b8..bebd978ff 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -414,12 +414,13 @@ func (i *Image) appendDrawTrianglesHistory(image *Image, vertices []float32, ind // 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. - // vertices is generated at ebiten package and doesn't have to be copied so far. + vs := make([]float32, len(vertices)) + copy(vs, vertices) is := make([]uint16, len(indices)) copy(is, indices) item := &drawTrianglesHistoryItem{ image: image, - vertices: vertices, + vertices: vs, indices: is, colorm: colorm, mode: mode, diff --git a/mipmap.go b/mipmap.go index b41bc19bf..1968fe8c4 100644 --- a/mipmap.go +++ b/mipmap.go @@ -121,9 +121,14 @@ func (m *mipmap) drawImage(src *mipmap, bounds image.Rectangle, geom *GeoM, colo colorm = nil } + screen := filter == driver.FilterScreen + if screen && level != 0 { + panic("ebiten: Mipmap must not be used when the filter is FilterScreen") + } + a, b, c, d, tx, ty := geom.elements() if level == 0 { - vs := quadVertices(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, a, b, c, d, tx, ty, cr, cg, cb, ca) + vs := quadVertices(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, a, b, c, d, tx, ty, cr, cg, cb, ca, screen) is := graphics.QuadIndices() m.orig.DrawTriangles(src.orig, vs, is, colorm, mode, filter, driver.AddressClampToZero) } else if buf := src.level(bounds, level); buf != nil { @@ -133,7 +138,7 @@ func (m *mipmap) drawImage(src *mipmap, bounds image.Rectangle, geom *GeoM, colo b *= s c *= s d *= s - vs := quadVertices(0, 0, w, h, a, b, c, d, tx, ty, cr, cg, cb, ca) + vs := quadVertices(0, 0, w, h, a, b, c, d, tx, ty, cr, cg, cb, ca, false) is := graphics.QuadIndices() m.orig.DrawTriangles(buf, vs, is, colorm, mode, filter, driver.AddressClampToZero) } @@ -149,7 +154,7 @@ func (m *mipmap) drawTriangles(src *mipmap, bounds image.Rectangle, vertices []V // TODO: Needs boundary check optimization? // See https://go101.org/article/bounds-check-elimination.html - vs := vertexSlice(len(vertices)) + vs := vertexSlice(len(vertices), false) for i, v := range vertices { vs[i*graphics.VertexFloatNum] = v.DstX vs[i*graphics.VertexFloatNum+1] = v.DstY @@ -194,7 +199,7 @@ func (m *mipmap) level(r image.Rectangle, level int) *buffered.Image { switch { case level == 1: src = m.orig - vs = quadVertices(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1) + vs = quadVertices(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false) filter = driver.FilterLinear case level > 1: src = m.level(r, level-1) @@ -203,11 +208,11 @@ func (m *mipmap) level(r image.Rectangle, level int) *buffered.Image { return nil } w, h := sizeForLevel(r.Dx(), r.Dy(), level-1) - vs = quadVertices(0, 0, w, h, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1) + vs = quadVertices(0, 0, w, h, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false) filter = driver.FilterLinear case level == -1: src = m.orig - vs = quadVertices(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1) + vs = quadVertices(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false) filter = driver.FilterNearest case level < -1: src = m.level(r, level+1) @@ -216,7 +221,7 @@ func (m *mipmap) level(r image.Rectangle, level int) *buffered.Image { return nil } w, h := sizeForLevel(r.Dx(), r.Dy(), level+1) - vs = quadVertices(0, 0, w, h, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1) + vs = quadVertices(0, 0, w, h, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false) filter = driver.FilterNearest default: panic(fmt.Sprintf("ebiten: invalid level: %d", level)) @@ -292,6 +297,10 @@ func (m *mipmap) mipmapLevel(geom *GeoM, width, height int, filter driver.Filter panic("ebiten: dst must be non zero at mipmapLevel") } + if filter == driver.FilterScreen { + return 0 + } + // Use 'negative' mipmap to render edges correctly (#611, #907). // It looks like 128 is the enlargement factor that causes edge missings to pass the test TestImageStretch. const tooBigScale = 128 diff --git a/vertex.go b/vertex.go index 5e4c10f37..40858f623 100644 --- a/vertex.go +++ b/vertex.go @@ -21,7 +21,9 @@ import ( ) var ( - theVerticesBackend = &verticesBackend{} + theVerticesBackend = &verticesBackend{ + backend: make([]float32, graphics.VertexFloatNum*1024), + } ) type verticesBackend struct { @@ -30,42 +32,42 @@ type verticesBackend struct { m sync.Mutex } -func (v *verticesBackend) slice(n int) []float32 { +func (v *verticesBackend) slice(n int, last bool) []float32 { v.m.Lock() need := n * graphics.VertexFloatNum - if v.head+need > len(v.backend) { - v.backend = nil + if l := len(v.backend); v.head+need > l { + for v.head+need > l { + l *= 2 + } + v.backend = make([]float32, l) v.head = 0 } - if v.backend == nil { - l := 1024 - if n > l { - l = n - } - v.backend = make([]float32, graphics.VertexFloatNum*l) - } - s := v.backend[v.head : v.head+need] - v.head += need + if last { + // If last is true, the vertices backend is sent to GPU and it is fine to reuse the slice. + v.head = 0 + } else { + v.head += need + } v.m.Unlock() return s } -func vertexSlice(n int) []float32 { - return theVerticesBackend.slice(n) +func vertexSlice(n int, last bool) []float32 { + return theVerticesBackend.slice(n, last) } -func quadVertices(sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32, cr, cg, cb, ca float32) []float32 { +func quadVertices(sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32, cr, cg, cb, ca float32, last bool) []float32 { x := float32(sx1 - sx0) y := float32(sy1 - sy0) ax, by, cx, dy := a*x, b*y, c*x, d*y u0, v0, u1, v1 := float32(sx0), float32(sy0), float32(sx1), float32(sy1) // This function is very performance-sensitive and implement in a very dumb way. - vs := vertexSlice(4) + vs := vertexSlice(4, last) _ = vs[:48] // For each values, see the comment at shareable.(*Image).DrawTriangles.