Использование Android, OpenGL ES 3.0
Попытка записи нескольких слоев GL_TEXTURE_2D_ARRAY с использованием объектов кадрового буфера для последовательного создания карт высот, а затем последовательное чтение из всех слоев GL_TEXTURE_2D_ARRAY в более поздней зацикленной операции.
Если я создаю слой текстуры карты высот, а затем сразу же читаю из текстуры, используя объект пиксельного буфера, чтение происходит успешно. IE, этот шаблон работает.
for( int i = 0; i < initialQuads; i++ ){
calcHeightmap( i );
readHMTexture( i );
}
Если я создам все слои текстуры карты высот, а затем прочитаю из слоев текстуры в цикле с задержкой, то чтение завершится ошибкой (все операции чтения возвращают 0 значений) и рендеринг OpenGl зависнет. IE, этот шаблон не работает.
for( int i = 0; i < initialQuads; i++ ){
calcHeightmap( i );
}
for( int i = 0; i < initialQuads; i++ ){
readHMTexture( i );
}
Я хочу сделать это, потому что я надеюсь добавить промежуточный шаг между начальным этапом генерации и этапом чтения, который будет использовать все слои одновременно и изменять слои текстур, подобно этому шейдеру, который я написал на Shadertoy https: //www.shadertoy.com/view/mssXRB (просто сложнее)
for( int i = 0; i < initialQuads; i++ ){
calcHeightmap( i );
}
for( int i = 0; i < numErosionIterations; i++ ){
erodeHeightmap();
}
for( int i = 0; i < initialQuads; i++ ){
readHMTexture( i );
}
Код для создания карт высот выглядит следующим образом. Он достоверно работает и создает карты высот, такие как здесь: https://i.imgur.com/wQw3ZRP.png
public void calcHeightmap( int hm ){
GLES30.glUseProgram( shader_hm.getProgram() );
GLES30.glViewport( 0, 0, texWH, texWH );
// Get the previously defined framebuffer and attach our textures as the targets
// Using multi-target rendering
int[] buffers = new int[]{ GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_COLOR_ATTACHMENT1 };
GLES30.glBindFramebuffer( GLES30.GL_FRAMEBUFFER, fbos[ hm ] );
GLES30.glFramebufferTextureLayer( GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, hmTexHandles[0], 0, hm );
GLES30.glFramebufferTextureLayer( GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT1, hmTexHandles[1], 0, hm );
GLES30.glDrawBuffers( 2, IntBuffer.wrap( buffers ) );
// Perform all the pre-render steps for the attributes and uniforms (enabling, assigning values, making active, binding)
for( String attr : shader_hm.attributes.keySet() ){
RefMethodwArgs preRenderStep = ( RefMethodwArgs ) shader_hm.attributes.get( attr )[ 2 ];
if ( preRenderStep != null ){
preRenderStep.invoke( shader_hm_PreRenderArgs.get( attr ) );
}
}
for( String uni : shader_hm.uniforms.keySet() ){
RefMethodwArgs preRenderStep = ( RefMethodwArgs ) shader_hm.uniforms.get( uni )[ 2 ];
if ( preRenderStep != null ){
preRenderStep.invoke( shader_hm_PreRenderArgs.get( uni ) );
}
}
geom_hm.drawListBuffer.position( 0 );
// Draw the square
GLES30.glDrawElements( GLES30.GL_TRIANGLES, geom_hm.drawOrder.length, GLES30.GL_UNSIGNED_INT, geom_hm.drawListBuffer );
// Perform the post-render steps (if they exist)
for( String attr : shader_hm.attributes.keySet() ){
RefMethodwArgs postRenderStep = ( RefMethodwArgs ) shader_hm.attributes.get( attr )[ 3 ];
if ( postRenderStep != null ){
postRenderStep.invoke();
}
}
for( String uni : shader_hm.uniforms.keySet() ){
RefMethodwArgs postRenderStep = ( RefMethodwArgs ) shader_hm.uniforms.get( uni )[ 3 ];
if ( postRenderStep != null ){
postRenderStep.invoke();
}
}
// Make sure we're no longer using a program or framebuffer
GLES30.glUseProgram( 0 );
GLES30.glBindFramebuffer( GLES30.GL_FRAMEBUFFER, 0 );
GLES30.glViewport( 0, 0, FutureWarRenderer.getRef().viewport_w, FutureWarRenderer.getRef().viewport_h );
}
Код для чтения карт высот выглядит следующим образом. Также проверяемо работает (если это делается сразу после отрисовки в фреймбуфер). Проверено, что значения действительно считывались и правильно использовались в последующих операциях.
public void readHMTexture( int hm ){
GLES30.glViewport( 0, 0, texWH, texWH );
// Get the previously defined framebuffer and attach our texture as the target
GLES30.glBindFramebuffer( GLES30.GL_FRAMEBUFFER, fbos[ hm ] );
GLES30.glFramebufferTextureLayer( GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, hmTexHandles[0], 0, hm );
GLES30.glFramebufferTextureLayer( GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT1, hmTexHandles[1], 0, hm );
GLES30.glBindBuffer( GLES30.GL_PIXEL_PACK_BUFFER, pbos[0] );
GLES30.glBufferData( GLES30.GL_PIXEL_PACK_BUFFER, texWH * texWH * 1, null, GLES30.GL_DYNAMIC_READ );
GLES30.glReadBuffer( GLES30.GL_COLOR_ATTACHMENT0 );
GLES30.glReadPixels( 0, 0, texWH, texWH, GLES30.GL_RED, GLES30.GL_UNSIGNED_BYTE, 0 );
ByteBuffer pixelBuffer0;
pixelBuffer0 = (ByteBuffer) GLES30.glMapBufferRange(GLES30.GL_PIXEL_PACK_BUFFER, 0, texWH * texWH * 1, GLES30.GL_MAP_READ_BIT); //downdload from the GPU to CPU
ByteBuffer pixelBuffer0_clone = ByteBuffer.allocate( pixelBuffer0.capacity() );
pixelBuffer0.rewind();//copy from the beginning
pixelBuffer0_clone.put( pixelBuffer0 );
pixelBuffer0.rewind();
texLayerByteBuffers0.add( pixelBuffer0_clone );
GLES30.glBindBuffer( GLES30.GL_PIXEL_PACK_BUFFER, pbos[1] );
GLES30.glBufferData( GLES30.GL_PIXEL_PACK_BUFFER, texWH * texWH * 1, null, GLES30.GL_DYNAMIC_READ );
GLES30.glReadBuffer( GLES30.GL_COLOR_ATTACHMENT1 );
GLES30.glReadPixels( 0, 0, texWH, texWH, GLES30.GL_RED, GLES30.GL_UNSIGNED_BYTE, 0 );
ByteBuffer pixelBuffer1;
pixelBuffer1 = (ByteBuffer) GLES30.glMapBufferRange(GLES30.GL_PIXEL_PACK_BUFFER, 0, texWH * texWH * 1, GLES30.GL_MAP_READ_BIT); //downdload from the GPU to CPU
ByteBuffer pixelBuffer1_clone = ByteBuffer.allocate( pixelBuffer1.capacity() );
pixelBuffer1.rewind();//copy from the beginning
pixelBuffer1_clone.put( pixelBuffer1 );
pixelBuffer1.rewind();
texLayerByteBuffers1.add( pixelBuffer1_clone );
GLES30.glUnmapBuffer( GLES30.GL_PIXEL_PACK_BUFFER );
GLES30.glBindBuffer( GLES30.GL_PIXEL_PACK_BUFFER, 0 );
GLES30.glBindFramebuffer( GLES30.GL_FRAMEBUFFER, 0 );
GLES30.glViewport( 0, 0, FutureWarRenderer.getRef().viewport_w, FutureWarRenderer.getRef().viewport_h );
}
Любые предложения или мысли о том, почему отложенное чтение слоев не удается, а немедленное чтение выполняется успешно? Или способ реструктурировать часть чтения, чтобы она работала при массовом отложенном чтении?
Изменить. Основываясь на приведенном ниже предложении rokuz, удалил PBO и заменил glReadPixels непосредственно на ByteBuffer. Теперь имеет почти более странное поведение. Если я запускаю исходный метод (всегда работает), а затем запускаю альтернативный вариант, на который я пытаюсь переключиться, он будет запускаться один раз, но затем никогда не будет успешно запускаться снова, если я не переключусь обратно на исходный метод.
Редактировать 2. На основании приведенного выше поведения проверено, действительно ли программа правильно завершает работу, и кажется, что второй метод зависает на телефоне, даже если он успешно отображается в первый раз. На изображении: https://i.imgur.com/kKCpZ8J.png процесс на самом деле не завершается, а затем зависает телефон, если я пытаюсь его закрыть.
Редактировать 3 Новая морщина. Основываясь на приведенном ниже совете @solidpixel, попытался использовать glFenceSync. Затем понял, что glClientWaitSync(fence) будет останавливать ЦП, и в итоге он не получит выгоды от использования PBO. Потом подумал, а может поумничать и использовать glGetSynciv( long sync, int pname, int bufSize, int[] length, int lengthOffset, int[] values, int valuesOffset ).
Однако glGetSynciv ведет себя довольно странно. Первоначально я настроил чтение, используя:
GLES30.glBindBuffer( GLES30.GL_PIXEL_PACK_BUFFER, pbos[ pboIndex ] );
GLES30.glBufferData( GLES30.GL_PIXEL_PACK_BUFFER, texWH * texWH * 1, null, GLES30.GL_DYNAMIC_READ );
GLES30.glReadBuffer( GLES30.GL_COLOR_ATTACHMENT0 );
// Start the first pixel buffer read
GLES30.glReadPixels( 0, 0, texWH, texWH, GLES30.GL_RED, GLES30.GL_UNSIGNED_BYTE, 0 );
hmReadFences[pboIndex] = GLES30.glFenceSync( GLES30.GL_SYNC_GPU_COMMANDS_COMPLETE, 0 );
// Set up an update to wait for the read
RefMethodwArgs finishHmRead0 = new RefMethodwArgs( this, "finishHMRead", new Object[]{ pboIndex } );
GameState.getRef().addUpdate( finishHmRead0 );
GLES30.glBindBuffer( GLES30.GL_PIXEL_PACK_BUFFER, pbos[ pboIndex+1 ] );
GLES30.glBufferData( GLES30.GL_PIXEL_PACK_BUFFER, texWH * texWH * 1, null, GLES30.GL_DYNAMIC_READ );
GLES30.glReadBuffer( GLES30.GL_COLOR_ATTACHMENT1 );
// Start the second pixel buffer read
GLES30.glReadPixels( 0, 0, texWH, texWH, GLES30.GL_RED, GLES30.GL_UNSIGNED_BYTE, 0 );
hmReadFences[pboIndex+1] = GLES30.glFenceSync( GLES30.GL_SYNC_GPU_COMMANDS_COMPLETE, 0 );
// Set up an update to wait for the read
RefMethodwArgs finishHmRead1 = new RefMethodwArgs( this, "finishHMRead", new Object[]{ pboIndex+1 } );
GameState.getRef().addUpdate( finishHmRead1 );
// Check that the fences got created and appear to have the correct status.
int[] length0 = new int[1]; // Shows length 1 for all cases
int[] status0 = new int[1]; // Shows 37144 (GLES30.GL_UNSIGNALED)
GLES30.glGetSynciv( hmReadFences[ pboIndex ], GLES30.GL_SYNC_STATUS, 1, length0, 0, status0, 0 );
int signalStatus = status0[0];
int[] length1 = new int[1]; // Shows length 1 for all cases
int[] status1 = new int[1]; // Shows 37144 (GLES30.GL_UNSIGNALED)
GLES30.glGetSynciv( hmReadFences[ pboIndex ], GLES30.GL_SYNC_STATUS, 1, length1, 0, status1, 0 );
signalStatus = status1[0];
Кажется, все в порядке. Оба забора имеют статус 37144 (GLES30.GL_UNSIGNALED) и, похоже, существуют. RefMethodwArgs finishHmRead — это вызов асинхронной функции обновления, которая проверяет, завершены ли заборы, запускается в Runnable gameUpdateTask = new Runnable() и, по-видимому, правильно вызывает указанный ссылочный метод. "finishHMRead", показанный ниже.
public void finishHMRead( int pboIndex ){
int[] length = new int[1];
int[] status = new int[1];
GLES30.glGetSynciv( hmReadFences[ pboIndex ], GLES30.GL_SYNC_STATUS, 1, length, 0, status, 0 );
int length = length[0] // Now shows 0 on every fence?
int signalStatus = status[0]; // Now just returns 0 on every fence?
int glSignaled = GLES30.GL_SIGNALED;
if ( signalStatus == glSignaled ){
// Do all the buffer data copying now that glReadPixels is done
} else {
// Resubmit the check for completion
RefMethodwArgs finishHmRead = new RefMethodwArgs( this, "finishHMRead", new Object[]{ pboIndex } );
FutureWarGameState.getRef().addUpdate( finishHmRead );
}
}
Однако при вызове finishHMRead(int pboIndex). Внезапно заборы ничего не возвращают из glGetSynciv. hmReadFences[ pboIndex ] все еще существует, вызов просто возвращает все 0.
Редактировать 4 Хорошо, наконец-то заработало. В соответствии с предложением @solidpixel, приведенным ниже, пришлось изменить отложенные вызовы методов, проверяющие статус считанного пикселя в цикле обновления, который выполняется только в потоке рендеринга. Добавьте проверки завершения чтения пикселей с помощью чего-то вроде. UpdateList.getRef().addRenderUpdate(finishHmRead);
Затем в средстве визуализации вызовите обновление, чтобы проверить статус считанного пикселя с помощью:
public void renderUpdate(){
long timeStart = System.currentTimeMillis();
long timeElapsed = 0;
int renderUpdateListLen = renderUpdateList.size();
if ( renderUpdateListLen > 0 ) {
while( renderUpdateListLen > 0 ) {
RefMethodwArgs curMethod = renderUpdateList.get( 0 );
if ( timeElapsed < longestRenderUpdateTime ){
// If we still have time available, run the update
if ( curMethod != null ){
curMethod.invoke();
}
} else {
// If we're out of time this cycle add the rest of the updates for the next cycle
renderUpdateListNextCycle.add( curMethod );
}
renderUpdateList.remove( 0 );
renderUpdateListLen = renderUpdateList.size();
timeElapsed = System.currentTimeMillis() - timeStart;
}
}
// Push all the remaining updates to the next cycle
if ( renderUpdateListNextCycle.size() > 0 ){
renderUpdateList.addAll( renderUpdateListNextCycle );
renderUpdateListNextCycle.clear();
}
}
Изменено выше (вызывается из основного потока рендеринга) "finishHMRead" на:
public void finishHMRead( int pboIndex ){
int[] length = new int[1];
int[] status = new int[1];
GLES30.glGetSynciv( hmReadFences[ pboIndex ], GLES30.GL_SYNC_STATUS, 1, length, 0, status, 0 );
int signalStatus = status[0];
int glSignaled = GLES30.GL_SIGNALED;
if ( signalStatus == glSignaled ){
// Ready a temporary ByteBuffer for mapping (we'll unmap the pixel buffer and lose this) and a permanent ByteBuffer
ByteBuffer pixelBuffer;
texLayerByteBuffers[ pboIndex ] = ByteBuffer.allocate( texWH * texWH );
// map data to a bytebuffer
GLES30.glBindBuffer( GLES30.GL_PIXEL_PACK_BUFFER, pbos[ pboIndex ] );
pixelBuffer = ( ByteBuffer ) GLES30.glMapBufferRange( GLES30.GL_PIXEL_PACK_BUFFER, 0, texWH * texWH * 1, GLES30.GL_MAP_READ_BIT );
// Copy to the long term ByteBuffer
pixelBuffer.rewind(); //copy from the beginning
texLayerByteBuffers[ pboIndex ].put( pixelBuffer );
// Unmap and unbind the currently bound pixel buffer
GLES30.glUnmapBuffer( GLES30.GL_PIXEL_PACK_BUFFER );
GLES30.glBindBuffer( GLES30.GL_PIXEL_PACK_BUFFER, 0 );
Log.i( "myTag", "Finished copy for pbo data for " + pboIndex + " at: " + (System.currentTimeMillis() - initSphereStart) );
acknowledgeHMReadComplete();
} else {
// If it wasn't done, resubmit for another check in the next render update cycle
RefMethodwArgs finishHmRead = new RefMethodwArgs( this, "finishHMRead", new Object[]{ pboIndex } );
UpdateList.getRef().addRenderUpdate( finishHmRead );
}
}
Затем я делаю что-то после того, как все пиксельные буферы успешно возвращены, и, наконец, начинаю рендеринг завершенного объекта (и выполняю шаги эрозии, о которых я упоминал изначально, один раз за кадр. Результатом является быстрый запуск и эрозия изображений, таких как: https:// i.imgur.com/0ju49lk.png
Спасибо за быстрое предложение @rokuz. Полное избавление от пиксельных буферов действительно решает проблему. Однако теперь он отмечает, что «критическая блокировка JNI удерживалась в течение 5,752 с» прямо перед первым вызовом glReadPixels. Итак, "решает" вопрос, просто вводит массовый стойл. Также видно при профилировании. «Завершенный расчет карты высот на: 2473. Критическая блокировка JNI удерживалась в течение 5,752 с. Завершено glReadPixels 0 (readHM) на итере 0 на: 9379». По крайней мере, дает представление о том, что может быть не так.
Сотрите предыдущий результат. Теперь я еще больше запутался. Запустился один раз успешно, затем каждый раз после этого он возвращался к тому же поведению. Переходит к пустому белому экрану, продолжает предоставлять информацию, например, о попытке рендеринга, но затем зависает эмулятор и даже не позволяет закрыть задачу. (с PBO или без, два разных эмулируемых телефона)
Что ж, это хороший знак, что glReadPixels
как-то работает, мы можем локализовать проблему в PBO. что касается остановки, согласно вашему коду raw glReadPixels
не должен сильно отличаться от PBO+glMapBufferRange
. В этом случае приложение должно ждать glMapBufferRange
, пока не будут выполнены команды рендеринга. Вы можете проверить этот блог для подробного объяснения - roxlu.com/2014/048/… Кроме того, во втором случае вы часто переназначаете фреймбуферы, это также дорого.
Использование glReadPixels()
для заполнения пиксельного буфера является асинхронной операцией. Вам нужно дождаться его завершения, прежде чем читать результаты на процессоре.
После glReadPixels()
вставьте забор:
fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)
... и затем подождите:
glClientWaitSync(fence)
Я думаю, вам также нужно вставить glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT)
перед отображением буфера, чтобы обеспечить видимость.
Обратите внимание, что glReadPixels()
работает медленно, поэтому в идеале вы должны дождаться ограничения во втором потоке, а не останавливать основной поток приложения.
Спасибо за совет. Я попробую рекомендацию и дам вам знать, если это сработает. Не знал, что такое метод забора/барьера и как его искать. После публикации этого я узнал, насколько openGL является асинхронным, чего я действительно не понимал. Подразумевалось, что если я вызову glDraw[...], его нужно будет завершить, прежде чем он позволит мне снова вызвать с теми же ресурсами/целью рендеринга, но, по-видимому, я могу вызвать 1000 кадров в цикле, и он просто отправляет и потом зависает вся система.
Хорошо, изучил glFenceSync, и я не думаю, что glClientWaitSync(fence) будет работать. Он намеренно останавливает ЦП до тех пор, пока не будут выполнены все команды графического процессора. Тем не менее, я нашел glGetSynciv, который возвращает статус glFenceSync, и кажется, что он отлично сработает для вашего последнего предложения «подождите забора во втором потоке». Однако. реализовано, и кажется, что заборы исчезают, согласно описанию в Редактировании 3 выше. Мысли?
Имеет ли второй поток связанный EGLContext в той же общей группе, что и основной поток?
Подозреваю, что нет. Глядя на Grafika, «Показать + захватить камеру» из другого комментария, который, по-видимому, использует GLSurfaceView с общим EGLContext.
В приведенном выше редактировании это работало с предложением об отсутствии EGLContext во втором потоке. Посмотрел на совместное использование контекстов, и это выглядело очень запутанным, поэтому просто переместил проверки в ограниченное по времени обновление в потоке рендеринга. Примите приведенный выше ответ, поскольку он был полезен и дал подсказки.
Вы пытались использовать
glReadPixels
без PBO? Просто для проверки, сохраняется ли проблема в этом случае.