Repeated calls to glTexImage2D

Started by Androcir, February 02, 2016, 16:08:59

Previous topic - Next topic

Androcir

Hello, i'm relatively new to LWJGL so i have a couple of questions.
First of all, i'm trying do develop a 2D game using LWJGL 3 and i've come across an issue on the rendering part.
Since i need to draw multiple tiles of the map, i am currently making 1 call to glTexImage2D each time i render one tile. Well this has proven to be quite heavy on the application itself and i was experiencing issues with the application window. Loss of fps, window not responding immideatly, etc.

So i am looking for a way to either reduce the number of calls to the glTexImage2D method or a new method that is lighter and more efficient.

This is the mothod where i render one tile. Meaning that this method is called once for every time a single tile is rendered, in other words, if i have 250 tiles it will be called 250 times per frame.

public void render() {
		glPushMatrix();
		glViewport(0, 0, GameWindow.WIDTH, GameWindow.HEIGHT);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0, GameWindow.WIDTH, GameWindow.HEIGHT, 0, 0, 1);

		glEnable(GL_TEXTURE_2D);
		glTexParameteri(GL_TEXTURE_2D,* GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 		
 		glBindTexture(GL_TEXTURE_2D, texture.getTextureID());
		glBegin(GL_QUADS); glTexCoord2f(0, 0); 
		glVertex2f(x, y); glTexCoord2f(1, 0);
  		glVertex2f(x + TILE_SIZE, y); 
		glTexCoord2f(1, 1); 
		glVertex2f(x + TILE_SIZE, y + TILE_SIZE); 
		glTexCoord2f(0, 1); 
		glVertex2f(x, y + TILE_SIZE); 
		glEnd();
 		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.getWidth(),texture.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE,texture.getTextureData());
		glDisable(GL_TEXTURE_2D);

		glFlush();
		glPopMatrix();

	}
}


I'm not 100% if this is the exact code i have (in terms of ordering) i worte this from memory so..
Anyways, if anyone knows someway that can help i would apreciate it.
Thanks

Kai

First of all, please show the real code of yours.
Arguing about code that is sketched out from memory which you are also not sure whether it is the actual code, is quite counter-productive to finding any issue.
People can spot right now at least three issues wrong with this code, then point it out to you with you then saying:
"oh yes, sorry, those two lines were actually swapped/exchanged in the real code, that's not the issue...".
One thing in particular: Why are you uploading the texture data after the draw call (that glBegin/glEnd)??
So, just show the real code.

Second, please carefully read about what glTexImage2D does. First performance improvement hint: Multiple texture objects and not one texture object updating its contents all the time.

Additionally, there are many things which you don't need to do per tile:
- viewport only needs to be updated whenever the window size changes
- projection matrix only needs to be updated whenever the window size changes
- glEnable(GL_TEXTURE_2D) can remain enabled iff you draw everything with a texture
- texture filtering parameters don't need to be set repeatedly on the same texture
- you don't need glFlush()

Now, a few questions:
- does every tile have a different 'texture' object?
- what about the contents (the texels) of those texture objects: is it mostly the same or different for each tile?

Androcir

Okay, you're right about the counterproductive part.
Sorry about that.
Before i read your answer i just realized that i missunderstood the way glTexImage2D is used, so i went ahead and redid everything in order to use it properly.
I also went ahead and implemented vaos and vbos to store the texture coordinates and vertex of each tile.
And also i corrected everything that you pointed out.

Now for your questions there are tiles with diferent textures yes.
I don't know what you mean with the second question.

My code now looks like this:
public void render() {
		glPushMatrix();
		glLoadIdentity();
		glOrtho(0, GameWindow.WIDTH, GameWindow.HEIGHT, 0, 0, 1);
				 
		
		GL30.glBindVertexArray(model.getModel().getVaoID());
		GL20.glEnableVertexAttribArray(0);
		GL20.glEnableVertexAttribArray(1);
		GL13.glActiveTexture(GL13.GL_TEXTURE0);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, model.getTexture().getTextureID());
		//GL11.glDrawArrays(GL_QUADS, 0,model.getModel().getVertexCount());
		GL11.glDrawElements(GL_QUADS, model.getModel().getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
		GL20.glDisableVertexAttribArray(1);
		GL20.glDisableVertexAttribArray(0);
		GL30.glBindVertexArray(0);

		glPopMatrix();

	}


And the loader, where i lod the textures looks like this:
public static int loadTexture(String path){
			BufferedImage image = null;
			ByteBuffer buffer = null;
			int textId = -1;
			try {
				image = ImageIO.read(new File(path));
				int[] pixels = new int[image.getWidth() * image.getHeight()];
			    image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());

			    buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * 4); //4 for RGBA, 3 for RGB
			    
			    for(int y = 0; y < image.getHeight(); y++){
			        for(int x = 0; x < image.getWidth(); x++){
			            int pixel = pixels[y * image.getWidth() + x];
			            buffer.put((byte) ((pixel >> 16) & 0xFF));     // Red component
			            buffer.put((byte) ((pixel >> 8) & 0xFF));      // Green component
			            buffer.put((byte) (pixel & 0xFF));               // Blue component
			            buffer.put((byte) ((pixel >> 24) & 0xFF));    // Alpha component. Only for RGBA
			        }
			    }

			    buffer.flip();
			    textId = GL11.glGenTextures();
			    GL11.glBindTexture(GL11.GL_TEXTURE_2D, textId);
			    GL11.glTexParameteri(GL11.GL_TEXTURE_2D,GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); 
			    GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
			    GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA,image.getWidth(),image.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE,buffer);
			    GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
			} catch(Exception ex){
				
			}
			//TextureObject(textId, buffer, image.getWidth(), image.getHeight());
			return textId;
	}


I am experiencing another problem now though... The application crashes due to this line:
GL11.glDrawElements(GL_QUADS, model.getModel().getVertexCount(), GL11.GL_UNSIGNED_INT, 0);


And i get this crash message:

A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000073567af6, pid=4076, tid=3324
#
# JRE version: Java(TM) SE Runtime Environment (8.0_60-b27) (build 1.8.0_60-b27)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.60-b23 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# C  [nvoglv64.DLL+0x13b7af6]
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# C:\Users\lel\Documents\Mestrado Exclipse\HorrorMaze\hs_err_pid4076.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.


EDIT:
Okay so i found one of the issues which is me using GL_QUADS on glDrawElements, since it doesn't accept that kind of primitive...

Kai

About the crash: Make sure you have specified buffer sources (via glVertexAttribPointer) for both vertex attributes with index 0 and index 1 with valid parameters, and that each respective buffer object is bound (via glBindBuffer) to GL_ARRAY_BUFFER at the time of calling glVertexAttribPointer.
You may not call glVertexPoint/glNormalPointer/glTexCoordPointer anymore now to specify vertex buffer sources, since apparently you are using generic vertex arrays now.
Since you are using glDrawElements you also (of course) need an element/index buffer bound (bound via glBindBuffer to GL_ELEMENT_ARRAY_BUFFER) at the time of calling glDrawElements.

Another thing: Please don't use AWT's BufferedImage for loading texture images. :)
LWJGL 3 has a MUCH nicer and VERY MUCH faster alternative with stb. See in the lwjgl3 GitHub sources for demos.

Androcir

Ok so i went ahead and created an index buffer. Here's the code for it:
private static void bindIndicesBuffer(IntBuffer indices){
		int vboID = GL15.glGenBuffers();
		vbos.add(vboID);
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboID);
		GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indices, GL15.GL_STATIC_DRAW);
	}


And here's the arrays of vertices that i create and stor on vbos and vaos
float[] vertices = { 
				this.x, this.y, 0.0f, 
				this.x + TILE_SIZE, y, 0.0f,
				this.x + TILE_SIZE, this.y + TILE_SIZE, 0.0f, 
				this.x,	this.y + TILE_SIZE, 0.0f 
				};
		
		int[] indices = {
				0,1,3,
				3,1,2
		};

		float[] textureVertices = { 
				0.0f, 0.0f,
				1.0f, 0.0f,
				1.0f, 1.0f, 
				0.0f, 1.0f };


With all that done i still get the same crash error though

EDIT:
-------------------
Ok so i fixed the crash. Turns out i was creating the indexvbo but outside the vao binding.
So now it doesn't crash but it also doesn't render the texture. I only renders some random triangles like this:
http://prntscr.com/9y5q54

Kai

QuoteMake sure you have specified buffer sources (via glVertexAttribPointer) for both vertex attributes with index 0 and index 1 with valid parameters, and that each respective buffer object is bound (via glBindBuffer) to GL_ARRAY_BUFFER at the time of calling glVertexAttribPointer.
You may not call glVertexPoint/glNormalPointer/glTexCoordPointer anymore now to specify vertex buffer sources, since apparently you are using generic vertex arrays now.
Apparently, there cannot be anything wrong with creating Java float[] arrays. The relevant part is where you setup the vertex attributes for OpenGL with that data.

Androcir

HEre's my loader class, where i setup everything related to vaos and vbos and also texture loading:
package Engine;

import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;

import org.lwjgl.BufferUtils;

import Primitives.Model;
import Primitives.TextureObject;
import Primitives.TexturedModel;

import org.lwjgl.opengl.*;

public class Loader {
	private static List<Integer> vaos = new ArrayList<Integer>();
	private static List<Integer> vbos = new ArrayList<Integer>();
	private static List<Integer> textures = new ArrayList<Integer>();
	
	public static TexturedModel loadToVAO(float[] vertices, int[] indices, float[] texture, int textureId){
		int vaoID = GL30.glGenVertexArrays();
		vaos.add(vaoID);
		GL30.glBindVertexArray(vaoID);

		IntBuffer indicesBuffer = createIntBuffer(indices);
		bindIndicesBuffer(indicesBuffer);
		FloatBuffer buffer = createFloatBuffer(vertices);
		createAndStoreVBO(buffer, 3, 0);
		buffer = createFloatBuffer(texture);
		createAndStoreVBO(buffer, 2, 1);
		
		GL30.glBindVertexArray(0);
		
		Model model = new Model(vaoID, indices.length);
		TextureObject textureObj = new TextureObject(textureId);
		
		return new TexturedModel(model, textureObj);
	}
	
	private static void createAndStoreVBO(FloatBuffer buffer, int size, int index){
		int vboID = GL15.glGenBuffers();
		vbos.add(vboID);
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
		GL20.glVertexAttribPointer(index, size, GL11.GL_FLOAT, false, 0, 0);
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
	}
	
	private static FloatBuffer createFloatBuffer(float[] vertices){
		FloatBuffer buffer = BufferUtils.createFloatBuffer(vertices.length);
		buffer.put(vertices);
		buffer.flip();
		return buffer;
	}
	
	private static IntBuffer createIntBuffer(int[] indices){
		IntBuffer buffer = BufferUtils.createIntBuffer(indices.length);
		buffer.put(indices);
		buffer.flip();
		return buffer;
	}
	
	private static void bindIndicesBuffer(IntBuffer indices){
		int vboID = GL15.glGenBuffers();
		vbos.add(vboID);
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboID);
		GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indices, GL15.GL_STATIC_DRAW);
	}	
	

	public static int loadTexture(String path){
			BufferedImage image = null;
			ByteBuffer buffer = null;
			int textId = -1;
			try {
				image = ImageIO.read(new File(path));
				int[] pixels = new int[image.getWidth() * image.getHeight()];
			    image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());

			    buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * 4); //4 for RGBA, 3 for RGB
			    
			    for(int y = 0; y < image.getHeight(); y++){
			        for(int x = 0; x < image.getWidth(); x++){
			            int pixel = pixels[y * image.getWidth() + x];
			            buffer.put((byte) ((pixel >> 16) & 0xFF));     // Red component
			            buffer.put((byte) ((pixel >> 8) & 0xFF));      // Green component
			            buffer.put((byte) (pixel & 0xFF));               // Blue component
			            buffer.put((byte) ((pixel >> 24) & 0xFF));    // Alpha component. Only for RGBA
			        }
			    }

			    buffer.flip();
			    textId = GL11.glGenTextures();
			    textures.add(textId);
			    GL11.glBindTexture(GL11.GL_TEXTURE_2D, textId);
			    GL11.glTexParameteri(GL11.GL_TEXTURE_2D,GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); 
			    GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
			    GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA,image.getWidth(),image.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE,buffer);
			    GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
			} catch(Exception ex){
				ex.printStackTrace();
			}
			//TextureObject(textId, buffer, image.getWidth(), image.getHeight());
			return textId;
	}
	
	public static void cleanUp(){
		for(int vbo : vbos){
			GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
			GL15.glDeleteBuffers(vbo);
		}
		for(int vao : vaos){
			GL30.glBindVertexArray(vao);
			GL30.glDeleteVertexArrays(vao);
		}
		for(int texture : textures){
			GL11.glDeleteTextures(texture);
		}
	}
}

Hydroque

Okay so what I want you to do is head over to ThinMatrix on youtube and follow his tutorials. You need them and they will be fantastic for how far you've gotten.

What you want to do is enable shaders and bind a uniform variable to a texture once and only need to update the texture when the texture data itself changes.