Skip to content

Instantly share code, notes, and snippets.

@Volcanoscar
Forked from roxlu/YUV420PGrabber.cpp
Created August 16, 2019 12:31
Show Gist options
  • Save Volcanoscar/4b8d88a4c54ca4d2fbc225d6ccf765ba to your computer and use it in GitHub Desktop.
Save Volcanoscar/4b8d88a4c54ca4d2fbc225d6ccf765ba to your computer and use it in GitHub Desktop.
OpenGL RGB > YUV420P shader/class (doesn't do much more. implementation/usage is up to you)
#include <assert.h>
#include <roxlu/core/Utils.h>
#include <roxlu/core/Log.h>
#include "YUV420PGrabber.h"
YUV420PGrabber::YUV420PGrabber()
:y_prog(0)
,y_vert(0)
,y_frag(0)
,uv_prog(0)
,uv_frag(0)
,scene_fbo(0)
,scene_depth(0)
,scene_tex(0)
,y_tex(0)
,u_tex(0)
,v_tex(0)
,window_w(0)
,window_h(0)
,video_w(0)
,video_h(0)
,uv_w(0)
,uv_h(0)
,vbo(0)
,vao(0)
{
}
YUV420PGrabber::~YUV420PGrabber() {
if(y_vert) {
glDeleteShader(y_vert);
}
if(y_frag) {
glDeleteShader(y_frag);
}
if(uv_frag) {
glDeleteShader(uv_frag);
}
if(y_prog) {
glDeleteProgram(y_prog);
}
if(uv_prog) {
glDeleteProgram(uv_prog);
}
if(scene_fbo) {
glDeleteFramebuffers(1, &scene_fbo);
}
if(scene_depth) {
glDeleteRenderbuffers(1, &scene_depth);
}
if(scene_tex) {
glDeleteTextures(1, &scene_tex);
}
if(y_tex) {
glDeleteTextures(1, &y_tex);
}
if(u_tex) {
glDeleteTextures(1, &u_tex);
}
if(v_tex) {
glDeleteTextures(1, &v_tex);
}
if(vbo) {
glDeleteBuffers(1, &vbo);
}
if(vao) {
glDeleteVertexArrays(1, &vao);
}
window_w = 0;
window_h = 0;
video_w = 0;
video_h = 0;
uv_w = 0;
uv_h = 0;
y_prog = 0;
y_vert = 0;
y_frag = 0;
uv_prog = 0;
uv_frag = 0;
scene_fbo = 0;
scene_depth = 0;
scene_tex = 0;
y_tex = 0;
u_tex = 0;
v_tex = 0;
vbo = 0;
vao = 0;
}
bool YUV420PGrabber::setup(int winW, int winH, int vidW, int vidH) {
assert(winW);
assert(winH);
assert(vidW);
assert(vidH);
window_w = winW;
window_h = winH;
video_w = vidW;
video_h = vidH;
uv_w = video_w * 0.25;
uv_h = video_h * 0.25;
if(!setupFBO()) {
return false;
}
if(!setupShaders()) {
return false;
}
if(!setupBuffers()) {
return false;
}
return true;
}
bool YUV420PGrabber::setupFBO() {
assert(window_h && window_w);
assert(video_w && video_h);
glGenFramebuffers(1, &scene_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, scene_fbo);
glGenRenderbuffers(1, &scene_depth);
glBindRenderbuffer(GL_RENDERBUFFER, scene_depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, window_w, window_h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, scene_depth);
glGenTextures(1, &scene_tex);
glBindTexture(GL_TEXTURE_2D, scene_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, window_w, window_h, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
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);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, scene_tex, 0);
glGenTextures(1, &y_tex);
glBindTexture(GL_TEXTURE_2D, y_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, video_w, video_h, 0, GL_RED, GL_UNSIGNED_BYTE, NULL);
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);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, y_tex, 0);
glGenTextures(1, &u_tex);
glBindTexture(GL_TEXTURE_2D, u_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, uv_w, uv_h, 0, GL_RED, GL_UNSIGNED_BYTE, NULL);
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);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, u_tex, 0);
glGenTextures(1, &v_tex);
glBindTexture(GL_TEXTURE_2D, v_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, uv_w, uv_h, 0, GL_RED, GL_UNSIGNED_BYTE, NULL);
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);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, v_tex, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(status != GL_FRAMEBUFFER_COMPLETE) {
RX_ERROR("Framebuffer is not yet complete");
return false;
}
return true;
}
bool YUV420PGrabber::setupShaders() {
assert(window_w && window_h);
assert(video_w && video_h);
// y - shader
y_vert = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(y_vert, 1, &YUV420P_Y_VS, NULL);
glCompileShader(y_vert); eglGetShaderInfoLog(y_vert);
y_frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(y_frag, 1, &YUV420P_Y_FS, NULL);
glCompileShader(y_frag); eglGetShaderInfoLog(y_frag);
y_prog = glCreateProgram();
glAttachShader(y_prog, y_vert);
glAttachShader(y_prog, y_frag);
glBindAttribLocation(y_prog, 0, "a_pos");
glBindAttribLocation(y_prog, 1, "a_tex");
glLinkProgram(y_prog); eglGetShaderLinkLog(y_prog);
glUseProgram(y_prog);
GLint u_tex = glGetUniformLocation(y_prog, "u_tex");
if(u_tex < 0) {
RX_ERROR("Error while trying to get u_tex.");
return false;
}
glUniform1i(u_tex, 0);
// uv - shader
uv_frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(uv_frag, 1, &YUV420P_UV_FS, NULL);
glCompileShader(uv_frag); eglGetShaderInfoLog(uv_frag);
uv_prog = glCreateProgram();
glAttachShader(uv_prog, y_vert);
glAttachShader(uv_prog, uv_frag);
glBindAttribLocation(uv_prog, 0, "a_pos");
glBindAttribLocation(uv_prog, 1, "a_tex");
glBindFragDataLocation(uv_prog, 0, "u_col");
glBindFragDataLocation(uv_prog, 1, "v_col");
glLinkProgram(uv_prog); eglGetShaderLinkLog(uv_prog);
glUseProgram(uv_prog);
u_tex = glGetUniformLocation(uv_prog, "u_tex");
if(u_tex < 0) {
RX_ERROR("Cannot get u_tex for uv program");
return false;
}
glUniform1i(u_tex, 0);
return true;
}
bool YUV420PGrabber::setupBuffers() {
assert(window_w && window_h);
assert(video_w && video_h);
assert(y_prog && y_vert && y_frag);
GLfloat vertices[] = {
-1.0, -1.0, 0.0, 0.0, // a
1.0, -1.0, 1.0, 0.0, // b
1.0, 1.0, 1.0, 1.0, // c
-1.0, -1.0, 0.0, 0.0,// a
1.0, 1.0, 1.0, 1.0, // c
-1.0, 1.0, 0.0, 1.0 // d
};
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, vertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (GLvoid*)NULL);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (GLvoid*)8);
glEnableVertexAttribArray(1);
return true;
}
void YUV420PGrabber::beginGrab() {
assert(scene_fbo);
assert(window_w);
assert(window_h);
glViewport(0, 0, window_w, window_h);
GLenum bufs[] = { GL_COLOR_ATTACHMENT0 };
glBindFramebuffer(GL_FRAMEBUFFER, scene_fbo);
glDrawBuffers(1, bufs);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void YUV420PGrabber::endGrab() {
assert(scene_fbo);
// render Y
GLenum y_buf[] = { GL_COLOR_ATTACHMENT1 };
glDrawBuffers(1, y_buf);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0,0,video_w, video_h);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, scene_tex);
glBindVertexArray(vao);
glUseProgram(y_prog);
glDrawArrays(GL_TRIANGLES, 0, 6);
// render U + V
GLenum uv_bufs[] = { GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 };
glDrawBuffers(2, uv_bufs);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0,0,uv_w, uv_h);
glUseProgram(uv_prog);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
#ifndef ROXLU_YUV420P_GRABBER_H
#define ROXLU_YUV420P_GRABBER_H
#include <roxlu/opengl/GL.h>
static const char* YUV420P_Y_VS = ""
"#version 150\n"
"in vec4 a_pos;"
"in vec2 a_tex;"
"out vec2 v_tex;"
"void main() {"
" gl_Position = a_pos; "
" v_tex = a_tex;"
"}";
static const char* YUV420P_Y_FS = ""
"#version 150\n"
"uniform sampler2D u_tex;"
"in vec2 v_tex; "
"out float fragcol;"
"float Y(vec3 c) {"
" float result = (0.257 * c.r) + (0.504 * c.g) + (0.098 * c.b) + 0.0625;"
" return result;"
"}"
"void main() {"
" fragcol = Y(texture(u_tex, v_tex).rgb);"
"}";
static const char* YUV420P_UV_FS = ""
"#version 150\n"
"uniform sampler2D u_tex;"
"in vec2 v_tex;"
"out float u_col;"
"out float v_col;"
"float V(vec3 c) {"
" float result = (0.439 * c.r) - (0.368 * c.g) - (0.071 * c.b) + 0.5; "
" return result;"
"}"
"float U(vec3 c) {"
" float result = -(0.148 * c.r) - (0.291 * c.g) + (0.439 * c.b) + 0.5; "
" return result; "
"}"
"void main() {"
" vec3 col = texture(u_tex, v_tex).rgb;"
" u_col = U(col); "
" v_col = V(col); "
"}";
class YUV420PGrabber {
public:
YUV420PGrabber();
~YUV420PGrabber();
bool setup(int windowW, int windowH, int videoW, int videoH);
void beginGrab();
void endGrab();
private:
bool setupFBO();
bool setupShaders();
bool setupBuffers();
public:
int window_w;
int window_h;
int video_w; /* the resulting video width, the y plane has the same width */
int video_h; /* the resulting video height, the y plane has the same height */
int uv_w; /* the width of the u and v planes */
int uv_h; /* the height of the u and v planes */
GLuint y_prog;
GLuint y_vert;
GLuint y_frag;
GLuint uv_prog;
GLuint uv_frag; /* fragment shader for uv pass, we reuse y_vert */
GLuint scene_fbo;
GLuint scene_depth;
GLuint scene_tex;
GLuint y_tex;
GLuint u_tex;
GLuint v_tex;
GLuint vbo;
GLuint vao;
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment