Przeglądaj źródła

Adds game configuration support including support for resource aliases to assist in defining different assets per target platform.
Adds support for configurable window size in windows, as well as fullscreen support.
Compressed texture support for following formats: DXT1, DXT3, DXT5, PVRTC2, PVRTC4, ATC, ATCA, ATCI.
Removed width/height parameters from Game::run() since these are always retrieved from the Platform now.

Steve Grenier 13 lat temu
rodzic
commit
820398598f

+ 0 - 2
gameplay/src/Base.h

@@ -198,7 +198,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>
@@ -214,7 +213,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>

+ 46 - 2
gameplay/src/FileSystem.cpp

@@ -1,5 +1,6 @@
 #include "Base.h"
 #include "FileSystem.h"
+#include "Properties.h"
 
 #ifdef WIN32
     #include <windows.h>
@@ -60,6 +61,7 @@ void makepath(std::string path, int mode)
 #endif
 
 static std::string __resourcePath("./");
+static std::map<std::string, std::string> __aliases;
 
 FileSystem::FileSystem()
 {
@@ -79,6 +81,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.
@@ -150,9 +192,11 @@ bool FileSystem::listFiles(const char* dirPath, std::vector<std::string>& files)
 
 FILE* FileSystem::openFile(const char* path, const char* mode)
 {
+    assert(path != NULL);
+
     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;
@@ -16,7 +17,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)
 {
     assert(__gameInstance == NULL);
@@ -64,20 +65,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())
@@ -131,7 +127,9 @@ void Game::shutdown()
         SAFE_DELETE(_audioListener);
 
         RenderState::finalize();
-        
+
+        SAFE_DELETE(_properties);
+
         _state = UNINITIALIZED;
     }
 }
@@ -312,6 +310,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.

+ 0 - 1
gameplay/src/Node.cpp

@@ -587,7 +587,6 @@ void Node::transformChanged()
     _dirtyBits |= NODE_DIRTY_WORLD | NODE_DIRTY_BOUNDS;
 
     // Notify our children that their transform has also changed (since transforms are inherited).
-    Joint* rootJoint = NULL;
     Node* n = getFirstChild();
     while (n)
     {

+ 1 - 1
gameplay/src/PlatformAndroid.cpp

@@ -758,7 +758,7 @@ int Platform::enterMessagePump()
         {
             gameplay::initEGL();
             WARN_VARG("Platform::enterMessagePump() - width: %d  height: %d assetsPath: %s", __width, __height, __assetsPath.c_str());
-            _game->run(__width, __height);
+            _game->run();
             initializeGame = false;
         }
         

+ 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

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

+ 73 - 16
gameplay/src/PlatformWin32.cpp

@@ -9,8 +9,8 @@
 #include <windowsx.h>
 
 // Default to 720p
-#define WINDOW_WIDTH    1280
-#define WINDOW_HEIGHT   720
+static int __windowWidth = 1280;
+static int __windowHeight = 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 / (float)__windowWidth;
+    static const float ACCELEROMETER_Y_FACTOR = 90.0f / (float)__windowHeight;
 
     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;
     }
 
@@ -459,9 +459,34 @@ Platform* Platform::create(Game* game)
     __hinstance = ::GetModuleHandle(NULL);
 
     LPCTSTR windowClass = L"gameplay";
-    LPCTSTR windowName = L"";
+    std::wstring windowName;
+    bool fullscreen = false;
 
-    RECT rect = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT };
+    // Parse window game config properties
+    Properties* config = game->getConfig()->getNamespace("window", true);
+    if (config)
+    {
+        // Read window size
+        int width = config->getInt("width");
+        int height = config->getInt("height");
+        if (width > 0)
+            __windowWidth = width;
+        if (height > 0)
+            __windowHeight = height;
+
+        // Read window caption
+        const char* title = config->getString("title");
+        int len = MultiByteToWideChar(CP_ACP, 0, title, -1, 0, 0);
+        wchar_t* buf = new wchar_t[len];
+        MultiByteToWideChar(CP_ACP, 0, title, -1, buf, len);
+        windowName = buf;
+        SAFE_DELETE_ARRAY(buf);
+
+        // Was fullscreen mode specified?
+        fullscreen = config->getBool("fullscreen");
+    }
+
+    RECT rect = { 0, 0, __windowWidth, __windowHeight };
 
     // Register our window class.
     WNDCLASSEX wc;
@@ -482,21 +507,53 @@ Platform* Platform::create(Game* game)
         goto error;
     
     // 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, styleEx;
+    
+    if (fullscreen)
+    {
+        DEVMODE dm;
+        memset(&dm, 0, sizeof(DEVMODE));
+        dm.dmSize = sizeof(DEVMODE);
+        dm.dmPelsWidth = __windowWidth;
+        dm.dmPelsHeight = __windowHeight;
+        dm.dmBitsPerPel = 32;
+        dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
+
+        if (ChangeDisplaySettings(&dm, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
+        {
+            // Revert to windowed mode (unsupported resolution?)
+            fullscreen = false;
+            printError("Failed to switch to full-screen mode with resolution of %dx%d.", __windowWidth, __windowHeight);
+        }
+    }
+
+    if (fullscreen)
+    {
+        style = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+        styleEx = WS_EX_APPWINDOW;
+
+        // Hide cursor in fullscreen mode?
+        //ShowCursor(FALSE);
+    }
+    else
+    {
+        //style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+        style = WS_POPUP | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+        styleEx = 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) - __windowWidth) / 2;
+    const int screenY = (GetSystemMetrics(SM_CYSCREEN) - __windowHeight) / 2;
     ::SetWindowPos(__hwnd, __hwnd, screenX, screenY, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
 
     // Choose pixel format. 32-bit. RGBA.
@@ -571,7 +628,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;
@@ -604,12 +661,12 @@ void Platform::signalShutdown()
 
 unsigned int Platform::getDisplayWidth()
 {
-    return WINDOW_WIDTH;
+    return __windowWidth;
 }
 
 unsigned int Platform::getDisplayHeight()
 {
-    return WINDOW_HEIGHT;
+    return __windowHeight;
 }
     
 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

@@ -437,7 +437,7 @@ void Properties::rewind()
     _namespacesItr = _namespaces.end();
 }
 
-Properties* Properties::getNamespace(const char* id) const
+Properties* Properties::getNamespace(const char* id, bool searchNames) const
 {
     Properties* ret = NULL;
     std::vector<Properties*>::const_iterator it;
@@ -445,7 +445,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:
 
     /**
@@ -174,14 +177,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)
+    {
+        LOG_ERROR_VARG("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)
+    {
+    	LOG_ERROR_VARG("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
+        LOG_ERROR_VARG("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
+        LOG_ERROR_VARG("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)
-    {
-        LOG_ERROR_VARG("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);
     assert(read == size);
     if (read != size)
     {
         LOG_ERROR_VARG("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))
      {
         LOG_ERROR_VARG("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
     {
         LOG_ERROR_VARG("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);
     assert(read == header.dataSize);
     if (read != header.dataSize)
     {
         LOG_ERROR_VARG("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)
+            {
+                LOG_ERROR_VARG("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;
 };
 
 }