/*(LGPL) ------------------------------------------------------------ glSDL 0.6 - SDL 2D API on top of OpenGL ------------------------------------------------------------ * (c) David Olofson, 2001-2003 * This code is released under the terms of the GNU LGPL. */ #define _GLSDL_NO_REDEFINES_ #include "glSDL.h" #ifdef HAVE_OPENGL /* * Note that this will result in whining about * TexInfo 0 being leaked, as the checking is * done before the screen is closed. Ignore. :-) */ /* #define LEAK_TRACKING */ #define DBG(x) /*error messages, warnings*/ #define DBG2(x) /*texture allocation*/ #define DBG3(x) /*chopping/tiling*/ #define DBG4(x) /*texture uploading*/ /*#define CKSTATS*/ /*colorkey statistics*/ /*#define FAKE_MAXTEXSIZE 256*/ #include #include #include #define HAS_SDL_OPENGL_H 1 #if HAS_SDL_OPENGL_H #include "SDL_opengl.h" #else #ifdef WIN32 #include #endif #if defined(__APPLE__) && defined(__MACH__) #include #include #else #include #include #endif #endif #define USING_GLSDL (IS_GLSDL_SURFACE(SDL_GetVideoSurface())) #define MAX_TEXINFOS 16384 static glSDL_TexInfo **texinfotab = NULL; static GLint maxtexsize = 256; static SDL_PixelFormat _RGBfmt, _RGBAfmt; static int screen_was_locked = 0; static void _UnloadTexture(glSDL_TexInfo *txi); static int scale = 1; static SDL_Surface *fake_screen = NULL; static int _glSDL_BlitGL(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect); DBG(static void _print_glerror(int point) { const char *err = ""; switch(glGetError()) { case GL_NO_ERROR: return; case GL_INVALID_ENUM: err = "GL_INVALID_ENUM"; break; case GL_INVALID_VALUE: err = "GL_INVALID_VALUE"; break; case GL_INVALID_OPERATION: err = "GL_INVALID_OPERATION"; break; case GL_STACK_OVERFLOW: err = "GL_STACK_OVERFLOW"; break; case GL_STACK_UNDERFLOW: err = "GL_STACK_UNDERFLOW"; break; case GL_OUT_OF_MEMORY: err = "GL_OUT_OF_MEMORY"; break; default: err = ""; break; } fprintf(stderr,"OpenGL error \"%s\" at point %d.\n", err, point); }) /* Get texinfo for a surface. */ glSDL_TexInfo *glSDL_GetTexInfo(SDL_Surface *surface) { if(!surface) return NULL; if(!texinfotab) return NULL; if(!surface->unused1) return NULL; if(surface->unused1 > MAX_TEXINFOS) return NULL; return texinfotab[surface->unused1 - 1]; } /* Allocate a "blank" texinfo for a suface. */ glSDL_TexInfo *glSDL_AllocTexInfo(SDL_Surface *surface) { int handle, i; glSDL_TexInfo *txi; if(!surface) return NULL; if(!texinfotab) { texinfotab = (glSDL_TexInfo**)calloc(MAX_TEXINFOS, sizeof(glSDL_TexInfo *)); if(!texinfotab) return NULL; } txi = glSDL_GetTexInfo(surface); if(txi) return txi; /* There already is one! --> */ /* Find a free handle... */ handle = -1; for(i = 0; i < MAX_TEXINFOS; ++i) if(NULL == texinfotab[i]) { handle = i; break; } if(handle < 0) { DBG(fprintf(stderr, "glSDL: Out of handles!\n")); return NULL; } /* ...and hook a new texinfo struct up to it. */ texinfotab[handle] = (glSDL_TexInfo*)calloc(1, sizeof(glSDL_TexInfo)); if(!texinfotab[handle]) return NULL; /* Connect the surface to the new TexInfo. */ surface->unused1 = (Uint32)handle + 1; DBG2(fprintf(stderr, "glSDL: Allocated TexInfo %d.\n", handle)); return texinfotab[handle]; } static void _FreeTexInfo(Uint32 handle) { if(handle >= MAX_TEXINFOS) return; if(!texinfotab[handle]) return; _UnloadTexture(texinfotab[handle]); texinfotab[handle]->textures = 0; free(texinfotab[handle]->texture); texinfotab[handle]->texture = NULL; free(texinfotab[handle]); texinfotab[handle] = NULL; DBG2(fprintf(stderr, "glSDL: Freed TexInfo %d.\n", handle)); } /* Detach and free the texinfo of a surface. */ void glSDL_FreeTexInfo(SDL_Surface *surface) { if(!glSDL_GetTexInfo(surface)) return; _FreeTexInfo(surface->unused1 - 1); GLSDL_FIX_SURFACE(surface); } /* * Calculate chopping/tiling of a surface to * fit it into the smallest possible OpenGL * texture. */ static int _CalcChop(glSDL_TexInfo *txi) { int rows, vw, vh; int vertical = 0; int texsize; int lastw, lasth, minsize; vw = txi->virt.w; vh = txi->virt.h; DBG3(fprintf(stderr, "w=%d, h=%d ", vw, vh)); if(vh > vw) { int t = vw; vw = vh; vh = t; vertical = 1; DBG3(fprintf(stderr, "(vertical) \t")); } /* * Check whether this is a "huge" surface - at least one dimension * must be <= than the maximum texture size, or we'll have to chop * in both directions. */ if(vh > maxtexsize) { fprintf(stderr, "glSDL: \"Huge\" surfaces not yet supported!\n"); return 0; } /* Calculate minimum size */ rows = 1; lastw = vw; lasth = vh; minsize = lastw > lasth ? lastw : lasth; while(1) { int w, h, size; ++rows; w = vw / rows; h = rows * vh; size = w > h ? w : h; if(size >= minsize) { --rows; break; } lastw = w; lasth = h; minsize = size; } if(minsize > maxtexsize) { /* Handle multiple textures for very wide/tall surfaces. */ minsize = maxtexsize; rows = (vw + minsize-1) / minsize; } DBG3(fprintf(stderr, "==> minsize=%d ", minsize)); DBG3(fprintf(stderr, "(rows=%d) \t", rows)); /* Recalculate with nearest higher power-of-2 width. */ for(texsize = 1; texsize < minsize; texsize <<= 1) ; txi->texsize = texsize; rows = (vw + texsize-1) / texsize; DBG3(fprintf(stderr, "==> texsize=%d (rows=%d) \t", texsize, rows)); /* Calculate number of tiles per texture */ txi->tilespertex = txi->texsize / vh; DBG3(fprintf(stderr, "tilespertex=%d \t", txi->tilespertex)); /* Calculate number of textures needed */ txi->textures = (rows + txi->tilespertex-1) / txi->tilespertex; txi->texture = (int*)malloc(txi->textures * sizeof(int)); memset(txi->texture, -1, txi->textures * sizeof(int)); DBG3(fprintf(stderr, "textures=%d, ", txi->textures)); if(!txi->texture) { fprintf(stderr, "glSDL: INTERNAL ERROR: Failed to allocate" " texture name table!\n"); return -2; } /* Set up tile size. (Only one axis supported here!) */ if(1 == rows) { txi->tilemode = GLSDL_TM_SINGLE; if(vertical) { txi->tilew = vh; txi->tileh = vw; } else { txi->tilew = vw; txi->tileh = vh; } } else if(vertical) { txi->tilemode = GLSDL_TM_VERTICAL; txi->tilew = vh; txi->tileh = texsize; } else { txi->tilemode = GLSDL_TM_HORIZONTAL; txi->tilew = texsize; txi->tileh = vh; } DBG3(fprintf(stderr, "tilew=%d, tileh=%d\n", txi->tilew, txi->tileh)); return 0; } /* TODO: * Keep a list or tree of free texture space rectangles. * The space manager should actively try to merge rectangles from the same texture. static int _AllocTexSpace(glSDL_TexInfo *txi) { Make sure that _CalcChop() has been called. Try to find the smallest free area (one or more tiles) in a single texture that would fit all tiles of this surface. if that fails { Allocate a new texture of suitable size. if that fails { Try to allocate space for each tile separately. if that fails return error --> } } Allocate the areas and link them to the texinfo. return success --> } */ /* Add a glSDL_TexInfo struct to an SDL_Surface */ static int glSDL_AddTexInfo(SDL_Surface *surface) { glSDL_TexInfo *txi; if(!surface) return -1; if(IS_GLSDL_SURFACE(surface)) return 0; /* Do nothing */ glSDL_AllocTexInfo(surface); txi = glSDL_GetTexInfo(surface); if(!txi) return -2; /* Oops! Didn't get a texinfo... --> */ txi->virt.w = txi->lw = surface->w; txi->virt.h = txi->lh = surface->h; if(_CalcChop(txi) < 0) return -3; SDL_SetClipRect(surface, &txi->virt); return 0; } /* Create a surface of the prefered OpenGL RGB texture format */ static SDL_Surface *_CreateRGBSurface(int w, int h) { SDL_Surface *s; Uint32 rmask, gmask, bmask; int bits = 24; #if SDL_BYTEORDER == SDL_BIG_ENDIAN rmask = 0x00ff0000; gmask = 0x0000ff00; bmask = 0x000000ff; #else rmask = 0x000000ff; gmask = 0x0000ff00; bmask = 0x00ff0000; #endif s = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, bits, rmask, gmask, bmask, 0); if(s) GLSDL_FIX_SURFACE(s); glSDL_AddTexInfo(s); return s; } /* Create a surface of the prefered OpenGL RGBA texture format */ static SDL_Surface *_CreateRGBASurface(int w, int h) { SDL_Surface *s; Uint32 rmask, gmask, bmask, amask; int bits = 32; #if SDL_BYTEORDER == SDL_BIG_ENDIAN rmask = 0xff000000; gmask = 0x00ff0000; bmask = 0x0000ff00; amask = 0x000000ff; #else rmask = 0x000000ff; gmask = 0x0000ff00; bmask = 0x00ff0000; amask = 0xff000000; #endif s = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, bits, rmask, gmask, bmask, amask); if(s) GLSDL_FIX_SURFACE(s); glSDL_AddTexInfo(s); return s; } static void _init_formats(void) { SDL_Surface *s = _CreateRGBSurface(1, 1); if(!s) return; _RGBfmt = *(s->format); glSDL_FreeSurface(s); s = _CreateRGBASurface(1, 1); if(!s) return; _RGBAfmt = *(s->format); glSDL_FreeSurface(s); } static int _FormatIsOk(SDL_Surface *surface) { SDL_PixelFormat *pf; if(!surface) return 1; /* Well, there ain't much we can do anyway... */ pf = surface->format; /* Colorkeying requires an alpha channel! */ if(surface->flags & SDL_SRCCOLORKEY) if(!pf->Amask) return 0; /* We need pitch == (width * BytesPerPixel) for glTex[Sub]Image2D() */ if(surface->pitch != (surface->w * pf->BytesPerPixel)) return 0; if(pf->Amask) { if(pf->BytesPerPixel != _RGBAfmt.BytesPerPixel) return 0; if(pf->Rmask != _RGBAfmt.Rmask) return 0; if(pf->Gmask != _RGBAfmt.Gmask) return 0; if(pf->Bmask != _RGBAfmt.Bmask) return 0; if(pf->Amask != _RGBAfmt.Amask) return 0; } else { if(pf->BytesPerPixel != _RGBfmt.BytesPerPixel) return 0; if(pf->Rmask != _RGBfmt.Rmask) return 0; if(pf->Gmask != _RGBfmt.Gmask) return 0; if(pf->Bmask != _RGBfmt.Bmask) return 0; } return 1; } static void _key2alpha(SDL_Surface *surface) { int x, y; #ifdef CKSTATS int transp = 0; #endif Uint32 ckey = surface->format->colorkey; if(SDL_LockSurface(surface) < 0) return; for(y = 0; y < surface->h; ++y) { Uint32 *px = (Uint32 *)((char *)surface->pixels + y*surface->pitch); for(x = 0; x < surface->w; ++x) if(px[x] == ckey) { px[x] = 0; #ifdef CKSTATS ++transp; #endif } } #ifdef CKSTATS printf("glSDL: key2alpha(); %dx%d surface, %d opaque pixels.\n", surface->w, surface->h, surface->w * surface->h - transp); #endif SDL_UnlockSurface(surface); } /*---------------------------------------------------------- SDL style API ----------------------------------------------------------*/ static void _KillAllTextures(void) { if(texinfotab) { unsigned i; #ifdef LEAK_TRACKING int leaked = 0; for(i = 0; i < MAX_TEXINFOS; ++i) if(texinfotab[i]) { ++leaked; fprintf(stderr, "glSDL: Leaked TexInfo" " %d! (%dx%d)\n", i, texinfotab[i]->virt.w, texinfotab[i]->virt.h ); } if(leaked) fprintf(stderr, "glSDL: Leaked %d TexInfos!\n", leaked); #endif for(i = 0; i < MAX_TEXINFOS; ++i) _FreeTexInfo(i); free(texinfotab); texinfotab = NULL; } } void glSDL_Quit(void) { if(SDL_WasInit(SDL_INIT_VIDEO)) { SDL_Surface *screen = SDL_GetVideoSurface(); glSDL_FreeTexInfo(screen); SDL_QuitSubSystem(SDL_INIT_VIDEO); if(fake_screen) { glSDL_FreeTexInfo(fake_screen); free(fake_screen); fake_screen = NULL; } } #ifndef LEAK_TRACKING _KillAllTextures(); #endif } void _glSDL_FullQuit(void) { #ifdef LEAK_TRACKING _KillAllTextures(); #endif glSDL_Quit(); SDL_Quit(); } void glSDL_QuitSubSystem(Uint32 flags) { if(flags & SDL_INIT_VIDEO) glSDL_Quit(); SDL_QuitSubSystem(flags); } SDL_Surface *glSDL_SetVideoMode(int width, int height, int bpp, Uint32 flags) { SDL_Surface *screen; GLint gl_doublebuf; if(!(flags & SDL_GLSDL)) { screen = SDL_SetVideoMode(width, height, bpp, flags); if(screen) GLSDL_FIX_SURFACE(screen); return screen; } if((SDL_Linked_Version()->major <= 1) && (SDL_Linked_Version()->minor <= 2) && (SDL_Linked_Version()->patch < 5)) fprintf(stderr, "glSDL WARNING: Using SDL version" " 1.2.5 or later is strongly" " recommended!\n"); /* * FIXME: Here's the place to insert proper handling of this call being * used for resizing the window... For now, just make sure we * don't end up with invalid texinfos and stuff no matter what. */ _KillAllTextures(); /* Remove flag to avoid confusion inside SDL - just in case! */ flags &= ~SDL_GLSDL; flags |= SDL_OPENGL; if(bpp == 15) { SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); } else if(bpp == 16) { SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); } else if(bpp >= 24) { SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); } gl_doublebuf = flags & SDL_DOUBLEBUF; SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, gl_doublebuf); DBG(printf("desired = %d x %d\n", width, height);) scale = 1; while((width*scale < 640) && (height*scale < 480)) ++scale; DBG(printf("real = %d x %d\n", width*scale, height*scale);) screen = SDL_SetVideoMode(width*scale, height*scale, bpp, flags); if(!screen) return NULL; GLSDL_FIX_SURFACE(screen); #ifdef FAKE_MAXTEXSIZE maxtexsize = FAKE_MAXTEXSIZE; #else glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxtexsize); #endif DBG(fprintf(stderr, "glSDL: Max texture size: %d\n", maxtexsize)); _init_formats(); if(glSDL_AddTexInfo(screen) < 0) { DBG(fprintf(stderr, "glSDL: Failed to add info to screen surface!\n")); SDL_QuitSubSystem(SDL_INIT_VIDEO); return NULL; } glSDL_SetLogicSize(screen, width*scale, height*scale); /* * Create a software shadow buffer of the requested size. * This is used for blit-from-screen and simulation of * direct software rendering. (Dog slow crap. It's only * legitimate use is probably screen shots.) */ fake_screen = _CreateRGBSurface(screen->w / scale, screen->h / scale); return fake_screen; } SDL_Surface *glSDL_GetVideoSurface(void) { if(fake_screen) return fake_screen; else return SDL_GetVideoSurface(); } void glSDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects) { if(IS_GLSDL_SURFACE(screen)) { /* if(screen_was_locked) { int i; for(i = 0; i < numrects; ++i) _glSDL_BlitGL(fake_screen, rects + i, vs, rects + i); } else */ glSDL_Flip(screen); } else SDL_UpdateRects(screen, numrects, rects); screen_was_locked = 0; } void glSDL_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Uint32 w, Uint32 h) { SDL_Rect r; r.x = x; r.y = y; r.w = w; r.h = h; glSDL_UpdateRects(screen, 1, &r); } int glSDL_Flip(SDL_Surface *screen) { if(!IS_GLSDL_SURFACE(screen)) return SDL_Flip(screen); /* TODO: Perform all rendering here, after globally reordering/optimizing all non-overlapping operations for the frame. (Reduce texture changes, blend mode changes etc...) Disadvantage: Won't mix well with user OpenGL calls - but then again, glSDL isn't meant for OpenGL aware applications in the first place! */ /* * Some XFree86 DRI drivers won't sync *at all* * without glFinish()! You may end up with commands * for several frames buffered up before any actual * rendering is done - and then, your program will * stall until most of the rendering is completed. * * (Of course, this wouldn't be much of an issue * if the drivers did retrace sync'ed flips, but as * most of the drivers don't, there's no way ever an * application is going to get smooth animation * without this kludge.) * * Update: That bl**dy *DRIVER* should be fixed! * We don't need this performance killing * kludge. (I know for sure that my current * driver doesn't have this problem.) */ #ifdef STUPID_GL_WORKAROUND glFlush(); /* Just in case. *heh* */ SDL_GL_SwapBuffers(); glFinish(); /* And here we kill parallel execution... :-( */ #else SDL_GL_SwapBuffers(); #endif return 0; } void glSDL_FreeSurface(SDL_Surface *surface) { if(!surface) return; glSDL_FreeTexInfo(surface); SDL_FreeSurface(surface); } int glSDL_LockSurface(SDL_Surface *surface) { if(!surface) return 0; if(IS_GLSDL_SURFACE(surface)) { if((surface == fake_screen) || (SDL_GetVideoSurface() == surface)) { if(scale > 1) return -1; glSDL_Invalidate(fake_screen, NULL); glPixelStorei(GL_UNPACK_ROW_LENGTH, fake_screen->pitch / fake_screen->format->BytesPerPixel); glReadPixels(0, 0, fake_screen->w, fake_screen->h, GL_RGB, GL_UNSIGNED_BYTE, fake_screen->pixels); return 0; } else { glSDL_Invalidate(surface, NULL); return SDL_LockSurface(surface); } } else return SDL_LockSurface(surface); } void glSDL_UnlockSurface(SDL_Surface *surface) { if(!surface) return; if(IS_GLSDL_SURFACE(surface)) { glSDL_UploadSurface(surface); if((surface == fake_screen) || (SDL_GetVideoSurface() == surface)) _glSDL_BlitGL(fake_screen, NULL, SDL_GetVideoSurface(), NULL); } else SDL_UnlockSurface(surface); } int glSDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key) { int res = SDL_SetColorKey(surface, flag, key); if(res < 0) return res; /* * If an application does this *after* SDL_DisplayFormat, * we're basically screwed, unless we want to do an * in-place surface conversion hack here. * * What we do is just kill the glSDL texinfo... No big * deal in most cases, as glSDL only converts once anyway, * *unless* you keep modifying the surface. */ if(IS_GLSDL_SURFACE(surface)) glSDL_FreeTexInfo(surface); return res; } int glSDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha) { /* * This is just parameters to OpenGL, so the actual * "work" is done in glSDL_BlitSurface(). */ return SDL_SetAlpha(surface, flag, alpha); } SDL_bool glSDL_SetClipRect(SDL_Surface *surface, SDL_Rect *rect) { SDL_bool res; SDL_Surface *screen; SDL_Rect fsr; if(!surface) return SDL_FALSE; screen = SDL_GetVideoSurface(); res = SDL_SetClipRect(surface, rect); if(!res) return SDL_FALSE; if(!rect) { fsr.x = 0; fsr.y = 0; fsr.w = screen->w; fsr.h = screen->h; rect = &fsr; } if(surface == fake_screen) { SDL_Rect r; r.x = rect->x * scale; r.y = rect->y * scale; r.w = rect->w * scale; r.h = rect->h * scale; surface = screen; SDL_SetClipRect(surface, rect); } if( (screen == surface) && IS_GLSDL_SURFACE(surface) ) { float xscale, yscale; glSDL_TexInfo *txi = glSDL_GetTexInfo(surface); rect = &surface->clip_rect; glViewport( rect->x * scale, screen->h - (rect->y + rect->h) * scale, rect->w * scale, rect->h * scale); /* * Note that this projection is upside down in * relation to the OpenGL coordinate system. */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); xscale = (float)txi->lw / (float)surface->w; yscale = (float)txi->lh / (float)surface->h; glOrtho( xscale*(float)rect->x, xscale*(float)(rect->w+rect->x), yscale*(float)(rect->h+rect->y), yscale*(float)rect->y, -1.0, 1.0); return SDL_TRUE; } return res; } static struct { int do_blend; int do_texture; GLint texture; GLenum sfactor, dfactor; } glstate; static void gl_reset(void) { glstate.do_blend = -1; glstate.do_blend = -1; glstate.texture = -1; glstate.sfactor = 0xffffffff; glstate.dfactor = 0xffffffff; } static __inline__ void gl_do_blend(int on) { if(glstate.do_blend == on) return; if(on) glEnable(GL_BLEND); else glDisable(GL_BLEND); glstate.do_blend = on; } static __inline__ void gl_do_texture(int on) { if(glstate.do_texture == on) return; if(on) glEnable(GL_TEXTURE_2D); else glDisable(GL_TEXTURE_2D); glstate.do_texture = on; } static __inline__ void gl_blendfunc(GLenum sfactor, GLenum dfactor) { if((sfactor == glstate.sfactor) && (dfactor == glstate.dfactor)) return; glBlendFunc(sfactor, dfactor); glstate.sfactor = sfactor; glstate.dfactor = dfactor; } static __inline__ void gl_texture(GLuint tx) { if(tx == glstate.texture) return; glBindTexture(GL_TEXTURE_2D, tx); glstate.texture = tx; } static int _glSDL_BlitFromGL(SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect) { int i, sy0, dy0; SDL_Rect sr, dr; if(scale > 1) return -1; /* FIXME: Some clipping, perhaps...? :-) */ /* In case the destination has an OpenGL texture... */ glSDL_Invalidate(dst, dstrect); /* Abuse the fake screen buffer a little. */ glPixelStorei(GL_UNPACK_ROW_LENGTH, fake_screen->pitch / fake_screen->format->BytesPerPixel); if(srcrect) glReadPixels(srcrect->x, srcrect->y, srcrect->w, srcrect->h, GL_RGB, GL_UNSIGNED_BYTE, fake_screen->pixels); else glReadPixels(0, 0, fake_screen->w, fake_screen->h, GL_RGB, GL_UNSIGNED_BYTE, fake_screen->pixels); /* Blit to the actual target! (Vert. flip... Uuurgh!) */ if(srcrect) sr = *srcrect; else { sr.x = sr.y = 0; sr.w = dst->w; } if(dstrect) dr = *dstrect; else dr.x = dr.y = 0; i = srcrect->h; sy0 = srcrect->y; dy0 = dstrect->y + dstrect->h - 1; while(i--) { sr.y = sy0 + i; dr.y = dy0 - i; sr.h = 1; if(SDL_BlitSurface(fake_screen, &sr, dst, &dr) < 0) return -1; } return 0; } static __inline__ void _BlitGL_single(glSDL_TexInfo *txi, float x1, float y1, float x2, float y2, int dx1, float dy1, float dx2, float dy2, unsigned char alpha, float texscale) { /* Select texture */ if(!txi->textures) return; if(-1 == txi->texture[0]) return; gl_texture(txi->texture[0]); glBegin(GL_QUADS); glColor4ub(255, 255, 255, alpha); glTexCoord2f(x1, y1); glVertex2i(dx1, dy1); glTexCoord2f(x2, y1); glVertex2i(dx2, dy1); glTexCoord2f(x2, y2); glVertex2i(dx2, dy2); glTexCoord2f(x1, y2); glVertex2i(dx1, dy2); glEnd(); } static void _BlitGL_htile(glSDL_TexInfo *txi, float x1, float y1, float x2, float y2, int dx1, float dy1, float dx2, float dy2, unsigned char alpha, float texscale) { float tileh = (float)txi->tileh * texscale; float tile = floor(x1); /* / 1.0 */ float texsize = (float)txi->texsize; int tex = (int)tile / txi->tilespertex; float yo = ((int)tile % txi->tilespertex) * tileh; /* Select texture */ if(tex >= txi->textures) return; if(-1 == txi->texture[tex]) return; gl_texture(txi->texture[tex]); glBegin(GL_QUADS); while(1) { int thisdx1, thisdx2; float thisx1 = x1 - tile; float thisx2 = x2 - tile; /* Stop condition */ if(tile >= x2) break; /* Maybe select next texture? */ if(yo + tileh > 1.0) { ++tex; glEnd(); if(tex >= txi->textures) return; if(-1 == txi->texture[tex]) return; gl_texture(txi->texture[tex]); yo = 0.0; glBegin(GL_QUADS); } /* Left clip to current tile */ if(thisx1 < 0.0) { thisdx1 = dx1 - (int)(thisx1 * texsize); thisx1 = 0.0; } else thisdx1 = dx1; /* Right clip to current tile */ if(thisx2 > 1.0) { thisdx2 = dx2 - (int)((thisx2 - 1.0) * texsize); thisx2 = 1.0; } else thisdx2 = dx2; glColor4ub(255, 255, 255, alpha); glTexCoord2f(thisx1, yo + y1); glVertex2i(thisdx1, dy1); glTexCoord2f(thisx2, yo + y1); glVertex2i(thisdx2, dy1); glTexCoord2f(thisx2, yo + y2); glVertex2i(thisdx2, dy2); glTexCoord2f(thisx1, yo + y2); glVertex2i(thisdx1, dy2); tile += 1.0; yo += tileh; } glEnd(); } static void _BlitGL_vtile(glSDL_TexInfo *txi, float x1, float y1, float x2, float y2, int dx1, float dy1, float dx2, float dy2, unsigned char alpha, float texscale) { float tilew = (float)txi->tilew * texscale; float tile = floor(y1); float texsize = (float)txi->texsize; float xo = tile * tilew; int tex = ((int)tile * txi->tilew + txi->tilew-1) / txi->texsize; /* Select texture */ if(tex >= txi->textures) return; if(-1 == txi->texture[tex]) return; gl_texture(txi->texture[tex]); glBegin(GL_QUADS); while(1) { int newtex; int thisdy1, thisdy2; float thisy1 = y1 - tile; float thisy2 = y2 - tile; /* Stop condition */ if(tile >= y2) break; /* Maybe select next texture? */ newtex = ((int)tile * txi->tilew + txi->tilew-1) / txi->texsize; if(newtex != tex) { tex = newtex; glEnd(); if(tex >= txi->textures) return; if(-1 == txi->texture[tex]) return; gl_texture(txi->texture[tex]); xo = 0.0; glBegin(GL_QUADS); } /* Left clip to current tile */ if(thisy1 < 0.0) { thisdy1 = dy1 - (int)(thisy1 * texsize); thisy1 = 0.0; } else thisdy1 = dy1; /* Right clip to current tile */ if(thisy2 > 1.0) { thisdy2 = dy2 - (int)((thisy2 - 1.0) * texsize); thisy2 = 1.0; } else thisdy2 = dy2; glColor4ub(255, 255, 255, alpha); glTexCoord2f(xo + x1, thisy1); glVertex2i(dx1, thisdy1); glTexCoord2f(xo + x2, thisy1); glVertex2i(dx2, thisdy1); glTexCoord2f(xo + x2, thisy2); glVertex2i(dx2, thisdy2); glTexCoord2f(xo + x1, thisy2); glVertex2i(dx1, thisdy2); tile += 1.0; xo += tilew; } glEnd(); } static int _glSDL_BlitGL(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect) { glSDL_TexInfo *txi; float x1, y1, x2, y2; int dx1, dy1, dx2, dy2; float texscale; unsigned char alpha; if(!src || !dst) return -1; /* Cull off-screen blits. */ if(dstrect) { if(dstrect->x > dst->w) return 0; if(dstrect->y > dst->h) return 0; if(srcrect) { if(dstrect->x + srcrect->w < 0) return 0; if(dstrect->y + srcrect->h < 0) return 0; } else { if(dstrect->x + src->w < 0) return 0; if(dstrect->y + src->h < 0) return 0; } } /* Make sure we have a source with a valid texture */ glSDL_UploadSurface(src); txi = glSDL_GetTexInfo(src); /* Set up blending */ if(src->flags & (SDL_SRCALPHA | SDL_SRCCOLORKEY)) { gl_blendfunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); gl_do_blend(1); } else gl_do_blend(0); /* Enable texturing */ gl_do_texture(1); /* Calculate texcoords */ if(!srcrect) srcrect = &txi->virt; texscale = 1.0 / (float)txi->texsize; x1 = (float)srcrect->x * texscale; y1 = (float)srcrect->y * texscale; x2 = (float)(srcrect->x+srcrect->w) * texscale; y2 = (float)(srcrect->y+srcrect->h) * texscale; /* Calculate screen coords. */ dx2 = srcrect->w * (float)txi->lw / (float)txi->virt.w; dy2 = srcrect->h * (float)txi->lh / (float)txi->virt.h; if(dstrect) { dx1 = dstrect->x; dy1 = dstrect->y; /* * FIXME: dstrect should be filled in with the *clipped* * rect for full SDL compatibility. This hack * might break some apps... */ dstrect->w = dx2; dstrect->h = dy2; } else dx1 = dy1 = 0; dx2 += dx1; dy2 += dy1; /* * Note that we actually *prevent* the use of "full surface alpha" * and alpha channel in combination - to stay SDL 2D compatible. */ if((src->flags & SDL_SRCALPHA) && (!src->format->Amask || (src->flags & SDL_SRCCOLORKEY))) alpha = src->format->alpha; else alpha = 255; /* Render! */ switch(txi->tilemode) { case GLSDL_TM_SINGLE: _BlitGL_single(txi, x1, y1, x2, y2, dx1, dy1, dx2, dy2, alpha, texscale); break; case GLSDL_TM_HORIZONTAL: _BlitGL_htile(txi, x1, y1, x2, y2, dx1, dy1, dx2, dy2, alpha, texscale); break; case GLSDL_TM_VERTICAL: _BlitGL_vtile(txi, x1, y1, x2, y2, dx1, dy1, dx2, dy2, alpha, texscale); break; case GLSDL_TM_HUGE: /* TODO */ break; } return 0; } int glSDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect) { SDL_Surface *vs; if(!src || !dst) return -1; /* * Figure out what to do: * Not using glSDL: SDL_BlitSurface() * screen->screen: _glSDL_BlitFromGL() + _glSDL_BlitGL() * surface->screen: _glSDL_BlitGL() * screen->surface: _glSDL_BlitFromGL() * surface->surface: SDL_BlitSurface() */ if(!USING_GLSDL) return SDL_BlitSurface(src, srcrect, dst, dstrect); vs = SDL_GetVideoSurface(); if(src == fake_screen) src = vs; if(dst == fake_screen) dst = vs; if(src == vs) { if(dst == vs) { /* FIXME: Any OpenGL extensions for this...? */ _glSDL_BlitFromGL(srcrect, fake_screen, dstrect); return _glSDL_BlitGL(fake_screen, srcrect, dst, dstrect); } else { return _glSDL_BlitFromGL(srcrect, dst, dstrect); } } else { if(dst == vs) { return _glSDL_BlitGL(src, srcrect, dst, dstrect); } else { glSDL_Invalidate(dst, dstrect); return SDL_BlitSurface(src, srcrect, dst, dstrect); } } } int glSDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color) { SDL_Surface *vs = SDL_GetVideoSurface(); int dx1, dy1, dx2, dy2; Uint32 r, g, b; /* * Some ugly reverse conversion for compatibility... * (We must do this before losing the dst pointer, * as the pixel formats of the screen and the * fake_screen may differ!) */ r = color & dst->format->Rmask; r = r >> dst->format->Rshift; r = r << dst->format->Rloss; g = color & dst->format->Gmask; g = g >> dst->format->Gshift; g = g << dst->format->Gloss; b = color & dst->format->Bmask; b = b >> dst->format->Bshift; b = b << dst->format->Bloss; if(dst == fake_screen) dst = vs; if(vs != dst) glSDL_Invalidate(dst, dstrect); if((vs != dst) || !USING_GLSDL) return SDL_FillRect(dst, dstrect, color); gl_do_texture(0); gl_do_blend(0); if(!dstrect) dstrect = &dst->clip_rect; dx1 = dstrect->x; dy1 = dstrect->y; dx2 = dx1 + dstrect->w; dy2 = dy1 + dstrect->h; glBegin(GL_QUADS); glColor3ub(r, g, b); glVertex2i(dx1, dy1); glVertex2i(dx2, dy1); glVertex2i(dx2, dy2); glVertex2i(dx1, dy2); glEnd(); return 0; } SDL_Surface *glSDL_DisplayFormat(SDL_Surface *surface) { SDL_Surface *s, *tmp; if(USING_GLSDL) { int use_rgba = (surface->flags & SDL_SRCCOLORKEY) || ((surface->flags & SDL_SRCALPHA) && surface->format->Amask); if(use_rgba) tmp = SDL_ConvertSurface(surface, &_RGBAfmt, SDL_SWSURFACE); else tmp = SDL_ConvertSurface(surface, &_RGBfmt, SDL_SWSURFACE); if(!tmp) return NULL; GLSDL_FIX_SURFACE(tmp); SDL_SetAlpha(tmp, 0, 0); if(surface->flags & SDL_SRCCOLORKEY) { /* * We drop colorkey data here, but we have to, * or we'll run into trouble when converting, * in particular from indexed color formats. */ SDL_SetColorKey(tmp, SDL_SRCCOLORKEY, surface->format->colorkey); _key2alpha(tmp); } SDL_SetColorKey(tmp, 0, 0); if(use_rgba) s = _CreateRGBASurface(surface->w, surface->h); else s = _CreateRGBSurface(surface->w, surface->h); if(!s) { glSDL_FreeSurface(tmp); return NULL; } SDL_BlitSurface(tmp, NULL, s, NULL); glSDL_FreeSurface(tmp); if(surface->flags & SDL_SRCALPHA) SDL_SetAlpha(s, SDL_SRCALPHA, surface->format->alpha); return s; } else { s = SDL_DisplayFormat(surface); if(s) GLSDL_FIX_SURFACE(s); return s; } } SDL_Surface *glSDL_DisplayFormatAlpha(SDL_Surface *surface) { SDL_Surface *s, *tmp; if(USING_GLSDL) { tmp = SDL_ConvertSurface(surface, &_RGBAfmt, SDL_SWSURFACE); if(!tmp) return NULL; GLSDL_FIX_SURFACE(tmp); SDL_SetAlpha(tmp, 0, 0); SDL_SetColorKey(tmp, 0, 0); s = _CreateRGBASurface(surface->w, surface->h); if(!s) { glSDL_FreeSurface(tmp); return NULL; } SDL_BlitSurface(tmp, NULL, s, NULL); glSDL_FreeSurface(tmp); if(surface->flags & SDL_SRCCOLORKEY) { SDL_SetColorKey(s, SDL_SRCCOLORKEY, surface->format->colorkey); _key2alpha(s); } if(surface->flags & SDL_SRCALPHA) SDL_SetAlpha(s, SDL_SRCALPHA, surface->format->alpha); return s; } else { s = SDL_DisplayFormatAlpha(surface); if(s) GLSDL_FIX_SURFACE(s); return s; } } SDL_Surface *glSDL_ConvertSurface (SDL_Surface *src, SDL_PixelFormat *fmt, Uint32 flags) { SDL_Surface *s = SDL_ConvertSurface(src, fmt, flags); if(s) GLSDL_FIX_SURFACE(s); return s; } SDL_Surface *glSDL_CreateRGBSurface (Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) { SDL_Surface *s = SDL_CreateRGBSurface(flags, width, height, depth, Rmask, Gmask, Bmask, Amask); if(s) GLSDL_FIX_SURFACE(s); return s; } SDL_Surface *glSDL_CreateRGBSurfaceFrom(void *pixels, int width, int height, int depth, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) { SDL_Surface *s = SDL_CreateRGBSurfaceFrom(pixels, width, height, depth, pitch, Rmask, Gmask, Bmask, Amask); if(s) GLSDL_FIX_SURFACE(s); return s; } SDL_Surface *glSDL_LoadBMP(const char *file) { SDL_Surface *s = SDL_LoadBMP(file); if(s) GLSDL_FIX_SURFACE(s); return s; } int glSDL_SaveBMP(SDL_Surface *surface, const char *file) { SDL_Rect r; SDL_Surface *buf; SDL_Surface *screen = SDL_GetVideoSurface(); if(!USING_GLSDL) return SDL_SaveBMP(surface, file); if((surface != screen) && (surface != fake_screen)) return SDL_SaveBMP(surface, file); buf = _CreateRGBSurface(fake_screen->w, fake_screen->h); r.x = 0; r.y = 0; r.w = fake_screen->w; r.h = fake_screen->h; if(_glSDL_BlitFromGL(&r, buf, &r) < 0) return -1; return SDL_SaveBMP(buf, file); glSDL_FreeSurface(buf); } /*---------------------------------------------------------- glSDL specific API extensions ----------------------------------------------------------*/ void glSDL_Invalidate(SDL_Surface *surface, SDL_Rect *area) { glSDL_TexInfo *txi; if(!surface) return; txi = glSDL_GetTexInfo(surface); if(!txi) return; if(!area) { txi->invalid_area.x = 0; txi->invalid_area.y = 0; txi->invalid_area.w = surface->w; txi->invalid_area.h = surface->h; return; } txi->invalid_area = *area; } void glSDL_SetLogicSize(SDL_Surface *surface, int w, int h) { SDL_Rect r; glSDL_TexInfo *txi; if(!IS_GLSDL_SURFACE(surface)) return; txi = glSDL_GetTexInfo(surface); txi->lw = w; txi->lh = h; if((SDL_GetVideoSurface() != surface) && (fake_screen != surface)) return; r.x = r.y = 0; r.w = w; r.h = h; glSDL_SetClipRect(surface, &r); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.0f, 0.0f, 0.0f); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); gl_reset(); } /* Upload a single texture. */ static int _UploadTexture(SDL_Surface *datasurf, glSDL_TexInfo *txi, int tex) { int bpp = datasurf->format->BytesPerPixel; glGenTextures(1, (unsigned int *)&txi->texture[tex]); glBindTexture(GL_TEXTURE_2D, txi->texture[tex]); glPixelStorei(GL_UNPACK_ROW_LENGTH, datasurf->pitch / datasurf->format->BytesPerPixel); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, datasurf->format->Amask ? GL_RGBA8 : GL_RGB8, txi->texsize, txi->texsize, 0, datasurf->format->Amask ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, NULL); DBG(_print_glerror(1)); switch(txi->tilemode) { case GLSDL_TM_SINGLE: case GLSDL_TM_HORIZONTAL: { /* Image tiled horizontally, or not at all */ int fromx = txi->tilew * tex * txi->tilespertex; int toy = 0; while(toy + txi->tileh <= txi->texsize) { int thistw; thistw = datasurf->w - fromx; if(thistw > txi->tilew) thistw = txi->tilew; else if(thistw <= 0) break; glTexSubImage2D(GL_TEXTURE_2D, 0, 0, toy, thistw, txi->tileh, datasurf->format->Amask ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, (char *)datasurf->pixels + bpp * fromx); DBG4(_print_glerror(2)); fromx += txi->tilew; toy += txi->tileh; glFlush(); } break; } case GLSDL_TM_VERTICAL: { /* Image tiled vertically */ int fromy = txi->tileh * tex * txi->tilespertex; int tox = 0; while(tox + txi->tilew <= txi->texsize) { int thisth; thisth = datasurf->h - fromy; if(thisth > txi->tileh) thisth = txi->tileh; else if(thisth <= 0) break; glTexSubImage2D(GL_TEXTURE_2D, 0, tox, 0, txi->tilew, thisth, datasurf->format->Amask ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, (char *)datasurf->pixels + datasurf->pitch * fromy); DBG4(_print_glerror(3)); fromy += txi->tileh; tox += txi->tilew; glFlush(); } break; } case GLSDL_TM_HUGE: /* "Huge" image - tiled both ways */ return -4; break; } return 0; } int glSDL_UploadSurface(SDL_Surface *surface) { SDL_Surface *datasurf = surface; glSDL_TexInfo *txi; int i; /* * For now, we just assume that *every* texture needs * conversion before uploading. */ /* If there's no TexInfo, add one. */ if(!IS_GLSDL_SURFACE(surface)) glSDL_AddTexInfo(surface); txi = glSDL_GetTexInfo(surface); /* No partial updates implemented yet... */ if(txi->invalid_area.w) glSDL_UnloadSurface(surface); else { int missing = 0; if(txi->textures) { for(i = 0; i < txi->textures; ++i) if(-1 == txi->texture[i]) { missing = 1; break; } if(!missing) return 0; /* They're already there! */ } } if(txi->texsize > maxtexsize) { fprintf(stderr, "glSDL: INTERNAL ERROR: Too large texture!\n"); return -1; /* This surface wasn't tiled properly... */ } /* * Kludge: Convert if not of preferred RGB or RGBA format. * * Conversion should only be done when *really* needed. * That is, it should rarely have to be done with OpenGL * 1.2+. * * Besides, any surface that's been SDL_DisplayFormat()ed * should already be in the best known OpenGL format - * preferably one that makes DMA w/o conversion possible. */ if(_FormatIsOk(surface)) datasurf = surface; else { DBG(fprintf(stderr, "glSDL: WARNING: On-the-fly conversion performed!\n")); if(surface->format->Amask) datasurf = glSDL_DisplayFormatAlpha(surface); else datasurf = glSDL_DisplayFormat(surface); if(!datasurf) return -2; } for(i = 0; i < txi->textures; ++i) if(_UploadTexture(datasurf, txi, i) < 0) return -3; if(datasurf != surface) glSDL_FreeSurface(datasurf); return 0; } static void _UnloadTexture(glSDL_TexInfo *txi) { int i; for(i = 0; i < txi->textures; ++i) glDeleteTextures(1, (unsigned int *)&txi->texture[i]); memset(&txi->invalid_area, 0, sizeof(txi->invalid_area)); } void glSDL_UnloadSurface(SDL_Surface *surface) { if(!IS_GLSDL_SURFACE(surface)) return; _UnloadTexture(glSDL_GetTexInfo(surface)); } SDL_Surface *glSDL_IMG_Load(const char *file) { SDL_Surface *s; s = IMG_Load(file); if(s) GLSDL_FIX_SURFACE(s); return s; } #endif /* HAVE_OPENGL */