Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QOpenGlWidget - why would a if-else in a fragment shader cause QPainter to fail?
Qt 6.11 is out! See what's new in the release blog

QOpenGlWidget - why would a if-else in a fragment shader cause QPainter to fail?

Scheduled Pinned Locked Moved Solved General and Desktop
16 Posts 5 Posters 3.8k Views 4 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • N NightShadeI

    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?

    Chris KawaC Offline
    Chris KawaC Offline
    Chris Kawa
    Lifetime Qt Champion
    wrote on last edited by
    #6

    @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.

    JonBJ N 2 Replies Last reply
    2
    • Chris KawaC Chris Kawa

      @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.

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by
      #7

      @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 see main() and a couple of statements accessing global variables. Clearly out of my depth :)

      Chris KawaC 1 Reply Last reply
      0
      • JonBJ JonB

        @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 see main() and a couple of statements accessing global variables. Clearly out of my depth :)

        Chris KawaC Offline
        Chris KawaC Offline
        Chris Kawa
        Lifetime Qt Champion
        wrote on last edited by
        #8

        @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 its main with those interpolated values as inputs. An attribute is sort of an input data to the vertex shader. They are set up on the CPU from C++ before running the GPU programs. A varying variable is the output from vertex shaders interpolated and passed as input to fragment shaders. Things like gl_Position or gl_FragColor are predefined input/output points of the various stages of the graphics pipeline. Things like texture2D are predefined "functions" that read input texture data using hardware samplers.

        1 Reply Last reply
        1
        • Chris KawaC Chris Kawa

          @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.

          N Offline
          N Offline
          NightShadeI
          wrote on last edited by NightShadeI
          #9

          @Chris-Kawa

          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

          N 1 Reply Last reply
          0
          • N NightShadeI

            @Chris-Kawa

            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

            N Offline
            N Offline
            NightShadeI
            wrote on last edited by NightShadeI
            #10

            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 with true it will return -1

            Chris KawaC 1 Reply Last reply
            0
            • N NightShadeI

              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 with true it will return -1

              Chris KawaC Offline
              Chris KawaC Offline
              Chris Kawa
              Lifetime Qt Champion
              wrote on last edited by
              #11

              @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?

              N 1 Reply Last reply
              0
              • Chris KawaC Chris Kawa

                @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?

                N Offline
                N Offline
                NightShadeI
                wrote on last edited by NightShadeI
                #12

                @Chris-Kawa
                @SGaist
                @JonB

                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

                Chris KawaC N 2 Replies Last reply
                0
                • N NightShadeI

                  @Chris-Kawa
                  @SGaist
                  @JonB

                  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

                  Chris KawaC Offline
                  Chris KawaC Offline
                  Chris Kawa
                  Lifetime Qt Champion
                  wrote on last edited by Chris Kawa
                  #13

                  @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.

                  N 1 Reply Last reply
                  2
                  • N NightShadeI

                    @Chris-Kawa
                    @SGaist
                    @JonB

                    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

                    N Offline
                    N Offline
                    NightShadeI
                    wrote on last edited by
                    #14
                    This post is deleted!
                    1 Reply Last reply
                    0
                    • Chris KawaC Chris Kawa

                      @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.

                      N Offline
                      N Offline
                      NightShadeI
                      wrote on last edited by
                      #15

                      @Chris-Kawa

                      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

                      W 1 Reply Last reply
                      1
                      • N NightShadeI has marked this topic as solved on
                      • N NightShadeI

                        @Chris-Kawa

                        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

                        W Offline
                        W Offline
                        wrosecrans
                        wrote on last edited by
                        #16

                        @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 Reply Last reply
                        0

                        • Login

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • Users
                        • Groups
                        • Search
                        • Get Qt Extensions
                        • Unsolved