I have 10-bit YUV (V210) video frames coming in from a capture card, and I would like to unpack this data inside of a GLSL shader and ultimately convert to RGB for screen output. I'm using a Quadro 4000 card on Linux (OpenGL 4.3).
I am uploading the texture with the following settings:
video frame: 720x486 pixels
physically occupies 933120 bytes in 128-byte aligned memory (stride of 1920)
texture is currently uploaded as 480x486 pixels (stride/4 x height) since this matches the byte count of the data
internalFormat of GL_RGB10_A2
format of GL_RGBA
type of GL_UNSIGNED_INT_2_10_10_10_REV
filtering is currently set to GL_NEAREST
Here is the upload command for clarity:
int stride = ((m_videoWidth + 47) / 48) * 128;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, stride / 4, m_videoHeight, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, bytes);
The data itself is packed like so:
U Y V A | Y U Y A | V Y U A | Y V Y A
Or see Blackmagic's illustration here: http://i.imgur.com/PtXBJbS.png
Each texel is 32-bits total (10 bits each for "R,G,B" channels and 2 bits for alpha). Where it gets complicated is that 6 pixels are packed into this block of 128 bits. These blocks simply repeat the above pattern until the end of the frame.
I know that the components of each texel can be accessed with texture2D(tex, coord).rgb but since the order is not the same for every texel (e.g. UYV vs YUY), I know that the texture coordinates must be manipulated to account for that.
However, I'm not sure how to deal with the fact that there are simply more pixels packed into this texture than the GL knows about, which I believe means that I have to account for scaling up/down as well as min/mag filtering (I need bilinear) internally in my shader. The output window needs to be able to be any size (smaller, same or larger than the texture) so the shader should not have any constants related to that.
How can I accomplish this?
Here is the completed shader with all channels and RGB conversion (no filtering is performed however):
#version 130
#extension GL_EXT_gpu_shader4 : enable
in vec2 texcoord;
uniform mediump sampler2D tex;
out mediump vec4 color;
// YUV offset
const vec3 yuvOffset = vec3(-0.0625, -0.5, -0.5);
// RGB coefficients
// BT.601 colorspace
const vec3 Rcoeff = vec3(1.1643, 0.000, 1.5958);
const vec3 Gcoeff = vec3(1.1643, -0.39173, -0.81290);
const vec3 Bcoeff = vec3(1.1643, 2.017, 0.000);
// U Y V A | Y U Y A | V Y U A | Y V Y A
int GROUP_FOR_INDEX(int i) {
return i / 4;
}
int SUBINDEX_FOR_INDEX(int i) {
return i % 4;
}
int _y(int i) {
return 2 * i + 1;
}
int _u(int i) {
return 4 * (i/2);
}
int _v(int i) {
return 4 * (i / 2) + 2;
}
int offset(int i) {
return i + (i / 3);
}
vec3 ycbcr2rgb(vec3 yuvToConvert) {
vec3 pix;
yuvToConvert += yuvOffset;
pix.r = dot(yuvToConvert, Rcoeff);
pix.g = dot(yuvToConvert, Gcoeff);
pix.b = dot(yuvToConvert, Bcoeff);
return pix;
}
void main(void) {
ivec2 size = textureSize2D(tex, 0).xy; // 480x486
ivec2 sizeOrig = ivec2(size.x * 1.5, size.y); // 720x486
// interpolate 0,0 -> 1,1 texcoords to 0,0 -> 720,486
ivec2 texcoordDenorm = ivec2(texcoord * sizeOrig);
// 0 1 1 2 3 3 4 5 5 6 7 7 etc.
int yOffset = offset(_y(texcoordDenorm.x));
int sourceColumnIndexY = GROUP_FOR_INDEX(yOffset);
// 0 0 1 1 2 2 4 4 5 5 6 6 etc.
int uOffset = offset(_u(texcoordDenorm.x));
int sourceColumnIndexU = GROUP_FOR_INDEX(uOffset);
// 0 0 2 2 3 3 4 4 6 6 7 7 etc.
int vOffset = offset(_v(texcoordDenorm.x));
int sourceColumnIndexV = GROUP_FOR_INDEX(vOffset);
// 1 0 2 1 0 2 1 0 2 etc.
int compY = SUBINDEX_FOR_INDEX(yOffset);
// 0 0 1 1 2 2 0 0 1 1 2 2 etc.
int compU = SUBINDEX_FOR_INDEX(uOffset);
// 2 2 0 0 1 1 2 2 0 0 1 1 etc.
int compV = SUBINDEX_FOR_INDEX(vOffset);
vec4 y = texelFetch(tex, ivec2(sourceColumnIndexY, texcoordDenorm.y), 0);
vec4 u = texelFetch(tex, ivec2(sourceColumnIndexU, texcoordDenorm.y), 0);
vec4 v = texelFetch(tex, ivec2(sourceColumnIndexV, texcoordDenorm.y), 0);
vec3 outColor = ycbcr2rgb(vec3(y[compY], u[compU], v[compV]));
color = vec4(outColor, 1.0);
}
If the image is going to be scaled up on the screen then you will likely want to do bilinear filtering, but this will need to be performed within the shader.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With