Skip to content

Instantly share code, notes, and snippets.

@roxlu
Created July 2, 2013 08:36
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save roxlu/5907666 to your computer and use it in GitHub Desktop.
Save roxlu/5907666 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
@JPGygax68
Copy link

Thanks Roxlu, I have used this as a starting point for my NV12 encoder, and it has saved me a tremendous amount of time and effort. Two things:

  1. OpenGL apparently does not support having different resolutions on its textures. I found this out the hard way because my network connection was down :-), but once it was back up, this stackoverflow answer confirmed my suspicion. It was easy enough to fix, just use separate framebuffer objects and you're good again.
  2. Since I've used your code for a paid project, I'd like to make a small donation for the help you've provided ($25). Do you have an account at https://pledgie.com ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment