I started watching these (https://www.youtube.com/playlist?list=PLILiqflMilIxta2xKk2EftiRHD4nQGW0u) tutorials for creating a 2d top-down game using LWJGL and I read that VBO's should be fast but for rendering 48*48 tiles per frame I get only about 60-70FPS with VSYNC off and 40FPS with VSYNC on which is pretty slow because I will add a lot more stuff to the game than just some static, not moving or changing, tiles.
What can I do to make this faster?
Thing is, I'm a complete noob with opengl or anything related. I have just watched the tutorials and I don't fully understand how half of the code works.
People said that I should put all the tiles in a single buffer and batch render them, but I have no idea how to do this. Please help. :(
I don't know what else to put here but the full source code and resources + shader files are available on github here (https://github.com/TeodorVecerdi/LWJGLTutorial).
Anyways, here are some parts of my code:
The main loop
double frame_time = 0;
int frames = 0, updates = 0;
shader.setUniform("sampler", 0);
double updates_cap = 1.0 / 60.0;
double time = Timer.getTime();
double unprocessed = 0;
while (!window.shouldClose()) {
boolean canRender = false;
double time2 = Timer.getTime();
double passed = time2 - time;
unprocessed += passed;
frame_time += passed;
time = time2;
while(unprocessed >= updates_cap) {
// --- [ update ] ---
unprocessed -= updates_cap;
canRender = true;
if (window.getInput().isKeyPressed(GLFW_KEY_ESCAPE)) {
glfwSetWindowShouldClose(window.getWindow(), true);
}
if (window.getInput().isKeyPressed(GLFW_KEY_1)) {
window.setVSYNC(true);
}
if (window.getInput().isKeyPressed(GLFW_KEY_2)) {
window.setVSYNC(false);
}
if (window.getInput().isKeyDown(GLFW_KEY_W)) {
camera.getPosition().sub(new Vector3f(0, 10, 0));
}
if (window.getInput().isKeyDown(GLFW_KEY_A)) {
camera.getPosition().sub(new Vector3f(-10, 0, 0));
}
if (window.getInput().isKeyDown(GLFW_KEY_S)) {
camera.getPosition().sub(new Vector3f(0, -10, 0));
}
if (window.getInput().isKeyDown(GLFW_KEY_D)) {
camera.getPosition().sub(new Vector3f(10, 0, 0));
}
world.correctCamera(camera, window);
window.update();
updates++;
}
if(canRender) {
// --- [ render ] ---
glClear(GL_COLOR_BUFFER_BIT);
world.render(tileRenderer, shader, camera, window);
window.swapBuffers();
frames++;
}
if (frame_time >= 1.0) {
System.out.println(String.format("UPS: %s, FPS: %s", updates, frames));
frames = 0;
updates = 0;
frame_time = 0;
}
}
The input handler methods used:
I have this in my constructor:
this.keys = new boolean[GLFW_KEY_LAST];
for(int i = 0; i < GLFW_KEY_LAST; i++)
keys[i] = false;
And here are the methods:
public boolean isKeyDown(int key) {
return glfwGetKey(window, key) == 1;
}
public boolean isKeyPressed(int key) {
return (isKeyDown(key) && !keys[key]);
}
public void update() {
for(int i = 32; i < GLFW_KEY_LAST; i++)
keys[i] = isKeyDown(i);
}
This is the render method from the World class:
public void render(TileRenderer renderer, Shader shader, Camera camera, Window window) {
int posX = ((int) camera.getPosition().x + (window.getWidth() / 2)) / (scale * 2);
int posY = ((int) camera.getPosition().y - (window.getHeight() / 2)) / (scale * 2);
for (int i = 0; i < view; i++) {
for (int j = 0; j < view; j++) {
Tile t = getTile(i - posX, j + posY);
if (t != null)
renderer.renderTile(t, i - posX, -j - posY, shader, world, camera);
}
}
}
This is the renderTile() method from TileRenderer:
public void renderTile(Tile tile, int x, int y, Shader shader, Matrix4f world, Camera camera) {
shader.bind();
if (tileTextures.containsKey(tile.getTexture()))
tileTextures.get(tile.getTexture()).bind(0);
Matrix4f tilePosition = new Matrix4f().translate(new Vector3f(x * 2, y * 2, 0));
Matrix4f target = new Matrix4f();
camera.getProjection().mul(world, target);
target.mul(tilePosition);
shader.setUniform("projection", target);
model.render();
}
This is the constructor and render method from Model class:
public Model(float[] vertices, float[] texture_coords, int[] indices) {
draw_count = indices.length;
v_id = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, v_id);
glBufferData(GL_ARRAY_BUFFER, createBuffer(vertices), GL_STATIC_DRAW);
t_id = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, t_id);
glBufferData(GL_ARRAY_BUFFER, createBuffer(texture_coords), GL_STATIC_DRAW);
i_id = glGenBuffers();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_id);
IntBuffer buffer = BufferUtils.createIntBuffer(indices.length);
buffer.put(indices);
buffer.flip();
glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
public void render() {
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, v_id);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, t_id);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_id);
glDrawElements(GL_TRIANGLES, draw_count, GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
}
I store the vertices, texture coords and indices in the tile renderer:
float[] vertices = new float[]{
-1f, 1f, 0, //top left 0
1f, 1f, 0, //top right 1
1f, -1f, 0, //bottom right 2
-1f, -1f, 0, //bottom left 3
};
float[] texture = new float[]{
0, 0,
1, 0,
1, 1,
0, 1,
};
int[] indices = new int[]{
0, 1, 2,
2, 3, 0
};
Java nested for-loops aren't very fast . :o The goal here is to revert back to a function that performs "tail-recursion" :
public void render_recursed(TileRenderer renderer, Shader shader, Camera camera, Window window, int posX, int posY, int i, int j, int view) {
// stop clause
if(i >= view || j >= view) return false;
// recursed contract
Tile t = getTile(i - posX, j + posY);
if (t != null)
renderer.renderTile(t, i - posX, -j - posY, shader, world, camera);
// next tile in row
if(!render_recursed(renderer,shader, camera, window, posX, posY, i, j+1, view))
if(!render_recursed(renderer,shader, camera, window, posX, posY, i+1, j, view)
return false;
// successful rendered (i*j) tiles
return true;
}
Try Recursed :
public void render(TileRenderer renderer, Shader shader, Camera camera, Window window) {
int posX = ((int) camera.getPosition().x + (window.getWidth() / 2)) / (scale * 2);
int posY = ((int) camera.getPosition().y - (window.getHeight() / 2)) / (scale * 2);
render_recursed(renderer,shader, camera, window, posX, posY, 0, 0, view);
}
Why ? ;D
Sometimes (https://stackoverflow.com/a/6463304) recursion is faster because Java and OpenGL bound with the native API, which perform inline functions and optimize memory usage with recursed function.