Support FBO attachment to cubemaps

TRAC #11364

Signed-off-by: Nicolas Capens
Signed-off-by: Daniel Koch

Author:    Andrew Lewycky

git-svn-id: https://angleproject.googlecode.com/svn/trunk@172 736b8ea6-26fd-11df-bfd4-992fa37f6226
diff --git a/src/libGLESv2/Framebuffer.cpp b/src/libGLESv2/Framebuffer.cpp
index 8c5114a..ee54ad1 100644
--- a/src/libGLESv2/Framebuffer.cpp
+++ b/src/libGLESv2/Framebuffer.cpp
@@ -12,6 +12,7 @@
 #include "libGLESv2/main.h"
 #include "libGLESv2/Renderbuffer.h"
 #include "libGLESv2/Texture.h"
+#include "libGLESv2/utilities.h"
 
 namespace gl
 {
@@ -51,7 +52,8 @@
 
 void Framebuffer::detachTexture(GLuint texture)
 {
-    if (mColorbufferHandle == texture && mColorbufferType == GL_TEXTURE)
+    if (mColorbufferHandle == texture
+        && (mColorbufferType == GL_TEXTURE_2D || es2dx::IsCubemapTextureTarget(mColorbufferType)))
     {
         mColorbufferType = GL_NONE;
         mColorbufferHandle = 0;
@@ -109,15 +111,19 @@
     gl::Context *context = gl::getContext();
     Colorbuffer *colorbuffer = NULL;
 
-    if (mColorbufferType == GL_RENDERBUFFER)
+    if (mColorbufferType == GL_NONE)
+    {
+        UNREACHABLE();
+        colorbuffer = NULL;
+    }
+    else if (mColorbufferType == GL_RENDERBUFFER)
     {
         colorbuffer = context->getColorbuffer(mColorbufferHandle);
     }
-    else if (mColorbufferType == GL_TEXTURE)
+    else
     {
-        colorbuffer = context->getTexture(mColorbufferHandle);
+        colorbuffer = context->getTexture(mColorbufferHandle)->getColorbuffer(mColorbufferType);
     }
-    else UNREACHABLE();
 
     if (colorbuffer && colorbuffer->isColorbuffer())
     {
diff --git a/src/libGLESv2/Texture.cpp b/src/libGLESv2/Texture.cpp
index 2d0bc4c..5f14c31 100644
--- a/src/libGLESv2/Texture.cpp
+++ b/src/libGLESv2/Texture.cpp
@@ -32,7 +32,7 @@
   if (surface) surface->Release();
 }
 
-Texture::Texture(Context *context) : Colorbuffer(0), mContext(context)
+Texture::Texture(Context *context) : mContext(context)
 {
     mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
     mMagFilter = GL_LINEAR;
@@ -40,6 +40,7 @@
     mWrapT = GL_REPEAT;
 
     mDirtyMetaData = true;
+    mIsRenderable = false;
 }
 
 Texture::~Texture()
@@ -133,6 +134,16 @@
     return mWrapT;
 }
 
+GLuint Texture::getWidth() const
+{
+    return mWidth;
+}
+
+GLuint Texture::getHeight() const
+{
+    return mHeight;
+}
+
 // Selects an internal Direct3D 9 format for storing an Image
 D3DFORMAT Texture::selectFormat(GLenum format)
 {
@@ -361,6 +372,7 @@
     if (mDirtyMetaData)
     {
         mBaseTexture = createTexture();
+        mIsRenderable = false;
     }
 
     if (mDirtyMetaData || dirtyImageData())
@@ -375,18 +387,12 @@
 }
 
 // Returns the top-level texture surface as a render target
-IDirect3DSurface9 *Texture::getRenderTarget(GLenum target)
+void Texture::needRenderTarget()
 {
-    if (mDirtyMetaData && mRenderTarget)
-    {
-        mRenderTarget->Release();
-        mRenderTarget = NULL;
-    }
-
-    if (!mRenderTarget)
+    if (!mIsRenderable)
     {
         mBaseTexture = convertToRenderTarget();
-        mRenderTarget = getSurface(target);
+        mIsRenderable = true;
     }
 
     if (dirtyImageData())
@@ -395,38 +401,36 @@
     }
 
     mDirtyMetaData = false;
-
-    return mRenderTarget;
 }
 
 void Texture::dropTexture()
 {
-    if (mRenderTarget)
-    {
-        mRenderTarget->Release();
-        mRenderTarget = NULL;
-    }
-
     if (mBaseTexture)
     {
         mBaseTexture = NULL;
     }
+
+    mIsRenderable = false;
 }
 
-void Texture::pushTexture(IDirect3DBaseTexture9 *newTexture)
+void Texture::pushTexture(IDirect3DBaseTexture9 *newTexture, bool renderable)
 {
     mBaseTexture = newTexture;
     mDirtyMetaData = false;
+    mIsRenderable = renderable;
 }
 
 
 Texture2D::Texture2D(Context *context) : Texture(context)
 {
     mTexture = NULL;
+    mColorbufferProxy = NULL;
 }
 
 Texture2D::~Texture2D()
 {
+    delete mColorbufferProxy;
+
     if (mTexture)
     {
         mTexture->Release();
@@ -543,7 +547,7 @@
     if (redefineTexture(level, internalFormat, width, height))
     {
         convertToRenderTarget();
-        pushTexture(mTexture);
+        pushTexture(mTexture, true);
     }
 
     if (width != 0 && height != 0)
@@ -576,7 +580,7 @@
     if (redefineTexture(0, mImageArray[0].format, mImageArray[0].width, mImageArray[0].height))
     {
         convertToRenderTarget();
-        pushTexture(mTexture);
+        pushTexture(mTexture, true);
     }
     else
     {
@@ -599,9 +603,10 @@
 // Tests for GL texture object completeness. [OpenGL ES 2.0.24] section 3.7.10 page 81.
 bool Texture2D::isComplete() const
 {
-    ASSERT(mWidth == mImageArray[0].width && mHeight == mImageArray[0].height);
+    GLsizei width = mImageArray[0].width;
+    GLsizei height = mImageArray[0].height;
 
-    if (mWidth <= 0 || mHeight <= 0)
+    if (width <= 0 || height <= 0)
     {
         return false;
     }
@@ -625,7 +630,7 @@
 
     if (mipmapping)
     {
-        int q = log2(std::max(mWidth, mHeight));
+        int q = log2(std::max(width, height));
 
         for (int level = 1; level <= q; level++)
         {
@@ -773,18 +778,6 @@
     return mTexture;
 }
 
-IDirect3DSurface9 *Texture2D::getSurface(GLenum target)
-{
-    ASSERT(target == GL_TEXTURE_2D);
-
-    IDirect3DSurface9 *surface = NULL;
-    HRESULT result = mTexture->GetSurfaceLevel(0, &surface);
-
-    ASSERT(SUCCEEDED(result));
-
-    return surface;
-}
-
 bool Texture2D::dirtyImageData() const
 {
     int q = log2(std::max(mWidth, mHeight));
@@ -821,7 +814,7 @@
         mImageArray[i].height = std::max(mImageArray[0].height >> i, 1);
     }
 
-    getRenderTarget();
+    needRenderTarget();
 
     for (unsigned int i = 1; i <= q; i++)
     {
@@ -841,13 +834,50 @@
     }
 }
 
+Colorbuffer *Texture2D::getColorbuffer(GLenum target)
+{
+    if (target != GL_TEXTURE_2D)
+    {
+        return error(GL_INVALID_OPERATION, (Colorbuffer *)NULL);
+    }
+
+    if (mColorbufferProxy == NULL)
+    {
+        mColorbufferProxy = new TextureColorbufferProxy(this, target);
+    }
+
+    return mColorbufferProxy;
+}
+
+IDirect3DSurface9 *Texture2D::getRenderTarget(GLenum target)
+{
+    ASSERT(target == GL_TEXTURE_2D);
+
+    needRenderTarget();
+
+    IDirect3DSurface9 *renderTarget = NULL;
+    mTexture->GetSurfaceLevel(0, &renderTarget);
+
+    return renderTarget;
+}
+
 TextureCubeMap::TextureCubeMap(Context *context) : Texture(context)
 {
     mTexture = NULL;
+
+    for (int i = 0; i < 6; i++)
+    {
+        mFaceProxies[i] = NULL;
+    }
 }
 
 TextureCubeMap::~TextureCubeMap()
 {
+    for (int i = 0; i < 6; i++)
+    {
+        delete mFaceProxies[i];
+    }
+
     if (mTexture)
     {
         mTexture->Release();
@@ -934,7 +964,9 @@
 // Tests for GL texture object completeness. [OpenGL ES 2.0.24] section 3.7.10 page 81.
 bool TextureCubeMap::isComplete() const
 {
-    if (mWidth <= 0 || mHeight <= 0 || mWidth != mHeight)
+    int size = mImageArray[0][0].width;
+
+    if (size <= 0)
     {
         return false;
     }
@@ -958,7 +990,7 @@
 
     for (int face = 0; face < 6; face++)
     {
-        if (mImageArray[face][0].width != mWidth || mImageArray[face][0].height != mHeight)
+        if (mImageArray[face][0].width != size || mImageArray[face][0].height != size)
         {
             return false;
         }
@@ -966,7 +998,7 @@
 
     if (mipmapping)
     {
-        int q = log2(mWidth);
+        int q = log2(size);
 
         for (int face = 0; face < 6; face++)
         {
@@ -1119,15 +1151,6 @@
     return mTexture;
 }
 
-IDirect3DSurface9 *TextureCubeMap::getSurface(GLenum target)
-{
-    ASSERT(es2dx::IsCubemapTextureTarget(target));
-
-    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)
 {
     redefineTexture(level, internalFormat, width);
@@ -1220,7 +1243,7 @@
     if (redefineTexture(level, internalFormat, width))
     {
         convertToRenderTarget();
-        pushTexture(mTexture);
+        pushTexture(mTexture, true);
     }
 
     ASSERT(width == height);
@@ -1287,7 +1310,7 @@
     if (redefineTexture(0, mImageArray[0][0].format, mImageArray[0][0].width))
     {
         convertToRenderTarget();
-        pushTexture(mTexture);
+        pushTexture(mTexture, true);
     }
     else
     {
@@ -1352,7 +1375,7 @@
         }
     }
 
-    getRenderTarget();
+    needRenderTarget();
 
     for (unsigned int f = 0; f < 6; f++)
     {
@@ -1372,4 +1395,57 @@
     }
 }
 
+Colorbuffer *TextureCubeMap::getColorbuffer(GLenum target)
+{
+    if (!es2dx::IsCubemapTextureTarget(target))
+    {
+        return error(GL_INVALID_OPERATION, (Colorbuffer *)NULL);
+    }
+
+    unsigned int face = faceIndex(target);
+
+    if (mFaceProxies[face] == NULL)
+    {
+        mFaceProxies[face] = new TextureColorbufferProxy(this, target);
+    }
+
+    return mFaceProxies[face];
+}
+
+IDirect3DSurface9 *TextureCubeMap::getRenderTarget(GLenum target)
+{
+    ASSERT(es2dx::IsCubemapTextureTarget(target));
+
+    needRenderTarget();
+
+    IDirect3DSurface9 *renderTarget = NULL;
+    mTexture->GetCubeMapSurface(static_cast<D3DCUBEMAP_FACES>(faceIndex(target)), 0, &renderTarget);
+
+    return renderTarget;
+}
+
+Texture::TextureColorbufferProxy::TextureColorbufferProxy(Texture *texture, GLenum target)
+  : Colorbuffer(NULL), mTexture(texture), mTarget(target)
+{
+    ASSERT(target == GL_TEXTURE_2D || es2dx::IsCubemapTextureTarget(target));
+    latchTextureInfo();
+}
+
+IDirect3DSurface9 *Texture::TextureColorbufferProxy::getRenderTarget()
+{
+    latchTextureInfo();
+
+    if (mRenderTarget) mRenderTarget->Release();
+
+    mRenderTarget = mTexture->getRenderTarget(mTarget);
+
+    return mRenderTarget;
+}
+
+void Texture::TextureColorbufferProxy::latchTextureInfo()
+{
+    mWidth = mTexture->getWidth();
+    mHeight = mTexture->getHeight();
+}
+
 }
diff --git a/src/libGLESv2/Texture.h b/src/libGLESv2/Texture.h
index 0463984..837467f 100644
--- a/src/libGLESv2/Texture.h
+++ b/src/libGLESv2/Texture.h
@@ -11,13 +11,15 @@
 #ifndef LIBGLESV2_TEXTURE_H_
 #define LIBGLESV2_TEXTURE_H_
 
+#include <vector>
+
 #define GL_APICALL
 #include <GLES2/gl2.h>
 #include <d3d9.h>
 
-#include <vector>
-
 #include "libGLESv2/Renderbuffer.h"
+#include "libGLESv2/utilities.h"
+#include "common/debug.h"
 
 namespace gl
 {
@@ -32,12 +34,12 @@
     MAX_TEXTURE_LEVELS = 12   // 1+log2 of MAX_TEXTURE_SIZE
 };
 
-class Texture : public Colorbuffer
+class Texture
 {
   public:
     explicit Texture(Context *context);
 
-    ~Texture();
+    virtual ~Texture();
 
     virtual GLenum getTarget() const = 0;
 
@@ -51,15 +53,33 @@
     GLenum getWrapS() const;
     GLenum getWrapT() const;
 
+    GLuint getWidth() const;
+    GLuint getHeight() const;
+
     virtual bool isComplete() const = 0;
 
     IDirect3DBaseTexture9 *getTexture();
-    IDirect3DSurface9 *getRenderTarget(GLenum target);
-    IDirect3DSurface9 *getRenderTarget() { return getRenderTarget(GL_TEXTURE_2D); } // FIXME: to be removed once FBO rendering is completed.
+    virtual Colorbuffer *getColorbuffer(GLenum target) = 0;
 
     virtual void generateMipmaps() = 0;
 
   protected:
+    class TextureColorbufferProxy;
+    friend class TextureColorbufferProxy;
+    class TextureColorbufferProxy : public Colorbuffer
+    {
+      public:
+        TextureColorbufferProxy(Texture *texture, GLenum target); // target is a 2D-like texture target (GL_TEXTURE_2D or one of the cube face targets)
+
+        virtual IDirect3DSurface9 *getRenderTarget();
+
+      private:
+        Texture *mTexture;
+        GLenum mTarget;
+
+        void latchTextureInfo();
+    };
+
     // Helper structure representing a single image layer
     struct Image
     {
@@ -87,19 +107,24 @@
     void setImage(GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels, Image *img);
     void subImage(GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels, Image *img);
 
+    void needRenderTarget();
+
     // The pointer returned is weak and it is assumed the derived class will keep a strong pointer until the next createTexture() call.
     virtual IDirect3DBaseTexture9 *createTexture() = 0;
     virtual void updateTexture() = 0;
     virtual IDirect3DBaseTexture9 *convertToRenderTarget() = 0;
-    virtual IDirect3DSurface9 *getSurface(GLenum target) = 0;
+    virtual IDirect3DSurface9 *getRenderTarget(GLenum target) = 0;
 
     virtual bool dirtyImageData() const = 0;
 
     void dropTexture();
-    void pushTexture(IDirect3DBaseTexture9 *newTexture);
+    void pushTexture(IDirect3DBaseTexture9 *newTexture, bool renderable);
 
     Blit *getBlitter();
 
+    unsigned int mWidth;
+    unsigned int mHeight;
+
   private:
     DISALLOW_COPY_AND_ASSIGN(Texture);
 
@@ -107,6 +132,7 @@
 
     IDirect3DBaseTexture9 *mBaseTexture; // This is a weak pointer. The derived class is assumed to own a strong pointer.
     bool mDirtyMetaData;
+    bool mIsRenderable;
 
     void loadImageData(GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type,
                        GLint unpackAlignment, const void *input, std::size_t outputPitch, void *output) const;
@@ -132,13 +158,14 @@
 
     virtual void generateMipmaps();
 
+    virtual Colorbuffer *getColorbuffer(GLenum target);
+
   private:
     DISALLOW_COPY_AND_ASSIGN(Texture2D);
 
     virtual IDirect3DBaseTexture9 *createTexture();
     virtual void updateTexture();
     virtual IDirect3DBaseTexture9 *convertToRenderTarget();
-    virtual IDirect3DSurface9 *getSurface(GLenum target);
 
     virtual bool dirtyImageData() const;
 
@@ -148,7 +175,11 @@
 
     IDirect3DTexture9 *mTexture;
 
+    TextureColorbufferProxy *mColorbufferProxy;
+
     bool redefineTexture(GLint level, GLenum internalFormat, GLsizei width, GLsizei height);
+
+    virtual IDirect3DSurface9 *getRenderTarget(GLenum target);
 };
 
 class TextureCubeMap : public Texture
@@ -175,13 +206,14 @@
 
     virtual void generateMipmaps();
 
+    virtual Colorbuffer *getColorbuffer(GLenum target);
+
   private:
     DISALLOW_COPY_AND_ASSIGN(TextureCubeMap);
 
     virtual IDirect3DBaseTexture9 *createTexture();
     virtual void updateTexture();
     virtual IDirect3DBaseTexture9 *convertToRenderTarget();
-    virtual IDirect3DSurface9 *getSurface(GLenum target);
 
     virtual bool dirtyImageData() const;
 
@@ -200,6 +232,10 @@
     Image mImageArray[6][MAX_TEXTURE_LEVELS];
 
     IDirect3DCubeTexture9 *mTexture;
+
+    TextureColorbufferProxy *mFaceProxies[6];
+
+    virtual IDirect3DSurface9 *getRenderTarget(GLenum target);
 };
 }
 
diff --git a/src/libGLESv2/libGLESv2.cpp b/src/libGLESv2/libGLESv2.cpp
index 5afb6fd..1edbfa1 100644
--- a/src/libGLESv2/libGLESv2.cpp
+++ b/src/libGLESv2/libGLESv2.cpp
@@ -1636,34 +1636,40 @@
 
         if (context)
         {
-            if (texture)
+            if (texture == 0)
             {
+                textarget = GL_NONE;
+            }
+            else
+            {
+                gl::Texture *tex = context->getTexture(texture);
+
+                if (tex == NULL)
+                {
+                    return error(GL_INVALID_OPERATION);
+                }
+
                 switch (textarget)
                 {
                   case GL_TEXTURE_2D:
-                    if (!context->getTexture2D())
+                    if (tex->getTarget() != GL_TEXTURE_2D)
                     {
                         return error(GL_INVALID_OPERATION);
                     }
                     break;
+
                   case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
-                    UNIMPLEMENTED();   // FIXME
-                    break;
                   case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
-                    UNIMPLEMENTED();   // FIXME
-                    break;
                   case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
-                    UNIMPLEMENTED();   // FIXME
-                    break;
                   case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
-                    UNIMPLEMENTED();   // FIXME
-                    break;
                   case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
-                    UNIMPLEMENTED();   // FIXME
-                    break;
                   case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
-                    UNIMPLEMENTED();   // FIXME
+                    if (tex->getTarget() != GL_TEXTURE_CUBE_MAP)
+                    {
+                        return error(GL_INVALID_OPERATION);
+                    }
                     break;
+
                   default:
                     return error(GL_INVALID_ENUM);
                 }
@@ -1681,7 +1687,7 @@
                 return error(GL_INVALID_OPERATION);
             }
 
-            framebuffer->setColorbuffer(GL_TEXTURE, texture);
+            framebuffer->setColorbuffer(textarget, texture);
         }
     }
     catch(std::bad_alloc&)