为啥创建 VBO 时静态方法会占用内存?

Posted

技术标签:

【中文标题】为啥创建 VBO 时静态方法会占用内存?【英文标题】:Why do static methods run up memory when creating VBO's?为什么创建 VBO 时静态方法会占用内存? 【发布时间】:2016-03-22 15:49:59 【问题描述】:

我有一个程序可以在屏幕上绘制六个字段,每个字段包含大约 2000 个顶点。要为这些创建 VBO,我需要使用 Buffer 对象(Int/Float)。我需要能够将顶点位置等保存到 xml 文件中,所以我想序列化模型对象,转换为 B64 并将它们保存为字符串。问题是 Buffer 对象不可序列化。

因此,为了解决这个问题,我从模型对象中删除了所有 VBO 代码(它最初是从一个单独的类扩展而来的)并创建了静态方法来创建我的 VBO。因此,我调用静态方法为模型创建 VBO,然后返回句柄,以便我可以调用渲染、更新顶点等。然而,这会在创建模型时产生严重增加内存的效果。

为什么会这样?最初内存使用情况甚至不明显。现在它使 JVM 崩溃。我没有更改任何代码逻辑,方法是相同的,只是现在它们是静态的并传回句柄。创建 VBO 时,静态方法会以某种方式使用更多内存吗?我以为会少一点?使用后我会清除所有缓冲区。我确实处理掉了所有剔除的模型。

编辑: 这是包含静态方法的 Render 类

package Drawing;

import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_DYNAMIC_DRAW;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glBufferSubData;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;

/**
* This class calls all vbo functions in a static way which allows me to 
 * separate the Int/FloatBuffers from the model classes like Cube and
 * Quad.  Int/FloatBuffers will not serialize so the Cube/Quad classes
 * cannot be saved to file.  Keeping them static and separated 
 * will overcome this problem.
 */
public class Render 

static int VERTEXCOUNT = 0;//((QUAD_SIZE * QUAD_SIZE) * 12);
static FloatBuffer fbData = null;//BufferUtils.createFloatBuffer(VERTEXCOUNT);
static FloatBuffer fbNorm = null;//BufferUtils.createFloatBuffer(VERTEXCOUNT);
static FloatBuffer fbtex = null;//BufferUtils.createFloatBuffer((VERTEXCOUNT / 12) * 8);
static IntBuffer Indices = null;
private static int _VAOHandle = 0;
private static IntBuffer vboHandles;

static final int POSITION_INDEX = 0; // index of vertex attribute "in_Position"
static final int NORMALS_IDX = 1;
static final int TEXTURE_IDX = 2;
static final int IBO_IDX = 3;

public static VBOIndexes createVBO(int QUAD_SIZE,
        float[] vertBuffer,
        float[] normals,
        float[] UVs,
        int[] idxBuffer)  throws Exception 

    VBOIndexes vboINDEXES = new VBOIndexes();

    try

        VERTEXCOUNT = (int) ((QUAD_SIZE * QUAD_SIZE) * 12);
        fbData = BufferUtils.createFloatBuffer(VERTEXCOUNT);
        fbNorm = BufferUtils.createFloatBuffer(VERTEXCOUNT);
        fbtex = BufferUtils.createFloatBuffer((VERTEXCOUNT / 12) * 8);
        Indices = BufferUtils.createIntBuffer(VERTEXCOUNT / 2);


        _VAOHandle = glGenVertexArrays();

        vboINDEXES.VAOHandle = _VAOHandle;

        System.out.println("VAOHandle is : " + _VAOHandle);

        glBindVertexArray(_VAOHandle);

        vboHandles =  BufferUtils.createIntBuffer(4);
        glGenBuffers(vboHandles);

        vboINDEXES.idxPOS = vboHandles.get(POSITION_INDEX);
        vboINDEXES.idxNORM = vboHandles.get(NORMALS_IDX);
        vboINDEXES.idxTEX = vboHandles.get(TEXTURE_IDX);
        vboINDEXES.idxIBO = vboHandles.get(IBO_IDX);

        //FloatBuffer fbData = BufferUtils.createFloatBuffer(vertBuffer.length);
        fbData.put(vertBuffer);
        fbData.rewind(); // rewind, otherwise LWJGL thinks our buffer is empty

        glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get(POSITION_INDEX));

        glBufferData(GL_ARRAY_BUFFER, fbData, GL_DYNAMIC_DRAW);
        fbData.clear(); //don't need this anymore  


        //populate the normals buffer
        //FloatBuffer fbNorm = BufferUtils.createFloatBuffer(normalsBuffer.length );
        fbNorm.put(normals);
        fbNorm.rewind();
        glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get(NORMALS_IDX)); //the vertex data
        glBufferData(GL_ARRAY_BUFFER, fbNorm, GL_STATIC_DRAW);
        fbNorm.clear(); //don't need this anymore 

        //populate the texture buffer

        fbtex.put(UVs);  
        fbtex.rewind();
        glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get(TEXTURE_IDX)); 
        glBufferData(GL_ARRAY_BUFFER, fbtex, GL_DYNAMIC_DRAW);
        fbtex.clear(); //don't need this anymore  

        Indices.put(idxBuffer);
        Indices.rewind();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboHandles.get(IBO_IDX));

        glBufferData(GL_ELEMENT_ARRAY_BUFFER, Indices, GL_STATIC_DRAW);

        glEnableVertexAttribArray(POSITION_INDEX);
        glEnableVertexAttribArray(NORMALS_IDX);
        glEnableVertexAttribArray(TEXTURE_IDX);

        glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get(POSITION_INDEX)); 
        glVertexAttribPointer(0,3, GL_FLOAT, false,0,0);    

        //normals
        glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get(NORMALS_IDX));
        glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, 0);  

        glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get(TEXTURE_IDX));
        glVertexAttribPointer(2, 2, GL_FLOAT, false, 0, 0); 

        //bind IBO
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboHandles.get(IBO_IDX));

        glBindVertexArray(0);

        glBindBuffer(GL_ARRAY_BUFFER, 0);


        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

        Indices.clear();

    catch (Exception ex)
        System.out.println("createVBO: " + ex.getMessage());
        throw ex;
    
    return vboINDEXES;


public static VBOIndexes createLineVBO( float[] vertBuffer,
                            int[] idxBuffer)  throws Exception 

    VBOIndexes vboINDEXES = new VBOIndexes();

    try

        fbData = BufferUtils.createFloatBuffer(vertBuffer.length);
        Indices = BufferUtils.createIntBuffer(vertBuffer.length / 2);

        _VAOHandle = glGenVertexArrays();

        vboINDEXES.VAOHandle = _VAOHandle;

        glBindVertexArray(_VAOHandle);

        vboHandles =  BufferUtils.createIntBuffer(4);
        glGenBuffers(vboHandles);

        vboINDEXES.idxPOS = vboHandles.get(POSITION_INDEX);
        vboINDEXES.idxIBO = vboHandles.get(IBO_IDX);

        fbData.put(vertBuffer);
        fbData.rewind(); // rewind, otherwise LWJGL thinks our buffer is empty

        glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get(POSITION_INDEX));

        glBufferData(GL_ARRAY_BUFFER, fbData, GL_STATIC_DRAW);
        fbData.clear(); //don't need this anymore  

        //IntBuffer Indices = BufferUtils.createIntBuffer(idxBuffer.length);
        Indices.put(idxBuffer);
        Indices.rewind();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboHandles.get(IBO_IDX));
        //Util.checkGLError();
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, Indices, GL_STATIC_DRAW);

        glEnableVertexAttribArray(POSITION_INDEX);
        glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get(POSITION_INDEX)); 
        glVertexAttribPointer(0,3, GL_FLOAT, false,0,0);    

        //bind IBO
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboHandles.get(IBO_IDX));

        Indices.clear();

        glBindVertexArray(0);

        glBindBuffer(GL_ARRAY_BUFFER, 0);
        //Util.checkGLError();

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    catch (Exception ex)
        System.out.println("createVBO: " + ex.getMessage());
        throw ex;
    
    return vboINDEXES;


  public static void updateVertices(int offset,
                            float[] vertBuffer,
                            int idxPOS)
  
      //populate the vertex buffer
      FloatBuffer fbData = BufferUtils.createFloatBuffer(vertBuffer.length);
      fbData.put(vertBuffer);   
      fbData.rewind();
      //glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get(POSITION_INDEX)); //the vertex data
      glBindBuffer(GL_ARRAY_BUFFER, idxPOS);
      glBufferSubData(GL_ARRAY_BUFFER, offset, fbData);
      fbData.clear(); //don't need this anymore  
  

  public static void updateNormals(int offset,
                              float[] normals,
                              int idxNORM)
  

      //populate the vertex buffer
      FloatBuffer fbData = BufferUtils.createFloatBuffer(normals.length);
      fbData.put(normals);
      fbData.rewind();
      //glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get( NORMALS_IDX)); //the vertex data
      glBindBuffer(GL_ARRAY_BUFFER, idxNORM);
      glBufferSubData(GL_ARRAY_BUFFER, 0, fbData);
      fbData.clear(); //don't need this anymore  
  

  public static void updateTexture(int offset,
          float[] UVs,
          int idxTEX)
  

      //populate the texture buffer
      FloatBuffer fbtex = BufferUtils.createFloatBuffer(UVs.length);
      fbtex.put( UVs );
      fbtex.rewind();

      //glBindBuffer(GL_ARRAY_BUFFER, vboHandles.get(TEXTURE_IDX)); //the texture data
      glBindBuffer(GL_ARRAY_BUFFER, idxTEX);
      glBufferSubData(GL_ARRAY_BUFFER, offset, fbtex);
      fbtex.clear(); //don't need this anymore  
  

  public Render()

  


在字段(瓦片对象数组)的构造函数中进行静态调用,如下所示:

public Quad(Map<Integer, Cube> c, int SIZE) throws Exception
//public Quad(ArrayList<Cube> c, int SIZE) throws Exception
    QUAD_SIZE = SIZE;
    initArrays();
    initVBOData(SIZE);
    createVBO();
    cubes = c;


【问题讨论】:

你真的需要纯 xml 格式的职位吗? xml 中的元数据和二进制中的位置是否适合您? 那部分并不是真正的问题。我实际上并没有保存所有的顶点位置,只是模型的高度和其他属性。问题是通过调用这些静态方法,内存只会被杀死。曾经只有 100k 的东西现在超过 1G 了吗?而且逻辑没有变。 几千个顶点真的是一个小数目,仔细检查你的代码,因为我猜你做错了什么。如果您可以发布一些代码.. 我看不出从VERTEXCOUNTvboHandles 的字段是静态成员甚至是类成员的任何理由。它们都应该是局部变量。 如果您阅读我的原始问题,您会发现我的问题不在于 VBO。我的问题是试图找到一种能够序列化和反序列化对象的方法。缓冲区对象不可序列化。我试图解决这个问题的尝试是创建一种创建 VBO 的静态方式。失败了。 【参考方案1】:

我相信答案是静态类不提供 GC。随着所有数据在创建 VBO 时传递,G 真的开始堆积起来......

【讨论】:

完全正确。据我所知,静态类是程序期间的分配。他们不会移动,也不会收集垃圾,他们只是坐在那里等待调用。 完全不正确。对象的生命周期与它的类是否为static 完全无关。 @sova @EJP,好吧,我还是个新手,没关系,但是静态最终对象呢? @sova 他们呢?如果您有问题,请提出。作为一个问题。和这里手头的事情没有任何关系。 @sova - 我不相信你是不正确的。通常,静态类可以预期会保留在内存中,直到它的加载器停止。【参考方案2】:

您的代码看起来很混乱,您应该花一些时间来更好地了解 OpenGL 的工作原理并使其更清晰、更易读。 我建议你从this sample 中汲取灵感。

几个注意事项:

你根本不需要静态 我不知道 lwjgl,但我猜它需要直接缓冲区,你不能保证它们会被垃圾收集器删除,所以你应该自己分配它们,当你不需要它们时释放它们了。我创建了一个小类来释放直接缓冲区,取自 JM3 here 通过将 opengl 资源名称保存在 vboINDEXES 中来避免冗余。您已经在直接使用vboHandles 如果你的缓冲区的尺寸没有改变,分配一次并保留它们(fbDatafbNormfbTex),也不需要clear()它们 因此,当您updateNormals() 不实例化新缓冲区时,请使用一开始就已实例化的缓冲区 从逻辑上看,IBO_INDEX 不应该是vboINDEXES 的一部分 当您调用glVertexAttribPointer 时,将保存属性索引的变量作为第一个参数传递,例如POSITION_INDEX

ps:index 的复数形式是indices

【讨论】:

以上是关于为啥创建 VBO 时静态方法会占用内存?的主要内容,如果未能解决你的问题,请参考以下文章

Java复习第三天-静态方法

java中的静态方法

java中的静态方法有啥作用?

在java中,为啥类实例也能访问静态域?

为啥不需要释放静态数组?

java static学傻了,;类方法中给成员变量赋值必须先new个对象,普通方法却不用,为啥呢?