mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 02:38:53 +01:00
text/v2: let StdFace and GoTextFaceSource have their own glyph caches
This commit is contained in:
parent
7a08737326
commit
57ae07eb36
@ -39,11 +39,6 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
type faceCacheKey struct {
|
||||
id uint64
|
||||
goTextFaceSize float64
|
||||
}
|
||||
|
||||
type glyphImageCacheKey struct {
|
||||
// id is rune for StdFace, and GID for GoTextFace.
|
||||
id uint32
|
||||
@ -60,28 +55,22 @@ type glyphImageCacheEntry struct {
|
||||
}
|
||||
|
||||
type glyphImageCache struct {
|
||||
cache map[faceCacheKey]map[glyphImageCacheKey]*glyphImageCacheEntry
|
||||
cache map[glyphImageCacheKey]*glyphImageCacheEntry
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
var theGlyphImageCache glyphImageCache
|
||||
|
||||
func (g *glyphImageCache) getOrCreate(face Face, key glyphImageCacheKey, create func() *ebiten.Image) *ebiten.Image {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
e, ok := g.cache[face.faceCacheKey()][key]
|
||||
e, ok := g.cache[key]
|
||||
if ok {
|
||||
e.atime = now()
|
||||
return e.image
|
||||
}
|
||||
|
||||
if g.cache == nil {
|
||||
g.cache = map[faceCacheKey]map[glyphImageCacheKey]*glyphImageCacheEntry{}
|
||||
}
|
||||
faceCacheKey := face.faceCacheKey()
|
||||
if g.cache[faceCacheKey] == nil {
|
||||
g.cache[faceCacheKey] = map[glyphImageCacheKey]*glyphImageCacheEntry{}
|
||||
g.cache = map[glyphImageCacheKey]*glyphImageCacheEntry{}
|
||||
}
|
||||
|
||||
img := create()
|
||||
@ -95,7 +84,7 @@ func (g *glyphImageCache) getOrCreate(face Face, key glyphImageCacheKey, create
|
||||
// Keep this until the face is GCed.
|
||||
e.atime = infTime
|
||||
}
|
||||
g.cache[faceCacheKey][key] = e
|
||||
g.cache[key] = e
|
||||
|
||||
// Clean up old entries.
|
||||
|
||||
@ -104,26 +93,15 @@ func (g *glyphImageCache) getOrCreate(face Face, key glyphImageCacheKey, create
|
||||
// Even after cleaning up the cache, the number of glyphs might still exceed the soft limit, but
|
||||
// this is fine.
|
||||
cacheSoftLimit := 128 * glyphVariationCount(face)
|
||||
if len(g.cache[faceCacheKey]) > cacheSoftLimit {
|
||||
for key, e := range g.cache[faceCacheKey] {
|
||||
if len(g.cache) > cacheSoftLimit {
|
||||
for key, e := range g.cache {
|
||||
// 60 is an arbitrary number.
|
||||
if e.atime >= now()-60 {
|
||||
continue
|
||||
}
|
||||
delete(g.cache[faceCacheKey], key)
|
||||
delete(g.cache, key)
|
||||
}
|
||||
}
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
func (g *glyphImageCache) clear(comp func(key faceCacheKey) bool) {
|
||||
g.m.Lock()
|
||||
defer g.m.Unlock()
|
||||
|
||||
for key := range g.cache {
|
||||
if comp(key) {
|
||||
delete(g.cache, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,14 +238,6 @@ func (g *GoTextFace) ensureFeaturesString() string {
|
||||
return g.featuresString
|
||||
}
|
||||
|
||||
// faceCacheKey implements Face.
|
||||
func (g *GoTextFace) faceCacheKey() faceCacheKey {
|
||||
return faceCacheKey{
|
||||
id: g.Source.id,
|
||||
goTextFaceSize: g.Size,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GoTextFace) outputCacheKey(text string) goTextOutputCacheKey {
|
||||
return goTextOutputCacheKey{
|
||||
text: text,
|
||||
@ -343,7 +335,7 @@ func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*ebiten.Im
|
||||
yoffset: subpixelOffset.Y,
|
||||
variations: g.ensureVariationsString(),
|
||||
}
|
||||
img := theGlyphImageCache.getOrCreate(g, key, func() *ebiten.Image {
|
||||
img := g.Source.getOrCreateGlyphImage(g, key, func() *ebiten.Image {
|
||||
return segmentsToImage(glyph.scaledSegments, subpixelOffset, b)
|
||||
})
|
||||
|
||||
|
@ -17,7 +17,6 @@ package text
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/go-text/typesetting/font"
|
||||
@ -25,6 +24,8 @@ import (
|
||||
"github.com/go-text/typesetting/opentype/api"
|
||||
"github.com/go-text/typesetting/shaping"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
type goTextOutputCacheKey struct {
|
||||
@ -53,10 +54,10 @@ type goTextOutputCacheValue struct {
|
||||
|
||||
// GoTextFaceSource is a source of a GoTextFace. This can be shared by multiple GoTextFace objects.
|
||||
type GoTextFaceSource struct {
|
||||
f font.Face
|
||||
id uint64
|
||||
f font.Face
|
||||
|
||||
outputCache map[goTextOutputCacheKey]*goTextOutputCacheValue
|
||||
outputCache map[goTextOutputCacheKey]*goTextOutputCacheValue
|
||||
glyphImageCache map[float64]*glyphImageCache
|
||||
|
||||
addr *GoTextFaceSource
|
||||
|
||||
@ -94,11 +95,9 @@ func NewGoTextFaceSource(source io.ReadSeeker) (*GoTextFaceSource, error) {
|
||||
}
|
||||
|
||||
s := &GoTextFaceSource{
|
||||
f: f,
|
||||
id: nextUniqueID(),
|
||||
f: f,
|
||||
}
|
||||
s.addr = s
|
||||
runtime.SetFinalizer(s, finalizeGoTextFaceSource)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@ -117,23 +116,14 @@ func NewGoTextFaceSourcesFromCollection(source io.ReadSeeker) ([]*GoTextFaceSour
|
||||
sources := make([]*GoTextFaceSource, len(fs))
|
||||
for i, f := range fs {
|
||||
s := &GoTextFaceSource{
|
||||
f: f,
|
||||
id: nextUniqueID(),
|
||||
f: f,
|
||||
}
|
||||
s.addr = s
|
||||
runtime.SetFinalizer(s, finalizeGoTextFaceSource)
|
||||
sources[i] = s
|
||||
}
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
func finalizeGoTextFaceSource(source *GoTextFaceSource) {
|
||||
runtime.SetFinalizer(source, nil)
|
||||
theGlyphImageCache.clear(func(key faceCacheKey) bool {
|
||||
return key.id == source.id
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GoTextFaceSource) copyCheck() {
|
||||
if g.addr != g {
|
||||
panic("text: illegal use of non-zero GoTextFaceSource copied by value")
|
||||
@ -233,3 +223,13 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) (shaping.Output,
|
||||
func (g *GoTextFaceSource) scale(size float64) float64 {
|
||||
return size / float64(g.f.Upem())
|
||||
}
|
||||
|
||||
func (g *GoTextFaceSource) getOrCreateGlyphImage(goTextFace *GoTextFace, key glyphImageCacheKey, create func() *ebiten.Image) *ebiten.Image {
|
||||
if g.glyphImageCache == nil {
|
||||
g.glyphImageCache = map[float64]*glyphImageCache{}
|
||||
}
|
||||
if _, ok := g.glyphImageCache[goTextFace.Size]; !ok {
|
||||
g.glyphImageCache[goTextFace.Size] = &glyphImageCache{}
|
||||
}
|
||||
return g.glyphImageCache[goTextFace.Size].getOrCreate(goTextFace, key, create)
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ package text
|
||||
|
||||
import (
|
||||
"image"
|
||||
"runtime"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
@ -33,7 +32,7 @@ var _ Face = (*StdFace)(nil)
|
||||
type StdFace struct {
|
||||
f *faceWithCache
|
||||
|
||||
id uint64
|
||||
glyphImageCache glyphImageCache
|
||||
|
||||
addr *StdFace
|
||||
}
|
||||
@ -44,20 +43,11 @@ func NewStdFace(face font.Face) *StdFace {
|
||||
f: &faceWithCache{
|
||||
f: face,
|
||||
},
|
||||
id: nextUniqueID(),
|
||||
}
|
||||
s.addr = s
|
||||
runtime.SetFinalizer(s, finalizeStdFace)
|
||||
return s
|
||||
}
|
||||
|
||||
func finalizeStdFace(face *StdFace) {
|
||||
runtime.SetFinalizer(face, nil)
|
||||
theGlyphImageCache.clear(func(key faceCacheKey) bool {
|
||||
return key.id == face.id
|
||||
})
|
||||
}
|
||||
|
||||
func (s *StdFace) copyCheck() {
|
||||
if s.addr != s {
|
||||
panic("text: illegal use of non-zero StdFace copied by value")
|
||||
@ -82,13 +72,6 @@ func (s *StdFace) UnsafeInternal() any {
|
||||
return s.f.f
|
||||
}
|
||||
|
||||
// faceCacheKey implements Face.
|
||||
func (s *StdFace) faceCacheKey() faceCacheKey {
|
||||
return faceCacheKey{
|
||||
id: s.id,
|
||||
}
|
||||
}
|
||||
|
||||
// advance implements Face.
|
||||
func (s *StdFace) advance(text string) float64 {
|
||||
return fixed26_6ToFloat64(font.MeasureString(s.f, text))
|
||||
@ -143,7 +126,7 @@ func (s *StdFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int
|
||||
xoffset: subpixelOffset.X,
|
||||
// yoffset is always the same if the rune is the same, so this doesn't have to be a key.
|
||||
}
|
||||
img := theGlyphImageCache.getOrCreate(s, key, func() *ebiten.Image {
|
||||
img := s.glyphImageCache.getOrCreate(s, key, func() *ebiten.Image {
|
||||
return s.glyphImageImpl(r, subpixelOffset, b)
|
||||
})
|
||||
imgX := (origin.X + b.Min.X).Floor()
|
||||
|
@ -20,7 +20,6 @@ package text
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
@ -37,8 +36,6 @@ type Face interface {
|
||||
// This is unsafe since this might make internal cache states out of sync.
|
||||
UnsafeInternal() any
|
||||
|
||||
faceCacheKey() faceCacheKey
|
||||
|
||||
advance(text string) float64
|
||||
|
||||
appendGlyphs(glyphs []Glyph, text string, indexOffset int, originX, originY float64) []Glyph
|
||||
@ -262,9 +259,3 @@ func CacheGlyphs(text string, face Face) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var currentUniqueID uint64
|
||||
|
||||
func nextUniqueID() uint64 {
|
||||
return atomic.AddUint64(¤tUniqueID, 1)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user