Added NV12 and NV21 texture support for OpenGL and OpenGL ES 2.0 renderers
authorSam Lantinga <slouken@libsdl.org>
Wed, 06 Aug 2014 11:34:54 -0700
changeset 9046 c3ec7c3e6c24
parent 9045 c689ac0e8411
child 9047 101109110bf7
Added NV12 and NV21 texture support for OpenGL and OpenGL ES 2.0 renderers
include/SDL_pixels.h
src/render/opengl/SDL_render_gl.c
src/render/opengl/SDL_shaders_gl.c
src/render/opengl/SDL_shaders_gl.h
src/render/opengles2/SDL_render_gles2.c
src/render/opengles2/SDL_shaders_gles2.c
src/render/opengles2/SDL_shaders_gles2.h
src/test/SDL_test_common.c
src/video/SDL_pixels.c
src/video/SDL_surface.c
test/testautomation_pixels.c
test/testoverlay2.c
--- a/include/SDL_pixels.h	Wed Aug 06 00:28:02 2014 -0700
+++ b/include/SDL_pixels.h	Wed Aug 06 11:34:54 2014 -0700
@@ -248,7 +248,11 @@
     SDL_PIXELFORMAT_UYVY =      /**< Packed mode: U0+Y0+V0+Y1 (1 plane) */
         SDL_DEFINE_PIXELFOURCC('U', 'Y', 'V', 'Y'),
     SDL_PIXELFORMAT_YVYU =      /**< Packed mode: Y0+V0+Y1+U0 (1 plane) */
-        SDL_DEFINE_PIXELFOURCC('Y', 'V', 'Y', 'U')
+        SDL_DEFINE_PIXELFOURCC('Y', 'V', 'Y', 'U'),
+    SDL_PIXELFORMAT_NV12 =      /**< Planar mode: Y + U/V interleaved  (2 planes) */
+        SDL_DEFINE_PIXELFOURCC('N', 'V', '1', '2'),
+    SDL_PIXELFORMAT_NV21 =      /**< Planar mode: Y + V/U interleaved  (2 planes) */
+        SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1')
 };
 
 typedef struct SDL_Color
--- a/src/render/opengl/SDL_render_gl.c	Wed Aug 06 00:28:02 2014 -0700
+++ b/src/render/opengl/SDL_render_gl.c	Wed Aug 06 11:34:54 2014 -0700
@@ -164,8 +164,9 @@
     int pitch;
     SDL_Rect locked_rect;
 
-    /* YV12 texture support */
+    /* YUV texture support */
     SDL_bool yuv;
+    SDL_bool nv12;
     GLuint utexture;
     GLuint vtexture;
 
@@ -531,6 +532,8 @@
     if (data->shaders && data->num_texture_units >= 3) {
         renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_YV12;
         renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_IYUV;
+        renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_NV12;
+        renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_NV21;
     }
 
 #ifdef __MACOSX__
@@ -611,16 +614,18 @@
         break;
     case SDL_PIXELFORMAT_YV12:
     case SDL_PIXELFORMAT_IYUV:
+    case SDL_PIXELFORMAT_NV12:
+    case SDL_PIXELFORMAT_NV21:
         *internalFormat = GL_LUMINANCE;
         *format = GL_LUMINANCE;
         *type = GL_UNSIGNED_BYTE;
         break;
 #ifdef __MACOSX__
     case SDL_PIXELFORMAT_UYVY:
-		*internalFormat = GL_RGB8;
-		*format = GL_YCBCR_422_APPLE;
-		*type = GL_UNSIGNED_SHORT_8_8_APPLE;
-		break;
+        *internalFormat = GL_RGB8;
+        *format = GL_YCBCR_422_APPLE;
+        *type = GL_UNSIGNED_SHORT_8_8_APPLE;
+        break;
 #endif
     default:
         return SDL_FALSE;
@@ -672,6 +677,11 @@
             /* Need to add size for the U and V planes */
             size += (2 * (texture->h * data->pitch) / 4);
         }
+        if (texture->format == SDL_PIXELFORMAT_NV12 ||
+            texture->format == SDL_PIXELFORMAT_NV21) {
+            /* Need to add size for the U/V plane */
+            size += ((texture->h * data->pitch) / 2);
+        }
         data->pixels = SDL_calloc(1, size);
         if (!data->pixels) {
             SDL_free(data);
@@ -801,6 +811,27 @@
         renderdata->glDisable(data->type);
     }
 
+    if (texture->format == SDL_PIXELFORMAT_NV12 ||
+        texture->format == SDL_PIXELFORMAT_NV21) {
+        data->nv12 = SDL_TRUE;
+
+        renderdata->glGenTextures(1, &data->utexture);
+        renderdata->glEnable(data->type);
+
+        renderdata->glBindTexture(data->type, data->utexture);
+        renderdata->glTexParameteri(data->type, GL_TEXTURE_MIN_FILTER,
+                                    scaleMode);
+        renderdata->glTexParameteri(data->type, GL_TEXTURE_MAG_FILTER,
+                                    scaleMode);
+        renderdata->glTexParameteri(data->type, GL_TEXTURE_WRAP_S,
+                                    GL_CLAMP_TO_EDGE);
+        renderdata->glTexParameteri(data->type, GL_TEXTURE_WRAP_T,
+                                    GL_CLAMP_TO_EDGE);
+        renderdata->glTexImage2D(data->type, 0, GL_LUMINANCE_ALPHA, texture_w/2,
+                                 texture_h/2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL);
+        renderdata->glDisable(data->type);
+    }
+
     return GL_CheckError("", renderer);
 }
 
@@ -848,6 +879,17 @@
                                     rect->w/2, rect->h/2,
                                     data->format, data->formattype, pixels);
     }
+
+    if (data->nv12) {
+        renderdata->glPixelStorei(GL_UNPACK_ROW_LENGTH, (pitch / 2));
+
+        /* Skip to the correct offset into the next texture */
+        pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
+        renderdata->glBindTexture(data->type, data->utexture);
+        renderdata->glTexSubImage2D(data->type, 0, rect->x/2, rect->y/2,
+                                    rect->w/2, rect->h/2,
+                                    GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, pixels);
+    }
     renderdata->glDisable(data->type);
 
     return GL_CheckError("glTexSubImage2D()", renderer);
@@ -1184,15 +1226,10 @@
 }
 
 static int
-GL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
-              const SDL_Rect * srcrect, const SDL_FRect * dstrect)
+GL_SetupCopy(SDL_Renderer * renderer, SDL_Texture * texture)
 {
     GL_RenderData *data = (GL_RenderData *) renderer->driverdata;
     GL_TextureData *texturedata = (GL_TextureData *) texture->driverdata;
-    GLfloat minx, miny, maxx, maxy;
-    GLfloat minu, maxu, minv, maxv;
-
-    GL_ActivateRenderer(renderer);
 
     data->glEnable(texturedata->type);
     if (texturedata->yuv) {
@@ -1204,6 +1241,12 @@
 
         data->glActiveTextureARB(GL_TEXTURE0_ARB);
     }
+    if (texturedata->nv12) {
+        data->glActiveTextureARB(GL_TEXTURE1_ARB);
+        data->glBindTexture(texturedata->type, texturedata->utexture);
+
+        data->glActiveTextureARB(GL_TEXTURE0_ARB);
+    }
     data->glBindTexture(texturedata->type, texturedata->texture);
 
     if (texture->modMode) {
@@ -1215,10 +1258,33 @@
     GL_SetBlendMode(data, texture->blendMode);
 
     if (texturedata->yuv) {
-        GL_SetShader(data, SHADER_YV12);
+        GL_SetShader(data, SHADER_YUV);
+    } else if (texturedata->nv12) {
+        if (texture->format == SDL_PIXELFORMAT_NV12) {
+            GL_SetShader(data, SHADER_NV12);
+        } else {
+            GL_SetShader(data, SHADER_NV21);
+        }
     } else {
         GL_SetShader(data, SHADER_RGB);
     }
+    return 0;
+}
+
+static int
+GL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
+              const SDL_Rect * srcrect, const SDL_FRect * dstrect)
+{
+    GL_RenderData *data = (GL_RenderData *) renderer->driverdata;
+    GL_TextureData *texturedata = (GL_TextureData *) texture->driverdata;
+    GLfloat minx, miny, maxx, maxy;
+    GLfloat minu, maxu, minv, maxv;
+
+    GL_ActivateRenderer(renderer);
+
+    if (GL_SetupCopy(renderer, texture) < 0) {
+        return -1;
+    }
 
     minx = dstrect->x;
     miny = dstrect->y;
@@ -1263,30 +1329,8 @@
 
     GL_ActivateRenderer(renderer);
 
-    data->glEnable(texturedata->type);
-    if (texturedata->yuv) {
-        data->glActiveTextureARB(GL_TEXTURE2_ARB);
-        data->glBindTexture(texturedata->type, texturedata->vtexture);
-
-        data->glActiveTextureARB(GL_TEXTURE1_ARB);
-        data->glBindTexture(texturedata->type, texturedata->utexture);
-
-        data->glActiveTextureARB(GL_TEXTURE0_ARB);
-    }
-    data->glBindTexture(texturedata->type, texturedata->texture);
-
-    if (texture->modMode) {
-        GL_SetColor(data, texture->r, texture->g, texture->b, texture->a);
-    } else {
-        GL_SetColor(data, 255, 255, 255, 255);
-    }
-
-    GL_SetBlendMode(data, texture->blendMode);
-
-    if (texturedata->yuv) {
-        GL_SetShader(data, SHADER_YV12);
-    } else {
-        GL_SetShader(data, SHADER_RGB);
+    if (GL_SetupCopy(renderer, texture) < 0) {
+        return -1;
     }
 
     centerx = center->x;
--- a/src/render/opengl/SDL_shaders_gl.c	Wed Aug 06 00:28:02 2014 -0700
+++ b/src/render/opengl/SDL_shaders_gl.c	Wed Aug 06 11:34:54 2014 -0700
@@ -113,7 +113,7 @@
 "}"
     },
 
-    /* SHADER_YV12 */
+    /* SHADER_YUV */
     {
         /* vertex shader */
 "varying vec4 v_color;\n"
@@ -164,6 +164,106 @@
 "    gl_FragColor = vec4(rgb, 1.0) * v_color;\n"
 "}"
     },
+
+    /* SHADER_NV12 */
+    {
+        /* vertex shader */
+"varying vec4 v_color;\n"
+"varying vec2 v_texCoord;\n"
+"\n"
+"void main()\n"
+"{\n"
+"    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n"
+"    v_color = gl_Color;\n"
+"    v_texCoord = vec2(gl_MultiTexCoord0);\n"
+"}",
+        /* fragment shader */
+"varying vec4 v_color;\n"
+"varying vec2 v_texCoord;\n"
+"uniform sampler2D tex0; // Y \n"
+"uniform sampler2D tex1; // U/V \n"
+"\n"
+"// YUV offset \n"
+"const vec3 offset = vec3(-0.0627451017, -0.501960814, -0.501960814);\n"
+"\n"
+"// RGB coefficients \n"
+"const vec3 Rcoeff = vec3(1.164,  0.000,  1.596);\n"
+"const vec3 Gcoeff = vec3(1.164, -0.391, -0.813);\n"
+"const vec3 Bcoeff = vec3(1.164,  2.018,  0.000);\n"
+"\n"
+"void main()\n"
+"{\n"
+"    vec2 tcoord;\n"
+"    vec3 yuv, rgb;\n"
+"\n"
+"    // Get the Y value \n"
+"    tcoord = v_texCoord;\n"
+"    yuv.x = texture2D(tex0, tcoord).r;\n"
+"\n"
+"    // Get the U and V values \n"
+"    tcoord *= 0.5;\n"
+"    yuv.yz = texture2D(tex1, tcoord).ra;\n"
+"\n"
+"    // Do the color transform \n"
+"    yuv += offset;\n"
+"    rgb.r = dot(yuv, Rcoeff);\n"
+"    rgb.g = dot(yuv, Gcoeff);\n"
+"    rgb.b = dot(yuv, Bcoeff);\n"
+"\n"
+"    // That was easy. :) \n"
+"    gl_FragColor = vec4(rgb, 1.0) * v_color;\n"
+"}"
+    },
+
+    /* SHADER_NV21 */
+    {
+        /* vertex shader */
+"varying vec4 v_color;\n"
+"varying vec2 v_texCoord;\n"
+"\n"
+"void main()\n"
+"{\n"
+"    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n"
+"    v_color = gl_Color;\n"
+"    v_texCoord = vec2(gl_MultiTexCoord0);\n"
+"}",
+        /* fragment shader */
+"varying vec4 v_color;\n"
+"varying vec2 v_texCoord;\n"
+"uniform sampler2D tex0; // Y \n"
+"uniform sampler2D tex1; // U/V \n"
+"\n"
+"// YUV offset \n"
+"const vec3 offset = vec3(-0.0627451017, -0.501960814, -0.501960814);\n"
+"\n"
+"// RGB coefficients \n"
+"const vec3 Rcoeff = vec3(1.164,  0.000,  1.596);\n"
+"const vec3 Gcoeff = vec3(1.164, -0.391, -0.813);\n"
+"const vec3 Bcoeff = vec3(1.164,  2.018,  0.000);\n"
+"\n"
+"void main()\n"
+"{\n"
+"    vec2 tcoord;\n"
+"    vec3 yuv, rgb;\n"
+"\n"
+"    // Get the Y value \n"
+"    tcoord = v_texCoord;\n"
+"    yuv.x = texture2D(tex0, tcoord).r;\n"
+"\n"
+"    // Get the U and V values \n"
+"    tcoord *= 0.5;\n"
+"    yuv.yz = texture2D(tex1, tcoord).ar;\n"
+"\n"
+"    // Do the color transform \n"
+"    yuv += offset;\n"
+"    rgb.r = dot(yuv, Rcoeff);\n"
+"    rgb.g = dot(yuv, Gcoeff);\n"
+"    rgb.b = dot(yuv, Bcoeff);\n"
+"\n"
+"    // That was easy. :) \n"
+"    gl_FragColor = vec4(rgb, 1.0) * v_color;\n"
+"}"
+    },
 };
 
 static SDL_bool
--- a/src/render/opengl/SDL_shaders_gl.h	Wed Aug 06 00:28:02 2014 -0700
+++ b/src/render/opengl/SDL_shaders_gl.h	Wed Aug 06 11:34:54 2014 -0700
@@ -26,7 +26,9 @@
     SHADER_NONE,
     SHADER_SOLID,
     SHADER_RGB,
-    SHADER_YV12,
+    SHADER_YUV,
+    SHADER_NV12,
+    SHADER_NV21,
     NUM_SHADERS
 } GL_Shader;
 
--- a/src/render/opengles2/SDL_render_gles2.c	Wed Aug 06 00:28:02 2014 -0700
+++ b/src/render/opengles2/SDL_render_gles2.c	Wed Aug 06 11:34:54 2014 -0700
@@ -81,8 +81,9 @@
     GLenum pixel_type;
     void *pixel_data;
     int pitch;
-    /* YV12 texture support */
+    /* YUV texture support */
     SDL_bool yuv;
+    SDL_bool nv12;
     GLenum texture_v;
     GLenum texture_u;
     GLES2_FBOList *fbo;
@@ -151,7 +152,9 @@
     GLES2_IMAGESOURCE_TEXTURE_ARGB,
     GLES2_IMAGESOURCE_TEXTURE_RGB,
     GLES2_IMAGESOURCE_TEXTURE_BGR,
-    GLES2_IMAGESOURCE_TEXTURE_YUV
+    GLES2_IMAGESOURCE_TEXTURE_YUV,
+    GLES2_IMAGESOURCE_TEXTURE_NV12,
+    GLES2_IMAGESOURCE_TEXTURE_NV21
 } GLES2_ImageSource;
 
 typedef struct GLES2_DriverContext
@@ -488,6 +491,8 @@
         break;
     case SDL_PIXELFORMAT_IYUV:
     case SDL_PIXELFORMAT_YV12:
+    case SDL_PIXELFORMAT_NV12:
+    case SDL_PIXELFORMAT_NV21:
         format = GL_LUMINANCE;
         type = GL_UNSIGNED_BYTE;
         break;
@@ -505,6 +510,7 @@
     data->pixel_format = format;
     data->pixel_type = type;
     data->yuv = ((texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12));
+    data->nv12 = ((texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21));
     data->texture_u = 0;
     data->texture_v = 0;
     scaleMode = GetScaleQuality();
@@ -518,6 +524,10 @@
             /* Need to add size for the U and V planes */
             size += (2 * (texture->h * data->pitch) / 4);
         }
+        if (data->nv12) {
+            /* Need to add size for the U/V plane */
+            size += ((texture->h * data->pitch) / 2);
+        }
         data->pixel_data = SDL_calloc(1, size);
         if (!data->pixel_data) {
             SDL_free(data);
@@ -557,6 +567,23 @@
         }
     }
 
+    if (data->nv12) {
+        renderdata->glGenTextures(1, &data->texture_u);
+        if (GL_CheckError("glGenTexures()", renderer) < 0) {
+            return -1;
+        }
+        renderdata->glActiveTexture(GL_TEXTURE1);
+        renderdata->glBindTexture(data->texture_type, data->texture_u);
+        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, scaleMode);
+        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, scaleMode);
+        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+        renderdata->glTexImage2D(data->texture_type, 0, GL_LUMINANCE_ALPHA, texture->w / 2, texture->h / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL);
+        if (GL_CheckError("glTexImage2D()", renderer) < 0) {
+            return -1;
+        }
+    }
+
     renderdata->glGenTextures(1, &data->texture);
     if (GL_CheckError("glGenTexures()", renderer) < 0) {
         return -1;
@@ -673,6 +700,20 @@
                 pixels, pitch / 2, 1);
     }
 
+    if (tdata->nv12) {
+        /* Skip to the correct offset into the next texture */
+        pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
+        data->glBindTexture(tdata->texture_type, tdata->texture_u);
+        GLES2_TexSubImage2D(data, tdata->texture_type,
+                rect->x / 2,
+                rect->y / 2,
+                rect->w / 2,
+                rect->h / 2,
+                GL_LUMINANCE_ALPHA,
+                GL_UNSIGNED_BYTE,
+                pixels, pitch, 2);
+    }
+
     return GL_CheckError("glTexSubImage2D()", renderer);
 }
 
@@ -1093,6 +1134,12 @@
     case GLES2_IMAGESOURCE_TEXTURE_YUV:
         ftype = GLES2_SHADER_FRAGMENT_TEXTURE_YUV_SRC;
         break;
+    case GLES2_IMAGESOURCE_TEXTURE_NV12:
+        ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_SRC;
+        break;
+    case GLES2_IMAGESOURCE_TEXTURE_NV21:
+        ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV21_SRC;
+        break;
     default:
         goto fault;
     }
@@ -1432,20 +1479,15 @@
 }
 
 static int
-GLES2_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect,
-                 const SDL_FRect *dstrect)
+GLES2_SetupCopy(SDL_Renderer *renderer, SDL_Texture *texture)
 {
     GLES2_DriverContext *data = (GLES2_DriverContext *)renderer->driverdata;
     GLES2_TextureData *tdata = (GLES2_TextureData *)texture->driverdata;
     GLES2_ImageSource sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;
     SDL_BlendMode blendMode;
-    GLfloat vertices[8];
-    GLfloat texCoords[8];
     GLES2_ProgramCacheEntry *program;
     Uint8 r, g, b, a;
 
-    GLES2_ActivateRenderer(renderer);
-
     /* Activate an appropriate shader and set the projection matrix */
     blendMode = texture->blendMode;
     if (renderer->target) {
@@ -1505,11 +1547,22 @@
                         break;
                 }
                 break;
+            case SDL_PIXELFORMAT_IYUV:
+            case SDL_PIXELFORMAT_YV12:
+                sourceType = GLES2_IMAGESOURCE_TEXTURE_YUV;
+                break;
+            case SDL_PIXELFORMAT_NV12:
+                sourceType = GLES2_IMAGESOURCE_TEXTURE_NV12;
+                break;
+            case SDL_PIXELFORMAT_NV21:
+                sourceType = GLES2_IMAGESOURCE_TEXTURE_NV21;
+                break;
+            default:
+                return SDL_SetError("Unsupported texture format");
             }
         }
         else sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;   /* Texture formats match, use the non color mapping shader (even if the formats are not ABGR) */
-    }
-    else {
+    } else {
         switch (texture->format)
         {
             case SDL_PIXELFORMAT_ARGB8888:
@@ -1524,13 +1577,18 @@
             case SDL_PIXELFORMAT_BGR888:
                 sourceType = GLES2_IMAGESOURCE_TEXTURE_BGR;
                 break;
-            // TODO: new shader to change yv planes YV12 format
             case SDL_PIXELFORMAT_IYUV:
             case SDL_PIXELFORMAT_YV12:
                 sourceType = GLES2_IMAGESOURCE_TEXTURE_YUV;
                 break;
+            case SDL_PIXELFORMAT_NV12:
+                sourceType = GLES2_IMAGESOURCE_TEXTURE_NV12;
+                break;
+            case SDL_PIXELFORMAT_NV21:
+                sourceType = GLES2_IMAGESOURCE_TEXTURE_NV21;
+                break;
             default:
-                return -1;
+                return SDL_SetError("Unsupported texture format");
         }
     }
 
@@ -1548,6 +1606,12 @@
 
         data->glActiveTexture(GL_TEXTURE0);
     }
+    if (tdata->nv12) {
+        data->glActiveTexture(GL_TEXTURE1);
+        data->glBindTexture(tdata->texture_type, tdata->texture_u);
+
+        data->glActiveTexture(GL_TEXTURE0);
+    }
     data->glBindTexture(tdata->texture_type, tdata->texture);
 
     /* Configure color modulation */
@@ -1578,6 +1642,23 @@
     GLES2_SetBlendMode(data, blendMode);
 
     GLES2_SetTexCoords(data, SDL_TRUE);
+    return 0;
+}
+
+static int
+GLES2_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect,
+                 const SDL_FRect *dstrect)
+{
+    GLES2_DriverContext *data = (GLES2_DriverContext *)renderer->driverdata;
+    GLES2_TextureData *tdata = (GLES2_TextureData *)texture->driverdata;
+    GLfloat vertices[8];
+    GLfloat texCoords[8];
+
+    GLES2_ActivateRenderer(renderer);
+
+    if (GLES2_SetupCopy(renderer, texture) < 0) {
+        return -1;
+    }
 
     /* Emit the textured quad */
     vertices[0] = dstrect->x;
@@ -1609,10 +1690,6 @@
 {
     GLES2_DriverContext *data = (GLES2_DriverContext *)renderer->driverdata;
     GLES2_TextureData *tdata = (GLES2_TextureData *)texture->driverdata;
-    GLES2_ImageSource sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;
-    GLES2_ProgramCacheEntry *program;
-    Uint8 r, g, b, a;
-    SDL_BlendMode blendMode;
     GLfloat vertices[8];
     GLfloat texCoords[8];
     GLfloat translate[8];
@@ -1621,6 +1698,10 @@
 
     GLES2_ActivateRenderer(renderer);
 
+    if (GLES2_SetupCopy(renderer, texture) < 0) {
+        return -1;
+    }
+
     data->glEnableVertexAttribArray(GLES2_ATTRIBUTE_CENTER);
     data->glEnableVertexAttribArray(GLES2_ATTRIBUTE_ANGLE);
     fAngle[0] = fAngle[1] = fAngle[2] = fAngle[3] = (GLfloat)(360.0f - angle);
@@ -1628,124 +1709,6 @@
     translate[0] = translate[2] = translate[4] = translate[6] = (center->x + dstrect->x);
     translate[1] = translate[3] = translate[5] = translate[7] = (center->y + dstrect->y);
 
-    /* Activate an appropriate shader and set the projection matrix */
-    blendMode = texture->blendMode;
-    if (renderer->target) {
-        /* Check if we need to do color mapping between the source and render target textures */
-        if (renderer->target->format != texture->format) {
-            switch (texture->format)
-            {
-            case SDL_PIXELFORMAT_ARGB8888:
-                switch (renderer->target->format)
-                {
-                    case SDL_PIXELFORMAT_ABGR8888:
-                    case SDL_PIXELFORMAT_BGR888:
-                        sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB;
-                        break;
-                    case SDL_PIXELFORMAT_RGB888:
-                        sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;
-                        break;
-                }
-                break;
-            case SDL_PIXELFORMAT_ABGR8888:
-                switch (renderer->target->format)
-                {
-                    case SDL_PIXELFORMAT_ARGB8888:
-                    case SDL_PIXELFORMAT_RGB888:
-                        sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB;
-                        break;
-                    case SDL_PIXELFORMAT_BGR888:
-                        sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;
-                        break;
-                }
-                break;
-            case SDL_PIXELFORMAT_RGB888:
-                switch (renderer->target->format)
-                {
-                    case SDL_PIXELFORMAT_ABGR8888:
-                        sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB;
-                        break;
-                    case SDL_PIXELFORMAT_ARGB8888:
-                        sourceType = GLES2_IMAGESOURCE_TEXTURE_BGR;
-                        break;
-                    case SDL_PIXELFORMAT_BGR888:
-                        sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB;
-                        break;
-                }
-                break;
-            case SDL_PIXELFORMAT_BGR888:
-                switch (renderer->target->format)
-                {
-                    case SDL_PIXELFORMAT_ABGR8888:
-                        sourceType = GLES2_IMAGESOURCE_TEXTURE_BGR;
-                        break;
-                    case SDL_PIXELFORMAT_ARGB8888:
-                        sourceType = GLES2_IMAGESOURCE_TEXTURE_RGB;
-                        break;
-                    case SDL_PIXELFORMAT_RGB888:
-                        sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB;
-                        break;
-                }
-                break;
-            }
-        }
-        else sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;   /* Texture formats match, use the non color mapping shader (even if the formats are not ABGR) */
-    }
-    else {
-        switch (texture->format)
-        {
-            case SDL_PIXELFORMAT_ARGB8888:
-                sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB;
-                break;
-            case SDL_PIXELFORMAT_ABGR8888:
-                sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;
-                break;
-            case SDL_PIXELFORMAT_RGB888:
-                sourceType = GLES2_IMAGESOURCE_TEXTURE_RGB;
-                break;
-            case SDL_PIXELFORMAT_BGR888:
-                sourceType = GLES2_IMAGESOURCE_TEXTURE_BGR;
-                break;
-            default:
-                return -1;
-        }
-    }
-    if (GLES2_SelectProgram(renderer, sourceType, blendMode) < 0)
-        return -1;
-
-    /* Select the target texture */
-    data->glBindTexture(tdata->texture_type, tdata->texture);
-
-    /* Configure color modulation */
-    /* !!! FIXME: grep for glUniform4f(), move that stuff to a subroutine, it's a lot of copy/paste. */
-    g = texture->g;
-    a = texture->a;
-
-    if (renderer->target &&
-        (renderer->target->format == SDL_PIXELFORMAT_ARGB8888 ||
-         renderer->target->format == SDL_PIXELFORMAT_RGB888)) {
-        r = texture->b;
-        b = texture->r;
-    } else {
-        r = texture->r;
-        b = texture->b;
-    }
-
-    program = data->current_program;
-
-    if (!CompareColors(program->modulation_r, program->modulation_g, program->modulation_b, program->modulation_a, r, g, b, a)) {
-        data->glUniform4f(program->uniform_locations[GLES2_UNIFORM_MODULATION], r * inv255f, g * inv255f, b * inv255f, a * inv255f);
-        program->modulation_r = r;
-        program->modulation_g = g;
-        program->modulation_b = b;
-        program->modulation_a = a;
-    }
-
-    /* Configure texture blending */
-    GLES2_SetBlendMode(data, blendMode);
-
-    GLES2_SetTexCoords(data, SDL_TRUE);
-
     /* Emit the textured quad */
     vertices[0] = dstrect->x;
     vertices[1] = dstrect->y;
@@ -2066,6 +2029,8 @@
 
     renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_YV12;
     renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_IYUV;
+    renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_NV12;
+    renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_NV21;
 
     GLES2_ResetState(renderer);
 
--- a/src/render/opengles2/SDL_shaders_gles2.c	Wed Aug 06 00:28:02 2014 -0700
+++ b/src/render/opengles2/SDL_shaders_gles2.c	Wed Aug 06 11:34:54 2014 -0700
@@ -150,6 +150,50 @@
     } \
 ";
 
+/* NV12 to ABGR conversion */
+static const Uint8 GLES2_FragmentSrc_TextureNV12Src_[] = " \
+    precision mediump float; \
+    uniform sampler2D u_texture; \
+    uniform sampler2D u_texture_u; \
+    uniform vec4 u_modulation; \
+    varying vec2 v_texCoord; \
+    \
+    void main() \
+    { \
+        mediump vec3 yuv; \
+        lowp vec3 rgb; \
+        yuv.x = texture2D(u_texture,   v_texCoord).r; \
+        yuv.yz = texture2D(u_texture_u, v_texCoord).ra - 0.5; \
+        rgb = mat3( 1,        1,       1, \
+                    0,       -0.39465, 2.03211, \
+                    1.13983, -0.58060, 0) * yuv; \
+        gl_FragColor = vec4(rgb, 1); \
+        gl_FragColor *= u_modulation; \
+    } \
+";
+
+/* NV21 to ABGR conversion */
+static const Uint8 GLES2_FragmentSrc_TextureNV21Src_[] = " \
+    precision mediump float; \
+    uniform sampler2D u_texture; \
+    uniform sampler2D u_texture_u; \
+    uniform vec4 u_modulation; \
+    varying vec2 v_texCoord; \
+    \
+    void main() \
+    { \
+        mediump vec3 yuv; \
+        lowp vec3 rgb; \
+        yuv.x = texture2D(u_texture,   v_texCoord).r; \
+        yuv.yz = texture2D(u_texture_u, v_texCoord).ar - 0.5; \
+        rgb = mat3( 1,        1,       1, \
+                    0,       -0.39465, 2.03211, \
+                    1.13983, -0.58060, 0) * yuv; \
+        gl_FragColor = vec4(rgb, 1); \
+        gl_FragColor *= u_modulation; \
+    } \
+";
+
 static const GLES2_ShaderInstance GLES2_VertexSrc_Default = {
     GL_VERTEX_SHADER,
     GLES2_SOURCE_SHADER,
@@ -199,6 +243,20 @@
     GLES2_FragmentSrc_TextureYUVSrc_
 };
 
+static const GLES2_ShaderInstance GLES2_FragmentSrc_TextureNV12Src = {
+    GL_FRAGMENT_SHADER,
+    GLES2_SOURCE_SHADER,
+    sizeof(GLES2_FragmentSrc_TextureNV12Src_),
+    GLES2_FragmentSrc_TextureNV12Src_
+};
+
+static const GLES2_ShaderInstance GLES2_FragmentSrc_TextureNV21Src = {
+    GL_FRAGMENT_SHADER,
+    GLES2_SOURCE_SHADER,
+    sizeof(GLES2_FragmentSrc_TextureNV21Src_),
+    GLES2_FragmentSrc_TextureNV21Src_
+};
+
 
 /*************************************************************************************************
  * Vertex/fragment shader binaries (NVIDIA Tegra 1/2)                                            *
@@ -731,6 +789,20 @@
     }
 };
 
+static GLES2_Shader GLES2_FragmentShader_TextureNV12Src = {
+    1,
+    {
+        &GLES2_FragmentSrc_TextureNV12Src
+    }
+};
+
+static GLES2_Shader GLES2_FragmentShader_TextureNV21Src = {
+    1,
+    {
+        &GLES2_FragmentSrc_TextureNV21Src
+    }
+};
+
 
 /*************************************************************************************************
  * Shader selector                                                                               *
@@ -820,6 +892,16 @@
         return &GLES2_FragmentShader_TextureYUVSrc;
     }
 
+    case GLES2_SHADER_FRAGMENT_TEXTURE_NV12_SRC:
+    {
+        return &GLES2_FragmentShader_TextureNV12Src;
+    }
+
+    case GLES2_SHADER_FRAGMENT_TEXTURE_NV21_SRC:
+    {
+        return &GLES2_FragmentShader_TextureNV21Src;
+    }
+
     default:
         return NULL;
     }
--- a/src/render/opengles2/SDL_shaders_gles2.h	Wed Aug 06 00:28:02 2014 -0700
+++ b/src/render/opengles2/SDL_shaders_gles2.h	Wed Aug 06 11:34:54 2014 -0700
@@ -47,7 +47,9 @@
     GLES2_SHADER_FRAGMENT_TEXTURE_ARGB_SRC,
     GLES2_SHADER_FRAGMENT_TEXTURE_BGR_SRC,
     GLES2_SHADER_FRAGMENT_TEXTURE_RGB_SRC,
-    GLES2_SHADER_FRAGMENT_TEXTURE_YUV_SRC
+    GLES2_SHADER_FRAGMENT_TEXTURE_YUV_SRC,
+    GLES2_SHADER_FRAGMENT_TEXTURE_NV12_SRC,
+    GLES2_SHADER_FRAGMENT_TEXTURE_NV21_SRC
 } GLES2_ShaderType;
 
 #define GLES2_SOURCE_SHADER (GLenum)-1
--- a/src/test/SDL_test_common.c	Wed Aug 06 00:28:02 2014 -0700
+++ b/src/test/SDL_test_common.c	Wed Aug 06 11:34:54 2014 -0700
@@ -572,6 +572,12 @@
     case SDL_PIXELFORMAT_YVYU:
         fprintf(stderr, "YVYU");
         break;
+    case SDL_PIXELFORMAT_NV12:
+        fprintf(stderr, "NV12");
+        break;
+    case SDL_PIXELFORMAT_NV21:
+        fprintf(stderr, "NV21");
+        break;
     default:
         fprintf(stderr, "0x%8.8x", format);
         break;
--- a/src/video/SDL_pixels.c	Wed Aug 06 00:28:02 2014 -0700
+++ b/src/video/SDL_pixels.c	Wed Aug 06 11:34:54 2014 -0700
@@ -122,6 +122,8 @@
     CASE(SDL_PIXELFORMAT_YUY2)
     CASE(SDL_PIXELFORMAT_UYVY)
     CASE(SDL_PIXELFORMAT_YVYU)
+    CASE(SDL_PIXELFORMAT_NV12)
+    CASE(SDL_PIXELFORMAT_NV21)
 #undef CASE
     default:
         return "SDL_PIXELFORMAT_UNKNOWN";
--- a/src/video/SDL_surface.c	Wed Aug 06 00:28:02 2014 -0700
+++ b/src/video/SDL_surface.c	Wed Aug 06 11:34:54 2014 -0700
@@ -1005,7 +1005,7 @@
     SDL_Rect rect;
     void *nonconst_src = (void *) src;
 
-    /* Check to make sure we are bliting somewhere, so we don't crash */
+    /* Check to make sure we are blitting somewhere, so we don't crash */
     if (!dst) {
         return SDL_InvalidParamError("dst");
     }
@@ -1015,17 +1015,21 @@
 
     /* Fast path for same format copy */
     if (src_format == dst_format) {
-        int bpp;
+        int bpp, i;
 
         if (SDL_ISPIXELFORMAT_FOURCC(src_format)) {
             switch (src_format) {
-            case SDL_PIXELFORMAT_YV12:
-            case SDL_PIXELFORMAT_IYUV:
             case SDL_PIXELFORMAT_YUY2:
             case SDL_PIXELFORMAT_UYVY:
             case SDL_PIXELFORMAT_YVYU:
                 bpp = 2;
                 break;
+            case SDL_PIXELFORMAT_YV12:
+            case SDL_PIXELFORMAT_IYUV:
+            case SDL_PIXELFORMAT_NV12:
+            case SDL_PIXELFORMAT_NV21:
+                bpp = 1;
+                break;
             default:
                 return SDL_SetError("Unknown FOURCC pixel format");
             }
@@ -1034,11 +1038,32 @@
         }
         width *= bpp;
 
-        while (height-- > 0) {
+        for (i = height; i--;) {
             SDL_memcpy(dst, src, width);
             src = (Uint8*)src + src_pitch;
             dst = (Uint8*)dst + dst_pitch;
         }
+
+        if (src_format == SDL_PIXELFORMAT_YV12 || src_format == SDL_PIXELFORMAT_IYUV) {
+            /* U and V planes are a quarter the size of the Y plane */
+            width /= 2;
+            height /= 2;
+            src_pitch /= 2;
+            dst_pitch /= 2;
+            for (i = height * 2; i--;) {
+                SDL_memcpy(dst, src, width);
+                src = (Uint8*)src + src_pitch;
+                dst = (Uint8*)dst + dst_pitch;
+            }
+        } else if (src_format == SDL_PIXELFORMAT_NV12 || src_format == SDL_PIXELFORMAT_NV21) {
+            /* U/V plane is half the height of the Y plane */
+            height /= 2;
+            for (i = height; i--;) {
+                SDL_memcpy(dst, src, width);
+                src = (Uint8*)src + src_pitch;
+                dst = (Uint8*)dst + dst_pitch;
+            }
+        }
         return 0;
     }
 
--- a/test/testautomation_pixels.c	Wed Aug 06 00:28:02 2014 -0700
+++ b/test/testautomation_pixels.c	Wed Aug 06 11:34:54 2014 -0700
@@ -79,14 +79,16 @@
   };
 
 /* Definition of all Non-RGB formats used to test pixel conversions */
-const int _numNonRGBPixelFormats = 5;
+const int _numNonRGBPixelFormats = 7;
 Uint32 _nonRGBPixelFormats[] =
   {
     SDL_PIXELFORMAT_YV12,
     SDL_PIXELFORMAT_IYUV,
     SDL_PIXELFORMAT_YUY2,
     SDL_PIXELFORMAT_UYVY,
-    SDL_PIXELFORMAT_YVYU
+    SDL_PIXELFORMAT_YVYU,
+    SDL_PIXELFORMAT_NV12,
+    SDL_PIXELFORMAT_NV21
   };
 char* _nonRGBPixelFormatsVerbose[] =
   {
@@ -94,7 +96,9 @@
     "SDL_PIXELFORMAT_IYUV",
     "SDL_PIXELFORMAT_YUY2",
     "SDL_PIXELFORMAT_UYVY",
-    "SDL_PIXELFORMAT_YVYU"
+    "SDL_PIXELFORMAT_YVYU",
+    "SDL_PIXELFORMAT_NV12",
+    "SDL_PIXELFORMAT_NV21"
   };
 
 /* Definition of some invalid formats for negative tests */
--- a/test/testoverlay2.c	Wed Aug 06 00:28:02 2014 -0700
+++ b/test/testoverlay2.c	Wed Aug 06 11:34:54 2014 -0700
@@ -206,6 +206,29 @@
     }
 }
 
+void
+ConvertRGBtoNV12(Uint8 *rgb, Uint8 *out, int w, int h,
+                 int monochrome, int luminance)
+{
+    int x, y;
+    int yuv[3];
+    Uint8 *op[2];
+
+    op[0] = out;
+    op[1] = op[0] + w*h;
+    for (y = 0; y < h; ++y) {
+        for (x = 0; x < w; ++x) {
+            RGBtoYUV(rgb, yuv, monochrome, luminance);
+            *(op[0]++) = yuv[0];
+            if (x % 2 == 0 && y % 2 == 0) {
+                *(op[1]++) = yuv[1];
+                *(op[1]++) = yuv[2];
+            }
+            rgb += 3;
+        }
+    }
+}
+
 static void
 PrintUsage(char *argv0)
 {
@@ -241,7 +264,11 @@
     int fps = 12;
     int fpsdelay;
     int nodelay = 0;
+#ifdef TEST_NV12
+    Uint32 pixel_format = SDL_PIXELFORMAT_NV12;
+#else
     Uint32 pixel_format = SDL_PIXELFORMAT_YV12;
+#endif
     int scale = 5;
     SDL_bool done = SDL_FALSE;
 
@@ -371,7 +398,17 @@
             rgb[2] = MooseColors[frame[j]].b;
             rgb += 3;
         }
-        ConvertRGBtoYV12(MooseFrameRGB, MooseFrame[i], MOOSEPIC_W, MOOSEPIC_H, 0, 100);
+        switch (pixel_format) {
+        case SDL_PIXELFORMAT_YV12:
+            ConvertRGBtoYV12(MooseFrameRGB, MooseFrame[i], MOOSEPIC_W, MOOSEPIC_H, 0, 100);
+            break;
+        case SDL_PIXELFORMAT_NV12:
+            ConvertRGBtoNV12(MooseFrameRGB, MooseFrame[i], MOOSEPIC_W, MOOSEPIC_H, 0, 100);
+            break;
+        default:
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unsupported pixel format\n");
+            break;
+        }
     }
 
     free(RawMooseData);