I try to render a quad with texture into a framebuffer object with a texture GL_COLOR_ATTACHMENT0 attachment. Then I call glReadPixels
to copy the content of the framebuffer back to main memory.
I am using EGL to provide an OpenGL context.
What I noticed is that unless I create an EGL pixel buffer (by calling eglCreatePbufferSurface
) and make that part of the current context, glReadPixels
will return a buffer that is completely black.
I have attached my code here, and the code produces the desired result as it is currently written. However, if I changes the line eglMakeCurrent(display, surface, surface, context);
to eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context);
, then the output I get is completely black.
Is it normal that I need both an EGL pixel buffer and OpenGL FBO to enable offscreen rendering?
A textbook I’ve been reading, OpenGL ES 3.0 Programming Guide by Dan Ginsburg, seems to suggest that OpenGL FBO is an alternative to EGL pixel buffer and we should not need both. If that’s the case, what am I doing wrong in my test code?
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <vector>
#include <algorithm>
#include "dlib/image_io.h"
#include "dlib/image_transforms.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include <EGL/egl.h>
#include "GLES3/gl31.h"
using namespace std;
namespace {
void assertOpenGLError(const std::string& msg) {
GLenum error = glGetError();
if (error != GL_NO_ERROR) {
stringstream s;
s << "OpenGL error 0x" << std::hex << error << " at " << msg;
throw runtime_error(s.str());
}
}
void assertEGLError(const std::string& msg) {
EGLint error = eglGetError();
if (error != EGL_SUCCESS) {
stringstream s;
s << "EGL error 0x" << std::hex << error << " at " << msg;
throw runtime_error(s.str());
}
}
template<typename T>
unsigned char SafeUCharCast(T input);
template<>
unsigned char SafeUCharCast<float>(float input)
{
return static_cast<unsigned char>(std::clamp<float>(input*255, 0.f, 255.f));
}
template<>
unsigned char SafeUCharCast<unsigned char>(unsigned char input)
{
return input;
}
template<typename T>
void SaveRGBImageToDisk(std::string fileName, size_t width, size_t height, const T* dataRaw)
{
constexpr size_t inputDepth = 3;
dlib::array2d<dlib::rgb_pixel> img(height, width);
int idx = 0;
for(auto& pix: img)
{
pix.red = SafeUCharCast(dataRaw[idx]);
pix.green = SafeUCharCast(dataRaw[idx+1]);
pix.blue = SafeUCharCast(dataRaw[idx+2]);
idx += inputDepth;
}
dlib::save_png(img, fileName+".png");
}
const static std::string vertexShader =
"#version 300 es\n"
"layout(location = 0) in vec4 position;\n"
"layout(location = 1) in vec2 texCoord;\n"
"out vec2 v_TexCoord;\n"
"uniform mat4 u_MVP;\n"
"void main() {\n"
" gl_Position = u_MVP*position;\n"
" v_TexCoord = texCoord;\n"
"}\n";
const static std::string fragmentShader =
"#version 300 es\n"
"precision highp float;\n"
"precision highp sampler2D;\n"
"layout(location = 0) out vec4 color;\n"
"in vec2 v_TexCoord;\n"
"uniform sampler2D u_Texture;\n"
"void main() {\n"
" vec4 texColor = texture(u_Texture, v_TexCoord);\n"
" color = texColor;\n"
"}\n";
unsigned int CompileShader(unsigned int type, const std::string& source) {
unsigned int id = glCreateShader(type);
const char* src = source.c_str();
glShaderSource(id, 1, &src, nullptr);
glCompileShader(id);
int result;
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
if(result == GL_FALSE) {
int length;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
char* message = (char*) alloca(length*sizeof(char));
glGetShaderInfoLog(id, length, &length, message);
std::cout << "Failed to compile "<< (type == GL_VERTEX_SHADER ? "vertex" : "fragment")<<" shader"<<std::endl;
std::cout << message << std::endl;
glDeleteShader(id);
return 0;
}
return id;
}
unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader){
unsigned int program = glCreateProgram();
unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);
glAttachShader(program, vs);
assertOpenGLError("glAttachShader");
glAttachShader(program, fs);
assertOpenGLError("glAttachShader");
glLinkProgram(program);
assertOpenGLError("glLinkProgram");
glValidateProgram(program);
assertOpenGLError("glValidateProgram");
glDeleteShader(vs);
glDeleteShader(fs);
return program;
}
std::vector<unsigned char> LoadImageInterleaved(std::string fileName, size_t& width, size_t& height)
{
constexpr auto numChannels = 3;
dlib::array2d<dlib::rgb_pixel> img;
dlib::load_png(img, fileName);
width = img.nc();
height = img.nr();
std::vector<unsigned char> inputBuffer(img.nr()*img.nc()*numChannels);
unsigned char* inputBufferRaw = &inputBuffer[0];
for(auto pix: img)
{
inputBufferRaw[0] = pix.red;
inputBufferRaw[1] = pix.green;
inputBufferRaw[2] = pix.blue;
inputBufferRaw += numChannels;
}
return inputBuffer;
}
}
int main() {
unsetenv( "DISPLAY" );
size_t imgWidth, imgHeight;
auto imgData = LoadImageInterleaved("../res/textures/penguin.png", imgWidth, imgHeight);
/*
* EGL initialization and OpenGL context creation.
*/
EGLDisplay display;
EGLConfig config;
EGLContext context;
EGLSurface surface;
EGLint num_config;
const EGLint DISPLAY_ATTRIBS[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_BLUE_SIZE, 5, EGL_GREEN_SIZE, 6, EGL_RED_SIZE, 5,
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_NONE
};
const EGLint CONTEXT_ATTRIBS[] = {
EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE
};
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
assertEGLError("eglGetDisplay");
eglInitialize(display, nullptr, nullptr);
assertEGLError("eglInitialize");
eglChooseConfig(display, DISPLAY_ATTRIBS, &config, 1, &num_config);
assertEGLError("eglChooseConfig");
eglBindAPI(EGL_OPENGL_API);
assertEGLError("eglBindAPI");
context = eglCreateContext(display, config, EGL_NO_CONTEXT, CONTEXT_ATTRIBS);
assertEGLError("eglCreateContext");
constexpr int winWidth = 256;
constexpr int winHeight = 256;
EGLint SURFACE_ATTRIBS[] = {
EGL_WIDTH, winWidth,
EGL_HEIGHT, winHeight,
EGL_NONE
};
surface = eglCreatePbufferSurface(display, config, SURFACE_ATTRIBS);
assertEGLError("eglCreatePbufferSurface");
eglMakeCurrent(display, surface, surface, context);
//eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context);
assertEGLError("eglMakeCurrent");
std::string versionString = std::string((const char*)glGetString(GL_VERSION));
std::cout<<versionString<<std::endl;
//Enable blending
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
auto CreateTexture = [](GLuint& tex, int width, int height, void* data) {
glGenTextures(1, &tex);
assertOpenGLError("glGenTextures");
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
assertOpenGLError("glTexImage2D");
glBindTexture(GL_TEXTURE_2D, 0);
};
/*
* Create an OpenGL framebuffer as render target.
*/
GLuint frameBuffer;
glGenFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
assertOpenGLError("glBindFramebuffer");
/*
* Create a texture as color attachment.
*/
GLuint outputTex;
CreateTexture(outputTex, winWidth, winHeight, nullptr);
glBindTexture(GL_TEXTURE_2D, outputTex);
/*
* Attach the texture to the framebuffer.
*/
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, outputTex, 0);
assertOpenGLError("glFramebufferTexture2D");
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
throw std::runtime_error("frame buffer went kaput");
/*
* Create an OpenGL texture and load it with image data.
*/
GLuint inputTex;
CreateTexture(inputTex, imgWidth, imgHeight, imgData.data());
glBindTexture(GL_TEXTURE_2D, inputTex);
constexpr int textureSlot = 0;
glActiveTexture(GL_TEXTURE0+textureSlot);
assertOpenGLError("glActiveTexture");
//Build and use the shaders
auto program = CreateShader(vertexShader, fragmentShader);
glUseProgram(program);
assertOpenGLError("glUseProgram");
glUniform1i(glGetUniformLocation(program, "u_Texture"), textureSlot);
assertOpenGLError("glUniform1i, glGetUniformLocation");
glm::mat4 proj = glm::ortho(0.f, (float)winWidth, 0.f, (float)winHeight, -1.f, 1.f);
glUniformMatrix4fv(glGetUniformLocation(program, "u_MVP"), 1, GL_FALSE, &proj[0][0]);
assertOpenGLError("glUniformMatrix4fv, glGetUniformLocation");
//Define geometry to render and texture mapping
float positions[] = {0.f, 0.f, 0.f, 0.f,
winWidth, 0.f, 1.f, 0.f,
winWidth, winHeight, 1.f, 1.f,
0.f, winHeight, 0.f, 1.f
};
unsigned int indices[] = {
0, 1, 2,
2, 3, 0
};
GLuint ibo;
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (const void*) 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (const void*) (2*sizeof(GLfloat)));
/*
* Render something.
*/
//glClearColor(0.3, 0.8, 0.5, 1.0);
glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(unsigned int), GL_UNSIGNED_INT, nullptr);
//glClear(GL_COLOR_BUFFER_BIT);
glFlush();
/*
* Read the framebuffer and save to disk as an image
*/
std::vector<unsigned char> buf(winWidth*winHeight*3);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(0, 0, winWidth, winHeight, GL_RGB, GL_UNSIGNED_BYTE, buf.data());
assertOpenGLError("glReadPixels");
SaveRGBImageToDisk("output", winWidth, winHeight, buf.data());
/*
* Destroy context.
*/
glDeleteBuffers(1, &ibo);
glDeleteBuffers(1, &vbo);
glDeleteTextures(1, &inputTex);
glDeleteFramebuffers(1, &frameBuffer);
glDeleteTextures(1, &outputTex);
glDeleteProgram(program);
eglDestroySurface(display, surface);
assertEGLError("eglDestroySurface");
eglDestroyContext(display, context);
assertEGLError("eglDestroyContext");
eglTerminate(display);
assertEGLError("eglTerminate");
return 0;
}