Why QSurfaceFormat is a cause of OpenGL error with the 1282 code (invalid operation) after glReadPixels call?
-
@Chris-Kawa thank a lot! I didn't know that I need to move pixels from the multisampled FBO to a normal FBO before using glReadPixels. For example, in WebGL 1.0 I don't need it because
canvas.getContext()
has an attributeantialias
that enabled by default:const gl = canvas.getContext("webgl");
I can set
antialias
obviously:const gl = canvas.getContext("webgl", { antialias: true });
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Example</title> </head> <body> <canvas id="renderCanvas" width="100" height="100"></canvas> <script> const canvas = document.getElementById("renderCanvas"); const gl = canvas.getContext("webgl", { antialias: true }); gl.clearColor(0, 1, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); const pixels = new Uint8Array(4); gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels); console.log(pixels[0], pixels[1], pixels[2]); </script> </body> </html>
Console output:0 255 0
But I understand now that the Qt
surfaceFormat.setSamples(4)
is different. -
But I don't know how to call
glBlitFrameBuffer
directly (without QOpenGLFramebufferObject::blitFramebuffer) I didn't use OpenGL extensions before.I found an example here: https://stackoverflow.com/questions/765434/glreadpixels-from-fbo-fails-with-multisampling in this answer:
I don't think you can read from a multisampled FBO with glReadPixels(). You need to blit from the multisampled FBO to a normal FBO, bind the normal FBO, and then read the pixels from the normal FBO.
Something like this:
// Bind the multisampled FBO for reading glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, my_multisample_fbo); // Bind the normal FBO for drawing glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, my_fbo); // Blit the multisampled FBO to the normal FBO glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); //Bind the normal FBO for reading glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, my_fbo); // Read the pixels! glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
I think he uses
EXT
even withglBindFramebufferEXT
and withGL_DRAW_FRAMEBUFFER_EXT
because he uses it with OpenGL 1.5. But OpenGL 2.0 hasglBindFramebuffer()
andGL_DRAW_FRAMEBUFFER
Now I need to call
glBlitFramebuffer
. It is undefined now. I found an example here how to call an extension: https://www.opengl-tutorial.org/intermediate-tutorials/tutorial-12-opengl-extensions/int NumberOfExtensions; glGetIntegerv(GL_NUM_EXTENSIONS, &NumberOfExtensions); for(i=0; i<NumberOfExtensions; i++) { const GLubyte *ccc=glGetStringi(GL_EXTENSIONS, i); if ( strcmp(ccc, (const GLubyte *)"GL_ARB_debug_output") == 0 ){ // The extension is supported by our hardware and driver // Try to get the "glDebugMessageCallbackARB" function : glDebugMessageCallbackARB = (PFNGLDEBUGMESSAGECALLBACKARBPROC) wglGetProcAddress("glDebugMessageCallbackARB"); } }
The problem is that
glGetStringi
is not declared in this scope:int numberOfExtensions; glGetIntegerv(GL_NUM_EXTENSIONS, &numberOfExtensions); for(int i = 0; i < numberOfExtensions; i++) { const GLubyte *ccc = glGetStringi(GL_EXTENSIONS, i); }
-
Another problem is here:
glDebugMessageCallbackARB = (PFNGLDEBUGMESSAGECALLBACKARBPROC) wglGetProcAddress("glDebugMessageCallbackARB");
PFNGLDEBUGMESSAGECALLBACKARBPROC
is WinAPI thing. I cannot use it because I want to build for Android too. I need to use Qt loader of function pointers but I didn't find how. -
@8Observer8 said:
But I understand now that the Qt surfaceFormat.setSamples(4) is different.
It has nothing to do with Qt. Open GL 2.0 and WebGL are two different specs, and, although similar, they do differ in many places. The
antialias
context attribute in WebGL is optional and entirely implementation defined i.e. it could be implemented as multisampling, supersampling or a postprocess like FXAA. The readPixels function in WebGL does not have the same restrictions as the one in OpenGL 2.0.I didn't use OpenGL extensions before.
You probably did. you just don't know it because Qt gives you nice wrappers around them. Same here. You don't need to deal with the extension interface directly. Qt provides functions that wrap that functionality:
hasOpenGLFramebufferBlit - to check if the extension is present
blitFramebuffer (any of the 4 overloads) - to blit a framebufferor you can avoid creating your own buffer entirely and use toImage which takes care of all the low level buffer creation and blits using that extension.
If you prefer to use the low level API directly you also don't have to resolve the extension's functions pointers directly. Qt does this for you and provides a platform independent wrapper. See QOpenGLExtraFunctions::glBlitFramebuffer.
-
I have this exception:
When I try to use
glGetStringi
fromQOpenGLExtraFunctions
:QOpenGLExtraFunctions extraFunc; int numberOfExtensions; glGetIntegerv(GL_NUM_EXTENSIONS, &numberOfExtensions); for(int i = 0; i < numberOfExtensions; i++) { const GLubyte *ccc = extraFunc.glGetStringi(GL_EXTENSIONS, i); }
I called
initializeOpenGLFunctions()
before it:void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0, 1.f, 0.f, 1.f); qDebug() << glGetError(); GLubyte pixels[3]; glReadPixels(0, 0, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixels); qDebug() << glGetError(); qDebug() << pixels[0] << pixels[1] << pixels[2]; // qDebug() << QOpenGLFramebufferObject::hasOpenGLFramebufferObjects(); int myMultisampleFBO; glBindFramebuffer(GL_READ_FRAMEBUFFER, myMultisampleFBO); int myFBO; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, myFBO); QOpenGLExtraFunctions extraFunc; int numberOfExtensions; glGetIntegerv(GL_NUM_EXTENSIONS, &numberOfExtensions); for(int i = 0; i < numberOfExtensions; i++) { const GLubyte *ccc = extraFunc.glGetStringi(GL_EXTENSIONS, i); } // glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); // // qDebug() << glGetString(GL_EXTENSIONS); // qDebug() << my_multisample_fbo; }
And I set 3.1 version of OpenGL for an experiment:
QSurfaceFormat surfaceFormat; surfaceFormat.setMajorVersion(3); surfaceFormat.setMinorVersion(1); surfaceFormat.setSamples(4); setFormat(surfaceFormat);
Because the documentation says that QOpenGLExtraFunctions for
OpenGL ES 3.0, 3.1 and 3.2 API
-
@8Observer8 You are calling
initializeOpenGLFunctions
andglGetStringi
on two completely different instances. YourextraFunc
variable does not have the function pointers resolved.There are multiple ways to get the extra functions:
Way 1. Subclass QOpenGLExtraFunctions:
class OpenGLWidget : public QOpenGLWidget, private QOpenGLExtraFunctions
initialize:
void initializeGL() override { initializeOpenGLFunctions(); }
and then you can simply use it:
void paintGL() override { glGetStringi(GL_EXTENSIONS, 42); ...
Way 2. Make a separate variable for your functions and initialize it. Keep in mind this is slow, so you should be doing it only once and store the variable. Don't do it every time in the paint event for example.
QOpenGLExtraFunctions extraFunc; extraFunc.initializeOpenGLFunctions(); extraFunc.glGetStringi(GL_EXTENSIONS, 42);
Way 3. Get the function pointers from your context:
context()->extraFunctions()->glGetStringi(GL_EXTENSIONS, 42);
In any case you don't need the low level API to get the list of extensions. Again, Qt provides wrappers. In this case you can get the set of supported extensions via QOpenGLContext::extensions() function.
There's also QOpenGLContext::hasExtension() to check for support of any single extension.Another issue is that this will absolutely not work:
int myMultisampleFBO; glBindFramebuffer(GL_READ_FRAMEBUFFER, myMultisampleFBO);
Bind does not create a buffer. It just binds an existing buffer to the read or write slot.
myMultisampleFBO
is not an existing buffer id. It's an uninitialized variable. This is undefined behavior. You need to create a buffer and get its id to pass it to glBindFramebuffer. -
I have 1282 error in the
glBlitFramebuffer
function. What is wrong?void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0, 1.f, 0.f, 1.f); GLuint myMultisampleFBO; glGenFramebuffers(1, &myMultisampleFBO); glBindFramebuffer(GL_READ_FRAMEBUFFER, myMultisampleFBO); GLuint myFBO; glGenFramebuffers(1, &myFBO); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, myFBO); glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); qDebug() << glGetError();
I tried to translate this example: https://stackoverflow.com/questions/765434/glreadpixels-from-fbo-fails-with-multisampling
// Bind the multisampled FBO for reading glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, my_multisample_fbo); // Bind the normal FBO for drawing glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, my_fbo); // Blit the multisampled FBO to the normal FBO glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); //Bind the normal FBO for reading glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, my_fbo); // Read the pixels! glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
I inherited from
QOpenGLExtraFuntions
:class OpenGLWidget : public QOpenGLWidget, private QOpenGLExtraFunctions { public: OpenGLWidget() { resize(200, 200); // Set format QSurfaceFormat surfaceFormat; surfaceFormat.setMajorVersion(3); surfaceFormat.setMinorVersion(1); surfaceFormat.setSamples(4); setFormat(surfaceFormat); }
-
@8Observer8 There are multiple issues in your code.
If you can use OpenGL 3.1 then you don't need extensions. Framebuffer object is part of the 3.1 spec. But keep in mind that merely setting the format to the specific version in the constructor doesn't guarantee you a context with that version. It's just a request and graphics driver is free to give you any other version if what you asked for exactly is not available. You have to check in
initializeGL
what you actually got by inspectingcontext()->format()
.If you're going to read something from a framebuffer in
initializeGL
you need to first write there something, e.g. callglClear
before you start reading anything. Better yet don't do that ininitializeGL
but where it belongs i.e. inpaintGL
.This still won't work:
GLuint myMultisampleFBO; glGenFramebuffers(1, &myMultisampleFBO); glBindFramebuffer(GL_READ_FRAMEBUFFER, myMultisampleFBO);
This does create a buffer object, but the buffer has no color attachments i.e. there's nothing to draw to. You need to attach a texture to it to be able to draw to it.
I tried to translate this example
That example doesn't apply here one to one. It shows how to blit between two buffers, but you need to understand what is your source and destination. In your code you're creating two buffers and then reading from one to the other. What's the point? There's nothing in them so nothing to read or write to. You want to read from the default framebuffer Qt has created for your window, not some random thing you create.
You need to manage the read and write slots i.e. restore them to their original values, otherwise any drawing you do later will not go to the widget but to your buffer.
Buffer objects in OpenGL have attachments. You can have depth, stencil and several color attachments for a buffer. To write to a buffer you need at least one color attachment, a 2D texture in your case. Setting that all up manually is a lot of work you can avoid. Qt again provides easy wrapper in form of
QOpenGLFramebufferObject
. The default constructor sets up a non-MSAA framebuffer with a single 2D texture color attachment. Exactly what you need here.Here's a bare bones example. Context and error checks are omitted for brevity, but you definitely need to do the checks.
class OpenGLWidget: public QOpenGLWidget, public QOpenGLExtraFunctions { private: std::unique_ptr<QOpenGLFramebufferObject> fbo; public: OpenGLWidget() { QSurfaceFormat surfaceFormat; surfaceFormat.setSamples(4); setFormat(surfaceFormat); } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.f, 1.f, 0.f, 1.f); // Create a non-MSAA buffer with a single 1x1 color texture attachment fbo.reset(new QOpenGLFramebufferObject(1,1)); } void paintGL() override { // Clear the default framebuffer glClear(GL_COLOR_BUFFER_BIT); // Set draw buffer to be fbo. Read buffer is already the default one glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo->handle()); // Blit from the default MSAA buffer to non-MSAA fbo glBlitFramebuffer(0,0,fbo->width(), fbo->height(), 0,0, fbo->width(), fbo->height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); // Set the draw buffer back to default glBindFramebuffer(GL_DRAW_FRAMEBUFFER, context()->defaultFramebufferObject()); // Set read buffer. glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle()); // Read the pixel GLubyte pixel[4]; glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); // Set read buffer back to default glBindFramebuffer(GL_READ_FRAMEBUFFER, context()->defaultFramebufferObject()); } };
-
This post is deleted!
-
-
I tried to build for Android but I have errors. I created a new topic: Qt 6.6.1 Buid Error: error: use of undeclared identifier 'GL_DRAW_FRAMEBUFFER'
-
@Chris-Kawa please, help me with building for Android