OpenGL使用FBO与PBO上行纹理 (YUYV)

  本文记录在OpenGL中使用 帧缓冲 (FBO)和像素缓冲(PBO)来上行视频帧数据(YUYV), 并最终渲染显示出来。在之前的文章中介绍了渲染 YUV420 和 YUYV 的流程,不过都是用的默认的帧缓冲,即创建一个和视频幅面一样的 GLFW 窗口, 但是在实际开发中会遇到各种幅面的素材,,此时默认的 GLFW 帧缓冲就不满足要求了, 所以需要借助自定义的 FBO 来完成各种幅面视频帧的上行。


本文相关详细代码地址: https://github.com/pengguoqing/samples_code/tree/master/OpenGL

一、 帧缓冲(FBO)

  关于帧缓冲, LearnOpenGL 官方网站已经介绍得比较详细了, 这里就不再赘述。上行视频帧数据主要就是用到帧缓冲的颜色缓冲。首先根据视频帧的幅面为帧缓冲创建一个同幅面的纹理附件,FBO 创建纹理附件和渲染缓冲附件的流程都大体相似,所以就直接封装了一个简单的 CXFBO的 C++ 类,只需要简单的设置纹理附件的宽高参数即可,使用方式如下:

	CXFbo yuy2fbo;
	yuy2fbo.InitFbo(imgwidth, imgheight);
    yuy2fbo.UnBindFbo();

初始化的部分代码如下:

	glGenTextures(1, &m_colortex);
    glBindTexture(GL_TEXTURE_2D, m_colortex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colortex, 0);

二、 像素缓冲(PBO)

  关于PBO这篇文章有非常详细的介绍: http://www.songho.ca/opengl/gl_pbo.html, 这里贴出原文中一张能简单清晰的阐述 PBO 原理的示意图:

在这里插入图片描述
比如 CPU 解码出一帧 YUV 数据后, 如果使用 PBO上行的话那么CPU 只需要运算将 YUV 数据拷贝至 像素缓冲区这一步, 后续流程都由利用 GPU 强大的并行运行能力将像素缓冲区数据加载到GPU显存中。 与不使用 PBO相比, CPU 只需要进行一次拷贝即可, 否则整个过程都由 CPU 控制,时钟周期这一缺点就显露出来了。这里贴出封装的 PBO初始化流程和调用接口:

public:
    bool Init(uint32_t width, uint32_t height, PBOTYPE type, GLenum pixfmt);
    void Map(uint8_t** ptr, uint32_t* linesize) const;
    void UnMap() const;
    void Bind() const;
    void UnBind() const;
bool CXPbo::Init(uint32_t width, uint32_t height, PBOTYPE type, GLenum pixfmt)
{
   m_width  = width;
   m_hegit  = height;
   m_pixfmt = pixfmt;
   
   switch (type)
   {
   case PBOTYPE::kDynamic:
        m_rwtype = GL_PIXEL_UNPACK_BUFFER;
    break;
   case PBOTYPE::kStage:
        m_rwtype = GL_PIXEL_PACK_BUFFER;
    break;
   default:
        return false;
    break;
   }
   

   glGenBuffers(1, &m_pbo);
   if (!m_pbo)
   {
     return false;
   }
    
   glBindBuffer(m_rwtype, m_pbo);

   m_pbosize  = m_width * GetPixfmtBpp(pixfmt) / 8;
   m_pbosize  = (m_pbosize + 3) & 0xFFFFFFFC;
   m_pbosize  *= m_hegit;
   glBufferData(m_rwtype, m_pbosize, nullptr, GL_STREAM_DRAW);

   glBindBuffer(m_rwtype, 0);

   return true;
}

三、 关于YUYV纹理的采样

  在之前的文章中 OpenGL渲染YUYV, 因为默认的帧缓冲和实际的素材的幅面相同, 而且又是用的 RGBA 来存储一组完整的 YUYV 数据, 所以 shader 中使用 texture直接采样时会自动进行插值,这就会导致 YUV 不是 严格严格匹配的,正确的做法是需要使用 textuerLod来加载原始纹理,以保证YUV的严格匹配,shader 如下:

vec3 SampleYUYV(vec2 pos)
{
	ivec2 size		= textureSize(image,  0);
	ivec2 actualpos = ivec2(pos.x, pos.y);
	vec2  actualuv	= (vec2(actualpos.xy)+0.5)/size;
	vec4 yuyv		= textureLod(image, actualuv, 0);
	
	float leftover  = fract(pos.x);
	float y   = (leftover<0.5f) ? yuyv.x : yuyv.z;

    vec3 yuv = vec3(y, yuyv.yw);

	return YUV_to_RGB(yuv);	
}

四、 测试 Demo

   首先准备一张 1280 * 960 幅面的 YUYV 数据,然后用 GLFW 创建一个 960 * 540 (16:9)的窗口用于显示。代码中使用宏来配置是否使用 PBO模式

#define USE_PBO  true

渲染管线的 pass 如下:

 while (!glfwWindowShouldClose(winhandle))
    {
        processInput(winhandle);
        glViewport(0, 0, imgwidth, imgheight);
        
        yuy2fbo.BindFbo();
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
        uploadshader.use();
        UploadYUYV(yuy2pbo, imgdata, uploadtex);

		glBindVertexArray(uploadvao);
		glBindTexture(GL_TEXTURE_2D, uploadtex);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glBindTexture(GL_TEXTURE_2D, 0);
        yuy2fbo.UnBindFbo();
        glBindVertexArray(0);

		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
        glViewport(0, 0, wndwidth, wndheight);
        rendershader.use();
        glBindVertexArray(rendervao);
        yuy2fbo.BindColorTexture();
        glGenerateMipmap(GL_TEXTURE_2D);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        yuy2fbo.UnBindColorTexture();
        glfwSwapBuffers(winhandle);
        glfwPollEvents();
    }

渲染结果如下:
在这里插入图片描述
使用 PBO后的纹理上行效率如下:
在这里插入图片描述
使用 glTexSubImage2D直接上行纹理的效率如下:
在这里插入图片描述
可以发现效率差别还是蛮大的。

五、 总结

  ① 使用 PBO进行纹理上行的效率要由于 glTexSubImage2D,但是测试的时候发现假如把 glMapBuffer的整个时间算上的话,两者其实是相差不多的。所以实际开发中可能需要 多个 PBO来交替上行纹理。
  ②渲染管线中存在多个 Pass 需要借助 帧缓冲来实现。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要复制两个纹理,你可以使用OpenGL的帧缓冲对象(Framebuffer Objects,FBOs)来实现。以下是一些示例代码: 1. 创建两个纹理: ``` GLuint texture1, texture2; glGenTextures(1, &texture1); glBindTexture(GL_TEXTURE_2D, texture1); // 设置纹理参数... glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glGenTextures(1, &texture2); glBindTexture(GL_TEXTURE_2D, texture2); // 设置纹理参数... glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); ``` 2. 创建帧缓冲对象: ``` GLuint fbo; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); ``` 3. 绑定第一个纹理到帧缓冲的颜色附件0: ``` glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0); ``` 4. 绑定第二个纹理到帧缓冲的颜色附件1: ``` glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, texture2, 0); ``` 5. 检查帧缓冲是否完整: ``` if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { // 处理错误... } ``` 6. 渲染到帧缓冲: ``` glBindFramebuffer(GL_FRAMEBUFFER, fbo); // 渲染纹理1... glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 渲染纹理2... glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindFramebuffer(GL_FRAMEBUFFER, 0); ``` 7. 在渲染完成后,你可以使用glCopyTexImage2D函数将第一个纹理的内容复制到第二个纹理: ``` glBindTexture(GL_TEXTURE_2D, texture1); glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, width, height, 0); glBindTexture(GL_TEXTURE_2D, texture2); glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, width, height, 0); ``` 这样,你就可以复制两个纹理了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值