QOpenGLWidget and Multiple Textures
-
Hey guys,
I have a class inheriting from QOpenGLWidget & QOpenGLExtraFunctions like below:
class VideoGate : public QOpenGLWidget, protected QOpenGLExtraFunctions { Q_OBJECT public: VideoGate(QWidget* parent = nullptr); ~VideoGate(); private: //OpenGL input & output texture handles GLint inputTextureUnit[numInputs]; GLuint inputTextureID[numInputs]; //OpenGL uniform locations and values GLint alphaUniformLocation; GLfloat alpha; //OpenGL program and variables for attribute locations QOpenGLShaderProgram* program; GLint vertexAttributeLocation; GLint texCoordAttributeLocation; GLint textureUniformLocation[numInputs]; protected: void initializeGL() override; void resizeGL(int w, int h) override; void paintGL() override; };
I'm trying to alpha mix two textures together in a fragment shader and display the output texture on the widget. For some reason I'm not able to do this properly even though it should be pretty straightforward. Here are my OpenGL functions:
void VideoGate::initializeGL() { //Set OpenGL version QSurfaceFormat format; format.setVersion(4,2); initializeOpenGLFunctions(); //Vertex and texture positions of image quad static const GLfloat vertexData[4][4] = {{1.0f,-1.0f,1.0f,0.0f},{-1.0f,-1.0f,0.0f,0.0f},{-1.0f,1.0f,0.0f,1.0f},{1.0f,1.0f,1.0f,1.0f}}; //Generate and bind VAO glGenVertexArrays(1, &vaoID); glBindVertexArray(vaoID); //Generate and bind VBO glGenBuffers(1, &vboID); glBindBuffer(GL_ARRAY_BUFFER, vboID); glBufferData(GL_ARRAY_BUFFER, 16*sizeof(GLfloat), vertexData, GL_STATIC_DRAW); //Create OpenGL program; add vertex and fragment shaders, link and bind program = new QOpenGLShaderProgram; program -> addShaderFromSourceFile(QOpenGLShader::Vertex, QString(":/GLSL/vertex.glsl")); program -> addShaderFromSourceFile(QOpenGLShader::Fragment, QString(":/GLSL/fragment.glsl")); program -> link(); program -> bind(); //Get locations of vertex shader attributes vertexAttributeLocation = glGetAttribLocation(program -> programId(), "vertex"); texCoordAttributeLocation = glGetAttribLocation(program -> programId(), "texCoord"); textureUniformLocation[0] = glGetUniformLocation(program -> programId(), "textureOne"); textureUniformLocation[1] = glGetUniformLocation(program -> programId(), "textureTwo"); inputTextureUnit[0] = 0; inputTextureUnit[1] = 1; glUniform1i(textureUniformLocation[0], inputTextureUnit[0]); //Set the value of the texture unit (GL_TEXTUREX) so it can be used in glActiveTexture glUniform1i(textureUniformLocation[1], inputTextureUnit[1]); //Get locations of fragment shader uniforms to be tied to UI alphaUniformLocation = glGetUniformLocation(program -> programId(), "alpha"); glEnable(GL_TEXTURE_2D); //Generate input textures glGenTextures(2, &inputTextureID[0]); //1 glActiveTexture(GL_TEXTURE0 + inputTextureUnit[0]); glBindTexture(GL_TEXTURE_2D, inputTextureID[0]); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, videoWidth, videoHeight, 0, GL_BGR, GL_UNSIGNED_BYTE, (const void*)(nullptr)); glGenerateMipmap(GL_TEXTURE_2D); //2 glActiveTexture(GL_TEXTURE0 + inputTextureUnit[1]); glBindTexture(GL_TEXTURE_2D, inputTextureID[1]); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, videoWidth, videoHeight, 0, GL_BGR, GL_UNSIGNED_BYTE, (const void*)(nullptr)); glGenerateMipmap(GL_TEXTURE_2D); program -> release(); } void VideoGate::paintGL() { glClearColor(0.0f,0.0f,0.0f,1.0f); glClear(GL_COLOR_BUFFER_BIT); program -> bind(); glUniform1f(alphaUniformLocation, alpha); glBindVertexArray(vaoID); glVertexAttribPointer(vertexAttributeLocation, 2, GL_FLOAT, GL_TRUE, 4*sizeof(GLfloat), (const void*)(0)); glVertexAttribPointer(texCoordAttributeLocation, 2, GL_FLOAT, GL_TRUE, 4*sizeof(GLfloat), (const void*)(2*sizeof(GLfloat))); glEnableVertexAttribArray(vertexAttributeLocation); glEnableVertexAttribArray(texCoordAttributeLocation); glActiveTexture(GL_TEXTURE0 + inputTextureUnit[0]); glBindTexture(GL_TEXTURE_2D, inputTextureID[0]); glActiveTexture(GL_TEXTURE0 + inputTextureUnit[1]); glBindTexture(GL_TEXTURE_2D, inputTextureID[1]); glDrawArrays(GL_QUADS, 0, 4); }
My shaders are below:
Vertex:
#version 420 in lowp vec2 vertex; in lowp vec2 texCoord; out lowp vec2 texc; void main(void) { texc = texCoord; gl_Position = vec4(vertex, 0.0, 1.0); }
Fragment:
#version 420 uniform float alpha; in lowp vec2 texc; uniform sampler2D textureOne; uniform sampler2D textureTwo; vec4 texOneColor; vec4 texTwoColor; void main(void) { texOneColor = texture2D(textureOne,texc.st); texTwoColor = texture2D(textureTwo, texc.st); gl_FragColor = mix(texOneColor, texTwoColor, alpha); }
Basically I can display the first texture on its own which I can't do for texture 2 but even that is only possible if I use GL_TEXTURE0 to bind the first texture which tells me somehow the problem is with which texture unit I'm activating. Once I start mixing the textures together in a continuous loop, I get all types of flickering as I update the textures as well.
I've managed to implement this with 3 different widgets with no issues. In that scenario, 2 input widgets take in a texture, render it to a new FBO / texture and send the results to the 3rd widget to mix them. This works but uses more resources.
Is this something related to the default FBO of the widget that is somehow attached to GL_TEXTURE0?
Where am I hurting QOpenGLWidget's feelings? -
Alright after developing spine problems from leaning into my screen for a week and losing more hair than my GPU has cores, I sort of fixed the issue.
So for some reason when I was reading data into my textures without setting the active texture back to the GL_TEXTURE0 default, things were going sideways.
So basically I had something like:
glActiveTexture(GL_TEXTURE0 + inputTextureUnit[vid]); glBindTexture(GL_TEXTURE_2D, inputTextureID[vid]); glTexImage2D(GL_TEXTURE_2D, 0, GL_internalFormat[vid], videoSpecs[vid].videoWidth, videoSpecs[vid].videoHeight, 0, GL_format[vid], GL_UNSIGNED_BYTE, (const void*)(frame));
But this was missing at the end:
glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0);
But I'm not 100% convinced that this is the cause because if I read still images, there is no issue. Its only when I read videos that I see the flickering and black texture issue. It seems like when other widgets start rendering and there is a texture active, they somehow corrupt it but I would certainly appreciate it if anyone has input on what actually might be happening.