簡體   English   中英

從 GL_TEXTURE_2D_ARRAY 上的多個幀緩沖區讀取延遲像素緩沖區

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

使用 Android、OpenGL ES 3.0

嘗試使用幀緩沖區對象寫入 GL_TEXTURE_2D_ARRAY 的多個層以按順序創建高度圖,然后在稍后的循環操作中按順序從 GL_TEXTURE_2D_ARRAY 的所有層讀取。

如果我創建一個高度圖紋理層,然后立即使用像素緩沖區 object 從紋理中讀取,則讀取成功。 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基於上述行為,檢查程序是否真正正確退出,並且即使在第一次成功呈現時,第二種方法也會掛起電話。 Per image at: https://i.imgur.com/kKCpZ8J.png進程實際上並沒有退出,如果我試圖關閉它然后掛斷電話。

編輯 3新皺紋。 根據下面的@solidpixel 建議,嘗試使用 glFenceSync。 然后意識到 glClientWaitSync(fence) 將使 CPU 停頓,最終使用 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 是對異步更新 function 的調用,它將檢查圍欄是否已完成,在 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 );
    }
}

然后我在所有像素緩沖區成功返回后做一些事情,最后開始渲染完成的 object(並執行我最初每幀一次提到的侵蝕步驟。導致快速啟動和侵蝕圖像,例如: https://i。 imgur.com/0ju49lk.png

使用glReadPixels()填充像素緩沖區是一種異步操作。 在讀取 CPU 上的結果之前,您需要等待它完成。

glReadPixels()之后插入一個柵欄:

fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)

...然后等待它:

glClientWaitSync(fence)

我認為您還需要在映射緩沖區之前插入glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT)以確保可見性。

請注意glReadPixels(),因此理想情況下您會在第二個線程中等待柵欄,而不是停止應用程序主線程。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM