Render Texture from PNG to Screen not working correctly OpenGL ES 3.0

Started by
28 comments, last by DevAndroid 5 years, 10 months ago

Hello everyone,

I'm trying to display a 2D texture to screen but the rendering isn't working correctly.

First of all I did follow this tutorial to be able to render a Text to screen (I adapted it to render with OpenGL ES 2.0) : https://learnopengl.com/code_viewer.php?code=in-practice/text_rendering

So here is the shader I'm using :


const char           gVertexShader[] =
        "#version 320 es\n"
                "layout (location = 0) in vec4 vertex;\n"
                "out vec2 TexCoords;\n"
        "uniform mat4 projection;\n"
                "void main() {\n"
                "  gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);\n"
        "  TexCoords = vertex.zw;\n"
                "}\n";

const char           gFragmentShader[] =
        "#version 320 es\n"
        "precision mediump float;\n"
        "in vec2 TexCoords;\n"
                "out vec4 color;\n"
                "uniform sampler2D text;\n"
        "uniform vec3 textColor;\n"
                "void main() {\n"
                "  vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);\n"
        "  color = vec4(textColor, 1.0) * sampled;\n"
        "}\n";

The render text works very well so I would like to keep those Shaders program to render a texture loaded from PNG.

For that I'm using libPNG to load the PNG to a texture, here is my code :


GLuint Cluster::loadPngFromPath(const char *file_name, int *width, int *height) {
    png_byte header[8];

    FILE *fp = fopen(file_name, "rb");
    if (fp == 0) {
        return 0;
    }

    fread(header, 1, 8, fp);
    if (png_sig_cmp(header, 0, 8)) {
        fclose(fp);
        return 0;
    }

    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) {
        fclose(fp);
        return 0;
    }

    png_infop info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
        fclose(fp);
        return 0;
    }

    png_infop end_info = png_create_info_struct(png_ptr);
    if (!end_info) {
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
        fclose(fp);
        return 0;
    }

    if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        fclose(fp);
        return 0;
    }

    png_init_io(png_ptr, fp);
    png_set_sig_bytes(png_ptr, 8);
    png_read_info(png_ptr, info_ptr);

    int bit_depth, color_type;
    png_uint_32 temp_width, temp_height;

    png_get_IHDR(png_ptr, info_ptr, &temp_width, &temp_height, &bit_depth, &color_type, NULL, NULL, NULL);

    if (width) {
	*width = temp_width;
    }
    if (height) {
	*height = temp_height;
    }

    png_read_update_info(png_ptr, info_ptr);

    int rowbytes = png_get_rowbytes(png_ptr, info_ptr);

    rowbytes += 3 - ((rowbytes-1) % 4);

    png_byte * image_data;
    image_data = (png_byte *) malloc(rowbytes * temp_height * sizeof(png_byte)+15);
    if (image_data == NULL) {
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        fclose(fp);
        return 0;
    }

    png_bytep * row_pointers = (png_bytep *) malloc(temp_height * sizeof(png_bytep));
    if (row_pointers == NULL) {
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        free(image_data);
        fclose(fp);
        return 0;
    }

    int i;
    for (i = 0; i < temp_height; i++) {
        row_pointers[temp_height - 1 - i] = image_data + i * rowbytes;
    }

    png_read_image(png_ptr, row_pointers);

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    glTexImage2D(GL_TEXTURE_2D, GL_ZERO, GL_RGB, temp_width, temp_height, GL_ZERO, GL_RGB, GL_UNSIGNED_BYTE, image_data);
    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);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
	
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
    free(image_data);
    free(row_pointers);
    fclose(fp);

    return texture;
}

This code just generates the texture and I store the id on memory

And then I want to display my texture on any position (X, Y) of my screen so I did the following (That's works, at least the positioning).


//MY TEXTURE IS 32x32 pixels !
void Cluster::printTexture(GLuint idTexture, GLfloat x, GLfloat y) {
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(VAO);
 
    GLfloat vertices[6][4] = {
            { x,     	y + 32,		0.0, 0.0 },
            { x,     	y,       	0.0, 1.0 },
            { x + 32, 	y,       	1.0, 1.0 },

            { x,     	y + 32,   	0.0, 0.0 },
            { x + 32, 	y,       	1.0, 1.0 },
            { x + 32,   y + 32,   	1.0, 0.0 }         
     };

     glBindTexture(GL_TEXTURE_2D, idTexture);
     glBindBuffer(GL_ARRAY_BUFFER, VBO);
     glBufferSubData(GL_ARRAY_BUFFER, GL_ZERO, sizeof(vertices), vertices);
     glBindBuffer(GL_ARRAY_BUFFER, GL_ZERO);
     glUniform1i(this->mTextShaderHandle, GL_ZERO);
     glDrawArrays(GL_TRIANGLE_STRIP, GL_ZERO, 6);
}

My .png is a blue square.

The result is that my texture is not loaded correctly. It is not complete and there are many small black spots. I don't know what's going on ? It could be the vertices or the load ? Or maybe I need to add something on the shader. I don't know, I really need help.

Thanks !

Advertisement

1) If you use the calculation of texture coordinates in vertex shader, you should remove them from Vertex data, 

2) You should setup vertex attributes:


GLfloat vertices[6][2] = {
            { x,     	y + 32	},
            { x,     	y		},
            { x + 32, 	y      	},

            { x,     	y + 32	},
            { x + 32, 	y		},
            { x + 32,   y + 32	}         
     };
	 glBindTexture(GL_TEXTURE_2D, idTexture);
     glBindBuffer(GL_ARRAY_BUFFER, VBO);
	 // first you should call glBufferData instead of glBufferSubData, because OpenGL doesn't know about memory size.
	 // if you call glBufferSubData, then you will get GL_INVALID_ENUM, see please:
	 // https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glBufferSubData.xml
	 // https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glBufferSubData.xhtml
     glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC);
	 // you should setup vertex attributes 
	 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), NULL);
     glUniform1i(this->mTextShaderHandle, GL_ZERO);
     glDrawArrays(GL_TRIANGLE_STRIP, GL_ZERO, 6);
     glBindVertexArray(0);

2) Do you set uniforms "texColor", "projection" ?

also you can simplify the pixel shader:


float alpha = texture(text, TexCoords).r);\n"
color = vec4(textColor, alpha);\n"

Try to use my corrections.

And you use OpenGL ES 3.0, not 2.0, because the VertexArray Object for OpenGL ES 2.0 is supported through GL_OES_vertex_array_object extension.

3DGraphics,Direct3D12,Vulkan,OpenCL,Algorithms

Hello Andrey,

Thank you very much for your answer and your help.

It seems that your modifications did resolve the problem I had (The texture is well displayed).

For your questions : I don't set texColor for the PNG Texture. Only when I draw a Text to color it.

I set a projection like that one time at the start of the program :


glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(WIDTH), 0.0f, static_cast<GLfloat>(HEIGHT));
glUniformMatrix4fv(this->mProjectionShaderHandle, 1, GL_FALSE, glm::value_ptr(projection));

I simplified the pixel shader.

I have a problem occuring now when I call glBufferData instead of glBufferSubData :

When I try to draw a text after drawing a texture I got an error 0x501 on glBufferSubData. Here is my draw code for the text rendering :


glBindTexture(GL_TEXTURE_2D, ch.TextureID);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferSubData(GL_ARRAY_BUFFER, GL_ZERO, sizeof(vertices), vertices); //error 0x501 there
glBindBuffer(GL_ARRAY_BUFFER, GL_ZERO);
glDrawArrays(GL_TRIANGLES, GL_ZERO, 6);

Just so you know, here is my initialization (just one time after surface creation and Shader program loading) here I set the memory allocation with glBufferData that's why I was using subData for my texture rendering.


//Configuration of Vertex Array Objects & Vertex Buffer Objects for texture quads
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW); //Here I have a glBufferData call
glEnableVertexAttribArray(GL_ZERO);
glVertexAttribPointer(GL_ZERO, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), GL_ZERO);
glBindBuffer(GL_ARRAY_BUFFER, GL_ZERO);
glBindVertexArray(GL_ZERO);

Maybe I have to call this kind of code only for the text rendering and not be global to whole program ?

Thank you again for your help.

Hello, DevAndroid, 

Try to comment binding of zero buffer Id in initialization/rendering code, because it can be saved in VAO state:


// glBindBuffer(GL_ARRAY_BUFFER, GL_ZERO);

 

3DGraphics,Direct3D12,Vulkan,OpenCL,Algorithms

Still glBufferSubData of text rendering fails with 0x501 error

 


//My print Text function
void Cluster::printText(const std::string &text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color) {
    glUniform3f(this->mColorShaderHandle, color.x/255.0f, color.y/255.0f, color.z/255.0f);
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(VAO);

    std::string::const_iterator c;
    for (c = text.begin(); c != text.end(); c++) {
        Character ch = this->mCharacters[*c];

        GLfloat xpos = x + ch.Bearing.x * scale;
        GLfloat ypos = y - (ch.Size.y - ch.Bearing.y) * scale;

        GLfloat w = ch.Size.x * scale;
        GLfloat h = ch.Size.y * scale;

        GLfloat vertices[6][4] = {
            { xpos,     ypos + h,   	0.0, 0.0 },
            { xpos,     ypos,       	0.0, 1.0 },
            { xpos + w, ypos,       	1.0, 1.0 },

            { xpos,     ypos + h,   	0.0, 0.0 },
            { xpos + w, ypos,       	1.0, 1.0 },
            { xpos + w, ypos + h,   	1.0, 0.0 }          
        };

        glBindTexture(GL_TEXTURE_2D, ch.TextureID);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);        
	    glBufferSubData(GL_ARRAY_BUFFER, GL_ZERO, sizeof(vertices), vertices);
        glBindBuffer(GL_ARRAY_BUFFER, GL_ZERO);
        glDrawArrays(GL_TRIANGLES, GL_ZERO, 6);
        x += (ch.Advance >> 6) * scale;
    }
    glBindVertexArray(GL_ZERO);
    glBindTexture(GL_TEXTURE_2D, GL_ZERO);
}

//my printTexture function
void Cluster::printTexture(GLuint idTexture, GLfloat x, GLfloat y) {
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(VAO);

    GLfloat vertices[6][2] = {
            { x,     	y + 32	},
            { x,     	y	},
            { x + 32, 	y      	},

            { x,     	y + 32	},
            { x + 32, 	y	},
            { x + 32,   y + 32	}
     };

     glBindTexture(GL_TEXTURE_2D, idTexture);
     glBindBuffer(GL_ARRAY_BUFFER, VBO);

     glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
     glVertexAttribPointer(GL_ZERO, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), GL_ZERO);
     glUniform1i(this->mTextShaderHandle, GL_ZERO);
     glDrawArrays(GL_TRIANGLES, GL_ZERO, 6);
     glBindVertexArray(0);
}

Here you have more details of my functions to print a text and print a texture.

GL_INVALID_VALUE is generated if offset or size is negative, or if together they define a region of memory that extends beyond the buffer object's allocated data store.

Try to check size of vertex data in glBufferData(2 parameter) /glBufferSubData (3 parameter)

if error still reproduced, you can create the Desktop version of project with OpenGL ES emulation and attach it to forum.

3DGraphics,Direct3D12,Vulkan,OpenCL,Algorithms

Yes, in fact, please just check my last post I edited with the full code.

for the text print I have 6 * 4 values. That's why I was initializing the global program with:


glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_STATIC_DRAW);

but for the texture I have 6 * 2 values and I make a glBufferData so I think it reduces the size of the VBO and so it fails when I try to set an other text..

I cannot really make this work on desktop easily because, I'm running this program on an android AOSP. So i'm using some custom things to create the surface (using HWC of Android)

 

EDIT:

By putting glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_STATIC_DRAW); at the end of printTexture I don't have the error but the texture is no longer displayed... But I have all texts.

Quote

Still glBufferSubData of text rendering fails with 0x501 error

try to remove 


// glBindBuffer(GL_ARRAY_BUFFER, GL_ZERO);

in 

Quote

 

but for the texture I have 6 * 2 values and I make a glBufferData so I think it reduces the size of the VBO and so it fails when I try to set an other text..


 

 

if you use


glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices, GL_STATIC_DRAW);

instead of glBufferData in printTexture with reduced size, is error reproduced ?

Try to use separate VBO for text and print texture, because using the same buffer for different size is strange decision.

Also for dynamic data you should use GL_DYNAMIC_DRAW

 

3DGraphics,Direct3D12,Vulkan,OpenCL,Algorithms

20 minutes ago, Andrey OGL_D3D said:

Try to use separate VBO for text and print texture, because using the same buffer for different size is strange decision.

Excellent, that is what I'm doing just right now.

Like that :


//Configuration of Vertex Array Objects & Vertex Buffer Objects for texture quads
	glGenVertexArrays(2, this->VAO);
    	glGenBuffers(2, this->VBO);

	//init font rendering VAO VBO
    	glBindVertexArray(this->VAO[0]);
    	glBindBuffer(GL_ARRAY_BUFFER, this->VBO[0]);
    	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, GL_ZERO, GL_DYNAMIC_DRAW);
    	glEnableVertexAttribArray(GL_ZERO);
    	glVertexAttribPointer(GL_ZERO, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), GL_ZERO);
    	glBindVertexArray(GL_ZERO);

	//init texture rendering VAO VBO
    	glBindVertexArray(this->VAO[1]);
    	glBindBuffer(GL_ARRAY_BUFFER, this->VBO[1]);
    	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 2, GL_ZERO, GL_STATIC_DRAW);
    	glEnableVertexAttribArray(GL_ZERO);
    	glVertexAttribPointer(GL_ZERO, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), GL_ZERO);
    	glBindVertexArray(GL_ZERO);
 

And here is my new printTexture and my new printText


void Cluster::printText(const std::string &text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color) {
    glUniform3f(this->mColorShaderHandle, color.x/255.0f, color.y/255.0f, color.z/255.0f);
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(this->VAO[0]);

    std::string::const_iterator c;
    for (c = text.begin(); c != text.end(); c++) {
        Character ch = this->mCharacters[*c];

        GLfloat xpos = x + ch.Bearing.x * scale;
        GLfloat ypos = y - (ch.Size.y - ch.Bearing.y) * scale;

        GLfloat w = ch.Size.x * scale;
        GLfloat h = ch.Size.y * scale;

        GLfloat vertices[6][4] = {
            { xpos,     ypos + h,   	0.0, 0.0 },
            { xpos,     ypos,       	0.0, 1.0 },
            { xpos + w, ypos,       	1.0, 1.0 },

            { xpos,     ypos + h,   	0.0, 0.0 },
            { xpos + w, ypos,       	1.0, 1.0 },
            { xpos + w, ypos + h,   	1.0, 0.0 }          
        };

        glBindTexture(GL_TEXTURE_2D, ch.TextureID);
        glBindBuffer(GL_ARRAY_BUFFER, this->VBO[0]);
     	glBufferSubData(GL_ARRAY_BUFFER, GL_ZERO, sizeof(vertices), vertices);
        glBindBuffer(GL_ARRAY_BUFFER, GL_ZERO);
        glDrawArrays(GL_TRIANGLES, GL_ZERO, 6);
    	x += (ch.Advance >> 6) * scale;
    }
    glBindVertexArray(GL_ZERO);
    glBindTexture(GL_TEXTURE_2D, GL_ZERO);
}

void Cluster::printTexture(GLuint idTexture, GLfloat x, GLfloat y) {
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(this->VAO[1]);

    GLfloat vertices[6][2] = {
            { x,     	y + 32	},
            { x,     	y	},
            { x + 32, 	y      	},

            { x,     	y + 32	},
            { x + 32, 	y	},
            { x + 32,   y + 32	}
    };

    glBindTexture(GL_TEXTURE_2D, idTexture);
    glBindBuffer(GL_ARRAY_BUFFER, this->VBO[1]);

    glBufferSubData(GL_ARRAY_BUFFER, GL_ZERO, sizeof(vertices), vertices);
    glDrawArrays(GL_TRIANGLE_STRIP, GL_ZERO, 6);
    glBindVertexArray(GL_ZERO);
    glBindTexture(GL_TEXTURE_2D, GL_ZERO);
}

And the main is like that :

I load every characters for font redering then I load my PNG's using libPNG.

Then I call my printText and my printTexture function like that for example :


glClearColor(GL_ZERO, GL_ZERO, GL_ZERO, GL_ZERO);
glClear(GL_COLOR_BUFFER_BIT);
printText("HELLO", 50.0f, 1000.0f, 0.5f, glm::vec3(47.0f, 239.0f, 236.0f));
printText("WORLD", 200.0f, 1000.0f, 0.4f, glm::vec3(125.0f, 185.0f, 184.0f));
printTexture(idOfTextureLoaded, 50.0f, 380.0f);
eglSwapBuffers(this->mDisplay, this->mSurface);

Result is that the text "Hello World" is displayed at the right position but the texture still not..

 

What do you think ? Thank you for your help.

12 minutes ago, DevAndroid said:

at the right position but the texture still not..

1) Problem with Z coordinates for textured quad ? You can use the same float vertices[6][4] instead of float vertices[6][2]

2) vertex shader is waiting


in vec4 vertex

attribute, but you set


glVertexAttribPointer(GL_ZERO, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), GL_ZERO);

 

You can remove duplicate code and use the common function for preparing VBO/VAO for textured quad and text symbol with different data.

3DGraphics,Direct3D12,Vulkan,OpenCL,Algorithms

This topic is closed to new replies.

Advertisement