简体   繁体   English

从 GL_TEXTURE_2D_ARRAY 上的多个帧缓冲区读取延迟像素缓冲区

[英]Delayed Pixel Buffer Read from Multiple Framebuffers on GL_TEXTURE_2D_ARRAY

Using Android, OpenGL ES 3.0使用 Android、OpenGL ES 3.0

Attempting to write multiple layers of a GL_TEXTURE_2D_ARRAY using framebuffer objects to create heightmaps in sequence, and then read from all of the layers of the GL_TEXTURE_2D_ARRAY in sequence in a later looped operation.尝试使用帧缓冲区对象写入 GL_TEXTURE_2D_ARRAY 的多个层以按顺序创建高度图,然后在稍后的循环操作中按顺序从 GL_TEXTURE_2D_ARRAY 的所有层读取。

If I create a heightmap texture layer, and then immediately read from the texture, using a pixel buffer object, the read occurs successfully.如果我创建一个高度图纹理层,然后立即使用像素缓冲区 object 从纹理中读取,则读取成功。 IE, this pattern works. IE,这种模式有效。

for( int i = 0; i < initialQuads; i++ ){
    calcHeightmap( i );
    readHMTexture( i );
}

If I create all the heightmap texture layers, then read from the texture layers in a delayed loop, then the read fails (all reads return 0 values) and OpenGl rendering hangs.如果我创建所有高度贴图纹理层,然后在延迟循环中从纹理层读取,则读取失败(所有读取都返回 0 值)并且 OpenGl 渲染挂起。 IE, this pattern does not work. IE,这种模式不起作用。

for( int i = 0; i < initialQuads; i++ ){
    calcHeightmap( i );
}
for( int i = 0; i < initialQuads; i++ ){
    readHMTexture( i );
}

I want to do this, because I'm hoping to add an intermediary step between the initial generation step and the read step that will use all the layers simultaneously and modify the texture layers, similar to this shader I wrote on Shadertoy https://www.shadertoy.com/view/mssXRB (Just way more complex)我想这样做,因为我希望在初始生成步骤和读取步骤之间添加一个中间步骤,该步骤将同时使用所有图层并修改纹理图层,类似于我在 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 );
}

The code to generate the heightmaps is as follows.生成高度图的代码如下。 It verifiably works and produces heightmaps such as can be seen here: https://i.imgur.com/wQw3ZRP.png它确实有效并生成高度图,如下所示: 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 );
}

The code to read the heightmaps is as follows.读取高度图的代码如下。 Also verifiably works (if done immediately after a draw to a framebuffer).也可验证地工作(如果在绘制到帧缓冲区后立即完成)。 Checked that values were actually read and properly being used in later operations.检查值是否实际读取并在以后的操作中正确使用。

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

Any suggestions or thoughts on why a delayed read of the layers fails, but an immediate read succeeds?关于为什么延迟读取图层失败但立即读取成功的任何建议或想法? Or a way to restructure the read portion so that it will work in a bulk delayed read?或者一种重组读取部分的方法,以便它可以在批量延迟读取中工作?

Edit Based on rokuz suggestion below, took out PBO, and replaced with glReadPixels directly to ByteBuffer.编辑根据下面的 rokuz 建议,取出 PBO,并直接替换为 glReadPixels 到 ByteBuffer。 Now has almost weirder behavior.现在有几乎更奇怪的行为。 If I run using the initial method (always works), then run using the alternative I'm trying to switch to, it will run a single time, yet then never run again successfully unless I switch back to the original method.如果我使用初始方法运行(总是有效),然后使用我尝试切换到的替代方法运行,它将运行一次,但除非我切换回原始方法,否则再也不会成功运行。

Edit 2 Based on above behavior, checked whether the program was actually exiting properly, and appears that the second method hangs the phone even when it successfully renders the first time.编辑 2基于上述行为,检查程序是否真正正确退出,并且即使在第一次成功呈现时,第二种方法也会挂起电话。 Per image at: https://i.imgur.com/kKCpZ8J.png process does not actually exit, and then hangs phone if I attempt to close it. Per image at: https://i.imgur.com/kKCpZ8J.png进程实际上并没有退出,如果我试图关闭它然后挂断电话。

Edit 3 New wrinkle.编辑 3新皱纹。 Based on @solidpixel advice below, attempted to use glFenceSync.根据下面的@solidpixel 建议,尝试使用 glFenceSync。 Then realized glClientWaitSync(fence) is going to stall the CPU, and it ends up with no gain for using a PBO.然后意识到 glClientWaitSync(fence) 将使 CPU 停顿,最终使用 PBO 没有任何好处。 Then thought, maybe be clever, and use glGetSynciv( long sync, int pname, int bufSize, int[] length, int lengthOffset, int[] values, int valuesOffset ).然后想,也许是聪明,并使用 glGetSynciv( long sync, int pname, int bufSize, int[] length, int lengthOffset, int[] values, int valuesOffset )。

However, glGetSynciv acts rather strange.然而,glGetSynciv 的行为相当奇怪。 Initially, I set up the read using:最初,我使用以下方法设置读取:

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];

Seems ok.似乎还可以。 Both fences show a status of 37144 (GLES30.GL_UNSIGNALED) and appear to exist.两个栅栏都显示状态 37144 (GLES30.GL_UNSIGNALED) 并且似乎存在。 RefMethodwArgs finishHmRead is a call to an asynchronous update function that will check whether the fences have completed, runs in a Runnable gameUpdateTask = new Runnable(), and appears to call correctly to the reference method specified. RefMethodwArgs finishHmRead 是对异步更新 function 的调用,它将检查围栏是否已完成,在 Runnable gameUpdateTask = new Runnable() 中运行,并且似乎正确调用了指定的参考方法。 "finishHMRead" shown below. “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 );
    }
}

However, when finishHMRead( int pboIndex ) calls.但是,当 finishHMRead( int pboIndex ) 调用时。 Suddenly, the fences return nothing from glGetSynciv.突然间,栅栏没有从 glGetSynciv 返回任何内容。 The hmReadFences[ pboIndex ] still exist, the call just returns all 0's. hmReadFences[ pboIndex ] 仍然存在,调用只返回全 0。

Edit 4 Alright, finally got this to work.编辑 4好吧,终于让这个工作了。 Per @solidpixel suggestion below, had to change delayed method calls checking on the status of the pixel read to an update loop that only executes in the render thread.根据下面的@solidpixel 建议,必须将检查像素读取状态的延迟方法调用更改为仅在渲染线程中执行的更新循环。 Add checks for pixel read completion with something like.使用类似的方法添加像素读取完成检查。 UpdateList.getRef().addRenderUpdate( finishHmRead ); UpdateList.getRef().addRenderUpdate( finishHmRead );

Then in the renderer call an update to check on the status of the pixel read with:然后在渲染器中调用更新来检查像素读取的状态:

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

Changed above (called from main render thread) "finishHMRead" to:将上面的(从主渲染线程调用)“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 );
    }
}

Then I do stuff after all the pixel buffers have successfully returned, and finally start rendering the completed object (and doing the erosion steps that I mentioned initially once per frame. Results in quick startup and erosion images such as: https://i.imgur.com/0ju49lk.png然后我在所有像素缓冲区成功返回后做一些事情,最后开始渲染完成的 object(并执行我最初每帧一次提到的侵蚀步骤。导致快速启动和侵蚀图像,例如: https://i。 imgur.com/0ju49lk.png

Using glReadPixels() to populate a pixel buffer is an asynchronous operation.使用glReadPixels()填充像素缓冲区是一种异步操作。 You need to wait for it to complete before you read the results on the CPU.在读取 CPU 上的结果之前,您需要等待它完成。

After the glReadPixels() insert a fence:glReadPixels()之后插入一个栅栏:

fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)

... and then wait on it: ...然后等待它:

glClientWaitSync(fence)

I think you also need to insert glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT) before mapping the buffer to ensure visibility.我认为您还需要在映射缓冲区之前插入glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT)以确保可见性。

Note that glReadPixels() is slow , so ideally you'd wait for the fence in a second thread rather than stalling the application main thread.请注意glReadPixels(),因此理想情况下您会在第二个线程中等待栅栏,而不是停止应用程序主线程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM