LWJGL Forum

Programming => Lightweight Java Gaming Library => Topic started by: TeodorVecerdi on June 27, 2017, 21:43:05

Title: Help with rendering 2D tiles
Post by: TeodorVecerdi on June 27, 2017, 21:43:05
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
       };

Title: Re: Help with rendering 2D tiles
Post by: broumbroum on July 28, 2017, 20:03:55
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.