LWJGL: управление буферной памятью

#java #opengl #native #lwjgl

#java #opengl #родной #lwjgl

Вопрос:

Я ищу некоторые рекомендации по памяти / производительности, какой подход лучше.

Если допустим, у меня есть 4 атрибута для сетки

 Vertex    3f
Normal    3f
TexCoords 2f
jointID   4i [Integer Joint Indices For Skeleton Animation]
 

И они мне нужны в памяти процессора, поскольку их можно изменить в любое время

Лучше ли

a. Создайте 4 отдельных буфера для каждого компонента

 //3,2,4 are the strides i.e vertex is 3 floats,texCoord is 2 floats so on
FloatBuffer vertices=BufferUtils.createFloatBuffer(numOfVertices*3);
FloatBuffer normals=BufferUtils.createFloatBuffer(numOfVertices*3);
FloatBuffer texCoords=BufferUtils.createFloatBuffer(numOfVertices*2);
IntBuffer   vertexJoints=BufferUtils.createIntBuffer(numOfVertices*4);
 

Или

б. Создайте большой байтбуфер с достаточной емкостью для хранения всех 4 атрибутов и создайте отдельные представления буфера Float / Int для каждого из атрибутов

  ByteBuffer  meshData=BufferUtils.createByteBuffer(((numOfVertices*3) (numOfVertices*3) (numOfVertices*2) (numOfVertices*4))*4); //*4 because both float/int is 4 bytes
 FloatBuffer vertices=meshData.position(0).limit(endVertexByte).asFloatBuffer();
 FloatBuffer normals=meshData.position(endVertexByte).limit(endNormalByte).asFloatBuffer();
 FloatBuffer texCoords=meshData.position(endNormalByte).limit(endTexCoordByte).asFloatBuffer();
 IntBuffer   jointIDs=meshData.position(endTexCoordByte).limit(endJointIndexByte or end of buffer in this case).asIntBuffer();
 

Из документов все методы BufferUtils создают DirectBuffer, который хранится в собственной памяти, и все, хотя 2-й подход создает буфер большего размера [поскольку мы умножаем на 4], чем все отдельные буферы атрибутов вместе взятые, он создает только один большой фрагмент встроенной памяти по сравнению с 4 отдельными областями памяти в первомподход.

Но это только мое мнение, мысли?

Ответ №1:

Разницы в производительности не будет, если мы просто посмотрим, как вы записываете (новые) данные в эти буферы с точки зрения процессора. В любом случае у вас есть только четыре последовательных области памяти, к которым вы подключаетесь при обновлении данных атрибутов вершин. Просто в первом случае эти области памяти смещены на неизвестное количество байтов (поскольку распределитель памяти JVM будет выделять каждую область отдельно), в то время как в последнем случае вы знаете смещение между каждыми двумя последовательными областями памяти, потому что вы выделили их в одном распределении буферной памяти JVM.

Однако разница заключается в том, как вы на самом деле сопоставляете эти области хост-памяти на стороне клиента с памятью буферного объекта OpenGL на стороне сервера. Я полагаю, что после обновления памяти на стороне хоста вы фактически загрузите ее в объекты буфера OpenGL на стороне сервера и не будете использовать указатели памяти на стороне клиента / хоста для команд спецификации вершин OpenGL (которые доступны только в контексте совместимости с OpenGL).

В этом случае для создания четырех отдельных смежных областей памяти на стороне клиента потребуется выполнить четыре команды загрузки буферной памяти OpenGL ( glBufferSubData() ) и драйвер OpenGL для выполнения четырех отдельных загрузок с прямым доступом к памяти (DMA) через PCIe. В случае, когда у вас есть только одна непрерывная область памяти на стороне клиента, вы можете выполнить только один glBufferSubData() вызов для всех данных атрибутов вершины в один объект buffer, где вы просто используете смещения байтов в вызовах спецификации вершин OpenGL (например, for glVertexAttribPointer() ).

Другая возможность также заключается в том, чтобы не выделять память хоста на стороне клиента самостоятельно, но иметь видимые для хоста, постоянно отображаемые области буфера, предоставленные вам OpenGL ( glBufferStorage() glMapBufferRange() ), которые затем вы можете записать и явно очистить или позволить им неявно / последовательно обновлять драйвером OpenGL. Как и в случае с четырьмя отдельными областями памяти на стороне клиента, вы также, вероятно, будете платить за «четыре отдельных передачи DMA», когда вы сопоставляете и очищаете четыре отдельных области объектов буфера OpenGL.

Итак, в конце концов, не так важно, есть ли у вас одно или четыре представления буфера NIO в вашей памяти на стороне клиента, но скольким объектам буфера OpenGL на стороне сервера вы сопоставляете эти области памяти — чем меньше, тем лучше.

Комментарии:

1. Итак, 2-й подход лучше, если у вас есть только один glBuffer в случае interleavedBuffer[где мы указываем шаги и смещения в vertexAttribPointer], но не приведет к различию, если у вас есть 4 отдельных объекта glBuffer, если вы хотите сохранить отдельные атрибуты, правильно? glMapBuffer невозможно эффективно использовать в многопоточной настройке, где для выполнения вызовов требуется переключение контекста, и единственный вариант — сначала загрузить данные вершин в cpu, а затем создать glBuffers в основном потоке. Но с точки зрения памяти jni я делаю некоторую экономию?

2. говоря об упаковке всего, является ли чередующийся glBuffer лучше, чем 4 отдельных glBuffers, когда дело доходит до скорости чтения gpu, или это отдельный вопрос для другого потока?

3. Я не совсем уверен, что мне нужно некоторое время для экспериментов, и если мои выводы совпадут с вашими, я отмечу это тогда