From afb5453a3a02e82589ad8d1e1b98c86d8a7fab47 Mon Sep 17 00:00:00 2001 From: bloeys Date: Sun, 3 Jul 2022 22:08:40 +0400 Subject: [PATCH] Make SizeU/SizeV uniforms instead of per-vertex+reduce glyph info --- glyphs/font_atlas.go | 46 +++++++++------------ glyphs/glyphs.go | 92 ++++++++++++++++-------------------------- main.go | 2 +- res/shaders/glyph.glsl | 44 ++++++++++---------- 4 files changed, 78 insertions(+), 106 deletions(-) diff --git a/glyphs/font_atlas.go b/glyphs/font_atlas.go index 1edb031..933303d 100755 --- a/glyphs/font_atlas.go +++ b/glyphs/font_atlas.go @@ -10,6 +10,7 @@ import ( "os" "unicode" + "github.com/bloeys/gglm/gglm" "github.com/bloeys/nterm/assert" "github.com/golang/freetype/truetype" "golang.org/x/image/font" @@ -17,23 +18,23 @@ import ( ) type FontAtlas struct { - Font *truetype.Font - Img *image.RGBA - Glyphs map[rune]FontAtlasGlyph + Font *truetype.Font + Img *image.RGBA + Glyphs map[rune]FontAtlasGlyph + + //Advance is global to the atlas because we only support monospaced fonts + Advance int LineHeight int + SizeUV gglm.Vec2 } type FontAtlasGlyph struct { - U float32 - V float32 - SizeU float32 - SizeV float32 + U float32 + V float32 Ascent float32 Descent float32 - Advance float32 BearingX float32 - Width float32 } //NewFontAtlasFromFile reads a TTF or TTC file and produces a font texture atlas containing @@ -119,11 +120,16 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo } //Create atlas + atlasSizeXF32 := float32(atlasSizeX) + atlasSizeYF32 := float32(atlasSizeY) atlas := &FontAtlas{ - Font: f, - Img: image.NewRGBA(image.Rect(0, 0, atlasSizeX, atlasSizeY)), - Glyphs: make(map[rune]FontAtlasGlyph, len(glyphs)), + Font: f, + Img: image.NewRGBA(image.Rect(0, 0, atlasSizeX, atlasSizeY)), + Glyphs: make(map[rune]FontAtlasGlyph, len(glyphs)), + + Advance: charAdv - charPaddingX, LineHeight: lineHeight, + SizeUV: *gglm.NewVec2(float32(charAdv-charPaddingX)/atlasSizeXF32, float32(lineHeight)/atlasSizeYF32), } //Clear background to black @@ -135,8 +141,6 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo } //Put glyphs on atlas - atlasSizeXF32 := float32(atlasSizeX) - atlasSizeYF32 := float32(atlasSizeY) charPaddingXFixed := fixed.I(charPaddingX) charPaddingYFixed := fixed.I(charPaddingY) @@ -145,28 +149,18 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo for _, g := range glyphs { gBounds, gAdvanceFixed, _ := face.GlyphBounds(g) - advanceCeilF32 := float32(gAdvanceFixed.Ceil()) ascent := absFixedI26_6(gBounds.Min.Y) descent := absFixedI26_6(gBounds.Max.Y) bearingX := absFixedI26_6(gBounds.Min.X) - glyphWidth := float32((absFixedI26_6(gBounds.Max.X) - absFixedI26_6(gBounds.Min.X)).Ceil()) - - //TODO: Since sizeU/sizeV are now constant we should upload as a uniform atlas.Glyphs[g] = FontAtlasGlyph{ U: float32((drawer.Dot.X).Floor()) / atlasSizeXF32, V: (atlasSizeYF32 - float32((drawer.Dot.Y).Ceil())) / atlasSizeYF32, - SizeU: advanceCeilF32 / atlasSizeXF32, - SizeV: float32(lineHeight) / atlasSizeYF32, - - Ascent: float32(ascent.Ceil()), - Descent: float32(descent.Ceil()), - Advance: float32(advanceCeilF32), - + Ascent: float32(ascent.Ceil()), + Descent: float32(descent.Ceil()), BearingX: float32(bearingX.Ceil()), - Width: glyphWidth, } //Get glyph to draw but undo any applied descent so that the glyph is drawn sitting on the line exactly. diff --git a/glyphs/glyphs.go b/glyphs/glyphs.go index d660109..abf69fe 100755 --- a/glyphs/glyphs.go +++ b/glyphs/glyphs.go @@ -47,49 +47,40 @@ func (gr *GlyphRend) DrawTextOpenGLAbs(text string, screenPos *gglm.Vec3, color // startPos := screenPos.Clone() pos := screenPos.Clone() + advanceF32 := float32(gr.Atlas.Advance) + lineHeightF32 := float32(gr.Atlas.LineHeight) instancedData := make([]float32, 0, len(rs)*floatsPerGlyph) //This a larger approximation than needed because we don't count spaces etc for i := 0; i < len(rs); i++ { r := rs[i] g := gr.Atlas.Glyphs[r] if r == '\n' { - screenPos.SetY(screenPos.Y() - float32(gr.Atlas.LineHeight)) + screenPos.SetY(screenPos.Y() - lineHeightF32) pos = screenPos.Clone() continue } else if r == ' ' { - pos.SetX(pos.X() + g.Advance) + pos.SetX(pos.X() + advanceF32) continue } gr.GlyphCount++ - // glyphHeight := float32(g.Ascent + g.Descent) - scale := gglm.NewVec3(g.Advance, float32(gr.Atlas.LineHeight), 1) - // scale := gglm.NewVec3(g.Width, glyphHeight, 1) + scale := gglm.NewVec3(advanceF32, lineHeightF32, 1) //See: https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png - //Quads are drawn from the center and so that's our baseline. But chars shouldn't be centered, they should follow ascent/decent/advance. - //To make them do that vertically, we raise them above the baseline (y+height/2), then since they are sitting on top of the baseline we can simply - //move them down by the decent amount to put them in the correct vertical position. - // - //Horizontally the character should be drawn from the left edge not the center, so we just move it forward by advance/2 + //The uvs coming in make it so that glyphs are sitting on top of the baseline (no descent) and with horizontal bearing applied. + //So to position correctly we move them down by the descent amount. drawPos := *pos drawPos.SetX(drawPos.X()) drawPos.SetY(drawPos.Y() - g.Descent) - // drawPos.SetX(drawPos.X() + g.BearingX) - // drawPos.SetY(drawPos.Y() - g.Descent) instancedData = append(instancedData, []float32{ g.U, g.V, - g.U + g.SizeU, g.V, - g.U, g.V + g.SizeV, - g.U + g.SizeU, g.V + g.SizeV, - color.R(), color.G(), color.B(), color.A(), //Color roundF32(drawPos.X()), roundF32(drawPos.Y()), drawPos.Z(), //Model pos scale.X(), scale.Y(), scale.Z(), //Model scale }...) - pos.SetX(pos.X() + g.Advance) + pos.SetX(pos.X() + advanceF32) } //Draw baselines @@ -163,8 +154,10 @@ func (gr *GlyphRend) SetFontFromFile(fontFile string, fontOptions *truetype.Opti //Any old textures are deleted func (gr *GlyphRend) updateFontAtlasTexture() error { + //Clean old texture and load new texture if gr.AtlasTex != nil { gl.DeleteTextures(1, &gr.AtlasTex.TexID) + gr.AtlasTex = nil } atlasTex, err := assets.LoadTextureInMemImg(gr.Atlas.Img, nil) @@ -180,6 +173,10 @@ func (gr *GlyphRend) updateFontAtlasTexture() error { gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) gl.BindTexture(gl.TEXTURE_2D, 0) + //Update material + gr.GlyphMat.DiffuseTex = gr.AtlasTex.TexID + gr.GlyphMat.SetUnifVec2("sizeUV", &gr.Atlas.SizeUV) + return nil } @@ -193,7 +190,6 @@ func (gr *GlyphRend) SetScreenSize(screenWidth, screenHeight int32) { viewMtx := gglm.LookAt(gglm.NewVec3(0, 0, -10), gglm.NewVec3(0, 0, 0), gglm.NewVec3(0, 1, 0)) projViewMtx := projMtx.Mul(viewMtx) - gr.GlyphMat.DiffuseTex = gr.AtlasTex.TexID gr.GlyphMat.SetUnifMat4("projViewMat", &projViewMtx.Mat4) } @@ -204,17 +200,6 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s GlyphVBO: make([]float32, 0), } - atlas, err := NewFontAtlasFromFile(fontFile, fontOptions) - if err != nil { - return nil, err - } - gr.Atlas = atlas - - err = gr.updateFontAtlasTexture() - if err != nil { - return nil, err - } - //Create glyph mesh gr.GlyphMesh = &meshes.Mesh{ Name: "glypQuad", @@ -240,7 +225,18 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s //Setup material gr.GlyphMat = materials.NewMaterial("glyphMat", "./res/shaders/glyph.glsl") - gr.GlyphMat.DiffuseTex = gr.AtlasTex.TexID + + //With the material ready we can generate the atlas + var err error + gr.Atlas, err = NewFontAtlasFromFile(fontFile, fontOptions) + if err != nil { + return nil, err + } + + err = gr.updateFontAtlasTexture() + if err != nil { + return nil, err + } //Create instanced buf and set its instanced attributes. //Multiple VBOs under one VAO, one VBO for vertex data, and one VBO for instanced data. @@ -254,11 +250,7 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s } gr.InstancedBuf.SetLayout( - buffers.Element{ElementType: buffers.DataTypeVec2}, //UVST0 - buffers.Element{ElementType: buffers.DataTypeVec2}, //UVST1 - buffers.Element{ElementType: buffers.DataTypeVec2}, //UVST2 - buffers.Element{ElementType: buffers.DataTypeVec2}, //UVST3 - + buffers.Element{ElementType: buffers.DataTypeVec2}, //UV0 buffers.Element{ElementType: buffers.DataTypeVec4}, //Color buffers.Element{ElementType: buffers.DataTypeVec3}, //ModelPos buffers.Element{ElementType: buffers.DataTypeVec3}, //ModelScale @@ -268,43 +260,27 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s gl.BindBuffer(gl.ARRAY_BUFFER, gr.InstancedBuf.BufID) layout := gr.InstancedBuf.GetLayout() - //4 UV values + //Instanced attributes uvEle := layout[0] gl.EnableVertexAttribArray(1) gl.VertexAttribPointer(1, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(uvEle.Offset)) gl.VertexAttribDivisor(1, 1) - uvEle = layout[1] + colorEle := layout[1] gl.EnableVertexAttribArray(2) - gl.VertexAttribPointer(2, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(uvEle.Offset)) + gl.VertexAttribPointer(2, colorEle.ElementType.CompCount(), colorEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(colorEle.Offset)) gl.VertexAttribDivisor(2, 1) - uvEle = layout[2] + posEle := layout[2] gl.EnableVertexAttribArray(3) - gl.VertexAttribPointer(3, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(uvEle.Offset)) + gl.VertexAttribPointer(3, posEle.ElementType.CompCount(), posEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(posEle.Offset)) gl.VertexAttribDivisor(3, 1) - uvEle = layout[3] + scaleEle := layout[3] gl.EnableVertexAttribArray(4) - gl.VertexAttribPointer(4, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(uvEle.Offset)) + gl.VertexAttribPointer(4, scaleEle.ElementType.CompCount(), scaleEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(scaleEle.Offset)) gl.VertexAttribDivisor(4, 1) - //Rest of instanced attributes - colorEle := layout[4] - gl.EnableVertexAttribArray(5) - gl.VertexAttribPointer(5, colorEle.ElementType.CompCount(), colorEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(colorEle.Offset)) - gl.VertexAttribDivisor(5, 1) - - posEle := layout[5] - gl.EnableVertexAttribArray(6) - gl.VertexAttribPointer(6, posEle.ElementType.CompCount(), posEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(posEle.Offset)) - gl.VertexAttribDivisor(6, 1) - - scaleEle := layout[6] - gl.EnableVertexAttribArray(7) - gl.VertexAttribPointer(7, scaleEle.ElementType.CompCount(), scaleEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(scaleEle.Offset)) - gl.VertexAttribDivisor(7, 1) - gl.BindBuffer(gl.ARRAY_BUFFER, 0) gr.InstancedBuf.UnBind() diff --git a/main.go b/main.go index 6ba456f..be30652 100755 --- a/main.go +++ b/main.go @@ -181,7 +181,7 @@ func (p *program) drawGrid() { sizeY := float32(p.GlyphRend.ScreenHeight) //columns - adv := p.GlyphRend.Atlas.Glyphs['A'].Advance + adv := p.GlyphRend.Atlas.Advance for i := int32(0); i < p.GlyphRend.ScreenWidth; i += int32(adv) { p.rend.Draw(p.gridMesh, gglm.NewTrMatId().Translate(gglm.NewVec3(float32(i)+0.5, sizeY/2, 0)).Scale(gglm.NewVec3(1, sizeY, 1)), p.gridMat) } diff --git a/res/shaders/glyph.glsl b/res/shaders/glyph.glsl index 2ea3e9d..17b2aac 100755 --- a/res/shaders/glyph.glsl +++ b/res/shaders/glyph.glsl @@ -1,41 +1,43 @@ //shader:vertex #version 410 -layout(location=0) in vec3 vertPosIn; -layout(location=1) in vec2[4] vertUV0STIn; //[(u,v), (u+sizeU,v), (u,v+sizeV), (u+sizeU,v+sizeV)] -layout(location=5) in vec4 vertColorIn; -layout(location=6) in vec3 modelPos; -layout(location=7) in vec3 modelScale; +//aVertPos must be in the range [0,1] +layout(location=0) in vec3 aVertPos; +layout(location=1) in vec2 aUV0; +layout(location=2) in vec4 aVertColor; +layout(location=3) in vec3 aModelPos; +layout(location=4) in vec3 aModelScale; -out vec2 vertUV0; -out vec4 vertColor; -out vec3 fragPos; +out vec2 v2fUV0; +out vec4 v2fColor; +out vec3 v2fFragPos; //MVP = Model View Projection uniform mat4 projViewMat; +uniform vec2 sizeUV; void main() { mat4 modelMat = mat4( - modelScale.x, 0.0, 0.0, 0.0, - 0.0, modelScale.y, 0.0, 0.0, - 0.0, 0.0, modelScale.z, 0.0, - modelPos.x, modelPos.y, modelPos.z, 1.0 + aModelScale.x, 0.0, 0.0, 0.0, + 0.0, aModelScale.y, 0.0, 0.0, + 0.0, 0.0, aModelScale.z, 0.0, + aModelPos.x, aModelPos.y, aModelPos.z, 1.0 ); - vertUV0 = vertUV0STIn[gl_VertexID]; - vertColor = vertColorIn; - fragPos = vec3(modelMat * vec4(vertPosIn, 1.0)); + v2fUV0 = aUV0 + sizeUV*aVertPos.xy; + v2fColor = aVertColor; + v2fFragPos = vec3(modelMat * vec4(aVertPos, 1.0)); - gl_Position = projViewMat * modelMat * vec4(vertPosIn, 1.0); + gl_Position = projViewMat * modelMat * vec4(aVertPos, 1.0); } //shader:fragment #version 410 -in vec4 vertColor; -in vec2 vertUV0; -in vec3 fragPos; +in vec4 v2fColor; +in vec2 v2fUV0; +in vec3 v2fFragPos; out vec4 fragColor; @@ -43,13 +45,13 @@ uniform sampler2D diffTex; void main() { - vec4 texColor = texture(diffTex, vertUV0); + vec4 texColor = texture(diffTex, v2fUV0); // if (texColor.r == 0) // { // fragColor = vec4(0,1,0,0.25); // } // else { - fragColor = vec4(vertColor.rgb, texColor.r); + fragColor = vec4(v2fColor.rgb, texColor.r); } }