Implement CHROMIUM_copy_compressed_texture for D3D11.

BUG=angleproject:1356

Change-Id: Id563997d2921cf558c52a781ae66d8bde58d1f2f
Reviewed-on: https://chromium-review.googlesource.com/339847
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Geoff Lang <geofflang@chromium.org>
diff --git a/extensions/CHROMIUM_compressed_copy_texture.txt b/extensions/CHROMIUM_compressed_copy_texture.txt
new file mode 100644
index 0000000..a2cfb2c
--- /dev/null
+++ b/extensions/CHROMIUM_compressed_copy_texture.txt
@@ -0,0 +1,88 @@
+Name
+
+    CHROMIUM_copy_compressed_texture
+
+Name Strings
+
+    GL_CHROMIUM_copy_compressed_texture
+
+Version
+
+    Last Modifed Date: August 5, 2015
+
+Dependencies
+
+    OpenGL ES 2.0 is required.
+
+    GL_AMD_compressed_ATC_texture, GL_ATI_texture_compression_atitc,
+    GL_EXT_texture_compression_dxt1, GL_ANGLE_texture_compression_dxt5,
+    GL_EXT_texture_compression_s3tc and GL_OES_compressed_ETC1_RGB8_texture
+    affects the definition of this extension.
+
+Overview
+
+    This extension provides functionality for copying compressed textures. It
+    adds a new function glCompressedCopyTextureCHROMIUM that works similarily
+    to glCopyTextureCHROMIUM, but for compressed textures.
+
+    Which compressed texture formats that this extension supports depends on
+    the supported texture compression formats of the host GPU.
+
+Issues
+
+    glCompressedCopyTextureCHROMIUM will first try to copy into a compressed
+    texture of the same format as the source texture. If unsucessful, the
+    destination texture format will be changed to GL_RGBA and the texture will
+    be stored uncompressed.
+
+New Procedures and Functions
+
+    The command
+
+        void glCompressedCopyTextureCHROMIUM (GLuint source_id, GLuint dest_id)
+
+    Copies the contents of a compressed texture referred to by <source_id> to
+    <dest_id> texture.
+
+    Texture level 0 is copied from the source image to level 0 of the
+    destination texture.
+
+    The internal format of the source texture must be one of the following
+    symbolic constants: GL_ATC_RGB_AMD, GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD,
+    GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
+    GL_ETC1_RGB8_OES
+
+    The destination texture will be created or replaced with the same internal
+    format as the source texture.
+
+    INVALID_OPERATION is generated if internal format of source texture is not
+    one of the valid formats described above.
+
+    INVALID_OPERATION is generated if destination texture is immutable.
+
+    INVALID_VALUE is generated if <source_id> or <dest_id> are not valid texture
+    objects.
+
+    INVALID_VALUE is generated if textures corresponding to <dest_id> have not
+    been bound as GL_TEXTURE_2D object.
+
+    INVALID_VALUE is generated if level 0 of the source texture is not defined.
+
+Errors
+
+    None.
+
+New Tokens
+
+    None.
+
+New State
+
+    None.
+
+Revision History
+
+    15/6/2015    Documented the extension.
+    5/8/2015     Added glCompressedCopySubTextureCHROMIUM.
+    1/6/2016     Remove glCompressedCopySubTextureCHROMIUM.
+    1/8/2016     Remove <target> argument.
diff --git a/include/GLES2/gl2ext.h b/include/GLES2/gl2ext.h
index aa5d46b..42491d5 100644
--- a/include/GLES2/gl2ext.h
+++ b/include/GLES2/gl2ext.h
@@ -1316,6 +1316,14 @@
 #endif
 #endif /* GL_CHROMIUM_copy_texture */
 
+#ifndef GL_CHROMIUM_compressed_copy_texture
+#define GL_CHROMIUM_compressed_copy_texture 1
+typedef void(GL_APIENTRYP PFNGLCOMPRESSEDCOPYTEXTURECHROMIUMPROC)(GLuint sourceId, GLuint destId);
+#ifdef GL_GLEXT_PROTOTYPES
+GL_APICALL void GL_APIENTRY glCompressedCopyTextureCHROMIUM(GLuint sourceId, GLuint destId);
+#endif
+#endif /* GL_CHROMIUM_compressed_copy_texture */
+
 #ifndef GL_CHROMIUM_sync_query
 #define GL_CHROMIUM_sync_query 1
 #define GL_COMMANDS_COMPLETED_CHROMIUM    0x84F7
diff --git a/src/libANGLE/Caps.cpp b/src/libANGLE/Caps.cpp
index ee7dd85..47e1cf0 100644
--- a/src/libANGLE/Caps.cpp
+++ b/src/libANGLE/Caps.cpp
@@ -166,6 +166,7 @@
       bindUniformLocation(false),
       syncQuery(false),
       copyTexture(false),
+      copyCompressedTexture(false),
       webglCompatibility(false),
       bindGeneratesResource(false),
       robustClientMemory(false),
@@ -580,6 +581,7 @@
         map["GL_CHROMIUM_bind_uniform_location"] = esOnlyExtension(&Extensions::bindUniformLocation);
         map["GL_CHROMIUM_sync_query"] = esOnlyExtension(&Extensions::syncQuery);
         map["GL_CHROMIUM_copy_texture"] = esOnlyExtension(&Extensions::copyTexture);
+        map["GL_CHROMIUM_copy_compressed_texture"] = esOnlyExtension(&Extensions::copyCompressedTexture);
         map["GL_ANGLE_webgl_compatibility"] = esOnlyExtension(&Extensions::webglCompatibility);
         map["GL_CHROMIUM_bind_generates_resource"] = esOnlyExtension(&Extensions::bindGeneratesResource);
         map["GL_ANGLE_robust_client_memory"] = esOnlyExtension(&Extensions::robustClientMemory);
diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h
index 92886c1..c1ca039 100644
--- a/src/libANGLE/Caps.h
+++ b/src/libANGLE/Caps.h
@@ -293,6 +293,9 @@
     // GL_CHROMIUM_copy_texture
     bool copyTexture;
 
+    // GL_CHROMIUM_copy_compressed_texture
+    bool copyCompressedTexture;
+
     // GL_ANGLE_webgl_compatibility
     bool webglCompatibility;
 
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 081ffc2..c4124b3 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -3071,6 +3071,15 @@
                                             unpackUnmultiplyAlpha == GL_TRUE, sourceTexture));
 }
 
+void Context::compressedCopyTextureCHROMIUM(GLuint sourceId, GLuint destId)
+{
+    syncStateForTexImage();
+
+    gl::Texture *sourceTexture = getTexture(sourceId);
+    gl::Texture *destTexture   = getTexture(destId);
+    handleError(destTexture->copyCompressedTexture(sourceTexture));
+}
+
 void Context::getBufferPointerv(GLenum target, GLenum /*pname*/, void **params)
 {
     Buffer *buffer = mGLState.getTargetBuffer(target);
diff --git a/src/libANGLE/Context.h b/src/libANGLE/Context.h
index 2ce004c..7fa050d 100644
--- a/src/libANGLE/Context.h
+++ b/src/libANGLE/Context.h
@@ -482,6 +482,7 @@
                                 GLboolean unpackFlipY,
                                 GLboolean unpackPremultiplyAlpha,
                                 GLboolean unpackUnmultiplyAlpha);
+    void compressedCopyTextureCHROMIUM(GLuint sourceId, GLuint destId);
 
     void generateMipmap(GLenum target);
 
diff --git a/src/libANGLE/Path.h b/src/libANGLE/Path.h
index 40e2cf2..b103c84 100644
--- a/src/libANGLE/Path.h
+++ b/src/libANGLE/Path.h
@@ -49,9 +49,7 @@
 
     bool hasPathData() const { return mHasData; }
 
-    const rx::PathImpl *getImplementation() const { return mPath; }
-
-    rx::PathImpl *getImplementation() { return mPath; }
+    rx::PathImpl *getImplementation() const { return mPath; }
 
   private:
     rx::PathImpl *mPath;
diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h
index 03212a5..c4befe7 100644
--- a/src/libANGLE/Program.h
+++ b/src/libANGLE/Program.h
@@ -241,8 +241,7 @@
     void setLabel(const std::string &label) override;
     const std::string &getLabel() const override;
 
-    rx::ProgramImpl *getImplementation() { return mProgram; }
-    const rx::ProgramImpl *getImplementation() const { return mProgram; }
+    rx::ProgramImpl *getImplementation() const { return mProgram; }
 
     bool attachShader(Shader *shader);
     bool detachShader(Shader *shader);
diff --git a/src/libANGLE/Renderbuffer.cpp b/src/libANGLE/Renderbuffer.cpp
index 977117d..7b30a46 100644
--- a/src/libANGLE/Renderbuffer.cpp
+++ b/src/libANGLE/Renderbuffer.cpp
@@ -95,17 +95,12 @@
     return NoError();
 }
 
-rx::RenderbufferImpl *Renderbuffer::getImplementation()
+rx::RenderbufferImpl *Renderbuffer::getImplementation() const
 {
     ASSERT(mRenderbuffer);
     return mRenderbuffer;
 }
 
-const rx::RenderbufferImpl *Renderbuffer::getImplementation() const
-{
-    return mRenderbuffer;
-}
-
 GLsizei Renderbuffer::getWidth() const
 {
     return mWidth;
diff --git a/src/libANGLE/Renderbuffer.h b/src/libANGLE/Renderbuffer.h
index eceda70..57cc082 100644
--- a/src/libANGLE/Renderbuffer.h
+++ b/src/libANGLE/Renderbuffer.h
@@ -42,8 +42,7 @@
     Error setStorageMultisample(size_t samples, GLenum internalformat, size_t width, size_t height);
     Error setStorageEGLImageTarget(egl::Image *imageTarget);
 
-    rx::RenderbufferImpl *getImplementation();
-    const rx::RenderbufferImpl *getImplementation() const;
+    rx::RenderbufferImpl *getImplementation() const;
 
     GLsizei getWidth() const;
     GLsizei getHeight() const;
diff --git a/src/libANGLE/Sampler.cpp b/src/libANGLE/Sampler.cpp
index dc1d7c9..e2c018a 100644
--- a/src/libANGLE/Sampler.cpp
+++ b/src/libANGLE/Sampler.cpp
@@ -140,13 +140,9 @@
     return mSamplerState;
 }
 
-const rx::SamplerImpl *Sampler::getImplementation() const
+rx::SamplerImpl *Sampler::getImplementation() const
 {
     return mImpl;
 }
 
-rx::SamplerImpl *Sampler::getImplementation()
-{
-    return mImpl;
-}
 }
diff --git a/src/libANGLE/Sampler.h b/src/libANGLE/Sampler.h
index f950bc0..ee133ed 100644
--- a/src/libANGLE/Sampler.h
+++ b/src/libANGLE/Sampler.h
@@ -64,8 +64,7 @@
 
     const SamplerState &getSamplerState() const;
 
-    const rx::SamplerImpl *getImplementation() const;
-    rx::SamplerImpl *getImplementation();
+    rx::SamplerImpl *getImplementation() const;
 
   private:
     rx::SamplerImpl *mImpl;
diff --git a/src/libANGLE/Shader.h b/src/libANGLE/Shader.h
index d73400a..7a674a8 100644
--- a/src/libANGLE/Shader.h
+++ b/src/libANGLE/Shader.h
@@ -96,7 +96,7 @@
     GLenum getType() const { return mType; }
     GLuint getHandle() const;
 
-    const rx::ShaderImpl *getImplementation() const { return mImplementation; }
+    rx::ShaderImpl *getImplementation() const { return mImplementation; }
 
     void deleteSource();
     void setSource(GLsizei count, const char *const *string, const GLint *length);
diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp
index 4405fe9..1502fd1 100644
--- a/src/libANGLE/Texture.cpp
+++ b/src/libANGLE/Texture.cpp
@@ -903,6 +903,21 @@
                                     unpackUnmultiplyAlpha, source);
 }
 
+Error Texture::copyCompressedTexture(const Texture *source)
+{
+    // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+    releaseTexImageInternal();
+    orphanImages();
+
+    ANGLE_TRY(mTexture->copyCompressedTexture(source));
+
+    ASSERT(source->getTarget() != GL_TEXTURE_CUBE_MAP && getTarget() != GL_TEXTURE_CUBE_MAP);
+    const auto &sourceDesc = source->mState.getImageDesc(source->getTarget(), 0);
+    mState.setImageDesc(getTarget(), 0, sourceDesc);
+
+    return NoError();
+}
+
 Error Texture::setStorage(GLenum target, GLsizei levels, GLenum internalFormat, const Extents &size)
 {
     ASSERT(target == mState.mTarget);
diff --git a/src/libANGLE/Texture.h b/src/libANGLE/Texture.h
index 36cef52..18ceea8 100644
--- a/src/libANGLE/Texture.h
+++ b/src/libANGLE/Texture.h
@@ -295,6 +295,7 @@
                          bool unpackPremultiplyAlpha,
                          bool unpackUnmultiplyAlpha,
                          const Texture *source);
+    Error copyCompressedTexture(const Texture *source);
 
     Error setStorage(GLenum target, GLsizei levels, GLenum internalFormat, const Extents &size);
 
@@ -305,8 +306,7 @@
     egl::Surface *getBoundSurface() const;
     egl::Stream *getBoundStream() const;
 
-    rx::TextureImpl *getImplementation() { return mTexture; }
-    const rx::TextureImpl *getImplementation() const { return mTexture; }
+    rx::TextureImpl *getImplementation() const { return mTexture; }
 
     // FramebufferAttachmentObject implementation
     Extents getAttachmentSize(const FramebufferAttachment::Target &target) const override;
diff --git a/src/libANGLE/VertexArray.h b/src/libANGLE/VertexArray.h
index da660e0..65c62f1 100644
--- a/src/libANGLE/VertexArray.h
+++ b/src/libANGLE/VertexArray.h
@@ -87,8 +87,7 @@
         return mState.getVertexAttributes();
     }
 
-    rx::VertexArrayImpl *getImplementation() { return mVertexArray; }
-    const rx::VertexArrayImpl *getImplementation() const { return mVertexArray; }
+    rx::VertexArrayImpl *getImplementation() const { return mVertexArray; }
 
     size_t getMaxEnabledAttribute() const { return mState.getMaxEnabledAttribute(); }
 
diff --git a/src/libANGLE/angletypes.h b/src/libANGLE/angletypes.h
index 0c5c7ea..3490b03 100644
--- a/src/libANGLE/angletypes.h
+++ b/src/libANGLE/angletypes.h
@@ -297,12 +297,6 @@
     return GetAs<DestT>(src->getImplementation());
 }
 
-template <typename DestT, typename SrcT>
-inline const DestT *GetImplAs(const SrcT *src)
-{
-    return GetAs<const DestT>(src->getImplementation());
-}
-
 }
 
 #include "angletypes.inl"
diff --git a/src/libANGLE/renderer/TextureImpl.cpp b/src/libANGLE/renderer/TextureImpl.cpp
index 465d4e3..af47963 100644
--- a/src/libANGLE/renderer/TextureImpl.cpp
+++ b/src/libANGLE/renderer/TextureImpl.cpp
@@ -40,4 +40,11 @@
     UNREACHABLE();
     return gl::Error(GL_INVALID_OPERATION, "CHROMIUM_copy_texture exposed but not implemented.");
 }
+
+gl::Error TextureImpl::copyCompressedTexture(const gl::Texture *source)
+{
+    UNREACHABLE();
+    return gl::Error(GL_INVALID_OPERATION,
+                     "CHROMIUM_copy_compressed_texture exposed but not implemented.");
+}
 }
diff --git a/src/libANGLE/renderer/TextureImpl.h b/src/libANGLE/renderer/TextureImpl.h
index 45c2307..c7a68b5 100644
--- a/src/libANGLE/renderer/TextureImpl.h
+++ b/src/libANGLE/renderer/TextureImpl.h
@@ -72,6 +72,8 @@
                                      bool unpackUnmultiplyAlpha,
                                      const gl::Texture *source);
 
+    virtual gl::Error copyCompressedTexture(const gl::Texture *source);
+
     virtual gl::Error setStorage(GLenum target, size_t levels, GLenum internalFormat, const gl::Extents &size) = 0;
 
     virtual gl::Error setEGLImageTarget(GLenum target, egl::Image *image) = 0;
diff --git a/src/libANGLE/renderer/TextureImpl_mock.h b/src/libANGLE/renderer/TextureImpl_mock.h
index 8b8f397..62fa17d 100644
--- a/src/libANGLE/renderer/TextureImpl_mock.h
+++ b/src/libANGLE/renderer/TextureImpl_mock.h
@@ -35,6 +35,7 @@
                            bool,
                            bool,
                            const gl::Texture *));
+    MOCK_METHOD1(copyCompressedTexture, gl::Error(const gl::Texture *source));
     MOCK_METHOD4(setStorage, gl::Error(GLenum, size_t, GLenum, const gl::Extents &));
     MOCK_METHOD3(setImageExternal,
                  gl::Error(GLenum, egl::Stream *, const egl::Stream::GLTextureDescription &));
diff --git a/src/libANGLE/renderer/d3d/RendererD3D.h b/src/libANGLE/renderer/d3d/RendererD3D.h
index 9378b68..90282ca 100644
--- a/src/libANGLE/renderer/d3d/RendererD3D.h
+++ b/src/libANGLE/renderer/d3d/RendererD3D.h
@@ -169,6 +169,10 @@
                                   bool unpackFlipY,
                                   bool unpackPremultiplyAlpha,
                                   bool unpackUnmultiplyAlpha) = 0;
+    virtual gl::Error copyCompressedTexture(const gl::Texture *source,
+                                            GLint sourceLevel,
+                                            TextureStorage *storage,
+                                            GLint destLevel) = 0;
 
     // RenderTarget creation
     virtual gl::Error createRenderTarget(int width, int height, GLenum format, GLsizei samples, RenderTargetD3D **outRT) = 0;
diff --git a/src/libANGLE/renderer/d3d/TextureD3D.cpp b/src/libANGLE/renderer/d3d/TextureD3D.cpp
index 96e8e20..a7652ed 100644
--- a/src/libANGLE/renderer/d3d/TextureD3D.cpp
+++ b/src/libANGLE/renderer/d3d/TextureD3D.cpp
@@ -968,6 +968,26 @@
     return gl::NoError();
 }
 
+gl::Error TextureD3D_2D::copyCompressedTexture(const gl::Texture *source)
+{
+    GLenum sourceTarget = source->getTarget();
+    GLint sourceLevel   = 0;
+
+    GLint destLevel = 0;
+
+    GLenum sizedInternalFormat = source->getFormat(sourceTarget, sourceLevel).asSized();
+    gl::Extents size(static_cast<int>(source->getWidth(sourceTarget, sourceLevel)),
+                     static_cast<int>(source->getHeight(sourceTarget, sourceLevel)), 1);
+    redefineImage(destLevel, sizedInternalFormat, size, false);
+
+    ANGLE_TRY(initializeStorage(false));
+    ASSERT(mTexStorage);
+
+    ANGLE_TRY(mRenderer->copyCompressedTexture(source, sourceLevel, mTexStorage, destLevel));
+
+    return gl::NoError();
+}
+
 gl::Error TextureD3D_2D::setStorage(GLenum target, size_t levels, GLenum internalFormat, const gl::Extents &size)
 {
     ASSERT(GL_TEXTURE_2D && size.depth == 1);
diff --git a/src/libANGLE/renderer/d3d/TextureD3D.h b/src/libANGLE/renderer/d3d/TextureD3D.h
index 7a8cad9..9b28be8 100644
--- a/src/libANGLE/renderer/d3d/TextureD3D.h
+++ b/src/libANGLE/renderer/d3d/TextureD3D.h
@@ -170,6 +170,7 @@
                              bool unpackPremultiplyAlpha,
                              bool unpackUnmultiplyAlpha,
                              const gl::Texture *source) override;
+    gl::Error copyCompressedTexture(const gl::Texture *source) override;
 
     gl::Error setStorage(GLenum target, size_t levels, GLenum internalFormat, const gl::Extents &size) override;
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index 957ba39..f455be0 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -3109,6 +3109,41 @@
     return gl::NoError();
 }
 
+gl::Error Renderer11::copyCompressedTexture(const gl::Texture *source,
+                                            GLint sourceLevel,
+                                            TextureStorage *storage,
+                                            GLint destLevel)
+{
+    TextureStorage11_2D *destStorage11 = GetAs<TextureStorage11_2D>(storage);
+    ASSERT(destStorage11);
+
+    ID3D11Resource *destResource = nullptr;
+    ANGLE_TRY(destStorage11->getResource(&destResource));
+
+    gl::ImageIndex destIndex = gl::ImageIndex::Make2D(destLevel);
+    UINT destSubresource     = destStorage11->getSubresourceIndex(destIndex);
+
+    TextureD3D *sourceD3D = GetImplAs<TextureD3D>(source);
+    ASSERT(sourceD3D);
+
+    TextureStorage *sourceStorage = nullptr;
+    ANGLE_TRY(sourceD3D->getNativeTexture(&sourceStorage));
+
+    TextureStorage11_2D *sourceStorage11 = GetAs<TextureStorage11_2D>(sourceStorage);
+    ASSERT(sourceStorage11);
+
+    ID3D11Resource *sourceResource = nullptr;
+    ANGLE_TRY(sourceStorage11->getResource(&sourceResource));
+
+    gl::ImageIndex sourceIndex = gl::ImageIndex::Make2D(sourceLevel);
+    UINT sourceSubresource     = sourceStorage11->getSubresourceIndex(sourceIndex);
+
+    mDeviceContext->CopySubresourceRegion(destResource, destSubresource, 0, 0, 0, sourceResource,
+                                          sourceSubresource, nullptr);
+
+    return gl::NoError();
+}
+
 gl::Error Renderer11::createRenderTarget(int width, int height, GLenum format, GLsizei samples, RenderTargetD3D **outRT)
 {
     const d3d11::Format &formatInfo = d3d11::Format::Get(format, mRenderer11DeviceCaps);
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
index b19c5dc..633cf2b 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
@@ -215,6 +215,10 @@
                           bool unpackFlipY,
                           bool unpackPremultiplyAlpha,
                           bool unpackUnmultiplyAlpha) override;
+    gl::Error copyCompressedTexture(const gl::Texture *source,
+                                    GLint sourceLevel,
+                                    TextureStorage *storage,
+                                    GLint destLevel) override;
 
     // RenderTarget creation
     gl::Error createRenderTarget(int width,
diff --git a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
index ad0e108..dd4b5c1 100644
--- a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
@@ -1239,6 +1239,7 @@
     extensions->lossyETCDecode           = true;
     extensions->syncQuery                 = GetEventQuerySupport(featureLevel);
     extensions->copyTexture               = true;
+    extensions->copyCompressedTexture     = true;
 
     // D3D11 Feature Level 10_0+ uses SV_IsFrontFace in HLSL to emulate gl_FrontFacing.
     // D3D11 Feature Level 9_3 doesn't support SV_IsFrontFace, and has no equivalent, so can't support gl_FrontFacing.
diff --git a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
index 61da361..75ed74a 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
@@ -2340,6 +2340,15 @@
     return gl::Error(GL_INVALID_OPERATION);
 }
 
+gl::Error Renderer9::copyCompressedTexture(const gl::Texture *source,
+                                           GLint sourceLevel,
+                                           TextureStorage *storage,
+                                           GLint destLevel)
+{
+    UNIMPLEMENTED();
+    return gl::Error(GL_INVALID_OPERATION);
+}
+
 gl::Error Renderer9::createRenderTarget(int width, int height, GLenum format, GLsizei samples, RenderTargetD3D **outRT)
 {
     const d3d9::TextureFormat &d3d9FormatInfo = d3d9::GetTextureFormatInfo(format);
diff --git a/src/libANGLE/renderer/d3d/d3d9/Renderer9.h b/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
index fb8d8fa..cc796cf 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
+++ b/src/libANGLE/renderer/d3d/d3d9/Renderer9.h
@@ -211,6 +211,10 @@
                           bool unpackFlipY,
                           bool unpackPremultiplyAlpha,
                           bool unpackUnmultiplyAlpha) override;
+    gl::Error copyCompressedTexture(const gl::Texture *source,
+                                    GLint sourceLevel,
+                                    TextureStorage *storage,
+                                    GLint destLevel) override;
 
     // RenderTarget creation
     gl::Error createRenderTarget(int width,
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index c92ddad..b98f6b4 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -3320,6 +3320,68 @@
     return true;
 }
 
+bool ValidateCompressedCopyTextureCHROMIUM(Context *context, GLuint sourceId, GLuint destId)
+{
+    if (!context->getExtensions().copyCompressedTexture)
+    {
+        context->handleError(Error(GL_INVALID_OPERATION,
+                                   "GL_CHROMIUM_copy_compressed_texture extension not available."));
+        return false;
+    }
+
+    const gl::Texture *source = context->getTexture(sourceId);
+    if (source == nullptr)
+    {
+        context->handleError(
+            Error(GL_INVALID_VALUE, "Source texture is not a valid texture object."));
+        return false;
+    }
+
+    if (source->getTarget() != GL_TEXTURE_2D)
+    {
+        context->handleError(
+            Error(GL_INVALID_VALUE, "Source texture must be of type GL_TEXTURE_2D."));
+        return false;
+    }
+
+    if (source->getWidth(GL_TEXTURE_2D, 0) == 0 || source->getHeight(GL_TEXTURE_2D, 0) == 0)
+    {
+        context->handleError(Error(GL_INVALID_VALUE, "Source texture must level 0 defined."));
+        return false;
+    }
+
+    const gl::Format &sourceFormat = source->getFormat(GL_TEXTURE_2D, 0);
+    if (!sourceFormat.info->compressed)
+    {
+        context->handleError(
+            Error(GL_INVALID_OPERATION, "Source texture must have a compressed internal format."));
+        return false;
+    }
+
+    const gl::Texture *dest = context->getTexture(destId);
+    if (dest == nullptr)
+    {
+        context->handleError(
+            Error(GL_INVALID_VALUE, "Destination texture is not a valid texture object."));
+        return false;
+    }
+
+    if (dest->getTarget() != GL_TEXTURE_2D)
+    {
+        context->handleError(
+            Error(GL_INVALID_VALUE, "Destination texture must be of type GL_TEXTURE_2D."));
+        return false;
+    }
+
+    if (dest->getImmutableFormat())
+    {
+        context->handleError(Error(GL_INVALID_OPERATION, "Destination cannot be immutable."));
+        return false;
+    }
+
+    return true;
+}
+
 bool ValidateCreateShader(Context *context, GLenum type)
 {
     switch (type)
diff --git a/src/libANGLE/validationES2.h b/src/libANGLE/validationES2.h
index 8f0dc1a..705c514 100644
--- a/src/libANGLE/validationES2.h
+++ b/src/libANGLE/validationES2.h
@@ -325,6 +325,7 @@
                                     GLboolean unpackFlipY,
                                     GLboolean unpackPremultiplyAlpha,
                                     GLboolean unpackUnmultiplyAlpha);
+bool ValidateCompressedCopyTextureCHROMIUM(Context *context, GLuint sourceId, GLuint destId);
 
 bool ValidateCreateShader(Context *context, GLenum type);
 bool ValidateBufferData(ValidationContext *context,
diff --git a/src/libGLESv2/entry_points_egl.cpp b/src/libGLESv2/entry_points_egl.cpp
index 27404b9..132bc94 100644
--- a/src/libGLESv2/entry_points_egl.cpp
+++ b/src/libGLESv2/entry_points_egl.cpp
@@ -1472,6 +1472,9 @@
         INSERT_PROC_ADDRESS(gl, CopyTextureCHROMIUM);
         INSERT_PROC_ADDRESS(gl, CopySubTextureCHROMIUM);
 
+        // GL_CHROMIUM_copy_compressed_texture
+        INSERT_PROC_ADDRESS(gl, CompressedCopyTextureCHROMIUM);
+
         // GL_ANGLE_webgl_compatibility
         INSERT_PROC_ADDRESS(gl, EnableExtensionANGLE);
 
diff --git a/src/libGLESv2/entry_points_gles_2_0_ext.cpp b/src/libGLESv2/entry_points_gles_2_0_ext.cpp
index d9b2165..e4f9288 100644
--- a/src/libGLESv2/entry_points_gles_2_0_ext.cpp
+++ b/src/libGLESv2/entry_points_gles_2_0_ext.cpp
@@ -1950,6 +1950,23 @@
     }
 }
 
+ANGLE_EXPORT void GL_APIENTRY CompressedCopyTextureCHROMIUM(GLuint sourceId, GLuint destId)
+{
+    EVENT("(GLuint sourceId = %u, GLuint destId = %u)", sourceId, destId);
+
+    Context *context = GetValidGlobalContext();
+    if (context)
+    {
+        if (!context->skipValidation() &&
+            !ValidateCompressedCopyTextureCHROMIUM(context, sourceId, destId))
+        {
+            return;
+        }
+
+        context->compressedCopyTextureCHROMIUM(sourceId, destId);
+    }
+}
+
 GL_APICALL GLboolean GL_APIENTRY EnableExtensionANGLE(const GLchar *name)
 {
     EVENT("(const GLchar *name = %p)", name);
diff --git a/src/libGLESv2/entry_points_gles_2_0_ext.h b/src/libGLESv2/entry_points_gles_2_0_ext.h
index 677162d..88d30c2 100644
--- a/src/libGLESv2/entry_points_gles_2_0_ext.h
+++ b/src/libGLESv2/entry_points_gles_2_0_ext.h
@@ -259,6 +259,9 @@
                                                      GLboolean unpackPremultiplyAlpha,
                                                      GLboolean unpackUnmultiplyAlpha);
 
+// GL_CHROMIUM_copy_compressed_texture
+ANGLE_EXPORT void GL_APIENTRY CompressedCopyTextureCHROMIUM(GLuint sourceId, GLuint destId);
+
 // GL_ANGLE_webgl_compatibility
 GL_APICALL GLboolean GL_APIENTRY EnableExtensionANGLE(const GLchar *name);
 
diff --git a/src/tests/angle_end2end_tests.gypi b/src/tests/angle_end2end_tests.gypi
index 624048c..73be486 100644
--- a/src/tests/angle_end2end_tests.gypi
+++ b/src/tests/angle_end2end_tests.gypi
@@ -24,6 +24,7 @@
             '<(angle_path)/src/tests/gl_tests/ClearTest.cpp',
             '<(angle_path)/src/tests/gl_tests/ColorMaskTest.cpp',
             '<(angle_path)/src/tests/gl_tests/ComputeShaderTest.cpp',
+            '<(angle_path)/src/tests/gl_tests/CopyCompressedTextureTest.cpp',
             '<(angle_path)/src/tests/gl_tests/CopyTexImageTest.cpp',
             '<(angle_path)/src/tests/gl_tests/CopyTextureTest.cpp',
             '<(angle_path)/src/tests/gl_tests/CubeMapTextureTest.cpp',
diff --git a/src/tests/gl_tests/CopyCompressedTextureTest.cpp b/src/tests/gl_tests/CopyCompressedTextureTest.cpp
new file mode 100644
index 0000000..4d41519
--- /dev/null
+++ b/src/tests/gl_tests/CopyCompressedTextureTest.cpp
@@ -0,0 +1,369 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// CopyCompressedTextureTest.cpp: Tests of the GL_CHROMIUM_copy_compressed_texture extension
+
+#include "test_utils/ANGLETest.h"
+
+namespace angle
+{
+
+class CopyCompressedTextureTest : public ANGLETest
+{
+  protected:
+    CopyCompressedTextureTest()
+    {
+        setWindowWidth(256);
+        setWindowHeight(256);
+        setConfigRedBits(8);
+        setConfigGreenBits(8);
+        setConfigBlueBits(8);
+        setConfigAlphaBits(8);
+    }
+
+    void SetUp() override
+    {
+        ANGLETest::SetUp();
+
+        glGenTextures(2, mTextures);
+
+        mProgram = CompileProgram(
+            "attribute vec2 a_position;\n"
+            "varying vec2 v_texcoord;\n"
+            "void main()\n"
+            "{\n"
+            "   gl_Position = vec4(a_position, 0.0, 1.0);\n"
+            "   v_texcoord = (a_position + 1.0) * 0.5;\n"
+            "}\n",
+            "precision mediump float;\n"
+            "uniform sampler2D u_texture;\n"
+            "varying vec2 v_texcoord;\n"
+            "void main()\n"
+            "{\n"
+            "    gl_FragColor = texture2D(u_texture, v_texcoord);\n"
+            "}\n");
+        ASSERT_NE(0u, mProgram);
+
+        if (extensionEnabled("GL_CHROMIUM_copy_compressed_texture"))
+        {
+            glCompressedCopyTextureCHROMIUM =
+                reinterpret_cast<PFNGLCOMPRESSEDCOPYTEXTURECHROMIUMPROC>(
+                    eglGetProcAddress("glCompressedCopyTextureCHROMIUM"));
+        }
+    }
+
+    void TearDown() override
+    {
+        glDeleteTextures(2, mTextures);
+        glDeleteProgram(mProgram);
+
+        ANGLETest::TearDown();
+    }
+
+    bool checkExtensions() const
+    {
+        if (!extensionEnabled("GL_CHROMIUM_copy_compressed_texture"))
+        {
+            std::cout
+                << "Test skipped because GL_CHROMIUM_copy_compressed_texture is not available."
+                << std::endl;
+            return false;
+        }
+
+        EXPECT_NE(nullptr, glCompressedCopyTextureCHROMIUM);
+        if (glCompressedCopyTextureCHROMIUM == nullptr)
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    GLuint mProgram     = 0;
+    GLuint mTextures[2] = {0, 0};
+
+    PFNGLCOMPRESSEDCOPYTEXTURECHROMIUMPROC glCompressedCopyTextureCHROMIUM = nullptr;
+};
+
+namespace
+{
+
+const GLColor &CompressedImageColor = GLColor::red;
+
+// Single compressed ATC block of source pixels all set to:
+// CompressedImageColor.
+const uint8_t CompressedImageATC[8] = {0x0, 0x7c, 0x0, 0xf8, 0x55, 0x55, 0x55, 0x55};
+
+// Single compressed ATCIA block of source pixels all set to:
+// CompressedImageColor.
+const uint8_t CompressedImageATCIA[16] = {0xff, 0xff, 0x0, 0x0,  0x0,  0x0,  0x0,  0x0,
+                                          0x0,  0x7c, 0x0, 0xf8, 0x55, 0x55, 0x55, 0x55};
+
+// Single compressed DXT1 block of source pixels all set to:
+// CompressedImageColor.
+const uint8_t CompressedImageDXT1[8] = {0x00, 0xf8, 0x00, 0xf8, 0xaa, 0xaa, 0xaa, 0xaa};
+
+// Single compressed DXT5 block of source pixels all set to:
+// CompressedImageColor.
+const uint8_t CompressedImageDXT5[16] = {0xff, 0xff, 0x0, 0x0,  0x0,  0x0,  0x0,  0x0,
+                                         0x0,  0xf8, 0x0, 0xf8, 0xaa, 0xaa, 0xaa, 0xaa};
+
+// Single compressed DXT1 block of source pixels all set to:
+// CompressedImageColor.
+const uint8_t CompressedImageETC1[8] = {0x0, 0x0, 0xf8, 0x2, 0xff, 0xff, 0x0, 0x0};
+
+}  // anonymous namespace
+
+// Test to ensure that the basic functionality of the extension works.
+TEST_P(CopyCompressedTextureTest, Basic)
+{
+    if (!checkExtensions())
+    {
+        return;
+    }
+
+    if (!extensionEnabled("GL_EXT_texture_compression_dxt1"))
+    {
+        std::cout << "Test skipped because GL_EXT_texture_compression_dxt1 is not available."
+                  << std::endl;
+        return;
+    }
+
+    glBindTexture(GL_TEXTURE_2D, mTextures[0]);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 0,
+                           sizeof(CompressedImageDXT1), CompressedImageDXT1);
+    ASSERT_GL_NO_ERROR();
+
+    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
+    ASSERT_GL_NO_ERROR();
+
+    // Load texture.
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
+    GLint textureLoc = glGetUniformLocation(mProgram, "u_texture");
+    glUseProgram(mProgram);
+    glUniform1i(textureLoc, 0);
+
+    // Draw.
+    drawQuad(mProgram, "a_position", 0.5f);
+
+    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, CompressedImageColor);
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test validation of compressed formats
+TEST_P(CopyCompressedTextureTest, InternalFormat)
+{
+    if (!checkExtensions())
+    {
+        return;
+    }
+
+    struct Data
+    {
+        GLint format;
+        const uint8_t *data;
+        GLsizei dataSize;
+
+        Data() : Data(GL_NONE, nullptr, 0) {}
+        Data(GLint format, const uint8_t *data, GLsizei dataSize)
+            : format(format), data(data), dataSize(dataSize)
+        {
+        }
+    };
+    std::vector<Data> supportedFormats;
+
+    if (extensionEnabled("GL_AMD_compressed_ATC_texture"))
+    {
+        supportedFormats.push_back(
+            Data(GL_ATC_RGB_AMD, CompressedImageATC, sizeof(CompressedImageATC)));
+        supportedFormats.push_back(Data(GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD, CompressedImageATCIA,
+                                        sizeof(CompressedImageATCIA)));
+    }
+    if (extensionEnabled("GL_EXT_texture_compression_dxt1"))
+    {
+        supportedFormats.push_back(Data(GL_COMPRESSED_RGB_S3TC_DXT1_EXT, CompressedImageDXT1,
+                                        sizeof(CompressedImageDXT1)));
+    }
+    if (extensionEnabled("GL_ANGLE_texture_compression_dxt5"))
+    {
+        supportedFormats.push_back(Data(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, CompressedImageDXT5,
+                                        sizeof(CompressedImageDXT5)));
+    }
+    if (extensionEnabled("GL_OES_compressed_ETC1_RGB8_texture"))
+    {
+        supportedFormats.push_back(
+            Data(GL_ETC1_RGB8_OES, CompressedImageETC1, sizeof(CompressedImageETC1)));
+    }
+
+    for (const auto &supportedFormat : supportedFormats)
+    {
+        glBindTexture(GL_TEXTURE_2D, mTextures[0]);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glCompressedTexImage2D(GL_TEXTURE_2D, 0, supportedFormat.format, 4, 4, 0,
+                               supportedFormat.dataSize, supportedFormat.data);
+        ASSERT_GL_NO_ERROR();
+
+        glBindTexture(GL_TEXTURE_2D, mTextures[1]);
+        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
+        ASSERT_GL_NO_ERROR();
+    }
+}
+
+// Test that uncompressed textures generate errors when copying
+TEST_P(CopyCompressedTextureTest, InternalFormatNotSupported)
+{
+    if (!checkExtensions())
+    {
+        return;
+    }
+
+    glBindTexture(GL_TEXTURE_2D, mTextures[0]);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red);
+    ASSERT_GL_NO_ERROR();
+
+    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+
+    // Check that the GL_RGBA format reports an error.
+    glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+}
+
+// Test validation of texture IDs
+TEST_P(CopyCompressedTextureTest, InvalidTextureIds)
+{
+    if (!checkExtensions())
+    {
+        return;
+    }
+
+    glBindTexture(GL_TEXTURE_2D, mTextures[0]);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 0,
+                           sizeof(CompressedImageDXT1), CompressedImageDXT1);
+    ASSERT_GL_NO_ERROR();
+
+    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+
+    glCompressedCopyTextureCHROMIUM(mTextures[0], 99993);
+    EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+    glCompressedCopyTextureCHROMIUM(99994, mTextures[1]);
+    EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+    glCompressedCopyTextureCHROMIUM(99995, 99996);
+    EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+    glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
+    EXPECT_GL_NO_ERROR();
+}
+
+// Test that only 2D textures are valid
+TEST_P(CopyCompressedTextureTest, BindingPoints)
+{
+    if (!checkExtensions())
+    {
+        return;
+    }
+
+    glBindTexture(GL_TEXTURE_CUBE_MAP, mTextures[0]);
+    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
+         face++)
+    {
+        glCompressedTexImage2D(face, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 0,
+                               sizeof(CompressedImageDXT1), CompressedImageDXT1);
+    }
+    ASSERT_GL_NO_ERROR();
+
+    glBindTexture(GL_TEXTURE_CUBE_MAP, mTextures[1]);
+    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+
+    glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
+    EXPECT_GL_ERROR(GL_INVALID_VALUE);
+}
+
+// Test the destination texture cannot be immutable
+TEST_P(CopyCompressedTextureTest, Immutable)
+{
+    if (!checkExtensions() || getClientMajorVersion() < 3)
+    {
+        return;
+    }
+
+    glBindTexture(GL_TEXTURE_2D, mTextures[0]);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 0,
+                           sizeof(CompressedImageDXT1), CompressedImageDXT1);
+    ASSERT_GL_NO_ERROR();
+
+    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
+    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+
+    glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+}
+
+// Use this to select which configurations (e.g. which renderer, which GLES major version) these
+// tests should be run against.
+ANGLE_INSTANTIATE_TEST(CopyCompressedTextureTest,
+                       ES2_D3D9(),
+                       ES2_D3D11(),
+                       ES3_D3D11(),
+                       ES2_OPENGL(),
+                       ES3_OPENGL(),
+                       ES2_OPENGLES(),
+                       ES3_OPENGLES());
+
+}  // namespace angle