use uniform buffer, though glitchy
This commit is contained in:
parent
b107dbf57b
commit
b2cfc9ffd8
4 changed files with 123 additions and 77 deletions
|
@ -13,15 +13,20 @@ struct Glyph {
|
||||||
struct Curve {
|
struct Curve {
|
||||||
vec2 p0, p1, p2;
|
vec2 p0, p1, p2;
|
||||||
};
|
};
|
||||||
|
|
||||||
// CURVE_SIZE
|
// CURVE_SIZE
|
||||||
// is the amount of vec2's in the curve
|
// is the amount of vec2's in the curve
|
||||||
// we can hardcode this, as we can simply
|
// we can hardcode this, as we can simply
|
||||||
// count the floats in the struct
|
// count the floats in the struct
|
||||||
// 6 floats => 3 vec2's
|
// 6 floats => 3 vec2's
|
||||||
#define CURVE_SIZE 3
|
#define CURVE_SIZE 3
|
||||||
|
const int CURVES_BUFFER_SIZE = {{CURVES_BUFFER_SIZE}};
|
||||||
|
|
||||||
|
layout(std140) uniform curves_t {
|
||||||
|
vec4 data[{{CURVES_BUFFER_SIZE}}];
|
||||||
|
} curves;
|
||||||
|
|
||||||
uniform isampler2D glyphs;
|
uniform isampler2D glyphs;
|
||||||
uniform sampler2DArray curves;
|
|
||||||
ivec3 curvesTextureSize;
|
ivec3 curvesTextureSize;
|
||||||
int curveBreakPoint;
|
int curveBreakPoint;
|
||||||
//uniform sampler2D iChannel0;
|
//uniform sampler2D iChannel0;
|
||||||
|
@ -57,16 +62,21 @@ Glyph loadGlyph(int index) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Curve loadCurve(int index) {
|
Curve loadCurve(int index) {
|
||||||
Curve result;
|
int index_a = int(floor((float(index) / 4.0) * 6.0));
|
||||||
//int dw = curveBreakPoint; // width including dead space
|
int index_b = index_a + 1;
|
||||||
int dw = curvesTextureSize[0];
|
vec4 a = curves.data[index_a];
|
||||||
int w = dw - (dw % CURVE_SIZE); // effectual width
|
vec4 b = curves.data[index_b];
|
||||||
int ix = (index * CURVE_SIZE) % w;
|
Curve curve;
|
||||||
int iz = ((index * CURVE_SIZE) + 2) / w;
|
if (index % 2 == 0) {
|
||||||
result.p0 = texelFetch(curves, ivec3(ix+0, 0, iz), 0).xy;
|
curve.p0 = vec2(a[0], a[1]);
|
||||||
result.p1 = texelFetch(curves, ivec3(ix+1, 0, iz), 0).xy;
|
curve.p1 = vec2(a[2], a[3]);
|
||||||
result.p2 = texelFetch(curves, ivec3(ix+2, 0, iz), 0).xy;
|
curve.p2 = vec2(b[0], b[1]);
|
||||||
return result;
|
} else {
|
||||||
|
curve.p0 = vec2(a[2], a[3]);
|
||||||
|
curve.p1 = vec2(b[0], b[1]);
|
||||||
|
curve.p2 = vec2(b[2], b[3]);
|
||||||
|
}
|
||||||
|
return curve;
|
||||||
}
|
}
|
||||||
|
|
||||||
float computeCoverage(float inverseDiameter, vec2 p0, vec2 p1, vec2 p2) {
|
float computeCoverage(float inverseDiameter, vec2 p0, vec2 p1, vec2 p2) {
|
||||||
|
@ -124,10 +134,6 @@ vec2 rotate(vec2 v) {
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
||||||
curvesTextureSize = textureSize(curves, 0);
|
|
||||||
curveBreakPoint = (curvesTextureSize[0] * 2) / 3;
|
|
||||||
//int w = curvesTextureSize[0]; // 16384 / 2 = 8192
|
|
||||||
|
|
||||||
float alpha = 0.0;
|
float alpha = 0.0;
|
||||||
|
|
||||||
// Inverse of the diameter of a pixel in uv units for anti-aliasing.
|
// Inverse of the diameter of a pixel in uv units for anti-aliasing.
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
#include "ofMain.h"
|
#include "ofMain.h"
|
||||||
#include "ofUtils.h"
|
#include "ofUtils.h"
|
||||||
|
|
||||||
|
#include "shader_catalog.hpp"
|
||||||
|
|
||||||
#include <GL/gl.h>
|
#include <GL/gl.h>
|
||||||
#include <GL/glext.h>
|
#include <GL/glext.h>
|
||||||
#ifdef TARGET_OPENGLES
|
#ifdef TARGET_OPENGLES
|
||||||
|
@ -429,14 +431,14 @@ class Font {
|
||||||
|
|
||||||
// If hinting is enabled, worldSize must be an integer and defines the font size in pixels used for hinting.
|
// If hinting is enabled, worldSize must be an integer and defines the font size in pixels used for hinting.
|
||||||
// Otherwise, worldSize can be an arbitrary floating-point value.
|
// Otherwise, worldSize can be an arbitrary floating-point value.
|
||||||
Font(FT_Face face, float worldSize = 1.0f, bool hinting = false, GLint bufferTargetType = GL_TEXTURE_2D_ARRAY) :
|
Font(FT_Face face, float worldSize = 1.0f, bool hinting = false, GLint bufferTargetType = GL_TEXTURE_2D_ARRAY, GLuint shaderProgram = 0) :
|
||||||
face(face),
|
face(face),
|
||||||
hinting(hinting),
|
hinting(hinting),
|
||||||
BUFFER_TARGET_TYPE(bufferTargetType),
|
BUFFER_TARGET_TYPE(bufferTargetType),
|
||||||
|
program(shaderProgram),
|
||||||
worldSize(worldSize){
|
worldSize(worldSize){
|
||||||
// TODO: modularize init, so we can initialize with settings and text
|
// TODO: modularize init, so we can initialize with settings and text
|
||||||
|
|
||||||
|
|
||||||
if(gl_max_texture_size == -1){
|
if(gl_max_texture_size == -1){
|
||||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_texture_size);
|
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_texture_size);
|
||||||
cout << "GL_MAX_TEXTURE_SIZE: " << ofToString(gl_max_texture_size) << endl;
|
cout << "GL_MAX_TEXTURE_SIZE: " << ofToString(gl_max_texture_size) << endl;
|
||||||
|
@ -463,22 +465,7 @@ class Font {
|
||||||
emSize = face->units_per_EM;
|
emSize = face->units_per_EM;
|
||||||
}
|
}
|
||||||
|
|
||||||
//int amounters = 0;
|
ubo_curves_index = glGetUniformBlockIndex(program, "curves_t");
|
||||||
//for(int i = 0; i < 1024; i++){
|
|
||||||
//testGL(10 + i);
|
|
||||||
//amounters = i + 1;
|
|
||||||
//cout << "added " << ofToString(amounters) << " LOL" << endl;
|
|
||||||
//bool hasError = false;
|
|
||||||
//GLenum err;
|
|
||||||
//while((err = glGetError()) != GL_NO_ERROR){
|
|
||||||
//hasError = true;
|
|
||||||
//// Process/log the error.
|
|
||||||
//}
|
|
||||||
//if(hasError){
|
|
||||||
//cout << "WHOOOPS" << endl;
|
|
||||||
//break;
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
glGenVertexArrays(1, &vao);
|
glGenVertexArrays(1, &vao);
|
||||||
|
|
||||||
|
@ -492,8 +479,13 @@ class Font {
|
||||||
glGenBuffers(1, &glyphBufferName);
|
glGenBuffers(1, &glyphBufferName);
|
||||||
glGenBuffers(1, &curveBufferName);
|
glGenBuffers(1, &curveBufferName);
|
||||||
#else
|
#else
|
||||||
|
if(BUFFER_TARGET_TYPE == GL_TEXTURE_2D_ARRAY){
|
||||||
glGenTextures(1, &glyphBufferName);
|
glGenTextures(1, &glyphBufferName);
|
||||||
glGenTextures(1, &curveBufferName);
|
glGenTextures(1, &curveBufferName);
|
||||||
|
}else{
|
||||||
|
glGenTextures(1, &glyphBufferName);
|
||||||
|
glGenBuffers(1, &curveBufferName);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
glBindVertexArray(vao);
|
glBindVertexArray(vao);
|
||||||
|
@ -526,7 +518,26 @@ class Font {
|
||||||
int widthCurveTextureLayer = 0;
|
int widthCurveTextureLayer = 0;
|
||||||
int previousCurveBufferSize = 0;
|
int previousCurveBufferSize = 0;
|
||||||
int bufferCurveSize = (sizeof(BufferCurve) / (sizeof(glm::float32_t) * 1));
|
int bufferCurveSize = (sizeof(BufferCurve) / (sizeof(glm::float32_t) * 1));
|
||||||
|
int shaderCurvesBufferSize = -1;
|
||||||
|
GLint ubo_curves_index;
|
||||||
|
|
||||||
|
void generateCurveBuffers(){
|
||||||
|
int shaderBufferByteSize = sizeof(BufferCurve) * shaderCurvesBufferSize;
|
||||||
|
shaderBufferCurves = bufferCurves;
|
||||||
|
shaderBufferCurves.resize(shaderCurvesBufferSize);
|
||||||
|
|
||||||
|
//cout << "generate curve buffer" << endl
|
||||||
|
//<< "\t shaderBufferByteSize: " << ofToString(shaderBufferByteSize)
|
||||||
|
//<< "\t bufferCurves.size(): " << ofToString(bufferCurves.size())
|
||||||
|
//<< "\t shaderBufferCurves.size(): " << ofToString(shaderBufferCurves.size())
|
||||||
|
//<< endl;
|
||||||
|
|
||||||
|
ubo_curves_index = glGetUniformBlockIndex(program, "curves_t");
|
||||||
|
glBindBufferBase(GL_UNIFORM_BUFFER, 0, curveBufferName);
|
||||||
|
glBufferData(GL_UNIFORM_BUFFER, shaderBufferByteSize, shaderBufferCurves.data(), GL_DYNAMIC_DRAW);
|
||||||
|
glUniformBlockBinding(program, ubo_curves_index, 0);
|
||||||
|
|
||||||
|
}
|
||||||
void generateCurveTextureLayers(int amountCurves){
|
void generateCurveTextureLayers(int amountCurves){
|
||||||
//cout << "generateCurveTextureLayers()" << endl;
|
//cout << "generateCurveTextureLayers()" << endl;
|
||||||
int curveBufferSize = amountCurves * bufferCurveSize;
|
int curveBufferSize = amountCurves * bufferCurveSize;
|
||||||
|
@ -575,6 +586,11 @@ class Font {
|
||||||
}
|
}
|
||||||
previousCurveBufferSize = curveBufferSize;
|
previousCurveBufferSize = curveBufferSize;
|
||||||
}
|
}
|
||||||
|
void uploadCurveBuffer(){
|
||||||
|
|
||||||
|
glBindBufferBase(GL_UNIFORM_BUFFER, 0, curveBufferName);
|
||||||
|
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(BufferCurve) * bufferCurves.size(), bufferCurves.data());
|
||||||
|
}
|
||||||
void uploadCurveTextureLayers(){
|
void uploadCurveTextureLayers(){
|
||||||
glActiveTexture(GL_TEXTURE0 + curveBufferUnit);
|
glActiveTexture(GL_TEXTURE0 + curveBufferUnit);
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName);
|
glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName);
|
||||||
|
@ -673,6 +689,8 @@ class Font {
|
||||||
}
|
}
|
||||||
void prepareGlyphsForText(const std::vector <GlyphIdentity> & variationText,
|
void prepareGlyphsForText(const std::vector <GlyphIdentity> & variationText,
|
||||||
FT_Library & library,
|
FT_Library & library,
|
||||||
|
shared_ptr <ShaderCatalog> shaderCatalog,
|
||||||
|
shared_ptr <ShaderCatalog::Entry> shader,
|
||||||
bool forceChange = false){
|
bool forceChange = false){
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
|
@ -731,6 +749,22 @@ class Font {
|
||||||
dirtyBuffers = true;
|
dirtyBuffers = true;
|
||||||
glyphBufferUnit = 1;
|
glyphBufferUnit = 1;
|
||||||
curveBufferUnit = 2;
|
curveBufferUnit = 2;
|
||||||
|
if(BUFFER_TARGET_TYPE == GL_UNIFORM_BUFFER){
|
||||||
|
int curvesBufferSize = getUboCurvesBufferSize(bufferCurves);
|
||||||
|
if(curvesBufferSize != shaderCurvesBufferSize){
|
||||||
|
shaderCurvesBufferSize = curvesBufferSize;
|
||||||
|
shaderCatalog->setReplacement("{{CURVES_BUFFER_SIZE}}", std::to_string(shaderCurvesBufferSize));
|
||||||
|
//cout << shaderCatalog->getReplacement("{{CURVES_BUFFER_SIZE}}") << " <<<< _ replacement for curves buffer size " << endl;
|
||||||
|
shaderCatalog->requestUpdate("font_ub", true);
|
||||||
|
shaderCatalog->update();
|
||||||
|
shader = shaderCatalog->get("font_ub");
|
||||||
|
program = shader->program;
|
||||||
|
generateCurveBuffers();
|
||||||
|
uploadBuffers();
|
||||||
|
}else{
|
||||||
|
uploadBuffers();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
generateCurveTextureLayers(bufferCurves.size());
|
generateCurveTextureLayers(bufferCurves.size());
|
||||||
// Reupload the full buffer contents. To make this even more
|
// Reupload the full buffer contents. To make this even more
|
||||||
// dynamic, the buffers could be overallocated and only the added
|
// dynamic, the buffers could be overallocated and only the added
|
||||||
|
@ -738,6 +772,27 @@ class Font {
|
||||||
uploadBuffers();
|
uploadBuffers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
int getUboCurvesBufferSize(const std::vector <BufferCurve> & bc){
|
||||||
|
int actualShaderBufferSize = ceil((bc.size() * 6.0) / 4.0);
|
||||||
|
int thresholdBase = 2048;
|
||||||
|
int n_max = gl_max_uniform_block_size / thresholdBase;
|
||||||
|
for(int i = 0; i < n_max + 1; i++){
|
||||||
|
int levelSize = thresholdBase * i;
|
||||||
|
// prevent flattering around a threshold
|
||||||
|
// when going a level down
|
||||||
|
// but immediately
|
||||||
|
if(actualShaderBufferSize <= levelSize){
|
||||||
|
if(shaderCurvesBufferSize == levelSize + thresholdBase){
|
||||||
|
return shaderCurvesBufferSize;
|
||||||
|
}
|
||||||
|
return levelSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// you want more? well, we don't have it :(
|
||||||
|
return gl_max_uniform_block_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool initializedGlyphsBufferTexture = false;
|
bool initializedGlyphsBufferTexture = false;
|
||||||
|
@ -849,35 +904,12 @@ class Font {
|
||||||
GL_RG_INTEGER,
|
GL_RG_INTEGER,
|
||||||
GL_INT,
|
GL_INT,
|
||||||
bufferGlyphs.data());
|
bufferGlyphs.data());
|
||||||
|
|
||||||
//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);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
if(BUFFER_TARGET_TYPE == GL_TEXTURE_2D_ARRAY){
|
||||||
uploadCurveTextureLayers();
|
uploadCurveTextureLayers();
|
||||||
|
}else if(BUFFER_TARGET_TYPE == GL_UNIFORM_BUFFER){
|
||||||
|
uploadCurveBuffer();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
dirtyBuffers = false;
|
dirtyBuffers = false;
|
||||||
}
|
}
|
||||||
|
@ -1183,14 +1215,19 @@ class Font {
|
||||||
#else
|
#else
|
||||||
glyphsUniformLocation = glGetUniformLocation(program, "glyphs");
|
glyphsUniformLocation = glGetUniformLocation(program, "glyphs");
|
||||||
glUniform1i(glyphsUniformLocation, glyphBufferUnit);
|
glUniform1i(glyphsUniformLocation, glyphBufferUnit);
|
||||||
curvesUniformLocation = glGetUniformLocation(program, "curves");
|
|
||||||
glUniform1i(curvesUniformLocation, curveBufferUnit);
|
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0 + glyphBufferUnit);
|
glActiveTexture(GL_TEXTURE0 + glyphBufferUnit);
|
||||||
glBindTexture(GL_TEXTURE_2D, glyphBufferName);
|
glBindTexture(GL_TEXTURE_2D, glyphBufferName);
|
||||||
|
|
||||||
|
if(BUFFER_TARGET_TYPE == GL_TEXTURE_2D_ARRAY){
|
||||||
|
curvesUniformLocation = glGetUniformLocation(program, "curves");
|
||||||
|
glUniform1i(curvesUniformLocation, curveBufferUnit);
|
||||||
glActiveTexture(GL_TEXTURE0 + curveBufferUnit);
|
glActiveTexture(GL_TEXTURE0 + curveBufferUnit);
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName);
|
glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName);
|
||||||
|
}else if(BUFFER_TARGET_TYPE == GL_UNIFORM_BUFFER){
|
||||||
|
glBindBuffer(GL_UNIFORM_BUFFER, curveBufferName);
|
||||||
|
glUniformBlockBinding(program, ubo_curves_index, 0);
|
||||||
|
}
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
#endif
|
#endif
|
||||||
|
@ -1428,7 +1465,8 @@ class Font {
|
||||||
|
|
||||||
std::vector <BufferGlyph> bufferGlyphs;
|
std::vector <BufferGlyph> bufferGlyphs;
|
||||||
std::vector <BufferCurve> bufferCurves;
|
std::vector <BufferCurve> bufferCurves;
|
||||||
std::vector <int> bufferCurveLayerPointers = {0};
|
std::vector <BufferCurve> shaderBufferCurves; // ubo
|
||||||
|
std::vector <int> bufferCurveLayerPointers = {0}; // t2a
|
||||||
std::unordered_map <GlyphIdentity, Glyph> glyphs;
|
std::unordered_map <GlyphIdentity, Glyph> glyphs;
|
||||||
|
|
||||||
static GLint gl_max_texture_size;
|
static GLint gl_max_texture_size;
|
||||||
|
@ -1446,6 +1484,7 @@ class Font {
|
||||||
static std::shared_ptr <Font> loadFont(FT_Library & library,
|
static std::shared_ptr <Font> loadFont(FT_Library & library,
|
||||||
const std::string & filename,
|
const std::string & filename,
|
||||||
GLint bufferTargetType,
|
GLint bufferTargetType,
|
||||||
|
GLuint shaderProgram,
|
||||||
float worldSize = 1.0f,
|
float worldSize = 1.0f,
|
||||||
bool hinting = false){
|
bool hinting = false){
|
||||||
std::string error;
|
std::string error;
|
||||||
|
@ -1457,16 +1496,17 @@ static std::shared_ptr <Font> loadFont(FT_Library & library,
|
||||||
std::cout << "ofxGPUFont::font.hpp[" << __LINE__ << "] FT loaded " << filename << ": " << error << std::endl;
|
std::cout << "ofxGPUFont::font.hpp[" << __LINE__ << "] FT loaded " << filename << ": " << error << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_shared <Font>(face, worldSize, hinting, bufferTargetType);
|
return std::make_shared <Font>(face, worldSize, hinting, bufferTargetType, shaderProgram);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void initializeFont(FT_Library & library,
|
static void initializeFont(FT_Library & library,
|
||||||
const std::string & filename,
|
const std::string & filename,
|
||||||
shared_ptr <Font> & mainFont,
|
shared_ptr <Font> & mainFont,
|
||||||
|
GLuint shaderProgram,
|
||||||
GLint bufferTargetType = GL_TEXTURE_2D_ARRAY){
|
GLint bufferTargetType = GL_TEXTURE_2D_ARRAY){
|
||||||
cout << "initializeFont" << endl;
|
cout << "initializeFont" << endl;
|
||||||
auto font = loadFont(library, filename, bufferTargetType);
|
auto font = loadFont(library, filename, bufferTargetType, shaderProgram);
|
||||||
if(!font){
|
if(!font){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,7 +208,7 @@ class ShaderCatalog::Impl {
|
||||||
if(error != ""){
|
if(error != ""){
|
||||||
std::cerr << "[shader] " << error << std::endl;
|
std::cerr << "[shader] " << error << std::endl;
|
||||||
}else{
|
}else{
|
||||||
std::cerr << "[shader] reloaded " << name << std::endl;
|
//std::cerr << "[shader] reloaded " << name << std::endl;
|
||||||
glDeleteProgram(it->second->program);
|
glDeleteProgram(it->second->program);
|
||||||
it->second->program = program;
|
it->second->program = program;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace ofxGPUFont {
|
||||||
class ShaderCatalog {
|
class ShaderCatalog {
|
||||||
public:
|
public:
|
||||||
struct Entry {
|
struct Entry {
|
||||||
unsigned int program;
|
GLuint program;
|
||||||
|
|
||||||
Entry() : program(0){
|
Entry() : program(0){
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue