diff --git a/stepmania/src/RageBitmapTexture.cpp b/stepmania/src/RageBitmapTexture.cpp index 261306972e..7dedab879a 100644 --- a/stepmania/src/RageBitmapTexture.cpp +++ b/stepmania/src/RageBitmapTexture.cpp @@ -7,27 +7,63 @@ Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. Chris Danford + Glenn Maynard ----------------------------------------------------------------------------- */ - -//----------------------------------------------------------------------------- -// In-line Links -//----------------------------------------------------------------------------- -//#pragma comment(lib, "winmm.lib") -#pragma comment(lib, "dxerr8.lib") - -//----------------------------------------------------------------------------- -// Includes -//----------------------------------------------------------------------------- #include "RageBitmapTexture.h" -#include "dxerr8.h" -#include "DXUtil.h" #include "RageUtil.h" #include "RageLog.h" #include "RageException.h" +#include "SDL.h" +#include "SDL_image.h" +#include "SDL_endian.h" +#include "SDL_rotozoom.h" +#include "SDL_utils.h" +#include "SDL_dither.h" +#include "RageTimer.h" + + + +/* 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. */ +const static unsigned int PixFmtMasks[][5] = +{ + /* Err. D3D's texture formats are little-endian, so the order of + * colors (bytewise) is BGRA; D3DFMT_A8R8G8B8 is really BGRA (bytewise). + * D3DFMT_A4R4G4B4 is 0xGBAR; flip the bytes and it's sane (0xARGB). + */ + { 0x00FF0000, /* B8G8R8A8 */ + 0x0000FF00, + 0x000000FF, + 0xFF000000, 32 }, + { 0x0F00, /* B4G4R4A4 */ + 0x00F0, + 0x000F, + 0xF000, 16 }, + { 0x7C00, /* B5G5R5A1 */ + 0x03E0, + 0x001F, + 0x8000, 16 }, + { 0xF800, /* B5G6R5 */ + 0x07E0, + 0x001F, + 0x0000, 16 } +}; + +int PixFmtMaskNo(D3DFORMAT fmt) +{ + switch(fmt) { + case D3DFMT_A8R8G8B8: return 0; + case D3DFMT_A4R4G4B4: return 1; + case D3DFMT_A1R5G5B5: return 2; + case D3DFMT_R5G6B5: return 3; + default: ASSERT(0); return 0; + } +} //----------------------------------------------------------------------------- // RageBitmapTexture constructor @@ -84,6 +120,238 @@ LPDIRECT3DTEXTURE8 RageBitmapTexture::GetD3DTexture() return m_pd3dTexture; } +#if 1 + +static int power_of_two(int input) +{ + int value = 1; + + while ( value < input ) value <<= 1; + + return value; +} + +/* + * Each dwMaxSize, dwTextureColorDepth and iAlphaBits are maximums; we may + * use less. iAlphaBits must be 0, 1 or 4. + * + * XXX: change iAlphaBits == 4 to iAlphaBits == 8 to indicate "as much alpha + * as needed", since that's what it really is; still only use 4 in 16-bit textures. + * + * Dither forces dithering when loading 16-bit textures. + * Stretch forces the loaded image to fill the texture completely. + */ + +/* Implementation: + * Load the input image first. Adjust texture hints and determine the desired + * texture format. Set up the texture. Adjust members based on what texture + * type we really got. + * + * If stretching, resize the loaded image to the destination size. This can be done + * in the loaded image format. + * + * If dithering: Convert the loaded image to RGBA8888, to make the dither simpler. + * (Most loaded images are in that format anyway.) Dither into the destination + * surface. + * + * If not dithering: convert the loaded image to the destination surface. + * + * Last, copy the destination surface into the destination texture. + * + * This could be a bit more efficient, to eliminate an extra conversion or two when + * stretching or dithering (or both), but most images do neither ... + */ +void RageBitmapTexture::Create( + int dwMaxSize, + int dwTextureColorDepth, + int iMipMaps, + int iAlphaBits, + bool bDither, + bool bStretch + ) +{ + HRESULT hr; + + // look in the file name for a format hints + m_sFilePath.MakeLower(); + + if( m_sFilePath.Find("no alpha") != -1 ) iAlphaBits = 0; + else if( m_sFilePath.Find("1 alpha") != -1 ) iAlphaBits = 1; + else if( m_sFilePath.Find("1alpha") != -1 ) iAlphaBits = 1; + else if( m_sFilePath.Find("0alpha") != -1 ) iAlphaBits = 0; + if( m_sFilePath.Find("dither") != -1 ) bDither = true; + + /* Load the image into an SDL surface. */ +RageTimer q; static float xxx = 0; + SDL_Surface *img = IMG_Load(m_sFilePath); + xxx += q.GetDeltaTime(); +LOG->Trace("load %f", xxx); + + /* Figure out which texture format to use. */ + D3DFORMAT fmtTexture; +// dwTextureColorDepth = 32; + if( dwTextureColorDepth == 16 ) { + /* 8-bit indexed color (BPP == 1) never needs more than 1 bit of alpha. + * + * If a file has only one bit of alpha, it'll never need more. (But if + * it has no alpha channel at all, it might still end up needing one + * for the color key.) + * + * XXX format->alpha isn't a bit count */ + if( img->format->BytesPerPixel == 1 || img->format->alpha == 1 ) + iAlphaBits = min(1, iAlphaBits); + + /* If we have no transparency, we don't need any alpha bits. */ + if( !img->format->alpha && !img->format->colorkey ) + iAlphaBits = 0; + +/* XXX todo? In debug mode only (or something), search for files that set a + * color key or alpha but don't use it and log it. If they don't have a color key set + * at all, we can load it more efficiently. Don't just scan every texture all + * the time to check this, though; better off getting it right in the data than + * spending cycles in a release build. + * + * Scan the image, and see if it actually uses its alpha channel/color key (if any). Reduce + * to 1 or 0 bits of alpha if possible. */ + + switch( iAlphaBits ) { + case 0: fmtTexture = D3DFMT_R5G6B5; break; + case 1: fmtTexture = D3DFMT_A1R5G5B5; break; + case 4: fmtTexture = D3DFMT_A4R4G4B4; break; + default: + ASSERT(0); // invalid iAlphaBits value + fmtTexture = D3DFMT_A1R5G5B5; break; + } +// fmtTexture = D3DFMT_A4R4G4B4; + } else if( dwTextureColorDepth == 32 ) + fmtTexture = D3DFMT_A8R8G8B8; + else + throw RageException( "Invalid color depth: %d bits", dwTextureColorDepth ); + + // find out what the max texture size is + dwMaxSize = min( dwMaxSize, int(DISPLAY->GetDeviceCaps().MaxTextureWidth) ); + + /* Save information about the source. Unless something else changes this + * later, the image inside the texture is the same size as the source. */ + m_iImageWidth = m_iSourceWidth = img->w; + m_iImageHeight = m_iSourceHeight = img->h; + + /* Texture dimensions need to be a power of two; jump to the next. */ + m_iTextureWidth = power_of_two(img->w); + m_iTextureHeight = power_of_two(img->h); + + /* Cap the size. */ + m_iTextureWidth = min(m_iTextureWidth, int(dwMaxSize)); + m_iTextureHeight = min(m_iTextureHeight, int(dwMaxSize)); + + if( FAILED( hr = m_pd3dDevice->CreateTexture( + m_iTextureWidth, // width + m_iTextureHeight, // height + 1, // mip map levels XXX: sending 4 breaks 2x2 pngs + 0, // usage (is a render target?) + fmtTexture, // our preferred texture format + D3DPOOL_MANAGED, // which memory pool + &m_pd3dTexture) )) + { + throw RageException( hr, "CreateTexture() failed for file '%s'.", m_sFilePath ); + } + + { + D3DSURFACE_DESC ddsd; + if ( FAILED( hr = m_pd3dTexture->GetLevelDesc( 0, &ddsd ) ) ) + throw RageException( hr, "Could not get level Description of D3D texture!" ); + + /* We might have received a texture of a different size or format than + * we asked for. */ + m_iTextureWidth = ddsd.Width; + m_iTextureHeight = ddsd.Height; + fmtTexture = ddsd.Format; + } + + /* If the source is larger than the texture, we have to scale it down; that's + * "stretching", I guess. */ + bStretch |= m_iSourceWidth > m_iTextureWidth || m_iSourceHeight > m_iTextureHeight; + + int target = PixFmtMaskNo(fmtTexture); + + /* Dither only when the target is 16bpp, not when it's 32bpp. */ + if( PixFmtMasks[target][4] /* XXX magic 4 */ == 4) + bDither = false; +// bStretch = bDither = false; + if( bStretch ) { + /* zoomSurface takes a ratio. I'm not sure if it's always exact; we don't + * want to accidentally create a 513x513 texture, for example (it won't just + * cause lines; it'll be copied to the texture completely wrong). */ + + /* If zoomSurface is having problems with some pixel formats, this will + * sanitize it: */ +/* if(img->format->BitsPerPixel == 8 || img->format->colorkey) { + int mask = 0; + SDL_Surface *dst = SDL_CreateRGBSurfaceSane(SDL_SWSURFACE, img->w, img->h, PixFmtMasks[target][4], + PixFmtMasks[target][0], PixFmtMasks[target][1], PixFmtMasks[target][2], PixFmtMasks[target][3]); + SDL_FreeSurface(img); + img = dst; + } */ + SDL_Surface *dst; + dst = zoomSurface(img, float(m_iTextureWidth)/m_iImageWidth, + float(m_iTextureHeight) / m_iImageHeight, SMOOTHING_ON); + + SDL_FreeSurface(img); + img = dst; + + /* The new image size is the full texture size. */ + m_iImageWidth = m_iTextureWidth; + m_iImageHeight = m_iTextureHeight; + } + + if( bDither ) + { + RageTimer t; + + /* Dither down to the destination format. */ + SDL_Surface *dst = SDL_CreateRGBSurfaceSane(SDL_SWSURFACE, img->w, img->h, PixFmtMasks[target][4], + PixFmtMasks[target][0], PixFmtMasks[target][1], PixFmtMasks[target][2], PixFmtMasks[target][3]); + + LOG->Trace("dithered in %f", t.GetDeltaTime()); + SM_SDL_OrderedDither(img, dst); + SDL_FreeSurface(img); + img = dst; + + LOG->Trace("dithered in %f", t.GetDeltaTime()); + } + + /* 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 ... + */ + ConvertSDLSurface(img, m_iTextureWidth, m_iTextureHeight, PixFmtMasks[target][4], + PixFmtMasks[target][0], PixFmtMasks[target][1], PixFmtMasks[target][2], PixFmtMasks[target][3]); + + /* Copy the data to the target texture. */ + { + D3DLOCKED_RECT d3dlr; + if( FAILED( hr=m_pd3dTexture->LockRect(0, &d3dlr, 0, 0) ) ) + throw RageException( hr, "LockRect failed for file '%s'.", m_sFilePath ); + + memcpy( (byte *)(d3dlr.pBits), img->pixels, img->h*img->pitch ); + ASSERT( !FAILED( m_pd3dTexture->UnlockRect(0) ) ) ; + } + + SDL_FreeSurface(img); + LOG->Trace( "RageBitmapTexture: Loaded '%s' (%ux%u) from disk. bStretch = %d, source %d,%d; image %d,%d.", + m_sFilePath, GetTextureWidth(), GetTextureHeight(), + bStretch, m_iSourceWidth, m_iSourceHeight, + m_iImageWidth, m_iImageHeight); +} + +#else + +#include "DXUtil.h" +#include "dxerr8.h" void RageBitmapTexture::Create( int dwMaxSize, @@ -192,6 +460,7 @@ void RageBitmapTexture::Create( m_iTextureWidth = ddsd.Width; m_iTextureHeight = ddsd.Height; + if( bStretch ) { m_iImageWidth = m_iTextureWidth; @@ -215,3 +484,4 @@ void RageBitmapTexture::Create( ); } +#endif \ No newline at end of file