使用PBO对比glTexImage2d函数可以提升纹理更新效率,因为glTexImage2d更新纹理需要把内存数据复制到显存,而这完全由CPU进行操作:
但使用PBO则可以让显卡驱动利用DMA通道,绕开CPU直接从指定的内存位置拷贝数据:
使用两个PBO则可以进行异步更新进一步提升效能,例如一个在写的同时另外一个可以读:
但PBO也仅仅是通过DMA提升纹理更新效率,因此纹理还是要创建的,我们先创建Y通道和UV通道两个只占用显存空间但内存全为0的纹理。
双PBO + 双FBO架构如下:
使用此方式可以把YUV转换后的数据离屏渲染到FBO中,作为一整个纹理可以灵活地使用卷积核、滤镜shader等实现降噪、抖音特效等画面的二次处理。
因为Y通道和UV通道的处理方式不一样,因此创建两个空纹理,Y通道一个,UV通道一个:
private void startBindTexture() {
createEmptyTexture(mGenYTextureId, mImgWidth, mImgHeight, GLES30.GL_LUMINANCE);
createEmptyTexture(mGenYTextureId, mImgWidth, mImgHeight, GLES30.GL_LUMINANCE);
}
private void createEmptyTexture(int textureID, int imgWidth, int imgHeight, int pixelFormat) {
GLES30.glGenTextures(1, new int[] {textureID}, 0); //只要值不重复即可
//UV纹理初始化
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureID);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
//创建一个占用指定空间的纹理,但暂时不复制数据进去,等PBO进行数据传输,取代glTexImage2D,利用DMA提高数据拷贝速度
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, pixelFormat, imgWidth, imgHeight, 0, pixelFormat, GLES30.GL_UNSIGNED_BYTE, null); //因为这里使用了双字节,所以纹理大小对比使用单字节的Y通道纹理,宽度首先要缩小一般,而uv层高度本来就只有y层一般,所以高度也除以2
}
其中Y通道使用单字节方式读写,也就是GL_LUMINANCE或者GL_ALPHA,UV通道为两个字节为一组,所以更适合使用两字节的模式,也就是GL_LUMINANCE_ALPHA格式,但相对地,纹理宽度就变成了纯灰度格式的一半,YUV的压缩方式也决定了每2个Y对应一个UV,因此UV层宽度也得除以2,所以,UV纹理的长宽便时mImgWidth / 2, mImgHeight / 2。
创建PBO:
/**
* 创建2个framebuffer作为每次渲染结果的叠加专用纹理
**/
private void createPBO() {
//创建Y通道PBO
mImgPanelYByteSize = mImgWidth * mImgHeight;
mYPanelPixelBuffferPointerArray = new int[2];
GLES30.glGenBuffers(2, mYPanelPixelBuffferPointerArray, 0);
GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mYPanelPixelBuffferPointerArray[0]);
GLES30.glBufferData(GLES30.GL_PIXEL_UNPACK_BUFFER, mImgPanelYByteSize, null, GLES30.GL_STREAM_DRAW);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mYPanelPixelBuffferPointerArray[1]);
GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER, mImgPanelYByteSize, null, GLES30.GL_STREAM_DRAW);
//创建UV通道PBO
mImgPanelUVByteSize = mImgWidth * mImgHeight / 2;
mUVPanelPixelBuffferPointerArray = new int[2];
GLES30.glGenBuffers(2, mUVPanelPixelBuffferPointerArray, 0);
GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mUVPanelPixelBuffferPointerArray[0]);
GLES30.glBufferData(GLES30.GL_PIXEL_UNPACK_BUFFER, mImgPanelUVByteSize, null, GLES30.GL_STREAM_DRAW);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mUVPanelPixelBuffferPointerArray[1]);
GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER, mImgPanelUVByteSize, null, GLES30.GL_STREAM_DRAW);
}
此时PBO已创建完成。现在我们来完成纹理数据更新的方法吧:
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public void refreshBuffer(byte[] imgBytes) {
//更新ypanel数据
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mGenYTextureId);
GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "textureY"), 0); //获取纹理属性的指针
GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mYPanelPixelBuffferPointerArray[mFrameCount % 2]);
GLES30.glTexSubImage2D(GLES30.GL_TEXTURE_2D, 0, 0, 0, mImgWidth, mImgHeight, GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, null); //1字节为一个单位
//更新图像数据,复制到 PBO 中
GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mYPanelPixelBuffferPointerArray[(mFrameCount + 1) % 2]);
GLES30.glBufferData(GLES30.GL_PIXEL_UNPACK_BUFFER, mImgPanelYByteSize, null, GLES30.GL_STREAM_DRAW);
Buffer buf = GLES30.glMapBufferRange(GLES30.GL_PIXEL_UNPACK_BUFFER, 0, mImgPanelYByteSize, GLES30.GL_MAP_WRITE_BIT | GLES30.GL_MAP_INVALIDATE_BUFFER_BIT);
//填充像素
ByteBuffer bytebuffer = ((ByteBuffer) buf).order(ByteOrder.nativeOrder());
bytebuffer.position(0);
bytebuffer.put(imgBytes, 0, mImgWidth * mImgHeight);
bytebuffer.position(0);
GLES30.glUnmapBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER);
GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, 0);
//更新uvpanel数据
GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mGenUVTextureId);
GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "textureUV"), 1); //获取纹理属性的指针
GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mUVPanelPixelBuffferPointerArray[mFrameCount % 2]);
GLES30.glTexSubImage2D(GLES30.GL_TEXTURE_2D, 0, 0, 0, mImgWidth / 2, mImgHeight / 2, GLES30.GL_LUMINANCE_ALPHA, GLES30.GL_UNSIGNED_BYTE, null); //2字节为一个单位,所以宽度因为单位为2字节一个,对比1字节时直接对半
//更新图像数据,复制到 PBO 中
GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, mUVPanelPixelBuffferPointerArray[(mFrameCount + 1) % 2]);
GLES30.glBufferData(GLES30.GL_PIXEL_UNPACK_BUFFER, mImgPanelUVByteSize, null, GLES30.GL_STREAM_DRAW);
buf = GLES30.glMapBufferRange(GLES30.GL_PIXEL_UNPACK_BUFFER, 0, mImgPanelUVByteSize, GLES30.GL_MAP_WRITE_BIT | GLES30.GL_MAP_INVALIDATE_BUFFER_BIT);
//填充像素
bytebuffer = ((ByteBuffer) buf).order(ByteOrder.nativeOrder());
bytebuffer.position(0);
bytebuffer.put(imgBytes, mImgWidth * mImgHeight, mImgWidth * mImgHeight / 2);
bytebuffer.position(0);
GLES30.glUnmapBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER);
GLES30.glBindBuffer(GLES30.GL_PIXEL_UNPACK_BUFFER, 0);
}
原理就是把yuv数组分段利用PBO写到对应的纹理中。
编写shader,实现YUV420转换为RGBA:
//https://blog.csdn.net/byhook/article/details/84037338 有各种YUV的排列方式
/**yuvtorgb
R = Y + 1.4075 *(V-128)
G = Y – 0.3455 *(U –128) – 0.7169 *(V –128)
B = Y + 1.779 *(U – 128)
**/
vec3 yuvToRGB(float y, float u, float v) {
float r = y + 1.370705 * (v - 0.5);
float g = y - 0.337633 * (u - 0.5) - 0.698001 * (v - 0.5);
float b = y + 1.732446 * (u - 0.5);
// r = clamp(r, 0.0, 1.0);
// g = clamp(g, 0.0, 1.0);
// b = clamp(b, 0.0, 1.0);
return vec3(r, g, b);
}
void convertYUV420SP(bool reverse, in vec2 fragVTexCoord, out vec4 fragColor){
float y = texture(textureY, fragVTexCoord)[0];
//uvuvuvuv
float u = texture(textureUV, fragVTexCoord)[3];
float v = texture(textureUV, fragVTexCoord)[0];
vec3 rgb;
if (reverse) {
rgb = yuvToRGB(y, v, u);//NV21
} else {
rgb = yuvToRGB(y, u, v);//NV12
}
fragColor = vec4(rgb, 1.0);
}
创建两个FBO,刚刚YUV转换为RGBA后的画面可以渲染到这里:
/**
* 创建2个framebuffer保存每次渲染结果
**/
private void createDoubleFrameBuffer() {
int frameBufferCount = 2;
//生成framebuffer
mFrameBufferPointerArray = new int[frameBufferCount];
GLES30.glGenFramebuffers(mFrameBufferPointerArray.length, mFrameBufferPointerArray, 0);
//生成渲染缓冲buffer
mRenderBufferPointerArray = new int[frameBufferCount];
GLES30.glGenRenderbuffers(mRenderBufferPointerArray.length, mRenderBufferPointerArray, 0);
//生成framebuffer纹理pointer
mFrameBufferTexturePointerArray = new int[frameBufferCount];
GLES30.glGenTextures(mFrameBufferTexturePointerArray.length, mFrameBufferTexturePointerArray, 0);
//遍历framebuffer并初始化
for (int i = 0; i < frameBufferCount; i++) {
//绑定帧缓冲,遍历两个framebuffer分别初始化
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferPointerArray[i]);
//绑定缓冲pointer
GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER, mRenderBufferPointerArray[i]);
//为渲染缓冲初始化存储,分配显存
GLES30.glRenderbufferStorage(GLES30.GL_RENDERBUFFER,
GLES30.GL_DEPTH_COMPONENT16, mFrameBufferWidth, mFrameBufferHeight); //设置framebuffer的长宽
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mFrameBufferTexturePointerArray[i]); //绑定纹理Pointer
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,//设置MIN采样方式
GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,//设置MAG采样方式
GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,//设置S轴拉伸方式
GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,//设置T轴拉伸方式
GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexImage2D//设置颜色附件纹理图的格式
(
GLES30.GL_TEXTURE_2D,
0, //层次
GLES30.GL_RGBA, //内部格式
mFrameBufferWidth, //宽度
mFrameBufferHeight, //高度
0, //边界宽度
GLES30.GL_RGBA, //格式
GLES30.GL_UNSIGNED_BYTE,//每个像素数据格式
null
);
GLES30.glFramebufferTexture2D //设置自定义帧缓冲的颜色缓冲附件
(
GLES30.GL_FRAMEBUFFER,
GLES30.GL_COLOR_ATTACHMENT0, //颜色缓冲附件
GLES30.GL_TEXTURE_2D,
mFrameBufferTexturePointerArray[i], //纹理id
0 //层次
);
GLES30.glFramebufferRenderbuffer //设置自定义帧缓冲的深度缓冲附件
(
GLES30.GL_FRAMEBUFFER,
GLES30.GL_DEPTH_ATTACHMENT, //深度缓冲附件
GLES30.GL_RENDERBUFFER, //渲染缓冲
mRenderBufferPointerArray[i] //渲染深度缓冲id
);
}
//绑回系统默认framebuffer,否则会显示不出东西
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);//绑定帧缓冲id
}
绑定Y通道纹理和UV通道纹理,绘制纹理到FBO中,绘制的同时YUV会被转换为色彩正常的RGBA颜色:
/**绘制画面到framebuffer**/
private void drawToFrameBuffer(float[] cameraMatrix, float[] projMatrix) {
if (mIsDestroyed) {
return;
}
GLES30.glUseProgram(mYuvBufferDrawProgram);
//设置视窗大小及位置
GLES30.glViewport(0, 0, mFrameBufferWidth, mFrameBufferHeight);
//绑定帧缓冲id
if (mFrameCount % 2 == 0) {
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferPointerArray[0]);
} else {
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferPointerArray[1]);
}
//清除深度缓冲与颜色缓冲
if (!mFrameBufferClean && !mFrameBufferCleanOnce) { //实现渲染画面叠加
GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
mFrameBufferCleanOnce = true;
}
if (mFrameBufferClean) {
GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
GLES30.glUniform1i(mGLFrameBufferProgramFunChoicePointer, -1); //第一次加载选择纹理方式渲染
}
//设置它的坐标系
locationTrans(cameraMatrix, projMatrix, this.mGLFrameuMVPMatrixPointer);
//设置图像分辨率
GLES30.glUniform2fv(mResoulutionPointer, 1, new float[]{mWindowW, mWindowH}, 0);
locationTrans(cameraMatrix, projMatrix, this.mGLFrameuMVPMatrixPointer);
if (mPointBuf != null && mColorBuf != null) {
if (mFrameCount < 0) {
mFrameCount = 0;
}
mPointBuf.position(0);
mColorBuf.position(0);
// GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "sTexture"), 0); //获取纹理属性的指针
//将顶点位置数据送入渲染管线
GLES30.glVertexAttribPointer(mGLFrameObjectPositionPointer, 3, GLES30.GL_FLOAT, false, 0, mPointBuf); //三维向量,size为2
//将顶点颜色数据送入渲染管线
GLES30.glVertexAttribPointer(mGLFrameObjectVertColorArrayPointer, 4, GLES30.GL_FLOAT, false, 0, mColorBuf);
//将顶点纹理坐标数据传送进渲染管线
GLES30.glVertexAttribPointer(mGLFrameVTexCoordPointer, 2, GLES30.GL_FLOAT, false, 0, mTexCoorBuffer); //二维向量,size为2
GLES30.glEnableVertexAttribArray(mGLFrameObjectPositionPointer); //启用顶点属性
GLES30.glEnableVertexAttribArray(mGLFrameObjectVertColorArrayPointer); //启用颜色属性
GLES30.glEnableVertexAttribArray(mGLFrameVTexCoordPointer); //启用纹理采样定位坐标
// GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
//绘制yuv
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mGenYTextureId);
GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "textureY"), 0); //获取纹理属性的指针
GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mGenUVTextureId);
GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "textureUV"), 1); //获取纹理属性的指针
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mPointBufferPos / 3); //绘制线条,添加的point浮点数/3才是坐标数(因为一个坐标由x,y,z3个float构成,不能直接用)
GLES30.glDisableVertexAttribArray(mGLFrameObjectPositionPointer);
GLES30.glDisableVertexAttribArray(mGLFrameObjectVertColorArrayPointer);
GLES30.glDisableVertexAttribArray(mGLFrameVTexCoordPointer);
}
//绑会系统默认framebuffer,否则会显示不出东西
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);//绑定帧缓冲id
}
此时画面已离屏渲染到FBO中,现在我们把FBO画面在shader中进行边缘锐化卷积操作,并最后绑定当前窗口的framebuffer——也就是0号framebuffer,然后纹理进行bindTexuter后,显示出来。
shader处理程序(resolution为屏幕分辨率uniform变量):
void kernalEffect(vec2 TexCoords)
{
float offset = 1.0 / resolution.y;
vec2 offsets[9] = vec2[](
vec2(-offset, offset), // 左上
vec2(0.0, offset), // 正上
vec2(offset, offset), // 右上
vec2(-offset, 0.0), // 左
vec2(0.0, 0.0), // 中
vec2(offset, 0.0), // 右
vec2(-offset, -offset), // 左下
vec2(0.0, -offset), // 正下
vec2(offset, -offset)// 右下
);
vec3 sampleTex[9];
for (int i = 0; i < 9; i++)
{
sampleTex[i] = vec3(texture(textureFBO, TexCoords.st + offsets[i]));
}
vec3 col = vec3(0.0);
for (int i = 0; i < 9; i++)
col += sampleTex[i] * kernel[i];
fragColor = vec4(col, 1.0);
}
绘制FBO到窗口的代码:
@Override
public void drawTo(float[] cameraMatrix, float[] projMatrix) {
if (mIsDestroyed) {
return;
}
locationTrans(cameraMatrix, projMatrix, mGLFrameuMVPMatrixPointer);
//先绘制内容到frambuffer
drawToFrameBuffer(cameraMatrix, projMatrix);
//绘制
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);//绑定帧系统默认缓冲id
locationTrans(cameraMatrix, projMatrix, mGLFrameuMVPMatrixPointer);
if (mPointBuf != null && mColorBuf != null) {
Log.i("cjztest", "drawFramebuffer");
GLES30.glUniform1i(mGLFrameBufferProgramFunChoicePointer, -1); //选择纹理方式渲染
mPointBuf.position(0);
mColorBuf.position(0);
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
//切换纹理到当前正在绘制的framebuffer
if (mFrameCount % 2 == 1) { //另外一个当前不需要投上屏幕的FBO可以用于输出处理结果。
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mFrameBufferTexturePointerArray[0]);
} else {
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mFrameBufferTexturePointerArray[1]);
}
GLES30.glUniform1i(GLES30.glGetUniformLocation(mYuvBufferDrawProgram, "textureFBO"), 0); //获取纹理属性的指针
//将顶点位置数据送入渲染管线
GLES30.glVertexAttribPointer(mGLFrameObjectPositionPointer, 3, GLES30.GL_FLOAT, false, 0, mPointBuf); //三维向量,size为2
//将顶点颜色数据送入渲染管线
GLES30.glVertexAttribPointer(mGLFrameObjectVertColorArrayPointer, 4, GLES30.GL_FLOAT, false, 0, mColorBuf);
//将顶点纹理坐标数据传送进渲染管线
GLES30.glVertexAttribPointer(mGLFrameVTexCoordPointer, 2, GLES30.GL_FLOAT, false, 0, mTexCoorBuffer); //二维向量,size为2
GLES30.glEnableVertexAttribArray(mGLFrameObjectPositionPointer); //启用顶点属性
GLES30.glEnableVertexAttribArray(mGLFrameObjectVertColorArrayPointer); //启用颜色属性
GLES30.glEnableVertexAttribArray(mGLFrameVTexCoordPointer); //启用纹理采样定位坐标
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mPointBufferPos / 3); //绘制线条,添加的point浮点数/3才是坐标数(因为一个坐标由x,y,z3个float构成,不能直接用)
GLES30.glDisableVertexAttribArray(mGLFrameObjectPositionPointer);
GLES30.glDisableVertexAttribArray(mGLFrameObjectVertColorArrayPointer);
GLES30.glDisableVertexAttribArray(mGLFrameVTexCoordPointer);
}
GLES30.glUniform1i(mFrameCountPointer, mFrameCount++);
}
最后效果如下: