0

I'm attempting to convert camera frames which are in YUV 4.2.2 (NV12) format to YUV 4.2.0 (YV12/i420) format. The U and V channels of NV12 are interleaved, while the YV12 are not.

For performance reasons, I'm trying to do this conversion in an OpenGl ES 2.0 shader.

The shaders are fairly easy. I separate out the Y and the UV channels and then pass the UV to a shader that separates it out into a U channel in gl_FragData[0] and V channel in gl_FragData[1]

Here is the Fragment Shader:

precision mediump float;
uniform sampler2D t_texture_uv;
varying highp vec2 uvVarying;
void main()
{
    /*
    vec2 uvu,uvv;
    int x,y;
    x = uvVarying.x;
    y = uvVarying.y;
    uvu = vec2(x*2,y);
    uvv = vec2((x*2)+1,y);
    gl_FragData[0] = texture2D(t_texture_uv, uvu);
    gl_FragData[1] = texture2D(t_texture_uv, uvv);
     */

    // this is the same as the logic above, but is less processor intensive
    gl_FragData[0] = texture2D(t_texture_uv, vec2( uvVarying.x*2   ,uvVarying.y)); // u-plane
    gl_FragData[1] = texture2D(t_texture_uv, vec2((uvVarying.x*2)+1,uvVarying.y)); // v-plane

}

And the vertex shader:

attribute vec2 position;
attribute vec2 uv;
uniform mat4 proj_matrix;
varying vec2 uvVarying;

void main()
{
    gl_Position = proj_matrix * vec4(vec3(position.xy, 0.0), 1.0);
    uvVarying = uv;
}

My problem is that I'm lost at how to retrieve the data from the Gl_FragData buffers back in my Objective C code. I tried glReadPixels, but it doesn't seem to work.

This is my code, but I think I've gone down the wrong path.

- (void) nv12_to_yv12_converter:(unsigned char*)YBuf UVBuff:(unsigned char*)UVBuf Width:(GLsizei)UVWidth Height:(GLsizei)UVHeight {
    unsigned char *Ubufout;
    unsigned char *Vbufout;

    EAGLContext *context;
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if (!context || ![EAGLContext setCurrentContext:context]) {
        NSLog(@"Failed to create ES context");
    }

    [self loadShaders];

    glGenFramebuffers(1, &_framebuffer);
    glGenRenderbuffers(1, &_renderbuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
    ;
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, UVWidth, UVHeight);
//    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.view.layer];
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderbuffer);


    _vertices[0] = -1.0f;  // x0
    _vertices[1] = -1.0f;  // y0
    _vertices[2] =  1.0f;  // ..
    _vertices[3] = -1.0f;
    _vertices[4] = -1.0f;
    _vertices[5] =  1.0f;
    _vertices[6] =  1.0f;  // x3
    _vertices[7] =  1.0f;  // y3


    int orientation = 0;
    static const GLfloat texCoords[] = {
        0.0f, 1.0f,
        1.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f,
    };

    glViewport(0, 0, _backingWidth, _backingHeight);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(_program);


    if (!validateProgram(_program))
    {
        NSLog(@"Failed to validate program");
        return;
    }

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    glDeleteTextures(1, &_textures[0]);

    glActiveTexture(GL_TEXTURE0);
    glGenTextures(1, &_textures[0]);
    glBindTexture(GL_TEXTURE_2D, _textures[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glEnable(GL_TEXTURE_2D);
    glUniform1i(glGetUniformLocation(_program, "t_texture_uv"), 0);

    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 GL_LUMINANCE,
                 16,
                 8,
                 0,
                 GL_LUMINANCE,
                 GL_UNSIGNED_BYTE,
                 UVBuf);



    _uniformMatrix = glGetUniformLocation(_program, "proj_matrix");
    GLfloat modelviewProj[16];
    mat4f_LoadOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, modelviewProj);
    glUniformMatrix4fv(_uniformMatrix, 1, GL_FALSE, modelviewProj);

    //draw RECT
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, _vertices);
    glEnableVertexAttribArray(ATTRIB_VERTEX);

    //ATTRIB_TEXTUREPOSITON
    glVertexAttribPointer(ATTRIB_UV, 2, GL_FLOAT, 0, 0, texCoords);
    glEnableVertexAttribArray(ATTRIB_UV);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glReadPixels(0, 0, UVWidth, UVHeight, GL_COLOR_ATTACHMENT0, GL_UNSIGNED_BYTE, &Ubufout);
    glReadPixels(0, 0, UVWidth, UVHeight, GL_COLOR_ATTACHMENT0+1, GL_UNSIGNED_BYTE, &Vbufout);


    NSLog(@"Complete");

}

Can anyone shed some light on how to retrieve the gl_FragData buffers?

genpfault
  • 51,148
  • 11
  • 85
  • 139
  • You never called `glDrawBuffers (...)`, so by default the only thing your fragment shader actually outputs to is `GL_COLOR_ATTACHMENT0`, and that maps to `gl_FragData [0]` by default. – Andon M. Coleman Jun 27 '14 at 16:24
  • You should really consider using a PBO for this, by the way if you are concerned about performance. – Andon M. Coleman Jun 27 '14 at 16:26
  • I'm trying not to make any of this visible, so I intentionally left out the draw buffers. Getting the data from GL_COLOR_ATTACHMENT0 is fine for the U buffer, but how do I access the V buffer (gl_FragData[1])? – Ron Hansen Jun 27 '14 at 17:34
  • I will investigate using a PBO instead of glReadPixels – Ron Hansen Jun 27 '14 at 17:36
  • Like I said, you need to use `glDrawBuffers (...)` to make sure that `gl_FragData [1]` actually goes somewhere. That command establishes the link between that array and the attachment points in your FBO. By default, it links `gl_FragData [0]` to `GL_COLOR_ATTACHMENT0` and nothing else. Thus, writing to `gl_FragData [1]` goes nowhere. You would need something like `GLenum draw_buffers [] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; glDrawBuffers (2, draw_buffers);` to solve your problem (map `gl_FragData [1]` to `GL_COLOR_ATTACHMENT1`). – Andon M. Coleman Jun 27 '14 at 18:27
  • By the way, you do not even have two color attachments in your FBO. So you need more than that to fix your problem. You need to allocate storage for a second renderbuffer and attach it to `GL_COLOR_ATTACHMENT1`. – Andon M. Coleman Jun 27 '14 at 18:36
  • 1
    ES 2.0 does not support multiple render targets. You will either have to use ES 3.0, or rely on extensions, to get this working. – Reto Koradi Jun 27 '14 at 18:47
  • Andon, are you certain that IOS supports GL_COLOR_ATTACHMENT1 in OpenGL ES 2.0? Our app target is IOS 5 so we cannot use 3.0 xCode is not allowing it and all the docs I can find say no. This post also says no http://stackoverflow.com/a/12776803/3783807 – Ron Hansen Jun 27 '14 at 18:51
  • ES 2.0 does so support multiple render targets, it just only requires a minimum of 1. Now, if the GLSL compiler is not actually complaining to you about your use of `gl_FragData [n]` where n > 0, then your implementation must support at least 2 render targets. – Andon M. Coleman Jun 27 '14 at 18:53
  • Check the value of `gl_MaxDrawBuffers` in your fragment shader to be sure (minimum is **1**, but it must be greater for this shader not to be generating an error). – Andon M. Coleman Jun 27 '14 at 19:00
  • Actually, I haven't gotten to the point of compiling my iOS app successfully, so I don't know if it will support gl_FragData[1]. There is a dearth of documentation about this. – Ron Hansen Jun 27 '14 at 19:00
  • If not supported, then it looks like my only option will be to make two shader calls, or to figure out the math the build the two planes as one buffer where the v plane follows the u plane – Ron Hansen Jun 27 '14 at 19:03
  • 1
    According to the ES 2.0 spec, the only supported FBO attachment points are `COLOR_ATTACHMENT0`, `DEPTH_ATTACHMENT`, and `STENCIL_ATTACHMENT`. ES 2.0 also does not have a `glDrawBuffers()` entry point, so there is no way to set up multiple render targets. From the revision history that is part of the ES 2.0 spec: "... neither DrawBuffers nor multiple color buffer attachments are supported". – Reto Koradi Jun 28 '14 at 03:35
  • @RetoKoradi: See: [`EXT_draw_buffers`](https://www.khronos.org/registry/gles/extensions/EXT/EXT_draw_buffers.txt), that extension is for OpenGL ES 2.0 and supported by a few implementations. There is an even older NV extension that introduced the functionality to ES 2.0 as well. – Andon M. Coleman Jun 29 '14 at 10:55
  • @AndonM.Coleman - No iOS device supports that extension, so unfortunately that won't help the asker. This will either need to be done in separate render passes (one FBO-bound texture for U, one for V) or OpenGL ES 3.0, which they've ruled out. Texture caches might help with capture back to the CPU, though: http://stackoverflow.com/questions/9550297/faster-alternative-to-glreadpixels-in-iphone-opengl-es-2-0/9704392#9704392 – Brad Larson Jul 02 '14 at 19:17

0 Answers0