From cb2ead0907126c2e684b69e06bf338c069eec922 Mon Sep 17 00:00:00 2001 From: bloeys Date: Fri, 8 Jul 2022 08:44:57 +0400 Subject: [PATCH] Avoid heap allocating in GetTextRuns+Perf notes+pprof by default --- .gitignore | 3 ++- glyphs/glyphs.go | 34 +++++++++++++++++++--------------- imgui.ini | 2 +- main.go | 48 +++++++++++++++++++++++++++--------------------- 4 files changed, 49 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index cb43a6a..71b10f9 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ -*.png \ No newline at end of file +*.png +*.cpu \ No newline at end of file diff --git a/glyphs/glyphs.go b/glyphs/glyphs.go index b6fb72f..a9fc5fe 100755 --- a/glyphs/glyphs.go +++ b/glyphs/glyphs.go @@ -31,6 +31,7 @@ type GlyphRend struct { GlyphMesh *meshes.Mesh InstancedBuf buffers.Buffer GlyphMat *materials.Material + TextRunsBuf []TextRun GlyphCount uint32 //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]. 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 { return } @@ -180,19 +182,20 @@ type TextRun struct { 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 - rs := []rune(t) - - if len(rs) == 0 { - return nil + if len(t) == 0 { + return } - runs := make([]TextRun, 0, 10) + rs := []rune(t) + + runs := textRunsBuf currRunScript := RuneInfos[rs[0]].ScriptTable //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 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) //then the full run is added, otherwise we slice the run to put neturals in a separate run if trailingCommonsCount == 0 || len(newRunRunes) == trailingCommonsCount { - runs = append(runs, TextRun{Runes: newRunRunes}) + *runs = append(*runs, TextRun{Runes: newRunRunes}) } else { - runs = append(runs, + *runs = append(*runs, 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 } - runs = append(runs, TextRun{Runes: rs[runStartIndex:]}) + *runs = append(*runs, TextRun{Runes: rs[runStartIndex:]}) //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 for _, r := range run.Runes { 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) } - - return runs } 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 const ( PosCtx_start PosCtx = iota @@ -438,6 +441,7 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s gr := &GlyphRend{ GlyphCount: 0, GlyphVBO: make([]float32, floatsPerGlyph*MaxGlyphsPerBatch), + TextRunsBuf: make([]TextRun, 0, 20), SpacesPerTab: 4, } diff --git a/imgui.ini b/imgui.ini index fb172a6..ab89c9c 100755 --- a/imgui.ini +++ b/imgui.ini @@ -1,5 +1,5 @@ [Window][Debug##Default] Pos=814,53 -Size=396,114 +Size=399,134 Collapsed=0 diff --git a/main.go b/main.go index 1161f4a..1645cf6 100755 --- a/main.go +++ b/main.go @@ -2,7 +2,10 @@ package main import ( "fmt" + "math" "math/rand" + "os" + "runtime/pprof" "github.com/bloeys/gglm/gglm" "github.com/bloeys/nmage/engine" @@ -10,6 +13,7 @@ import ( "github.com/bloeys/nmage/materials" "github.com/bloeys/nmage/meshes" "github.com/bloeys/nmage/renderer/rend3dgl" + "github.com/bloeys/nmage/timing" nmageimgui "github.com/bloeys/nmage/ui/imgui" "github.com/bloeys/nterm/glyphs" "github.com/golang/freetype/truetype" @@ -74,12 +78,11 @@ func main() { //Don't flash white p.win.SDLWin.GLSwap() - // var pf, _ = os.Create("pprof.cpu") - // defer pf.Close() - - // pprof.StartCPUProfile(pf) + var pf, _ = os.Create("pprof.cpu") + defer pf.Close() + pprof.StartCPUProfile(pf) engine.Run(p, p.win, p.imguiInfo) - // pprof.StopCPUProfile() + pprof.StopCPUProfile() } func (p *program) Init() { @@ -164,7 +167,8 @@ func (p *program) Update() { imgui.InputText("", &textToShow) 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 { 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) } } + + imgui.Checkbox("Draw many", &drawManyLines) } var isDrawingBounds = false +var drawManyLines = false var textToShow = " Hello there يا friend. اسمي عمر wow!" var xOff float32 = 0 @@ -215,23 +222,22 @@ func (p *program) Render() { b = 0 } - textColor := gglm.NewVec4(r, g, b, 1) str := textToShow - // str := "مرحبا بك my friend" - // str := "my, friend" - // str := " hello there يا friend. أسمي عمر wow" - // str := " ijojo\n\n Hello there, friend|. pq?\n ABCDEFG\tHIJKLMNOPQRSTUVWXYZ\nمرحبا بك" - // str := " ijojo\n\n Hello there, friend|. pq?\n ABCDEFG\tHIJKLMNOPQRSTUVWXYZ" + charCount := len([]rune(str)) + fps := int(timing.GetAvgFPS()) + textColor := gglm.NewVec4(r, g, b, 1) + if drawManyLines { - p.GlyphRend.DrawTextOpenGLAbs(str, gglm.NewVec3(xOff, float32(p.GlyphRend.Atlas.LineHeight)*5+yOff, 0), textColor) - - // strLen := len(str) - // const charsPerFrame = 100_000 - // for i := 0; i < charsPerFrame/strLen; i++ { - // p.GlyphRend.DrawTextOpenGLAbs(str, gglm.NewVec3(xOff, float32(p.GlyphRend.Atlas.LineHeight)*8+yOff, 0), textColor) - // } - // 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)) + 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.win.SDLWin.SetTitle(fmt.Sprint("FPS: ", fps, " Draws/f: ", math.Ceil(charsPerFrame/glyphs.MaxGlyphsPerBatch), " chars/f: ", charsPerFrame, " chars/s: ", fps*charsPerFrame)) + } else { + charsPerFrame := float64(charCount) + 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))) + } }