【问题标题】:Most Efficient Way to Retrieve Texture Pixel Data?检索纹理像素数据的最有效方法?
【发布时间】:2016-06-06 01:23:33
【问题描述】:

我至少知道 Dx9 的 Directx,它有一个纹理对象,您只能将一小部分纹理获取到 CPU 可访问内存。我相信这是一个名为“LockRect”的函数。 OpenGL 有glGetTexImage(),但它会抓取整个图像,如果格式与纹理不同,那么它必须在传输整个纹理的基础上将整个纹理转换为新的像素格式。这个函数也不在 OpenGL ES 中。帧缓冲区是另一种选择,但我可能会绑定一个帧缓冲区,其中颜色附件连接到纹理。然后是从帧缓冲区读取的glReadPixels,所以它应该从纹理中读取。 glReadPixels 的像素格式选项有限,因此必须进行转换,但我可以读取我需要的像素(只有 1 个像素)。我没有使用过这种方法,但似乎可以。如果有人可以确认帧缓冲区方法,那它是一个可行的选择。那么这个方法也适用于 OpenGL ES 2+。

还有其他方法吗?帧缓冲区方法的效率如何(如果有效),它最终是否必须在读取像素之前将整个纹理转换为所需的格式,还是完全由实现定义?

编辑:@Nicol_Bolas请停止从标签中删除 OpenGL 并添加 OpenGL-ES,OpenGL-ES 不适用,OpenGL 是。这是专门针对 OpenGL 的,但如果可能的话,我希望它与 Open ES 2+ 兼容,尽管并非必须如此。如果只有 OpenGL 的解决方案可用,那么我会考虑是否值得权衡。谢谢。

【问题讨论】:

    标签: opengl opengl-es-2.0 opengl-es-3.0


    【解决方案1】:

    请注意,我在 ES 方面没有太多经验,因此在这种情况下可能有更好的方法来做这件事。不过,一般要点适用于普通 OpenGL 或 ES。


    首先,最重要的性能考虑应该是在阅读时。如果您在渲染时从显卡请求数据,您的程序(CPU 端)将不得不暂停,直到显卡返回数据,由于您无法发出进一步的渲染命令,这将减慢渲染速度。作为一般规则,您应该始终上传、渲染、下载 - 不要混合使用这些过程中的任何一个,这会极大地影响速度,而且很大程度上取决于驱动程序/硬件/操作系统。

    我建议在渲染周期结束时使用glReadPixels( )。我怀疑该功能的格式限制与帧缓冲区格式的限制有关。此外,你真的应该使用 8 位无符号或浮点,both of which are supported。如果您遇到不允许任何这些支持格式的特殊情况,您应该解释这是什么,因为可能有专门处理它的方法。

    如果您在渲染的特定点(而不是结束)需要帧缓冲区的内容,请创建第二个纹理 + 帧缓冲区(同样格式相同)作为有效的“后缓冲区”,然后从目标复制该纹理的帧缓冲区。这发生在视频卡上,因此它不会直接读取总线延迟。这是我写的执行此操作的内容:

    glActiveTexture( GL_TEXTURE0 + unit );
    glBindTexture( GL_TEXTURE_2D, backbufferTextureHandle );
    glBindFramebuffer( GL_READ_FRAMEBUFFER, framebufferHandle );
    glCopyTexSubImage2D(
            GL_TEXTURE_2D,
            0, // level
            0, 0, // offset
            0, 0, // x, y
            screenX, screenY );
    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, framebufferHandle );
    

    然后,当您需要数据时,将后台缓冲区绑定到 GL_READ_FRAMEBUFFER 并在其上使用glReadPixels( )

    最后,您应该记住,下载数据仍然会停止 CPU 端。如果您在显示帧缓冲区之前下载,您将推迟显示图像,直到您可以再次执行命令,这可能会导致可见的延迟。因此,我建议您仍然使用非默认帧缓冲区,即使您只关心最终缓冲区状态,并结束渲染周期以达到以下效果:

    (1.) Blit 到默认帧缓冲区:

    glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 ); // Default framebuffer
    glBindFramebuffer( GL_READ_FRAMEBUFFER, framebufferHandle );
    glBlitFramebuffer(
            0, 0, screenX, screenY,
            0, 0, screenX, screenY,
            GL_COLOR_BUFFER_BIT,
            GL_NEAREST );
    

    (2.) 在您给定的情况下调用任何您的交换缓冲区命令。

    (3.) 来自帧缓冲区的下载调用(可能是 glReadPixels( ) 或其他)。

    至于 blit/texcopy 操作对速度的影响,它在大多数现代硬件上都相当不错,即使每帧执行 10 次以上,我也没有发现它有明显的影响,但如果您正在处理过时的硬件,这可能值得三思。

    【讨论】:

    • 我试图获得一个纹理,一旦它从磁盘加载到 GPU 内存中,其内容就不会改变。我不需要特定的格式,但如果格式不匹配,使用glReadPixels() 将需要某种计算,速度较慢但不确定多少。我只需要检索一个像素,所以它应该不会太糟糕,在最坏的情况下是像 DXT 这样的压缩格式。不过,它会为我节省一些工作。逻辑顺序是获取像素并将该像素用作渲染的输入。它还与多个样本一起完成了计算。
    • @user3901459 数据量并没有真正影响速度;总线延迟是主要限制因素,而不是带宽,因此一个像素与整个帧缓冲区的时间大致相同。也就是说,如果您只想要上传的数据,我认为您做错了。只需保留您最初传递给 OpenGL 的任何图像数组。如果您要从光盘加载压缩格式,那就有点困难了;我没有任何纹理压缩方面的经验,但大概你可以在软件中做到这一点并完全去掉 GPU。
    • 在某些时候带宽会影响它,对吧?以一个非常大的纹理为例,抓住整个物体会使其变慢吗?我想这取决于相对于延迟需要多长时间。将它全部保存在内存中也不是最好的,如果所有其他方法都失败了,我将不得不这样做,但这实际上会使内存使用量翻倍。特别是在像移动设备这样的共享内存系统上。我想可以制作一些混合系统来读取纹理并将其存储在 CPU 内存中,如果它有一段时间不使用然后删除它。它仍然需要足够快,否则它会停止
    • @user3901459 延迟是主要因素。视频带宽非常好,通常您在不知不觉中上传/下载了很多。主要问题是等待双向命令发生,特别是因为 GPU 必须等待任何排队的命令完成。
    • @user3901459 它实际上并没有使内存使用量翻倍,因为 CPU 和视频内存不同。所以如果你上传后删除它,纹理实际上占用了0个正常的系统内存。即使实际的物理内存来自同一个地方,它通常也被分成固定的块,所以你不会真正丢失任何东西。下载每一帧以避免保留数据确实很笨重,并且可能会引入其他问题,从而抵消好处。但是,我是从为视频卡使用桌面 OpenGL 的人的角度说的 - 所以它可能会有所不同。
    猜你喜欢
    • 2011-06-18
    • 1970-01-01
    • 2014-06-02
    • 2014-02-03
    • 2017-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多