【问题标题】:Best way to boost the performance of my LWJGL game?提高我的 LWJGL 游戏性能的最佳方法?
【发布时间】:2012-03-10 18:12:24
【问题描述】:

我正在为学校准备一个涉及渲染元素周期表的小项目。我选择使用 LWJGL 来执行此操作。然而,问题是,当我渲染表格时,游戏以 ~30fps(上限为 60fps)开始,并迅速波动到个位数的 fps。我相信问题可能是内存泄漏,但我不确定。任何人都可以看到我的代码有任何明显的问题吗?以下是渲染表格所涉及的主要类:

EntityPeriodicTable:负责保存大量 EntityElement 对象(见下文),激活它们的逻辑(tick() 和 updateInput())。 包 com.flafla2.periodicTable;

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<elements.length;x++)
            elements[x].render();
        GL11.glEnable(GL11.GL_TEXTURE_2D);
        GL11.glEnable(GL11.GL_BLEND);
        for(int x=0;x<elements.length;x++)
            elements[x].renderWithTex();
    }

    @Override
    void tick() {
        for(int x=0;x<elements.length;x++)
            elements[x].tick();
    }

    @Override
    public void updateInput(Vector2D mousePos)
    {
        this.mousePos = mousePos;
        for(int x=0;x<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<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();
        }
    }

}

EntityElement:保存表格中特定元素的数据,并渲染(渲染代码未完成)

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() {

    }

}

字体:处理在屏幕上呈现文本:

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:;?!\"&',-.[]#()+ ";
    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<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 < 26) ? 0 : ((letterNumb < 52) ? 1 : 2);

            switch(gridNumb)
            {
            case 1:
                yOffset = 34;
                break;
            case 2:
                yOffset = 69;
                break;
            default:
                yOffset = 0;
            }

            for(int x=0;x<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();
    }
}

TextureLoader:加载纹理(duh lol)

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 < 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(); //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;
    }
}

我知道,代码很多,但如果有人能帮助我,将不胜感激。

谢谢,Flafla2。

【问题讨论】:

  • 你说内存泄漏,你试过看使用情况吗?你newing 了很多向量,这很正常而且很好,但那是我首先要看的地方。如果可以,请尝试分析 CPU 和内存使用情况,并查看热点在哪里。但请注意,在 CPU 上 分析您的渲染代码将提供不正确的数据,并且应该大部分被忽略(驱动程序/卡缓冲区如此之多,以至于 CPU 配置文件不会匹配)。
  • 谢谢,我现在尝试分析(我之前在分析器上遇到了问题,我会看看能否解决问题)。
  • 好的,我找到了问题。在TextureLoader.java 中,我没有使用glDeleteTextures(textureID),因此Font.java 中使用的纹理不会从内存中卸载。现在,我得到了稳定的 50+ fps(当然是在我糟糕的 macbook 上)。不过,感谢您的帮助!
  • 太棒了。如果您愿意,我可以将我的评论复制到答案中(或者您可以自己回答),以便您接受并关闭问题。
  • 抱歉,我没能回答,因为我的低声誉迫使我等了 8 个小时才能回答我自己的问题。不过,时间到了,我会这样做的!

标签: java render lwjgl frame-rate lag


【解决方案1】:

以为你已经解决了,还有更多的改进空间。我看到您在图像中有您的字体,并且对于您要绘制的每个字符,您都会将带有该字母的图像部分加载到纹理中,然后需要清理所有内容。

最好将整个图像加载到一个大纹理中,在程序执行期间保留该纹理,并在渲染每一帧时重复使用它。您可以通过指定正确的纹理坐标来选择要渲染的正确字符。

除非你的 MacBook 真的很旧,否则你应该能够在 CPU 使用率低的情况下达到 60fps 的上限。

【讨论】:

  • 谢谢,这让我的 FPS 从 ~30 提高到 ~60!
【解决方案2】:

好的,我找到了问题。

TextureLoader.java 中,我没有使用glDeleteTextures(textureID),因此Font.java 中使用的纹理不会从内存中卸载。现在,我获得了稳定的 50+ fps(当然是在我糟糕的 macbook 上)。

另外,另一个检查的答案将我的 fps 提高到 ~60。如果有人想知道,这里是新的 drawLetter() 方法,有一些变化:

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;

        int width = fontSheet.getWidth(); int height = fontSheet.getHeight();
        double d[] = {(double)letterPos.x/width, (double)letterPos.y/height, (double)(letterPos.x+FONT_WIDTH)/width, (double)(letterPos.y+FONT_HEIGHT)/height};
        GL11.glPushMatrix();
            GL11.glTranslatef(pos.x, pos.y, 0);
            GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID);
            GL11.glBegin(GL11.GL_QUADS);
            {
                GL11.glTexCoord2d(d[0], d[1]);
                GL11.glVertex2f(0, 0);

                GL11.glTexCoord2d(d[2], d[1]);
                GL11.glVertex2f(dim.x, 0);

                GL11.glTexCoord2d(d[2], d[3]);
                GL11.glVertex2f(dim.x, dim.y);

                GL11.glTexCoord2d(d[0], d[3]);
                GL11.glVertex2f(0, dim.y);
            }
            GL11.glEnd();
        GL11.glPopMatrix();

        //GL11.glDeleteTextures(textureID);
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-14
    • 1970-01-01
    • 2010-10-11
    • 1970-01-01
    • 1970-01-01
    • 2019-06-14
    相关资源
    最近更新 更多