Implementation of render targets, by Mason Wheeler and Gabriel Jacobo
authorSam Lantinga <slouken@libsdl.org>
Wed, 18 Jan 2012 22:45:49 -0500
changeset 6232 37e8d0736366
parent 6231 5eecf59b698f
child 6235 d169541f5049
Implementation of render targets, by Mason Wheeler and Gabriel Jacobo Thanks guys!
include/SDL_render.h
src/render/SDL_render.c
src/render/SDL_sysrender.h
src/render/direct3d/SDL_render_d3d.c
src/render/opengl/SDL_render_gl.c
src/render/opengles/SDL_glesfuncs.h
src/render/opengles/SDL_render_gles.c
src/render/opengles2/SDL_gles2funcs.h
src/render/opengles2/SDL_render_gles2.c
test/Makefile.in
test/testrendertarget.c
--- a/include/SDL_render.h	Wed Jan 18 22:22:54 2012 -0500
+++ b/include/SDL_render.h	Wed Jan 18 22:45:49 2012 -0500
@@ -88,7 +88,8 @@
 typedef enum
 {
     SDL_TEXTUREACCESS_STATIC,    /**< Changes rarely, not lockable */
-    SDL_TEXTUREACCESS_STREAMING  /**< Changes frequently, lockable */
+    SDL_TEXTUREACCESS_STREAMING, /**< Changes frequently, lockable */
+    SDL_TEXTUREACCESS_TARGET     /**< Texture can be used as a render target */
 } SDL_TextureAccess;
 
 /**
@@ -561,6 +562,31 @@
                                            const SDL_Rect * srcrect,
                                            const SDL_Rect * dstrect);
 
+
+/**
+ * \fn SDL_bool SDL_RenderTargetSupported(SDL_Renderer *renderer)
+ *
+ * \brief Determines whether a window supports the use of render targets
+ *
+ * \param renderer The renderer that will be checked
+ *
+ * \return SDL_TRUE if supported, SDL_FALSE if not.
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_RenderTargetSupported(SDL_Renderer *renderer);
+
+/**
+ * \fn int SDL_SetTargetTexture(SDL_Renderer *renderer, SDL_Texture *texture)
+ *
+ * \brief Set a texture as the current rendering target.
+ *
+ * \param renderer The renderer that will be checked
+ *
+ * \param texture The targeted texture, or NULL for the default render target
+ *
+ * \return 0 on success, or -1 if there is no rendering context current, or the driver doesn't support the requested operation.
+ */
+extern DECLSPEC int SDLCALL SDL_SetTargetTexture(SDL_Renderer *renderer, SDL_Texture *texture);
+
 /**
  *  \brief Read pixels from the current rendering target.
  *  
--- a/src/render/SDL_render.c	Wed Jan 18 22:22:54 2012 -0500
+++ b/src/render/SDL_render.c	Wed Jan 18 22:45:49 2012 -0500
@@ -1014,9 +1014,9 @@
 SDL_RenderFillRect(SDL_Renderer * renderer, const SDL_Rect * rect)
 {
     SDL_Rect full_rect;
-	
+
     CHECK_RENDERER_MAGIC(renderer, -1);
-	
+
     /* If 'rect' == NULL, then outline the whole surface */
     if (!rect) {
         full_rect.x = 0;
@@ -1150,6 +1150,35 @@
                                       format, pixels, pitch);
 }
 
+SDL_bool
+SDL_RenderTargetSupported(SDL_Renderer *renderer)
+{
+    if ((!renderer) || (!renderer->SetTargetTexture)) {
+        return SDL_FALSE;
+    }
+    return SDL_TRUE;
+}
+
+int
+SDL_SetTargetTexture(SDL_Renderer *renderer, SDL_Texture *texture)
+{
+    
+    if(!renderer) {
+        return -1;
+    }
+    if (!renderer->SetTargetTexture) {
+        SDL_Unsupported();
+        return -1;
+    }
+    // Warning: texture==NULL is a valid parameter
+    if( texture ) {
+        CHECK_TEXTURE_MAGIC(texture, -1);
+        if(renderer != texture->renderer) return -1;
+    }
+    
+    return renderer->SetTargetTexture(renderer, texture);
+}
+
 void
 SDL_RenderPresent(SDL_Renderer * renderer)
 {
--- a/src/render/SDL_sysrender.h	Wed Jan 18 22:22:54 2012 -0500
+++ b/src/render/SDL_sysrender.h	Wed Jan 18 22:45:49 2012 -0500
@@ -87,6 +87,7 @@
                             int count);
     int (*RenderCopy) (SDL_Renderer * renderer, SDL_Texture * texture,
                        const SDL_Rect * srcrect, const SDL_Rect * dstrect);
+    int (*SetTargetTexture) (SDL_Renderer * renderer, SDL_Texture * texture);
     int (*RenderReadPixels) (SDL_Renderer * renderer, const SDL_Rect * rect,
                              Uint32 format, void * pixels, int pitch);
     void (*RenderPresent) (SDL_Renderer * renderer);
--- a/src/render/direct3d/SDL_render_d3d.c	Wed Jan 18 22:22:54 2012 -0500
+++ b/src/render/direct3d/SDL_render_d3d.c	Wed Jan 18 22:45:49 2012 -0500
@@ -111,6 +111,7 @@
                           const SDL_Rect * srcrect, const SDL_Rect * dstrect);
 static int D3D_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
                                 Uint32 format, void * pixels, int pitch);
+static int D3D_SetTargetTexture(SDL_Renderer * renderer, SDL_Texture * texture);
 static void D3D_RenderPresent(SDL_Renderer * renderer);
 static void D3D_DestroyTexture(SDL_Renderer * renderer,
                                SDL_Texture * texture);
@@ -138,6 +139,12 @@
     SDL_bool updateSize;
     SDL_bool beginScene;
     D3DTEXTUREFILTERTYPE scaleMode;
+    IDirect3DSurface9 *defaultRenderTarget;
+    IDirect3DSurface9 *currentRenderTarget;
+    SDL_bool renderTargetActive;
+    SDL_Rect viewport_copy;
+    
+    Uint32 NumSimultaneousRTs;
 } D3D_RenderData;
 
 typedef struct
@@ -392,6 +399,7 @@
     renderer->RenderFillRects = D3D_RenderFillRects;
     renderer->RenderCopy = D3D_RenderCopy;
     renderer->RenderReadPixels = D3D_RenderReadPixels;
+    renderer->SetTargetTexture = D3D_SetTargetTexture;
     renderer->RenderPresent = D3D_RenderPresent;
     renderer->DestroyTexture = D3D_DestroyTexture;
     renderer->DestroyRenderer = D3D_DestroyRenderer;
@@ -478,6 +486,7 @@
     IDirect3DDevice9_GetDeviceCaps(data->device, &caps);
     renderer->info.max_texture_width = caps.MaxTextureWidth;
     renderer->info.max_texture_height = caps.MaxTextureHeight;
+    data->NumSimultaneousRTs = caps.NumSimultaneousRTs;
 
     /* Set up parameters for rendering */
     IDirect3DDevice9_SetVertexShader(data->device, NULL);
@@ -507,6 +516,11 @@
     IDirect3DDevice9_SetTextureStageState(data->device, 1, D3DTSS_ALPHAOP,
                                           D3DTOP_DISABLE);
 
+    /* Store the default render target */
+    IDirect3DDevice9_GetRenderTarget(data->device, 0, &data->defaultRenderTarget );
+    data->currentRenderTarget = NULL;
+    data->renderTargetActive = SDL_FALSE;
+
     /* Set an identity world and view matrix */
     matrix.m[0][0] = 1.0f;
     matrix.m[0][1] = 0.0f;
@@ -555,6 +569,80 @@
 }
 
 static int
+D3D_SetTargetTexture(SDL_Renderer * renderer, SDL_Texture * texture)
+{
+    D3D_RenderData *data = (D3D_RenderData *) renderer->driverdata;
+    D3D_TextureData *texturedata;
+    HRESULT result;
+
+    if (!renderer) return -1;
+    D3D_ActivateRenderer(renderer);
+
+    if (data->NumSimultaneousRTs < 2) {
+        SDL_Unsupported();
+        return -1;
+    }
+
+    // Release the previous render target if it wasn't the default one
+    if (data->currentRenderTarget != NULL) {
+        IDirect3DSurface9_Release(data->currentRenderTarget);
+        data->currentRenderTarget = NULL;
+    }
+
+    /* Prepare an identity world and view matrix */
+    D3DMATRIX matrix;
+    matrix.m[0][0] = 1.0f;
+    matrix.m[0][1] = 0.0f;
+    matrix.m[0][2] = 0.0f;
+    matrix.m[0][3] = 0.0f;
+    matrix.m[1][0] = 0.0f;
+    matrix.m[1][1] = 1.0f;
+    matrix.m[1][2] = 0.0f;
+    matrix.m[1][3] = 0.0f;
+    matrix.m[2][0] = 0.0f;
+    matrix.m[2][1] = 0.0f;
+    matrix.m[2][2] = 1.0f;
+    matrix.m[2][3] = 0.0f;
+    matrix.m[3][0] = 0.0f;
+    matrix.m[3][1] = 0.0f;
+    matrix.m[3][2] = 0.0f;
+    matrix.m[3][3] = 1.0f;
+
+    if (texture == NULL) {
+        if (data->renderTargetActive) {
+            data->renderTargetActive = SDL_FALSE;
+            IDirect3DDevice9_SetRenderTarget(data->device, 0, data->defaultRenderTarget );
+            renderer->viewport = data->viewport_copy;
+            D3D_UpdateViewport(renderer);
+        }
+        return 0;
+    }
+    if (renderer != texture->renderer) return -1;
+
+    if ( !data->renderTargetActive ) {
+        data->viewport_copy = renderer->viewport;
+    }
+
+    texturedata = (D3D_TextureData *) texture->driverdata;
+    result = IDirect3DTexture9_GetSurfaceLevel(texturedata->texture, 0, &data->currentRenderTarget );
+    if(FAILED(result)) {
+        return -1;
+    }
+    result = IDirect3DDevice9_SetRenderTarget(data->device, 0, data->currentRenderTarget );
+    if(FAILED(result)) {
+        return -1;
+    }
+
+    data->renderTargetActive = SDL_TRUE;
+    renderer->viewport.x = 0;
+    renderer->viewport.y = 0;
+    renderer->viewport.w = texture->w;
+    renderer->viewport.h = texture->h;
+    D3D_UpdateViewport(renderer);
+    return 0;
+}
+
+static int
 D3D_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
 {
     D3D_RenderData *renderdata = (D3D_RenderData *) renderer->driverdata;
@@ -580,6 +668,11 @@
         usage = D3DUSAGE_DYNAMIC;
     } else
 #endif
+    if (texture->access == SDL_TEXTUREACCESS_TARGET) {
+        pool = D3DPOOL_DEFAULT;         // D3DPOOL_MANAGED does not work with usage=D3DUSAGE_RENDERTARGET
+        usage = D3DUSAGE_RENDERTARGET;
+    }
+    else
     {
         pool = D3DPOOL_MANAGED;
         usage = 0;
@@ -1187,6 +1280,13 @@
     D3D_RenderData *data = (D3D_RenderData *) renderer->driverdata;
 
     if (data) {
+        // Release the render target
+        IDirect3DSurface9_Release(data->defaultRenderTarget);
+        if (data->currentRenderTarget != NULL) {
+            IDirect3DSurface9_Release(data->currentRenderTarget);
+            data->currentRenderTarget = NULL;
+        }
+        
         if (data->device) {
             IDirect3DDevice9_Release(data->device);
         }
--- a/src/render/opengl/SDL_render_gl.c	Wed Jan 18 22:22:54 2012 -0500
+++ b/src/render/opengl/SDL_render_gl.c	Wed Jan 18 22:45:49 2012 -0500
@@ -66,6 +66,7 @@
                          const SDL_Rect * srcrect, const SDL_Rect * dstrect);
 static int GL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
                                Uint32 pixel_format, void * pixels, int pitch);
+static int GL_SetTargetTexture(SDL_Renderer * renderer, SDL_Texture * texture);
 static void GL_RenderPresent(SDL_Renderer * renderer);
 static void GL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture);
 static void GL_DestroyRenderer(SDL_Renderer * renderer);
@@ -82,6 +83,15 @@
      0}
 };
 
+typedef struct GL_FBOList GL_FBOList;
+
+struct GL_FBOList
+{
+    Uint32 w, h;
+    GLuint FBO;
+    GL_FBOList *next;
+};
+
 typedef struct
 {
     SDL_GLContext context;
@@ -91,6 +101,11 @@
         Uint32 color;
         int blendMode;
     } current;
+    
+    SDL_bool GL_EXT_framebuffer_object_supported;
+    GL_FBOList *framebuffers;
+    SDL_Texture *renderTarget;
+    SDL_Rect viewport_copy;
 
     /* OpenGL functions */
 #define SDL_PROC(ret,func,params) ret (APIENTRY *func) params;
@@ -101,6 +116,12 @@
     SDL_bool GL_ARB_multitexture_supported;
     PFNGLACTIVETEXTUREARBPROC glActiveTextureARB;
     GLint num_texture_units;
+    
+    PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT;
+    PFNGLDELETEFRAMEBUFFERSEXTPROC glDeleteFramebuffersEXT;
+    PFNGLFRAMEBUFFERTEXTURE2DEXTPROC glFramebufferTexture2DEXT;
+    PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT;
+    PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT;
 
     /* Shader support */
     GL_ShaderContext *shaders;
@@ -123,6 +144,8 @@
     SDL_bool yuv;
     GLuint utexture;
     GLuint vtexture;
+    
+    GL_FBOList *fbo;
 } GL_TextureData;
 
 
@@ -227,6 +250,29 @@
     data->glLoadIdentity();
 }
 
+
+GL_FBOList *
+GL_GetFBO(GL_RenderData *data, Uint32 w, Uint32 h)
+{
+    GL_FBOList *result = data->framebuffers;
+
+    while (result && ((result->w != w) || (result->h != h))) {
+        result = result->next;
+    }
+
+    if (!result) {
+        result = SDL_malloc(sizeof(GL_FBOList));
+        if (result) {
+            result->w = w;
+            result->h = h;
+            data->glGenFramebuffersEXT(1, &result->FBO);
+            result->next = data->framebuffers;
+            data->framebuffers = result;
+        }
+    }
+    return result;
+}
+
 SDL_Renderer *
 GL_CreateRenderer(SDL_Window * window, Uint32 flags)
 {
@@ -269,6 +315,7 @@
     renderer->RenderDrawLines = GL_RenderDrawLines;
     renderer->RenderFillRects = GL_RenderFillRects;
     renderer->RenderCopy = GL_RenderCopy;
+    renderer->SetTargetTexture = GL_SetTargetTexture;
     renderer->RenderReadPixels = GL_RenderReadPixels;
     renderer->RenderPresent = GL_RenderPresent;
     renderer->DestroyTexture = GL_DestroyTexture;
@@ -341,6 +388,22 @@
         renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_YV12;
         renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_IYUV;
     }
+    
+    if (SDL_GL_ExtensionSupported("GL_EXT_framebuffer_object")) {
+        data->GL_EXT_framebuffer_object_supported = SDL_TRUE;
+        data->glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)
+            SDL_GL_GetProcAddress("glGenFramebuffersEXT");
+        data->glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)
+            SDL_GL_GetProcAddress("glDeleteFramebuffersEXT");
+        data->glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)
+            SDL_GL_GetProcAddress("glFramebufferTexture2DEXT");
+        data->glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)
+            SDL_GL_GetProcAddress("glBindFramebufferEXT");
+       data->glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)
+            SDL_GL_GetProcAddress("glCheckFramebufferStatusEXT");
+    }
+    data->framebuffers = NULL;
+    data->renderTarget = NULL;
 
     /* Set up parameters for rendering */
     GL_ResetState(renderer);
@@ -403,6 +466,74 @@
 }
 
 static int
+GL_SetTargetTexture(SDL_Renderer * renderer, SDL_Texture * texture)
+{
+    GL_RenderData *data = (GL_RenderData *) renderer->driverdata;    
+    
+    GL_TextureData *texturedata;
+    GLenum status;
+
+    if (!renderer) return -1;
+    GL_ActivateRenderer(renderer);
+    
+    if (! data->GL_EXT_framebuffer_object_supported) {
+        SDL_Unsupported();
+        return -1;
+    }
+    
+    if (texture == NULL) {
+        if (data->renderTarget != NULL) {
+            data->renderTarget = NULL;
+            renderer->viewport = data->viewport_copy;
+            data->glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+            data->glMatrixMode(GL_PROJECTION);
+            data->glLoadIdentity();
+            data->glMatrixMode(GL_MODELVIEW);
+            data->glLoadIdentity();
+            data->glViewport(renderer->viewport.x, renderer->viewport.y, renderer->viewport.w, renderer->viewport.h);
+            data->glOrtho(0.0, (GLdouble) renderer->viewport.w, (GLdouble) renderer->viewport.h, 0.0, 0.0, 1.0);
+        }
+        return 0;
+    }
+    if (renderer != texture->renderer) return -1;
+    if (data->renderTarget==NULL) {
+        // Keep a copy of the default viewport to restore when texture==NULL
+        data->viewport_copy = renderer->viewport;
+    }
+    
+    
+    texturedata = (GL_TextureData *) texture->driverdata;
+    if (!texturedata) {
+        if (texture->native && texture->native->driverdata) {
+            texture = texture->native;
+            texturedata = texture->driverdata;
+        }
+        else return -1;
+    }
+    data->glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, texturedata->fbo->FBO);
+    /* TODO: check if texture pixel format allows this operation */
+    data->glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, texturedata->type, texturedata->texture, 0);
+    /* Check FBO status */
+    status = data->glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+    if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
+        return -1;
+    }
+
+    data->renderTarget = texture;
+    renderer->viewport.x = 0;
+    renderer->viewport.y = 0;
+    renderer->viewport.w = texture->w;
+    renderer->viewport.h = texture->h;
+    data->glMatrixMode(GL_PROJECTION);
+    data->glLoadIdentity();
+    data->glOrtho(0.0, (GLdouble) texture->w, 0.0, (GLdouble) texture->h, 0.0, 1.0);
+    data->glMatrixMode(GL_MODELVIEW);
+    data->glLoadIdentity();
+    data->glViewport(0, 0, texture->w, texture->h);    
+    return 0;
+}
+
+static int
 GL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
 {
     GL_RenderData *renderdata = (GL_RenderData *) renderer->driverdata;
@@ -446,10 +577,17 @@
     }
 
     texture->driverdata = data;
+    
+    if (texture->access == SDL_TEXTUREACCESS_TARGET) {
+        data->fbo = GL_GetFBO(renderdata, texture->w, texture->h);
+    } else {
+        data->fbo = NULL;
+    }
 
     renderdata->glGetError();
     renderdata->glGenTextures(1, &data->texture);
-    if (renderdata->GL_ARB_texture_rectangle_supported) {
+    if ((renderdata->GL_ARB_texture_rectangle_supported)
+        /*&& texture->access != SDL_TEXTUREACCESS_TARGET*/){
         data->type = GL_TEXTURE_RECTANGLE_ARB;
         texture_w = texture->w;
         texture_h = texture->h;
@@ -1013,6 +1151,13 @@
             GL_DestroyShaderContext(data->shaders);
         }
         if (data->context) {
+            while (data->framebuffers) {
+                GL_FBOList *nextnode = data->framebuffers->next;
+                /* delete the framebuffer object */
+                data->glDeleteFramebuffersEXT(1, &data->framebuffers->FBO);
+                SDL_free(data->framebuffers);
+                data->framebuffers = nextnode;
+            }            
             /* SDL_GL_MakeCurrent(0, NULL); *//* doesn't do anything */
             SDL_GL_DeleteContext(data->context);
         }
--- a/src/render/opengles/SDL_glesfuncs.h	Wed Jan 18 22:22:54 2012 -0500
+++ b/src/render/opengles/SDL_glesfuncs.h	Wed Jan 18 22:45:49 2012 -0500
@@ -27,5 +27,13 @@
 SDL_PROC(void, glTexSubImage2D, (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *))
 SDL_PROC(void, glVertexPointer, (GLint, GLenum, GLsizei, const GLvoid *))
 SDL_PROC(void, glViewport, (GLint, GLint, GLsizei, GLsizei))
+SDL_PROC(void, glBindFramebufferOES, (GLenum, GLuint))
+SDL_PROC(void, glFramebufferTexture2DOES, (GLenum, GLenum, GLenum, GLuint, GLint))
+SDL_PROC(GLenum, glCheckFramebufferStatusOES, (GLenum))
+SDL_PROC(void, glPushMatrix, (void))
+SDL_PROC(void, glTranslatef, (GLfloat, GLfloat, GLfloat))
+SDL_PROC(void, glRotatef, (GLfloat, GLfloat, GLfloat, GLfloat))
+SDL_PROC(void, glPopMatrix, (void))
+SDL_PROC(void, glDeleteFramebuffersOES, (GLsizei, const GLuint*))
 
 /* vi: set ts=4 sw=4 expandtab: */
--- a/src/render/opengles/SDL_render_gles.c	Wed Jan 18 22:22:54 2012 -0500
+++ b/src/render/opengles/SDL_render_gles.c	Wed Jan 18 22:45:49 2012 -0500
@@ -73,6 +73,16 @@
 static void GLES_DestroyTexture(SDL_Renderer * renderer,
                                 SDL_Texture * texture);
 static void GLES_DestroyRenderer(SDL_Renderer * renderer);
+static int GLES_SetTargetTexture(SDL_Renderer * renderer, SDL_Texture * texture);
+
+typedef struct GLES_FBOList GLES_FBOList;
+
+struct GLES_FBOList
+{
+   Uint32 w, h;
+   GLuint FBO;
+   GLES_FBOList *next;
+};
 
 
 SDL_RenderDriver GLES_RenderDriver = {
@@ -98,6 +108,10 @@
 #define SDL_PROC(ret,func,params) ret (APIENTRY *func) params;
 #include "SDL_glesfuncs.h"
 #undef SDL_PROC
+    SDL_bool GL_OES_framebuffer_object_supported;
+    GLES_FBOList *framebuffers;
+    SDL_Texture *renderTarget;
+    SDL_Rect viewport_copy;
 
     SDL_bool useDrawTexture;
     SDL_bool GL_OES_draw_texture_supported;
@@ -113,6 +127,7 @@
     GLenum formattype;
     void *pixels;
     int pitch;
+    GLES_FBOList *fbo;
 } GLES_TextureData;
 
 static void
@@ -179,6 +194,27 @@
 
 static SDL_GLContext SDL_CurrentContext = NULL;
 
+GLES_FBOList *
+GLES_GetFBO(GLES_RenderData *data, Uint32 w, Uint32 h)
+{
+   GLES_FBOList *result = data->framebuffers;
+   while ((result) && ((result->w != w) || (result->h != h)) )
+   {
+       result = result->next;
+   }
+   if (result == NULL)
+   {
+       result = SDL_malloc(sizeof(GLES_FBOList));
+       result->w = w;
+       result->h = h;
+       glGenFramebuffersOES(1, &result->FBO);
+       result->next = data->framebuffers;
+       data->framebuffers = result;
+   }
+   return result;
+}
+
+
 static int
 GLES_ActivateRenderer(SDL_Renderer * renderer)
 {
@@ -221,6 +257,71 @@
     data->glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 }
 
+static int
+GLES_SetTargetTexture(SDL_Renderer * renderer, SDL_Texture * texture)
+{
+    GLES_RenderData *data = (GLES_RenderData *) renderer->driverdata;
+    int w, h;
+    GLES_TextureData *texturedata = NULL;
+    GLenum status;
+
+    if (!renderer) return -1;
+    GLES_ActivateRenderer(renderer);
+    if (! data->GL_OES_framebuffer_object_supported) {
+        SDL_Unsupported();
+        return -1;
+    }
+
+    if (texture == NULL) {
+        if (data->renderTarget != NULL) {
+            data->renderTarget = NULL;
+            renderer->viewport = data->viewport_copy;
+            data->glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
+            data->glMatrixMode(GL_PROJECTION);
+            data->glLoadIdentity();
+            data->glMatrixMode(GL_MODELVIEW);
+            data->glLoadIdentity();
+            data->glViewport(renderer->viewport.x, renderer->viewport.y, renderer->viewport.w, renderer->viewport.h);
+            data->glOrthof(0.0, (GLfloat) renderer->viewport.w, (GLfloat) renderer->viewport.h, 0.0, 0.0, 1.0);
+        }
+        return 0;
+    }
+
+    if (renderer != texture->renderer) return -1;
+    if (data->renderTarget==NULL) {
+        // Keep a copy of the default viewport to restore when texture==NULL
+        data->viewport_copy = renderer->viewport;
+    }
+    texturedata = (GLES_TextureData *) texture->driverdata;
+    if (!texturedata) {
+        if (texture->native && texture->native->driverdata) {
+            texture = texture->native;
+            texturedata = texture->driverdata;
+        }
+        else return -1;
+    }
+    data->glBindFramebufferOES(GL_FRAMEBUFFER_OES, texturedata->fbo->FBO);
+    /* TODO: check if texture pixel format allows this operation */
+    data->glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, texturedata->type, texturedata->texture, 0);
+    /* Check FBO status */
+    status = data->glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
+    if (status != GL_FRAMEBUFFER_COMPLETE_OES) {
+        return -1;
+    }
+    data->renderTarget = texture;
+    renderer->viewport.x = 0;
+    renderer->viewport.y = 0;
+    renderer->viewport.w = texture->w;
+    renderer->viewport.h = texture->h;
+    data->glMatrixMode(GL_PROJECTION);
+    data->glLoadIdentity();
+    data->glOrthof(0.0, (GLfloat) texture->w, 0.0, (GLfloat) texture->h, 0.0, 1.0);
+    data->glMatrixMode(GL_MODELVIEW);
+    data->glLoadIdentity();
+    data->glViewport(0, 0, texture->w, texture->h);
+    return 0;
+}
+
 SDL_Renderer *
 GLES_CreateRenderer(SDL_Window * window, Uint32 flags)
 {
@@ -274,6 +375,7 @@
     renderer->info.flags = SDL_RENDERER_ACCELERATED;
     renderer->driverdata = data;
     renderer->window = window;
+    renderer->SetTargetTexture = GLES_SetTargetTexture;
 
     data->context = SDL_GL_CreateContext(window);
     if (!data->context) {
@@ -317,6 +419,12 @@
     data->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value);
     renderer->info.max_texture_height = value;
 
+    if (SDL_GL_ExtensionSupported("GL_OES_framebuffer_object")) {
+        data->GL_OES_framebuffer_object_supported = SDL_TRUE;
+    }
+    data->framebuffers = NULL;
+    data->renderTarget = NULL;
+
     /* Set up parameters for rendering */
     GLES_ResetState(renderer);
 
@@ -335,7 +443,7 @@
 
     if (event->event == SDL_WINDOWEVENT_MINIMIZED) {
         /* According to Apple documentation, we need to finish drawing NOW! */
-	data->glFinish();
+        data->glFinish();
     }
 }
 
@@ -403,6 +511,11 @@
     }
 
     texture->driverdata = data;
+    if (texture->access == SDL_TEXTUREACCESS_TARGET) {
+       data->fbo = GLES_GetFBO(renderer->driverdata, texture->w, texture->h);
+    } else {
+       data->fbo = NULL;
+    }
 
     renderdata->glGetError();
     renderdata->glEnable(GL_TEXTURE_2D);
@@ -757,15 +870,26 @@
         SDL_Window *window = renderer->window;
 
         SDL_GetWindowSize(window, &w, &h);
-        cropRect[0] = srcrect->x;
-        cropRect[1] = srcrect->y + srcrect->h;
-        cropRect[2] = srcrect->w;
-        cropRect[3] = -srcrect->h;
-        data->glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES,
-                               cropRect);
-        data->glDrawTexiOES(renderer->viewport.x + dstrect->x,
-	              h - (renderer->viewport.y + dstrect->y) - dstrect->h, 0,
-                            dstrect->w, dstrect->h);
+        if (data->renderTarget != NULL) {
+            cropRect[0] = srcrect->x;
+            cropRect[1] = srcrect->y;
+            cropRect[2] = srcrect->w;
+            cropRect[3] = srcrect->h;
+            data->glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES,
+                                   cropRect);
+            data->glDrawTexiOES(renderer->viewport.x + dstrect->x, renderer->viewport.y + dstrect->y, 0,
+                                dstrect->w, dstrect->h);
+        } else {
+            cropRect[0] = srcrect->x;
+            cropRect[1] = srcrect->y + srcrect->h;
+            cropRect[2] = srcrect->w;
+            cropRect[3] = -srcrect->h;
+            data->glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES,
+                                   cropRect);
+            data->glDrawTexiOES(renderer->viewport.x + dstrect->x,
+                        h - (renderer->viewport.y + dstrect->y) - dstrect->h, 0,
+                        dstrect->w, dstrect->h);
+        }
     } else {
 
         minx = dstrect->x;
@@ -901,6 +1025,12 @@
 
     if (data) {
         if (data->context) {
+            while (data->framebuffers) {
+               GLES_FBOList *nextnode = data->framebuffers->next;
+               data->glDeleteFramebuffersOES(1, &data->framebuffers->FBO);
+               SDL_free(data->framebuffers);
+               data->framebuffers = nextnode;
+            }
             SDL_GL_DeleteContext(data->context);
         }
         SDL_free(data);
--- a/src/render/opengles2/SDL_gles2funcs.h	Wed Jan 18 22:22:54 2012 -0500
+++ b/src/render/opengles2/SDL_gles2funcs.h	Wed Jan 18 22:45:49 2012 -0500
@@ -40,3 +40,7 @@
 SDL_PROC(void, glUseProgram, (GLuint))
 SDL_PROC(void, glVertexAttribPointer, (GLuint, GLint, GLenum, GLboolean, GLsizei, const void *))
 SDL_PROC(void, glViewport, (GLint, GLint, GLsizei, GLsizei))
+SDL_PROC(void, glBindFramebuffer, (GLenum, GLuint))
+SDL_PROC(void, glFramebufferTexture2D, (GLenum, GLenum, GLenum, GLuint, GLint))
+SDL_PROC(GLenum, glCheckFramebufferStatus, (GLenum))
+SDL_PROC(void, glDeleteFramebuffers, (GLsizei, const GLuint *))
--- a/src/render/opengles2/SDL_render_gles2.c	Wed Jan 18 22:22:54 2012 -0500
+++ b/src/render/opengles2/SDL_render_gles2.c	Wed Jan 18 22:45:49 2012 -0500
@@ -55,6 +55,15 @@
  * Context structures                                                                            *
  *************************************************************************************************/
 
+typedef struct GLES2_FBOList GLES2_FBOList;
+
+struct GLES2_FBOList
+{
+   Uint32 w, h;
+   GLuint FBO;
+   GLES2_FBOList *next;
+};
+
 typedef struct GLES2_TextureData
 {
     GLenum texture;
@@ -63,6 +72,7 @@
     GLenum pixel_type;
     void *pixel_data;
     size_t pitch;
+    GLES2_FBOList *fbo;
 } GLES2_TextureData;
 
 typedef struct GLES2_ShaderCacheEntry
@@ -134,6 +144,9 @@
 #define SDL_PROC(ret,func,params) ret (APIENTRY *func) params;
 #include "SDL_gles2funcs.h"
 #undef SDL_PROC
+    GLES2_FBOList *framebuffers;
+    SDL_Texture *renderTarget;
+    SDL_Rect viewport_copy;
 
     int shader_format_count;
     GLenum *shader_formats;
@@ -154,6 +167,8 @@
 static int GLES2_UpdateViewport(SDL_Renderer * renderer);
 static void GLES2_DestroyRenderer(SDL_Renderer *renderer);
 
+static int GLES2_SetTargetTexture(SDL_Renderer * renderer, SDL_Texture * texture);
+
 static SDL_GLContext SDL_CurrentContext = NULL;
 
 static int GLES2_LoadFunctions(GLES2_DriverContext * data)
@@ -176,7 +191,7 @@
             SDL_SetError("Couldn't load GLES2 function %s: %s\n", #func, SDL_GetError()); \
             return -1; \
         } \
-    } while ( 0 );  
+    } while ( 0 );
 #endif /* _SDL_NOGETPROCADDR_ */
 
 #include "SDL_gles2funcs.h"
@@ -184,6 +199,26 @@
     return 0;
 }
 
+GLES2_FBOList *
+GLES2_GetFBO(GLES2_DriverContext *data, Uint32 w, Uint32 h)
+{
+   GLES2_FBOList *result = data->framebuffers;
+   while ((result) && ((result->w != w) || (result->h != h)) )
+   {
+       result = result->next;
+   }
+   if (result == NULL)
+   {
+       result = SDL_malloc(sizeof(GLES2_FBOList));
+       result->w = w;
+       result->h = h;
+       glGenFramebuffers(1, &result->FBO);
+       result->next = data->framebuffers;
+       data->framebuffers = result;
+   }
+   return result;
+}
+
 static int
 GLES2_ActivateRenderer(SDL_Renderer * renderer)
 {
@@ -207,7 +242,7 @@
 GLES2_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
 {
     GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
-    
+
     if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED) {
         /* Rebind the context to the window area */
         SDL_CurrentContext = NULL;
@@ -267,6 +302,12 @@
             }
         }
         if (rdata->context) {
+            while (rdata->framebuffers) {
+                GLES2_FBOList *nextnode = rdata->framebuffers->next;
+                rdata->glDeleteFramebuffers(1, &rdata->framebuffers->FBO);
+                SDL_free(rdata->framebuffers);
+                rdata->framebuffers = nextnode;
+            }
             SDL_GL_DeleteContext(rdata->context);
         }
         if (rdata->shader_formats) {
@@ -371,6 +412,13 @@
         return -1;
     }
     texture->driverdata = tdata;
+
+    if (texture->access == SDL_TEXTUREACCESS_TARGET) {
+       tdata->fbo = GLES2_GetFBO(renderer->driverdata, texture->w, texture->h);
+    } else {
+       tdata->fbo = NULL;
+    }
+
     return 0;
 }
 
@@ -433,7 +481,7 @@
     int y;
 
     GLES2_ActivateRenderer(renderer);
-    
+
     /* Bail out if we're supposed to update an empty rectangle */
     if (rect->w <= 0 || rect->h <= 0)
         return 0;
@@ -543,7 +591,7 @@
     entry->vertex_shader = vertex;
     entry->fragment_shader = fragment;
     entry->blend_mode = blendMode;
-    
+
     /* Create the program and link it */
     rdata->glGetError();
     entry->id = rdata->glCreateProgram();
@@ -560,7 +608,7 @@
         SDL_free(entry);
         return NULL;
     }
-    
+
     /* Predetermine locations of uniform variables */
     entry->uniform_locations[GLES2_UNIFORM_PROJECTION] =
         rdata->glGetUniformLocation(entry->id, "u_projection");
@@ -625,7 +673,7 @@
         SDL_SetError("No shader matching the requested characteristics was found");
         return NULL;
     }
-    
+
     /* Find a matching shader instance that's supported on this hardware */
     for (i = 0; i < shader->instance_count && !instance; ++i)
     {
@@ -872,7 +920,7 @@
     GLES2_DriverContext *rdata = (GLES2_DriverContext *)renderer->driverdata;
 
     GLES2_ActivateRenderer(renderer);
-    
+
     rdata->glClearColor((GLfloat) renderer->r * inv255f,
                  (GLfloat) renderer->g * inv255f,
                  (GLfloat) renderer->b * inv255f,
@@ -1080,20 +1128,83 @@
 
     /* Activate an appropriate shader and set the projection matrix */
     blendMode = texture->blendMode;
-    switch (texture->format)
-    {
-        case SDL_PIXELFORMAT_ABGR8888:
-            sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;
-            break;
-        case SDL_PIXELFORMAT_ARGB8888:
-            sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB;
-            break;
-        case SDL_PIXELFORMAT_BGR888:
-            sourceType = GLES2_IMAGESOURCE_TEXTURE_BGR;
-            break;
-        case SDL_PIXELFORMAT_RGB888:
-            sourceType = GLES2_IMAGESOURCE_TEXTURE_RGB;
-            break;
+    if (rdata->renderTarget!=NULL) {
+        /* Check if we need to do color mapping between the source and render target textures */
+        if (rdata->renderTarget->format != texture->format) {
+            switch (texture->format)
+            {
+            case SDL_PIXELFORMAT_ABGR8888:
+                switch (rdata->renderTarget->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_ARGB8888:
+                switch (rdata->renderTarget->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_BGR888:
+                switch (rdata->renderTarget->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;
+            case SDL_PIXELFORMAT_RGB888:
+                switch (rdata->renderTarget->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;
+            }
+        }
+        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_ABGR8888:
+                sourceType = GLES2_IMAGESOURCE_TEXTURE_ABGR;
+                break;
+            case SDL_PIXELFORMAT_ARGB8888:
+                sourceType = GLES2_IMAGESOURCE_TEXTURE_ARGB;
+                break;
+            case SDL_PIXELFORMAT_BGR888:
+                sourceType = GLES2_IMAGESOURCE_TEXTURE_BGR;
+                break;
+            case SDL_PIXELFORMAT_RGB888:
+                sourceType = GLES2_IMAGESOURCE_TEXTURE_RGB;
+                break;
+        }
     }
     if (GLES2_SelectProgram(renderer, sourceType, blendMode) < 0)
         return -1;
@@ -1119,15 +1230,29 @@
     GLES2_SetTexCoords(rdata, SDL_TRUE);
 
     /* Emit the textured quad */
-    vertices[0] = (GLfloat)dstrect->x;
-    vertices[1] = (GLfloat)dstrect->y;
-    vertices[2] = (GLfloat)(dstrect->x + dstrect->w);
-    vertices[3] = (GLfloat)dstrect->y;
-    vertices[4] = (GLfloat)dstrect->x;
-    vertices[5] = (GLfloat)(dstrect->y + dstrect->h);
-    vertices[6] = (GLfloat)(dstrect->x + dstrect->w);
-    vertices[7] = (GLfloat)(dstrect->y + dstrect->h);
+    if (rdata->renderTarget!=NULL) {
+        // Flip the texture vertically to compensate for the inversion it'll be subjected to later when it's rendered to the screen
+        vertices[0] = (GLfloat)dstrect->x;
+        vertices[1] = (GLfloat)renderer->viewport.h-dstrect->y;
+        vertices[2] = (GLfloat)(dstrect->x + dstrect->w);
+        vertices[3] = (GLfloat)renderer->viewport.h-dstrect->y;
+        vertices[4] = (GLfloat)dstrect->x;
+        vertices[5] = (GLfloat)renderer->viewport.h-(dstrect->y + dstrect->h);
+        vertices[6] = (GLfloat)(dstrect->x + dstrect->w);
+        vertices[7] = (GLfloat)renderer->viewport.h-(dstrect->y + dstrect->h);
+    }
+    else {
+        vertices[0] = (GLfloat)dstrect->x;
+        vertices[1] = (GLfloat)dstrect->y;
+        vertices[2] = (GLfloat)(dstrect->x + dstrect->w);
+        vertices[3] = (GLfloat)dstrect->y;
+        vertices[4] = (GLfloat)dstrect->x;
+        vertices[5] = (GLfloat)(dstrect->y + dstrect->h);
+        vertices[6] = (GLfloat)(dstrect->x + dstrect->w);
+        vertices[7] = (GLfloat)(dstrect->y + dstrect->h);
+    }
     rdata->glVertexAttribPointer(GLES2_ATTRIBUTE_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
+
     texCoords[0] = srcrect->x / (GLfloat)texture->w;
     texCoords[1] = srcrect->y / (GLfloat)texture->h;
     texCoords[2] = (srcrect->x + srcrect->w) / (GLfloat)texture->w;
@@ -1231,6 +1356,60 @@
     rdata->glDisableVertexAttribArray(GLES2_ATTRIBUTE_TEXCOORD);
 }
 
+static int
+GLES2_SetTargetTexture(SDL_Renderer * renderer, SDL_Texture * texture)
+{
+    GLES2_DriverContext *data = (GLES2_DriverContext *) renderer->driverdata;
+    GLES2_TextureData *texturedata = NULL;
+    GLenum status;
+    SDL_BlendMode blendMode;
+
+    if (!renderer) return -1;
+
+    blendMode = texture->blendMode;
+    if (texture == NULL) {
+        if (data->renderTarget!=NULL) {
+            data->glBindFramebuffer(GL_FRAMEBUFFER, 0);
+            renderer->viewport = data->viewport_copy;
+            data->renderTarget = NULL;
+            data->glViewport(renderer->viewport.x, renderer->viewport.y, renderer->viewport.w, renderer->viewport.h);
+            if(data->current_program) GLES2_SetOrthographicProjection(renderer);
+        }
+        return 0;
+    }
+    if (renderer != texture->renderer) return -1;
+    if (data->renderTarget==NULL) {
+        // Keep a copy of the default viewport to restore when texture==NULL
+        data->viewport_copy = renderer->viewport;
+    }
+
+    texturedata = (GLES2_TextureData *) texture->driverdata;
+    if (!texturedata) {
+        if (texture->native && texture->native->driverdata) {
+            texture = texture->native;
+            texturedata = texture->driverdata;
+        }
+        else return -1;
+    }
+    data->glBindFramebuffer(GL_FRAMEBUFFER, texturedata->fbo->FBO);
+    /* TODO: check if texture pixel format allows this operation */
+    data->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texturedata->texture_type, texturedata->texture, 0);
+    /* Check FBO status */
+    status = data->glCheckFramebufferStatus(GL_FRAMEBUFFER);
+    if (status != GL_FRAMEBUFFER_COMPLETE) {
+        return -1;
+    }
+    
+    renderer->viewport.x = 0;
+    renderer->viewport.y = 0;
+    renderer->viewport.w = texture->w;
+    renderer->viewport.h = texture->h;
+    data->renderTarget = texture;
+    data->glViewport(0, 0, texture->w, texture->h);
+    if(data->current_program) GLES2_SetOrthographicProjection(renderer);
+    return 0;
+}
+
 static SDL_Renderer *
 GLES2_CreateRenderer(SDL_Window *window, Uint32 flags)
 {
@@ -1241,10 +1420,10 @@
     GLboolean hasCompiler;
 #endif
     Uint32 windowFlags;
-    
+
     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
-    
+
     windowFlags = SDL_GetWindowFlags(window);
     if (!(windowFlags & SDL_WINDOW_OPENGL)) {
         if (SDL_RecreateWindow(window, windowFlags | SDL_WINDOW_OPENGL) < 0) {
@@ -1331,6 +1510,9 @@
         rdata->shader_formats[nFormats - 1] = (GLenum)-1;
 #endif /* ZUNE_HD */
 
+    rdata->framebuffers = NULL;
+    rdata->renderTarget = NULL;
+
     /* Populate the function pointers for the module */
     renderer->WindowEvent         = &GLES2_WindowEvent;
     renderer->CreateTexture       = &GLES2_CreateTexture;
@@ -1347,6 +1529,7 @@
     renderer->RenderPresent       = &GLES2_RenderPresent;
     renderer->DestroyTexture      = &GLES2_DestroyTexture;
     renderer->DestroyRenderer     = &GLES2_DestroyRenderer;
+    renderer->SetTargetTexture    = &GLES2_SetTargetTexture;
 
     GLES2_ResetState(renderer);
 
--- a/test/Makefile.in	Wed Jan 18 22:22:54 2012 -0500
+++ b/test/Makefile.in	Wed Jan 18 22:45:49 2012 -0500
@@ -44,6 +44,7 @@
 	testoverlay2$(EXE) \
 	testplatform$(EXE) \
 	testpower$(EXE) \
+	testrendertarget$(EXE) \
 	testresample$(EXE) \
 	testscale$(EXE) \
 	testsem$(EXE) \
@@ -181,6 +182,9 @@
 testpower$(EXE): $(srcdir)/testpower.c
 	$(CC) -o $@ $? $(CFLAGS) $(LIBS)
 
+testrendertarget$(EXE): $(srcdir)/testrendertarget.c $(srcdir)/common.c
+	$(CC) -o $@ $(srcdir)/testrendertarget.c $(srcdir)/common.c $(CFLAGS) $(LIBS)
+
 testscale$(EXE): $(srcdir)/testscale.c $(srcdir)/common.c
 	$(CC) -o $@ $(srcdir)/testscale.c $(srcdir)/common.c $(CFLAGS) $(LIBS)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/testrendertarget.c	Wed Jan 18 22:45:49 2012 -0500
@@ -0,0 +1,204 @@
+/*
+  Copyright (C) 1997-2011 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely.
+*/
+/* Simple program:  Move N sprites around on the screen as fast as possible */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "SDL.h"
+#include "common.h"
+
+#define WINDOW_WIDTH    640
+#define WINDOW_HEIGHT   480
+
+static CommonState *state;
+
+typedef struct {
+    SDL_Window *window;
+    SDL_Renderer *renderer;
+    SDL_Texture *background;
+    SDL_Texture *sprite;
+    SDL_Rect sprite_rect;
+    int scale_direction;
+} DrawState;
+
+/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
+static void
+quit(int rc)
+{
+    CommonQuit(state);
+    exit(rc);
+}
+
+SDL_Texture *
+LoadTexture(SDL_Renderer *renderer, char *file, SDL_bool transparent)
+{
+    SDL_Surface *temp;
+    SDL_Texture *texture;
+
+    /* Load the sprite image */
+    temp = SDL_LoadBMP(file);
+    if (temp == NULL) {
+        fprintf(stderr, "Couldn't load %s: %s", file, SDL_GetError());
+        return NULL;
+    }
+
+    /* Set transparent pixel as the pixel at (0,0) */
+    if (transparent) {
+        if (temp->format->palette) {
+            SDL_SetColorKey(temp, SDL_TRUE, *(Uint8 *) temp->pixels);
+        } else {
+            switch (temp->format->BitsPerPixel) {
+            case 15:
+                SDL_SetColorKey(temp, SDL_TRUE,
+                                (*(Uint16 *) temp->pixels) & 0x00007FFF);
+                break;
+            case 16:
+                SDL_SetColorKey(temp, SDL_TRUE, *(Uint16 *) temp->pixels);
+                break;
+            case 24:
+                SDL_SetColorKey(temp, SDL_TRUE,
+                                (*(Uint32 *) temp->pixels) & 0x00FFFFFF);
+                break;
+            case 32:
+                SDL_SetColorKey(temp, SDL_TRUE, *(Uint32 *) temp->pixels);
+                break;
+            }
+        }
+    }
+
+    /* Create textures from the image */
+    texture = SDL_CreateTextureFromSurface(renderer, temp);
+    if (!texture) {
+        fprintf(stderr, "Couldn't create texture: %s\n", SDL_GetError());
+        SDL_FreeSurface(temp);
+        return NULL;
+    }
+    SDL_FreeSurface(temp);
+
+    /* We're ready to roll. :) */
+    return texture;
+}
+
+void
+Draw(DrawState *s)
+{
+    SDL_Rect viewport;
+    SDL_Texture *target;
+
+    SDL_RenderGetViewport(s->renderer, &viewport);
+
+    target = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, viewport.w, viewport.h);
+    SDL_SetTargetTexture(s->renderer, target);
+
+    /* Draw the background */
+    SDL_RenderCopy(s->renderer, s->background, NULL, NULL);
+
+    /* Scale and draw the sprite */
+    s->sprite_rect.w += s->scale_direction;
+    s->sprite_rect.h += s->scale_direction;
+    if (s->scale_direction > 0) {
+        if (s->sprite_rect.w >= viewport.w || s->sprite_rect.h >= viewport.h) {
+            s->scale_direction = -1;
+        }
+    } else {
+        if (s->sprite_rect.w <= 1 || s->sprite_rect.h <= 1) {
+            s->scale_direction = 1;
+        }
+    }
+    s->sprite_rect.x = (viewport.w - s->sprite_rect.w) / 2;
+    s->sprite_rect.y = (viewport.h - s->sprite_rect.h) / 2;
+
+    SDL_RenderCopy(s->renderer, s->sprite, NULL, &s->sprite_rect);
+
+    SDL_SetTargetTexture(s->renderer, NULL);
+    SDL_RenderCopy(s->renderer, target, NULL, NULL);
+    SDL_DestroyTexture(target);
+
+    /* Update the screen! */
+    SDL_RenderPresent(s->renderer);
+}
+
+int
+main(int argc, char *argv[])
+{
+    DrawState *drawstates;
+    int i, done;
+    SDL_Event event;
+    int frames;
+    Uint32 then, now;
+
+    /* Initialize test framework */
+    state = CommonCreateState(argv, SDL_INIT_VIDEO);
+    if (!state) {
+        return 1;
+    }
+    for (i = 1; i < argc;) {
+        int consumed;
+
+        consumed = CommonArg(state, i);
+        if (consumed == 0) {
+            fprintf(stderr, "Usage: %s %s\n", argv[0], CommonUsage(state));
+            return 1;
+        }
+        i += consumed;
+    }
+    if (!CommonInit(state)) {
+        quit(2);
+    }
+
+    drawstates = SDL_stack_alloc(DrawState, state->num_windows);
+    for (i = 0; i < state->num_windows; ++i) {
+        DrawState *drawstate = &drawstates[i];
+
+        drawstate->window = state->windows[i];
+        drawstate->renderer = state->renderers[i];
+        drawstate->sprite = LoadTexture(drawstate->renderer, "icon.bmp", SDL_TRUE);
+        drawstate->background = LoadTexture(drawstate->renderer, "sample.bmp", SDL_FALSE);
+        if (!drawstate->sprite || !drawstate->background) {
+            quit(2);
+        }
+        SDL_QueryTexture(drawstate->sprite, NULL, NULL,
+                         &drawstate->sprite_rect.w, &drawstate->sprite_rect.h);
+        drawstate->scale_direction = 1;
+    }
+
+    /* Main render loop */
+    frames = 0;
+    then = SDL_GetTicks();
+    done = 0;
+    while (!done) {
+        /* Check for events */
+        ++frames;
+        while (SDL_PollEvent(&event)) {
+            CommonEvent(state, &event, &done);
+        }
+        for (i = 0; i < state->num_windows; ++i) {
+            Draw(&drawstates[i]);
+        }
+    }
+
+    /* Print out some timing information */
+    now = SDL_GetTicks();
+    if (now > then) {
+        double fps = ((double) frames * 1000) / (now - then);
+        printf("%2.2f frames per second\n", fps);
+    }
+
+    SDL_stack_free(drawstates);
+
+    quit(0);
+    return 0;
+}
+
+/* vi: set ts=4 sw=4 expandtab: */