glTextureRangeAPPLE( GL_TEXTURE_RECTANGLE_ARB, m_mappedMemSize, m_mappedMem );
并且
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_SHARED_APPLE);
为了提高上传到VRAM的性能,我使用的纹理不是BGRA 纹理(对于 1920x1080 的每帧重约 8MB),而是三个单独的 Y、U 和 V 纹理。它们分别是 GL_LUMINANCE 和 GL_UNSIGNED_BYTE 格式,并且 Y 纹理是原始尺寸,而 U 和 V 则是其一半的尺寸。这样一来,上传的大小减少到了约 3 MB,已经显示出了一些改进。
我创建了一个这些 YUV 纹理的池(根据视频的大小,通常在 3 至 8 个表面之间(每个表面有 Y、U 和 V 三个组件),每个纹理都映射到上述 m_mappedMem 中自己的区域。
当我收到新解码的视频帧时,我会查找一组空闲的 YUV 表面,并使用以下代码更新它们三个组件:
glActiveTexture(m_textureUnits[texUnit]);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, planeInfo->m_texHandle);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_SHARED_APPLE);
glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
memcpy( planeInfo->m_buffer, srcData, planeInfo->m_planeSize );
glTexSubImage2D( GL_TEXTURE_RECTANGLE_ARB,
0,
0,
0,
planeInfo->m_width,
planeInfo->m_height,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
planeInfo->m_buffer );
(作为一个附加问题:我不确定是否应该为每个纹理使用不同的纹理单元?[顺便说一句,我正在使用单位0用于Y,1用于U,2用于V])
完成后,我所使用的纹理被标记为已使用,并且填充了VideoFrame类的信息(即纹理编号以及它们在缓冲区中占用的区域等),并放入队列中等待渲染。一旦达到最小队列大小,主应用程序就会收到通知,可以开始渲染视频了。
同时,主渲染线程(在确保正确状态等之后)访问此队列(该队列类通过互斥锁保护其内部访问),并呈现顶部帧。
该主要渲染线程有两个帧缓冲区,并通过glFramebufferTexture2D与它们关联的两个纹理,以实现某种双缓冲技术。
在主要渲染循环中,它检查哪个是前缓冲区,然后使用纹理单元0将此前缓冲区呈现到屏幕上:
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, frontTexHandle);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT );
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_TEXTURE_COORD_ARRAY );
glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
glVertexPointer(4, GL_FLOAT, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_texCoordBuffer);
glTexCoordPointer(2, GL_FLOAT, 0, 0);
glDrawArrays(GL_QUADS, 0, 4);
glPopClientAttrib();
在将当前帧渲染到屏幕之前(由于视频的帧率通常为24 fps,因此在下一帧视频被渲染之前,该帧可能会被渲染多次 - 这就是我使用这种方法的原因),我调用视频解码器类来检查是否有新的帧可用(即它负责同步时间轴并使用新帧更新后备缓冲区),如果有可用帧,则从视频解码器类内部将其渲染到后备缓冲纹理中(这发生在与主渲染线程相同的线程上)。
glBindFramebuffer(GL_FRAMEBUFFER, backbufferFBOHandle);
glPushAttrib(GL_VIEWPORT_BIT); // need to set viewport all the time?
glViewport(0,0,m_surfaceWidth,m_surfaceHeight);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_TEXTURE);
glPushMatrix();
glLoadIdentity();
glScalef( (GLfloat)m_surfaceWidth, (GLfloat)m_surfaceHeight, 1.0f );
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texID_Y);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texID_U);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texID_V);
glUseProgram(m_yuv2rgbShader->GetProgram());
glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
glEnableVertexAttribArray(m_attributePos);
glVertexAttribPointer(m_attributePos, 4, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_texCoordBuffer);
glEnableVertexAttribArray(m_attributeTexCoord);
glVertexAttribPointer(m_attributeTexCoord, 2, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_QUADS, 0, 4);
glUseProgram(0);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glPopAttrib();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
请注意,为了简洁起见,我省略了某些安全检查和注释。
在上述调用之后,视频解码器设置一个标志,表示缓冲区可以交换,在上面的主线程渲染循环之后,它检查该标志并相应地设置前缓冲区/后缓冲区。此外,使用的表面被标记为自由和可用状态。
在我的原始代码中,当我使用BGRA和通过glTexSubImage2D和glBegin和glEnd上传时,我没有遇到任何问题,但一旦我开始改进事情,使用着色器将YUV组件转换为BGRA,并进行DMA传输和glDrawArrays,这些问题开始出现。
基本上,它似乎部分像是撕裂效果(顺便说一下,我将GL交换间隔设置为1以与刷新同步),部分地像是在其中跳回几帧。
我期望拥有一个池,我可以将其渲染到,并且在渲染到目标表面后将其释放,并且双缓冲该目标表面应该足够了,但显然还需要在其他地方进行更多的同步处理-但是我真的不知道如何解决这个问题。
我假设因为现在通过DMA处理glTexSubImage2D(根据文档,该函数应立即返回),因此上传可能尚未完成(下一帧正在其上渲染),或者我忘记了(或不知道)需要为OpenGL(Mac)进行其他同步机制。
根据OpenGL分析器在我开始优化代码之前的情况:
- 几乎70%的GLTime在glTexSubImage2D中(即将8MB BGRA上传到VRAM)
- 几乎30%在CGLFlushDrawable中
而在我将代码更改为上述内容之后,它现在显示:
- 约4%的GLTime在glTexSubImage2D中(因此DMA似乎运行良好)
- 16%在GLCFlushDrawable中
- 几乎75%在glDrawArrays中(这让我大吃一惊)
对于这些结果有什么评论吗?
如果您需要有关我的代码设置的任何其他信息,请告诉我。非常感谢您提供解决此问题的提示。
编辑:这里是我的着色器供参考。
#version 110
attribute vec2 texCoord;
attribute vec4 position;
// the tex coords for the fragment shader
varying vec2 texCoordY;
varying vec2 texCoordUV;
//the shader entry point is the main method
void main()
{
texCoordY = texCoord ;
texCoordUV = texCoordY * 0.5;
gl_Position = gl_ModelViewProjectionMatrix * position;
}
和片段:
#version 110
uniform sampler2DRect texY;
uniform sampler2DRect texU;
uniform sampler2DRect texV;
// the incoming tex coord for this vertex
varying vec2 texCoordY;
varying vec2 texCoordUV;
// RGB coefficients
const vec3 R_cf = vec3(1.164383, 0.000000, 1.596027);
const vec3 G_cf = vec3(1.164383, -0.391762, -0.812968);
const vec3 B_cf = vec3(1.164383, 2.017232, 0.000000);
// YUV offset
const vec3 offset = vec3(-0.0625, -0.5, -0.5);
void main()
{
// get the YUV values
vec3 yuv;
yuv.x = texture2DRect(texY, texCoordY).r;
yuv.y = texture2DRect(texU, texCoordUV).r;
yuv.z = texture2DRect(texV, texCoordUV).r;
yuv += offset;
// set up the rgb result
vec3 rgb;
// YUV to RGB transform
rgb.r = dot(yuv, R_cf);
rgb.g = dot(yuv, G_cf);
rgb.b = dot(yuv, B_cf);
gl_FragColor = vec4(rgb, 1.0);
}
编辑2:顺便提一句,我还有另一种渲染流程,使用VDADecoder对象进行解码,性能非常好,但是存在同样的闪烁问题。因此,我的代码中肯定存在一些线程问题-到目前为止,我还无法确定具体的问题所在。但是,我还需要为那些不支持VDA的机器提供软件解码器解决方案,因此CPU负载相当高,因此我尝试将YUV转换为RGB的负载卸载到GPU上。