[Newbie] Textures And Pixels Reading Unexpected Behaviour. Can't find what is wrong.

Let me briefly explain you what is going on. I did some basic OpenGL coding to render a cube with a texture. When I upload only one texture and bind it, the texture is getting applied correctly on the cube. If I load more textures, but only bind the first one before every draw call, I’m getting the following result:

[ATTACH=CONFIG]1935[/ATTACH]

In the Debug output you can see how many textures i’m uploading and some information about them. But I’m only binding the first one a.jpg before every draw call.
Debug Output:


file:      Resources/a.jpg      channels: 3, Divisible by 4: yes, width: 2048,  height: 2048,  widthXheight: 4194304
file: Resources/container.jpg   channels: 3, Divisible by 4: yes, width: 512,  height: 512,  widthXheight: 262144
file: Resources/brick2.jpg      channels: 3, Divisible by 4: yes, width: 900,  height: 900,  widthXheight: 810000
file: Resources/brick3.jpg      channels: 3, Divisible by 4: yes, width: 736,  height: 736,  widthXheight: 541696
file: Resources/container2.png  channels: 4, Divisible by 4: yes, width: 500,  height: 500,  widthXheight: 250000

I made sure that:

  1. The glTexImage2D() function takes the correct format of the source data.
  2. The width * channels is multiple of 4 in order to prevent this OpenGL common mistake about pixel alignment
  3. 99% my shaders and the vertex data are correct. I will post them at the end of this post just in case you find any problems.
  4. I’m not getting any glErrors or shader compilation errors.

So, before reading my code remember: This unexpected behaviour happens only when I’m uploading more than one textures to the GPU. If I load only the one which I’m using to render the cube, everything works just fine (take a look):
[ATTACH=CONFIG]1936[/ATTACH]
Debug Output:


file:      Resources/a.jpg      channels: 3, Divisible by 4: yes, width: 2048,  height: 2048,  widthXheight: 4194304

One last thing. If I change the Texture class (which you can find at the end of this post) to force 4 channels like this:



        //Load the pixel data to the RAM and force 4 channels. So if for example the source image is RGB
        //the *data pointer will point to an array with RGBA values which the A channels will be initialised to 1.
	unsigned char *data = stbi_load(path.c_str(), &m_width, &m_height, &m_channels, 4);

	//Image loaded successfully.
	if (data)
	{

		//Generate the texture and bind it.
		GLCall(glGenTextures(1, &m_id));
		GLCall(glBindTexture(GL_TEXTURE_2D, m_id));

		GLCall(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data));

		//Texture Filters.
		GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
		GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
		GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
		GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));

		//Generate mipmaps.
		GLCall(glGenerateMipmap(GL_TEXTURE_2D));

the rendering of the cube works just fine no matter how many textures i upload, no matter what the width of each source file is and no matter the number of channels the source image has. But this is bad because it wastes VRAM.
If for example you have a black and white image which requires one channel, you will initialise 3 more channels (GBA) for each pixel just only for the driver to read the data correctly.

So by your experience is anything else I can check? What else could cause this? Should I stick with the force 4 channels and waste VRAM is some cases but always be sure that the texture is going to work?

Below You can read and check my code. Most important are the Texture class and Main.cpp. The Shader class is big, but you don’t actually need to see it, it works but i posted it just in case.
In the Renderer Class, you can see the vertex specification and how I render things.

Vertex Shader:


#version 330 core

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;


out vec2 TexCoord;

void main()
{
	gl_Position = proj * view * model * vec4(aPos, 1.0);

	TexCoord = aTexCoord;
}

Fragment Shader:


#version 330 core

out vec4 Color;
in vec2 TexCoord;

uniform sampler2D diffuse;

void main()
{
	Color = texture(diffuse, TexCoord);
}

Texture Class:


#include "texture.h"
#include "stb_image/stb_image.h"
#include "glcall.h"
#include "engine_error.h"
#include <math.h>


//Constructor.
Texture::Texture(std::string path, bool trans, int unit)
{

	//Reverse the pixels.
	stbi_set_flip_vertically_on_load(1);

	//Try to load the image.
	unsigned char *data = stbi_load(path.c_str(), &m_width, &m_height, &m_channels, 0);

	//Debug.
	float check = (m_width * m_channels) / 4.0f;
	printf("file: %20s 	channels: %d, Divisible by 4: %s, width: %d,  height: %d,  widthXheight: %d
", 
		path.c_str(), m_channels, check == ceilf(check) ? "yes" : "no", m_width, m_height, m_width * m_height);


	//Image loaded successfully.
	if (data)
	{

		//Generate the texture and bind it.
		GLCall(glGenTextures(1, &m_id));
		GLCall(glBindTexture(GL_TEXTURE_2D, m_id));

		
		//Not Transparent texture.
		if (m_channels == 3)
		{
			GLCall(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGB, GL_UNSIGNED_BYTE, data));
		}

		//Transparent texture.
		else if (m_channels == 4)
		{
			GLCall(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data));
		}

		//Unsupported Channels.
		else
		{
			throw EngineError("Unsupported Channels!!!");
		}
		

		//Texture Filters.
		GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
		GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
		GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
		GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));

		//Generate mipmaps.
		GLCall(glGenerateMipmap(GL_TEXTURE_2D));
	}


	//Loading Failed.
	else
		throw EngineError("The was an error loading image: " + path);



	//Unbind the texture.
	GLCall(glBindTexture(GL_TEXTURE_2D, 0));

	//Free the image data.
	stbi_image_free(data);
}





//Destroy the texture.
Texture::~Texture()
{
	GLCall(glDeleteTextures(1, &m_id));
}





//Bind the texture.
void Texture::Bind(int unit)
{
	GLCall(glActiveTexture(GL_TEXTURE0 + unit));
	GLCall(glBindTexture(GL_TEXTURE_2D, m_id));
}


Main.cpp:


#include "Renderer.h"
#include "camera.h"



int main(void)
{
	GLFWwindow* window;

	/* Initialize the library */
	if (!glfwInit())
		return -1;


	//Use openGL version 3.3 Core Profile.
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	/* Create a windowed mode window and its OpenGL context */
	window = glfwCreateWindow(800, 600, "Hello World", NULL, NULL);
	if (!window)
	{
		glfwTerminate();
		return -1;
	}

	/* Make the window's context current */
	glfwMakeContextCurrent(window);


	//Initialize GLEW.
	if (glewInit() != GLEW_OK)
	{
		glfwTerminate();
		return -1;
	}


	//Enable Depth Test.
	GLCall(glEnable(GL_DEPTH_TEST));

	//Create the renderer and the shader objects.
	Renderer *renderer = new Renderer();
	Shader *shader     = new Shader("Shaders/basic_vertex.glsl", "Shaders/basic_fragment.glsl");


	//Load the textures.
	Texture *texture1 = new Texture("Resources/a.jpg", false);
	Texture *texture2 = new Texture("Resources/container.jpg", false);
	Texture *texture3 = new Texture("Resources/brick2.jpg", false);
	Texture *texture4 = new Texture("Resources/brick3.jpg", false);
	Texture *texture5 = new Texture("Resources/brick4.jpg", false);
	Texture *texture6 = new Texture("Resources/container2.png", true);


	/* Loop until the user closes the window */
	while (!glfwWindowShouldClose(window))
	{

		//Clear the screen.
		renderer->ClearScreen(0.0f, 0.0f, 0.0f);

		//Render the cube WITH THE texture1 (DONT USE THE OTHER TEXTURES AT ALL).
		renderer->Render(texture1, shader);

		//Update.
		renderer->Update(window);
	}


	//-------------Clean Up-------------//
	delete renderer;
	delete shader;

	//forget about textures for now.
	//-------------Clean Up-------------//


	glfwTerminate();
	return 0;
}

Renderer Class:


#include "Renderer.h"


Renderer::Renderer()
{

	//Vertex Data.
	float vertices[] = {

		// positions          // normals           // texture coords
		-0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,
		 0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,
		 0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
		 0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
		-0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,
		-0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,

		-0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 0.0f,
		 0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 0.0f,
		 0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 1.0f,
		 0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 1.0f,
		-0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 1.0f,
		-0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 0.0f,

		-0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
		-0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
		-0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
		-0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
		-0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 0.0f,
		-0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,

		 0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
		 0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
		 0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
		 0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
		 0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f,
		 0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,

		-0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,
		 0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 1.0f,
		 0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,
		 0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,
		-0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f,
		-0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,

		-0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,
		 0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,
		 0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
		 0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
		-0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,
		-0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f
	};


	//Generate a VAO and a VBO.
	GLCall(glGenVertexArrays(1, &m_VAO));
	GLCall(glGenBuffers(1, &m_VBO));

	//Bind VAO and VBO.
	GLCall(glBindVertexArray(m_VAO));
	GLCall(glBindBuffer(GL_ARRAY_BUFFER, m_VBO));

	//Transfer The Data.
	GLCall(glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW));

	//Positions.
	GLCall(glEnableVertexAttribArray(0));
	GLCall(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void *)0));

	//Normals.
	GLCall(glEnableVertexAttribArray(1));
	GLCall(glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void *) 12));

	//Texture Coordinates.
	GLCall(glEnableVertexAttribArray(2));
	GLCall(glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void *) 24));

	//Unbind The Buffers.
	GLCall(glBindVertexArray(0));
	GLCall(glBindBuffer(GL_ARRAY_BUFFER, 0));
}





Renderer::~Renderer()
{
}





void Renderer::ClearScreen(float r, float g, float b)
{
	GLCall(glClearColor(r, g, b, 1.0f));
	GLCall(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
}





void Renderer::Update(GLFWwindow * window)
{
	glfwSwapBuffers(window);
	glfwPollEvents();
}





void Renderer::Render(Texture *texture, Shader *program)
{

	//Bind VAO.
	GLCall(glBindVertexArray(m_VAO));

	//Bind The Program.
	program->Bind();

	//Set the unit to be used on the shader.
	program->SetUniform1i("diffuse", 0);

	//Bind the texture at unit zero.
	texture->Bind(0);

	//Some Matrices.
	glm::mat4 model = glm::mat4(1.0f);
	glm::mat4 view  = glm::mat4(1.0f);
	glm::mat4 proj  = glm::mat4(1.0f);

	//Create the View Matrix.
	view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
	view = glm::rotate(view, glm::radians(30.0f), glm::vec3(1.0f, 0.0f, 0.0f));
	view = glm::rotate(view, glm::radians(30.0f), glm::vec3(0.0f, 1.0f, 0.0f));

	//Create The Perspective Projection.
	proj = glm::perspective(glm::radians(45.0f), 800.0f / 600, 0.1f, 100.0f);

	//Set the transformation uniforms.
	program->SetUniformMat4f("model", model);
	program->SetUniformMat4f("view", view);
	program->SetUniformMat4f("proj", proj);

	//Draw Call.
	GLCall(glDrawArrays(GL_TRIANGLES, 0, 36));
}

Shader Class:


#include "shader.h"
#include "glcall.h"
#include "engine_error.h"
#include <fstream>
#include <string>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>


struct ShaderSource 
{
	std::string vertex_src;
	std::string fragment_src;
};


static void ReadSources(std::string filename, bool is_vertex, struct ShaderSource *src)
{

	//Create a file object.
	std::ifstream file;

	//Open the file.
	file.open(filename, std::ios::in);

	//If the file opened successfully read it.
	if (file.is_open())
	{

		//Size of the file.
		file.seekg(0, std::ios::end);
		std::streampos size = file.tellg();
		file.seekg(0, std::ios::beg);

		//Allocate memory to store the data.
		char *data = (char *)malloc(sizeof(char) * (size + (std::streampos)1) );

		//Read the data from the file.
		file.read(data, size);

		//Close the string.
		data[file.gcount()] = '\0';

		//Close the file.
		file.close();

		//This was the vertex file.
		if (is_vertex)
			src->vertex_src = (const char *)data;

		//This was the fragment file.
		else
			src->fragment_src = (const char *)data;

		//Release the memory for the data since I coppied them into the ShaderSource structure.
		free(data);
	}


	//Problem opening the file.
	else
		throw EngineError("There was a problem opening the file: " + filename);
}





static unsigned int CompileShader(std::string source, GLenum type)
{

	//__________Local Variables__________//
	int length, success;
	//__________Local Variables__________//


	//Create the shader object.
	GLCall(unsigned int shader = glCreateShader(type));

	//std::string to const c string.
	const char *src = source.c_str();

	//Copy the source code into the shader object.
	GLCall(glShaderSource(shader, 1, &src, NULL));

	//Compile the shader.
	GLCall(glCompileShader(shader));

	//Get the shader info length.
	GLCall(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length));

	//Get the shader compilations status.
	GLCall(glGetShaderiv(shader, GL_COMPILE_STATUS, &success));


	//Compilation Failed.
	if (!success)
	{

		//Error string.
		std::string error;

		//Allocate memory for the info log.
		char *info = (char *)malloc(sizeof(char) * (length+1) );

		//Get the info.
		GLCall(glGetShaderInfoLog(shader, length, NULL, info));

		//Terminate the string.
		info[length] = '\0';

		//Initialize the error message as vertex compilation error.
		if (type == GL_VERTEX_SHADER)
			error = "Vertex Shader compilations error: ";

		//Initialize the error message as fragment compilation error.
		else
			error = "Fragment Shader compilation error: ";

		//Add the info log to the message.
		error += info;

		//Free info.
		free(info);

		//Throw a message error.
		throw EngineError(error);
	}

	return shader;
}





static unsigned int CreateProgram(ShaderSource &src)
{

	//__________Local Variables__________//
	int length, success;
	//__________Local Variables__________//

	unsigned int program = glCreateProgram();

	unsigned int vertex_shader   = CompileShader(src.vertex_src, GL_VERTEX_SHADER);
	unsigned int fragment_shader = CompileShader(src.fragment_src, GL_FRAGMENT_SHADER);

	GLCall(glAttachShader(program, vertex_shader));
	GLCall(glAttachShader(program, fragment_shader));

	GLCall(glLinkProgram(program));
	GLCall(glValidateProgram(program));



	//Get the shader info length.
	GLCall(glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length));

	//Get the shader compilations status.
	GLCall(glGetProgramiv(program, GL_LINK_STATUS, &success));

	//Linking Failed.
	if (!success)
	{

		//Error string.
		std::string error = "Linking Error: ";

		//Allocate memory for the info log.
		char *info = (char *)malloc(sizeof(char) * (length + 1));

		//Get the info.
		GLCall(glGetProgramInfoLog(program, length, NULL, info));

		//Terminate the string.
		info[length] = '\0';

		//Add the info log to the message.
		error += info;

		//Free info.
		free(info);

		//Throw a message error.
		throw EngineError(error);
	}

	return program;
}






Shader::Shader(std::string vertex_filename, std::string fragment_filename)
{

	//Create a ShaderSource object.
	ShaderSource source;

	//Read the sources.
	ReadSources(vertex_filename, true, &source);
	ReadSources(fragment_filename, false, &source);

	//Create the program.
	m_id = CreateProgram(source);

	//And start using it.
	this->Bind();
	
}






Shader::~Shader()
{
}





void Shader::Bind()
{
	GLCall(glUseProgram(m_id));
}






void Shader::SetUniform1i(std::string name, int value)
{

	//Bind the shader.
	this->Bind();

	//Get uniform location.
	GLCall(int location = glGetUniformLocation(m_id, name.c_str()));

	//Set the value.
	GLCall(glUniform1i(location, value));
}

void Shader::SetUniformMat4f(std::string name, glm::mat4 mat)
{
	//Bind the shader.
	this->Bind();

	//Get uniform location.
	GLCall(int location = glGetUniformLocation(m_id, name.c_str()));

	//Set the mat4.
	GLCall(glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(mat)));
}

void Shader::SetUniformVec3(std::string name, glm::vec3 vec3)
{
	//Bind the shader.
	this->Bind();

	//Get uniform location.
	GLCall(int location = glGetUniformLocation(m_id, name.c_str()));

	//Set the Uniform.
	GLCall(glUniform3f(location, vec3.x, vec3.y, vec3.z));
}

void Shader::SetUniform1f(std::string name, float value)
{
	//Bind the shader.
	this->Bind();

	//Get uniform location.
	GLCall(int location = glGetUniformLocation(m_id, name.c_str()));

	GLCall(glUniform1f(location, value));
}

Also I would like to say something that I noticed right now. The problem was being caused by images which the width and height was not a power of 2. I resized these images to 512x512 (which 512 is a power of 2) and the result is what I expected, a nice looking brick cube. So by taking a look at my texture code, what do you think is wrong?