From 3743e40e4071b53012c44e7ac41aabeded0f6d2b Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 18 Jan 2012 22:45:49 -0500 Subject: [PATCH] Implementation of render targets, by Mason Wheeler and Gabriel Jacobo Thanks guys! --- include/SDL_render.h | 28 ++- src/render/SDL_render.c | 33 +++- src/render/SDL_sysrender.h | 1 + src/render/direct3d/SDL_render_d3d.c | 100 ++++++++++ src/render/opengl/SDL_render_gl.c | 147 +++++++++++++- src/render/opengles/SDL_glesfuncs.h | 8 + src/render/opengles/SDL_render_gles.c | 150 ++++++++++++++- src/render/opengles2/SDL_gles2funcs.h | 4 + src/render/opengles2/SDL_render_gles2.c | 245 +++++++++++++++++++++--- test/Makefile.in | 4 + test/testrendertarget.c | 204 ++++++++++++++++++++ 11 files changed, 879 insertions(+), 45 deletions(-) create mode 100644 test/testrendertarget.c diff --git a/include/SDL_render.h b/include/SDL_render.h index a80520763..51b641b61 100644 --- a/include/SDL_render.h +++ b/include/SDL_render.h @@ -88,7 +88,8 @@ typedef struct SDL_RendererInfo 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 @@ extern DECLSPEC int SDLCALL SDL_RenderCopy(SDL_Renderer * renderer, 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. * diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 1648db8d4..4ac83c80c 100755 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -1014,9 +1014,9 @@ int 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 @@ SDL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect, 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) { diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h index 0afd28dc2..0c34952a2 100755 --- a/src/render/SDL_sysrender.h +++ b/src/render/SDL_sysrender.h @@ -87,6 +87,7 @@ struct SDL_Renderer 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); diff --git a/src/render/direct3d/SDL_render_d3d.c b/src/render/direct3d/SDL_render_d3d.c index 69179c8ee..11ce494d5 100755 --- a/src/render/direct3d/SDL_render_d3d.c +++ b/src/render/direct3d/SDL_render_d3d.c @@ -111,6 +111,7 @@ static int D3D_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, 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 @@ typedef struct 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 @@ D3D_CreateRenderer(SDL_Window * window, Uint32 flags) 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 @@ D3D_CreateRenderer(SDL_Window * window, Uint32 flags) 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 @@ D3D_CreateRenderer(SDL_Window * window, Uint32 flags) 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; @@ -554,6 +568,80 @@ GetScaleQuality(void) } } +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) { @@ -580,6 +668,11 @@ D3D_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture) 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_DestroyRenderer(SDL_Renderer * renderer) 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); } diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c index 5236f241c..952dcc8eb 100755 --- a/src/render/opengl/SDL_render_gl.c +++ b/src/render/opengl/SDL_render_gl.c @@ -66,6 +66,7 @@ static int GL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, 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 @@ SDL_RenderDriver GL_RenderDriver = { 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 @@ typedef struct 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 @@ typedef struct 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 @@ typedef struct SDL_bool yuv; GLuint utexture; GLuint vtexture; + + GL_FBOList *fbo; } GL_TextureData; @@ -227,6 +250,29 @@ GL_ResetState(SDL_Renderer *renderer) 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 @@ GL_CreateRenderer(SDL_Window * window, Uint32 flags) 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 @@ GL_CreateRenderer(SDL_Window * window, Uint32 flags) 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); @@ -402,6 +465,74 @@ GetScaleQuality(void) } } +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) { @@ -446,10 +577,17 @@ GL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture) } 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_DestroyRenderer(SDL_Renderer * renderer) 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); } diff --git a/src/render/opengles/SDL_glesfuncs.h b/src/render/opengles/SDL_glesfuncs.h index ebf57cfb3..4f2b2ce34 100644 --- a/src/render/opengles/SDL_glesfuncs.h +++ b/src/render/opengles/SDL_glesfuncs.h @@ -27,5 +27,13 @@ SDL_PROC(void, glTexParameteriv, (GLenum, GLenum, const GLint *)) 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: */ diff --git a/src/render/opengles/SDL_render_gles.c b/src/render/opengles/SDL_render_gles.c index d76416170..66f770181 100755 --- a/src/render/opengles/SDL_render_gles.c +++ b/src/render/opengles/SDL_render_gles.c @@ -73,6 +73,16 @@ static void GLES_RenderPresent(SDL_Renderer * renderer); 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 @@ typedef struct #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 @@ typedef struct GLenum formattype; void *pixels; int pitch; + GLES_FBOList *fbo; } GLES_TextureData; static void @@ -179,6 +194,27 @@ static int GLES_LoadFunctions(GLES_RenderData * data) 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 @@ GLES_ResetState(SDL_Renderer *renderer) 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 @@ GLES_CreateRenderer(SDL_Window * window, Uint32 flags) 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 @@ GLES_CreateRenderer(SDL_Window * window, Uint32 flags) 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 @@ GLES_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event) if (event->event == SDL_WINDOWEVENT_MINIMIZED) { /* According to Apple documentation, we need to finish drawing NOW! */ - data->glFinish(); + data->glFinish(); } } @@ -403,6 +511,11 @@ GLES_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture) } 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 @@ GLES_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, 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 @@ GLES_DestroyRenderer(SDL_Renderer * renderer) 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); diff --git a/src/render/opengles2/SDL_gles2funcs.h b/src/render/opengles2/SDL_gles2funcs.h index fe143c957..e64528a46 100644 --- a/src/render/opengles2/SDL_gles2funcs.h +++ b/src/render/opengles2/SDL_gles2funcs.h @@ -40,3 +40,7 @@ SDL_PROC(void, glUniformMatrix4fv, (GLint, GLsizei, GLboolean, const GLfloat *)) 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 *)) diff --git a/src/render/opengles2/SDL_render_gles2.c b/src/render/opengles2/SDL_render_gles2.c index 2e59572aa..3a767644f 100755 --- a/src/render/opengles2/SDL_render_gles2.c +++ b/src/render/opengles2/SDL_render_gles2.c @@ -55,6 +55,15 @@ SDL_RenderDriver GLES2_RenderDriver = { * 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 @@ typedef struct GLES2_TextureData GLenum pixel_type; void *pixel_data; size_t pitch; + GLES2_FBOList *fbo; } GLES2_TextureData; typedef struct GLES2_ShaderCacheEntry @@ -134,6 +144,9 @@ typedef struct GLES2_DriverContext #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 void GLES2_WindowEvent(SDL_Renderer * renderer, 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 @@ static int GLES2_LoadFunctions(GLES2_DriverContext * data) 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 @@ static int GLES2_LoadFunctions(GLES2_DriverContext * data) 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 @@ static void 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 @@ GLES2_DestroyRenderer(SDL_Renderer *renderer) } } 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 @@ GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture) 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 @@ GLES2_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect 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 @@ GLES2_CacheProgram(SDL_Renderer *renderer, GLES2_ShaderCacheEntry *vertex, 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 @@ GLES2_CacheProgram(SDL_Renderer *renderer, GLES2_ShaderCacheEntry *vertex, 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 @@ GLES2_CacheShader(SDL_Renderer *renderer, GLES2_ShaderType type, SDL_BlendMode b 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_RenderClear(SDL_Renderer * renderer) 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 @@ GLES2_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *s /* 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_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *s 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 @@ GLES2_ResetState(SDL_Renderer *renderer) 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 @@ GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) 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 @@ GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) 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 @@ GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->RenderPresent = &GLES2_RenderPresent; renderer->DestroyTexture = &GLES2_DestroyTexture; renderer->DestroyRenderer = &GLES2_DestroyRenderer; + renderer->SetTargetTexture = &GLES2_SetTargetTexture; GLES2_ResetState(renderer); diff --git a/test/Makefile.in b/test/Makefile.in index 557bcd881..38dd6f5ef 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -44,6 +44,7 @@ TARGETS = \ testoverlay2$(EXE) \ testplatform$(EXE) \ testpower$(EXE) \ + testrendertarget$(EXE) \ testresample$(EXE) \ testscale$(EXE) \ testsem$(EXE) \ @@ -181,6 +182,9 @@ testplatform$(EXE): $(srcdir)/testplatform.c 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) diff --git a/test/testrendertarget.c b/test/testrendertarget.c new file mode 100644 index 000000000..10c52654e --- /dev/null +++ b/test/testrendertarget.c @@ -0,0 +1,204 @@ +/* + Copyright (C) 1997-2011 Sam Lantinga + + 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 +#include +#include + +#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: */