mirror of
https://github.com/bloeys/nterm.git
synced 2025-12-29 06:28:20 +00:00
Fix bug with atlas texture not being updated on GPU
This commit is contained in:
143
glyphs/glyphs.go
143
glyphs/glyphs.go
@ -1,6 +1,7 @@
|
||||
package glyphs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"unicode"
|
||||
|
||||
@ -85,15 +86,6 @@ func (gr *GlyphRend) DrawTextOpenGL(text string, screenPos *gglm.Vec3, color *gg
|
||||
screenHeightF32 := float32(gr.ScreenHeight)
|
||||
screenPos.Set(screenPos.X()*screenWidthF32, screenPos.Y()*screenHeightF32, screenPos.Z())
|
||||
|
||||
//The projection matrix fits the screen size. This is needed so we can size and position characters correctly.
|
||||
projMtx := gglm.Ortho(0, screenWidthF32, screenHeightF32, 0, 0.1, 10)
|
||||
viewMtx := gglm.LookAt(gglm.NewVec3(0, 0, -1), gglm.NewVec3(0, 0, 0), gglm.NewVec3(0, 1, 0))
|
||||
projViewMtx := projMtx.Clone().Mul(viewMtx)
|
||||
|
||||
gr.GlyphMat.DiffuseTex = gr.AtlasTex.TexID
|
||||
gr.GlyphMat.SetUnifMat4("projViewMat", &projViewMtx.Mat4)
|
||||
gr.GlyphMat.Bind()
|
||||
|
||||
//Prepass to pre-allocate the buffer
|
||||
rs := []rune(text)
|
||||
const floatsPerGlyph = 18
|
||||
@ -152,7 +144,6 @@ func (gr *GlyphRend) Draw() {
|
||||
gr.GlyphMat.Bind()
|
||||
|
||||
gl.DrawElementsInstanced(gl.TRIANGLES, gr.GlyphMesh.Buf.IndexBufCount, gl.UNSIGNED_INT, gl.PtrOffset(0), gr.GlyphCount)
|
||||
gr.InstancedBuf.UnBind()
|
||||
|
||||
gr.GlyphCount = 0
|
||||
gr.GlyphVBO = []float32{}
|
||||
@ -161,6 +152,7 @@ func (gr *GlyphRend) Draw() {
|
||||
func (gr *GlyphRend) SetFace(fontOptions *truetype.Options) {
|
||||
face := truetype.NewFace(gr.Atlas.Font, fontOptions)
|
||||
gr.Atlas = NewFontAtlasFromFont(gr.Atlas.Font, face, uint(fontOptions.Size))
|
||||
gr.updateFontAtlasTexture("temp-atlas")
|
||||
}
|
||||
|
||||
func (gr *GlyphRend) SetFontFromFile(fontFile string, fontOptions *truetype.Options) error {
|
||||
@ -171,33 +163,36 @@ func (gr *GlyphRend) SetFontFromFile(fontFile string, fontOptions *truetype.Opti
|
||||
}
|
||||
|
||||
gr.Atlas = atlas
|
||||
gr.updateFontAtlasTexture(fontFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *GlyphRend) SetScreenSize(screenWidth, screenHeight int32) {
|
||||
gr.ScreenWidth = screenWidth
|
||||
gr.ScreenHeight = screenHeight
|
||||
//updateFontAtlasTexture uploads the texture representing the font atlas to the GPU
|
||||
//and updates the GlyphRend.AtlasTex field.
|
||||
//
|
||||
//Any old textures are deleted
|
||||
func (gr *GlyphRend) updateFontAtlasTexture(fontFile string) error {
|
||||
|
||||
if gr.AtlasTex != nil {
|
||||
gl.DeleteTextures(1, &gr.AtlasTex.TexID)
|
||||
}
|
||||
|
||||
func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, screenHeight int32) (*GlyphRend, error) {
|
||||
|
||||
atlas, err := NewFontAtlasFromFile(fontFile, fontOptions)
|
||||
pngFileName := fontFile + "-atlas.png"
|
||||
err := SaveImgToPNG(gr.Atlas.Img, pngFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Create OpenGL texture
|
||||
pngFileName := fontFile + "atlas.png"
|
||||
err = SaveImgToPNG(atlas.Img, pngFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
defer os.Remove(pngFileName)
|
||||
|
||||
atlasTex, err := assets.LoadPNGTexture(pngFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
os.Remove(pngFileName)
|
||||
gr.AtlasTex = &atlasTex
|
||||
|
||||
//TODO: We want a function to load without caching. For now we clear manually
|
||||
delete(assets.Textures, atlasTex.TexID)
|
||||
delete(assets.TexturePaths, pngFileName)
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, atlasTex.TexID)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
@ -206,8 +201,43 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gr *GlyphRend) SetScreenSize(screenWidth, screenHeight int32) {
|
||||
|
||||
gr.ScreenWidth = screenWidth
|
||||
gr.ScreenHeight = screenHeight
|
||||
|
||||
//The projection matrix fits the screen size. This is needed so we can size and position characters correctly.
|
||||
projMtx := gglm.Ortho(0, float32(screenWidth), float32(screenHeight), 0, 0.1, 10)
|
||||
viewMtx := gglm.LookAt(gglm.NewVec3(0, 0, -1), gglm.NewVec3(0, 0, 0), gglm.NewVec3(0, 1, 0))
|
||||
projViewMtx := projMtx.Clone().Mul(viewMtx)
|
||||
|
||||
gr.GlyphMat.DiffuseTex = gr.AtlasTex.TexID
|
||||
gr.GlyphMat.SetUnifMat4("projViewMat", &projViewMtx.Mat4)
|
||||
}
|
||||
|
||||
func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, screenHeight int32) (*GlyphRend, error) {
|
||||
|
||||
gr := &GlyphRend{
|
||||
GlyphCount: 0,
|
||||
GlyphVBO: make([]float32, 0),
|
||||
}
|
||||
|
||||
atlas, err := NewFontAtlasFromFile(fontFile, fontOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gr.Atlas = atlas
|
||||
|
||||
err = gr.updateFontAtlasTexture(fontFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Create glyph mesh
|
||||
glyphMesh := &meshes.Mesh{
|
||||
gr.GlyphMesh = &meshes.Mesh{
|
||||
Name: "glypQuad",
|
||||
|
||||
//VertPos, UV, Color; Instanced attributes are stored separately
|
||||
@ -216,28 +246,29 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s
|
||||
),
|
||||
}
|
||||
|
||||
glyphMesh.Buf.SetData([]float32{
|
||||
gr.GlyphMesh.Buf.SetData([]float32{
|
||||
-0.5, -0.5, 0,
|
||||
0.5, -0.5, 0,
|
||||
-0.5, 0.5, 0,
|
||||
0.5, 0.5, 0,
|
||||
})
|
||||
|
||||
glyphMesh.Buf.SetIndexBufData([]uint32{
|
||||
gr.GlyphMesh.Buf.SetIndexBufData([]uint32{
|
||||
0, 1, 2,
|
||||
1, 3, 2,
|
||||
})
|
||||
|
||||
//Setup material
|
||||
glyphMat := materials.NewMaterial("glyphMat", "./res/shaders/glyph")
|
||||
glyphMat.SetAttribute(glyphMesh.Buf)
|
||||
gr.GlyphMat = materials.NewMaterial("glyphMat", "./res/shaders/glyph")
|
||||
gr.GlyphMat.SetAttribute(gr.GlyphMesh.Buf)
|
||||
gr.GlyphMat.DiffuseTex = gr.AtlasTex.TexID
|
||||
|
||||
//Create instanced buf and set its instanced attributes.
|
||||
//Multiple VBOs under one VAO, one VBO for vertex data, and one VBO for instanced data.
|
||||
instancedBuf := buffers.Buffer{
|
||||
VAOID: glyphMesh.Buf.VAOID,
|
||||
gr.InstancedBuf = buffers.Buffer{
|
||||
VAOID: gr.GlyphMesh.Buf.VAOID,
|
||||
}
|
||||
instancedBuf.SetLayout(
|
||||
gr.InstancedBuf.SetLayout(
|
||||
buffers.Element{ElementType: buffers.DataTypeVec2}, //UVST0
|
||||
buffers.Element{ElementType: buffers.DataTypeVec2}, //UVST1
|
||||
buffers.Element{ElementType: buffers.DataTypeVec2}, //UVST2
|
||||
@ -248,67 +279,55 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s
|
||||
buffers.Element{ElementType: buffers.DataTypeVec3}, //ModelScale
|
||||
)
|
||||
|
||||
gl.GenBuffers(1, &instancedBuf.BufID)
|
||||
if instancedBuf.BufID == 0 {
|
||||
panic("Failed to create openGL buffer")
|
||||
gl.GenBuffers(1, &gr.InstancedBuf.BufID)
|
||||
if gr.InstancedBuf.BufID == 0 {
|
||||
return nil, errors.New("failed to create OpenGL VBO buffer")
|
||||
}
|
||||
|
||||
instancedBuf.Bind()
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, instancedBuf.BufID)
|
||||
layout := instancedBuf.GetLayout()
|
||||
gr.InstancedBuf.Bind()
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, gr.InstancedBuf.BufID)
|
||||
layout := gr.InstancedBuf.GetLayout()
|
||||
|
||||
//4 UV values
|
||||
uvEle := layout[0]
|
||||
gl.EnableVertexAttribArray(1)
|
||||
gl.VertexAttribPointer(1, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, instancedBuf.Stride, gl.PtrOffset(uvEle.Offset))
|
||||
gl.VertexAttribPointer(1, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(uvEle.Offset))
|
||||
gl.VertexAttribDivisor(1, 1)
|
||||
|
||||
uvEle = layout[1]
|
||||
gl.EnableVertexAttribArray(2)
|
||||
gl.VertexAttribPointer(2, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, instancedBuf.Stride, gl.PtrOffset(uvEle.Offset))
|
||||
gl.VertexAttribPointer(2, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(uvEle.Offset))
|
||||
gl.VertexAttribDivisor(2, 1)
|
||||
|
||||
uvEle = layout[2]
|
||||
gl.EnableVertexAttribArray(3)
|
||||
gl.VertexAttribPointer(3, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, instancedBuf.Stride, gl.PtrOffset(uvEle.Offset))
|
||||
gl.VertexAttribPointer(3, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(uvEle.Offset))
|
||||
gl.VertexAttribDivisor(3, 1)
|
||||
|
||||
uvEle = layout[3]
|
||||
gl.EnableVertexAttribArray(4)
|
||||
gl.VertexAttribPointer(4, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, instancedBuf.Stride, gl.PtrOffset(uvEle.Offset))
|
||||
gl.VertexAttribPointer(4, uvEle.ElementType.CompCount(), uvEle.ElementType.GLType(), false, gr.InstancedBuf.Stride, gl.PtrOffset(uvEle.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, instancedBuf.Stride, gl.PtrOffset(colorEle.Offset))
|
||||
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, instancedBuf.Stride, gl.PtrOffset(posEle.Offset))
|
||||
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, instancedBuf.Stride, gl.PtrOffset(scaleEle.Offset))
|
||||
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)
|
||||
instancedBuf.UnBind()
|
||||
gr.InstancedBuf.UnBind()
|
||||
|
||||
return &GlyphRend{
|
||||
Atlas: atlas,
|
||||
AtlasTex: &atlasTex,
|
||||
|
||||
GlyphMesh: glyphMesh,
|
||||
InstancedBuf: instancedBuf,
|
||||
GlyphMat: glyphMat,
|
||||
|
||||
GlyphCount: 0,
|
||||
GlyphVBO: make([]float32, 0),
|
||||
|
||||
ScreenWidth: screenWidth,
|
||||
ScreenHeight: screenHeight,
|
||||
}, nil
|
||||
gr.SetScreenSize(screenWidth, screenHeight)
|
||||
return gr, nil
|
||||
}
|
||||
|
||||
27
main.go
27
main.go
@ -1,8 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bloeys/gglm/gglm"
|
||||
"github.com/bloeys/nmage/engine"
|
||||
"github.com/bloeys/nmage/input"
|
||||
@ -22,6 +20,7 @@ type program struct {
|
||||
imguiInfo nmageimgui.ImguiInfo
|
||||
|
||||
FontSize uint32
|
||||
Dpi float64
|
||||
GlyphRend *glyphs.GlyphRend
|
||||
}
|
||||
|
||||
@ -71,10 +70,14 @@ func main() {
|
||||
|
||||
func (p *program) Init() {
|
||||
|
||||
w, h := p.win.SDLWin.GetSize()
|
||||
dpi, _, _, err := sdl.GetDisplayDPI(0)
|
||||
if err != nil {
|
||||
panic("Failed to get display DPI. Err: " + err.Error())
|
||||
}
|
||||
println("DPI:", dpi)
|
||||
|
||||
var err error
|
||||
p.GlyphRend, err = glyphs.NewGlyphRend("./res/fonts/Consolas.ttf", &truetype.Options{Size: float64(p.FontSize), DPI: 72}, w, h)
|
||||
w, h := p.win.SDLWin.GetSize()
|
||||
p.GlyphRend, err = glyphs.NewGlyphRend("./res/fonts/Consolas.ttf", &truetype.Options{Size: float64(p.FontSize), DPI: p.Dpi}, w, h)
|
||||
if err != nil {
|
||||
panic("Failed to create atlas from font file. Err: " + err.Error())
|
||||
}
|
||||
@ -84,11 +87,7 @@ func (p *program) Start() {
|
||||
|
||||
}
|
||||
|
||||
var frameStartTime time.Time
|
||||
var frameTime time.Duration = 16 * time.Millisecond
|
||||
|
||||
func (p *program) FrameStart() {
|
||||
frameStartTime = time.Now()
|
||||
}
|
||||
|
||||
func (p *program) Update() {
|
||||
@ -97,6 +96,10 @@ func (p *program) Update() {
|
||||
p.shouldRun = false
|
||||
}
|
||||
|
||||
if input.KeyClicked(sdl.K_SPACE) {
|
||||
p.handleWindowResize()
|
||||
}
|
||||
|
||||
fontSizeChanged := false
|
||||
if input.KeyClicked(sdl.K_KP_PLUS) {
|
||||
p.FontSize += 2
|
||||
@ -107,7 +110,7 @@ func (p *program) Update() {
|
||||
}
|
||||
|
||||
if fontSizeChanged {
|
||||
p.GlyphRend.SetFace(&truetype.Options{Size: float64(p.FontSize), DPI: 72})
|
||||
p.GlyphRend.SetFace(&truetype.Options{Size: float64(p.FontSize), DPI: p.Dpi})
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +123,6 @@ func (p *program) Render() {
|
||||
}
|
||||
|
||||
func (p *program) FrameEnd() {
|
||||
frameTime = time.Since(frameStartTime)
|
||||
}
|
||||
|
||||
func (g *program) GetWindow() *engine.Window {
|
||||
@ -140,6 +142,9 @@ func (p *program) Deinit() {
|
||||
}
|
||||
|
||||
func (p *program) handleWindowResize() {
|
||||
|
||||
println("Old size:", p.GlyphRend.ScreenWidth, ",", p.GlyphRend.ScreenHeight)
|
||||
w, h := p.win.SDLWin.GetSize()
|
||||
p.GlyphRend.SetScreenSize(w, h)
|
||||
println("New size:", w, ",", h, "\n")
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
#version 410
|
||||
|
||||
uniform sampler2D diffTex;
|
||||
|
||||
in vec4 vertColor;
|
||||
in vec2 vertUV0;
|
||||
in vec3 fragPos;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D diffTex;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 texColor = texture(diffTex, vertUV0);
|
||||
|
||||
@ -6,7 +6,6 @@ layout(location=5) in vec4 vertColorIn;
|
||||
layout(location=6) in vec3 modelPos;
|
||||
layout(location=7) in vec3 modelScale;
|
||||
|
||||
|
||||
out vec2 vertUV0;
|
||||
out vec4 vertColor;
|
||||
out vec3 fragPos;
|
||||
|
||||
Reference in New Issue
Block a user