Help with rendering 2D tiles

Started by TeodorVecerdi, June 27, 2017, 21:43:05

Previous topic - Next topic

TeodorVecerdi

I started watching these 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.

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
        };


broumbroum

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 recursion is faster because Java and OpenGL bound with the native API, which perform inline functions and optimize memory usage with recursed function.