QOpenGlWidget - why would a if-else in a fragment shader cause QPainter to fail?
-
wrote on 29 May 2023, 10:31 last edited by
Spent the last few hours trying to debug why QPainter wasn't drawing after rendering some textures with QOpenGLWidget
Did all my setup and teardown of logic etc, including unbinding the ShaderProgram
Turned out if I have some fragment shader with a conditional, it causes QPainter to fail , even if the conditional does the same thing in either true or false branch (this is an if-else)
Why would this possibly affect QPainter? Very strange bug. Happy to hear any theories
for what its worth, I know its bad to have the conditionals in a fragment shader, this was just a toy project
Sample OpenGL fragment code:
void main() { vec4 inCol = texture2D(texture, v_texcoord); if (someTestValue < 0.5) { gl_FragColor = inCol; } else { gl_FragColor = inCol; } if (gl_FragColor.a <= 0.0) discard; }
If we make that if (true)
Or even just remove one of those branches, everything works perfectly. Does this sound like a QT bug?
-
wrote on 29 May 2023, 10:49 last edited by
Actually it also seems the condition needs to test against some varying type to fail, if its another type then its okay ... as in the QPainter can be seen
-
Actually it also seems the condition needs to test against some varying type to fail, if its another type then its okay ... as in the QPainter can be seen
wrote on 29 May 2023, 11:26 last edited by@NightShadeI
I assume the behaviour you see is "coincidence". Having anif
like this cannot affect anything (other than maybe a nanosecond's speed), look at the generated machine code. All this does is assign to a variable namedgl_FragColor
, which I assume is an address in memory with no side-effects.for what its worth, I know its bad to have the conditionals in a fragment shader
I know nothing about this.
I suggest you produce a full, standalone, minimal reproducer and start from there.
-
Hi,
Where does someTestValue come from ?
-
wrote on 29 May 2023, 13:11 last edited by
its a varying
varying float someTestValue;
Which ultimately is from the vertex shader, simply forwarding on its own test value:
someTestValue = someVertexTestValue;
That is set like this in the C++
int testLocation = program->attributeLocation("someVertexTestValue"); program->enableAttributeArray(testLocation); program->setAttributeBuffer(testLocation, GL_FLOAT, offset, 1, sizeof(VertexData));
Obviously this isn't all the code, it might take a while to produce a minimal workable example if its really needed , but I hope it gives some idea, or some way to test if this is just me
This is Qt 6.4.0
-
Spent the last few hours trying to debug why QPainter wasn't drawing after rendering some textures with QOpenGLWidget
Did all my setup and teardown of logic etc, including unbinding the ShaderProgram
Turned out if I have some fragment shader with a conditional, it causes QPainter to fail , even if the conditional does the same thing in either true or false branch (this is an if-else)
Why would this possibly affect QPainter? Very strange bug. Happy to hear any theories
for what its worth, I know its bad to have the conditionals in a fragment shader, this was just a toy project
Sample OpenGL fragment code:
void main() { vec4 inCol = texture2D(texture, v_texcoord); if (someTestValue < 0.5) { gl_FragColor = inCol; } else { gl_FragColor = inCol; } if (gl_FragColor.a <= 0.0) discard; }
If we make that if (true)
Or even just remove one of those branches, everything works perfectly. Does this sound like a QT bug?
@NightShadeI said:
it causes QPainter to fail
What does that mean? Nothing is drawn? Wrong thing is drawn? You get errors at shader compilation or execution?
Did you check the output to see if your shader compilation outputs any errors? Did you check if any of your GL calls return errors (e.g. if
program->attributeLocation
returns -1)? Did you try to run it under Debug context to see if any errors are reported at runtime?@JonB said:
Having an if like this cannot affect anything
Shader ifs are not your regular CPU ifs. GPUs executes shader programs in groups, usually 2x2 fragments. An
if
works like a mask. If the conditional diverges in a group you basically execute the whole thing twice with masked output from fragments that don't take the particular branch. Todays hardware does some reordering to mask the hit, but it's very different from branch predicting on CPUs and generally speaking ifs are very costly and should be avoided. One of the tricks GPU drivers do is compile the shader twice, hardcoding each branch and then mixing and matching resulting binary. This can also lead to significant binary growth when a lot of branches are present and can lead to further perf downgrade when all the branches don't fit in the GPU cache.All this does is assign to a variable named gl_FragColor, which I assume is an address in memory with no side-effects.
It's very rarely an address in memory and it has a bunch of side effects :) gl_FragColor is the output of the fragment shader, which gets forwarded to the next pipeline stage, usually via dedicated silicon in the compute unit of the GPU.
-
@NightShadeI said:
it causes QPainter to fail
What does that mean? Nothing is drawn? Wrong thing is drawn? You get errors at shader compilation or execution?
Did you check the output to see if your shader compilation outputs any errors? Did you check if any of your GL calls return errors (e.g. if
program->attributeLocation
returns -1)? Did you try to run it under Debug context to see if any errors are reported at runtime?@JonB said:
Having an if like this cannot affect anything
Shader ifs are not your regular CPU ifs. GPUs executes shader programs in groups, usually 2x2 fragments. An
if
works like a mask. If the conditional diverges in a group you basically execute the whole thing twice with masked output from fragments that don't take the particular branch. Todays hardware does some reordering to mask the hit, but it's very different from branch predicting on CPUs and generally speaking ifs are very costly and should be avoided. One of the tricks GPU drivers do is compile the shader twice, hardcoding each branch and then mixing and matching resulting binary. This can also lead to significant binary growth when a lot of branches are present and can lead to further perf downgrade when all the branches don't fit in the GPU cache.All this does is assign to a variable named gl_FragColor, which I assume is an address in memory with no side-effects.
It's very rarely an address in memory and it has a bunch of side effects :) gl_FragColor is the output of the fragment shader, which gets forwarded to the next pipeline stage, usually via dedicated silicon in the compute unit of the GPU.
wrote on 29 May 2023, 21:42 last edited by@Chris-Kawa
Hi, point taken about all the detail. But, excuse my ignorance, how do you know any of this code is to do with GPU?? I seemain()
and a couple of statements accessing global variables. Clearly out of my depth :) -
@Chris-Kawa
Hi, point taken about all the detail. But, excuse my ignorance, how do you know any of this code is to do with GPU?? I seemain()
and a couple of statements accessing global variables. Clearly out of my depth :)@JonB said:
how do you know any of this code is to do with GPU??
Well, for one OP said it's shader code :)
I see main() and a couple of statements accessing global variables
The language is GLSL. It's based on cut down C with some domain specific modifications. Basic shader pipeline has a Vertex and Fragment program. Vertex program executes its
main
for each and every vertex of the geometry and then for each triangle the results from its 3 vertices are interpolated across every fragment and a Fragment program runs itsmain
with those interpolated values as inputs. Anattribute
is sort of an input data to the vertex shader. They are set up on the CPU from C++ before running the GPU programs. Avarying
variable is the output from vertex shaders interpolated and passed as input to fragment shaders. Things likegl_Position
orgl_FragColor
are predefined input/output points of the various stages of the graphics pipeline. Things liketexture2D
are predefined "functions" that read input texture data using hardware samplers. -
@NightShadeI said:
it causes QPainter to fail
What does that mean? Nothing is drawn? Wrong thing is drawn? You get errors at shader compilation or execution?
Did you check the output to see if your shader compilation outputs any errors? Did you check if any of your GL calls return errors (e.g. if
program->attributeLocation
returns -1)? Did you try to run it under Debug context to see if any errors are reported at runtime?@JonB said:
Having an if like this cannot affect anything
Shader ifs are not your regular CPU ifs. GPUs executes shader programs in groups, usually 2x2 fragments. An
if
works like a mask. If the conditional diverges in a group you basically execute the whole thing twice with masked output from fragments that don't take the particular branch. Todays hardware does some reordering to mask the hit, but it's very different from branch predicting on CPUs and generally speaking ifs are very costly and should be avoided. One of the tricks GPU drivers do is compile the shader twice, hardcoding each branch and then mixing and matching resulting binary. This can also lead to significant binary growth when a lot of branches are present and can lead to further perf downgrade when all the branches don't fit in the GPU cache.All this does is assign to a variable named gl_FragColor, which I assume is an address in memory with no side-effects.
It's very rarely an address in memory and it has a bunch of side effects :) gl_FragColor is the output of the fragment shader, which gets forwarded to the next pipeline stage, usually via dedicated silicon in the compute unit of the GPU.
wrote on 29 May 2023, 22:18 last edited by NightShadeIAh I think you might be onto something...
Indeed attributeLocation is returning -1 , but interesting the conditional still works so that's something ill have to investigate further
By not working I mean the QPainter won't draw anything only under these circumstances. Actually it seems more undefined behaviour like ... Since 1 run I saw a line of red just along the top of the screen with everything else normal (im trying to render a red rectangle with QPainter)
Certainly no GLSL compilation errors logged though and usually when there are, my textures (from a vertex buffer) don't actually get rendered.
No errors I see are currently logged on this seeming UB , except that -1 you now mention, which probably means im doing something silly somewhere
-
Ah I think you might be onto something...
Indeed attributeLocation is returning -1 , but interesting the conditional still works so that's something ill have to investigate further
By not working I mean the QPainter won't draw anything only under these circumstances. Actually it seems more undefined behaviour like ... Since 1 run I saw a line of red just along the top of the screen with everything else normal (im trying to render a red rectangle with QPainter)
Certainly no GLSL compilation errors logged though and usually when there are, my textures (from a vertex buffer) don't actually get rendered.
No errors I see are currently logged on this seeming UB , except that -1 you now mention, which probably means im doing something silly somewhere
wrote on 29 May 2023, 22:30 last edited by NightShadeINevermind, unfortunately it does return a valid value when I use it in the conditional. It just returns -1 when I don't use it in the fragment shader , so thus when no errors are returned the problem still persists
I.e. the code in my question would correctly return '3' (success) , but if I change
someTestValue
withtrue
it will return-1
-
Nevermind, unfortunately it does return a valid value when I use it in the conditional. It just returns -1 when I don't use it in the fragment shader , so thus when no errors are returned the problem still persists
I.e. the code in my question would correctly return '3' (success) , but if I change
someTestValue
withtrue
it will return-1
@NightShadeI Shader compilers are extremely aggressive when it comes to unused variables. Anything that is not used in any branch is completely removed from the compiled shader blob, so if you query attributes that don't get used you'll get -1 (invalid location).
I would try creating a debug context. If there's no errors from it maybe your shader is just not doing what you think it does?
-
@NightShadeI Shader compilers are extremely aggressive when it comes to unused variables. Anything that is not used in any branch is completely removed from the compiled shader blob, so if you query attributes that don't get used you'll get -1 (invalid location).
I would try creating a debug context. If there's no errors from it maybe your shader is just not doing what you think it does?
wrote on 30 May 2023, 11:02 last edited by NightShadeII have produced the most minimal reproducible example that I possibly could, cutting out about 500 lines of code. The example doesn't include everything , like a lot of things I found didn't make a difference (e.g. begin/end native painting). Please find example here:
MainWidget.h
#pragma once #include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLTexture> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGLWidgets/QOpenGLWidget> struct VertexData { QVector2D theCenter; QVector2D theTextureCoord; QVector2D theOffset{0.f, 0.f}; float theExtra{0.f}; }; class GeometryEngine : protected QOpenGLFunctions { QOpenGLBuffer theGlBuffer; std::array<VertexData, 6> theData{ VertexData{QVector2D{100, 400}, QVector2D{0.f, 1.f}}, VertexData{QVector2D{400, 400}, QVector2D{1.f, 1.f}}, VertexData{QVector2D{100, 100}, QVector2D{0.f, 0.f}}, VertexData{QVector2D{400, 400}, QVector2D{1.f, 1.f}}, VertexData{QVector2D{400, 100}, QVector2D{1.f, 0.f}}, VertexData{QVector2D{100, 100}, QVector2D{0.f, 0.f}} }; public: GeometryEngine() { initializeOpenGLFunctions(); initGeometry(); } void initGeometry() { theGlBuffer.create(); theGlBuffer.bind(); theGlBuffer.allocate(theData.data(), theData.size() * sizeof(VertexData)); } void drawGeometry(QOpenGLShaderProgram& aProgram) { theGlBuffer.bind(); const int myCenterLoc = aProgram.attributeLocation("a_center"); aProgram.enableAttributeArray(myCenterLoc); aProgram.setAttributeBuffer(myCenterLoc, GL_FLOAT, offsetof(VertexData, VertexData::theCenter), 2, sizeof(VertexData)); const int myTexLoc = aProgram.attributeLocation("a_texcoord"); aProgram.enableAttributeArray(myTexLoc); aProgram.setAttributeBuffer(myTexLoc, GL_FLOAT, offsetof(VertexData, VertexData::theTextureCoord), 2, sizeof(VertexData)); const int myOffetLoc = aProgram.attributeLocation("a_offset"); aProgram.enableAttributeArray(myOffetLoc); aProgram.setAttributeBuffer(myOffetLoc, GL_FLOAT, offsetof(VertexData, VertexData::theOffset), 2, sizeof(VertexData)); const int myExtraLoc = aProgram.attributeLocation("a_extra"); aProgram.enableAttributeArray(myExtraLoc); aProgram.setAttributeBuffer(myExtraLoc, GL_FLOAT, offsetof(VertexData, VertexData::theExtra), 1, sizeof(VertexData)); glDrawArrays(GL_TRIANGLES, 0, theData.size()); theGlBuffer.release(); } }; class MainWidget : public QOpenGLWidget , protected QOpenGLFunctions { QOpenGLShaderProgram theProgram; GeometryEngine* theGeometries = nullptr; QOpenGLTexture* theTexture = nullptr; int theWidth{0}; int theHeight{0}; public: void initShaders() { if (!theProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shader/vshader.glsl")) close(); if (!theProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shader/fshader.glsl")) close(); if (!theProgram.link()) close(); } void initializeGL() override { initializeOpenGLFunctions(); initShaders(); initTextures(); theGeometries = new GeometryEngine{}; } void initTextures() { theTexture = new QOpenGLTexture{QImage{":/tileset.png"}}; theTexture->setMinificationFilter(QOpenGLTexture::Nearest); theTexture->setMagnificationFilter(QOpenGLTexture::Nearest); } void resizeGL(int aWidth, int aHeight) override { theWidth = aWidth; theHeight = aHeight; } void paintGL() override { theProgram.bind(); theTexture->bind(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); QMatrix4x4 myMatrix; myMatrix.ortho(0, theWidth, theHeight, 0, -1.f, 0); theProgram.setUniformValue("mvp_matrix", myMatrix); theProgram.setUniformValue("texture", 0); theGeometries->drawGeometry(theProgram); QPainter myPainter{this}; myPainter.fillRect(0, 0, 100, 100, Qt::red); myPainter.setPen(Qt::red); myPainter.drawLine(rect().topLeft(), rect().bottomRight()); } };
shader/vshader.glsl
#ifdef GL_ES // Set default precision to medium precision mediump int; precision mediump float; #endif uniform mat4 mvp_matrix; attribute vec2 a_center; attribute vec2 a_texcoord; attribute vec2 a_offset; attribute float a_extra; varying vec2 v_texcoord; void main() { vec4 new_pos = vec4(a_center.x + a_offset.x, a_center.y, 1.0, 1.0); gl_Position = mvp_matrix * new_pos; v_texcoord = a_texcoord; }
shader/fshader.glsl
#ifdef GL_ES // Set default precision to medium precision mediump int; precision mediump float; #endif uniform sampler2D texture; varying vec2 v_texcoord; void main() { vec4 inCol = texture2D(texture, v_texcoord); gl_FragColor = inCol; }
main:
#include "MainWidget.hpp" int main(int aArgc, char *aArgv[]) { QApplication myApplication{aArgc, aArgv}; myApplication.setApplicationName("Playground"); myApplication.setApplicationVersion("0.1"); #ifndef QT_NO_OPENGL MainWidget widget; widget.show(); #else QLabel note("OpenGL Support required"); note.show(); #endif return myApplication.exec(); }
Ultimately the issue occurs when we use MORE THAN THREE attributes, so in vshader you have to add a_offset.x + a_extra -- You will notice most your screen becomes red -- Also again, if it matters, my Qt version is 6.4.0 and Windows 10
Hope it helps :) You can replace tileset with any image you choose, tried to make it all plug and play
Any help would be super appreciated, tried just about anything and can't work this one out
-
I have produced the most minimal reproducible example that I possibly could, cutting out about 500 lines of code. The example doesn't include everything , like a lot of things I found didn't make a difference (e.g. begin/end native painting). Please find example here:
MainWidget.h
#pragma once #include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLTexture> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGLWidgets/QOpenGLWidget> struct VertexData { QVector2D theCenter; QVector2D theTextureCoord; QVector2D theOffset{0.f, 0.f}; float theExtra{0.f}; }; class GeometryEngine : protected QOpenGLFunctions { QOpenGLBuffer theGlBuffer; std::array<VertexData, 6> theData{ VertexData{QVector2D{100, 400}, QVector2D{0.f, 1.f}}, VertexData{QVector2D{400, 400}, QVector2D{1.f, 1.f}}, VertexData{QVector2D{100, 100}, QVector2D{0.f, 0.f}}, VertexData{QVector2D{400, 400}, QVector2D{1.f, 1.f}}, VertexData{QVector2D{400, 100}, QVector2D{1.f, 0.f}}, VertexData{QVector2D{100, 100}, QVector2D{0.f, 0.f}} }; public: GeometryEngine() { initializeOpenGLFunctions(); initGeometry(); } void initGeometry() { theGlBuffer.create(); theGlBuffer.bind(); theGlBuffer.allocate(theData.data(), theData.size() * sizeof(VertexData)); } void drawGeometry(QOpenGLShaderProgram& aProgram) { theGlBuffer.bind(); const int myCenterLoc = aProgram.attributeLocation("a_center"); aProgram.enableAttributeArray(myCenterLoc); aProgram.setAttributeBuffer(myCenterLoc, GL_FLOAT, offsetof(VertexData, VertexData::theCenter), 2, sizeof(VertexData)); const int myTexLoc = aProgram.attributeLocation("a_texcoord"); aProgram.enableAttributeArray(myTexLoc); aProgram.setAttributeBuffer(myTexLoc, GL_FLOAT, offsetof(VertexData, VertexData::theTextureCoord), 2, sizeof(VertexData)); const int myOffetLoc = aProgram.attributeLocation("a_offset"); aProgram.enableAttributeArray(myOffetLoc); aProgram.setAttributeBuffer(myOffetLoc, GL_FLOAT, offsetof(VertexData, VertexData::theOffset), 2, sizeof(VertexData)); const int myExtraLoc = aProgram.attributeLocation("a_extra"); aProgram.enableAttributeArray(myExtraLoc); aProgram.setAttributeBuffer(myExtraLoc, GL_FLOAT, offsetof(VertexData, VertexData::theExtra), 1, sizeof(VertexData)); glDrawArrays(GL_TRIANGLES, 0, theData.size()); theGlBuffer.release(); } }; class MainWidget : public QOpenGLWidget , protected QOpenGLFunctions { QOpenGLShaderProgram theProgram; GeometryEngine* theGeometries = nullptr; QOpenGLTexture* theTexture = nullptr; int theWidth{0}; int theHeight{0}; public: void initShaders() { if (!theProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shader/vshader.glsl")) close(); if (!theProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shader/fshader.glsl")) close(); if (!theProgram.link()) close(); } void initializeGL() override { initializeOpenGLFunctions(); initShaders(); initTextures(); theGeometries = new GeometryEngine{}; } void initTextures() { theTexture = new QOpenGLTexture{QImage{":/tileset.png"}}; theTexture->setMinificationFilter(QOpenGLTexture::Nearest); theTexture->setMagnificationFilter(QOpenGLTexture::Nearest); } void resizeGL(int aWidth, int aHeight) override { theWidth = aWidth; theHeight = aHeight; } void paintGL() override { theProgram.bind(); theTexture->bind(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); QMatrix4x4 myMatrix; myMatrix.ortho(0, theWidth, theHeight, 0, -1.f, 0); theProgram.setUniformValue("mvp_matrix", myMatrix); theProgram.setUniformValue("texture", 0); theGeometries->drawGeometry(theProgram); QPainter myPainter{this}; myPainter.fillRect(0, 0, 100, 100, Qt::red); myPainter.setPen(Qt::red); myPainter.drawLine(rect().topLeft(), rect().bottomRight()); } };
shader/vshader.glsl
#ifdef GL_ES // Set default precision to medium precision mediump int; precision mediump float; #endif uniform mat4 mvp_matrix; attribute vec2 a_center; attribute vec2 a_texcoord; attribute vec2 a_offset; attribute float a_extra; varying vec2 v_texcoord; void main() { vec4 new_pos = vec4(a_center.x + a_offset.x, a_center.y, 1.0, 1.0); gl_Position = mvp_matrix * new_pos; v_texcoord = a_texcoord; }
shader/fshader.glsl
#ifdef GL_ES // Set default precision to medium precision mediump int; precision mediump float; #endif uniform sampler2D texture; varying vec2 v_texcoord; void main() { vec4 inCol = texture2D(texture, v_texcoord); gl_FragColor = inCol; }
main:
#include "MainWidget.hpp" int main(int aArgc, char *aArgv[]) { QApplication myApplication{aArgc, aArgv}; myApplication.setApplicationName("Playground"); myApplication.setApplicationVersion("0.1"); #ifndef QT_NO_OPENGL MainWidget widget; widget.show(); #else QLabel note("OpenGL Support required"); note.show(); #endif return myApplication.exec(); }
Ultimately the issue occurs when we use MORE THAN THREE attributes, so in vshader you have to add a_offset.x + a_extra -- You will notice most your screen becomes red -- Also again, if it matters, my Qt version is 6.4.0 and Windows 10
Hope it helps :) You can replace tileset with any image you choose, tried to make it all plug and play
Any help would be super appreciated, tried just about anything and can't work this one out
@NightShadeI The problem is you're not disabling your attribute arrays after drawing. Qt wrapper API makes it look as if it was a program specific state but it's not. It's a global state that all draw calls share.
QPainter explicitly disables attribute arrays at locations 0-2 to use them for something, but then uses locations 3-5 with glVertexAttrib to store some MVP matrix for draws without checking if any arrays are bound at that location.
That it doesn't clear those locations first could be considered a bug I guess, but the gist of it is that you're accidentally corrupting attribute 3 (and above) that QPainter uses without properly sanitizing it first.
The easy fix is to always disable your attribute arrays after a draw that uses them, or at least before you switch to QPainter calls.
As a side note - since you're mixing native OpenGL with QPainter you should be using beginNativePainting() / endNativePainting(). You're sharing state with QPainter and those calls help to restore any state you or QPainter might have changed, so you don't step on each other's feet.
-
I have produced the most minimal reproducible example that I possibly could, cutting out about 500 lines of code. The example doesn't include everything , like a lot of things I found didn't make a difference (e.g. begin/end native painting). Please find example here:
MainWidget.h
#pragma once #include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLTexture> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGLWidgets/QOpenGLWidget> struct VertexData { QVector2D theCenter; QVector2D theTextureCoord; QVector2D theOffset{0.f, 0.f}; float theExtra{0.f}; }; class GeometryEngine : protected QOpenGLFunctions { QOpenGLBuffer theGlBuffer; std::array<VertexData, 6> theData{ VertexData{QVector2D{100, 400}, QVector2D{0.f, 1.f}}, VertexData{QVector2D{400, 400}, QVector2D{1.f, 1.f}}, VertexData{QVector2D{100, 100}, QVector2D{0.f, 0.f}}, VertexData{QVector2D{400, 400}, QVector2D{1.f, 1.f}}, VertexData{QVector2D{400, 100}, QVector2D{1.f, 0.f}}, VertexData{QVector2D{100, 100}, QVector2D{0.f, 0.f}} }; public: GeometryEngine() { initializeOpenGLFunctions(); initGeometry(); } void initGeometry() { theGlBuffer.create(); theGlBuffer.bind(); theGlBuffer.allocate(theData.data(), theData.size() * sizeof(VertexData)); } void drawGeometry(QOpenGLShaderProgram& aProgram) { theGlBuffer.bind(); const int myCenterLoc = aProgram.attributeLocation("a_center"); aProgram.enableAttributeArray(myCenterLoc); aProgram.setAttributeBuffer(myCenterLoc, GL_FLOAT, offsetof(VertexData, VertexData::theCenter), 2, sizeof(VertexData)); const int myTexLoc = aProgram.attributeLocation("a_texcoord"); aProgram.enableAttributeArray(myTexLoc); aProgram.setAttributeBuffer(myTexLoc, GL_FLOAT, offsetof(VertexData, VertexData::theTextureCoord), 2, sizeof(VertexData)); const int myOffetLoc = aProgram.attributeLocation("a_offset"); aProgram.enableAttributeArray(myOffetLoc); aProgram.setAttributeBuffer(myOffetLoc, GL_FLOAT, offsetof(VertexData, VertexData::theOffset), 2, sizeof(VertexData)); const int myExtraLoc = aProgram.attributeLocation("a_extra"); aProgram.enableAttributeArray(myExtraLoc); aProgram.setAttributeBuffer(myExtraLoc, GL_FLOAT, offsetof(VertexData, VertexData::theExtra), 1, sizeof(VertexData)); glDrawArrays(GL_TRIANGLES, 0, theData.size()); theGlBuffer.release(); } }; class MainWidget : public QOpenGLWidget , protected QOpenGLFunctions { QOpenGLShaderProgram theProgram; GeometryEngine* theGeometries = nullptr; QOpenGLTexture* theTexture = nullptr; int theWidth{0}; int theHeight{0}; public: void initShaders() { if (!theProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shader/vshader.glsl")) close(); if (!theProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shader/fshader.glsl")) close(); if (!theProgram.link()) close(); } void initializeGL() override { initializeOpenGLFunctions(); initShaders(); initTextures(); theGeometries = new GeometryEngine{}; } void initTextures() { theTexture = new QOpenGLTexture{QImage{":/tileset.png"}}; theTexture->setMinificationFilter(QOpenGLTexture::Nearest); theTexture->setMagnificationFilter(QOpenGLTexture::Nearest); } void resizeGL(int aWidth, int aHeight) override { theWidth = aWidth; theHeight = aHeight; } void paintGL() override { theProgram.bind(); theTexture->bind(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); QMatrix4x4 myMatrix; myMatrix.ortho(0, theWidth, theHeight, 0, -1.f, 0); theProgram.setUniformValue("mvp_matrix", myMatrix); theProgram.setUniformValue("texture", 0); theGeometries->drawGeometry(theProgram); QPainter myPainter{this}; myPainter.fillRect(0, 0, 100, 100, Qt::red); myPainter.setPen(Qt::red); myPainter.drawLine(rect().topLeft(), rect().bottomRight()); } };
shader/vshader.glsl
#ifdef GL_ES // Set default precision to medium precision mediump int; precision mediump float; #endif uniform mat4 mvp_matrix; attribute vec2 a_center; attribute vec2 a_texcoord; attribute vec2 a_offset; attribute float a_extra; varying vec2 v_texcoord; void main() { vec4 new_pos = vec4(a_center.x + a_offset.x, a_center.y, 1.0, 1.0); gl_Position = mvp_matrix * new_pos; v_texcoord = a_texcoord; }
shader/fshader.glsl
#ifdef GL_ES // Set default precision to medium precision mediump int; precision mediump float; #endif uniform sampler2D texture; varying vec2 v_texcoord; void main() { vec4 inCol = texture2D(texture, v_texcoord); gl_FragColor = inCol; }
main:
#include "MainWidget.hpp" int main(int aArgc, char *aArgv[]) { QApplication myApplication{aArgc, aArgv}; myApplication.setApplicationName("Playground"); myApplication.setApplicationVersion("0.1"); #ifndef QT_NO_OPENGL MainWidget widget; widget.show(); #else QLabel note("OpenGL Support required"); note.show(); #endif return myApplication.exec(); }
Ultimately the issue occurs when we use MORE THAN THREE attributes, so in vshader you have to add a_offset.x + a_extra -- You will notice most your screen becomes red -- Also again, if it matters, my Qt version is 6.4.0 and Windows 10
Hope it helps :) You can replace tileset with any image you choose, tried to make it all plug and play
Any help would be super appreciated, tried just about anything and can't work this one out
wrote on 30 May 2023, 20:27 last edited byThis post is deleted! -
@NightShadeI The problem is you're not disabling your attribute arrays after drawing. Qt wrapper API makes it look as if it was a program specific state but it's not. It's a global state that all draw calls share.
QPainter explicitly disables attribute arrays at locations 0-2 to use them for something, but then uses locations 3-5 with glVertexAttrib to store some MVP matrix for draws without checking if any arrays are bound at that location.
That it doesn't clear those locations first could be considered a bug I guess, but the gist of it is that you're accidentally corrupting attribute 3 (and above) that QPainter uses without properly sanitizing it first.
The easy fix is to always disable your attribute arrays after a draw that uses them, or at least before you switch to QPainter calls.
As a side note - since you're mixing native OpenGL with QPainter you should be using beginNativePainting() / endNativePainting(). You're sharing state with QPainter and those calls help to restore any state you or QPainter might have changed, so you don't step on each other's feet.
wrote on 30 May 2023, 20:51 last edited byThanks so much, funny thing is I just found this also at the same time as you were posting it , and now that makes so much sense with your explanation. Thanks so much for your help in all of this :) Guess can mark as solved. I'm happy with cleaning up my own state at least for now, can probably make my own RAII style wrapper to do it if need be
-
-
Thanks so much, funny thing is I just found this also at the same time as you were posting it , and now that makes so much sense with your explanation. Thanks so much for your help in all of this :) Guess can mark as solved. I'm happy with cleaning up my own state at least for now, can probably make my own RAII style wrapper to do it if need be
wrote on 31 May 2023, 03:33 last edited by@NightShadeI Just make sure you've read the OpenGL Wiki's page on gotchas with mixing RAII/OO/ and OpenGL
https://www.khronos.org/opengl/wiki/Common_Mistakes#The_Object_Oriented_Language_Problem
1/16