I have made a simple Open GLES 2.0 shader on iOS where it works without problems and now I am attempting to do the same for GLES 3.0 on Android (5.1) compiling with the lastest NDK 10e. But now I get an all white output or really strange colors.
The idea is that the lookup up table is passed in as a 2D texture and the color from the actual image points to different x coordinates on line 0.0 in the lookup texture. I made the lookup texture 256x256 to avoid problems with it being to small instead of having it 256x1.
Too verify the fragment shader I output the colors from the input image to the left and they display correctly but the right is all white or very wrong colors depending on the input image. One problem I believe is not the problem is overflow because I don't clamp the values. I don't do this is in iOS and the texture( ) sampler should return 0.0 <= value <= 1.0
In the code attached I make a lookup table that will not do anything to the colors. Commenting this for loop out should give only black colors because of the memset above but still white or very strange colors.
Both devices with ARM Mali and Qualcomm Adreno GPU shows the same behavior and the code executes without any runtime fragment compile or GL errors. Android 5 or 6 also show the same error.
Now I feel that I've exhausted all my options and hope that someone can spot my mistake(s)
Full code attached from gl setup, program compile to gl shutdown, with fragments
static const char gVertexShader[] =
"#version 300 es\n"
"in vec4 vPosition;\n"
"in vec4 inputTextureCoordinate;\n"
"out vec2 texPosition;\n"
"void main() {\n"
" gl_Position = vPosition;\n"
" texPosition = inputTextureCoordinate.xy;\n"
"}\n";
static const char gFragmentShader[] =
"#version 300 es\n"
"layout(location = 0) out mediump vec4 color_frag_out;\n"
"precision mediump float;\n"
"in vec2 texPosition;\n"
"uniform sampler2D lookupTex;\n"
"uniform sampler2D rgbaTex;\n"
"void main() {\n"
"vec4 rgbaColor = texture(rgbaTex, texPosition);\n"
"float redlookup = texture(lookupTex, vec2(rgbaColor.r, 0.0)).b;\n"
"float greenlookup = texture(lookupTex, vec2(rgbaColor.g, 0.0)).g;\n"
"float bluelookup = texture(lookupTex, vec2(rgbaColor.b, 0.0)).r;\n"
"if (texPosition.x > 0.5) {\n"
"color_frag_out = vec4(redlookup, greenlookup, bluelookup, 1.0);\n"
"} else {\n"
"color_frag_out = rgbaColor;\n"
"}\n"
"}\n";
void start_frag_test(char* rgbaIn, char* rgbaOut, int w, int h)
{
EGLConfig eglConf;
EGLSurface eglSurface;
EGLContext eglCtx;
EGLDisplay eglDisp;
GLuint fboTexId;
GLint fboId;
GLuint gProgram;
GLuint gvPositionHandle;
GLint textureLocations[2];
GLuint textureIds[2];
GLint textCoordLoc = -10;
char* lookuptable = 0;
int lookuptableWidth = 256;
int lookuptableHeight = 256;
/// Init GL
// EGL config attributes
const EGLint confAttr[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // very important!
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, // we will create a pixelbuffer surface
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8, // if you need the alpha channel
/* EGL_DEPTH_SIZE, 16, // if you need the depth buffer */
EGL_NONE
};
// EGL context attributes
const EGLint ctxAttr[] = {
EGL_CONTEXT_CLIENT_VERSION, 3, // very important!
EGL_NONE
};
// surface attributes
// the surface size is set to the input frame size
const EGLint surfaceAttr[] = {
EGL_WIDTH, w,
EGL_HEIGHT, h,
EGL_NONE
};
EGLint eglMajVers, eglMinVers;
EGLint numConfigs;
eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(eglDisp, &eglMajVers, &eglMinVers);
MY_LOGI("EGL init with version %d.%d", eglMajVers, eglMinVers);
// choose the first config, i.e. best config
eglChooseConfig(eglDisp, confAttr, &eglConf, 1, &numConfigs);
eglCtx = eglCreateContext(eglDisp, eglConf, EGL_NO_CONTEXT, ctxAttr);
// create a pixelbuffer surface
eglSurface = eglCreatePbufferSurface(eglDisp, eglConf, surfaceAttr);
eglMakeCurrent(eglDisp, eglSurface, eglSurface, eglCtx);
// Print GL info
printGLString("Version", GL_VERSION);
printGLString("Vendor", GL_VENDOR);
printGLString("Renderer", GL_RENDERER);
printGLString("Extensions", GL_EXTENSIONS);
gProgram = createShaderProgram(gVertexShader, gFragmentShader);
if (!gProgram) {
MY_LOGE("Could not create program.");
return;
}
gvPositionHandle = glGetAttribLocation(gProgram, "vPosition");
checkGlError("glGetAttribLocation");
// Generate Frambuffer
glGenFramebuffers(1, &fboId);
checkGlError("glGenFramebuffers");
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
checkGlError("glBindFramebuffer");
// Now generate COLOR ATTACHMENT TEXTURE to use as output in fragment shader
glGenTextures (1, &fboTexId);
glBindTexture(GL_TEXTURE_2D, fboTexId);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, fboTexId, 0);
checkGlError("glFramebufferTexture2D");
// Assign a drawbuffer the FBO
GLenum drawBuffers [1] = {GL_COLOR_ATTACHMENT0};
glDrawBuffers(1, drawBuffers);
checkGlError("glDrawBuffers");
// Clears color to none white
glClearColor(.0f, 0.9f, .0f, .0f);
glUseProgram(gProgram);
checkGlError("glUseProgram");
glViewport(0, 0, w, h);
checkGlError("glViewport");
glGenTextures(2, &textureIds);
textureLocations[0] = glGetUniformLocation(gProgram, "rgbaTex");
textureLocations[1] = glGetUniformLocation(gProgram, "lookupTex");
// Bind texture with image data
glActiveTexture(GL_TEXTURE0 + textureLocations[0]);
checkGlError("glActiveTexture");
glBindTexture(GL_TEXTURE_2D, textureIds[0]); // bind input texture
checkGlError("glBindTexture 0");
MY_LOGI("rgbain id:%d loc:%d w:%d h:%d", textureIds[0], textureLocations[0], w, h);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(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_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgbaIn);
checkGlError("glTexImage2D");
// Bind texture with lookup data
int lookupTableSize = lookuptableWidth * lookuptableHeight * 4;
MY_LOGI("Lookup table size:%d", lookupTableSize);
lookuptable = malloc(lookupTableSize);
memset(lookuptable, 0,lookupTableSize); // Make sure all are 0
unsigned int lookupvalue = 0;
int i;
// Fill table with 0 to 255 for all color channels
// With this for loop commented out the lookup table should represent all black
// from the memset above. But output is still white.
for (i = 0; i < lookuptableWidth*4; i+=4)
{
lookuptable[i] = lookupvalue; // R
lookuptable[i+1] = lookupvalue; // G
lookuptable[i+2] = lookupvalue; // B
lookuptable[i+3] = 255; // A
lookupvalue++;
}
glActiveTexture(GL_TEXTURE0 + textureLocations[1]);
checkGlError("glActiveTexture");
glBindTexture(GL_TEXTURE_2D, textureIds[1]); // bind input texture
checkGlError("glBindTexture 0");
MY_LOGI("lookup id:%d loc:%d w:%d h:%d", textureIds[1], textureLocations[1], w, h);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
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_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, lookuptable);
checkGlError("glTexImage2D");
textCoordLoc = glGetAttribLocation(gProgram, "inputTextureCoordinate");
glVertexAttribPointer( gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, screenVertices);
glEnableVertexAttribArray( gvPositionHandle );
glVertexAttribPointer( textCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, texVertices );
glEnableVertexAttribArray( textCoordLoc );
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
checkGlError("glDrawArrays");
glFinish();
checkGlError("glFinish");
glReadBuffer(GL_COLOR_ATTACHMENT0);
checkGlError("glReadBuffer");
glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, rgbaOut);
checkGlError("glDrawArrays");
if (!eglMakeCurrent(eglDisp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT))
{
MY_LOGE("eglMakeCurrent failed");
}
MY_LOGI("eglMakeCurrent");
eglDestroyContext(eglDisp, eglCtx);
MY_LOGI("eglDestroyContext");
eglDestroySurface(eglDisp, eglSurface);
MY_LOGI("eglDestroySurface");
//eglReleaseThread();
eglTerminate(eglDisp);
MY_LOGI("eglTerminate");
eglDisp = EGL_NO_DISPLAY;
eglSurface = EGL_NO_SURFACE;
eglCtx = EGL_NO_CONTEXT;
free(lookuptable);
}
const GLfloat screenVertices[] = {
-1.0f, 1.0f, // top left
1.0f, 1.0f, // top right
-1.0f, -1.0f, // bottom left
1.0f, -1.0f // bottom right
};
static const float texVertices[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
static void checkGlError(const char* op)
{
GLint error;
for (error = glGetError(); error; error = glGetError())
{
MY_LOGE("after %s() glError (0x%x)\n", op, error);
}
}
static void printGLString(const char *name, GLenum s) {
const char *v = (const char *) glGetString(s);
MY_LOGI("GL %s = %s\n", name, v);
}
GLuint loadShaderFromString(GLenum shaderType, const char* pSource) {
GLuint shader = glCreateShader(shaderType);
if (shader) {
glShaderSource(shader, 1, &pSource, NULL);
glCompileShader(shader);
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen) {
char* buf = (char*) malloc(infoLen);
if (buf) {
glGetShaderInfoLog(shader, infoLen, NULL, buf);
MY_LOGE("Could not compile shader %d:\n%s\n",
shaderType, buf);
free(buf);
}
glDeleteShader(shader);
shader = 0;
}
}
}
return shader;
}
GLuint createShaderProgram(const char* pVertexSource, const char* pFragmentSource) {
GLuint vertexShader = loadShaderFromString(GL_VERTEX_SHADER, pVertexSource);
if (!vertexShader) {
return 0;
}
GLuint pixelShader = loadShaderFromString(GL_FRAGMENT_SHADER, pFragmentSource);
if (!pixelShader) {
return 0;
}
GLuint program = glCreateProgram();
if (program) {
glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
glLinkProgram(program);
GLint linkStatus = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
if (linkStatus != GL_TRUE) {
GLint bufLength = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
if (bufLength) {
char* buf = (char*) malloc(bufLength);
if (buf) {
glGetProgramInfoLog(program, bufLength, NULL, buf);
MY_LOGE("Could not link program:\n%s\n", buf);
free(buf);
}
}
glDeleteProgram(program);
program = 0;
}
}
return program;
}
Your useage of the sampler uniforms is wrong. The following code fragment does not what you intend it to do:
glGenTextures(2, &textureIds);
textureLocations[0] = glGetUniformLocation(gProgram, "rgbaTex");
textureLocations[1] = glGetUniformLocation(gProgram, "lookupTex");
// Bind texture with image data
glActiveTexture(GL_TEXTURE0 + textureLocations[0]);
You must not use the uniform location as the texture unit. The sampler*
data types in GLSL represent opaque handles which must be set to the index of the texture unit. By default, all uniforms are intialized to zero, so any sampler
will sample from the a texture which is bound to texture unit GL_TEXTURE0
. If you want to use multitexturing, you have to manually set those uniform values of the sampler, to match the texture units you want to use. So your code should look like this:
// after the shader program was linked
textureLocations[0] = glGetUniformLocation(gProgram, "rgbaTex");
textureLocations[1] = glGetUniformLocation(gProgram, "lookupTex");
glUseProgram(your_program);
glUniform1i(textureLocations[0], 0); // sample unit 0 for rgba Texture
glUniform1i(textureLocations[1], 1); // sample unit 1 for the LUT
// create the textures
glGenTextures(2, &textureIds);
// it doesn't matter which unit you use to create the texture objects
// ...
// at draw time: bind all the necessary textures
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(..., textureIds[0]);
glActiveTexture(GL_TEXTURE0 + 1);
glBindTexture(..., textureIds[1]);
// issue the draw call ...