Use a compute shader to convert RGB to planar YUV, then downsample the UV plane by a factor of two.
Here's the compute shader:
layout(local_size_x = 32, local_size_y = 32) in;
layout(binding = 0) uniform sampler2D src;
layout(binding = 0) uniform writeonly image2D dst_y;
layout(binding = 1) uniform writeonly image2D dst_uv;
void main() {
ivec2 id = ivec2(gl_GlobalInvocationID.xy);
vec3 yuv = rgb_to_yuv(texelFetch(src, id).rgb);
imageStore(dst_y, id, vec4(yuv.x,0,0,0));
imageStore(dst_uv, id, vec4(yuv.yz,0,0));
}
There's lots of different YUV conventions, and I don't know which one is expected by your encoder. So replace rgb_to_yuv
above with the inverse of your YUV -> RGB convertion.
Then proceed as follows:
GLuint in_rgb = ...;
int width = ..., height = ...;
GLuint tex[2];
glCreateTextures(GL_TEXTURE_2D, tex, 2);
glTextureStorage2D(tex[0], 1, GL_R8, width, height);
glTextureStorage2D(tex[1], 2, GL_RG8, width, height);
glBindTextures(0, 1, &in_rgb);
glBindImageTextures(0, 2, tex);
glUseProgram(compute);
int wgs[3];
glGetProgramiv(compute, GL_COMPUTE_WORK_GROUP_SIZE, wgs);
glDispatchCompute(width/wgs[0], height/wgs[1], 1);
glUseProgram(0);
glGenerateTextureMipmap(tex[1]);
uint8_t *data = (uint8_t*)malloc(width*height*3/2);
glGetTextureImage(tex[0], 0, GL_RED, GL_UNSIGNED_BYTE, width*height, data);
glGetTextureImage(tex[1], 1, GL_RG, GL_UNSIGNED_BYTE, width*height/2,
data + width*height);
DISCLAIMER:
- This code is untested.
- It assumes that width and height are divisible by 32.
- It might be missing a memory barrier somewhere.
- It's not the most efficient way to read data out of the GPU -- you might need to at least read one frame behind while the next one is calculated.