diff --git a/stepmania/src/RageBitmapTexture.cpp b/stepmania/src/RageBitmapTexture.cpp index 69cbb6af78..b2598c92de 100644 --- a/stepmania/src/RageBitmapTexture.cpp +++ b/stepmania/src/RageBitmapTexture.cpp @@ -16,6 +16,7 @@ #include "RageLog.h" #include "RageException.h" #include "RageDisplay.h" +#include "RageDisplayInternal.h" #include "SDL.h" #include "SDL_image.h" @@ -26,23 +27,33 @@ #include "RageTimer.h" +enum pixfmts { + FMT_RGBA8, + FMT_RGBA4, + FMT_RGB5A1, + FMT_PAL, + NUM_PIX_FORMATS +}; + /* Definitions for various texture formats. We'll probably want RGBA * in OpenGL, not ARGB ... All of these are in local (little) endian; * this may or may not need adjustment for OpenGL. */ struct PixFmt_t { int bpp; - GLenum type; /* data format */ GLenum internalfmt; /* target format */ + GLenum format; /* target format */ + GLenum type; /* data format */ unsigned int masks[4]; -} PixFmtMasks[] = { +} PixFmtMasks[NUM_PIX_FORMATS] = { /* XXX: GL_UNSIGNED_SHORT_4_4_4_4 is affected by endianness; GL_UNSIGNED_BYTE * is not, but all SDL masks are affected by endianness, so GL_UNSIGNED_BYTE * is reversed. This isn't endian-safe. */ { /* B8G8R8A8 */ 32, - GL_UNSIGNED_BYTE, GL_RGBA8, + GL_RGBA, + GL_UNSIGNED_BYTE, { 0x000000FF, 0x0000FF00, 0x00FF0000, @@ -50,8 +61,9 @@ struct PixFmt_t { }, { /* B4G4R4A4 */ 16, - GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4, + GL_RGBA, + GL_UNSIGNED_SHORT_4_4_4_4, { 0xF000, 0x0F00, 0x00F0, @@ -59,22 +71,30 @@ struct PixFmt_t { }, { /* B5G5R5A1 */ 16, - GL_UNSIGNED_SHORT_5_5_5_1, GL_RGB5_A1, + GL_RGBA, + GL_UNSIGNED_SHORT_5_5_5_1, { 0xF800, 0x07C0, 0x003E, 0x0001 }, + }, { + /* Paletted */ + 8, + GL_COLOR_INDEX8_EXT, + GL_COLOR_INDEX, + GL_UNSIGNED_BYTE, + { 0,0,0,0 } /* N/A */ } }; int PixFmtMaskNo(GLenum fmt) { switch(fmt) { - case GL_RGBA8: return 0; - case GL_RGBA4: return 1; - case GL_RGB5_A1: return 2; - default: ASSERT(0); return 0; + case GL_RGBA8: return FMT_RGBA8; + case GL_RGBA4: return FMT_RGBA4; + case GL_RGB5_A1: return FMT_RGB5A1; + default: ASSERT(0); return FMT_RGBA8; } } @@ -133,6 +153,7 @@ SDL_Surface *RageBitmapTexture::CreateImg(int &pixfmt) /* Load the image into an SDL surface. */ SDL_Surface *img = IMG_Load(GetFilePath()); + /* XXX: Wait, we don't want to throw for all images; in particular, we * want to tolerate corrupt/unknown background images. */ if(img == NULL) @@ -225,10 +246,6 @@ SDL_Surface *RageBitmapTexture::CreateImg(int &pixfmt) pixfmt = PixFmtMaskNo(fmtTexture); - /* Dither only when the target is 16bpp, not when it's 32bpp. */ - if( PixFmtMasks[pixfmt].bpp == 32) - m_ActualID.bDither = false; - if( m_ActualID.bStretch ) { /* resize currently only does RGBA8888 */ @@ -239,32 +256,6 @@ SDL_Surface *RageBitmapTexture::CreateImg(int &pixfmt) zoomSurface(img, m_iImageWidth, m_iImageHeight ); } - if( m_ActualID.bDither ) - { - /* Dither down to the destination format. */ - SDL_Surface *dst = SDL_CreateRGBSurfaceSane(SDL_SWSURFACE, img->w, img->h, PixFmtMasks[pixfmt].bpp, - PixFmtMasks[pixfmt].masks[0], PixFmtMasks[pixfmt].masks[1], - PixFmtMasks[pixfmt].masks[2], PixFmtMasks[pixfmt].masks[3]); - - SM_SDL_OrderedDither(img, dst); - SDL_FreeSurface(img); - img = dst; - } - - /* Convert the data to the destination format. Hmm. We could just - * convert the format, leaving the resolution alone (simplifying - * ConvertSDLSurface), and then load the texture a little more - * intelligently. If we do that with OpenGL, is the rest - * of the texture (that we didn't fill) guaranteed to be black? - * We don't want anything else to be linearly filtered in on the - * edge of the texture ... - */ - /* We could check to see if we happen to simply be in a reversed - * pixel order, and tell OpenGL to do the switch for us. */ - ConvertSDLSurface(img, img->w, img->h, PixFmtMasks[pixfmt].bpp, - PixFmtMasks[pixfmt].masks[0], PixFmtMasks[pixfmt].masks[1], - PixFmtMasks[pixfmt].masks[2], PixFmtMasks[pixfmt].masks[3]); - return img; } @@ -278,28 +269,114 @@ SDL_Surface *RageBitmapTexture::CreateImg(int &pixfmt) * Dither forces dithering when loading 16-bit textures. * Stretch forces the loaded image to fill the texture completely. */ - void RageBitmapTexture::Create() { - int pixfmt; - - SDL_Surface *img = CreateImg(pixfmt); + /* This will be set to the pixfmt we should use if we use an RGBA texture. */ + int desired_rgba_pixfmt; + SDL_Surface *img = CreateImg(desired_rgba_pixfmt); if(!m_uGLTextureID) glGenTextures(1, &m_uGLTextureID); DISPLAY->SetTexture(this); - glPixelStorei(GL_UNPACK_ROW_LENGTH, img->pitch / img->format->BytesPerPixel); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - /* Here's a trick for loading a texture that doesn't necessarily have power-of-two - * dimensions into a texture, without wasting time converting it: */ - glTexImage2D(GL_TEXTURE_2D, 0, PixFmtMasks[pixfmt].internalfmt, power_of_two(img->w), power_of_two(img->h), 0, - GL_RGBA, PixFmtMasks[pixfmt].type, NULL); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, - img->w, img->h, GL_RGBA, PixFmtMasks[pixfmt].type, img->pixels); + int pixfmt = desired_rgba_pixfmt; + + /* XXX this needs to be a hint */ + Uint8 AlphaR=0, AlphaG=0, AlphaB=0; + SetAlphaRGB(img, AlphaR, AlphaG, AlphaB); + + if(img->format->BitsPerPixel == 8 && DISPLAY->GetSpecs().EXT_paletted_texture) + { + /* The image is currently paletted. Let's try to set up a paletted texture. */ + GLubyte palette[256*4]; + memset(palette, 0, sizeof(palette)); + int p = 0; + /* Copy the palette to the simple, unpacked data OGL expects. If + * we're color keyed, change it over as we go. */ + for(int i = 0; i < img->format->palette->ncolors; ++i) + { + palette[p++] = img->format->palette->colors[i].r; + palette[p++] = img->format->palette->colors[i].g; + palette[p++] = img->format->palette->colors[i].b; + + if(img->flags & SDL_SRCCOLORKEY && i == int(img->format->colorkey)) + palette[p++] = 0; + else + palette[p++] = 0xFF; /* opaque */ + } + + /* Set the palette. */ + glColorTableEXT(GL_TEXTURE_2D, GL_RGBA8, 256, GL_RGBA, GL_UNSIGNED_BYTE, palette); + + int RealFormat = 0; + glGetColorTableParameterivEXT(GL_TEXTURE_2D, GL_COLOR_TABLE_FORMAT_EXT, &RealFormat); + if(RealFormat == GL_RGBA8) + { + /* Good, the color table is what we asked for. Use a paletted texture format. */ + pixfmt = FMT_PAL; + } else { + /* This is another case I don't expect to happen; if it does, log, + * turn off PT's permanently and continue as an RGBA texture. */ + LOG->Info("Expected an RGBA8 palette, got %i instead; disabling paletted textures", RealFormat); + DISPLAY->DisablePalettedTexture(); + } + } + +retry: + if(pixfmt != FMT_PAL) + { + /* It's either not a paletted image, or we can't handle paletted images. + * Convert to the desired RGBA format, dithering if appropriate. */ + + /* Never dither when the target is 32bpp; there's no point. */ + if( PixFmtMasks[pixfmt].bpp == 32) + m_ActualID.bDither = false; + + if( m_ActualID.bDither ) + { + /* Dither down to the destination format. */ + SDL_Surface *dst = SDL_CreateRGBSurfaceSane(SDL_SWSURFACE, img->w, img->h, PixFmtMasks[pixfmt].bpp, + PixFmtMasks[pixfmt].masks[0], PixFmtMasks[pixfmt].masks[1], + PixFmtMasks[pixfmt].masks[2], PixFmtMasks[pixfmt].masks[3]); + + SM_SDL_OrderedDither(img, dst); + SDL_FreeSurface(img); + img = dst; + } + } + + /* Convert the data to the destination format if it's not in it already. */ + ConvertSDLSurface(img, m_iTextureWidth, m_iTextureHeight, PixFmtMasks[pixfmt].bpp, + PixFmtMasks[pixfmt].masks[0], PixFmtMasks[pixfmt].masks[1], + PixFmtMasks[pixfmt].masks[2], PixFmtMasks[pixfmt].masks[3]); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, img->pitch / img->format->BytesPerPixel); + glTexImage2D(GL_TEXTURE_2D, 0, PixFmtMasks[pixfmt].internalfmt, + m_iTextureWidth, m_iTextureHeight, 0, + PixFmtMasks[pixfmt].format, PixFmtMasks[pixfmt].type, img->pixels); + + /* If we're paletted, and didn't get the 8-bit palette we asked for ...*/ + if(img->format->BitsPerPixel == 8) + { + int size; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INDEX_SIZE_EXT, &size); + if(size != 8) + { + /* I don't know any reason this should actually fail (paletted textures + * but no support for 8-bit palettes?), so let's just disable paletted + * textures the first time this happens. */ + LOG->Info("Expected an 8-bit palette, got a %i-bit one instead; disabling paletted textures", size); + DISPLAY->DisablePalettedTexture(); + pixfmt = desired_rgba_pixfmt; + goto retry; + } + } glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glFlush(); diff --git a/stepmania/src/SDL_utils.cpp b/stepmania/src/SDL_utils.cpp index 6e7a5e0fce..34b79eef6d 100644 --- a/stepmania/src/SDL_utils.cpp +++ b/stepmania/src/SDL_utils.cpp @@ -140,7 +140,6 @@ void mySDL_GetBitsPerChannel(const SDL_PixelFormat *fmt, Uint32 bits[4]) bits[2] = 8 - fmt->Bloss; bits[3] = 8 - fmt->Aloss; } -#include "RageLog.h" void ConvertSDLSurface(SDL_Surface *&image, int width, int height, int bpp, @@ -150,22 +149,48 @@ void ConvertSDLSurface(SDL_Surface *&image, SDL_SWSURFACE, width, height, bpp, R, G, B, A); ASSERT(ret_image != NULL); - /* If the formats are the same, no conversion is needed. One exception: - * if we have a color key and we're not paletted (8-bit). (If we have - * a color key but we're paletted, we'll handle this on texture load.) xxx unimplemented */ - if(!(image->flags & SDL_SRCCOLORKEY) && - width == image->w && height == image->h && - !memcmp(image->format, ret_image->format, sizeof(SDL_PixelFormat))) + /* If the formats are the same, no conversion is needed. */ + if(width == image->w && height == image->h && bpp == image->format->BitsPerPixel && + image->format->Rmask == ret_image->format->Rmask && + image->format->Gmask == ret_image->format->Gmask && + image->format->Bmask == ret_image->format->Bmask && + image->format->Amask == ret_image->format->Amask) { - LOG->Trace("noconv"); - SDL_FreeSurface(ret_image); - return; + /* One exception: if we have a color key and we're not paletted (8-bit). + * In this case, we need to do the blit to get rid of the color key. */ + if(!( image->flags & SDL_SRCCOLORKEY && image->format->BitsPerPixel != 8) ) + { + SDL_FreeSurface(ret_image); + return; + } } /* We don't want to actually blend the alpha channel over the destination converted * surface; we want to simply blit it, so make sure SDL_SRCALPHA is not on. */ SDL_SetAlpha( image, 0, SDL_ALPHA_OPAQUE ); + /* Copy the palette, if we have one. */ + if(image->format->palette) + SDL_SetPalette(ret_image, SDL_LOGPAL, image->format->palette->colors, + 0, image->format->palette->ncolors); + + if(image->format->BitsPerPixel == 8 && ret_image->format->BitsPerPixel == 8 && + image->flags & SDL_SRCCOLORKEY) + { + /* The source and dest are both paletted, and we have a color key. + * First, make sure that the image we're blitting to has a default + * color of the color key, so any places we don't blit to will + * be transparent. (The default color in the image is 0, so we're + * all set if the color key is 0.) */ + if(image->format->colorkey != 0) + SDL_FillRect(ret_image, NULL, image->format->colorkey); + + /* Copy over the color key mode, and then turn off color keying in the + * source so the color key index gets copied like any other color. */ + SDL_SetColorKey( ret_image, SDL_SRCCOLORKEY, image->format->colorkey); + SDL_SetColorKey( image, 0, 0 ); + } + SDL_Rect area; area.x = area.y = 0; area.w = short(image->w); @@ -207,3 +232,43 @@ SDL_Surface *SDL_CreateRGBSurfaceSane return SDL_CreateRGBSurface(flags, width, height, depth, Rmask, Gmask, Bmask, Amask); } + +/* Set the underlying RGB values of all pixels in 'img' that are + * completely transparent. */ +void SetAlphaRGB(SDL_Surface *img, Uint8 r, Uint8 g, Uint8 b) +{ + /* If it's a paletted surface, all we have to do is change the + * colorkey, if any. */ + if(img->format->BitsPerPixel == 8) + { + if(img->flags & SDL_SRCCOLORKEY) + { + img->format->palette->colors[img->format->colorkey].r = r; + img->format->palette->colors[img->format->colorkey].g = g; + img->format->palette->colors[img->format->colorkey].b = b; + } + + return; + } + + /* It's RGBA. If there's no alpha channel, we have nothing to do. */ + if(!img->format->Amask) + return; + + Uint32 trans = SDL_MapRGBA(img->format, r, g, b, 0); + for(int y = 0; y < img->h; ++y) + { + Uint8 *row = (Uint8 *)img->pixels + img->pitch*y; + + for(int x = 0; x < img->w; ++x) + { + Uint32 val = decodepixel(row, img->format->BytesPerPixel); + if(val != trans && !(val&img->format->Amask)) + { + encodepixel(row, img->format->BytesPerPixel, trans); + } + + row += img->format->BytesPerPixel; + } + } +} diff --git a/stepmania/src/SDL_utils.h b/stepmania/src/SDL_utils.h index 80859120c5..afbcf115f4 100644 --- a/stepmania/src/SDL_utils.h +++ b/stepmania/src/SDL_utils.h @@ -20,5 +20,7 @@ SDL_Surface *SDL_CreateRGBSurfaceSane (Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); +void SetAlphaRGB(SDL_Surface *img, Uint8 r, Uint8 g, Uint8 b); + #endif