title: Best way to boost the performance of my LWJGL game?
body: <p>I am putting together a small project for school that involves rendering the periodic table.  I chose to use LWJGL to do this.  The problem is, however, that when I render the table, the game starts out at ~30fps(capped at 60fps), and quickly fluctuates to a single-digit fps.  I believe that the problem could be a memory leak, but I am unsure.  Can anybody see any glaring problems with my code?  Here are the main classes involved in rendering the table:</p>
  
  <p>EntityPeriodicTable: In charge of holding a huge array of EntityElement objects(see below), activating their logic(tick() and updateInput()).
      package com.flafla2.periodicTable;</p>
  
  <pre><code>import org.lwjgl.opengl.GL11;
  
  public class EntityPeriodicTable extends ClickableEntity { //ClickableEntity is an abstract class in charge of the tick(), updateInput(), and render() methods, as well as positioning
  
      public EntityElement[] elements = {//This is unfinished, but you get the idea.
          //new EntityElement(Atomic #, State, Metal, "Symbol", "Name", new Vector2D(posx,posy), this)
          new EntityElement(1, 2, 2, "H", "Hydrogen", new Vector2D(1,1), this),
          new EntityElement(2, 2, 2, "He", "Helium", new Vector2D(18,1), this),
  
          new EntityElement(3, 0, 0, "Li", "Lithium", new Vector2D(1,2), this),
          new EntityElement(4, 0, 0, "Be", "Beryllium", new Vector2D(2,2), this),
          new EntityElement(5, 0, 1, "B", "Boron", new Vector2D(13,2), this),
          new EntityElement(6, 0, 2, "C", "Carbon", new Vector2D(14,2), this),
          new EntityElement(7, 2, 2, "N", "Nitrogen", new Vector2D(15,2), this),
          new EntityElement(8, 2, 2, "O", "Oxygen", new Vector2D(16,2), this),
          new EntityElement(9, 2, 2, "F", "Fluorine", new Vector2D(17,2), this),
          new EntityElement(10,2, 2, "Ne", "Neon", new Vector2D(18,2), this),
  
          new EntityElement(11, 0, 0, "Na", "Sodium", new Vector2D(1,3), this),
          new EntityElement(12, 0, 0, "Mg", "Magnesium", new Vector2D(2,3), this),
          new EntityElement(13, 0, 0, "Al", "Aluminum", new Vector2D(13,3), this),
          new EntityElement(14, 0, 1, "Si", "Silicon", new Vector2D(14,3), this),
          new EntityElement(15, 0, 2, "P", "Phosphorous", new Vector2D(15,3), this),
          new EntityElement(16, 0, 2, "S", "Sulfur", new Vector2D(16,3), this),
          new EntityElement(17, 2, 2, "Cl", "Chlorine", new Vector2D(17,3), this),
          new EntityElement(18, 2, 2, "Ar", "Argon", new Vector2D(18,3), this),
  
          new EntityElement(19, 0, 0, "K", "Potassium", new Vector2D(1,4), this),
          new EntityElement(20, 0, 0, "Ca", "Calcium", new Vector2D(2,4), this),
          new EntityElement(21, 0, 0, "Sc", "Scandium", new Vector2D(3,4), this),
          new EntityElement(22, 0, 0, "Ti", "Hydrogen", new Vector2D(4,4), this),
          new EntityElement(23, 0, 0, "V", "Hydrogen", new Vector2D(5,4), this),
          new EntityElement(24, 0, 0, "Cr", "Hydrogen", new Vector2D(6,4), this),
          new EntityElement(25, 0, 0, "Mn", "Hydrogen", new Vector2D(7,4), this),
          new EntityElement(26, 0, 0, "Fe", "Hydrogen", new Vector2D(8,4), this),
          new EntityElement(27, 0, 0, "Co", "Hydrogen", new Vector2D(9,4), this),
          new EntityElement(28, 0, 0, "Ni", "Hydrogen", new Vector2D(10,4), this),
          new EntityElement(29, 0, 0, "Cu", "Hydrogen", new Vector2D(11,4), this),
          new EntityElement(30, 0, 0, "Zn", "Hydrogen", new Vector2D(12,4), this),
          new EntityElement(31, 0, 0, "Ga", "Hydrogen", new Vector2D(13,4), this),
          new EntityElement(32, 0, 1, "Ge", "Hydrogen", new Vector2D(14,4), this),
          new EntityElement(33, 0, 1, "As", "Hydrogen", new Vector2D(15,4), this),
          new EntityElement(34, 0, 2, "Se", "Hydrogen", new Vector2D(16,4), this),
          new EntityElement(35, 1, 2, "Br", "Hydrogen", new Vector2D(17,4), this),
          new EntityElement(36, 2, 2, "Kr", "Hydrogen", new Vector2D(18,4), this),
      };
  
      public final int ELEMENT_SIZE = 40;
      public Vector2D mousePos = new Vector2D(0,0); //Simple 2D vector struct.
  
      public double[] SOLID_RGB = {0,0,0};
      public double[] LIQUID_RGB = {0,0,1};
      public double[] GAS_RGB = {1,0,0};
  
      public double[] METAL_RGB;
      public double[] NONMETAL_RGB;
      public double[] METALLOID_RGB;
      public double[] RECENT_RGB;
  
      public EntityPeriodicTable(Vector2D pos) {
          this.pos = pos;
          METAL_RGB = new double[3];
          METAL_RGB[0] = 0.596078431; //152/255
          METAL_RGB[1] = 0.984313725; //251/255
          METAL_RGB[2] = 0.596078431; //152/255
  
          NONMETAL_RGB = new double[3];
          NONMETAL_RGB[0] = 1;
          NONMETAL_RGB[1] = 0.647058824; //165/255
          NONMETAL_RGB[2] = 0;
  
          METALLOID_RGB = new double[3];
          METALLOID_RGB[0] = 0.866666667; //221/255
          METALLOID_RGB[1] = 0.62745098; //160/255
          METALLOID_RGB[2] = 0.866666667; //221/255
  
          RECENT_RGB = new double[3];
          RECENT_RGB[0] = 0.803921569; //205/255
          RECENT_RGB[1] = 0.788235294; //201/255
          RECENT_RGB[2] = 0.788235294; //201/255
      }
  
      @Override
      void render() {
          GL11.glDisable(GL11.GL_TEXTURE_2D);
          GL11.glDisable(GL11.GL_BLEND);
          for(int x=0;x&lt;elements.length;x++)
              elements[x].render();
          GL11.glEnable(GL11.GL_TEXTURE_2D);
          GL11.glEnable(GL11.GL_BLEND);
          for(int x=0;x&lt;elements.length;x++)
              elements[x].renderWithTex();
      }
  
      @Override
      void tick() {
          for(int x=0;x&lt;elements.length;x++)
              elements[x].tick();
      }
  
      @Override
      public void updateInput(Vector2D mousePos)
      {
          this.mousePos = mousePos;
          for(int x=0;x&lt;elements.length;x++)
          {
              if(mousePos.isInBoundsWithDim(elements[x].pos.x, elements[x].pos.y, elements[x].dim.x, elements[x].dim.y))
                  elements[x].isSelected = true;
              else
                  elements[x].isSelected = false;
          }
      }
  
      @Override
      void onEntityClicked() {
          for(int x=0;x&lt;elements.length;x++)
          {
              if(mousePos.isInBoundsWithDim(elements[x].pos.x, elements[x].pos.y, elements[x].dim.x, elements[x].dim.y))
                  elements[x].onEntityClicked();
          }
      }
  
  }
  </code></pre>
  
  <p>EntityElement: Holds data of a specific element on the table, and renders it(render code is unfinished)</p>
  
  <pre><code>package com.flafla2.periodicTable;
  
  import org.lwjgl.opengl.GL11;
  
  public class EntityElement extends ClickableEntity {
  
      String symbol;
      String element;
      int atomicNumber;
      EntityPeriodicTable table;
      int state;//0=solid, 1=liquid, 2=gas
      int metalState;//0=metal, 1=metalloid, 2=nonmetal, 3=discovered recently
      Vector2D gridPos;
  
      public EntityElement(int an, int st, int ms, String sy, String en, Vector2D gp, EntityPeriodicTable pt)
      {
          symbol = sy;
          element = en;
          atomicNumber = an;
          table = pt;
          state = st;
          metalState = ms;
          gridPos = gp;
  
          dim.x = table.ELEMENT_SIZE; dim.y = table.ELEMENT_SIZE;
          pos.x = table.pos.x + table.ELEMENT_SIZE*(gridPos.x-1); pos.y = table.pos.y + table.ELEMENT_SIZE*(gridPos.y-1);
      }
  
      public double[] getStateColor()
      {
          switch(state)
          {
          case 0:
              return table.SOLID_RGB;
          case 1:
              return table.LIQUID_RGB;
          case 2:
              return table.GAS_RGB;
          default:
              double[] d = {0.0d,0.0d,0.0d};
              return d;
          }
      }
  
      public double[] getMetalColor()
      {
          switch(metalState)
          {
          case 0:
              return table.METAL_RGB;
          case 1:
              return table.METALLOID_RGB;
          case 2:
              return table.NONMETAL_RGB;
          case 3:
              return table.RECENT_RGB;
          default:
              double[] d = {0.0d,0.0d,0.0d};
              return d;
          }
      }
  
      @Override
      void render() {
          GL11.glPushMatrix();
              GL11.glTranslatef(pos.x, pos.y, 0);
              double[] d = getMetalColor();
              GL11.glColor3d(d[0], d[1], d[2]);
              GL11.glBegin(GL11.GL_QUADS);
              {
                  GL11.glVertex2f(0, 0);//topleft
                  GL11.glVertex2f(dim.x, 0);//topright
                  GL11.glVertex2f(dim.x, dim.y);//bottomright
                  GL11.glVertex2f(0, dim.y);//bottomleft
              }
              GL11.glEnd();
              GL11.glColor3d(1.0d, 1.0d, 1.0d);
          GL11.glPopMatrix();
      }
  
      public void renderWithTex()
      {
          Font.drawString(symbol, new Vector2D(pos.x+dim.x/2-Font.getStringWidth(symbol,2)/2,pos.y+dim.y/2-Font.FONT_HEIGHT), 2);
      }
  
      @Override
      void tick() {
          if(isSelected)
          {
              dim.x = table.ELEMENT_SIZE+6; dim.y = table.ELEMENT_SIZE+6;
              pos.x = table.pos.x + table.ELEMENT_SIZE*(gridPos.x-1)-3; pos.y = table.pos.y + table.ELEMENT_SIZE*(gridPos.y-1)-3;
          } else
          {
              dim.x = table.ELEMENT_SIZE; dim.y = table.ELEMENT_SIZE;
              pos.x = table.pos.x + table.ELEMENT_SIZE*(gridPos.x-1); pos.y = table.pos.y + table.ELEMENT_SIZE*(gridPos.y-1);
          }
      }
  
      @Override
      void onEntityClicked() {
  
      }
  
  }
  </code></pre>
  
  <p>Font: Handles rendering text onscreen:</p>
  
  <pre><code>package com.flafla2.periodicTable;
  
  import java.awt.image.BufferedImage;
  
  import javax.imageio.ImageIO;
  
  import org.lwjgl.opengl.GL11;
  
  public class Font {
      public static final String fontText = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789:;?!\"&amp;',-.[]#()+ ";
      public static final BufferedImage fontSheet = TextureLoader.loadTexture("/res/text.png");
  
      public static final int FONT_WIDTH = 9;
      public static final int FONT_HEIGHT = 8;
  
      public static void drawString(String s, Vector2D pos, float dim)
      {
          drawString(s,pos,new Vector2D((int)Math.floor(dim*FONT_WIDTH),(int)Math.floor(dim*FONT_HEIGHT)));
      }
  
      public static void drawString(String s, Vector2D pos)
      {
          drawString(s,pos,new Vector2D(9,8));
      }
  
      public static void drawString(String s, Vector2D pos, Vector2D dim)
      {
          for(int x=0;x&lt;s.length();x++)
          {
              drawLetter(s.charAt(x),new Vector2D(pos.x+dim.x*x,pos.y),dim);
          }
      }
  
      public static int getStringWidth(String s)
      {
          return s.length()*FONT_WIDTH;
      }
  
      public static int getStringWidth(String s,float f)
      {
          return (int)Math.floor(s.length()*FONT_WIDTH*f);
      }
  
      public static Vector2D getPosOfLetterOnImg(Character c,int gridNumb)
      {
          int xOffset = 0;
          int yOffset = 0;
          if(!c.equals(' '))
          {
              int letterNumb = fontText.indexOf(c);
              xOffset = (letterNumb%26)*FONT_WIDTH;
              if(xOffset != 0)
                  xOffset -=1;
              yOffset = 0;
              int yGridOffset = (letterNumb &lt; 26) ? 0 : ((letterNumb &lt; 52) ? 1 : 2);
  
              switch(gridNumb)
              {
              case 1:
                  yOffset = 34;
                  break;
              case 2:
                  yOffset = 69;
                  break;
              default:
                  yOffset = 0;
              }
  
              for(int x=0;x&lt;yGridOffset;x++)
                  yOffset += FONT_HEIGHT+x+3;
          } else
          {
              xOffset = 235;
              yOffset = 92;
          }
  
          return new Vector2D(xOffset,yOffset);
      }
  
      public static void drawLetter(Character c, Vector2D pos, Vector2D dim)
      {
          if(fontSheet == null)
              return;
  
          Vector2D letterPos = getPosOfLetterOnImg(c,2);
  
          BufferedImage letterImage = fontSheet.getSubimage(letterPos.x, letterPos.y, FONT_WIDTH, FONT_HEIGHT);
          int textureID = TextureLoader.loadGLTexture(letterImage);
          letterImage = null;
  
          GL11.glPushMatrix();
              GL11.glTranslatef(pos.x, pos.y, 0);
              GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID);
              GL11.glBegin(GL11.GL_QUADS);
              {
                  GL11.glTexCoord2f(0, 0);
                  GL11.glVertex2f(0, 0);
  
                  GL11.glTexCoord2f(1, 0);
                  GL11.glVertex2f(dim.x, 0);
  
                  GL11.glTexCoord2f(1, 1);
                  GL11.glVertex2f(dim.x, dim.y);
  
                  GL11.glTexCoord2f(0, 1);
                  GL11.glVertex2f(0, dim.y);
              }
              GL11.glEnd();
          GL11.glPopMatrix();
      }
  }
  </code></pre>
  
  <p>TextureLoader: Loads textures(duh lol)</p>
  
  <pre><code>package com.flafla2.periodicTable;
  
  import java.awt.image.BufferedImage;
  import java.io.IOException;
  import java.nio.ByteBuffer;
  
  import javax.imageio.ImageIO;
  
  import org.lwjgl.BufferUtils;
  import org.lwjgl.opengl.GL11;
  import org.lwjgl.opengl.GL12;
  
  public class TextureLoader {
      public static BufferedImage loadTexture(String texturePath)
      {
          try {
              return ImageIO.read(PeriodicTable.class.getResource(texturePath));
          } catch (IOException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }
          return null;
      }
  
      private static final int BYTES_PER_PIXEL = 4;
      public static int loadGLTexture(BufferedImage image){
           int[] pixels = new int[image.getWidth() * image.getHeight()];
           image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
  
           ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * BYTES_PER_PIXEL); //4 for RGBA, 3 for RGB
  
           for(int y = 0; y &lt; image.getHeight(); y++){
               for(int x = 0; x &lt; image.getWidth(); x++){
                   int pixel = pixels[y * image.getWidth() + x];
                   buffer.put((byte) ((pixel &gt;&gt; 16) &amp; 0xFF));     // Red component
                   buffer.put((byte) ((pixel &gt;&gt; 8) &amp; 0xFF));      // Green component
                   buffer.put((byte) (pixel &amp; 0xFF));               // Blue component
                   buffer.put((byte) ((pixel &gt;&gt; 24) &amp; 0xFF));    // Alpha component. Only for RGBA
               } 
           }
  
           buffer.flip(); //FOR THE LOVE OF GOD DO NOT FORGET THIS
  
           // You now have a ByteBuffer filled with the color data of each pixel.
           // Now just create a texture ID and bind it. Then you can load it using 
           // whatever OpenGL method you want, for example:
  
           int textureID = GL11.glGenTextures(); //Generate texture ID
           GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID); //Bind texture ID
  
           //Setup wrap mode
           GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
           GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
  
           //Setup texture scaling filtering
           GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
           GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
  
           //Send texel data to OpenGL
           GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, image.getWidth(), image.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
           buffer = null;
  
           //Return the texture ID so we can bind it later again
           return textureID;
      }
  }
  </code></pre>
  
  <p>I know, it's a lot of code, but if anyone can help me out it would be greatly appreciated.</p>
  
  <p>Thanks, Flafla2.</p>