Avoid heap allocating in GetTextRuns+Perf notes+pprof by default

This commit is contained in:
bloeys
2022-07-08 08:44:57 +04:00
parent d8289139d3
commit cb2ead0907
4 changed files with 49 additions and 38 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
*.png *.png
*.cpu

View File

@ -31,6 +31,7 @@ type GlyphRend struct {
GlyphMesh *meshes.Mesh GlyphMesh *meshes.Mesh
InstancedBuf buffers.Buffer InstancedBuf buffers.Buffer
GlyphMat *materials.Material GlyphMat *materials.Material
TextRunsBuf []TextRun
GlyphCount uint32 GlyphCount uint32
//NOTE: Because of the sad realities (bugs?) of CGO, passing an array in a struct //NOTE: Because of the sad realities (bugs?) of CGO, passing an array in a struct
@ -60,7 +61,8 @@ func (gr *GlyphRend) DrawTextOpenGL01(text string, screenPos *gglm.Vec3, color *
//Color is RGBA in the range [0,1]. //Color is RGBA in the range [0,1].
func (gr *GlyphRend) DrawTextOpenGLAbs(text string, screenPos *gglm.Vec3, color *gglm.Vec4) { func (gr *GlyphRend) DrawTextOpenGLAbs(text string, screenPos *gglm.Vec3, color *gglm.Vec4) {
runs := gr.GetTextRuns(text) runs := gr.TextRunsBuf[:]
gr.GetTextRuns(text, &runs)
if runs == nil { if runs == nil {
return return
} }
@ -180,19 +182,20 @@ type TextRun struct {
IsLtr bool IsLtr bool
} }
func (gr *GlyphRend) GetTextRuns(t string) []TextRun { func (gr *GlyphRend) GetTextRuns(t string, textRunsBuf *[]TextRun) {
//PERF: Might be better to pass a []TextRun buffer to avoid allocating on the heap if len(t) == 0 {
rs := []rune(t) return
if len(rs) == 0 {
return nil
} }
runs := make([]TextRun, 0, 10) rs := []rune(t)
runs := textRunsBuf
currRunScript := RuneInfos[rs[0]].ScriptTable currRunScript := RuneInfos[rs[0]].ScriptTable
//TODO: We need to detect neutral characters through BiDi category, not being in common //TODO: We need to detect neutral characters through BiDi category, not being in common
//TODO: Diacritics go into things like 'Category_Mn' and don't necessairly follow the parent script (e.g. Arabic diacritics are NOT in unicode.Arabic).
//They should be part of the same run but right now we split them into their own run.
runStartIndex := 0 runStartIndex := 0
for i := 1; i < len(rs); i++ { for i := 1; i < len(rs); i++ {
@ -216,9 +219,9 @@ func (gr *GlyphRend) GetTextRuns(t string) []TextRun {
//If we have a run without trailing neutrals or had a run of just neutrals (e.g. starting sentence with spaces) //If we have a run without trailing neutrals or had a run of just neutrals (e.g. starting sentence with spaces)
//then the full run is added, otherwise we slice the run to put neturals in a separate run //then the full run is added, otherwise we slice the run to put neturals in a separate run
if trailingCommonsCount == 0 || len(newRunRunes) == trailingCommonsCount { if trailingCommonsCount == 0 || len(newRunRunes) == trailingCommonsCount {
runs = append(runs, TextRun{Runes: newRunRunes}) *runs = append(*runs, TextRun{Runes: newRunRunes})
} else { } else {
runs = append(runs, *runs = append(*runs,
TextRun{Runes: newRunRunes[:len(newRunRunes)-trailingCommonsCount]}, TextRun{Runes: newRunRunes[len(newRunRunes)-trailingCommonsCount:]}) TextRun{Runes: newRunRunes[:len(newRunRunes)-trailingCommonsCount]}, TextRun{Runes: newRunRunes[len(newRunRunes)-trailingCommonsCount:]})
} }
@ -227,12 +230,12 @@ func (gr *GlyphRend) GetTextRuns(t string) []TextRun {
currRunScript = ri.ScriptTable currRunScript = ri.ScriptTable
} }
runs = append(runs, TextRun{Runes: rs[runStartIndex:]}) *runs = append(*runs, TextRun{Runes: rs[runStartIndex:]})
//Detect directionality of each run //Detect directionality of each run
for i := 0; i < len(runs); i++ { for i := 0; i < len(*runs); i++ {
run := &runs[i] run := &(*runs)[i]
bidiCat := BidiCategory_L bidiCat := BidiCategory_L
for _, r := range run.Runes { for _, r := range run.Runes {
if !unicode.Is(unicode.Common, r) { if !unicode.Is(unicode.Common, r) {
@ -242,12 +245,12 @@ func (gr *GlyphRend) GetTextRuns(t string) []TextRun {
} }
run.IsLtr = !(bidiCat == BidiCategory_R || bidiCat == BidiCategory_AL || bidiCat == BidiCategory_RLE || bidiCat == BidiCategory_RLO || bidiCat == BidiCategory_RLI || bidiCat == BidiCategory_RLM) run.IsLtr = !(bidiCat == BidiCategory_R || bidiCat == BidiCategory_AL || bidiCat == BidiCategory_RLE || bidiCat == BidiCategory_RLO || bidiCat == BidiCategory_RLI || bidiCat == BidiCategory_RLM)
} }
return runs
} }
func (gr *GlyphRend) glyphFromRunes(curr, prev, next rune) FontAtlasGlyph { func (gr *GlyphRend) glyphFromRunes(curr, prev, next rune) FontAtlasGlyph {
//PERF: Map access times are absolute garbage to the point that ~85%+ of the runtime of this func
//is spent reading from maps :). Using nSet or fMap or similar would be a lot better.
type PosCtx int type PosCtx int
const ( const (
PosCtx_start PosCtx = iota PosCtx_start PosCtx = iota
@ -438,6 +441,7 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s
gr := &GlyphRend{ gr := &GlyphRend{
GlyphCount: 0, GlyphCount: 0,
GlyphVBO: make([]float32, floatsPerGlyph*MaxGlyphsPerBatch), GlyphVBO: make([]float32, floatsPerGlyph*MaxGlyphsPerBatch),
TextRunsBuf: make([]TextRun, 0, 20),
SpacesPerTab: 4, SpacesPerTab: 4,
} }

View File

@ -1,5 +1,5 @@
[Window][Debug##Default] [Window][Debug##Default]
Pos=814,53 Pos=814,53
Size=396,114 Size=399,134
Collapsed=0 Collapsed=0

46
main.go
View File

@ -2,7 +2,10 @@ package main
import ( import (
"fmt" "fmt"
"math"
"math/rand" "math/rand"
"os"
"runtime/pprof"
"github.com/bloeys/gglm/gglm" "github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/engine" "github.com/bloeys/nmage/engine"
@ -10,6 +13,7 @@ import (
"github.com/bloeys/nmage/materials" "github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/meshes" "github.com/bloeys/nmage/meshes"
"github.com/bloeys/nmage/renderer/rend3dgl" "github.com/bloeys/nmage/renderer/rend3dgl"
"github.com/bloeys/nmage/timing"
nmageimgui "github.com/bloeys/nmage/ui/imgui" nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/bloeys/nterm/glyphs" "github.com/bloeys/nterm/glyphs"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
@ -74,12 +78,11 @@ func main() {
//Don't flash white //Don't flash white
p.win.SDLWin.GLSwap() p.win.SDLWin.GLSwap()
// var pf, _ = os.Create("pprof.cpu") var pf, _ = os.Create("pprof.cpu")
// defer pf.Close() defer pf.Close()
pprof.StartCPUProfile(pf)
// pprof.StartCPUProfile(pf)
engine.Run(p, p.win, p.imguiInfo) engine.Run(p, p.win, p.imguiInfo)
// pprof.StopCPUProfile() pprof.StopCPUProfile()
} }
func (p *program) Init() { func (p *program) Init() {
@ -164,7 +167,8 @@ func (p *program) Update() {
imgui.InputText("", &textToShow) imgui.InputText("", &textToShow)
if imgui.Button("Print Runs") { if imgui.Button("Print Runs") {
runs := p.GlyphRend.GetTextRuns(textToShow) runs := make([]glyphs.TextRun, 0, 20)
p.GlyphRend.GetTextRuns(textToShow, &runs)
for _, run := range runs { for _, run := range runs {
fmt.Printf("%s; runes: %#x\n\n", string(run.Runes), run.Runes) fmt.Printf("%s; runes: %#x\n\n", string(run.Runes), run.Runes)
} }
@ -179,9 +183,12 @@ func (p *program) Update() {
p.GlyphRend.GlyphMat.SetUnifInt32("drawBounds", 0) p.GlyphRend.GlyphMat.SetUnifInt32("drawBounds", 0)
} }
} }
imgui.Checkbox("Draw many", &drawManyLines)
} }
var isDrawingBounds = false var isDrawingBounds = false
var drawManyLines = false
var textToShow = " Hello there يا friend. اسمي عمر wow!" var textToShow = " Hello there يا friend. اسمي عمر wow!"
var xOff float32 = 0 var xOff float32 = 0
@ -215,23 +222,22 @@ func (p *program) Render() {
b = 0 b = 0
} }
textColor := gglm.NewVec4(r, g, b, 1)
str := textToShow str := textToShow
// str := "مرحبا بك my friend" charCount := len([]rune(str))
// str := "my, friend" fps := int(timing.GetAvgFPS())
// str := " hello there يا friend. أسمي عمر wow" textColor := gglm.NewVec4(r, g, b, 1)
// str := " ijojo\n\n Hello there, friend|. pq?\n ABCDEFG\tHIJKLMNOPQRSTUVWXYZ\nمرحبا بك" if drawManyLines {
// str := " ijojo\n\n Hello there, friend|. pq?\n ABCDEFG\tHIJKLMNOPQRSTUVWXYZ"
const charsPerFrame = 500_000
for i := 0; i < charsPerFrame/charCount; i++ {
p.GlyphRend.DrawTextOpenGLAbs(str, gglm.NewVec3(xOff, float32(p.GlyphRend.Atlas.LineHeight)*5+yOff, 0), textColor) p.GlyphRend.DrawTextOpenGLAbs(str, gglm.NewVec3(xOff, float32(p.GlyphRend.Atlas.LineHeight)*5+yOff, 0), textColor)
}
// strLen := len(str) p.win.SDLWin.SetTitle(fmt.Sprint("FPS: ", fps, " Draws/f: ", math.Ceil(charsPerFrame/glyphs.MaxGlyphsPerBatch), " chars/f: ", charsPerFrame, " chars/s: ", fps*charsPerFrame))
// const charsPerFrame = 100_000 } else {
// for i := 0; i < charsPerFrame/strLen; i++ { charsPerFrame := float64(charCount)
// p.GlyphRend.DrawTextOpenGLAbs(str, gglm.NewVec3(xOff, float32(p.GlyphRend.Atlas.LineHeight)*8+yOff, 0), textColor) p.GlyphRend.DrawTextOpenGLAbs(str, gglm.NewVec3(xOff, float32(p.GlyphRend.Atlas.LineHeight)*5+yOff, 0), textColor)
// } p.win.SDLWin.SetTitle(fmt.Sprint("FPS: ", fps, " Draws/f: ", math.Ceil(charsPerFrame/glyphs.MaxGlyphsPerBatch), " chars/f: ", charsPerFrame, " chars/s: ", fps*int(charsPerFrame)))
// fps := int(timing.GetAvgFPS()) }
// p.win.SDLWin.SetTitle(fmt.Sprint("FPS: ", fps, " Draws/f: ", math.Ceil(charsPerFrame/glyphs.MaxGlyphsPerBatch), " chars/f: ", charsPerFrame, " chars/s: ", fps*charsPerFrame))
} }