我有一个视频播放器应用程序,并使用多个线程以保持用户交互仍然顺畅。

最初,对视频进行解码的线程只是将生成的帧作为BGRA写入RAM缓冲区中,而glTexSubImage2D已将其上传到VRAM,足以正常播放视频,但是-如预期的那样-对于高清(esp 1920x1080)速度很慢。

为了改善这一点,我实现了另一种类型的池类,该池类具有自己的GL上下文(在Mac上是NSOpenGLContext,它与主上下文共享资源)。
此外,我更改了代码,以便使用

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分量的时间乘以3))-每个纹理都映射到上面的自己的区域中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 );

(作为一个附带的问题:我不确定是否应该为每个纹理使用不同的纹理单位?[我对Y使用单位0,对U使用1单位,对V btw使用2)]

完成此操作后,我将使用的纹理标记为已使用,并且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,因此该帧可能在渲染下一个视频帧之前渲染了几次-这就是我使用此方法的原因),我将视频解码器称为类,以检查是否有新帧可用(即,它负责同步到时间轴并使用新帧更新后备缓冲区),如果有帧可用,那么我正在从videodecoder类内部渲染到后备缓冲区纹理(此发生在与主渲染线程相同的线程上):
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);

[请注意,为简洁起见,我省略了某些安全检查和评论]

在上述调用之后,视频解码器会设置一个可以交换缓冲区的标志,并且从上面的主线程渲染循环之后,它将检查该标志并相应地设置frontBuffer/backBuffer。同样,已使用的表面也被标记为空闲并且可以再次使用。

在我的原始代码中,当我使用BGRA并通过glTexSubImage2D和glBegin和glEnd上载时,我没有遇到任何问题,但是一旦开始改进,使用着色器将YUV组件转换为BGRA,DMA传输以及glDrawArrays这些问题开始出现。

基本上,这看起来像是一种撕裂效果(顺便说一句,我将GL交换间隔设置为1以与刷新同步),并且部分地看起来像是在两者之间跳了几帧。

我希望有一个我要渲染的表面池,并在渲染到目标表面后将其释放,并对该目标表面进行双缓冲就足够了,但是显然在其他地方需要进行更多的同步-但是我不这样做真的不知道该如何解决。

我假设由于glTexSubImage2D现在由DMA处理(根据文档应立即返回的功能),上传可能还没有完成(下一帧正在渲染),或者我忘记了(或不这样做)。不知道)我需要OpenGL(Mac)的其他一些同步机制。

根据OpenGL探查器的介绍,在我开始优化代码之前:
  • 在glTexSubImage2D中将近GLTime的70%(即将8MB BGRA上传到VRAM)
  • CGLFlush中的
  • 将近30%

    在将代码更改为上面的代码后,它现在显示:
  • 在glTexSubImage2D中大约占GLTime的4%(因此DMA似乎运行良好)
  • GLCFlushDrawable 中的
  • 16%
  • 在glDrawArrays中几乎达到了75%(这让我感到非常惊讶)

  • 对这些结果有何评论?

    如果您需要有关如何设置我的代码的更多信息,请告诉我。提示如何解决此问题将不胜感激。

    编辑:这是我的着色器以供引用
    #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

    最佳答案

    从我所看到的(即glPushMatrix调用等)来看,我假设您使用的不是最新的硬件,并且很有可能遇到了旧显卡的问题,例如CGLFlushDrawable Why is CGLFlushDrawable so slow? (I am using VBOs)

    您说的第二件事是YUV-> RGB着色器,该着色器显然可以多次访问源纹理,并且在任何视频卡上(尤其是较旧的视频卡),这都必须很慢。因此,glDrawArrays()调用的大量时间实际上反射(reflect)了您正在使用非常繁重的着色器程序(就内存访问而言)的事实,即使着色器代码看起来“无辜”也是如此。

    着色器代码访问纹理(从而访问系统的RAM),并且在性能方面(对于此视频卡)与执行RAM-> VRAM复制相同。

    一般建议:尽量避免使用非矩形和非二次方纹理。这也会破坏性能。还应避免使用任何非标准的纹理格式和扩展名。越简单-越好。如果您确实需要FullHD分辨率,请尝试使用2048x1024纹理或2048x2048之类的东西(顺便说一句,按纯算术,这应该会比较慢)。

    关于multithreading - Mac上的OpenGL中的多线程视频渲染显示了严重的闪烁问题,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/10680883/

    10-15 18:51