Browse Source

Merge pull request #414 from blackberry-gaming/next-sgrenier

Next sgrenier
Steve Grenier 13 years ago
parent
commit
37ffb72eb8

+ 0 - 3
gameplay/src/Base.h

@@ -168,7 +168,6 @@ extern void printError(const char* format, ...);
 #include <png.h>
 
 #define WINDOW_VSYNC        1
-#define WINDOW_FULLSCREEN   0
 
 // Graphics (OpenGL)
 #if defined (__QNX__) || defined(__ANDROID__)
@@ -181,7 +180,6 @@ extern void printError(const char* format, ...);
     extern PFNGLISVERTEXARRAYOESPROC glIsVertexArray;
     #define glClearDepth glClearDepthf
     #define OPENGL_ES
-    #define USE_PVRTC
 #elif WIN32
     #define WIN32_LEAN_AND_MEAN
     #include <GL/glew.h>
@@ -197,7 +195,6 @@ extern void printError(const char* format, ...);
         #define glIsVertexArray glIsVertexArrayOES
         #define glClearDepth glClearDepthf
         #define OPENGL_ES
-        #define USE_PVRTC
         #define USE_VAO
     #elif TARGET_OS_MAC
         #include <OpenGL/gl.h>

+ 44 - 2
gameplay/src/FileSystem.cpp

@@ -1,5 +1,6 @@
 #include "Base.h"
 #include "FileSystem.h"
+#include "Properties.h"
 
 #ifdef WIN32
     #include <windows.h>
@@ -61,6 +62,7 @@ void makepath(std::string path, int mode)
 #endif
 
 static std::string __resourcePath("./");
+static std::map<std::string, std::string> __aliases;
 
 FileSystem::FileSystem()
 {
@@ -80,6 +82,46 @@ const char* FileSystem::getResourcePath()
     return __resourcePath.c_str();
 }
 
+void FileSystem::loadResourceAliases(const char* aliasFilePath)
+{
+    Properties* properties = Properties::create(aliasFilePath);
+    if (properties)
+    {
+        Properties* aliases;
+        while ((aliases = properties->getNextNamespace()) != NULL)
+        {
+            loadResourceAliases(aliases);
+        }
+    }
+    SAFE_DELETE(properties);
+}
+
+void FileSystem::loadResourceAliases(Properties* properties)
+{
+    assert(properties);
+
+    const char* name;
+    while ((name = properties->getNextProperty()) != NULL)
+    {
+        __aliases[name] = properties->getString();
+    }
+}
+
+const char* FileSystem::resolvePath(const char* path)
+{
+    size_t len = strlen(path);
+    if (len > 1 && path[0] == '@')
+    {
+        std::string alias(path + 1);
+        std::map<std::string, std::string>::const_iterator itr = __aliases.find(alias);
+        if (itr == __aliases.end())
+            return path; // no matching alias found
+        return itr->second.c_str();
+    }
+
+    return path;
+}
+
 bool FileSystem::listFiles(const char* dirPath, std::vector<std::string>& files)
 {
     // TODO make this method work with absolute and relative paths.
@@ -154,8 +196,8 @@ FILE* FileSystem::openFile(const char* path, const char* mode)
     GP_ASSERT(path);
 
     std::string fullPath(__resourcePath);
-    fullPath += path;
-    
+    fullPath += resolvePath(path);
+
 #ifdef __ANDROID__
     std::string directoryPath = fullPath.substr(0, fullPath.rfind('/'));
     struct stat s;

+ 49 - 0
gameplay/src/FileSystem.h

@@ -4,6 +4,8 @@
 namespace gameplay
 {
 
+class Properties;
+
 /**
  * Defines a set of functions for interacting with the device filesystem.
  */
@@ -33,6 +35,53 @@ public:
      */
     static const char* getResourcePath();
 
+    /**
+     * Loads a properties file containing a list of filesystem aliases.
+     *
+     * The specified aliases file is a valid properties file that contains one
+     * or more namespaces with a list of fielsystem aliases that will be used
+     * to establish soft links to files when reading files through this class.
+     *
+     * This can be helpful for managing loading of resources that may change
+     * from one platform to another (such as texture formats). An aliases
+     * file per-platform can be maintained and asset loading code can refer
+     * to the alias name instead of the actual hard file name.
+     *
+     * @param aliasFilePath Path to a properties file containing filesystem aliases.
+     * @see Properties
+     */
+    static void loadResourceAliases(const char* aliasFilePath);
+
+    /**
+     * Loads a set of filesystem aliases from the given Properties object.
+     *
+     * The specified properties object contains a single namespace with a list
+     * of fielsystem aliases that will be used to establish soft links to files
+     * when reading files through this class.
+     *
+     * This can be helpful for managing loading of resources that may change
+     * from one platform to another (such as texture formats). An aliases
+     * file per-platform can be maintained and asset loading code can refer
+     * to the alias name instead of the actual hard file name.
+     *
+     * @param properties Properties object containing filesystem aliases.
+     * @see Properties
+     */
+    static void loadResourceAliases(Properties* properties);
+
+    /**
+     * Resolves a filesystem path.
+     *
+     * If the specified path is a filesystem alias, the alias will be
+     * resolved and the physical file will be returned.
+     *
+     * Note that this method does not convert a relative path to an
+     * absolute filesystem path.
+     *
+     * @param path Path to resolve.
+     */
+    static const char* resolvePath(const char* path);
+
     /**
      * Lists the files in the specified directory and adds the files to the vector. Excludes directories.
      * 

+ 34 - 12
gameplay/src/Game.cpp

@@ -2,6 +2,7 @@
 #include "Game.h"
 #include "Platform.h"
 #include "RenderState.h"
+#include "FileSystem.h"
 
 // Extern global variables
 GLenum __gl_error_code = GL_NO_ERROR;
@@ -17,7 +18,7 @@ long Game::_pausedTimeTotal = 0L;
 Game::Game() 
     : _initialized(false), _state(UNINITIALIZED), 
       _frameLastFPS(0), _frameCount(0), _frameRate(0), 
-      _clearDepth(1.0f), _clearStencil(0),
+      _clearDepth(1.0f), _clearStencil(0), _properties(NULL),
       _animationController(NULL), _audioController(NULL), _physicsController(NULL), _audioListener(NULL)
 {
     GP_ASSERT(__gameInstance == NULL);
@@ -66,20 +67,15 @@ bool Game::isVsync()
     return Platform::isVsync();
 }
 
-int Game::run(int width, int height)
+int Game::run()
 {
     if (_state != UNINITIALIZED)
         return -1;
 
-    if (width == -1)
-        _width = Platform::getDisplayWidth();
-    else
-        _width = width;
-    
-    if (height == -1)
-        _height = Platform::getDisplayHeight();
-    else
-        _height = height;
+    loadConfig();
+
+    _width = Platform::getDisplayWidth();
+    _height = Platform::getDisplayHeight();
 
     // Start up game systems.
     if (!startup())
@@ -133,7 +129,9 @@ void Game::shutdown()
         SAFE_DELETE(_audioListener);
 
         RenderState::finalize();
-        
+
+        SAFE_DELETE(_properties);
+
         _state = UNINITIALIZED;
     }
 }
@@ -314,6 +312,30 @@ void Game::updateOnce()
     _audioController->update(elapsedTime);
 }
 
+Properties* Game::getConfig() const
+{
+    if (_properties == NULL)
+        const_cast<Game*>(this)->loadConfig();
+
+    return _properties;
+}
+
+void Game::loadConfig()
+{
+    if (_properties == NULL)
+    {
+        // Try to load custom config from file.
+        _properties = Properties::create("game.config");
+        if (_properties == NULL)
+            _properties = new Properties();
+
+        // Load filesystem aliases
+        Properties* aliases = _properties->getNamespace("aliases", true);
+        if (aliases)
+            FileSystem::loadResourceAliases(aliases);
+    }
+}
+
 void Game::fireTimeEvents(long frameTime)
 {
     while (_timeEvents->size() > 0)

+ 17 - 4
gameplay/src/Game.h

@@ -99,14 +99,21 @@ public:
     inline State getState() const;
 
     /**
-     * Call this method to initialize the game, and begin running the game.
+     * Returns the game configuration object.
      *
-     * @param width The width of the game window to run at. Default is -1 meaning native resolution width.
-     * @param height The height of the game window to run at. Default is -1 meaning native resolution height.
+     * This method returns a Properties object containing the contents
+     * of the game.config file.
+     *
+     * @return The game conifguration Properties object.
+     */
+    Properties* getConfig() const;
+
+    /**
+     * Called to initialize the game, and begin running the game.
      * 
      * @return Zero for normal termination, or non-zero if an error occurred.
      */
-    int run(int width = -1, int height = -1);
+    int run();
 
     /**
      * Pauses the game after being run.
@@ -393,6 +400,11 @@ private:
      */
     void fireTimeEvents(long frameTime);
 
+    /**
+     * Loads the game configuration.
+     */
+    void loadConfig();
+
     bool _initialized;                          // If game has initialized yet.
     State _state;                               // The game state.
     static long _pausedTimeLast;                // The last time paused.
@@ -406,6 +418,7 @@ private:
     Vector4 _clearColor;                        // The clear color value last used for clearing the color buffer.
     float _clearDepth;                          // The clear depth value last used for clearing the depth buffer.
     int _clearStencil;                          // The clear stencil value last used for clearing the stencil buffer.
+    Properties* _properties;                    // Game configuration properties object.
     AnimationController* _animationController;  // Controls the scheduling and running of animations.
     AudioController* _audioController;          // Controls audio sources that are playing in the game.
     PhysicsController* _physicsController;      // Controls the simulation of a physics scene and entities.

+ 1 - 1
gameplay/src/PlatformAndroid.cpp

@@ -666,7 +666,7 @@ static void engine_handle_cmd(struct android_app* app, int32_t cmd)
 
             if (Game::getInstance()->getState() == Game::UNINITIALIZED)
             {
-                Game::getInstance()->run(__width, __height);
+                Game::getInstance()->run();
             }
             else
             {

+ 1 - 1
gameplay/src/PlatformMacOSX.mm

@@ -138,7 +138,7 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
     NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
     NSString* path = [bundlePath stringByAppendingString:@"/Contents/Resources/"];
     FileSystem::setResourcePath([path cStringUsingEncoding:NSASCIIStringEncoding]);
-    _game->run(WINDOW_WIDTH, WINDOW_HEIGHT);
+    _game->run();
     
     [[self window] setLevel: NSFloatingWindowLevel];
     [[self window] makeKeyAndOrderFront: self];

+ 1 - 1
gameplay/src/PlatformQNX.cpp

@@ -788,7 +788,7 @@ int Platform::enterMessagePump()
     __timeStart = timespec2millis(&__timespec);
     __timeAbsolute = 0L;
 
-    _game->run(__screenWindowSize[0], __screenWindowSize[1]);
+    _game->run();
 
     // Message loop.
     while (true)

+ 65 - 18
gameplay/src/PlatformWin32.cpp

@@ -9,8 +9,8 @@
 #include <windowsx.h>
 
 // Default to 720p
-#define WINDOW_WIDTH    1280
-#define WINDOW_HEIGHT   720
+static int __width = 1280;
+static int __height = 720;
 
 static long __timeTicksPerMillis;
 static long __timeStart;
@@ -262,8 +262,8 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     }
 
     // Scale factors for the mouse movement used to simulate the accelerometer.
-    static const float ACCELEROMETER_X_FACTOR = 90.0f / WINDOW_WIDTH;
-    static const float ACCELEROMETER_Y_FACTOR = 90.0f / WINDOW_HEIGHT;
+    static const float ACCELEROMETER_X_FACTOR = 90.0f / __width;
+    static const float ACCELEROMETER_Y_FACTOR = 90.0f / __height;
 
     static int lx, ly;
 
@@ -332,7 +332,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         break;
 
     case WM_MOUSEMOVE:
-    {
+    {
         int x = GET_X_LPARAM(lParam);
         int y = GET_Y_LPARAM(lParam);
 
@@ -359,7 +359,7 @@ LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
                 lx = x;
                 ly = y;
             }
-        }
+        }
         break;
     }
 
@@ -457,9 +457,37 @@ Platform* Platform::create(Game* game)
     __hinstance = ::GetModuleHandle(NULL);
 
     LPCTSTR windowClass = L"gameplay";
-    LPCTSTR windowName = L"";
+    std::wstring windowName;
+    bool fullscreen = false;
+    
+    // Read window settings from config
+    Properties* config = game->getConfig()->getNamespace("window", true);
+    if (config)
+    {
+        // Read window title
+        const char* title = config->getString("title");
+        if (title)
+        {
+            int len = MultiByteToWideChar(CP_ACP, 0, title, -1, NULL, 0);
+            wchar_t* wtitle = new wchar_t[len];
+            MultiByteToWideChar(CP_ACP, 0, title, -1, wtitle, len);
+            windowName = wtitle;
+            SAFE_DELETE_ARRAY(wtitle);
+        }
+
+        // Read window size
+        int width = config->getInt("width");
+        if (width != 0)
+            __width = width;
+        int height = config->getInt("height");
+        if (height != 0)
+            __height = height;
+
+        // Read fullscreen state
+        fullscreen = config->getBool("fullscreen");
+    }
 
-    RECT rect = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT };
+    RECT rect = { 0, 0, __width, __height };
 
     // Register our window class.
     WNDCLASSEX wc;
@@ -478,23 +506,42 @@ Platform* Platform::create(Game* game)
 
     if (!::RegisterClassEx(&wc))
         goto error;
-    
+
+    if (fullscreen)
+    {
+        DEVMODE dm;
+        memset(&dm, 0, sizeof(dm));
+        dm.dmSize= sizeof(dm);
+        dm.dmPelsWidth	= __width;
+        dm.dmPelsHeight	= __height;
+        dm.dmBitsPerPel	= 32;
+        dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
+
+        // Try To Set Selected Mode And Get Results.  NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
+        if (ChangeDisplaySettings(&dm, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
+        {
+            fullscreen = false;
+            GP_ERROR("Failed to start fullscreen with resolution %dx%d.", __width, __height);
+        }
+    }
+
     // Set the window style.
-    DWORD style   = ( WINDOW_FULLSCREEN ? WS_POPUP : WS_POPUP | WS_BORDER | WS_CAPTION | WS_SYSMENU) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
-    DWORD styleEx = (WINDOW_FULLSCREEN ? WS_EX_APPWINDOW : WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
+    DWORD style   = (fullscreen ? WS_POPUP : WS_POPUP | WS_BORDER | WS_CAPTION | WS_SYSMENU) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
+    //DWORD style   = (fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
+    DWORD styleEx = (fullscreen ? WS_EX_APPWINDOW : WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
 
     // Adjust the window rectangle so the client size is the requested size.
     AdjustWindowRectEx(&rect, style, FALSE, styleEx);
-    
+
     // Create the native Windows window.
-    __hwnd = CreateWindowEx(styleEx, windowClass, windowName, style, 0, 0, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, __hinstance, NULL);
+    __hwnd = CreateWindowEx(styleEx, windowClass, windowName.c_str(), style, 0, 0, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, __hinstance, NULL);
 
     // Get the drawing context.
     __hdc = GetDC(__hwnd);
 
     // Center the window
-    const int screenX = (GetSystemMetrics(SM_CXSCREEN) - WINDOW_WIDTH) / 2;
-    const int screenY = (GetSystemMetrics(SM_CYSCREEN) - WINDOW_HEIGHT) / 2;
+    const int screenX = (GetSystemMetrics(SM_CXSCREEN) - __width) / 2;
+    const int screenY = (GetSystemMetrics(SM_CYSCREEN) - __height) / 2;
     ::SetWindowPos(__hwnd, __hwnd, screenX, screenY, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
 
     // Choose pixel format. 32-bit. RGBA.
@@ -569,7 +616,7 @@ int Platform::enterMessagePump()
     SwapBuffers(__hdc);
 
     if (_game->getState() != Game::RUNNING)
-        _game->run(WINDOW_WIDTH, WINDOW_HEIGHT);
+        _game->run();
 
     // Enter event dispatch loop.
     MSG msg;
@@ -602,12 +649,12 @@ void Platform::signalShutdown()
 
 unsigned int Platform::getDisplayWidth()
 {
-    return WINDOW_WIDTH;
+    return __width;
 }
 
 unsigned int Platform::getDisplayHeight()
 {
-    return WINDOW_HEIGHT;
+    return __height;
 }
     
 long Platform::getAbsoluteTime()

+ 1 - 1
gameplay/src/PlatformiOS.mm

@@ -146,7 +146,7 @@ int getKey(unichar keyCode);
         
         _game = Game::getInstance();
         __timeStart = getMachTimeInMilliseconds();
-        _game->run(WINDOW_WIDTH, WINDOW_HEIGHT);          
+        _game->run();
     }
     return self;
 }

+ 2 - 2
gameplay/src/Properties.cpp

@@ -520,7 +520,7 @@ void Properties::rewind()
     _namespacesItr = _namespaces.end();
 }
 
-Properties* Properties::getNamespace(const char* id) const
+Properties* Properties::getNamespace(const char* id, bool searchNames) const
 {
     GP_ASSERT(id);
 
@@ -530,7 +530,7 @@ Properties* Properties::getNamespace(const char* id) const
     for (it = _namespaces.begin(); it < _namespaces.end(); it++)
     {
         ret = *it;
-        if (strcmp(ret->_id.c_str(), id) == 0)
+        if (strcmp(searchNames ? ret->_namespace.c_str() : ret->_id.c_str(), id) == 0)
         {
             return ret;
         }

+ 13 - 6
gameplay/src/Properties.h

@@ -31,7 +31,8 @@ namespace gameplay
     // This line defines a namespace of type "mynamespace" without an ID:
     mynamespace
     {
-        // This namespace can be retrieved by searching for its ID, "spriteTexture":
+        // This namespace can be retrieved by searching for its ID, "spriteTexture",
+        // or by its name "texture":
         texture spriteTexture 
         {
             fileName = sprite.png
@@ -125,6 +126,8 @@ namespace gameplay
  */
 class Properties
 {
+    friend class Game;
+
 public:
 
     /**
@@ -176,14 +179,18 @@ public:
     void rewind();
 
     /**
-     * Get a specific namespace by ID.  This method will perform a depth-first search
-     * on all namespaces and inner namespaces within this Property.
+     * Get a specific namespace by ID or name. This method will perform
+     * a depth-first search on all namespaces and inner namespaces within
+     * this Property.
      *
-     * @param id The ID of a specific namespace.
+     * @param id The ID or name of the namespace to find.
+     * @param searchNames If true, namespace names are used in the search,
+     *      instead of namespace IDs. By default this parameter is false
+     *      and namespace IDs are searched.
      * 
-     * @return A properties object with the given ID.
+     * @return A properties object with the given ID or name.
      */
-    Properties* getNamespace(const char* id) const;
+    Properties* getNamespace(const char* id, bool searchNames = false) const;
 
     /**
      * Get the name of this Property's namespace.

+ 412 - 64
gameplay/src/Texture.cpp

@@ -3,12 +3,48 @@
 #include "Texture.h"
 #include "FileSystem.h"
 
+// PVRTC (GL_IMG_texture_compression_pvrtc) : Imagination based gpus
+#ifndef GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG
+#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
+#endif
+#ifndef GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
+#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03
+#endif
+#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG
+#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
+#endif
+#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
+#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
+#endif
+
+// S3TC/DXT (GL_EXT_texture_compression_s3tc) : Most desktop/console gpus
+#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
+#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
+#endif
+#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
+#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
+#endif
+#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
+#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
+#endif
+
+// ATC (GL_AMD_compressed_ATC_texture) : Qualcomm/Adreno based gpus
+#ifndef ATC_RGB_AMD
+#define ATC_RGB_AMD 0x8C92
+#endif
+#ifndef ATC_RGBA_EXPLICIT_ALPHA_AMD
+#define ATC_RGBA_EXPLICIT_ALPHA_AMD 0x8C93
+#endif
+#ifndef ATC_RGBA_INTERPOLATED_ALPHA_AMD
+#define ATC_RGBA_INTERPOLATED_ALPHA_AMD 0x87EE
+#endif
+
 namespace gameplay
 {
 
 static std::vector<Texture*> __textureCache;
 
-Texture::Texture() : _handle(0), _mipmapped(false), _cached(false)
+Texture::Texture() : _handle(0), _mipmapped(false), _cached(false), _compressed(false)
 {
 }
 
@@ -60,7 +96,7 @@ Texture* Texture::create(const char* path, bool generateMipmaps)
     Texture* texture = NULL;
 
     // Filter loading based on file extension.
-    const char* ext = strrchr(path, '.');
+    const char* ext = strrchr(FileSystem::resolvePath(path), '.');
     if (ext)
     {
         switch (strlen(ext))
@@ -75,12 +111,13 @@ Texture* Texture::create(const char* path, bool generateMipmaps)
             }
             else if (tolower(ext[1]) == 'p' && tolower(ext[2]) == 'v' && tolower(ext[3]) == 'r')
             {
-#ifdef USE_PVRTC
                 // PowerVR Compressed Texture RGBA
                 texture = createCompressedPVRTC(path);
-#else
-                texture = NULL; // Cannot handle PVRTC if not supported on platform
-#endif
+            }
+            else if (tolower(ext[1]) == 'd' && tolower(ext[2]) == 'd' && tolower(ext[3]) == 's')
+            {
+                // DDS file format (DXT/S3TC) compressed textures
+                texture = createCompressedDDS(path);
             }
             break;
         }
@@ -110,7 +147,7 @@ Texture* Texture::create(Image* image, bool generateMipmaps)
     case Image::RGBA:
         return create(Texture::RGBA, image->getWidth(), image->getHeight(), image->getData(), generateMipmaps);
     }
-    
+
     return NULL;
 }
 
@@ -147,18 +184,195 @@ Texture* Texture::create(Format format, unsigned int width, unsigned int height,
     return texture;
 }
 
-#ifdef USE_PVRTC
+// Computes the size of a PVRTC data chunk for a mipmap level of the given size.
+unsigned int computePVRTCDataSize(int width, int height, int bpp)
+{
+    int blockSize;
+    int widthBlocks;
+    int heightBlocks;
+
+    if (bpp == 4)
+    {
+        blockSize = 4 * 4; // Pixel by pixel block size for 4bpp
+        widthBlocks = std::max(width >> 2, 2);
+        heightBlocks = std::max(height >> 2, 2);
+    }
+    else
+    {
+        blockSize = 8 * 4; // Pixel by pixel block size for 2bpp
+        widthBlocks = std::max(width >> 3, 2);
+        heightBlocks = std::max(height >> 2, 2);
+    }
+
+    return widthBlocks * heightBlocks * ((blockSize  * bpp) >> 3);
+}
+
 Texture* Texture::createCompressedPVRTC(const char* path)
 {
-    char PVRTCIdentifier[] = "PVR!";
+    FILE* file = FileSystem::openFile(path, "rb");
+    if (file == NULL)
+    {
+        GP_ERROR("Failed to load file: %s", path);
+        return NULL;
+    }
+
+    // Read first 4 bytes to determine PVRTC format
+    unsigned int read;
+    unsigned int version;
+    read = fread(&version, sizeof(unsigned int), 1, file);
+    assert(read == 1);
 
-    enum
+    // Rewind to start of header
+    fseek(file, 0, SEEK_SET);
+
+    // Read texture data
+    GLsizei width, height;
+    GLenum format;
+    GLubyte* data = NULL;
+    unsigned int mipMapCount;
+
+    if (version == 0x03525650)
     {
-        PVRTC_2 = 24,
-        PVRTC_4
-    };
+    	// Modern PVR file format.
+    	data = readCompressedPVRTC(path, file, &width, &height, &format, &mipMapCount);
+    }
+    else
+    {
+    	// Legacy PVR file format.
+    	data = readCompressedPVRTCLegacy(path, file, &width, &height, &format, &mipMapCount);
+    }
+
+    fclose(file);
+
+    if (data == NULL)
+    {
+    	GP_ERROR("Failed to read PVR file: %s", path);
+    	return NULL;
+    }
+
+    int bpp = (format == GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG || format == GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG) ? 2 : 4;
+
+    // Generate our texture.
+	GLuint textureId;
+	GL_ASSERT( glGenTextures(1, &textureId) );
+	GL_ASSERT( glBindTexture(GL_TEXTURE_2D, textureId) );
+	GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipMapCount > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR) );
 
+	Texture* texture = new Texture();
+	texture->_handle = textureId;
+	texture->_width = width;
+	texture->_height = height;
+	texture->_mipmapped = mipMapCount > 1;
+	texture->_compressed = true;
+
+	// Load the data for each level
+	GLubyte* ptr = data;
+	for (unsigned int level = 0; level < mipMapCount; ++level)
+	{
+		unsigned int dataSize = computePVRTCDataSize(width, height, bpp);
+
+		// Upload data to GL
+		GL_ASSERT( glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height, 0, dataSize, ptr) );
+
+		width = std::max(width >> 1, 1);
+		height = std::max(height >> 1, 1);
+		ptr += dataSize;
+	}
+
+	// Free data
+	SAFE_DELETE_ARRAY(data);
+
+    return texture;
+}
+
+GLubyte* Texture::readCompressedPVRTC(const char* path, FILE* file, GLsizei* width, GLsizei* height, GLenum* format, unsigned int* mipMapCount)
+{
     struct pvrtc_file_header
+    {
+        unsigned int version;
+        unsigned int flags;
+        unsigned int pixelFormat[2];
+        unsigned int colorSpace;
+        unsigned int channelType;
+        unsigned int height;
+        unsigned int width;
+        unsigned int depth;
+        unsigned int surfaceCount;
+        unsigned int faceCount;
+        unsigned int mipMapCount;
+        unsigned int metaDataSize;
+    };
+
+    unsigned int read;
+
+    // Read header data
+    pvrtc_file_header header;
+    read = fread(&header, sizeof(pvrtc_file_header), 1, file);
+    assert(read == 1);
+
+    if (header.pixelFormat[1] != 0)
+    {
+        // Unsupported pixel format
+        GP_ERROR("Unsupported pixel format in PVR file (MSB == %d != 0): %s", header.pixelFormat[1], path);
+        return NULL;
+    }
+
+    int bpp;
+    switch (header.pixelFormat[0])
+    {
+    case 0:
+        *format = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
+        bpp = 2;
+        break;
+    case 1:
+        *format = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
+        bpp = 2;
+        break;
+    case 2:
+        *format = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
+        bpp = 4;
+        break;
+    case 3:
+        *format = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+        bpp = 4;
+        break;
+    default:
+        // Unsupported format
+        GP_ERROR("Unsupported pixel format value (%d) in PVR file: %s", header.pixelFormat[0], path);
+        return NULL;
+    }
+
+    *width = (GLsizei)header.width;
+    *height = (GLsizei)header.height;
+    *mipMapCount = header.mipMapCount;
+
+    // Skip meta-data
+    assert( fseek(file, header.metaDataSize, SEEK_CUR) == 0 );
+
+    // Compute total size of data to be read.
+    int w = *width;
+    int h = *height;
+    unsigned int dataSize = 0;
+    for (unsigned int level = 0; level < header.mipMapCount; ++level)
+    {
+        dataSize += computePVRTCDataSize(w, h, bpp);
+    	w = std::max(w>>1, 1);
+    	h = std::max(h>>1, 1);
+    }
+
+    // Read data
+    GLubyte* data = new GLubyte[dataSize];
+    read = fread(data, 1, dataSize, file);
+    assert(read == dataSize);
+
+    return data;
+}
+
+GLubyte* Texture::readCompressedPVRTCLegacy(const char* path, FILE* file, GLsizei* width, GLsizei* height, GLenum* format, unsigned int* mipMapCount)
+{
+    char PVRTCIdentifier[] = "PVR!";
+
+    struct pvrtc_file_header_legacy
     {
         unsigned int size;                  // size of the structure
         unsigned int height;                // height of surface to be created
@@ -173,24 +387,16 @@ Texture* Texture::createCompressedPVRTC(const char* path)
         unsigned int alphaBitMask;          // mask for alpha channel
         unsigned int pvrtcTag;              // magic number identifying pvrtc file
         unsigned int surfaceCount;          // number of surfaces present in the pvrtc
-    } ;
-
-    FILE* file = FileSystem::openFile(path, "rb");
-    if (file == NULL)
-    {
-        GP_ERROR("Failed to load file: %s", path);
-        return NULL;
-    }
+    };
 
     // Read the file header
-    unsigned int size = sizeof(pvrtc_file_header);
-    pvrtc_file_header header;
+    unsigned int size = sizeof(pvrtc_file_header_legacy);
+    pvrtc_file_header_legacy header;
     unsigned int read = (int)fread(&header, 1, size, file);
     GP_ASSERT(read == size);
     if (read != size)
     {
         GP_ERROR("Read file header error for pvrtc file: %s (%d < %d)", path, (int)read, (int)size);
-        fclose(file);
         return NULL;
     }
 
@@ -201,86 +407,223 @@ Texture* Texture::createCompressedPVRTC(const char* path)
         PVRTCIdentifier[3] != (char)((header.pvrtcTag >> 24) & 0xff))
      {
         GP_ERROR("Invalid PVRTC compressed texture file: %s", path);
-        fclose(file);
         return NULL;
     }
 
     // Format flags for GLenum format
-    GLenum format;
-    unsigned int formatFlags = header.formatflags & 0xff;
-    if (formatFlags == PVRTC_4)
+    if (header.bpp == 4)
     {
-        format = header.alphaBitMask ? COMPRESSED_RGBA_PVRTC_4BPP : COMPRESSED_RGB_PVRTC_4BPP;
+        *format = header.alphaBitMask ? GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG : GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
     }
-    else if (formatFlags == PVRTC_2)
+    else if (header.bpp == 2)
     {
-        format = header.alphaBitMask ? COMPRESSED_RGBA_PVRTC_2BPP : COMPRESSED_RGB_PVRTC_2BPP;
+        *format = header.alphaBitMask ? GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG : GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
     }
     else
     {
         GP_ERROR("Invalid PVRTC compressed texture format flags for file: %s", path);
-        fclose(file);
         return NULL;
     }
 
-    unsigned char* data = new unsigned char[header.dataSize];
+    *width = (GLsizei)header.width;
+    *height = (GLsizei)header.height;
+    *mipMapCount = header.mipmapCount + 1; // +1 because mipmapCount does not include the base level
+
+    GLubyte* data = new GLubyte[header.dataSize];
     read = (int)fread(data, 1, header.dataSize, file);
     GP_ASSERT(read == header.dataSize);
     if (read != header.dataSize)
     {
         GP_ERROR("Read file data error for pvrtc file: %s (%d < %d)", path, (int)read, (int)header.dataSize);
         SAFE_DELETE_ARRAY(data);
-        fclose(file);
         return NULL;
     }
-    // Close file
-    fclose(file);
 
-    // Load our texture.
+    return data;
+}
+
+Texture* Texture::createCompressedDDS(const char* path)
+{
+    // DDS file structures
+    struct dds_pixel_format
+    {
+        unsigned int dwSize;
+        unsigned int dwFlags;
+        unsigned int dwFourCC;
+        unsigned int dwRGBBitCount;
+        unsigned int dwRBitMask;
+        unsigned int dwGBitMask;
+        unsigned int dwBBitMask;
+        unsigned int dwABitMask;
+    };
+
+    struct dds_header
+    {
+        unsigned int     dwSize;
+        unsigned int     dwFlags;
+        unsigned int     dwHeight;
+        unsigned int     dwWidth;
+        unsigned int     dwPitchOrLinearSize;
+        unsigned int     dwDepth;
+        unsigned int     dwMipMapCount;
+        unsigned int     dwReserved1[11];
+        dds_pixel_format ddspf;
+        unsigned int     dwCaps;
+        unsigned int     dwCaps2;
+        unsigned int     dwCaps3;
+        unsigned int     dwCaps4;
+        unsigned int     dwReserved2;
+    };
+
+    struct dds_mip_level
+    {
+        GLubyte* data;
+        GLsizei width;
+        GLsizei height;
+        GLsizei size;
+    };
+
+    Texture* texture = NULL;
+
+    // Read DDS file
+    FILE* fp = FileSystem::openFile(path, "rb");
+    if (fp == NULL)
+        return NULL;
+
+    // Validate DDS magic number
+    char code[4];
+    if (fread(code, 1, 4, fp) != 4 || strncmp(code, "DDS ", 4) != 0)
+        return NULL; // not a valid dds file
+
+    // Read DDS header
+    dds_header header;
+    if (fread(&header, sizeof(dds_header), 1, fp) != 1)
+        return NULL;
+
+    if ((header.dwFlags & 0x20000/*DDSD_MIPMAPCOUNT*/) == 0)
+    {
+        // Mipmap count not specified (non-mipmapped texture)
+        header.dwMipMapCount = 1;
+    }
+
+    // Allocate mip level structures
+    dds_mip_level* mipLevels = new dds_mip_level[header.dwMipMapCount];
+    memset(mipLevels, 0, sizeof(dds_mip_level) * header.dwMipMapCount);
+
+    GLenum format, internalFormat;
+    bool compressed = false;
+    GLsizei width = header.dwWidth;
+    GLsizei height = header.dwHeight;
+    int bytesPerBlock;
+
+    if (header.ddspf.dwFlags & 0x4/*DDPF_FOURCC*/)
+    {
+        compressed = true;
+
+        // Compressed
+        switch (header.ddspf.dwFourCC)
+        {
+        case ('D'|('X'<<8)|('T'<<16)|('1'<<24)):
+            format = internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+            bytesPerBlock = 8;
+            break;
+        case ('D'|('X'<<8)|('T'<<16)|('3'<<24)):
+            format = internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+            bytesPerBlock = 16;
+            break;
+        case ('D'|('X'<<8)|('T'<<16)|('5'<<24)):
+            format = internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+            bytesPerBlock = 16;
+            break;
+        case ('A'|('T'<<8)|('C'<<16)|(' '<<24)):
+            format = internalFormat = ATC_RGB_AMD;
+            bytesPerBlock = 8;
+            break;
+        case ('A'|('T'<<8)|('C'<<16)|('A'<<24)):
+            format = internalFormat = ATC_RGBA_EXPLICIT_ALPHA_AMD;
+            bytesPerBlock = 16;
+            break;
+        case ('A'|('T'<<8)|('C'<<16)|('I'<<24)):
+            format = internalFormat = ATC_RGBA_INTERPOLATED_ALPHA_AMD;
+            bytesPerBlock = 16;
+            break;
+        default:
+            // Unsupported
+            return NULL;
+        }
+
+        for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
+        {
+            mipLevels[i].width = width;
+            mipLevels[i].height = height;
+            mipLevels[i].size =  std::max(1, (width+3) >> 2) * std::max(1, (height+3) >> 2) * bytesPerBlock;
+            mipLevels[i].data = new GLubyte[mipLevels[i].size];
+
+            if (fread(mipLevels[i].data, 1, mipLevels[i].size, fp) != (unsigned int)mipLevels[i].size)
+            {
+                GP_ERROR("Failed to load dds compressed texture bytes for texture: %s", path);
+                goto cleanup;
+            }
+
+            width  = std::max(1, width >> 1);
+            height = std::max(1, height >> 1);
+        }
+    }
+    else if (header.ddspf.dwFlags == 0x40/*DDPF_RGB*/)
+    {
+        // RGB (uncompressed)
+        // Note: Use GL_BGR as internal format to flip bytes.
+        return NULL; // unsupported
+    }
+    else if (header.ddspf.dwFlags == 0x41/*DDPF_RGB|DDPF_ALPHAPIXELS*/)
+    {
+        // RGBA (uncompressed)
+        // Note: Use GL_BGRA as internal format to flip bytes.
+        return NULL; // unsupported
+    }
+    else
+    {
+        // Unsupported
+        return NULL;
+    }
+
+    // Generate GL texture
     GLuint textureId;
     GL_ASSERT( glGenTextures(1, &textureId) );
     GL_ASSERT( glBindTexture(GL_TEXTURE_2D, textureId) );
-    GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, header.mipmapCount > 0 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ) );
+    GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, header.dwMipMapCount > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ) );
 
-    Texture* texture = new Texture();
+    // Create gameplay texture
+    texture = new Texture();
     texture->_handle = textureId;
-    texture->_width = header.width;
-    texture->_height = header.height;
-
-    // Load the data for each level
-    unsigned int width = header.width;
-    unsigned int height = header.height;
-    unsigned int blockSize = 0;
-    unsigned int widthBlocks = 0;
-    unsigned int heightBlocks = 0;
-    unsigned int bpp = 0;
-    unsigned int dataSize = 0;
-    unsigned char* dataOffset = data;
+    texture->_width = header.dwWidth;
+    texture->_height = header.dwHeight;
+    texture->_compressed = compressed;
+    texture->_mipmapped = header.dwMipMapCount > 1;
 
-    for (unsigned int level = 0; level <= header.mipmapCount; level++)
+    // Load texture data
+    for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
     {
-        if (formatFlags == PVRTC_4)
+        if (compressed)
         {
-            dataSize = ( max((int)width, 8) * max((int)height, 8) * 4 + 7) / 8;
+            GL_ASSERT( glCompressedTexImage2D(GL_TEXTURE_2D, i, format, mipLevels[i].width, mipLevels[i].height, 0, mipLevels[i].size, mipLevels[i].data) );
         }
         else
         {
-            dataSize = ( max((int)width, 16) * max((int)height, 8) * 2 + 7) / 8;
+            GL_ASSERT( glTexImage2D(GL_TEXTURE_2D, i, internalFormat, mipLevels[i].width, mipLevels[i].height, 0, format, GL_UNSIGNED_INT, mipLevels[i].data) );
         }
-
-        GL_ASSERT( glCompressedTexImage2D(GL_TEXTURE_2D, level, (GLenum)format, width, height, 0, dataSize, dataOffset) );
-
-        dataOffset += dataSize;
-        width = max((int)width >> 1, 1);
-        height = max((int)height >> 1, 1);
     }
 
-    SAFE_DELETE_ARRAY(data);
+cleanup:
+
+    // Cleanup mip data
+    for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
+        SAFE_DELETE_ARRAY(mipLevels[i].data);
+    SAFE_DELETE_ARRAY(mipLevels);
 
     return texture;
 }
-#endif
-    
+
 unsigned int Texture::getWidth() const
 {
     return _width;
@@ -335,6 +678,11 @@ bool Texture::isMipmapped() const
     return _mipmapped;
 }
 
+bool Texture::isCompressed() const
+{
+    return _compressed;
+}
+
 Texture::Sampler::Sampler(Texture* texture)
     : _texture(texture), _wrapS(Texture::REPEAT), _wrapT(Texture::REPEAT), _magFilter(Texture::LINEAR)
 {

+ 15 - 10
gameplay/src/Texture.h

@@ -29,13 +29,7 @@ public:
         RGB     = GL_RGB,
         RGBA    = GL_RGBA,
         ALPHA   = GL_ALPHA,
-        DEPTH   = GL_DEPTH_COMPONENT,
-#ifdef USE_PVRTC
-        COMPRESSED_RGB_PVRTC_4BPP = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG,
-        COMPRESSED_RGBA_PVRTC_4BPP = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG,
-        COMPRESSED_RGB_PVRTC_2BPP = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG,
-        COMPRESSED_RGBA_PVRTC_2BPP = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
-#endif
+        DEPTH   = GL_DEPTH_COMPONENT
     };
 
     /**
@@ -134,6 +128,9 @@ public:
     /**
      * Creates a texture from the given image resource.
      *
+     * Note that for textures that include mipmap data in the source data (such as most compressed textures),
+     * the generateMipmaps flags should NOT be set to true.
+     *
      * @param path The image resource path.
      * @param generateMipmaps true to auto-generate a full mipmap chain, false otherwise.
      * 
@@ -189,6 +186,11 @@ public:
      */
     bool isMipmapped() const;
 
+    /**
+     * Determines if this texture is a compressed teture.
+     */
+    bool isCompressed() const;
+
     /**
      * Returns the texture handle.
      *
@@ -213,16 +215,19 @@ private:
      */
     virtual ~Texture();
 
-#ifdef USE_PVRTC
     static Texture* createCompressedPVRTC(const char* path);
-#endif
-    
+    static Texture* createCompressedDDS(const char* path);
+
+    static GLubyte* readCompressedPVRTC(const char* path, FILE* file, GLsizei* width, GLsizei* height, GLenum* format, unsigned int* mipMapCount);
+    static GLubyte* readCompressedPVRTCLegacy(const char* path, FILE* file, GLsizei* width, GLsizei* height, GLenum* format, unsigned int* mipMapCount);
+
     std::string _path;
     TextureHandle _handle;
     unsigned int _width;
     unsigned int _height;
     bool _mipmapped;
     bool _cached;
+    bool _compressed;
 };
 
 }