本文介绍如何采用 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 数据分别拷贝到对应的纹理。
cpp
// 假设有一个AVFrame对象名为 m_pFrame
// Y纹理
glBindTexture(GL_TEXTURE_2D, m_texture[0]);
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[0], m_frameSize.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data[0]);
// U纹理
glBindTexture(GL_TEXTURE_2D, m_texture[1]);
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[1], m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data[1]);
// V纹理
glBindTexture(GL_TEXTURE_2D, m_texture[2]);
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[2], m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data[2]);
然后在片段着色器中将三个纹理合成。
glsl
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; // 纹理坐标直接传递给片段着色器
}
glsl
#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
设置不同的数值。
着色器代码如下:
glsl
#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_
:指定采样级别。
代码如下:
cpp
// 假设有一个AVFrame对象名为 m_pFrame
// Y纹理
glBindTexture(GL_TEXTURE_2D, m_texture[0]);
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[0], m_frameSize.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data[0]);
// 生成mipmap
glGenerateMipmap(m_texture[0]);
// U纹理
glBindTexture(GL_TEXTURE_2D, m_texture[1]);
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[1], m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data[1]);
// 生成mipmap
glGenerateMipmap(m_texture[1]);
// V纹理
glBindTexture(GL_TEXTURE_2D, m_texture[2]);
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[2]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_pFrame.linesize[2], m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame.data[2]);
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 版本和当前渲染器名称时:
cpp
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 了:
glsl
#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。
完整代码
cpp
#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;
};
cpp
#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,1],左下角为 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 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;
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[0]);
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[0], m_pFrame->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame->data[0]);
// U纹理
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_textures[1]);
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[1], m_pFrame->height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame->data[1]);
// V纹理
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, m_textures[2]);
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[2], m_pFrame->height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pFrame->data[2]);
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() << "Vertex shader compilation failed. Error: " << vertexShader.log();
return;
}
QOpenGLShader fragmentShader(QOpenGLShader::Fragment);
if (!fragmentShader.compileSourceCode(FRAGMENT_SHADER))
{
qDebug() << "Fragment shader compilation failed. Error: " << fragmentShader.log();
return;
}
m_shaderProgram.addShader(&vertexShader);
m_shaderProgram.addShader(&fragmentShader);
m_shaderProgram.link();
m_shaderProgram.bind();
m_shaderProgram.setAttributeBuffer("vertexIn", GL_FLOAT, 0, 3, 3 * sizeof(float));
m_shaderProgram.enableAttributeArray("vertexIn");
m_shaderProgram.setAttributeBuffer("textureIn", GL_FLOAT, 12 * sizeof(float), 2, 2 * sizeof(float));
m_shaderProgram.enableAttributeArray("textureIn");
m_shaderProgram.setUniformValue("textureY", 0);
m_shaderProgram.setUniformValue("textureU", 1);
m_shaderProgram.setUniformValue("textureV", 2);
}
void COpenGLRenderWidget::InitTextures()
{
m_textures.resize(3);
glGenTextures(3, m_textures.data());
glBindTexture(GL_TEXTURE_2D, m_textures[0]);
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[0], m_pFrame->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
glBindTexture(GL_TEXTURE_2D, m_textures[1]);
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[1], m_pFrame->height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
glBindTexture(GL_TEXTURE_2D, m_textures[2]);
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[2], 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 实现零拷贝渲染