OpenGL ES 2.0/3.0 Offscreen rendering with glRenderbuffer
Rendering to offscreen surfaces is a key component to any graphics pipeline. Post-processing effects, deferred rendering, and newer global illumination strategies all use it. Unfortunately, implementing offscreen rendering on OpenGL ES is not well documented. OpenGL ES is often used on embedded/mobile devices, and until recently, these devices haven’t typically had the graphics bandwidth to keep up with new rendering techniques. Compound this with the fact that many mobile games have simple gameplay/small screens that do not need such complex lighting models, many people now use off the shelf engines for their games, and that there is still a good amount of mobile hardware out there that doesn’t even support render to offscreen surfaces, and it is no surprise that few people use the technique and it’s not well discussed.
In implementing offscreen rendering for OpenGL ES, I turned to the very good OpenGL ES Programming book as it has a whole chapter on framebuffer objects. When I tried the samples in the book, however, I was having a lot of difficulty getting it working on my linux-based mobile device. A lot of the implementation examples use a technique of creating framebuffer objects using textures, but you can also use framebuffer objects via something called render buffers. One reason this is good to know is because many hardware vendors support very few render-to-texture formats. You can often find yourself struggling with your implementation not working because the output formats aren’t supported.
Thankfully, I found this article and thought I’d copy the information here since it’s the only place I’ve seen working code that demonstrated the technique. It also includes the very important step of reading the output format and uses glReadPixels() so you can validate that you were writing correctly to the offscreen renderbuffer surface.
In my case, on an Intel graphics part, I found that the format (which is also the most recommended one) that worked was GL_RGB/GL_UNSIGNED_SHORT_5_6_5. Steps 1-8 is standard OpenGL ES setup code that is included so you can verify your setup. Step 9 is where the glFrameBuffer and glRenderBuffer objects are created.
#define CONTEXT_ES20 #ifdef CONTEXT_ES20 EGLint ai32ContextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; #endif // Step 1 - Get the default display. EGLDisplay eglDisplay = eglGetDisplay((EGLNativeDisplayType)0); // Step 2 - Initialize EGL. eglInitialize(eglDisplay, 0, 0); #ifdef CONTEXT_ES20 // Step 3 - Make OpenGL ES the current API. eglBindAPI(EGL_OPENGL_ES_API); // Step 4 - Specify the required configuration attributes. EGLint pi32ConfigAttribs[5]; pi32ConfigAttribs[0] = EGL_SURFACE_TYPE; pi32ConfigAttribs[1] = EGL_WINDOW_BIT; pi32ConfigAttribs[2] = EGL_RENDERABLE_TYPE; pi32ConfigAttribs[3] = EGL_OPENGL_ES2_BIT; pi32ConfigAttribs[4] = EGL_NONE; #else EGLint pi32ConfigAttribs[3]; pi32ConfigAttribs[0] = EGL_SURFACE_TYPE; pi32ConfigAttribs[1] = EGL_WINDOW_BIT; pi32ConfigAttribs[2] = EGL_NONE; #endif // Step 5 - Find a config that matches all requirements. int iConfigs; EGLConfig eglConfig; eglChooseConfig(eglDisplay, pi32ConfigAttribs, &eglConfig, 1, &iConfigs); if (iConfigs != 1) { printf("Error: eglChooseConfig(): config not found.n"); exit(-1); } // Step 6 - Create a surface to draw to. EGLSurface eglSurface; eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, (EGLNativeWindowType)NULL, NULL); // Step 7 - Create a context. EGLContext eglContext; #ifdef CONTEXT_ES20 eglContext = eglCreateContext(eglDisplay, eglConfig, NULL, ai32ContextAttribs); #else eglContext = eglCreateContext(eglDisplay, eglConfig, NULL, NULL); #endif // Step 8 - Bind the context to the current thread eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); // end of standard gl context setup // Step 9 - create framebuffer object GLuint fboId = 0; GLuint renderBufferWidth = 1280; GLuint renderBufferHeight = 720; // create a framebuffer object glGenFramebuffers(1, &fboId); glBindFramebuffer(GL_FRAMEBUFFER, fboId); // create a texture object // note that this is commented out/not used in this case but is // included for completeness/as example /* GLuint textureId; glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_2D, textureId); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //GL_LINEAR_MIPMAP_LINEAR 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_GENERATE_MIPMAP_HINT, GL_TRUE); // automatic mipmap glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, renderBufferWidth, renderBufferHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); glBindTexture(GL_TEXTURE_2D, 0); // attach the texture to FBO color attachment point glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0); */ qDebug() << glGetError(); GLuint renderBuffer; glGenRenderbuffers(1, &renderBuffer); glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer); qDebug() << glGetError(); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB565, renderBufferWidth, renderBufferHeight); qDebug() << glGetError(); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer); qDebug() << glGetError(); GLuint depthRenderbuffer; glGenRenderbuffers(1, &depthRenderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, renderBufferWidth, renderBufferHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer); // check FBO status GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if(status != GL_FRAMEBUFFER_COMPLETE) { printf("Problem with OpenGL framebuffer after specifying color render buffer: n%xn", status); } else { printf("FBO creation succeddedn"); } // check the output format // This is critical to knowing what surface format just got created // ES only supports 5-6-5 and other limited formats and the driver // might have picked another format GLint format = 0, type = 0; glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &format); glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &type); // clear the offscreen buffer glClearColor(1.0,0.0,1.0,1.0); glClear(GL_COLOR_BUFFER_BIT); // commit the clear to the offscreen surface eglSwapBuffers(eglDisplay, eglSurface); // You should put your own calculation code here based on format/type // you discovered above int size = 2 * renderBufferHeight * renderBufferWidth; unsigned char *data = new unsigned char[size]; printf("size %d", size); // in my case, I got back a buffer that was RGB565 glReadPixels(0,0,renderBufferWidth,renderBufferHeight,GL_RGB, GL_RGB565, data); // Check output buffer to make sure you cleared it properly. // In 5-6-5 format, clearing to clearcolor=(1, 0, 1, 1) // you get 1111100000011111b = 0xF81F in hex if( (data[0] != 0x1F) || (data[1] != 0xF8)) printf("Error rendering to offscreen buffern"); QImage image(data2, renderBufferWidth, renderBufferHeight,renderBufferWidth*2, QImage::Format_RGB16); image.save("result.png");
2 thoughts on “OpenGL ES 2.0/3.0 Offscreen rendering with glRenderbuffer”
hello
please can you shared the complet code
thanks
This was part of a larger project, so I can’t post all of it. I would start with a very simple hello world OpenGL app, then add this code. That should be sufficient. https://www.lighthouse3d.com/tutorials/glsl-tutorial/hello-world/