text: Optimize speed: Don't use a struct for map keys

Especially for browsers
This commit is contained in:
Hajime Hoshi 2018-02-12 00:07:25 +09:00
parent a5f9382878
commit da51377cc5

View File

@ -65,7 +65,7 @@ func fontFaceToFace(f font.Face) face {
} }
var ( var (
charBounds = map[char]fixed.Rectangle26_6{} charBounds = map[font.Face]map[rune]fixed.Rectangle26_6{}
) )
type char struct { type char struct {
@ -74,11 +74,15 @@ type char struct {
} }
func (c *char) bounds() fixed.Rectangle26_6 { func (c *char) bounds() fixed.Rectangle26_6 {
if b, ok := charBounds[*c]; ok { if m, ok := charBounds[c.face.f]; ok {
return b if b, ok := m[c.rune]; ok {
return b
}
} else {
charBounds[c.face.f] = map[rune]fixed.Rectangle26_6{}
} }
b, _, _ := c.face.f.GlyphBounds(c.rune) b, _, _ := c.face.f.GlyphBounds(c.rune)
charBounds[*c] = b charBounds[c.face.f][c.rune] = b
return b return b
} }
@ -135,7 +139,7 @@ func (g *glyph) draw(dst *ebiten.Image, x, y fixed.Int26_6, clr color.Color) {
af := float64(ca) / 0xffff af := float64(ca) / 0xffff
op.ColorM.Scale(rf, gf, bf, af) op.ColorM.Scale(rf, gf, bf, af)
a := atlases[g.char.atlasGroup()] a := atlases[g.char.face.f][g.char.atlasGroup()]
sx, sy := a.at(g) sx, sy := a.at(g)
r := image.Rect(sx, sy, sx+a.glyphSize, sy+a.glyphSize) r := image.Rect(sx, sy, sx+a.glyphSize, sy+a.glyphSize)
op.SourceRect = &r op.SourceRect = &r
@ -144,7 +148,7 @@ func (g *glyph) draw(dst *ebiten.Image, x, y fixed.Int26_6, clr color.Color) {
} }
var ( var (
atlases = map[int]*atlas{} atlases = map[font.Face]map[int]*atlas{}
) )
type atlas struct { type atlas struct {
@ -158,7 +162,7 @@ type atlas struct {
// This value is always power of 2. // This value is always power of 2.
glyphSize int glyphSize int
charToGlyph map[char]*glyph runeToGlyph map[rune]*glyph
} }
func (a *atlas) at(glyph *glyph) (int, int) { func (a *atlas) at(glyph *glyph) (int, int) {
@ -183,10 +187,10 @@ func (a *atlas) appendGlyph(face face, rune rune, now int64) *glyph {
char: char{face, rune}, char: char{face, rune},
atime: now, atime: now,
} }
if len(a.charToGlyph) == a.maxGlyphNum() { if len(a.runeToGlyph) == a.maxGlyphNum() {
var oldest *glyph var oldest *glyph
t := int64(math.MaxInt64) t := int64(math.MaxInt64)
for _, g := range a.charToGlyph { for _, g := range a.runeToGlyph {
if g.atime < t { if g.atime < t {
t = g.atime t = g.atime
oldest = g oldest = g
@ -196,13 +200,13 @@ func (a *atlas) appendGlyph(face face, rune rune, now int64) *glyph {
panic("not reached") panic("not reached")
} }
idx := oldest.index idx := oldest.index
delete(a.charToGlyph, oldest.char) delete(a.runeToGlyph, oldest.char.rune)
g.index = idx g.index = idx
} else { } else {
g.index = len(a.charToGlyph) g.index = len(a.runeToGlyph)
} }
a.charToGlyph[g.char] = g a.runeToGlyph[g.char.rune] = g
a.draw(g) a.draw(g)
return g return g
} }
@ -234,13 +238,19 @@ func (a *atlas) draw(glyph *glyph) {
func getGlyphFromCache(face face, r rune, now int64) *glyph { func getGlyphFromCache(face face, r rune, now int64) *glyph {
ch := char{face, r} ch := char{face, r}
a, ok := atlases[ch.atlasGroup()] var at *atlas
if ok { if m, ok := atlases[face.f]; ok {
g, ok := a.charToGlyph[ch] a, ok := m[ch.atlasGroup()]
if ok { if ok {
g.atime = now g, ok := a.runeToGlyph[r]
return g if ok {
g.atime = now
return g
}
} }
at = a
} else {
atlases[face.f] = map[int]*atlas{}
} }
if ch.empty() { if ch.empty() {
@ -252,7 +262,7 @@ func getGlyphFromCache(face face, r rune, now int64) *glyph {
} }
} }
if !ok { if at == nil {
// Don't use ebiten.MaxImageSize here. // Don't use ebiten.MaxImageSize here.
// It's because the back-end image pixels will be restored from GPU // It's because the back-end image pixels will be restored from GPU
// whenever a new glyph is rendered on the image, and restoring cost is // whenever a new glyph is rendered on the image, and restoring cost is
@ -265,15 +275,15 @@ func getGlyphFromCache(face face, r rune, now int64) *glyph {
// TODO: How about making a new function for 'flagile' image? // TODO: How about making a new function for 'flagile' image?
const size = 1024 const size = 1024
i, _ := ebiten.NewImage(size, size, ebiten.FilterNearest) i, _ := ebiten.NewImage(size, size, ebiten.FilterNearest)
a = &atlas{ at = &atlas{
image: i, image: i,
glyphSize: ch.atlasGroup(), glyphSize: ch.atlasGroup(),
charToGlyph: map[char]*glyph{}, runeToGlyph: map[rune]*glyph{},
} }
atlases[ch.atlasGroup()] = a atlases[face.f][ch.atlasGroup()] = at
} }
return a.appendGlyph(ch.face, ch.rune, now) return at.appendGlyph(ch.face, ch.rune, now)
} }
var textM sync.Mutex var textM sync.Mutex