From 7feb9630e6f98a806d5a96d5ee11ddfe1b79e390 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sat, 29 Apr 2023 18:39:00 +0200 Subject: [PATCH] use a texture array instead of texture --- data/ofxGPUFont/shaders/DEBUG_ES3/font.frag | 36 ++- src/gpufont/font.hpp | 290 ++++++++++++++------ 2 files changed, 238 insertions(+), 88 deletions(-) diff --git a/data/ofxGPUFont/shaders/DEBUG_ES3/font.frag b/data/ofxGPUFont/shaders/DEBUG_ES3/font.frag index ca0413d..5af667f 100644 --- a/data/ofxGPUFont/shaders/DEBUG_ES3/font.frag +++ b/data/ofxGPUFont/shaders/DEBUG_ES3/font.frag @@ -2,6 +2,7 @@ precision highp float; precision highp isampler2D; precision highp sampler2D; +precision highp sampler2DArray; // Based on: http://wdobbie.com/post/gpu-text-rendering-with-vector-textures/ @@ -12,10 +13,18 @@ struct Glyph { struct Curve { vec2 p0, p1, p2; }; +// CURVE_SIZE +// is the amount of vec2's in the curve +// we can hardcode this, as we can simply +// count the floats in the struct +// 6 floats => 3 vec2's +#define CURVE_SIZE 3 uniform isampler2D glyphs; -uniform sampler2D curves; -uniform sampler2D iChannel0; +uniform sampler2DArray curves; +ivec3 curvesTextureSize; +int curveBreakPoint; +//uniform sampler2D iChannel0; //uniform vec4 color; // Controls for debugging and exploring: @@ -49,9 +58,14 @@ Glyph loadGlyph(int index) { Curve loadCurve(int index) { Curve result; - result.p0 = texelFetch(curves, ivec2(3*index+0, 0), 0).xy; - result.p1 = texelFetch(curves, ivec2(3*index+1, 0), 0).xy; - result.p2 = texelFetch(curves, ivec2(3*index+2, 0), 0).xy; + //int dw = curveBreakPoint; // width including dead space + int dw = curvesTextureSize[0]; + int w = dw - (dw % CURVE_SIZE); // effectual width + int ix = (index * CURVE_SIZE) % w; + int iz = ((index * CURVE_SIZE) + 2) / w; + result.p0 = texelFetch(curves, ivec3(ix+0, 0, iz), 0).xy; + result.p1 = texelFetch(curves, ivec3(ix+1, 0, iz), 0).xy; + result.p2 = texelFetch(curves, ivec3(ix+2, 0, iz), 0).xy; return result; } @@ -109,13 +123,23 @@ vec2 rotate(vec2 v) { } void main() { + + curvesTextureSize = textureSize(curves, 0); + curveBreakPoint = (curvesTextureSize[0] * 2) / 3; + //int w = curvesTextureSize[0]; // 16384 / 2 = 8192 + float alpha = 0.0; // Inverse of the diameter of a pixel in uv units for anti-aliasing. vec2 inverseDiameter = 1.0 / (antiAliasingWindowSize * fwidth(uv)); + vec4 c = color; + Glyph glyph = loadGlyph(bufferIndex); for (int i = 0; i < glyph.count; i++) { + //if (glyph.start + i > curveBreakPoint) { + //c = vec4(1.0,0.0,0.0,1.0); + //} Curve curve = loadCurve(glyph.start + i); vec2 p0 = curve.p0 - uv; @@ -133,7 +157,7 @@ void main() { } alpha = clamp(alpha, 0.0, 1.0); - result = vec4(color.rgb, color.a * alpha); + result = vec4(c.rgb, c.a * alpha); if (enableControlPointsVisualization) { // Visualize control points. diff --git a/src/gpufont/font.hpp b/src/gpufont/font.hpp index a387aeb..d4eb652 100644 --- a/src/gpufont/font.hpp +++ b/src/gpufont/font.hpp @@ -3,13 +3,18 @@ // This file was extracted to improve the organization of the code, // but it is still compiled in the "main.cpp" translation unit, // because both files have mostly the same dependencies (OpenGL, GLM, FreeType). +#include "fwd.hpp" #include "ofMain.h" #include "ofUtils.h" +#include #include +#include #ifdef TARGET_OPENGLES - #include - #include + //#include + //#include + //#include + //#include #endif #include #include FT_FREETYPE_H @@ -190,7 +195,7 @@ class Font { }; struct BufferCurve { - float x0, y0, x1, y1, x2, y2; + glm::float32_t x0, y0, x1, y1, x2, y2; }; // keep this, just because I wrote it @@ -424,6 +429,12 @@ class Font { gpuTextureOffset(gpuTextureOffset){ // TODO: modularize init, so we can initialize with settings and text + + if(gl_max_texture_size == -1){ + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_texture_size); + cout << "GL_MAX_TEXTURE_SIZE: " << ofToString(gl_max_texture_size) << endl; + } + if(hinting){ loadFlags = FT_LOAD_NO_BITMAP; kerningMode = FT_KERNING_DEFAULT; @@ -486,7 +497,7 @@ class Font { glVertexAttribPointer(3, 4, GL_FLOAT, false, sizeof(BufferVertex), (void *)offsetof(BufferVertex, r)); glBindVertexArray(0); - uploadBuffers(); + //uploadBuffers(); #ifndef TARGET_OPENGLES glBindTexture(GL_TEXTURE_BUFFER, glyphTextureName); @@ -499,6 +510,82 @@ class Font { #else #endif } + int amountCurveTextureLayers = 1; + int widthCurveTextureLayer = 0; + int previousCurveBufferSize = 0; + int bufferCurveSize = (sizeof(BufferCurve) / (sizeof(glm::float32_t) * 1)); + + void generateCurveTextureLayers(int amountCurves){ + //cout << "generateCurveTextureLayers()" << endl; + int curveBufferSize = amountCurves * bufferCurveSize; + curveBufferSize = calculateUpperPowerOfTwo(curveBufferSize); + int overshoot = curveBufferSize % gl_max_texture_size == 0 ? 0 : 1; + int neededLayers = (curveBufferSize / gl_max_texture_size) + overshoot; + // LOL + //cout << "\tlayers needed: " << ofToString(neededLayers) << endl + //<< "\tcurveBufferSize p2: " << ofToString(curveBufferSize) << endl + //<< "\tcurveBufferSize or: " << ofToString(bufferCurves.size()) << endl + //<< "\tsize of BufferCurve: " << ofToString(bufferCurveSize) << endl + //<< "\ttotal glyphs: " << ofToString(glyphs.size()) << endl + //; + if(neededLayers != amountCurveTextureLayers + || curveBufferSize > previousCurveBufferSize){ + glDeleteTextures(1, &curveBufferName); + glGenTextures(1, &curveBufferName); + //glActiveTexture(GL_TEXTURE0 + curveBufferUnit); + glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName); + if(neededLayers == 1){ + widthCurveTextureLayer = curveBufferSize; + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RG32F, curveBufferSize, 1, neededLayers); + }else{ + if(neededLayers > 4){ + neededLayers = 4; + // TODO: implement multiple arrays + // LOL + //cout << "more layers than 4 needed" << endl; + }else{ + } + widthCurveTextureLayer = gl_max_texture_size; + glTexStorage3D(GL_TEXTURE_2D_ARRAY, + 1, + GL_RG32F, + gl_max_texture_size, + 1, + neededLayers); + } + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + //glActiveTexture(GL_TEXTURE0); + amountCurveTextureLayers = neededLayers; + } + previousCurveBufferSize = curveBufferSize; + } + void uploadCurveTextureLayers(){ + glActiveTexture(GL_TEXTURE0 + curveBufferUnit); + glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName); + for(int i = 0; i < amountCurveTextureLayers; i++){ + int width = widthCurveTextureLayer; + int height = 1; + int layerPointer = i == 0 ? 0 : + 5461; + //(i * gl_max_texture_size) - (gl_max_texture_size % bufferCurveSize); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, + 0, + 0, + 0, + i, + width, + height, + 1, + GL_RG, + GL_FLOAT, + &bufferCurves[layerPointer]); + } + glActiveTexture(GL_TEXTURE0); + } ~Font(){ glDeleteVertexArrays(1, &vao); @@ -565,7 +652,8 @@ class Font { it++; } - uploadBuffers(); + dirtyBuffers = true; + //uploadBuffers(); } void prepareGlyphsForText(const std::vector & variationText, FT_Library & library, @@ -579,8 +667,10 @@ class Font { } int i = 0; + string text = ""; for(const auto & glyphIdentity : variationText){ const uint32_t & charcode = glyphIdentity.charcode; + text += charcode; if(charcode == '\0'){ // do we need this? break; } @@ -608,13 +698,24 @@ class Font { continue; } + // LOL + //cout << (char)glyphIdentity.charcode + //<< " " << ofToString(glyphIdentity.coords[0]) << " " + //<< " (" << ofToString(bufferCurves.size()) << ")" << endl; + buildGlyph(glyphIdentity, glyphIndex); + + changed = true; i++; } if(changed){ + dirtyBuffers = true; + glyphBufferUnit = 1; + curveBufferUnit = 2; + generateCurveTextureLayers(bufferCurves.size()); // Reupload the full buffer contents. To make this even more // dynamic, the buffers could be overallocated and only the added // data could be uploaded. @@ -625,6 +726,7 @@ class Font { private: bool initializedGlyphsBufferTexture = false; bool initializedCurvesBufferTexture = false; + bool dirtyBuffers = false; int calcTextureOffset(){ return gpuTextureOffset * 2; } @@ -637,11 +739,34 @@ class Font { //cout << "GL_MAX_ARRAY_TEXTURE_LAYERS: " << ofToString(size) << endl; //size = 0; - GLsizei width = 16384; + GLsizei width = gl_max_texture_size; GLsizei height = 1; GLsizei layerCount = 4; GLsizei mipLevelCount = 1; + //GLsizei bufSize = 42; + //GLint * params[bufSize]; + //for(int i = 0; i < bufSize; i++){ + //params[i] = 0; + //} + //glGetInternalformativ(GL_TEXTURE_2D_ARRAY, + //GL_RG32F, + //GL_MAX_DEPTH, + //bufSize / 2, + //params[0]); + //glGetInternalformativ(GL_TEXTURE_2D_ARRAY, + //GL_RG32F, + //GL_MAX_LAYERS, + //bufSize / 2, + //params[21]); + //stringstream s; + //s << "and here:" << endl; + //for(int i = 0; i < bufSize; i++){ + //s << ofToString(i) << ": " << ofToString(params[i]) << endl; + //} + //cout << s.str(); + + // Read you texels here. In the current example, we have 2*2*2 = 8 texels, with each texel being 4 GLubytes. GLubyte texels[layerCount][width * height]; for(int l = 0; l < layerCount; l++){ @@ -652,7 +777,7 @@ class Font { glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D_ARRAY, texture); // Allocate the storage. - glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipLevelCount, GL_RGBA8, width, height, layerCount); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipLevelCount, GL_RG32F, width, height, layerCount); // Upload pixel data. // The first 0 refers to the mipmap level (level 0, since there's only 1) // The following 2 zeroes refers to the x and y offsets in case you only want to specify a subrectangle. @@ -660,7 +785,7 @@ class Font { // Altogether you can specify a 3D box subset of the overall texture, but only one mip level at a time. for(int i = 0; i < (int)layerCount; i++){ //cout << "OFFSET " << ofToString(i) << endl; - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, layerCount, GL_RGBA, GL_UNSIGNED_BYTE, texels[i]); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, layerCount, GL_RG, GL_FLOAT, texels[i]); } // Always set reasonable texture parameters @@ -670,78 +795,79 @@ class Font { glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } void uploadBuffers(){ - //cout << "bufferGlyphs.size(): " << bufferGlyphs.size() << endl; - //cout << "bufferCurves.size(): " << bufferCurves.size() << endl; - #ifndef TARGET_OPENGLES - glBindBuffer(GL_TEXTURE_BUFFER, glyphBufferName); - glBufferData(GL_TEXTURE_BUFFER, sizeof(BufferGlyph) * bufferGlyphs.size(), bufferGlyphs.data(), GL_STATIC_DRAW); - glBindBuffer(GL_TEXTURE_BUFFER, 0); + if(dirtyBuffers){ + //cout << "bufferGlyphs.size(): " << bufferGlyphs.size() << endl; + //cout << "bufferCurves.size(): " << bufferCurves.size() << endl; + #ifndef TARGET_OPENGLES + glBindBuffer(GL_TEXTURE_BUFFER, glyphBufferName); + glBufferData(GL_TEXTURE_BUFFER, sizeof(BufferGlyph) * bufferGlyphs.size(), bufferGlyphs.data(), GL_STATIC_DRAW); + glBindBuffer(GL_TEXTURE_BUFFER, 0); - glBindBuffer(GL_TEXTURE_BUFFER, curveBufferName); - glBufferData(GL_TEXTURE_BUFFER, sizeof(BufferCurve) * bufferCurves.size(), bufferCurves.data(), GL_STATIC_DRAW); - glBindBuffer(GL_TEXTURE_BUFFER, 0); - #else - int size; - //glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &size); - //cout << "GL_MAX_SHADER_STORAGE_BLOCK_SIZE: " << ofToString(size) << endl; - glyphBufferUnit = 1; - curveBufferUnit = 2; - int bufferGlyphsSize = bufferGlyphs.size(); - if(!isPowerOfTwo(bufferGlyphsSize)){ - bufferGlyphsSize = calculateUpperPowerOfTwo(bufferGlyphsSize); - } - glActiveTexture(GL_TEXTURE0 + glyphBufferUnit); - glBindTexture(GL_TEXTURE_2D, glyphBufferName); - if(!initializedGlyphsBufferTexture){ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, - GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, - GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - initializedGlyphsBufferTexture = true; - } - glTexImage2D(GL_TEXTURE_2D, - 0, - GL_RG32I, - bufferGlyphsSize, - 1, - 0, - GL_RG_INTEGER, - GL_INT, - bufferGlyphs.data()); + glBindBuffer(GL_TEXTURE_BUFFER, curveBufferName); + glBufferData(GL_TEXTURE_BUFFER, sizeof(BufferCurve) * bufferCurves.size(), bufferCurves.data(), GL_STATIC_DRAW); + glBindBuffer(GL_TEXTURE_BUFFER, 0); + #else + int size; + //glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &size); + //cout << "GL_MAX_SHADER_STORAGE_BLOCK_SIZE: " << ofToString(size) << endl; + int bufferGlyphsSize = bufferGlyphs.size() * sizeof(BufferGlyph); + if(!isPowerOfTwo(bufferGlyphsSize)){ + bufferGlyphsSize = calculateUpperPowerOfTwo(bufferGlyphsSize); + } + glActiveTexture(GL_TEXTURE0 + glyphBufferUnit); + glBindTexture(GL_TEXTURE_2D, glyphBufferName); + if(!initializedGlyphsBufferTexture){ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + initializedGlyphsBufferTexture = true; + } + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RG32I, + bufferGlyphsSize, + 1, + 0, + GL_RG_INTEGER, + GL_INT, + bufferGlyphs.data()); - int bufferCurvesSize = bufferCurves.size(); - if(!isPowerOfTwo(bufferCurvesSize)){ - bufferCurvesSize = calculateUpperPowerOfTwo(bufferCurvesSize); - } - cout << "bufferCurvesSize: " << ofToString(bufferCurvesSize) << endl; - glActiveTexture(GL_TEXTURE0 + curveBufferUnit); - glBindTexture(GL_TEXTURE_2D, curveBufferName); - if(!initializedCurvesBufferTexture){ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, - GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, - GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - initializedCurvesBufferTexture = true; - } - glTexImage2D(GL_TEXTURE_2D, - 0, - GL_RG32F, - bufferCurvesSize * sizeof(glm::highp_f32), - 1, - 0, - GL_RG, - GL_FLOAT, - bufferCurves.data()); - glActiveTexture(GL_TEXTURE0); - #endif + //int bufferCurvesSize = bufferCurves.size() * bufferCurveSize; + //if(!isPowerOfTwo(bufferCurvesSize)){ + //bufferCurvesSize = calculateUpperPowerOfTwo(bufferCurvesSize); + //} + //glActiveTexture(GL_TEXTURE0 + curveBufferUnit); + //glBindTexture(GL_TEXTURE_2D, curveBufferName); + //if(!initializedCurvesBufferTexture){ + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + //GL_NEAREST); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + //GL_NEAREST); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + //initializedCurvesBufferTexture = true; + //} + //glTexImage2D(GL_TEXTURE_2D, + //0, + //GL_RG32F, + //bufferCurvesSize, + //1, + //0, + //GL_RG, + //GL_FLOAT, + //bufferCurves.data()); + glActiveTexture(GL_TEXTURE0); + uploadCurveTextureLayers(); + #endif + dirtyBuffers = false; + } } void buildGlyph(const GlyphIdentity & glyphIdentity, @@ -976,8 +1102,6 @@ class Font { glActiveTexture(GL_TEXTURE0); #else - // Important! The GLint for the location needs to stick around - // we cannot reuse a location as in Desktop for some reason glyphsUniformLocation = glGetUniformLocation(program, "glyphs"); glUniform1i(glyphsUniformLocation, glyphBufferUnit); curvesUniformLocation = glGetUniformLocation(program, "curves"); @@ -987,7 +1111,7 @@ class Font { glBindTexture(GL_TEXTURE_2D, glyphBufferName); glActiveTexture(GL_TEXTURE0 + curveBufferUnit); - glBindTexture(GL_TEXTURE_2D, curveBufferName); + glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName); glActiveTexture(GL_TEXTURE0); #endif @@ -1219,9 +1343,11 @@ class Font { std::vector bufferGlyphs; std::vector bufferCurves; - //std::unordered_map glyphs; + std::vector bufferCurveLayerPointers = {0}; std::unordered_map glyphs; + static GLint gl_max_texture_size; + public: // ID of the shader program to use. GLuint program = 0;