Browse Source

Unified the texture get/set data API between Direct3D9 & OpenGL.
Refactored OpenGL texture data restore on context recreation: now data is explicitly stored then restored, instead of relying on resource reload.

Lasse Öörni 14 years ago
parent
commit
a7a94a7af2

+ 0 - 2
Docs/Reference.dox

@@ -413,8 +413,6 @@ For details on how Direct3D9 and OpenGL rendering differs, see \ref APIDifferenc
 
 
 - On OpenGL vertex attribute bindings also depend on the currently set shaders. To ensure correct operation, first set the shaders, then the vertex buffers.
 - On OpenGL vertex attribute bindings also depend on the currently set shaders. To ensure correct operation, first set the shaders, then the vertex buffers.
 
 
-- OpenGL does not provide texture locking operations for direct pixel data access.
-
 - On Direct3D9 the depth stencil surface can be equal size or larger than the color render target. On OpenGL the sizes must always match. Furthermore, OpenGL can not use the system depth stencil buffer when rendering to a texture. To overcome these limitations, Graphics will create correctly sized depth stencil buffers on demand whenever a texture is set as a color render target, and a null depth stencil is specified.
 - On Direct3D9 the depth stencil surface can be equal size or larger than the color render target. On OpenGL the sizes must always match. Furthermore, OpenGL can not use the system depth stencil buffer when rendering to a texture. To overcome these limitations, Graphics will create correctly sized depth stencil buffers on demand whenever a texture is set as a color render target, and a null depth stencil is specified.
 
 
 - On Direct3D9 setting the first color render target resets the viewport dimensions. On OpenGL there is no such mechanism, but as sometimes (for example in shadow rendering) only a depth stencil buffer is set for rendering, Graphics will instead reset the viewport when the depth stencil buffer is set. To ensure correct operation on both APIs, first set the render targets, then the depth stencil buffer, and finally the viewport, if you need it to be less than the whole render target.
 - On Direct3D9 setting the first color render target resets the viewport dimensions. On OpenGL there is no such mechanism, but as sometimes (for example in shadow rendering) only a depth stencil buffer is set for rendering, Graphics will instead reset the viewport when the depth stencil buffer is set. To ensure correct operation on both APIs, first set the render targets, then the depth stencil buffer, and finally the viewport, if you need it to be less than the whole render target.

+ 2 - 0
Engine/Engine/APITemplates.h

@@ -641,6 +641,8 @@ template <class T> void RegisterTexture(asIScriptEngine* engine, const char* cla
     engine->RegisterObjectMethod(className, "uint get_levels() const", asMETHOD(T, GetLevels), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint get_levels() const", asMETHOD(T, GetLevels), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "int get_width() const", asMETHOD(T, GetWidth), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "int get_width() const", asMETHOD(T, GetWidth), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "int get_height() const", asMETHOD(T, GetHeight), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "int get_height() const", asMETHOD(T, GetHeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "int get_levelWidth(uint) const", asMETHOD(T, GetLevelWidth), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "int get_levelHeight(uint) const", asMETHOD(T, GetLevelHeight), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_filterMode(TextureFilterMode)", asMETHOD(T, SetFilterMode), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_filterMode(TextureFilterMode)", asMETHOD(T, SetFilterMode), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "TextureFilterMode get_filterMode() const", asMETHOD(T, GetFilterMode), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "TextureFilterMode get_filterMode() const", asMETHOD(T, GetFilterMode), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_addressMode(TextureCoordinate, TextureAddressMode)", asMETHOD(T, SetAddressMode), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_addressMode(TextureCoordinate, TextureAddressMode)", asMETHOD(T, SetAddressMode), asCALL_THISCALL);

+ 2 - 0
Engine/Engine/GraphicsAPI.cpp

@@ -167,6 +167,8 @@ static bool TextureCubeLoad(CubeMapFace face, Image* image, TextureCube* ptr)
 
 
 static void RegisterTextures(asIScriptEngine* engine)
 static void RegisterTextures(asIScriptEngine* engine)
 {
 {
+    /// \todo Expose getting/setting raw texture data
+    
     engine->RegisterEnum("TextureUsage");
     engine->RegisterEnum("TextureUsage");
     engine->RegisterEnumValue("TextureUsage", "TEXTURE_STATIC", TEXTURE_STATIC);
     engine->RegisterEnumValue("TextureUsage", "TEXTURE_STATIC", TEXTURE_STATIC);
     engine->RegisterEnumValue("TextureUsage", "TEXTURE_DYNAMIC", TEXTURE_DYNAMIC);
     engine->RegisterEnumValue("TextureUsage", "TEXTURE_DYNAMIC", TEXTURE_DYNAMIC);

+ 57 - 0
Engine/Graphics/Direct3D9/D3D9Texture.cpp

@@ -109,6 +109,20 @@ void Texture::ClearDataLost()
     dataLost_ = false;
     dataLost_ = false;
 }
 }
 
 
+int Texture::GetLevelWidth(unsigned level) const
+{
+    if (level > levels_)
+        return 0;
+    return Max(width_ >> level, 1);
+}
+
+int Texture::GetLevelHeight(unsigned level) const
+{
+    if (level > levels_)
+        return 0;
+    return Max(height_ >> level, 1);
+}
+
 TextureUsage Texture::GetUsage() const
 TextureUsage Texture::GetUsage() const
 {
 {
     if (usage_ & D3DUSAGE_DEPTHSTENCIL)
     if (usage_ & D3DUSAGE_DEPTHSTENCIL)
@@ -123,6 +137,49 @@ TextureUsage Texture::GetUsage() const
     return TEXTURE_STATIC;
     return TEXTURE_STATIC;
 }
 }
 
 
+unsigned Texture::GetDataSize(int width, int height) const
+{
+    if (format_ == D3DFMT_DXT1 || format_ == D3DFMT_DXT3 || format_ == D3DFMT_DXT5)
+        return GetRowDataSize(width) * ((height + 3) >> 2);
+    else
+        return GetRowDataSize(width) * height;
+}
+
+unsigned Texture::GetRowDataSize(int width) const
+{
+    switch (format_)
+    {
+    case D3DFMT_A8:
+    case D3DFMT_L8:
+        return width;
+    
+    case D3DFMT_D16:
+    case D3DFMT_R5G6B5:
+    case D3DFMT_A4R4G4B4:
+    case D3DFMT_A8L8:
+        return width * 2;
+
+    case D3DFMT_X8R8G8B8:
+        // Note: here source and destination data size differ
+        return width * 3;
+
+    case D3DFMT_A8R8G8B8:
+    case D3DFMT_R32F:
+    case D3DFMT_D24S8:
+        return width * 4;
+
+    case D3DFMT_DXT1:
+        return ((width + 3) >> 2) * 8;
+        
+    case D3DFMT_DXT3:
+    case D3DFMT_DXT5:
+        return ((width + 3) >> 2) * 16;
+        
+    default:
+        return 0;
+    }
+}
+
 unsigned Texture::GetDXTFormat(CompressedFormat format)
 unsigned Texture::GetDXTFormat(CompressedFormat format)
 {
 {
     switch (format)
     switch (format)

+ 14 - 15
Engine/Graphics/Direct3D9/D3D9Texture.h

@@ -33,15 +33,6 @@ static const int MAX_TEXTURE_QUALITY_LEVELS = 3;
 
 
 class XMLFile;
 class XMLFile;
 
 
-/// Locked texture rectangle structure
-struct LockedRect
-{
-    /// Texture data, format-specific
-    unsigned char* bits_;
-    /// Byte offset between rows
-    unsigned pitch_;
-};
-
 /// Base class for texture resources
 /// Base class for texture resources
 class Texture : public Resource, public GPUObject
 class Texture : public Resource, public GPUObject
 {
 {
@@ -51,7 +42,7 @@ public:
     /// Destruct
     /// Destruct
     virtual ~Texture();
     virtual ~Texture();
     
     
-    /// Set number of requested mipmap levels. Needs to be called before setting size
+    /// Set number of requested mip levels. Needs to be called before setting size
     void SetNumLevels(unsigned levels);
     void SetNumLevels(unsigned levels);
     /// Set filtering mode
     /// Set filtering mode
     void SetFilterMode(TextureFilterMode filter);
     void SetFilterMode(TextureFilterMode filter);
@@ -64,11 +55,9 @@ public:
     /// Clear default pool data lost flag
     /// Clear default pool data lost flag
     void ClearDataLost();
     void ClearDataLost();
     
     
-    /// Return texture usage type
-    TextureUsage GetUsage() const;
     /// Return texture format
     /// Return texture format
     unsigned GetFormat() const { return format_; }
     unsigned GetFormat() const { return format_; }
-    /// Return number of mipmap levels
+    /// Return number of mip levels
     unsigned GetLevels() const { return levels_; }
     unsigned GetLevels() const { return levels_; }
     /// Return width
     /// Return width
     int GetWidth() const { return width_; }
     int GetWidth() const { return width_; }
@@ -84,6 +73,16 @@ public:
     const Color& GetBorderColor() const { return borderColor_; }
     const Color& GetBorderColor() const { return borderColor_; }
     /// Return backup texture
     /// Return backup texture
     Texture* GetBackupTexture() const { return backupTexture_; }
     Texture* GetBackupTexture() const { return backupTexture_; }
+    /// Return mip level width, or 0 if level does not exist
+    int GetLevelWidth(unsigned level) const;
+    /// Return mip level width, or 0 if level does not exist
+    int GetLevelHeight(unsigned level) const;
+    /// Return texture usage type
+    TextureUsage GetUsage() const;
+    /// Return data size in bytes for a rectangular region
+    unsigned GetDataSize(int width, int height) const;
+    /// Return data size in bytes for a pixel or block row
+    unsigned GetRowDataSize(int width) const;
     /// Return API-specific DXT compressed texture format
     /// Return API-specific DXT compressed texture format
     static unsigned GetDXTFormat(CompressedFormat format);
     static unsigned GetDXTFormat(CompressedFormat format);
     
     
@@ -101,9 +100,9 @@ protected:
     unsigned pool_;
     unsigned pool_;
     /// Texture usage type
     /// Texture usage type
     unsigned usage_;
     unsigned usage_;
-    /// Current mipmap levels
+    /// Current mip levels
     unsigned levels_;
     unsigned levels_;
-    /// Requested mipmap levels
+    /// Requested mip levels
     unsigned requestedLevels_;
     unsigned requestedLevels_;
     /// Texture width
     /// Texture width
     int width_;
     int width_;

+ 176 - 78
Engine/Graphics/Direct3D9/D3D9Texture2D.cpp

@@ -37,7 +37,6 @@ OBJECTTYPESTATIC(Texture2D);
 
 
 Texture2D::Texture2D(Context* context) :
 Texture2D::Texture2D(Context* context) :
     Texture(context),
     Texture(context),
-    lockedLevel_(-1),
     followWindowSize_(false)
     followWindowSize_(false)
 {
 {
 }
 }
@@ -94,8 +93,6 @@ void Texture2D::Release()
         if (!graphics_)
         if (!graphics_)
             return;
             return;
         
         
-        Unlock();
-        
         for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
         for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
         {
         {
             if (graphics_->GetTexture(i) == this)
             if (graphics_->GetTexture(i) == this)
@@ -157,6 +154,115 @@ bool Texture2D::SetSize(int width, int height, unsigned format, TextureUsage usa
     return Create();
     return Create();
 }
 }
 
 
+bool Texture2D::SetData(unsigned level, int x, int y, int width, int height, const void* data)
+{
+    if (!object_)
+    {
+        LOGERROR("No texture created, can not set data");
+        return false;
+    }
+    
+    if (!data)
+    {
+        LOGERROR("Null source for setting data");
+        return false;
+    }
+    
+    if (level >= levels_)
+    {
+        LOGERROR("Illegal mip level for setting data");
+        return false;
+    }
+    
+    bool compressed = format_ == D3DFMT_DXT1 || format_ == D3DFMT_DXT3 || format_ == D3DFMT_DXT5;
+    if (compressed)
+    {
+        x &= ~3;
+        y &= ~3;
+    }
+
+    int levelWidth = GetLevelWidth(level);
+    int levelHeight = GetLevelHeight(level);
+    if (x < 0 || x + width > levelWidth || y < 0 || y + height > levelHeight || width <= 0 || height <= 0)
+    {
+        LOGERROR("Illegal dimensions for setting data");
+        return false;
+    }
+
+    D3DLOCKED_RECT d3dLockedRect;
+    RECT d3dRect;
+    d3dRect.left = x;
+    d3dRect.top = y;
+    d3dRect.right = x + width;
+    d3dRect.bottom = y + height;
+
+    DWORD flags = 0;
+    if (level == 0 && x == 0 && y == 0 && width == levelWidth && height == levelHeight && pool_ == D3DPOOL_DEFAULT)
+        flags |= D3DLOCK_DISCARD;
+
+    if (FAILED(((IDirect3DTexture9*)object_)->LockRect(level, &d3dLockedRect, &d3dRect, flags)))
+    {
+        LOGERROR("Could not lock texture");
+        return false;
+    }
+
+    if (compressed)
+    {
+        height = (height + 3) >> 2;
+        y >>= 2;
+    }
+
+    unsigned char* src = (unsigned char*)data;
+    unsigned rowSize = GetRowDataSize(width);
+    unsigned rowOffset = GetRowDataSize(x);
+    // GetRowDataSize() returns CPU-side (source) data size, so need to convert for X8R8G8B8
+    if (format_ == D3DFMT_X8R8G8B8)
+    {
+        rowSize = rowSize / 3 * 4;
+        rowOffset = rowOffset / 3 * 4;
+    }
+
+    // Perform conversion from RGB / RGBA as necessary
+    switch (format_)
+    {
+    default:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* dest = (unsigned char*)d3dLockedRect.pBits + (y + i) * d3dLockedRect.Pitch + rowOffset;
+            memcpy(dest, src, rowSize);
+            src += rowSize;
+        }
+        break;
+    
+    case D3DFMT_X8R8G8B8:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* dest = (unsigned char*)d3dLockedRect.pBits + (y + i) * d3dLockedRect.Pitch + rowOffset;
+            for (int j = 0; j < levelWidth; ++j)
+            {
+                *dest++  = src[2]; *dest++ = src[1]; *dest++ = src[0]; *dest++ = 255;
+                src += 3;
+           }
+        }
+        break;
+        
+    case D3DFMT_A8R8G8B8:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* dest = (unsigned char*)d3dLockedRect.pBits + (y + i) * d3dLockedRect.Pitch + rowOffset;
+            for (int j = 0; j < levelWidth; ++j)
+            {
+                *dest++  = src[2]; *dest++ = src[1]; *dest++ = src[0]; *dest++ = src[3];
+                src += 4;
+           }
+        }
+        break;
+    }
+    
+    ((IDirect3DTexture9*)object_)->UnlockRect(level);
+    return true;
+}
+
 bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
 bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
 {
 {
     if (!image)
     if (!image)
@@ -214,42 +320,9 @@ bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
         
         
         for (unsigned i = 0; i < manualLevels; ++i)
         for (unsigned i = 0; i < manualLevels; ++i)
         {
         {
-            LockedRect hwRect;
-            if (!Lock(i, 0, hwRect))
-                return false;
-            
+            SetData(i, 0, 0, levelWidth, levelHeight, levelData);
             memoryUse += levelWidth * levelHeight * components;
             memoryUse += levelWidth * levelHeight * components;
-            
-            for (int y = 0; y < levelHeight; ++y)
-            {
-                unsigned char* dest = hwRect.bits_ + hwRect.pitch_ * y;
-                unsigned char* src = levelData + components * levelWidth * y;
-                
-                switch (components)
-                {
-                case 1:
-                case 2:
-                    memcpy(dest, src, components * levelWidth);
-                    break;
-                
-                case 3:
-                    for (int x = 0; x < levelWidth; ++x)
-                    {
-                        *dest++ = src[2]; *dest++ = src[1]; *dest++ = src[0]; *dest++ = 255;
-                        src += 3;
-                    }
-                    break;
-                
-                case 4:
-                    for (int x = 0; x < levelWidth; ++x)
-                    {
-                        *dest++ = src[2]; *dest++ = src[1]; *dest++ = src[0]; *dest++ = src[3];
-                        src += 4;
-                    }
-                    break;
-                }
-            }
-            
+
             if (i < manualLevels - 1)
             if (i < manualLevels - 1)
             {
             {
                 image = image->GetNextLevel();
                 image = image->GetNextLevel();
@@ -257,8 +330,6 @@ bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
                 levelWidth = image->GetWidth();
                 levelWidth = image->GetWidth();
                 levelHeight = image->GetHeight();
                 levelHeight = image->GetHeight();
             }
             }
-            
-            Unlock();
         }
         }
     }
     }
     else
     else
@@ -282,20 +353,8 @@ bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
         for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
         for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
         {
         {
             CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
             CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
+            SetData(i, 0, 0, level.width_, level.height_, level.data_);
             memoryUse += level.rows_ * level.rowSize_;
             memoryUse += level.rows_ * level.rowSize_;
-            
-            LockedRect hwRect;
-            if (!Lock(i, 0, hwRect))
-                return false;
-            
-            for (unsigned j = 0; j < level.rows_; ++j)
-            {
-                unsigned char* dest = hwRect.bits_ + hwRect.pitch_ * j;
-                unsigned char* src = level.data_ + level.rowSize_ * j;
-                memcpy(dest, src, level.rowSize_);
-            }
-            
-            Unlock();
         }
         }
     }
     }
     
     
@@ -303,53 +362,92 @@ bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
     return true;
     return true;
 }
 }
 
 
-bool Texture2D::Lock(unsigned level, const IntRect* rect, LockedRect& lockedRect)
+bool Texture2D::GetData(unsigned level, void* dest) const
 {
 {
     if (!object_)
     if (!object_)
     {
     {
-        LOGERROR("No texture created, can not lock");
+        LOGERROR("No texture created, can not get data");
         return false;
         return false;
     }
     }
     
     
-    if (lockedLevel_ != -1)
+    if (!dest)
     {
     {
-        LOGERROR("Texture already locked");
+        LOGERROR("Null destination for getting data");
         return false;
         return false;
     }
     }
     
     
-    D3DLOCKED_RECT d3dLockedRect;
-    RECT d3dRect;
-    if (rect)
+    if (level >= levels_)
     {
     {
-        d3dRect.left = rect->left_;
-        d3dRect.top = rect->top_;
-        d3dRect.right = rect->right_;
-        d3dRect.bottom = rect->bottom_;
+        LOGERROR("Illegal mip level for getting data");
+        return false;
     }
     }
     
     
-    DWORD flags = 0;
-    if (!rect && pool_ == D3DPOOL_DEFAULT)
-        flags |= D3DLOCK_DISCARD;
+    bool compressed = format_ == D3DFMT_DXT1 || format_ == D3DFMT_DXT3 || format_ == D3DFMT_DXT5;
+    int levelWidth = GetLevelWidth(level);
+    int levelHeight = GetLevelHeight(level);
+
+    D3DLOCKED_RECT d3dLockedRect;
+    RECT d3dRect;
+    d3dRect.left = 0;
+    d3dRect.top = 0;
+    d3dRect.right = levelWidth;
+    d3dRect.bottom = levelHeight;
     
     
-    if (FAILED(((IDirect3DTexture9*)object_)->LockRect(level, &d3dLockedRect, rect ? &d3dRect : 0, flags)))
+    if (FAILED(((IDirect3DTexture9*)object_)->LockRect(level, &d3dLockedRect, &d3dRect, D3DLOCK_READONLY)))
     {
     {
         LOGERROR("Could not lock texture");
         LOGERROR("Could not lock texture");
         return false;
         return false;
     }
     }
     
     
-    lockedRect.bits_ = (unsigned char*)d3dLockedRect.pBits;
-    lockedRect.pitch_ = d3dLockedRect.Pitch;
-    lockedLevel_ = level;
-    return true;
-}
+    int height = levelHeight;
+    if (compressed)
+        height = (height + 3) >> 2;
 
 
-void Texture2D::Unlock()
-{
-    if (lockedLevel_ != -1)
+    unsigned char* destPtr = (unsigned char*)dest;
+    unsigned rowSize = GetRowDataSize(levelWidth);
+    // GetRowDataSize() returns CPU-side (destination) data size, so need to convert for X8R8G8B8
+    if (format_ == D3DFMT_X8R8G8B8)
+        rowSize = rowSize / 3 * 4;
+
+    // Perform conversion to RGB / RGBA as necessary
+    switch (format_)
     {
     {
-        ((IDirect3DTexture9*)object_)->UnlockRect(lockedLevel_);
-        lockedLevel_ = -1;
+    default:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* src = (unsigned char*)d3dLockedRect.pBits + i * d3dLockedRect.Pitch;
+            memcpy(destPtr, src, rowSize);
+            destPtr += rowSize;
+        }
+        break;
+    
+    case D3DFMT_X8R8G8B8:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* src = (unsigned char*)d3dLockedRect.pBits + i * d3dLockedRect.Pitch;
+            for (int j = 0; j < levelWidth; ++j)
+            {
+                destPtr[2] = *src++; destPtr[1] = *src++; destPtr[0] = *src++; ++src;
+                destPtr += 3;
+           }
+        }
+        break;
+        
+    case D3DFMT_A8R8G8B8:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* src = (unsigned char*)d3dLockedRect.pBits + i * d3dLockedRect.Pitch;
+            for (int j = 0; j < levelWidth; ++j)
+            {
+                destPtr[2] = *src++; destPtr[1] = *src++; destPtr[0] = *src++; destPtr[3] = *src++;
+                destPtr += 4;
+           }
+        }
+        break;
     }
     }
+    
+    ((IDirect3DTexture9*)object_)->UnlockRect(level);
+    return true;
 }
 }
 
 
 bool Texture2D::Create()
 bool Texture2D::Create()

+ 4 - 6
Engine/Graphics/Direct3D9/D3D9Texture2D.h

@@ -53,13 +53,13 @@ public:
     
     
     /// Set size, format and usage. Zero size will follow application window size. Return true if successful
     /// Set size, format and usage. Zero size will follow application window size. Return true if successful
     bool SetSize(int width, int height, unsigned format, TextureUsage usage = TEXTURE_STATIC);
     bool SetSize(int width, int height, unsigned format, TextureUsage usage = TEXTURE_STATIC);
+    /// Set data either partially or fully on a mip level. Return true if successful
+    bool SetData(unsigned level, int x, int y, int width, int height, const void* data);
     /// Load from an image. Return true if successful
     /// Load from an image. Return true if successful
     bool Load(SharedPtr<Image> image, bool useAlpha = false);
     bool Load(SharedPtr<Image> image, bool useAlpha = false);
-    /// Lock a rectangular area from a mipmap level. A null rect locks the entire level. Return true if successful
-    bool Lock(unsigned level, const IntRect* rect, LockedRect& lockedRect);
-    /// Unlock texture
-    void Unlock();
     
     
+    /// Get data from a mip level. The destination buffer must be big enough. Return true if successful
+    bool GetData(unsigned level, void* dest) const;
     /// Return render surface
     /// Return render surface
     RenderSurface* GetRenderSurface() const { return renderSurface_; }
     RenderSurface* GetRenderSurface() const { return renderSurface_; }
     
     
@@ -69,8 +69,6 @@ private:
     
     
     /// Render surface
     /// Render surface
     SharedPtr<RenderSurface> renderSurface_;
     SharedPtr<RenderSurface> renderSurface_;
-    /// Currently locked mipmap level
-    int lockedLevel_;
     /// Follow window size flag
     /// Follow window size flag
     bool followWindowSize_;
     bool followWindowSize_;
 };
 };

+ 179 - 79
Engine/Graphics/Direct3D9/D3D9TextureCube.cpp

@@ -81,8 +81,6 @@ void TextureCube::Release()
         if (!graphics_)
         if (!graphics_)
             return;
             return;
         
         
-        Unlock();
-        
         for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
         for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
         {
         {
             if (graphics_->GetTexture(i) == this)
             if (graphics_->GetTexture(i) == this)
@@ -151,6 +149,115 @@ bool TextureCube::SetSize(int size, unsigned format, TextureUsage usage)
     return Create();
     return Create();
 }
 }
 
 
+bool TextureCube::SetData(CubeMapFace face, unsigned level, int x, int y, int width, int height, const void* data)
+{
+    if (!object_)
+    {
+        LOGERROR("No texture created, can not set data");
+        return false;
+    }
+    
+    if (!data)
+    {
+        LOGERROR("Null source for setting data");
+        return false;
+    }
+    
+    if (level >= levels_)
+    {
+        LOGERROR("Illegal mip level for setting data");
+        return false;
+    }
+    
+    bool compressed = format_ == D3DFMT_DXT1 || format_ == D3DFMT_DXT3 || format_ == D3DFMT_DXT5;
+    if (compressed)
+    {
+        x &= ~3;
+        y &= ~3;
+    }
+    
+    int levelWidth = GetLevelWidth(level);
+    int levelHeight = GetLevelHeight(level);
+    if (x < 0 || x + width > levelWidth || y < 0 || y + height > levelHeight || width <= 0 || height <= 0)
+    {
+        LOGERROR("Illegal dimensions for setting data");
+        return false;
+    }
+    
+    D3DLOCKED_RECT d3dLockedRect;
+    RECT d3dRect;
+    d3dRect.left = x;
+    d3dRect.top = y;
+    d3dRect.right = x + width;
+    d3dRect.bottom = y + height;
+    
+    DWORD flags = 0;
+    if (level == 0 && x == 0 && y == 0 && width == levelWidth && height == levelHeight && pool_ == D3DPOOL_DEFAULT)
+        flags |= D3DLOCK_DISCARD;
+
+    if (FAILED(((IDirect3DCubeTexture9*)object_)->LockRect((D3DCUBEMAP_FACES)face, level, &d3dLockedRect, &d3dRect, flags)))
+    {
+        LOGERROR("Could not lock texture");
+        return false;
+    }
+    
+    if (compressed)
+    {
+        height = (height + 3) >> 2;
+        y >>= 2;
+    }
+    
+    unsigned char* src = (unsigned char*)data;
+    unsigned rowSize = GetRowDataSize(width);
+    unsigned rowOffset = GetRowDataSize(x);
+    // GetRowDataSize() returns CPU-side (source) data size, so need to convert for X8R8G8B8
+    if (format_ == D3DFMT_X8R8G8B8)
+    {
+        rowSize = rowSize / 3 * 4;
+        rowOffset = rowOffset / 3 * 4;
+    }
+
+    // Perform conversion from RGB / RGBA as necessary
+    switch (format_)
+    {
+    default:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* dest = (unsigned char*)d3dLockedRect.pBits + (y + i) * d3dLockedRect.Pitch + rowOffset;
+            memcpy(dest, src, rowSize);
+            src += rowSize;
+        }
+        break;
+    
+    case D3DFMT_X8R8G8B8:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* dest = (unsigned char*)d3dLockedRect.pBits + (y + i) * d3dLockedRect.Pitch + rowOffset;
+            for (int j = 0; j < width; ++j)
+            {
+                *dest++  = src[2]; *dest++ = src[1]; *dest++ = src[0]; *dest++ = 255;
+                src += 3;
+           }
+        }
+        break;
+        
+    case D3DFMT_A8R8G8B8:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* dest = (unsigned char*)d3dLockedRect.pBits + (y + i) * d3dLockedRect.Pitch + rowOffset;
+            for (int j = 0; j < width; ++j)
+            {
+                *dest++  = src[2]; *dest++ = src[1]; *dest++ = src[0]; *dest++ = src[3];
+                src += 4;
+           }
+        }
+        break;
+    }
+    
+    ((IDirect3DCubeTexture9*)object_)->UnlockRect((D3DCUBEMAP_FACES)face, level);
+    return true;
+}
+
 bool TextureCube::Load(Deserializer& source)
 bool TextureCube::Load(Deserializer& source)
 {
 {
     PROFILE(LoadTextureCube);
     PROFILE(LoadTextureCube);
@@ -284,42 +391,9 @@ bool TextureCube::Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha)
         
         
         for (unsigned i = 0; i < manualLevels; ++i)
         for (unsigned i = 0; i < manualLevels; ++i)
         {
         {
-            LockedRect hwRect;
-            if (!Lock(face, i, 0, hwRect))
-                return false;
-            
+            SetData(face, i, 0, 0, levelWidth, levelHeight, levelData);
             memoryUse += levelWidth * levelHeight * components;
             memoryUse += levelWidth * levelHeight * components;
-            
-            for (int y = 0; y < levelHeight; ++y)
-            {
-                unsigned char* dest = hwRect.bits_ + hwRect.pitch_ * y;
-                unsigned char* src = levelData + components * levelWidth * y;
-                
-                switch (components)
-                {
-                case 1:
-                case 2:
-                    memcpy(dest, src, components * levelWidth);
-                    break;
-                    
-                case 3:
-                    for (int x = 0; x < levelWidth; ++x)
-                    {
-                        *dest++ = src[2]; *dest++ = src[1]; *dest++ = src[0]; *dest++ = 255;
-                        src += 3;
-                    }
-                    break;
-                
-                case 4:
-                    for (int x = 0; x < levelWidth; ++x)
-                    {
-                        *dest++ = src[2]; *dest++ = src[1]; *dest++ = src[0]; *dest++ = src[3];
-                        src += 4;
-                    }
-                    break;
-                }
-            }
-            
+
             if (i < manualLevels - 1)
             if (i < manualLevels - 1)
             {
             {
                 image = image->GetNextLevel();
                 image = image->GetNextLevel();
@@ -327,8 +401,6 @@ bool TextureCube::Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha)
                 levelWidth = image->GetWidth();
                 levelWidth = image->GetWidth();
                 levelHeight = image->GetHeight();
                 levelHeight = image->GetHeight();
             }
             }
-            
-            Unlock();
         }
         }
     }
     }
     else
     else
@@ -375,20 +447,8 @@ bool TextureCube::Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha)
         for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
         for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
         {
         {
             CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
             CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
+            SetData(face, i, 0, 0, level.width_, level.height_, level.data_);
             memoryUse += level.rows_ * level.rowSize_;
             memoryUse += level.rows_ * level.rowSize_;
-            
-            LockedRect hwRect;
-            if (!Lock(face, i, 0, hwRect))
-                return false;
-            
-            for (unsigned j = 0; j < level.rows_; ++j)
-            {
-                unsigned char* dest = hwRect.bits_ + hwRect.pitch_ * j;
-                unsigned char* src = level.data_ + level.rowSize_ * j;
-                memcpy(dest, src, level.rowSize_);
-            }
-            
-            Unlock();
         }
         }
     }
     }
     
     
@@ -397,56 +457,96 @@ bool TextureCube::Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha)
     for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
     for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
         totalMemoryUse += faceMemoryUse_[i];
         totalMemoryUse += faceMemoryUse_[i];
     SetMemoryUse(totalMemoryUse);
     SetMemoryUse(totalMemoryUse);
+    
     return true;
     return true;
 }
 }
 
 
-bool TextureCube::Lock(CubeMapFace face, unsigned level, const IntRect* rect, LockedRect& lockedRect)
+bool TextureCube::GetData(CubeMapFace face, unsigned level, void* dest) const
 {
 {
     if (!object_)
     if (!object_)
     {
     {
-        LOGERROR("No texture created, can not lock");
+        LOGERROR("No texture created, can not get data");
         return false;
         return false;
     }
     }
-    if (lockedLevel_ != -1)
+    
+    if (!dest)
     {
     {
-        LOGERROR("Texture already locked");
+        LOGERROR("Null destination for getting data");
         return false;
         return false;
     }
     }
     
     
-    D3DLOCKED_RECT d3dLockedRect;
-    RECT d3dRect;
-    if (rect)
+    if (level >= levels_)
     {
     {
-        d3dRect.left = rect->left_;
-        d3dRect.top = rect->top_;
-        d3dRect.right = rect->right_;
-        d3dRect.bottom = rect->bottom_;
+        LOGERROR("Illegal mip level for getting data");
+        return false;
     }
     }
     
     
-    DWORD flags = 0;
-    if (!rect && pool_ == D3DPOOL_DEFAULT)
-        flags |= D3DLOCK_DISCARD;
+    bool compressed = format_ == D3DFMT_DXT1 || format_ == D3DFMT_DXT3 || format_ == D3DFMT_DXT5;
+    int levelWidth = GetLevelWidth(level);
+    int levelHeight = GetLevelHeight(level);
+
+    D3DLOCKED_RECT d3dLockedRect;
+    RECT d3dRect;
+    d3dRect.left = 0;
+    d3dRect.top = 0;
+    d3dRect.right = levelWidth;
+    d3dRect.bottom = levelHeight;
     
     
-    if (FAILED(((IDirect3DCubeTexture9*)object_)->LockRect((D3DCUBEMAP_FACES)face, level, &d3dLockedRect, rect ? &d3dRect : 0, flags)))
+    if (FAILED(((IDirect3DCubeTexture9*)object_)->LockRect((D3DCUBEMAP_FACES)face, level, &d3dLockedRect, &d3dRect, D3DLOCK_READONLY)))
     {
     {
         LOGERROR("Could not lock texture");
         LOGERROR("Could not lock texture");
         return false;
         return false;
     }
     }
-    
-    lockedRect.bits_ = (unsigned char*)d3dLockedRect.pBits;
-    lockedRect.pitch_ = d3dLockedRect.Pitch;
-    lockedLevel_ = level;
-    lockedFace_ = face;
-    return true;
-}
 
 
-void TextureCube::Unlock()
-{
-    if (lockedLevel_ != -1)
+    int height = levelHeight;
+    if (compressed)
+        height = (height + 3) >> 2;
+
+    unsigned char* destPtr = (unsigned char*)dest;
+    unsigned rowSize = GetRowDataSize(levelWidth);
+    // GetRowDataSize() returns CPU-side (destination) data size, so need to convert for X8R8G8B8
+    if (format_ == D3DFMT_X8R8G8B8)
+        rowSize = rowSize / 3 * 4;
+
+    // Perform conversion to RGB / RGBA as necessary
+    switch (format_)
     {
     {
-        ((IDirect3DCubeTexture9*)object_)->UnlockRect((D3DCUBEMAP_FACES)lockedFace_, lockedLevel_);
-        lockedLevel_ = -1;
+    default:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* src = (unsigned char*)d3dLockedRect.pBits + i * d3dLockedRect.Pitch;
+            memcpy(destPtr, src, rowSize);
+            destPtr += rowSize;
+        }
+        break;
+
+    case D3DFMT_X8R8G8B8:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* src = (unsigned char*)d3dLockedRect.pBits + i * d3dLockedRect.Pitch;
+            for (int j = 0; j < levelWidth; ++j)
+            {
+                destPtr[2] = *src++; destPtr[1] = *src++; destPtr[0] = *src++; ++src;
+                destPtr += 3;
+           }
+        }
+        break;
+        
+    case D3DFMT_A8R8G8B8:
+        for (int i = 0; i < height; ++i)
+        {
+            unsigned char* src = (unsigned char*)d3dLockedRect.pBits + i * d3dLockedRect.Pitch;
+            for (int j = 0; j < levelWidth; ++j)
+            {
+                destPtr[2] = *src++; destPtr[1] = *src++; destPtr[0] = *src++; destPtr[3] = *src++;
+                destPtr += 4;
+           }
+        }
+        break;
     }
     }
+    
+    ((IDirect3DCubeTexture9*)object_)->UnlockRect((D3DCUBEMAP_FACES)face, level);
+    return true;
 }
 }
 
 
 bool TextureCube::Create()
 bool TextureCube::Create()

+ 5 - 5
Engine/Graphics/Direct3D9/D3D9TextureCube.h

@@ -54,15 +54,15 @@ public:
     
     
     /// Set size, format and usage. Return true if successful
     /// Set size, format and usage. Return true if successful
     bool SetSize(int size, unsigned format, TextureUsage usage = TEXTURE_STATIC);
     bool SetSize(int size, unsigned format, TextureUsage usage = TEXTURE_STATIC);
+    /// Set data either partially or fully on a face's mip level. Return true if successful
+    bool SetData(CubeMapFace face, unsigned level, int x, int y, int width, int height, const void* data);
     /// Load one face from a stream. Return true if successful
     /// Load one face from a stream. Return true if successful
     bool Load(CubeMapFace face, Deserializer& source);
     bool Load(CubeMapFace face, Deserializer& source);
     /// Load one face from an image. Return true if successful
     /// Load one face from an image. Return true if successful
     bool Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha = false);
     bool Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha = false);
-    /// Lock a rectangular area from one face and mipmap level. A null rect locks the entire face. Return true if successful
-    bool Lock(CubeMapFace face, unsigned level, const IntRect* rect, LockedRect& lockedRect);
-    /// Unlock texture
-    void Unlock();
     
     
+    /// Get data from a face's mip level. The destination buffer must be big enough. Return true if successful
+    bool GetData(CubeMapFace face, unsigned level, void* dest) const;
     /// Return render surface for one face
     /// Return render surface for one face
     RenderSurface* GetRenderSurface(CubeMapFace face) const { return renderSurfaces_[face]; }
     RenderSurface* GetRenderSurface(CubeMapFace face) const { return renderSurfaces_[face]; }
     
     
@@ -74,7 +74,7 @@ private:
     SharedPtr<RenderSurface> renderSurfaces_[MAX_CUBEMAP_FACES];
     SharedPtr<RenderSurface> renderSurfaces_[MAX_CUBEMAP_FACES];
     /// Memory use per face
     /// Memory use per face
     unsigned faceMemoryUse_[MAX_CUBEMAP_FACES];
     unsigned faceMemoryUse_[MAX_CUBEMAP_FACES];
-    /// Currently locked mipmap level
+    /// Currently locked mip level
     int lockedLevel_;
     int lockedLevel_;
     /// Currently locked face
     /// Currently locked face
     CubeMapFace lockedFace_;
     CubeMapFace lockedFace_;

+ 53 - 2
Engine/Graphics/OpenGL/OGLTexture.cpp

@@ -69,7 +69,6 @@ Texture::Texture(Context* context) :
     depthBits_(0),
     depthBits_(0),
     width_(0),
     width_(0),
     height_(0),
     height_(0),
-    dataLost_(false),
     dynamic_(false),
     dynamic_(false),
     shadowCompare_(false),
     shadowCompare_(false),
     parametersDirty_(true),
     parametersDirty_(true),
@@ -126,7 +125,6 @@ void Texture::SetParametersDirty()
 
 
 void Texture::ClearDataLost()
 void Texture::ClearDataLost()
 {
 {
-    dataLost_ = false;
 }
 }
 
 
 void Texture::UpdateParameters()
 void Texture::UpdateParameters()
@@ -186,6 +184,20 @@ void Texture::UpdateParameters()
     parametersDirty_ = false;
     parametersDirty_ = false;
 }
 }
 
 
+int Texture::GetLevelWidth(unsigned level) const
+{
+    if (level > levels_)
+        return 0;
+    return Max(width_ >> level, 1);
+}
+
+int Texture::GetLevelHeight(unsigned level) const
+{
+    if (level > levels_)
+        return 0;
+    return Max(height_ >> level, 1);
+}
+
 TextureUsage Texture::GetUsage() const
 TextureUsage Texture::GetUsage() const
 {
 {
     /// \todo Check for rendertarget / depthstencil mode
     /// \todo Check for rendertarget / depthstencil mode
@@ -196,6 +208,45 @@ TextureUsage Texture::GetUsage() const
     return TEXTURE_STATIC;
     return TEXTURE_STATIC;
 }
 }
 
 
+unsigned Texture::GetDataSize(int width, int height) const
+{
+    if (format_ == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || format_ == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
+        format_ == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
+        return GetRowDataSize(width) * ((height + 3) >> 2);
+    else
+        return GetRowDataSize(width) * height;
+}
+
+unsigned Texture::GetRowDataSize(int width) const
+{
+    switch (format_)
+    {
+    case GL_ALPHA:
+    case GL_LUMINANCE:
+        return width;
+
+    case GL_LUMINANCE_ALPHA:
+        return width * 2;
+
+    case GL_RGB:
+        return width * 3;
+
+    case GL_RGBA:
+    case GL_DEPTH24_STENCIL8_EXT:
+        return width * 4;
+
+    case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+        return ((width + 3) >> 2) * 8;
+
+    case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+    case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+        return ((width + 3) >> 2) * 16;
+        
+    default:
+        return 0;
+    }
+}
+
 unsigned Texture::GetDXTFormat(CompressedFormat format)
 unsigned Texture::GetDXTFormat(CompressedFormat format)
 {
 {
     switch (format)
     switch (format)

+ 17 - 8
Engine/Graphics/OpenGL/OGLTexture.h

@@ -28,6 +28,7 @@
 #include "Image.h"
 #include "Image.h"
 #include "GraphicsDefs.h"
 #include "GraphicsDefs.h"
 #include "Resource.h"
 #include "Resource.h"
+#include "SharedArrayPtr.h"
 
 
 static const int MAX_TEXTURE_QUALITY_LEVELS = 3;
 static const int MAX_TEXTURE_QUALITY_LEVELS = 3;
 
 
@@ -56,13 +57,11 @@ public:
     void SetBackupTexture(Texture* texture);
     void SetBackupTexture(Texture* texture);
     /// Dirty the parameters
     /// Dirty the parameters
     void SetParametersDirty();
     void SetParametersDirty();
-    /// Clear data lost flag
+    /// Clear data lost flag. No-op on OpenGL.
     void ClearDataLost();
     void ClearDataLost();
     /// Update changed parameters to OpenGL. Called by Graphics when binding the texture
     /// Update changed parameters to OpenGL. Called by Graphics when binding the texture
     void UpdateParameters();
     void UpdateParameters();
-    
-    /// Return texture usage type
-    TextureUsage GetUsage() const;
+
     /// Return texture's OpenGL target
     /// Return texture's OpenGL target
     unsigned GetTarget() const { return target_; }
     unsigned GetTarget() const { return target_; }
     /// Return texture format
     /// Return texture format
@@ -75,8 +74,8 @@ public:
     int GetWidth() const { return width_; }
     int GetWidth() const { return width_; }
     /// Return height
     /// Return height
     int GetHeight() const { return height_; }
     int GetHeight() const { return height_; }
-    /// Return whether data is lost
-    bool IsDataLost() const { return dataLost_; }
+    /// Return whether data is lost. Always false on OpenGL
+    bool IsDataLost() const { return false; }
     /// Return whether parameters are dirty
     /// Return whether parameters are dirty
     bool GetParametersDirty() const { return parametersDirty_; }
     bool GetParametersDirty() const { return parametersDirty_; }
     /// Return filtering mode
     /// Return filtering mode
@@ -89,6 +88,16 @@ public:
     const Color& GetBorderColor() const { return borderColor_; }
     const Color& GetBorderColor() const { return borderColor_; }
     /// Return backup texture
     /// Return backup texture
     Texture* GetBackupTexture() const { return backupTexture_; }
     Texture* GetBackupTexture() const { return backupTexture_; }
+    /// Return mip level width, or 0 if level does not exist
+    int GetLevelWidth(unsigned level) const;
+    /// Return mip level width, or 0 if level does not exist
+    int GetLevelHeight(unsigned level) const;
+    /// Return texture usage type
+    TextureUsage GetUsage() const;
+    /// Return data size in bytes for a rectangular region
+    unsigned GetDataSize(int width, int height) const;
+    /// Return data size in bytes for a pixel or block row
+    unsigned GetRowDataSize(int width) const;
     /// Return API-specific compressed texture format
     /// Return API-specific compressed texture format
     static unsigned GetDXTFormat(CompressedFormat format);
     static unsigned GetDXTFormat(CompressedFormat format);
     /// Return the non-internal texture format corresponding to an OpenGL internal format
     /// Return the non-internal texture format corresponding to an OpenGL internal format
@@ -118,8 +127,6 @@ protected:
     int width_;
     int width_;
     /// Texture height
     /// Texture height
     int height_;
     int height_;
-    /// Data lost flag
-    bool dataLost_;
     /// Dynamic flag
     /// Dynamic flag
     bool dynamic_;
     bool dynamic_;
     /// Shadow compare mode, OpenGL only
     /// Shadow compare mode, OpenGL only
@@ -136,4 +143,6 @@ protected:
     Color borderColor_;
     Color borderColor_;
     /// Backup texture
     /// Backup texture
     SharedPtr<Texture> backupTexture_;
     SharedPtr<Texture> backupTexture_;
+    /// Save data per mip level
+    Vector<SharedArrayPtr<unsigned char> > savedLevels_;
 };
 };

+ 129 - 23
Engine/Graphics/OpenGL/OGLTexture2D.cpp

@@ -72,19 +72,45 @@ bool Texture2D::Load(Deserializer& source)
     return Load(image);
     return Load(image);
 }
 }
 
 
+void Texture2D::OnDeviceLost()
+{
+    savedLevels_.Clear();
+    
+    // Check if save data is supported, in that case save data of each mip level
+    if (GetDataSize(width_, height_))
+    {
+        for (unsigned i = 0; i < levels_; ++i)
+        {
+            int levelWidth = GetLevelWidth(i);
+            int levelHeight = GetLevelHeight(i);
+            SharedArrayPtr<unsigned char> savedLevel(new unsigned char[GetDataSize(levelWidth, levelHeight)]);
+            GetData(i, savedLevel.GetPtr());
+            savedLevels_.Push(savedLevel);
+        }
+    }
+    
+    Release();
+}
+
 void Texture2D::OnDeviceReset()
 void Texture2D::OnDeviceReset()
 {
 {
     if (followWindowSize_)
     if (followWindowSize_)
         Create();
         Create();
     else if (!object_)
     else if (!object_)
     {
     {
-        // Reload from the original file if possible
-        ResourceCache* cache = GetSubsystem<ResourceCache>();
-        const String& name = GetName();
-        if (cache && !name.Empty() && cache->Exists(name))
-            cache->ReloadResource(this);
-        else
-            Create();
+        Create();
+        
+        // Restore texture from save data if it exists
+        if (savedLevels_.Size())
+        {
+            for (unsigned i = 0; i < savedLevels_.Size(); ++i)
+            {
+                int levelWidth = GetLevelWidth(i);
+                int levelHeight = GetLevelHeight(i);
+                SetData(i, 0, 0, levelWidth, levelHeight, savedLevels_[i].GetPtr());
+            }
+            savedLevels_.Clear();
+        }
     }
     }
 }
 }
 
 
@@ -106,7 +132,6 @@ void Texture2D::Release()
         
         
         glDeleteTextures(1, &object_);
         glDeleteTextures(1, &object_);
         object_ = 0;
         object_ = 0;
-        dataLost_ = true;
     }
     }
     else
     else
     {
     {
@@ -153,6 +178,65 @@ bool Texture2D::SetSize(int width, int height, unsigned format, TextureUsage usa
     return Create();
     return Create();
 }
 }
 
 
+bool Texture2D::SetData(unsigned level, int x, int y, int width, int height, const void* data)
+{
+    if (!object_ || !graphics_)
+    {
+        LOGERROR("No texture created, can not set data");
+        return false;
+    }
+    
+    if (!data)
+    {
+        LOGERROR("Null source for setting data");
+        return false;
+    }
+    
+    if (level >= levels_)
+    {
+        LOGERROR("Illegal mip level for setting data");
+        return false;
+    }
+    
+    bool compressed = format_ == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || format_ == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
+        format_ == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+    if (compressed)
+    {
+        x &= ~3;
+        y &= ~3;
+    }
+    
+    int levelWidth = GetLevelWidth(level);
+    int levelHeight = GetLevelHeight(level);
+    if (x < 0 || x + width > levelWidth || y < 0 || y + height > levelHeight || width <= 0 || height <= 0)
+    {
+        LOGERROR("Illegal dimensions for setting data");
+        return false;
+    }
+    
+    bool wholeLevel = x == 0 && y == 0 && width == levelWidth && height == levelHeight;
+    
+    graphics_->SetTextureForUpdate(this);
+    
+    if (!compressed)
+    {
+        if (wholeLevel)
+            glTexImage2D(target_, level, format_, width, height, 0, GetExternalFormat(format_), GetDataType(format_), data);
+        else
+            glTexSubImage2D(target_, level, x, y, width, height, GetExternalFormat(format_), GetDataType(format_), data);
+    }
+    else
+    {
+        if (wholeLevel)
+            glCompressedTexImage2D(target_, level, format_, width, height, 0, GetDataSize(width, height), data);
+        else
+            glCompressedTexSubImage2D(target_, level, x, y, width, height, format_, GetDataSize(width, height), data);
+    }
+    
+    graphics_->SetTexture(0, 0);
+    return true;
+}
+
 bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
 bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
 {
 {
     if (!image)
     if (!image)
@@ -208,12 +292,10 @@ bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
         if (!object_)
         if (!object_)
             return false;
             return false;
         
         
-        graphics_->SetTextureForUpdate(this);
-        
         for (unsigned i = 0; i < levels_; ++i)
         for (unsigned i = 0; i < levels_; ++i)
         {
         {
-            glTexImage2D(target_, i, format_, levelWidth, levelHeight, 0, GetExternalFormat(format_), GL_UNSIGNED_BYTE,
-                levelData);
+            SetData(i, 0, 0, levelWidth, levelHeight, levelData);
+            memoryUse += levelWidth * levelHeight * components;
             
             
             if (i < levels_ - 1)
             if (i < levels_ - 1)
             {
             {
@@ -222,11 +304,7 @@ bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
                 levelWidth = image->GetWidth();
                 levelWidth = image->GetWidth();
                 levelHeight = image->GetHeight();
                 levelHeight = image->GetHeight();
             }
             }
-            
-            memoryUse += levelWidth * levelHeight * components;
         }
         }
-        
-        graphics_->SetTexture(0, 0);
     }
     }
     else
     else
     {
     {
@@ -246,21 +324,49 @@ bool Texture2D::Load(SharedPtr<Image> image, bool useAlpha)
         SetNumLevels(Max((int)(levels - mipsToSkip), 1));
         SetNumLevels(Max((int)(levels - mipsToSkip), 1));
         SetSize(width, height, format);
         SetSize(width, height, format);
         
         
-        graphics_->SetTextureForUpdate(this);
-        
         for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
         for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
         {
         {
             CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
             CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
-            glCompressedTexImage2D(target_, i, format_, level.width_, level.height_, 0, level.dataSize_, level.data_);
-            
+            SetData(i, 0, 0, level.width_, level.height_, level.data_);
             memoryUse += level.rows_ * level.rowSize_;
             memoryUse += level.rows_ * level.rowSize_;
         }
         }
-        
-        graphics_->SetTexture(0, 0);
     }
     }
     
     
     SetMemoryUse(memoryUse);
     SetMemoryUse(memoryUse);
-    ClearDataLost();
+    return true;
+}
+
+bool Texture2D::GetData(unsigned level, void* dest) const
+{
+    if (!object_ || !graphics_)
+    {
+        LOGERROR("No texture created, can not get data");
+        return false;
+    }
+    
+    if (!dest)
+    {
+        LOGERROR("Null destination for getting data");
+        return false;
+    }
+    
+    if (level >= levels_)
+    {
+        LOGERROR("Illegal mip level for getting data");
+        return false;
+    }
+    
+    bool compressed = format_ == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || format_ == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
+        format_ == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+    
+    graphics_->SetTextureForUpdate(const_cast<Texture2D*>(this));
+    
+    if (!compressed)
+        glGetTexImage(target_, level, GetExternalFormat(format_), GetDataType(format_), dest);
+    else
+        glGetCompressedTexImage(target_, level, dest);
+    
+    graphics_->SetTexture(0, 0);
     return true;
     return true;
 }
 }
 
 

+ 7 - 1
Engine/Graphics/OpenGL/OGLTexture2D.h

@@ -43,16 +43,22 @@ public:
     
     
     /// Load resource. Return true if successful
     /// Load resource. Return true if successful
     virtual bool Load(Deserializer& source);
     virtual bool Load(Deserializer& source);
-    /// Recreate the texture from the original file if necessary and possible
+    /// Save data and release the texture
+    virtual void OnDeviceLost();
+    /// Recreate the texture from saved data if necessary and possible
     virtual void OnDeviceReset();
     virtual void OnDeviceReset();
     /// Release the texture
     /// Release the texture
     virtual void Release();
     virtual void Release();
     
     
     /// Set size, format and usage. Zero size will follow application window size. Return true if successful
     /// Set size, format and usage. Zero size will follow application window size. Return true if successful
     bool SetSize(int width, int height, unsigned format, TextureUsage usage = TEXTURE_STATIC);
     bool SetSize(int width, int height, unsigned format, TextureUsage usage = TEXTURE_STATIC);
+    /// Set data either partially or fully on a mip level. Return true if successful
+    bool SetData(unsigned level, int x, int y, int width, int height, const void* data);
     /// Load from an image. Return true if successful
     /// Load from an image. Return true if successful
     bool Load(SharedPtr<Image> image, bool useAlpha = false);
     bool Load(SharedPtr<Image> image, bool useAlpha = false);
     
     
+    /// Get data from a mip level. The destination buffer must be big enough. Return true if successful
+    bool GetData(unsigned level, void* dest) const;
     /// Return render surface
     /// Return render surface
     RenderSurface* GetRenderSurface() const { return renderSurface_; }
     RenderSurface* GetRenderSurface() const { return renderSurface_; }
     
     

+ 138 - 21
Engine/Graphics/OpenGL/OGLTextureCube.cpp

@@ -60,15 +60,48 @@ void TextureCube::RegisterObject(Context* context)
     context->RegisterFactory<TextureCube>();
     context->RegisterFactory<TextureCube>();
 }
 }
 
 
+void TextureCube::OnDeviceLost()
+{
+    savedLevels_.Clear();
+    
+    // Check if save data is supported, in that case save data of each face and mip level
+    if (GetDataSize(width_, height_))
+    {
+        for (unsigned i = 0; i < levels_; ++i)
+        {
+            for (unsigned face = FACE_POSITIVE_X; face < MAX_CUBEMAP_FACES; ++face)
+            {
+                int levelWidth = GetLevelWidth(i);
+                int levelHeight = GetLevelHeight(i);
+                SharedArrayPtr<unsigned char> savedLevel(new unsigned char[GetDataSize(levelWidth, levelHeight)]);
+                GetData((CubeMapFace)face, i, savedLevel.GetPtr());
+                savedLevels_.Push(savedLevel);
+            }
+        }
+    }
+    
+    Release();
+}
+
 void TextureCube::OnDeviceReset()
 void TextureCube::OnDeviceReset()
 {
 {
     if (!object_)
     if (!object_)
     {
     {
-        // Reload from the original file if possible
-        ResourceCache* cache = GetSubsystem<ResourceCache>();
-        const String& name = GetName();
-        if (cache && !name.Empty() && cache->Exists(name))
-            cache->ReloadResource(this);
+        Create();
+        
+        // Restore texture from save data if it exists
+        if (savedLevels_.Size())
+        {
+            for (unsigned i = 0; i < savedLevels_.Size(); ++i)
+            {
+                CubeMapFace face = (CubeMapFace)(i % 6);
+                unsigned level = i / 6;
+                int levelWidth = GetLevelWidth(level);
+                int levelHeight = GetLevelHeight(level);
+                SetData(face, level, 0, 0, levelWidth, levelHeight, savedLevels_[i].GetPtr());
+            }
+            savedLevels_.Clear();
+        }
     }
     }
 }
 }
 
 
@@ -93,7 +126,6 @@ void TextureCube::Release()
         
         
         glDeleteTextures(1, &object_);
         glDeleteTextures(1, &object_);
         object_ = 0;
         object_ = 0;
-        dataLost_ = true;
     }
     }
 }
 }
 
 
@@ -142,6 +174,69 @@ bool TextureCube::SetSize(int size, unsigned format, TextureUsage usage)
     return Create();
     return Create();
 }
 }
 
 
+bool TextureCube::SetData(CubeMapFace face, unsigned level, int x, int y, int width, int height, const void* data)
+{
+    if (!object_ || !graphics_)
+    {
+        LOGERROR("No texture created, can not set data");
+        return false;
+    }
+    
+    if (!data)
+    {
+        LOGERROR("Null source for setting data");
+        return false;
+    }
+    
+    if (level >= levels_)
+    {
+        LOGERROR("Illegal mip level for setting data");
+        return false;
+    }
+    
+    bool compressed = format_ == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || format_ == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
+        format_ == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+    if (compressed)
+    {
+        x &= ~3;
+        y &= ~3;
+    }
+    
+    int levelWidth = GetLevelWidth(level);
+    int levelHeight = GetLevelHeight(level);
+    if (x < 0 || x + width > levelWidth || y < 0 || y + height > levelHeight || width <= 0 || height <= 0)
+    {
+        LOGERROR("Illegal dimensions for setting data");
+        return false;
+    }
+    
+    bool wholeLevel = x == 0 && y == 0 && width == levelWidth && height == levelHeight;
+    
+    graphics_->SetTextureForUpdate(this);
+    
+    if (!compressed)
+    {
+        if (wholeLevel)
+            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, level, format_, width, height, 0, GetExternalFormat(format_),
+                GetDataType(format_), data);
+        else
+            glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, level, x, y, width, height, GetExternalFormat(format_),
+                GetDataType(format_), data);
+    }
+    else
+    {
+        if (wholeLevel)
+            glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, level, format_, width, height, 0,
+                GetDataSize(width, height), data);
+        else
+            glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, level, x, y, width, height, format_,
+                GetDataSize(width, height), data);
+    }
+    
+    graphics_->SetTexture(0, 0);
+    return true;
+}
+
 bool TextureCube::Load(Deserializer& source)
 bool TextureCube::Load(Deserializer& source)
 {
 {
     PROFILE(LoadTextureCube);
     PROFILE(LoadTextureCube);
@@ -183,7 +278,6 @@ bool TextureCube::Load(Deserializer& source)
         faceElem = faceElem.GetNextElement("face");
         faceElem = faceElem.GetNextElement("face");
     }
     }
     
     
-    ClearDataLost();
     return true;
     return true;
 }
 }
 
 
@@ -271,12 +365,10 @@ bool TextureCube::Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha)
             }
             }
         }
         }
         
         
-        graphics_->SetTextureForUpdate(this);
-        
         for (unsigned i = 0; i < levels_; ++i)
         for (unsigned i = 0; i < levels_; ++i)
         {
         {
-            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, i, format_, levelWidth, levelHeight, 0,
-                GetExternalFormat(format_), GL_UNSIGNED_BYTE, levelData);
+            SetData(face, i, 0, 0, levelWidth, levelHeight, levelData);
+            memoryUse += levelWidth * levelHeight * components;
             
             
             if (i < levels_ - 1)
             if (i < levels_ - 1)
             {
             {
@@ -285,11 +377,7 @@ bool TextureCube::Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha)
                 levelWidth = image->GetWidth();
                 levelWidth = image->GetWidth();
                 levelHeight = image->GetHeight();
                 levelHeight = image->GetHeight();
             }
             }
-            
-            memoryUse += levelWidth * levelHeight * components;
         }
         }
-        
-        graphics_->SetTexture(0, 0);
     }
     }
     else
     else
     {
     {
@@ -332,17 +420,12 @@ bool TextureCube::Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha)
             }
             }
         }
         }
         
         
-        graphics_->SetTextureForUpdate(this);
-        
         for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
         for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
         {
         {
             CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
             CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
-            glCompressedTexImage2D(target_, i, format_, level.width_, level.height_, 0, level.dataSize_, level.data_);
-            
+            SetData(face, i, 0, 0, level.width_, level.height_, level.data_);
             memoryUse += level.rows_ * level.rowSize_;
             memoryUse += level.rows_ * level.rowSize_;
         }
         }
-        
-        graphics_->SetTexture(0, 0);
     }
     }
     
     
     faceMemoryUse_[face] = memoryUse;
     faceMemoryUse_[face] = memoryUse;
@@ -353,6 +436,40 @@ bool TextureCube::Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha)
     return true;
     return true;
 }
 }
 
 
+bool TextureCube::GetData(CubeMapFace face, unsigned level, void* dest) const
+{
+    if (!object_ || !graphics_)
+    {
+        LOGERROR("No texture created, can not get data");
+        return false;
+    }
+    
+    if (!dest)
+    {
+        LOGERROR("Null destination for getting data");
+        return false;
+    }
+    
+    if (level >= levels_)
+    {
+        LOGERROR("Illegal mip level for getting data");
+        return false;
+    }
+    
+    bool compressed = format_ == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || format_ == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
+        format_ == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+    
+    graphics_->SetTextureForUpdate(const_cast<TextureCube*>(this));
+    
+    if (!compressed)
+        glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, level, GetExternalFormat(format_), GetDataType(format_), dest);
+    else
+        glGetCompressedTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, level, dest);
+    
+    graphics_->SetTexture(0, 0);
+    return true;
+}
+
 bool TextureCube::Create()
 bool TextureCube::Create()
 {
 {
     Release();
     Release();

+ 7 - 1
Engine/Graphics/OpenGL/OGLTextureCube.h

@@ -45,18 +45,24 @@ public:
     
     
     /// Load resource. Return true if successful
     /// Load resource. Return true if successful
     virtual bool Load(Deserializer& source);
     virtual bool Load(Deserializer& source);
-    /// Recreate the texture from the original file if necessary and possible
+    /// Save data and release the texture
+    virtual void OnDeviceLost();
+    /// Recreate the texture from saved data if necessary and possible
     virtual void OnDeviceReset();
     virtual void OnDeviceReset();
     /// Release the texture
     /// Release the texture
     virtual void Release();
     virtual void Release();
     
     
     /// Set size, format and usage. Return true if successful
     /// Set size, format and usage. Return true if successful
     bool SetSize(int size, unsigned format, TextureUsage usage = TEXTURE_STATIC);
     bool SetSize(int size, unsigned format, TextureUsage usage = TEXTURE_STATIC);
+    /// Set data either partially or fully on a face's mip level. Return true if successful
+    bool SetData(CubeMapFace face, unsigned level, int x, int y, int width, int height, const void* data);
     /// Load one face from a stream. Return true if successful
     /// Load one face from a stream. Return true if successful
     bool Load(CubeMapFace face, Deserializer& source);
     bool Load(CubeMapFace face, Deserializer& source);
     /// Load one face from an image. Return true if successful
     /// Load one face from an image. Return true if successful
     bool Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha = false);
     bool Load(CubeMapFace face, SharedPtr<Image> image, bool useAlpha = false);
     
     
+    /// Get data from a face's mip level. The destination buffer must be big enough. Return true if successful
+    bool GetData(CubeMapFace face, unsigned level, void* dest) const;
     /// Return render surface for one face
     /// Return render surface for one face
     RenderSurface* GetRenderSurface(CubeMapFace face) const { return renderSurfaces_[face]; }
     RenderSurface* GetRenderSurface(CubeMapFace face) const { return renderSurfaces_[face]; }
     
     

+ 5 - 5
Engine/Resource/Image.h

@@ -35,7 +35,7 @@ enum CompressedFormat
     CF_DXT5
     CF_DXT5
 };
 };
 
 
-/// Compressed image mipmap level
+/// Compressed image mip level
 struct CompressedLevel
 struct CompressedLevel
 {
 {
     /// Construct as empty
     /// Construct as empty
@@ -103,11 +103,11 @@ public:
     bool IsCompressed() const { return compressedFormat_ != CF_NONE; }
     bool IsCompressed() const { return compressedFormat_ != CF_NONE; }
     /// Return compressed format
     /// Return compressed format
     CompressedFormat GetCompressedFormat() const { return compressedFormat_; }
     CompressedFormat GetCompressedFormat() const { return compressedFormat_; }
-    /// Return number of compressed mipmap levels
+    /// Return number of compressed mip levels
     unsigned GetNumCompressedLevels() const { return numCompressedLevels_; }
     unsigned GetNumCompressedLevels() const { return numCompressedLevels_; }
-    /// Return next mipmap level by bilinear filtering
+    /// Return next mip level by bilinear filtering
     SharedPtr<Image> GetNextLevel() const;
     SharedPtr<Image> GetNextLevel() const;
-    /// Return a compressed mipmap level
+    /// Return a compressed mip level
     CompressedLevel GetCompressedLevel(unsigned index) const;
     CompressedLevel GetCompressedLevel(unsigned index) const;
     
     
 private:
 private:
@@ -122,7 +122,7 @@ private:
     int height_;
     int height_;
     /// Number of color components
     /// Number of color components
     unsigned components_;
     unsigned components_;
-    /// Number of compressed mipmap levels
+    /// Number of compressed mip levels
     unsigned numCompressedLevels_;
     unsigned numCompressedLevels_;
     /// Compressed format
     /// Compressed format
     CompressedFormat compressedFormat_;
     CompressedFormat compressedFormat_;

+ 1 - 5
Engine/UI/Font.cpp

@@ -112,11 +112,7 @@ const FontFace* Font::GetFace(int pointSize)
 {
 {
     Map<int, FontFace>::ConstIterator i = faces_.Find(pointSize);
     Map<int, FontFace>::ConstIterator i = faces_.Find(pointSize);
     if (i != faces_.End())
     if (i != faces_.End())
-    {
-        // Check if the font texture has lost its data, and recreate in that case
-        if (!i->second_.texture_->IsDataLost())
-            return &i->second_;
-    }
+        return &i->second_;
     
     
     PROFILE(GetFontFace);
     PROFILE(GetFontFace);