From 43072eb0243ac67c02943a7c422eedd0e54e237d Mon Sep 17 00:00:00 2001 From: nikitar020 <42252263+nikitar020@users.noreply.github.com> Date: Mon, 14 Jan 2019 20:50:03 +0000 Subject: [PATCH] Changes to minimize memory allocations and to ensure OpenGL objects cleanup (#148) --- glfont/font.go | 26 ++++++++++++++++++-------- glfont/truetype.go | 12 ++++++++++-- gui/fontmap.go | 20 ++++++++++++++++++++ gui/fonts.go | 3 +-- gui/gui.go | 28 +++++++++++++++++++++------- gui/renderer.go | 19 ++++++++++++++++++- 6 files changed, 88 insertions(+), 20 deletions(-) diff --git a/glfont/font.go b/glfont/font.go index f91d749..190fd39 100644 --- a/glfont/font.go +++ b/glfont/font.go @@ -24,6 +24,7 @@ type Font struct { texture uint32 // Holds the glyph texture id. color color ttf *truetype.Font + ttfFace font.Face scale float32 linePadding float32 lineHeight float32 @@ -51,10 +52,26 @@ func LoadFont(reader io.Reader, scale float32, windowWidth int, windowHeight int //set screen resolution resUniform := gl.GetUniformLocation(program, gl.Str("resolution\x00")) gl.Uniform2f(resUniform, float32(windowWidth), float32(windowHeight)) + gl.UseProgram(0) return LoadTrueTypeFont(program, reader, scale) } +func (f *Font) Free() { + for _, chr := range f.characters { + gl.DeleteTextures(1, &chr.textureID) + } + + gl.DeleteBuffers(1, &f.vbo) + gl.DeleteVertexArrays(1, &f.vao) + + gl.DeleteProgram(f.program) + + f.vbo = 0 + f.vao = 0 + f.program = 0 +} + //SetColor allows you to set the text color to be used when you draw the text func (f *Font) SetColor(red float32, green float32, blue float32, alpha float32) { f.color.r = red @@ -223,14 +240,7 @@ func (f *Font) GetRune(r rune) (*character, error) { char := new(character) - //create new face to measure glyph diamensions - ttfFace := truetype.NewFace(f.ttf, &truetype.Options{ - Size: float64(f.scale), - DPI: DPI, - Hinting: font.HintingFull, - }) - - gBnd, gAdv, ok := ttfFace.GlyphBounds(r) + gBnd, gAdv, ok := f.ttfFace.GlyphBounds(r) if ok != true { return nil, fmt.Errorf("ttf face glyphBounds error") } diff --git a/glfont/truetype.go b/glfont/truetype.go index 08ed93e..e5dc492 100644 --- a/glfont/truetype.go +++ b/glfont/truetype.go @@ -6,6 +6,7 @@ import ( "github.com/go-gl/gl/all-core/gl" "github.com/golang/freetype/truetype" + "golang.org/x/image/font" ) type character struct { @@ -17,14 +18,14 @@ type character struct { bearingV int //glyph bearing vertical } -//LoadTrueTypeFont builds a set of textures based on a ttf files gylphs +//LoadTrueTypeFont builds a set of textures based on a ttf files glyphs func LoadTrueTypeFont(program uint32, r io.Reader, scale float32) (*Font, error) { data, err := ioutil.ReadAll(r) if err != nil { return nil, err } - //make Font stuct type + //make Font struct type f := new(Font) f.scale = scale f.characters = map[rune]*character{} @@ -62,5 +63,12 @@ func LoadTrueTypeFont(program uint32, r io.Reader, scale float32) (*Font, error) gl.BindBuffer(gl.ARRAY_BUFFER, 0) gl.BindVertexArray(0) + //create new face to measure glyph dimensions + f.ttfFace = truetype.NewFace(f.ttf, &truetype.Options { + Size: float64(f.scale), + DPI: DPI, + Hinting: font.HintingFull, + }) + return f, nil } diff --git a/gui/fontmap.go b/gui/fontmap.go index 7af5a81..d974065 100644 --- a/gui/fontmap.go +++ b/gui/fontmap.go @@ -14,6 +14,26 @@ func NewFontMap(defaultFont *glfont.Font, defaultBoldFont *glfont.Font) *FontMap } } +func (fm *FontMap) Free() { + if fm.defaultFont != nil { + fm.defaultFont.Free() + fm.defaultFont = nil + } + + if fm.defaultBoldFont != nil { + fm.defaultBoldFont.Free() + fm.defaultBoldFont = nil + } +} + +func (fm *FontMap) AssignFonts(defaultFont *glfont.Font, defaultBoldFont *glfont.Font) { + fm.defaultFont.Free() + fm.defaultBoldFont.Free() + + fm.defaultFont = defaultFont + fm.defaultBoldFont = defaultBoldFont +} + func (fm *FontMap) UpdateResolution(w int, h int) { fm.defaultFont.UpdateResolution(w, h) fm.defaultBoldFont.UpdateResolution(w, h) diff --git a/gui/fonts.go b/gui/fonts.go index 2c7166b..a222812 100644 --- a/gui/fonts.go +++ b/gui/fonts.go @@ -40,8 +40,7 @@ func (gui *GUI) loadFonts() error { if gui.fontMap == nil { gui.fontMap = NewFontMap(defaultFont, boldFont) } else { - gui.fontMap.defaultFont = defaultFont - gui.fontMap.defaultBoldFont = boldFont + gui.fontMap.AssignFonts(defaultFont, boldFont) } // add special non-ascii fonts here diff --git a/gui/gui.go b/gui/gui.go index b4b7fa1..2a4fb4d 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -16,6 +16,7 @@ import ( "github.com/liamg/aminal/terminal" "github.com/liamg/aminal/version" "go.uber.org/zap" + "unsafe" ) type GUI struct { @@ -413,7 +414,7 @@ func (gui *GUI) redraw(defaultCell buffer.Cell) { if y < len(lines) { - bufStr := "" + var builder strings.Builder bold := false dim := false col := 0 @@ -423,14 +424,14 @@ func (gui *GUI) redraw(defaultCell buffer.Cell) { for x := 0; x < colCount; x++ { if x < len(cells) { cell := cells[x] - if bufStr != "" && (cell.Attr().Dim != dim || cell.Attr().Bold != bold || colour != cell.Fg()) { + if builder.Len() > 0 && (cell.Attr().Dim != dim || cell.Attr().Bold != bold || colour != cell.Fg()) { var alpha float32 = 1.0 if dim { alpha = 0.5 } - gui.renderer.DrawCellText(bufStr, uint(col), uint(y), alpha, colour, bold) + gui.renderer.DrawCellText(builder.String(), uint(col), uint(y), alpha, colour, bold) col = x - bufStr = "" + builder.Reset() } dim = cell.Attr().Dim colour = cell.Fg() @@ -439,15 +440,15 @@ func (gui *GUI) redraw(defaultCell buffer.Cell) { if r == 0 { r = ' ' } - bufStr += string(r) + builder.WriteRune(r) } } - if bufStr != "" { + if builder.Len() > 0 { var alpha float32 = 1.0 if dim { alpha = 0.5 } - gui.renderer.DrawCellText(bufStr, uint(col), uint(y), alpha, colour, bold) + gui.renderer.DrawCellText(builder.String(), uint(col), uint(y), alpha, colour, bold) } } @@ -526,6 +527,10 @@ func (gui *GUI) createWindowWithOpenGLVersion(major int, minor int) (*glfw.Windo return window, nil } +func (gui *GUI) onDebugMessage(source uint32, gltype uint32, id uint32, severity uint32, length int32, message string, userParam unsafe.Pointer) { + gui.logger.Infof("GL debug message: %s", message) +} + // initOpenGL initializes OpenGL and returns an intiialized program. func (gui *GUI) createProgram() (uint32, error) { if err := gl.Init(); err != nil { @@ -533,6 +538,12 @@ func (gui *GUI) createProgram() (uint32, error) { } gui.logger.Infof("OpenGL version %s", gl.GoStr(gl.GetString(gl.VERSION))) + if gui.config.DebugMode { + // This allows to catch some OpenGL errors + gl.DebugMessageCallback(gui.onDebugMessage, nil) + gl.Enable(gl.DEBUG_OUTPUT) + } + gui.logger.Debugf("Compiling shaders...") vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER) @@ -550,6 +561,9 @@ func (gui *GUI) createProgram() (uint32, error) { gl.AttachShader(prog, fragmentShader) gl.LinkProgram(prog) + gl.DeleteShader(vertexShader) + gl.DeleteShader(fragmentShader) + return prog, nil } diff --git a/gui/renderer.go b/gui/renderer.go index 5b08b20..4a357f0 100644 --- a/gui/renderer.go +++ b/gui/renderer.go @@ -155,7 +155,6 @@ func (rect *rectangle) setColour(colour [3]float32) { } func (rect *rectangle) Free() { - gl.UseProgram(rect.prog) gl.DeleteVertexArrays(1, &rect.vao) gl.DeleteBuffers(1, &rect.vbo) gl.DeleteBuffers(1, &rect.cv) @@ -183,6 +182,24 @@ func NewOpenGLRenderer(config *config.Config, fontMap *FontMap, areaX int, areaY return r } +// This method ensures that all OpenGL resources are deleted correctly +func (r *OpenGLRenderer) Free() { + for _, rect := range r.rectangles { + rect.Free() + } + r.rectangles = map[[2]uint]*rectangle{} + + for _, tex := range r.textureMap { + gl.DeleteTextures(1, &tex) + } + r.textureMap = map[*image.RGBA]uint32{} + + r.fontMap.Free() + + gl.DeleteProgram(r.program) + r.program = 0 +} + func (r *OpenGLRenderer) GetTermSize() (uint, uint) { return r.termCols, r.termRows }