任佳湍 发表于 2025-6-3 13:32:32

OpenGL渲染YUV实战:GPU加速转换与MipMap模糊效果实现

本文介绍如何采用 Qt + OpenGL 绘制 YUV 数据,并通过 OpenGL 来实现画质模糊。
前言

我们在开发音视频程序的时候,对于解码后帧的渲染往往有几个操作需要做:

[*]将 YUV420 格式的图像数据转换成 RGB 格式
[*]渲染 RGB 图像
在普通方案中,格式转换以及渲染都是在 CPU 中去做。而要降低 CPU 使用率,我们可以将上面这两个操作通通移动到 GPU 中去做,具体如果实现,请往下看。
OpenGL 渲染

我们通过 Qt 提供的 QOpenGLWidget 和 QOpenGLFunction 来进行画面的 GPU 渲染(其实 Qt 也只是对 OpenGL 接口的一套封装,类似于 GLAD 和 GLFW)。关于如何使用这两个类,本文不再赘述,如果不明白的同学,可以看我的这篇文章。
YUV420P

下面先让我们简单的学习一下 YUV420P 。YUV420P是一种常用的图像格式,主要用于视频处理和存储。它由三个平面构成:Y(亮度)、U(色度蓝色)和V(色度红色)。Y平面存储所有像素的亮度信息,而U和V平面只存储每四个Y像素对应的色度信息。这种采样方式使得YUV420P比RGB更高效,因为它减少了色度数据的存储量,从而节省了存储空间和带宽。
结构


[*]Y平面:包含所有像素的亮度信息。
[*]U平面:包含每个2x2像素块的蓝色色度信息。
[*]V平面:包含每个2x2像素块的红色色度信息。

转换

将YUV420P转换为RGB需要对U和V进行插值,然后应用转换公式。例如,每个Y值对应一个U和V值,通过插值得到全分辨率的U和V平面,再转换为RGB。
YUV 直出渲染

了解完 YUV420P 格式之后,我们来讲一讲应该怎样去渲染。在代码中,我们将 Y、U、V 三个分量分别存放在三个纹理中,将解码后的 AVFrame 中的 YUV 数据分别拷贝到对应的纹理。
// 假设有一个AVFrame对象名为 m_pFrame

// Y纹理
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame.linesize, m_frameSize.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data);

// U纹理
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame.linesize, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data);

// V纹理
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame.linesize, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data);然后在片段着色器中将三个纹理合成。
attribute vec3 vertexIn;    // xyz顶点坐标
attribute vec2 textureIn;   // xy纹理坐标
varying vec2 textureOut;    // 传递给片段着色器的纹理坐标

void main(void)
{
    gl_Position = vec4(vertexIn, 1.0);// 1.0表示vertexIn是一个顶点位置
    textureOut = textureIn; // 纹理坐标直接传递给片段着色器
}#version 330

vec3 yuv2rgb(vec3 yuv) {
        float r = 1.164 * yuv.x + 1.596 * yuv.z;
        float g = 1.164 * yuv.x - 0.392 * yuv.y - 0.813 * yuv.z;
        float b = 1.164 * yuv.x + 2.017 * yuv.y;
        return vec3(r, g, b);
}

varying vec2 textureOut;

uniform sampler2D textureY;
uniform sampler2D textureU;
uniform sampler2D textureV;

void main(void)
{
    vec3 yuv;
    vec3 rgb;

    yuv.x = texture(textureY, textureOut).r - 0.063;
    yuv.y = texture(textureU, textureOut).r - 0.502;
    yuv.z = texture(textureV, textureOut).r - 0.502;

    rgb = yuv2rgb(yuv);

    gl_FragColor = vec4(rgb, 1);
}OpenGL 实现画质模糊

在我们将软件商业化时,往往需要一些付费点,例如:画质。我们可以让非会员的画质降低,而降低画质又可以有几种方法:

[*]降低输入的画质
[*]FFmpeg 下采样+上采样
[*]OpenGL MipMap 多级纹理
直接降低输入画质是最简单的,但可能存在某些特殊情况不能直接在源头降低。而采用 FFmpeg 的方式则会增加两次重采样,白白消耗 CPU。而采用 MipMap 的模式,则能在不增加太多 GPU 占用率的情况下,完全不占用 CPU。
接下来我将为大家介绍一种使用 MipMap 来让画质模糊的方式。首先,让我们了解了解什么叫 MipMap。
The height and width of each image, or level, in the mipmap is a factor of two smaller than the previous level.

Mipmap(多级渐减图像)是OpenGL中用于优化纹理采样的一种技术。具体而言,Mipmap为每个纹理生成一系列不同分辨率的版本,每个级别(Level of Detail, LOD)的尺寸是前一级别的1/2。当渲染场景时,渲染器会根据纹理在屏幕上的实际大小自动选择合适的Mipmap级别,从而实现平滑过渡和抗锯齿效果。需要特别指出的是:Mipmap并不是简单的上采样(Upsampling)和下采样(Downsampling),而是通过预生成不同分辨率的纹理版本来实现高质量的模糊效果。这种预处理方式可以显著提升渲染效率,并确保纹理在不同缩放比例下都能保持视觉质量。
在实际应用中,可以通过以下方式配置Mipmap:

[*]调用glGenerateMipmap自动生成Mipmap
[*]设置合适的过滤器(Filter)来控制Mipmap的使用
[*]配置LOD偏移量(LOD bias)来调整Mipmap的使用级别
在 OpenGL 中,我们使用 glGenerateMipmap来生成 Mipmap。同时,我们需要将纹理环绕模式改成GL_LINEAR_MIPMAP_LINEAR。同时根据需要显示的不同模糊级别,为着色器中的 lodLevel设置不同的数值。
着色器代码如下:
#version 330

vec3 yuv2rgb(vec3 yuv) {
        float r = 1.164 * yuv.x + 1.596 * yuv.z;
        float g = 1.164 * yuv.x - 0.392 * yuv.y - 0.813 * yuv.z;
        float b = 1.164 * yuv.x + 2.017 * yuv.y;
        return vec3(r, g, b);
}

varying vec2 textureOut;

uniform sampler2D textureY;
uniform sampler2D textureU;
uniform sampler2D textureV;
uniform float lodLevel;

void main(void)
{
    vec3 yuv;
    vec3 rgb;

    // 跟普通纹理采样函数不同的是,这里我们调用的是textureLod,也就是选择不同等级的纹理。
    yuv.x = textureLod(textureY, textureOut, lodLevel).r - 0.063;
    yuv.y = textureLod(textureU, textureOut, lodLevel).r - 0.502;
    yuv.z = textureLod(textureV, textureOut, lodLevel).r - 0.502;

    rgb = yuv2rgb(yuv);

    gl_FragColor = vec4(rgb, 1);
}textureLod的各个参数意义为:

[*]_sampler_:指定绑定到纹理的采样器,即哪个纹理要被采样。
[*]_P_:采样的纹理坐标。
[*]_lod_:指定采样级别。
代码如下:
// 假设有一个AVFrame对象名为 m_pFrame

// Y纹理
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame.linesize, m_frameSize.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data);
// 生成mipmap
glGenerateMipmap(m_texture);

// U纹理
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame.linesize, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data);
// 生成mipmap
glGenerateMipmap(m_texture);

// V纹理
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// 生成mipmap
glGenerateMipmap(m_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame.linesize, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data);


m_shaderProgram.setUniformValue("textureY", 0);
m_shaderProgram.setUniformValue("textureU", 1);
m_shaderProgram.setUniformValue("textureV", 2);

// 设置0为原画
m_shaderProgram.setUniformValue("lodLevel", 0);
// 设置1为一级模糊
//m_shaderProgram.setUniformValue("lodLevel", 1);其中,GL_TEXTURE_MIN_FILTER 表示我们要配置纹理的缩小过滤方式(当纹理被应用到比其实际尺寸小的区域时如何采样)。GL_LINEAR_MIPMAP_LINEAR 是一个三线性过滤(Trilinear Filtering)模式,具体含义是:

[*]在相邻mipmap层级之间执行线性插值(例如介于1级和2级mipmap之间)
[*]在每个mipmap层级内部也执行线性插值(即双线性过滤)
陷阱

在我实际运用到项目上时发现,窗口内容竟然变黑了?一查日志才知道,着色器竟然编译失败了,报错内容如下:
ERROR: 0:27: 'textureLod' : no matching overloaded function found
ERROR: 0:27: 'r' :field selection requires structure, vector, or matrix on left hand side
ERROR: 0:28: 'textureLod' : no matching overloaded function found
ERROR: 0:28: 'r' :field selection requires structure, vector, or matrix on left hand side
ERROR: 0:29: 'textureLod' : no matching overloaded function found
ERROR: 0:29: 'r' :field selection requires structure, vector, or matrix on left hand side
但都 5202 年了,一般不会有显卡不支持 OpenGL 3.3 吧,那为什么会这样报错呢?当我们调用 OpenGL API 打印 OpenGL 版本和当前渲染器名称时:
const GLubyte* version = glGetString(GL_VERSION);   // 获取OpenGL版本(如 "OpenGL ES 2.0")
const GLubyte* renderer = glGetString(GL_RENDERER); // 获取渲染器名称(如GPU型号)结果显示为:
OpenGL ES 2.0
Microsoft Basic Render Driver
为什么渲染器是"Microsoft Basic Render Driver"?同时,OpenGL 的版本为什么是 OpenGL ES 2.0?
OpenGL ES 2.0 是移动设备和嵌入式系统的常见标准,与桌面版 OpenGL 存在差异(如精度、扩展支持)。OpenGL ES 并不是桌面端应用的,为什么会在桌面应用中出现 OpenGL ES 呢?通过一番搜索,最终我找到了答案:
如果系统没有硬件加速(如显卡驱动未安装),Windows 会使用  "Microsoft Basic Render Driver" ,此时需要降级到兼容模式(如使用 OpenGL ES 2.0 类似的特性),而 OpenGL ES 2.0 中不支持 textureLod 函数,所以自然就会报错。并且完全基于 CPU 计算(软件渲染),不支持 GPU 硬件加速,因此性能极低。有关Microsoft Basic Render Driver,可以看这:Microsoft 基本显示驱动程序 - Windows drivers。
其实这个问题根本原因是找到为什么会降级,但是苦于找不到具体原因,手上有没有一台可以复现的机器。所以,只能采用另外一条路,使用 OpenGL ES 2.0 的拓展:GL_EXT_shader_texture_lod
我们只需要小小的修改着色器代码,将 textureLod 改为 texture2DLodEXT,就能使用 MipMap 了:
#extension GL_EXT_shader_texture_lod : require
precision mediump float;

vec3 yuv2rgb(vec3 yuv) {
        float r = 1.164 * yuv.x + 1.596 * yuv.z;
        float g = 1.164 * yuv.x - 0.392 * yuv.y - 0.813 * yuv.z;
        float b = 1.164 * yuv.x + 2.017 * yuv.y;
        return vec3(r, g, b);
}

varying vec2 textureOut;

uniform sampler2D textureY;
uniform sampler2D textureU;
uniform sampler2D textureV;
uniform float lodLevel;

void main(void)
{
    vec3 yuv;
    vec3 rgb;

    yuv.x = texture2DLodEXT(textureY, textureOut, lodLevel).r - 0.063;
    yuv.y = texture2DLodEXT(textureU, textureOut, lodLevel).r - 0.502;
    yuv.z = texture2DLodEXT(textureV, textureOut, lodLevel).r - 0.502;

    rgb = yuv2rgb(yuv);

    gl_FragColor = vec4(rgb, 1);
}首先,在开头添加一行
extension GL_EXT_shader_texture_lod : require

代表开启 GL_EXT_shader_texture_lod拓展,其次调用 texture2DLodEXT来加载不同的 mipmap。
完整代码

#pragma once

#include <vector>

#include <QOpenGLBuffer>
#include <QOpenGLWidget>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>

#include "FrameObserver.h"

struct AVFrame;
class COpenGLRenderWidget : public QOpenGLWidget, protected QOpenGLFunctions, public IFrameObserver
{
    Q_OBJECT

public:
    explicit COpenGLRenderWidget(QWidget *parent = nullptr);
    ~COpenGLRenderWidget() override;

    void OnFrame(const AVFrame* frame, bool isHardwareFrame) override;
    void OnInputCodecContext(const AVCodecContext* ctx) override;
    HWND GetWindowHandle() const override;

private:
    void InitShaders();
    void InitTextures();
    void DeinitTextures();

private:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int w, int h) override;
   
private:
    AVFrame* m_pFrame = nullptr;

    QOpenGLShaderProgram m_shaderProgram;
    QOpenGLBuffer m_vbo;
    std::vector<GLuint> m_textures;

    bool m_bIsNeedUpdate = false;
};#include "OpenGLRenderWidget.h"extern "C"{#include "libavformat/avformat.h"}static const GLfloat coordinate[] = {    // 顶点坐标,存储4个xyz坐标    // 坐标范围为[-1,1],中心点为 0,0    // 二维图像z始终为0    // GL_TRIANGLE_STRIP的绘制方式:    // 使用前3个坐标绘制一个三角形,使用后三个坐标绘制一个三角形,正好为一个矩形    // x   y   z    -1.0f, -1.0f, 0.0f,   1.0f, -1.0f, 0.0f,    -1.0f,1.0f, 0.0f,   1.0f,1.0f, 0.0f,    // 纹理坐标,存储4个xy坐标    // 坐标范围为,左下角为 0,0    0.0f, 1.0f,    1.0f, 1.0f,    0.0f, 0.0f,    1.0f, 0.0f};constexpr auto VERTEX_SHADER = R"(attribute vec3 vertexIn;    // xyz顶点坐标
attribute vec2 textureIn;   // xy纹理坐标
varying vec2 textureOut;    // 传递给片段着色器的纹理坐标

void main(void)
{
    gl_Position = vec4(vertexIn, 1.0);// 1.0表示vertexIn是一个顶点位置
    textureOut = textureIn; // 纹理坐标直接传递给片段着色器
})";constexpr auto FRAGMENT_SHADER = R"(#version 330vec3 yuv2rgb(vec3 yuv) {        float r = 1.164 * yuv.x + 1.596 * yuv.z;        float g = 1.164 * yuv.x - 0.392 * yuv.y - 0.813 * yuv.z;        float b = 1.164 * yuv.x + 2.017 * yuv.y;        return vec3(r, g, b);}varying vec2 textureOut;uniform sampler2D textureY; uniform sampler2D textureU; uniform sampler2D textureV; uniform float lodLevel;void main(void) {   vec3 yuv;   vec3 rgb;   // 跟普通纹理采样函数不同的是,这里我们调用的是textureLod,也就是选择不同等级的纹理。    //yuv.x = textureLod(textureY, textureOut, lodLevel).r - 0.063;   //yuv.y = textureLod(textureU, textureOut, lodLevel).r - 0.502;   //yuv.z = textureLod(textureV, textureOut, lodLevel).r - 0.502;   yuv.x = texture(textureY, textureOut).r - 0.063;   yuv.y = texture(textureU, textureOut).r - 0.502;   yuv.z = texture(textureV, textureOut).r - 0.502;   rgb = yuv2rgb(yuv);   gl_FragColor = vec4(rgb, 1); })";COpenGLRenderWidget::COpenGLRenderWidget(QWidget *parent)    : QOpenGLWidget(parent){}COpenGLRenderWidget::~COpenGLRenderWidget(){}void COpenGLRenderWidget::OnFrame(const AVFrame * frame, bool isHardwareFrame){    if (frame->width != m_pFrame->width || frame->height != m_pFrame->height)    {      m_bIsNeedUpdate = true;    }    av_frame_unref(m_pFrame);    av_frame_ref(m_pFrame, frame);    update();}void COpenGLRenderWidget::OnInputCodecContext(const AVCodecContext* ctx){}HWND COpenGLRenderWidget::GetWindowHandle() const{    return HWND();}void COpenGLRenderWidget::initializeGL(){    if (!m_pFrame)    {      m_pFrame = av_frame_alloc();    }    initializeOpenGLFunctions();    glDisable(GL_DEPTH_TEST);    m_vbo.create();    m_vbo.bind();    m_vbo.allocate(coordinate, sizeof(coordinate));    InitShaders();    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);    glClear(GL_COLOR_BUFFER_BIT);}void COpenGLRenderWidget::paintGL(){    m_shaderProgram.bind();    if (m_bIsNeedUpdate)    {      DeinitTextures();      InitTextures();      m_bIsNeedUpdate = false;    }    if (m_textures.size() != 3)    {      return;    }    glActiveTexture(GL_TEXTURE0);    glBindTexture(GL_TEXTURE_2D, m_textures);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame->linesize, m_pFrame->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame->data);    // U纹理    glActiveTexture(GL_TEXTURE1);    glBindTexture(GL_TEXTURE_2D, m_textures);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame->linesize, m_pFrame->height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame->data);    // V纹理    glActiveTexture(GL_TEXTURE2);    glBindTexture(GL_TEXTURE_2D, m_textures);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame->linesize, m_pFrame->height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame->data);    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);    m_shaderProgram.release();}void COpenGLRenderWidget::resizeGL(int w, int h){    glViewport(0, 0, w, h);    update();}void COpenGLRenderWidget::InitShaders(){    QOpenGLShader vertexShader(QOpenGLShader::Vertex);    if (!vertexShader.compileSourceCode(VERTEX_SHADER))    {      qDebug() height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);    glBindTexture(GL_TEXTURE_2D, m_textures);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame->linesize, m_pFrame->height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);}void COpenGLRenderWidget::DeinitTextures(){    if (m_textures.size() != 3)    {      return;    }    glDeleteTextures(3, m_textures.data());    m_textures.clear();}更高效率

虽然我们将画面渲染通过 OpenGL 来实现 GPU 绘制,但是,将纹理从内存拷贝的 GPU 的显存,同样是需要消耗 CPU 的。同样,FFmpeg 的解码也是需要消耗 CPU 的。那有没有一种方法能够将所有的工作都交给 GPU 呢?答案当然是有的,敬请期待接下来的内容:

[*]FFmpeg 硬解码
[*]DXVA2+D3D9 实现零拷贝渲染

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: OpenGL渲染YUV实战:GPU加速转换与MipMap模糊效果实现