From 780465b702cedfb351541e9fa6162baa38d21276 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 29 Jun 2020 22:18:40 +0900 Subject: [PATCH] ebitenutil: Cache ColorM and reuse them at DrawRect Fixes #1221 --- ebitenutil/shapes.go | 22 +++----- internal/colormcache/cache.go | 96 +++++++++++++++++++++++++++++++++++ text/text.go | 62 +--------------------- 3 files changed, 105 insertions(+), 75 deletions(-) create mode 100644 internal/colormcache/cache.go diff --git a/ebitenutil/shapes.go b/ebitenutil/shapes.go index df019e0f3..7a51bd9f7 100644 --- a/ebitenutil/shapes.go +++ b/ebitenutil/shapes.go @@ -19,6 +19,7 @@ import ( "math" "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/internal/colormcache" ) var ( @@ -30,22 +31,11 @@ func init() { _ = emptyImage.Fill(color.White) } -func colorScale(clr color.Color) (rf, gf, bf, af float64) { - r, g, b, a := clr.RGBA() - if a == 0 { - return 0, 0, 0, 0 - } - - rf = float64(r) / float64(a) - gf = float64(g) / float64(a) - bf = float64(b) / float64(a) - af = float64(a) / 0xffff - return -} - // DrawLine draws a line segment on the given destination dst. // // DrawLine is intended to be used mainly for debugging or prototyping purpose. +// +// DrawLine is not concurrent-safe. func DrawLine(dst *ebiten.Image, x1, y1, x2, y2 float64, clr color.Color) { ew, eh := emptyImage.Size() length := math.Hypot(x2-x1, y2-y1) @@ -54,7 +44,7 @@ func DrawLine(dst *ebiten.Image, x1, y1, x2, y2 float64, clr color.Color) { op.GeoM.Scale(length/float64(ew), 1/float64(eh)) op.GeoM.Rotate(math.Atan2(y2-y1, x2-x1)) op.GeoM.Translate(x1, y1) - op.ColorM.Scale(colorScale(clr)) + op.ColorM = colormcache.ColorToColorM(clr) // Filter must be 'nearest' filter (default). // Linear filtering would make edges blurred. _ = dst.DrawImage(emptyImage, op) @@ -63,13 +53,15 @@ func DrawLine(dst *ebiten.Image, x1, y1, x2, y2 float64, clr color.Color) { // DrawRect draws a rectangle on the given destination dst. // // DrawRect is intended to be used mainly for debugging or prototyping purpose. +// +// DrawRect is not concurrent-safe. func DrawRect(dst *ebiten.Image, x, y, width, height float64, clr color.Color) { ew, eh := emptyImage.Size() op := &ebiten.DrawImageOptions{} op.GeoM.Scale(width/float64(ew), height/float64(eh)) op.GeoM.Translate(x, y) - op.ColorM.Scale(colorScale(clr)) + op.ColorM = colormcache.ColorToColorM(clr) // Filter must be 'nearest' filter (default). // Linear filtering would make edges blurred. _ = dst.DrawImage(emptyImage, op) diff --git a/internal/colormcache/cache.go b/internal/colormcache/cache.go new file mode 100644 index 000000000..4d10eaf6c --- /dev/null +++ b/internal/colormcache/cache.go @@ -0,0 +1,96 @@ +// Copyright 2020 The Ebiten Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package colormcache + +import ( + "image/color" + "math" + + "github.com/hajimehoshi/ebiten" +) + +var ( + monotonicClock int64 +) + +func now() int64 { + monotonicClock++ + return monotonicClock +} + +const ( + cacheLimit = 512 // This is an arbitrary number. +) + +type colorMCacheKey uint32 + +type colorMCacheEntry struct { + m ebiten.ColorM + atime int64 +} + +var ( + colorMCache = map[colorMCacheKey]*colorMCacheEntry{} + emptyColorM ebiten.ColorM +) + +func init() { + emptyColorM.Scale(0, 0, 0, 0) +} + +func ColorToColorM(clr color.Color) ebiten.ColorM { + // RGBA() is in [0 - 0xffff]. Adjust them in [0 - 0xff]. + cr, cg, cb, ca := clr.RGBA() + cr /= 0x101 + cg /= 0x101 + cb /= 0x101 + ca /= 0x101 + if ca == 0 { + return emptyColorM + } + + key := colorMCacheKey(uint32(cr) | (uint32(cg) << 8) | (uint32(cb) << 16) | (uint32(ca) << 24)) + e, ok := colorMCache[key] + if ok { + e.atime = now() + return e.m + } + + if len(colorMCache) > cacheLimit { + oldest := int64(math.MaxInt64) + oldestKey := colorMCacheKey(0) + for key, c := range colorMCache { + if c.atime < oldest { + oldestKey = key + oldest = c.atime + } + } + delete(colorMCache, oldestKey) + } + + cm := ebiten.ColorM{} + rf := float64(cr) / float64(ca) + gf := float64(cg) / float64(ca) + bf := float64(cb) / float64(ca) + af := float64(ca) / 0xff + cm.Scale(rf, gf, bf, af) + e = &colorMCacheEntry{ + m: cm, + atime: now(), + } + colorMCache[key] = e + + return e.m +} diff --git a/text/text.go b/text/text.go index 853398771..47cc64c4c 100644 --- a/text/text.go +++ b/text/text.go @@ -27,6 +27,7 @@ import ( "golang.org/x/image/math/fixed" "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/internal/colormcache" ) var ( @@ -46,13 +47,6 @@ const ( cacheLimit = 512 // This is an arbitrary number. ) -type colorMCacheKey uint32 - -type colorMCacheEntry struct { - m ebiten.ColorM - atime int64 -} - func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *glyphImage, x, y fixed.Int26_6, clr ebiten.ColorM) { if img == nil { return @@ -202,58 +196,6 @@ func getGlyphImages(face font.Face, runes []rune) []*glyphImage { var textM sync.Mutex -var ( - colorMCache = map[colorMCacheKey]*colorMCacheEntry{} - emptyColorM ebiten.ColorM -) - -func init() { - emptyColorM.Scale(0, 0, 0, 0) -} - -func colorToColorM(clr color.Color) ebiten.ColorM { - // RGBA() is in [0 - 0xffff]. Adjust them in [0 - 0xff]. - cr, cg, cb, ca := clr.RGBA() - cr >>= 8 - cg >>= 8 - cb >>= 8 - ca >>= 8 - if ca == 0 { - return emptyColorM - } - key := colorMCacheKey(uint32(cr) | (uint32(cg) << 8) | (uint32(cb) << 16) | (uint32(ca) << 24)) - e, ok := colorMCache[key] - if ok { - e.atime = now() - return e.m - } - if len(colorMCache) > cacheLimit { - oldest := int64(math.MaxInt64) - oldestKey := colorMCacheKey(0) - for key, c := range colorMCache { - if c.atime < oldest { - oldestKey = key - oldest = c.atime - } - } - delete(colorMCache, oldestKey) - } - - cm := ebiten.ColorM{} - rf := float64(cr) / float64(ca) - gf := float64(cg) / float64(ca) - bf := float64(cb) / float64(ca) - af := float64(ca) / 0xff - cm.Scale(rf, gf, bf, af) - e = &colorMCacheEntry{ - m: cm, - atime: now(), - } - colorMCache[key] = e - - return e.m -} - // Draw draws a given text on a given destination image dst. // // face is the font for text rendering. @@ -282,7 +224,7 @@ func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Co runes := []rune(text) glyphImgs := getGlyphImages(face, runes) - colorm := colorToColorM(clr) + colorm := colormcache.ColorToColorM(clr) for i, r := range runes { if prevR >= 0 {