internal/affine: Cache scaling ColorM for heuristic optimization

Closes #1474
This commit is contained in:
Hajime Hoshi 2021-01-28 01:44:43 +09:00
parent 14abc28d3a
commit a6dd6196b4
3 changed files with 104 additions and 11 deletions

View File

@ -17,6 +17,7 @@ package affine
import (
"image/color"
"math"
"sync"
)
// ColorMDim is a dimension of a ColorM.
@ -441,15 +442,16 @@ func (c *ColorM) Concat(other *ColorM) *ColorM {
// Scale scales the matrix by (r, g, b, a).
func (c *ColorM) Scale(r, g, b, a float32) *ColorM {
if !c.isInited() {
return &ColorM{
body: []float32{
r, 0, 0, 0,
0, g, 0, 0,
0, 0, b, 0,
0, 0, 0, a,
},
return getCachedScalingColorM(r, g, b, a)
}
if c.ScaleOnly() {
if c.body == nil {
return getCachedScalingColorM(r, g, b, a)
}
return getCachedScalingColorM(r * c.body[0], g * c.body[5], b * c.body[10], a * c.body[15])
}
eb := make([]float32, len(colorMIdentityBody))
if c.body != nil {
copy(eb, c.body)
@ -549,3 +551,62 @@ func (c *ColorM) ChangeHSV(hueTheta float64, saturationScale float32, valueScale
c = c.Concat(yCbCrToRgb)
return c
}
type cachedScalingColorMKey struct {
r, g, b, a float32
}
type cachedScalingColorMValue struct {
c *ColorM
atime uint64
}
var (
cachedScalingColorM = map[cachedScalingColorMKey]*cachedScalingColorMValue{}
cachedScalingColorMM sync.Mutex
cacheMonotonicClock uint64
)
func getCachedScalingColorM(r, g, b, a float32) *ColorM {
key := cachedScalingColorMKey{r, g, b, a}
cachedScalingColorMM.Lock()
defer cachedScalingColorMM.Unlock()
cacheMonotonicClock++
now := cacheMonotonicClock
if v, ok := cachedScalingColorM[key]; ok {
v.atime = now
return v.c
}
const maxCacheSize = 512 // An arbitrary number
for len(cachedScalingColorM) >= maxCacheSize {
var oldest uint64 = math.MaxUint64
var oldestKey cachedScalingColorMKey
for k, v := range cachedScalingColorM {
if v.atime < oldest {
oldestKey = k
oldest = v.atime
}
}
delete(cachedScalingColorM, oldestKey)
}
v := &cachedScalingColorMValue{
c: &ColorM{
body: []float32{
r, 0, 0, 0,
0, g, 0, 0,
0, 0, b, 0,
0, 0, 0, a,
},
},
atime: now,
}
cachedScalingColorM[key] = v
return v.c
}

View File

@ -22,6 +22,33 @@ import (
. "github.com/hajimehoshi/ebiten/v2/internal/affine"
)
func TestColorMScale(t *testing.T) {
cases := []struct {
In *ColorM
Out *ColorM
}{
{
nil,
(*ColorM)(nil).Scale(0.25, 0.5, 0.75, 1),
},
{
(*ColorM)(nil).Scale(0.5, 0.5, 0.5, 0.8),
(*ColorM)(nil).Scale(0.125, 0.25, 0.375, 0.8),
},
{
(*ColorM)(nil).Translate(0, 0, 0, 0),
(*ColorM)(nil).Scale(0.25, 0.5, 0.75, 1),
},
}
for _, c := range cases {
got := c.In.Scale(0.25, 0.5, 0.75, 1)
want := c.Out
if got != want {
t.Errorf("%v.Scale(): got: %v, want: %v", c.In, got, want)
}
}
}
func TestColorMScaleOnly(t *testing.T) {
cases := []struct {
In *ColorM

View File

@ -27,7 +27,6 @@ import (
"golang.org/x/image/math/fixed"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/internal/colormcache"
"github.com/hajimehoshi/ebiten/v2/internal/hooks"
)
@ -167,13 +166,19 @@ func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Co
textM.Lock()
defer textM.Unlock()
cr, cg, cb, ca := clr.RGBA()
if ca == 0 {
return
}
var colorm ebiten.ColorM
colorm.Scale(float64(cr)/float64(ca), float64(cg)/float64(ca), float64(cb)/float64(ca), float64(ca)/0xffff)
fx, fy := fixed.I(x), fixed.I(y)
prevR := rune(-1)
faceHeight := face.Metrics().Height
colorm := colormcache.ColorToColorM(clr)
for _, r := range text {
if prevR >= 0 {
fx += face.Kern(prevR, r)