mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-11 19:48:54 +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 {
|
type glyphImageCacheKey struct {
|
||||||
// id is rune for StdFace, and GID for GoTextFace.
|
// id is rune for StdFace, and GID for GoTextFace.
|
||||||
id uint32
|
id uint32
|
||||||
@ -60,28 +55,22 @@ type glyphImageCacheEntry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type glyphImageCache struct {
|
type glyphImageCache struct {
|
||||||
cache map[faceCacheKey]map[glyphImageCacheKey]*glyphImageCacheEntry
|
cache map[glyphImageCacheKey]*glyphImageCacheEntry
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var theGlyphImageCache glyphImageCache
|
|
||||||
|
|
||||||
func (g *glyphImageCache) getOrCreate(face Face, key glyphImageCacheKey, create func() *ebiten.Image) *ebiten.Image {
|
func (g *glyphImageCache) getOrCreate(face Face, key glyphImageCacheKey, create func() *ebiten.Image) *ebiten.Image {
|
||||||
g.m.Lock()
|
g.m.Lock()
|
||||||
defer g.m.Unlock()
|
defer g.m.Unlock()
|
||||||
|
|
||||||
e, ok := g.cache[face.faceCacheKey()][key]
|
e, ok := g.cache[key]
|
||||||
if ok {
|
if ok {
|
||||||
e.atime = now()
|
e.atime = now()
|
||||||
return e.image
|
return e.image
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.cache == nil {
|
if g.cache == nil {
|
||||||
g.cache = map[faceCacheKey]map[glyphImageCacheKey]*glyphImageCacheEntry{}
|
g.cache = map[glyphImageCacheKey]*glyphImageCacheEntry{}
|
||||||
}
|
|
||||||
faceCacheKey := face.faceCacheKey()
|
|
||||||
if g.cache[faceCacheKey] == nil {
|
|
||||||
g.cache[faceCacheKey] = map[glyphImageCacheKey]*glyphImageCacheEntry{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
img := create()
|
img := create()
|
||||||
@ -95,7 +84,7 @@ func (g *glyphImageCache) getOrCreate(face Face, key glyphImageCacheKey, create
|
|||||||
// Keep this until the face is GCed.
|
// Keep this until the face is GCed.
|
||||||
e.atime = infTime
|
e.atime = infTime
|
||||||
}
|
}
|
||||||
g.cache[faceCacheKey][key] = e
|
g.cache[key] = e
|
||||||
|
|
||||||
// Clean up old entries.
|
// 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
|
// Even after cleaning up the cache, the number of glyphs might still exceed the soft limit, but
|
||||||
// this is fine.
|
// this is fine.
|
||||||
cacheSoftLimit := 128 * glyphVariationCount(face)
|
cacheSoftLimit := 128 * glyphVariationCount(face)
|
||||||
if len(g.cache[faceCacheKey]) > cacheSoftLimit {
|
if len(g.cache) > cacheSoftLimit {
|
||||||
for key, e := range g.cache[faceCacheKey] {
|
for key, e := range g.cache {
|
||||||
// 60 is an arbitrary number.
|
// 60 is an arbitrary number.
|
||||||
if e.atime >= now()-60 {
|
if e.atime >= now()-60 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
delete(g.cache[faceCacheKey], key)
|
delete(g.cache, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return img
|
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
|
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 {
|
func (g *GoTextFace) outputCacheKey(text string) goTextOutputCacheKey {
|
||||||
return goTextOutputCacheKey{
|
return goTextOutputCacheKey{
|
||||||
text: text,
|
text: text,
|
||||||
@ -343,7 +335,7 @@ func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*ebiten.Im
|
|||||||
yoffset: subpixelOffset.Y,
|
yoffset: subpixelOffset.Y,
|
||||||
variations: g.ensureVariationsString(),
|
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)
|
return segmentsToImage(glyph.scaledSegments, subpixelOffset, b)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ package text
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-text/typesetting/font"
|
"github.com/go-text/typesetting/font"
|
||||||
@ -25,6 +24,8 @@ import (
|
|||||||
"github.com/go-text/typesetting/opentype/api"
|
"github.com/go-text/typesetting/opentype/api"
|
||||||
"github.com/go-text/typesetting/shaping"
|
"github.com/go-text/typesetting/shaping"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type goTextOutputCacheKey struct {
|
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.
|
// GoTextFaceSource is a source of a GoTextFace. This can be shared by multiple GoTextFace objects.
|
||||||
type GoTextFaceSource struct {
|
type GoTextFaceSource struct {
|
||||||
f font.Face
|
f font.Face
|
||||||
id uint64
|
|
||||||
|
|
||||||
outputCache map[goTextOutputCacheKey]*goTextOutputCacheValue
|
outputCache map[goTextOutputCacheKey]*goTextOutputCacheValue
|
||||||
|
glyphImageCache map[float64]*glyphImageCache
|
||||||
|
|
||||||
addr *GoTextFaceSource
|
addr *GoTextFaceSource
|
||||||
|
|
||||||
@ -94,11 +95,9 @@ func NewGoTextFaceSource(source io.ReadSeeker) (*GoTextFaceSource, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s := &GoTextFaceSource{
|
s := &GoTextFaceSource{
|
||||||
f: f,
|
f: f,
|
||||||
id: nextUniqueID(),
|
|
||||||
}
|
}
|
||||||
s.addr = s
|
s.addr = s
|
||||||
runtime.SetFinalizer(s, finalizeGoTextFaceSource)
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,23 +116,14 @@ func NewGoTextFaceSourcesFromCollection(source io.ReadSeeker) ([]*GoTextFaceSour
|
|||||||
sources := make([]*GoTextFaceSource, len(fs))
|
sources := make([]*GoTextFaceSource, len(fs))
|
||||||
for i, f := range fs {
|
for i, f := range fs {
|
||||||
s := &GoTextFaceSource{
|
s := &GoTextFaceSource{
|
||||||
f: f,
|
f: f,
|
||||||
id: nextUniqueID(),
|
|
||||||
}
|
}
|
||||||
s.addr = s
|
s.addr = s
|
||||||
runtime.SetFinalizer(s, finalizeGoTextFaceSource)
|
|
||||||
sources[i] = s
|
sources[i] = s
|
||||||
}
|
}
|
||||||
return sources, nil
|
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() {
|
func (g *GoTextFaceSource) copyCheck() {
|
||||||
if g.addr != g {
|
if g.addr != g {
|
||||||
panic("text: illegal use of non-zero GoTextFaceSource copied by value")
|
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 {
|
func (g *GoTextFaceSource) scale(size float64) float64 {
|
||||||
return size / float64(g.f.Upem())
|
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 (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"runtime"
|
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"golang.org/x/image/font"
|
"golang.org/x/image/font"
|
||||||
@ -33,7 +32,7 @@ var _ Face = (*StdFace)(nil)
|
|||||||
type StdFace struct {
|
type StdFace struct {
|
||||||
f *faceWithCache
|
f *faceWithCache
|
||||||
|
|
||||||
id uint64
|
glyphImageCache glyphImageCache
|
||||||
|
|
||||||
addr *StdFace
|
addr *StdFace
|
||||||
}
|
}
|
||||||
@ -44,20 +43,11 @@ func NewStdFace(face font.Face) *StdFace {
|
|||||||
f: &faceWithCache{
|
f: &faceWithCache{
|
||||||
f: face,
|
f: face,
|
||||||
},
|
},
|
||||||
id: nextUniqueID(),
|
|
||||||
}
|
}
|
||||||
s.addr = s
|
s.addr = s
|
||||||
runtime.SetFinalizer(s, finalizeStdFace)
|
|
||||||
return s
|
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() {
|
func (s *StdFace) copyCheck() {
|
||||||
if s.addr != s {
|
if s.addr != s {
|
||||||
panic("text: illegal use of non-zero StdFace copied by value")
|
panic("text: illegal use of non-zero StdFace copied by value")
|
||||||
@ -82,13 +72,6 @@ func (s *StdFace) UnsafeInternal() any {
|
|||||||
return s.f.f
|
return s.f.f
|
||||||
}
|
}
|
||||||
|
|
||||||
// faceCacheKey implements Face.
|
|
||||||
func (s *StdFace) faceCacheKey() faceCacheKey {
|
|
||||||
return faceCacheKey{
|
|
||||||
id: s.id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// advance implements Face.
|
// advance implements Face.
|
||||||
func (s *StdFace) advance(text string) float64 {
|
func (s *StdFace) advance(text string) float64 {
|
||||||
return fixed26_6ToFloat64(font.MeasureString(s.f, text))
|
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,
|
xoffset: subpixelOffset.X,
|
||||||
// yoffset is always the same if the rune is the same, so this doesn't have to be a key.
|
// 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)
|
return s.glyphImageImpl(r, subpixelOffset, b)
|
||||||
})
|
})
|
||||||
imgX := (origin.X + b.Min.X).Floor()
|
imgX := (origin.X + b.Min.X).Floor()
|
||||||
|
@ -20,7 +20,6 @@ package text
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"golang.org/x/image/math/fixed"
|
"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.
|
// This is unsafe since this might make internal cache states out of sync.
|
||||||
UnsafeInternal() any
|
UnsafeInternal() any
|
||||||
|
|
||||||
faceCacheKey() faceCacheKey
|
|
||||||
|
|
||||||
advance(text string) float64
|
advance(text string) float64
|
||||||
|
|
||||||
appendGlyphs(glyphs []Glyph, text string, indexOffset int, originX, originY float64) []Glyph
|
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