YUV420p in OpenGL

Hello,

I’m trying to display YUV420p video using OpenGL. I understand how it is supposed to work (uploading Y, U, and then V buffers in separate textures while providing them to the fragment shader that converts it to RGB). Unfortunately, it’s not working for me.

So this is what I’m doing… Shader stuff:

GLuint shaders[3];
shaders[0] = my_load_shader_from_file("./shaders/vertexShader.vert", GL_VERTEX_SHADER);
shaders[1] = my_load_shader_from_file("./shaders/fragmentShader.frag", GL_FRAGMENT_SHADER);
shaders[2] = my_load_shader_from_file("./shaders/yuv.frag", GL_FRAGMENT_SHADER);

GLuint rgbShader = my_create_program(shaders, 0, 1);
GLuint yuvShader = my_create_program(shaders, 0, 2);

GLuint RGBposLoc = my_get_attrib_location(rgbShader, "aPos");
GLuint RGBtexLoc = my_get_attrib_location(rgbShader, "aTexCoord");

GLuint YUVposLoc = my_get_attrib_location(yuvShader, "aPos");
GLuint YUVtexLoc = my_get_attrib_location(yuvShader, "aTexCoord");

GLuint yLoc = glGetUniformLocation(yuvShader, "textureY");
GLuint uLoc = glGetUniformLocation(yuvShader, "textureU");
GLuint vLoc = glGetUniformLocation(yuvShader, "textureV");

My fragment shader looks like this:

#version 330 core
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D textureY;
uniform sampler2D textureU;
uniform sampler2D textureV;

void main()
{
    vec3 rgb;
    vec3 yuv;
    yuv.x=texture2D(textureY, TexCoord).r;
    yuv.y=texture2D(textureU, TexCoord).r - 0.5;
    yuv.z=texture2D(textureV, TexCoord).r - 0.5;
    
    rgb = mat3( 1,           1,        1,
                0,   -0.337633, 1.732446,
                1.370705,   -0.698001, 0)*yuv;
    FragColor = vec4(rgb, 1.0);
}

Textures initialized like so:

GLuint *textures = my_create_textures(5);

my_bind_texture(textures[0]);
my_generate_texture_from_buffer(GL_TEXTURE_2D, GL_LUMINANCE, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL);
my_bind_texture(textures[1]);
my_generate_texture_from_buffer(GL_TEXTURE_2D, GL_LUMINANCE, width/2, height/2, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL);
my_bind_texture(textures[2]);
my_generate_texture_from_buffer(GL_TEXTURE_2D, GL_LUMINANCE, width/2, height/2, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL);

and uniforms:

glUniform1i(yLoc, textures[0]);
glUniform1i(uLoc, textures[1]);
glUniform1i(vLoc, textures[2]);

Then in render loop:

my_use_program(yuvShader);
my_bind_texture(textures[0]);
my_generate_texture_from_buffer(GL_TEXTURE_2D, GL_LUMINANCE, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, yBuffer);
my_bind_texture(textures[1]);
my_generate_texture_from_buffer(GL_TEXTURE_2D, GL_LUMINANCE, width/2, height/2, GL_LUMINANCE, GL_UNSIGNED_BYTE, vBuffer);
my_bind_texture(textures[2]);
my_generate_texture_from_buffer(GL_TEXTURE_2D, GL_LUMINANCE, width/2, height/2, GL_LUMINANCE, GL_UNSIGNED_BYTE, uBuffer);
my_bind_vertex_object_and_draw_it(VAOs[0], GL_TRIANGLES, 6);

Binding to target: GL_TEXTURE_2D

Getting Y, U, and V buffers in the render loop like this:

uint8_t *yuv_buffer = malloc(yuv_size);
memcpy(yuv_buffer, inputBuffer + (i++ * yuv_size), yuv_size);
memcpy(yBuffer, yuv_buffer, width*height);
memcpy(uBuffer, yuv_buffer + width*height, width*height/4);
memcpy(vBuffer, yuv_buffer + width*height + width*height/4, width*height/4);
free(yuv_buffer);

Where “inputBuffer” is partially read yuv video
VAOs, VBOs and EBOs like so:

GLuint *VAOs = my_generate_VAOs(2);
GLuint *VBOs = my_generate_VBOs(2);
GLuint *EBOs = my_generate_EBOs(2);

my_bind_VAOs(VAOs[0]);
my_bind_buffer_set_data(GL_ARRAY_BUFFER, VBOs[0], sizeof(vertices), vertices, GL_STATIC_DRAW);
my_bind_buffer_set_data(GL_ELEMENT_ARRAY_BUFFER, EBOs[0], sizeof(indices), indices, GL_STATIC_DRAW);
my_enable_vertex_attrib_array(RGBposLoc, 3, GL_FLOAT, 5 * sizeof(float), (float *)0);
my_enable_vertex_attrib_array(RGBtexLoc, 2, GL_FLOAT, 5 * sizeof(float), (void *)(3 * sizeof(float)));

my_bind_VAOs(VAOs[1]);
my_bind_buffer_set_data(GL_ARRAY_BUFFER, VBOs[1], sizeof(verticesRGB), verticesRGB, GL_STATIC_DRAW);
my_bind_buffer_set_data(GL_ELEMENT_ARRAY_BUFFER, EBOs[1], sizeof(indices), indices, GL_STATIC_DRAW);
my_enable_vertex_attrib_array(YUVposLoc, 3, GL_FLOAT, 5 * sizeof(float), (float *)0);
my_enable_vertex_attrib_array(YUVtexLoc, 2, GL_FLOAT, 5 * sizeof(float), (void *)(3 * sizeof(float)));

I’m also doing yuv2rgb conversion just to see that stream “out-of-the-box” in OpenGL. And that works perfectly.

What am I doing so wrong?
Thank You for Your help!