Implement CopyTex(Sub)Image2D

TRAC #11474

Signed-off-by: Shannon Woods
Signed-off-by: Daniel Koch

Author:    Andrew Lewycky

git-svn-id: https://angleproject.googlecode.com/svn/trunk@132 736b8ea6-26fd-11df-bfd4-992fa37f6226
diff --git a/src/libGLESv2/Texture.cpp b/src/libGLESv2/Texture.cpp
index 3b3b448..9e203e8 100644
--- a/src/libGLESv2/Texture.cpp
+++ b/src/libGLESv2/Texture.cpp
@@ -16,12 +16,13 @@
 #include "mathutil.h"
 #include "common/debug.h"
 #include "utilities.h"
+#include "Blit.h"
 
 namespace gl
 {
 
 Texture::Image::Image()
-  : dirty(false), surface(NULL)
+  : width(0), height(0), dirty(false), surface(NULL)
 {
 }
 
@@ -30,7 +31,7 @@
   if (surface) surface->Release();
 }
 
-Texture::Texture() : Colorbuffer(0)
+Texture::Texture(Context *context) : Colorbuffer(0), mContext(context)
 {
     mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
     mMagFilter = GL_LINEAR;
@@ -44,6 +45,11 @@
 {
 }
 
+Blit *Texture::getBlitter()
+{
+    return mContext->getBlitter();
+}
+
 // Returns true on successful filter state update (valid enum parameter)
 bool Texture::setMinFilter(GLenum filter)
 {
@@ -388,7 +394,28 @@
     return mRenderTarget;
 }
 
-Texture2D::Texture2D()
+void Texture::dropTexture()
+{
+    if (mRenderTarget)
+    {
+        mRenderTarget->Release();
+        mRenderTarget = NULL;
+    }
+
+    if (mBaseTexture)
+    {
+        mBaseTexture = NULL;
+    }
+}
+
+void Texture::pushTexture(IDirect3DBaseTexture9 *newTexture)
+{
+    mBaseTexture = newTexture;
+    mDirtyMetaData = false;
+}
+
+
+Texture2D::Texture2D(Context *context) : Texture(context)
 {
     mTexture = NULL;
 }
@@ -407,15 +434,62 @@
     return GL_TEXTURE_2D;
 }
 
+// While OpenGL doesn't check texture consistency until draw-time, D3D9 requires a complete texture
+// for render-to-texture (such as CopyTexImage). We have no way of keeping individual inconsistent levels.
+// Call this when a particular level of the texture must be defined with a specific format, width and height.
+//
+// Returns true if the existing texture was unsuitable had to be destroyed. If so, it will also set
+// a new height and width for the texture by working backwards from the given width and height.
+bool Texture2D::redefineTexture(GLint level, GLenum internalFormat, GLsizei width, GLsizei height)
+{
+    bool widthOkay = (mWidth >> level == width);
+    bool heightOkay = (mHeight >> level == height);
+
+    bool sizeOkay = ((widthOkay && heightOkay)
+                     || (widthOkay && mHeight >> level == 0 && height == 1)
+                     || (heightOkay && mWidth >> level == 0 && width == 1));
+
+    bool textureOkay = (sizeOkay && internalFormat == mImageArray[0].format);
+
+    if (!textureOkay)
+    {
+        TRACE("Redefining 2D texture (%d, 0x%04X, %d, %d => 0x%04X, %d, %d).", level,
+              mImageArray[0].format, mWidth, mHeight,
+              internalFormat, width, height);
+
+        // Purge all the levels and the texture.
+
+        for (int i = 0; i < MAX_TEXTURE_LEVELS; i++)
+        {
+            if (mImageArray[i].surface != NULL)
+            {
+                mImageArray[i].dirty = false;
+
+                mImageArray[i].surface->Release();
+                mImageArray[i].surface = NULL;
+            }
+        }
+
+        if (mTexture != NULL)
+        {
+            mTexture->Release();
+            mTexture = NULL;
+            dropTexture();
+        }
+
+        mWidth = width << level;
+        mHeight = height << level;
+        mImageArray[0].format = internalFormat;
+    }
+
+    return !textureOkay;
+}
+
 void Texture2D::setImage(GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels)
 {
-    Texture::setImage(width, height, format, type, unpackAlignment, pixels, &mImageArray[level]);
+    redefineTexture(level, internalFormat, width, height);
 
-    if (level == 0)
-    {
-        mWidth = width;
-        mHeight = height;
-    }
+    Texture::setImage(width, height, format, type, unpackAlignment, pixels, &mImageArray[level]);
 }
 
 void Texture2D::commitRect(GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height)
@@ -459,6 +533,56 @@
     commitRect(level, xoffset, yoffset, width, height);
 }
 
+void Texture2D::copyImage(GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, Renderbuffer *source)
+{
+    if (redefineTexture(level, internalFormat, width, height))
+    {
+        convertToRenderTarget();
+        pushTexture(mTexture);
+    }
+
+    RECT sourceRect;
+    sourceRect.left = x;
+    sourceRect.top = y + height;
+    sourceRect.right = x + width;
+    sourceRect.bottom = y;
+
+    IDirect3DSurface9 *dest;
+    HRESULT hr = mTexture->GetSurfaceLevel(level, &dest);
+
+    getBlitter()->formatConvert(source->getRenderTarget(), sourceRect, internalFormat, 0, 0, dest);
+    dest->Release();
+
+    mImageArray[level].width = width;
+    mImageArray[level].height = height;
+    mImageArray[level].format = internalFormat;
+}
+
+void Texture2D::copySubImage(GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height, Renderbuffer *source)
+{
+    if (redefineTexture(0, mImageArray[0].format, mImageArray[0].width, mImageArray[0].height))
+    {
+        convertToRenderTarget();
+        pushTexture(mTexture);
+    }
+    else
+    {
+        getRenderTarget(GL_TEXTURE_2D);
+    }
+
+    RECT sourceRect;
+    sourceRect.left = x;
+    sourceRect.top = y + height;
+    sourceRect.right = x + width;
+    sourceRect.bottom = y;
+
+    IDirect3DSurface9 *dest;
+    HRESULT hr = mTexture->GetSurfaceLevel(level, &dest);
+
+    getBlitter()->formatConvert(source->getRenderTarget(), sourceRect, mImageArray[0].format, xoffset, yoffset, dest);
+    dest->Release();
+}
+
 // Tests for GL texture object completeness. [OpenGL ES 2.0.24] section 3.7.10 page 81.
 bool Texture2D::isComplete() const
 {
@@ -654,7 +778,7 @@
     return false;
 }
 
-TextureCubeMap::TextureCubeMap()
+TextureCubeMap::TextureCubeMap(Context *context) : Texture(context)
 {
     mTexture = NULL;
 }
@@ -711,12 +835,10 @@
 
     if (mTexture != NULL)
     {
-        IDirect3DSurface9 *destLevel = NULL;
-        HRESULT result = mTexture->GetCubeMapSurface(static_cast<D3DCUBEMAP_FACES>(face), level, &destLevel);
+        IDirect3DSurface9 *destLevel = getCubeMapSurface(face, level);
+        ASSERT(destLevel != NULL);
 
-        ASSERT(SUCCEEDED(result));
-
-        if (SUCCEEDED(result))
+        if (destLevel != NULL)
         {
             Image *img = &mImageArray[face][level];
 
@@ -730,7 +852,7 @@
             destPoint.x = xoffset;
             destPoint.y = yoffset;
 
-            result = getDevice()->UpdateSurface(img->surface, &sourceRect, destLevel, &destPoint);
+            HRESULT result = getDevice()->UpdateSurface(img->surface, &sourceRect, destLevel, &destPoint);
             ASSERT(SUCCEEDED(result));
 
             destLevel->Release();
@@ -842,14 +964,12 @@
 
             if (img->dirty)
             {
-                IDirect3DSurface9 *levelSurface;
-                HRESULT result = mTexture->GetCubeMapSurface(static_cast<D3DCUBEMAP_FACES>(face), level, &levelSurface);
+                IDirect3DSurface9 *levelSurface = getCubeMapSurface(face, level);
+                ASSERT(levelSurface != NULL);
 
-                ASSERT(SUCCEEDED(result));
-
-                if (SUCCEEDED(result))
+                if (levelSurface != NULL)
                 {
-                    result = device->UpdateSurface(img->surface, NULL, levelSurface, NULL);
+                    HRESULT result = device->UpdateSurface(img->surface, NULL, levelSurface, NULL);
                     ASSERT(SUCCEEDED(result));
 
                     levelSurface->Release();
@@ -934,21 +1054,16 @@
 {
     ASSERT(es2dx::IsCubemapTextureTarget(target));
 
-    IDirect3DSurface9 *surface = NULL;
-    HRESULT result = mTexture->GetCubeMapSurface(static_cast<D3DCUBEMAP_FACES>(faceIndex(target)), 0, &surface);
-    ASSERT(SUCCEEDED(result));
+    IDirect3DSurface9 *surface = getCubeMapSurface(target, 0);
+    ASSERT(surface != NULL);
     return surface;
 }
 
 void TextureCubeMap::setImage(int face, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels)
 {
-    Texture::setImage(width, height, format, type, unpackAlignment, pixels, &mImageArray[face][level]);
+    redefineTexture(level, internalFormat, width);
 
-    if (face == 0 && level == 0)
-    {
-        mWidth = width;
-        mHeight = height;
-    }
+    Texture::setImage(width, height, format, type, unpackAlignment, pixels, &mImageArray[face][level]);
 }
 
 unsigned int TextureCubeMap::faceIndex(GLenum face)
@@ -977,4 +1092,137 @@
     return false;
 }
 
+// While OpenGL doesn't check texture consistency until draw-time, D3D9 requires a complete texture
+// for render-to-texture (such as CopyTexImage). We have no way of keeping individual inconsistent levels & faces.
+// Call this when a particular level of the texture must be defined with a specific format, width and height.
+//
+// Returns true if the existing texture was unsuitable had to be destroyed. If so, it will also set
+// a new size for the texture by working backwards from the given size.
+bool TextureCubeMap::redefineTexture(GLint level, GLenum internalFormat, GLsizei width)
+{
+    // Are these settings compatible with level 0?
+    bool sizeOkay = (mImageArray[0][0].width >> level == width);
+
+    bool textureOkay = (sizeOkay && internalFormat == mImageArray[0][0].format);
+
+    if (!textureOkay)
+    {
+        TRACE("Redefining cube texture (%d, 0x%04X, %d => 0x%04X, %d).", level,
+              mImageArray[0][0].format, mImageArray[0][0].width,
+              internalFormat, width);
+
+        // Purge all the levels and the texture.
+        for (int i = 0; i < MAX_TEXTURE_LEVELS; i++)
+        {
+            for (int f = 0; f < 6; f++)
+            {
+                if (mImageArray[f][i].surface != NULL)
+                {
+                    mImageArray[f][i].dirty = false;
+
+                    mImageArray[f][i].surface->Release();
+                    mImageArray[f][i].surface = NULL;
+                }
+            }
+        }
+
+        if (mTexture != NULL)
+        {
+            mTexture->Release();
+            mTexture = NULL;
+            dropTexture();
+        }
+
+        mWidth = width << level;
+        mImageArray[0][0].width = width << level;
+        mHeight = width << level;
+        mImageArray[0][0].height = width << level;
+
+        mImageArray[0][0].format = internalFormat;
+    }
+
+    return !textureOkay;
+}
+
+void TextureCubeMap::copyImage(GLenum face, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, Renderbuffer *source)
+{
+    unsigned int faceindex = faceIndex(face);
+
+    if (redefineTexture(level, internalFormat, width))
+    {
+        convertToRenderTarget();
+        pushTexture(mTexture);
+    }
+
+    RECT sourceRect;
+    sourceRect.left = x;
+    sourceRect.top = y + height;
+    sourceRect.right = x + width;
+    sourceRect.bottom = y;
+
+    IDirect3DSurface9 *dest = getCubeMapSurface(face, level);
+
+    getBlitter()->formatConvert(source->getRenderTarget(), sourceRect, internalFormat, 0, 0, dest);
+    dest->Release();
+
+    mImageArray[faceindex][level].width = width;
+    mImageArray[faceindex][level].height = height;
+    mImageArray[faceindex][level].format = internalFormat;
+}
+
+IDirect3DSurface9 *TextureCubeMap::getCubeMapSurface(unsigned int faceIdentifier, unsigned int level)
+{
+    unsigned int faceIndex;
+
+    if (faceIdentifier < 6)
+    {
+        faceIndex = faceIdentifier;
+    }
+    else if (faceIdentifier >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && faceIdentifier <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z)
+    {
+        faceIndex = faceIdentifier - GL_TEXTURE_CUBE_MAP_POSITIVE_X;
+    }
+    else
+    {
+        UNREACHABLE();
+        faceIndex = 0;
+    }
+
+    if (mTexture == NULL)
+    {
+        UNREACHABLE();
+        return NULL;
+    }
+
+    IDirect3DSurface9 *surface = NULL;
+
+    HRESULT hr = mTexture->GetCubeMapSurface(static_cast<D3DCUBEMAP_FACES>(faceIndex), level, &surface);
+
+    return (SUCCEEDED(hr)) ? surface : NULL;
+}
+
+void TextureCubeMap::copySubImage(GLenum face, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height, Renderbuffer *source)
+{
+    if (redefineTexture(0, mImageArray[0][0].format, mImageArray[0][0].width))
+    {
+        convertToRenderTarget();
+        pushTexture(mTexture);
+    }
+    else
+    {
+        getRenderTarget(face);
+    }
+
+    RECT sourceRect;
+    sourceRect.left = x;
+    sourceRect.top = y + height;
+    sourceRect.right = x + width;
+    sourceRect.bottom = y;
+
+    IDirect3DSurface9 *dest = getCubeMapSurface(face, level);
+
+    getBlitter()->formatConvert(source->getRenderTarget(), sourceRect, mImageArray[0][0].format, xoffset, yoffset, dest);
+    dest->Release();
+}
+
 }