Add texture rectangle extension.

This is needed to support binding IOSurfaces to textures on OSX. This
commit adds support in the API and tests, but didn't need to implement
compiler changes as it already supported ARB_texture_rectangle.

Implementation of CHROMIUM_opy_texture for rectangle texture and the
spec are left for follow-up commits.

Change-Id: I45c66be763a9d3f6f619640f9f95f39b05c70867
Reviewed-on: https://chromium-review.googlesource.com/559106
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/include/GLES2/gl2ext_angle.h b/include/GLES2/gl2ext_angle.h
index 0683c94..ae6acce 100644
--- a/include/GLES2/gl2ext_angle.h
+++ b/include/GLES2/gl2ext_angle.h
@@ -537,6 +537,14 @@
 #endif
 #endif /* GL_ANGLE_multiview */
 
+#ifndef GL_ANGLE_texture_rectangle
+#define GL_ANGLE_texture_rectangle 1
+#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ANGLE 0x84F8
+#define GL_TEXTURE_RECTANGLE_ANGLE 0x84F5
+#define GL_TEXTURE_BINDING_RECTANGLE_ANGLE 0x84F6
+#define GL_SAMPLER_2D_RECT_ANGLE 0x8B63
+#endif /* GL_ANGLE_texture_rectangle */
+
 // clang-format on
 
 #endif  // INCLUDE_GLES2_GL2EXT_ANGLE_H_
diff --git a/include/angle_gl.h b/include/angle_gl.h
index 18de80b..b70a31c 100644
--- a/include/angle_gl.h
+++ b/include/angle_gl.h
@@ -16,9 +16,4 @@
 #include "GLES3/gl31.h"
 #include "GLES3/gl32.h"
 
-// The following enum is used in ANGLE, but is from desktop GL
-#ifndef GL_SAMPLER_2D_RECT_ARB
-#define GL_SAMPLER_2D_RECT_ARB 0x8B63
-#endif
-
 #endif // ANGLEGL_H_
diff --git a/src/common/utilities.cpp b/src/common/utilities.cpp
index 156bbfb..427e55e 100644
--- a/src/common/utilities.cpp
+++ b/src/common/utilities.cpp
@@ -124,6 +124,7 @@
         return GL_FLOAT;
       case GL_INT:
       case GL_SAMPLER_2D:
+      case GL_SAMPLER_2D_RECT_ANGLE:
       case GL_SAMPLER_3D:
       case GL_SAMPLER_CUBE:
       case GL_SAMPLER_2D_ARRAY:
@@ -251,7 +252,7 @@
       case GL_SAMPLER_CUBE:
       case GL_SAMPLER_2D_ARRAY:
       case GL_SAMPLER_EXTERNAL_OES:
-      case GL_SAMPLER_2D_RECT_ARB:
+      case GL_SAMPLER_2D_RECT_ANGLE:
       case GL_SAMPLER_2D_MULTISAMPLE:
       case GL_INT_SAMPLER_2D:
       case GL_INT_SAMPLER_3D:
@@ -321,7 +322,7 @@
       case GL_INT_SAMPLER_2D_ARRAY:
       case GL_INT_SAMPLER_2D_MULTISAMPLE:
       case GL_SAMPLER_EXTERNAL_OES:
-      case GL_SAMPLER_2D_RECT_ARB:
+      case GL_SAMPLER_2D_RECT_ANGLE:
       case GL_UNSIGNED_INT_SAMPLER_2D:
       case GL_UNSIGNED_INT_SAMPLER_3D:
       case GL_UNSIGNED_INT_SAMPLER_CUBE:
@@ -385,6 +386,7 @@
       case GL_SAMPLER_2D_ARRAY:
       case GL_SAMPLER_EXTERNAL_OES:
       case GL_SAMPLER_2D_MULTISAMPLE:
+      case GL_SAMPLER_2D_RECT_ANGLE:
       case GL_INT_SAMPLER_2D:
       case GL_INT_SAMPLER_3D:
       case GL_INT_SAMPLER_CUBE:
@@ -471,6 +473,9 @@
       case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
           return GL_TEXTURE_2D_MULTISAMPLE;
 
+      case GL_SAMPLER_2D_RECT_ANGLE:
+          return GL_TEXTURE_RECTANGLE_ANGLE;
+
       default:
         UNREACHABLE();
         return 0;
@@ -694,7 +699,7 @@
       case GL_SAMPLER_2D:
       case GL_SAMPLER_CUBE:
       case GL_SAMPLER_EXTERNAL_OES:
-      case GL_SAMPLER_2D_RECT_ARB:
+      case GL_SAMPLER_2D_RECT_ANGLE:
       case GL_SAMPLER_2D_ARRAY:
       case GL_SAMPLER_2D_MULTISAMPLE:
       case GL_SAMPLER_3D:
diff --git a/src/compiler/translator/util.cpp b/src/compiler/translator/util.cpp
index 32ed128..2336a7b 100644
--- a/src/compiler/translator/util.cpp
+++ b/src/compiler/translator/util.cpp
@@ -332,7 +332,7 @@
         case EbtSamplerExternal2DY2YEXT:
             return GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT;
         case EbtSampler2DRect:
-            return GL_SAMPLER_2D_RECT_ARB;
+            return GL_SAMPLER_2D_RECT_ANGLE;
         case EbtSampler2DArray:
             return GL_SAMPLER_2D_ARRAY;
         case EbtSampler2DMS:
diff --git a/src/libANGLE/Caps.cpp b/src/libANGLE/Caps.cpp
index 8b159f6..67d53e5 100644
--- a/src/libANGLE/Caps.cpp
+++ b/src/libANGLE/Caps.cpp
@@ -229,7 +229,8 @@
       surfacelessContext(false),
       clientArrays(false),
       robustResourceInitialization(false),
-      programCacheControl(false)
+      programCacheControl(false),
+      textureRectangle(false)
 {
 }
 
@@ -699,6 +700,7 @@
         map["GL_ANGLE_client_arrays"] = esOnlyExtension(&Extensions::clientArrays);
         map["GL_ANGLE_robust_resource_initialization"] = esOnlyExtension(&Extensions::robustResourceInitialization);
         map["GL_ANGLE_program_cache_control"] = esOnlyExtension(&Extensions::programCacheControl);
+        map["GL_ANGLE_texture_rectangle"] = enableableExtension(&Extensions::textureRectangle);
         // clang-format on
 
         return map;
@@ -746,6 +748,7 @@
     : maxElementIndex(0),
       max3DTextureSize(0),
       max2DTextureSize(0),
+      maxRectangleTextureSize(0),
       maxArrayTextureLayers(0),
       maxLODBias(0),
       maxCubeMapTextureSize(0),
diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h
index 67dbbeb..797a249 100644
--- a/src/libANGLE/Caps.h
+++ b/src/libANGLE/Caps.h
@@ -370,6 +370,9 @@
 
     // GL_ANGLE_program_cache_control
     bool programCacheControl;
+
+    // GL_ANGLE_texture_rectangle
+    bool textureRectangle;
 };
 
 struct ExtensionInfo
@@ -432,6 +435,7 @@
     GLuint64 maxElementIndex;
     GLuint max3DTextureSize;
     GLuint max2DTextureSize;
+    GLuint maxRectangleTextureSize;
     GLuint maxArrayTextureLayers;
     GLfloat maxLODBias;
     GLuint maxCubeMapTextureSize;
diff --git a/src/libANGLE/Compiler.cpp b/src/libANGLE/Compiler.cpp
index 30af05b..430cb79 100644
--- a/src/libANGLE/Compiler.cpp
+++ b/src/libANGLE/Compiler.cpp
@@ -72,6 +72,7 @@
     mResources.OES_EGL_image_external          = extensions.eglImageExternal;
     mResources.OES_EGL_image_external_essl3    = extensions.eglImageExternalEssl3;
     mResources.NV_EGL_stream_consumer_external = extensions.eglStreamConsumerExternal;
+    mResources.ARB_texture_rectangle           = extensions.textureRectangle;
     // TODO: use shader precision caps to determine if high precision is supported?
     mResources.FragmentPrecisionHigh = 1;
     mResources.EXT_frag_depth        = extensions.fragDepth;
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 2e7ec29..1c5c6a1 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -334,6 +334,13 @@
         }
     }
 
+    if (mExtensions.textureRectangle)
+    {
+        Texture *zeroTextureRectangle =
+            new Texture(mImplementation.get(), 0, GL_TEXTURE_RECTANGLE_ANGLE);
+        mZeroTextures[GL_TEXTURE_RECTANGLE_ANGLE].set(this, zeroTextureRectangle);
+    }
+
     if (mExtensions.eglImageExternal || mExtensions.eglStreamConsumerExternal)
     {
         Texture *zeroTextureExternal =
@@ -1422,6 +1429,9 @@
         case GL_MAX_TEXTURE_SIZE:
             *params = mCaps.max2DTextureSize;
             break;
+        case GL_MAX_RECTANGLE_TEXTURE_SIZE_ANGLE:
+            *params = mCaps.maxRectangleTextureSize;
+            break;
         case GL_MAX_CUBE_MAP_TEXTURE_SIZE:
             *params = mCaps.maxCubeMapTextureSize;
             break;
@@ -3028,6 +3038,10 @@
         {
             index = ImageIndex::Make2D(level);
         }
+        else if (textarget == GL_TEXTURE_RECTANGLE_ANGLE)
+        {
+            index = ImageIndex::MakeRectangle(level);
+        }
         else if (textarget == GL_TEXTURE_2D_MULTISAMPLE)
         {
             ASSERT(level == 0);
diff --git a/src/libANGLE/ContextState.cpp b/src/libANGLE/ContextState.cpp
index f1e89d9..0d240ff 100644
--- a/src/libANGLE/ContextState.cpp
+++ b/src/libANGLE/ContextState.cpp
@@ -229,6 +229,17 @@
             *numParams = 1;
             return true;
         }
+        case GL_MAX_RECTANGLE_TEXTURE_SIZE_ANGLE:
+        case GL_TEXTURE_BINDING_RECTANGLE_ANGLE:
+        {
+            if (!getExtensions().textureRectangle)
+            {
+                return false;
+            }
+            *type      = GL_INT;
+            *numParams = 1;
+            return true;
+        }
         case GL_MAX_DRAW_BUFFERS_EXT:
         case GL_MAX_COLOR_ATTACHMENTS_EXT:
         {
diff --git a/src/libANGLE/ImageIndex.cpp b/src/libANGLE/ImageIndex.cpp
index 657e3ad..e1b6624 100644
--- a/src/libANGLE/ImageIndex.cpp
+++ b/src/libANGLE/ImageIndex.cpp
@@ -38,6 +38,11 @@
     return ImageIndex(GL_TEXTURE_2D, mipIndex, ENTIRE_LEVEL);
 }
 
+ImageIndex ImageIndex::MakeRectangle(GLint mipIndex)
+{
+    return ImageIndex(GL_TEXTURE_RECTANGLE_ANGLE, mipIndex, ENTIRE_LEVEL);
+}
+
 ImageIndex ImageIndex::MakeCube(GLenum target, GLint mipIndex)
 {
     ASSERT(gl::IsCubeMapTextureTarget(target));
@@ -112,6 +117,13 @@
                               nullptr);
 }
 
+ImageIndexIterator ImageIndexIterator::MakeRectangle(GLint minMip, GLint maxMip)
+{
+    return ImageIndexIterator(GL_TEXTURE_RECTANGLE_ANGLE, Range<GLint>(minMip, maxMip),
+                              Range<GLint>(ImageIndex::ENTIRE_LEVEL, ImageIndex::ENTIRE_LEVEL),
+                              nullptr);
+}
+
 ImageIndexIterator ImageIndexIterator::MakeCube(GLint minMip, GLint maxMip)
 {
     return ImageIndexIterator(GL_TEXTURE_CUBE_MAP, Range<GLint>(minMip, maxMip), Range<GLint>(0, 6),
diff --git a/src/libANGLE/ImageIndex.h b/src/libANGLE/ImageIndex.h
index 5992784..dff8343 100644
--- a/src/libANGLE/ImageIndex.h
+++ b/src/libANGLE/ImageIndex.h
@@ -31,6 +31,7 @@
     bool is3D() const;
 
     static ImageIndex Make2D(GLint mipIndex);
+    static ImageIndex MakeRectangle(GLint mipIndex);
     static ImageIndex MakeCube(GLenum target, GLint mipIndex);
     static ImageIndex Make2DArray(GLint mipIndex, GLint layerIndex);
     static ImageIndex Make3D(GLint mipIndex, GLint layerIndex = ENTIRE_LEVEL);
@@ -55,6 +56,7 @@
 {
   public:
     static ImageIndexIterator Make2D(GLint minMip, GLint maxMip);
+    static ImageIndexIterator MakeRectangle(GLint minMip, GLint maxMip);
     static ImageIndexIterator MakeCube(GLint minMip, GLint maxMip);
     static ImageIndexIterator Make3D(GLint minMip, GLint maxMip, GLint minLayer, GLint maxLayer);
     static ImageIndexIterator Make2DArray(GLint minMip, GLint maxMip, const GLsizei *layerCounts);
diff --git a/src/libANGLE/State.cpp b/src/libANGLE/State.cpp
index 86424cd..c771ba0 100644
--- a/src/libANGLE/State.cpp
+++ b/src/libANGLE/State.cpp
@@ -150,6 +150,10 @@
         mShaderStorageBuffers.resize(caps.maxShaderStorageBufferBindings);
         mImageUnits.resize(caps.maxImageUnits);
     }
+    if (extensions.textureRectangle)
+    {
+        mSamplerTextures[GL_TEXTURE_RECTANGLE_ANGLE].resize(caps.maxCombinedTextureImageUnits);
+    }
     if (extensions.eglImageExternal || extensions.eglStreamConsumerExternal)
     {
         mSamplerTextures[GL_TEXTURE_EXTERNAL_OES].resize(caps.maxCombinedTextureImageUnits);
@@ -1917,6 +1921,11 @@
         ASSERT(mActiveSampler < mMaxCombinedTextureImageUnits);
         *params = getSamplerTextureId(static_cast<unsigned int>(mActiveSampler), GL_TEXTURE_2D);
         break;
+      case GL_TEXTURE_BINDING_RECTANGLE_ANGLE:
+          ASSERT(mActiveSampler < mMaxCombinedTextureImageUnits);
+          *params = getSamplerTextureId(static_cast<unsigned int>(mActiveSampler),
+                                        GL_TEXTURE_RECTANGLE_ANGLE);
+          break;
       case GL_TEXTURE_BINDING_CUBE_MAP:
         ASSERT(mActiveSampler < mMaxCombinedTextureImageUnits);
         *params =
diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp
index 2340e5d..057d9c6 100644
--- a/src/libANGLE/Texture.cpp
+++ b/src/libANGLE/Texture.cpp
@@ -1158,7 +1158,7 @@
     mBoundSurface = surface;
 
     // Set the image info to the size and format of the surface
-    ASSERT(mState.mTarget == GL_TEXTURE_2D);
+    ASSERT(mState.mTarget == GL_TEXTURE_2D || mState.mTarget == GL_TEXTURE_RECTANGLE_ANGLE);
     Extents size(surface->getWidth(), surface->getHeight(), 1);
     ImageDesc desc(size, Format(surface->getConfig()->renderTargetFormat));
     mState.setImageDesc(mState.mTarget, 0, desc);
@@ -1173,7 +1173,7 @@
     ANGLE_TRY(mTexture->releaseTexImage(context));
 
     // Erase the image info for level 0
-    ASSERT(mState.mTarget == GL_TEXTURE_2D);
+    ASSERT(mState.mTarget == GL_TEXTURE_2D || mState.mTarget == GL_TEXTURE_RECTANGLE_ANGLE);
     mState.clearImageDesc(mState.mTarget, 0);
     mDirtyChannel.signal();
     return NoError();
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp
index 4a9a413..eccb9b6 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp
+++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp
@@ -48,7 +48,8 @@
             const TextureGL *textureGL = GetImplAs<TextureGL>(texture);
 
             if (texture->getTarget() == GL_TEXTURE_2D ||
-                texture->getTarget() == GL_TEXTURE_2D_MULTISAMPLE)
+                texture->getTarget() == GL_TEXTURE_2D_MULTISAMPLE ||
+                texture->getTarget() == GL_TEXTURE_RECTANGLE_ANGLE)
             {
                 functions->framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint,
                                                 texture->getTarget(), textureGL->getTextureID(),
diff --git a/src/libANGLE/renderer/gl/StateManagerGL.cpp b/src/libANGLE/renderer/gl/StateManagerGL.cpp
index dbaad8b..51117bd 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.cpp
+++ b/src/libANGLE/renderer/gl/StateManagerGL.cpp
@@ -171,6 +171,7 @@
     ASSERT(extensions.maxViews >= 1u);
 
     mTextures[GL_TEXTURE_2D].resize(rendererCaps.maxCombinedTextureImageUnits);
+    mTextures[GL_TEXTURE_RECTANGLE_ANGLE].resize(rendererCaps.maxCombinedTextureImageUnits);
     mTextures[GL_TEXTURE_CUBE_MAP].resize(rendererCaps.maxCombinedTextureImageUnits);
     mTextures[GL_TEXTURE_2D_ARRAY].resize(rendererCaps.maxCombinedTextureImageUnits);
     mTextures[GL_TEXTURE_3D].resize(rendererCaps.maxCombinedTextureImageUnits);
diff --git a/src/libANGLE/renderer/gl/TextureGL.cpp b/src/libANGLE/renderer/gl/TextureGL.cpp
index 352cc69..e45a462 100644
--- a/src/libANGLE/renderer/gl/TextureGL.cpp
+++ b/src/libANGLE/renderer/gl/TextureGL.cpp
@@ -41,7 +41,8 @@
 
 bool UseTexImage2D(GLenum textureType)
 {
-    return textureType == GL_TEXTURE_2D || textureType == GL_TEXTURE_CUBE_MAP;
+    return textureType == GL_TEXTURE_2D || textureType == GL_TEXTURE_CUBE_MAP ||
+           textureType == GL_TEXTURE_RECTANGLE_ANGLE;
 }
 
 bool UseTexImage3D(GLenum textureType)
@@ -811,7 +812,7 @@
                                       std::max(size.height >> level, 1),
                                       1);
 
-                if (getTarget() == GL_TEXTURE_2D)
+                if (getTarget() == GL_TEXTURE_2D || getTarget() == GL_TEXTURE_RECTANGLE_ANGLE)
                 {
                     if (internalFormatInfo.compressed)
                     {
@@ -989,7 +990,7 @@
 
 gl::Error TextureGL::bindTexImage(const gl::Context *context, egl::Surface *surface)
 {
-    ASSERT(getTarget() == GL_TEXTURE_2D);
+    ASSERT(getTarget() == GL_TEXTURE_2D || getTarget() == GL_TEXTURE_RECTANGLE_ANGLE);
 
     // Make sure this texture is bound
     mStateManager->bindTexture(getTarget(), mTextureID);
@@ -1001,7 +1002,7 @@
 gl::Error TextureGL::releaseTexImage(const gl::Context *context)
 {
     // Not all Surface implementations reset the size of mip 0 when releasing, do it manually
-    ASSERT(getTarget() == GL_TEXTURE_2D);
+    ASSERT(getTarget() == GL_TEXTURE_2D || getTarget() == GL_TEXTURE_RECTANGLE_ANGLE);
 
     mStateManager->bindTexture(getTarget(), mTextureID);
     if (UseTexImage2D(getTarget()))
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
index 92c7f3b..fb911b9 100644
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
@@ -1002,6 +1002,14 @@
                                      functions->hasGLESExtension("GL_ARB_invalidate_subdata");
 
     extensions->translatedShaderSource = true;
+
+    if (functions->isAtLeastGL(gl::Version(3, 1)) ||
+        functions->hasGLExtension("GL_ARB_texture_rectangle"))
+    {
+        extensions->textureRectangle = true;
+        caps->maxRectangleTextureSize =
+            QuerySingleGLInt(functions, GL_MAX_RECTANGLE_TEXTURE_SIZE_ANGLE);
+    }
 }
 
 void GenerateWorkarounds(const FunctionsGL *functions, WorkaroundsGL *workarounds)
diff --git a/src/libANGLE/validationEGL.cpp b/src/libANGLE/validationEGL.cpp
index 442ed71..4128289 100644
--- a/src/libANGLE/validationEGL.cpp
+++ b/src/libANGLE/validationEGL.cpp
@@ -35,6 +35,9 @@
         case GL_TEXTURE_2D:
             maxDimension = caps.max2DTextureSize;
             break;
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            maxDimension = caps.maxRectangleTextureSize;
+            break;
         case GL_TEXTURE_CUBE_MAP:
             maxDimension = caps.maxCubeMapTextureSize;
             break;
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index f3ee493..cb0b596 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -257,7 +257,7 @@
 }
 
 template <typename ParamType>
-bool ValidateTextureWrapModeValue(Context *context, ParamType *params, bool isExternalTextureTarget)
+bool ValidateTextureWrapModeValue(Context *context, ParamType *params, bool restrictedWrapModes)
 {
     switch (ConvertToGLenum(params[0]))
     {
@@ -266,11 +266,11 @@
 
         case GL_REPEAT:
         case GL_MIRRORED_REPEAT:
-            if (isExternalTextureTarget)
+            if (restrictedWrapModes)
             {
-                // OES_EGL_image_external specifies this error.
+                // OES_EGL_image_external and ANGLE_texture_rectangle specifies this error.
                 context->handleError(InvalidEnum()
-                                     << "external textures only support CLAMP_TO_EDGE wrap mode");
+                                     << "texture only support CLAMP_TO_EDGE wrap mode");
                 return false;
             }
             break;
@@ -284,9 +284,7 @@
 }
 
 template <typename ParamType>
-bool ValidateTextureMinFilterValue(Context *context,
-                                   ParamType *params,
-                                   bool isExternalTextureTarget)
+bool ValidateTextureMinFilterValue(Context *context, ParamType *params, bool restrictedMinFilter)
 {
     switch (ConvertToGLenum(params[0]))
     {
@@ -298,11 +296,11 @@
         case GL_LINEAR_MIPMAP_NEAREST:
         case GL_NEAREST_MIPMAP_LINEAR:
         case GL_LINEAR_MIPMAP_LINEAR:
-            if (isExternalTextureTarget)
+            if (restrictedMinFilter)
             {
                 // OES_EGL_image_external specifies this error.
-                context->handleError(
-                    InvalidEnum() << "external textures only support NEAREST and LINEAR filtering");
+                context->handleError(InvalidEnum()
+                                     << "texture only support NEAREST and LINEAR filtering");
                 return false;
             }
             break;
@@ -861,6 +859,9 @@
         case GL_TEXTURE_CUBE_MAP:
             return true;
 
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            return context->getExtensions().textureRectangle;
+
         case GL_TEXTURE_3D:
         case GL_TEXTURE_2D_ARRAY:
             return (context->getClientMajorVersion() >= 3);
@@ -881,6 +882,9 @@
         case GL_TEXTURE_CUBE_MAP:
             return true;
 
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            return context->getExtensions().textureRectangle;
+
         default:
             return false;
     }
@@ -924,6 +928,8 @@
         case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
         case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
             return true;
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            return context->getExtensions().textureRectangle;
         default:
             return false;
     }
@@ -1023,6 +1029,8 @@
         case GL_TEXTURE_2D_ARRAY:
         case GL_TEXTURE_2D_MULTISAMPLE:
             return true;
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            return context->getExtensions().textureRectangle;
         default:
             return false;
     }
@@ -1095,6 +1103,8 @@
         case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
             maxDimension = caps.maxCubeMapTextureSize;
             break;
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            return level == 0;
         case GL_TEXTURE_3D:
             maxDimension = caps.max3DTextureSize;
             break;
@@ -2494,6 +2504,14 @@
         case GL_TEXTURE_BINDING_2D_ARRAY:
         case GL_TEXTURE_BINDING_2D_MULTISAMPLE:
             break;
+        case GL_TEXTURE_BINDING_RECTANGLE_ANGLE:
+            if (!context->getExtensions().textureRectangle)
+            {
+                context->handleError(InvalidEnum()
+                                     << "ANGLE_texture_rectangle extension not present");
+                return false;
+            }
+            break;
         case GL_TEXTURE_BINDING_EXTERNAL_OES:
             if (!context->getExtensions().eglStreamConsumerExternal &&
                 !context->getExtensions().eglImageExternal)
@@ -2676,6 +2694,10 @@
             maxDimension = caps.maxCubeMapTextureSize;
             break;
 
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            maxDimension = caps.maxRectangleTextureSize;
+            break;
+
         case GL_TEXTURE_2D_ARRAY:
             maxDimension = caps.max2DTextureSize;
             break;
@@ -5688,16 +5710,24 @@
         case GL_TEXTURE_WRAP_S:
         case GL_TEXTURE_WRAP_T:
         case GL_TEXTURE_WRAP_R:
-            if (!ValidateTextureWrapModeValue(context, params, target == GL_TEXTURE_EXTERNAL_OES))
             {
-                return false;
+                bool restrictedWrapModes =
+                    target == GL_TEXTURE_EXTERNAL_OES || target == GL_TEXTURE_RECTANGLE_ANGLE;
+                if (!ValidateTextureWrapModeValue(context, params, restrictedWrapModes))
+                {
+                    return false;
+                }
             }
             break;
 
         case GL_TEXTURE_MIN_FILTER:
-            if (!ValidateTextureMinFilterValue(context, params, target == GL_TEXTURE_EXTERNAL_OES))
             {
-                return false;
+                bool restrictedMinFilter =
+                    target == GL_TEXTURE_EXTERNAL_OES || target == GL_TEXTURE_RECTANGLE_ANGLE;
+                if (!ValidateTextureMinFilterValue(context, params, restrictedMinFilter))
+                {
+                    return false;
+                }
             }
             break;
 
@@ -5793,6 +5823,12 @@
                                      << "Base level must be 0 for multisampled textures.");
                 return false;
             }
+            if (target == GL_TEXTURE_RECTANGLE_ANGLE && static_cast<GLuint>(params[0]) != 0)
+            {
+                context->handleError(InvalidOperation()
+                                     << "Base level must be 0 for rectangle textures.");
+                return false;
+            }
             break;
 
         case GL_TEXTURE_MAX_LEVEL:
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index c98e52a..2810c11 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -314,8 +314,9 @@
         case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
             return textureType == GL_TEXTURE_CUBE_MAP;
 
-        // TODO(geofflang): accept GL_TEXTURE_RECTANGLE_ARB if the texture_rectangle extension is
-        // supported
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            return textureType == GL_TEXTURE_RECTANGLE_ANGLE &&
+                   context->getExtensions().textureRectangle;
 
         default:
             return false;
@@ -328,9 +329,8 @@
     {
         case GL_TEXTURE_2D:
             return true;
-
-        // TODO(geofflang): accept GL_TEXTURE_RECTANGLE_ARB if the texture_rectangle extension is
-        // supported
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            return context->getExtensions().textureRectangle;
 
         // TODO(geofflang): accept GL_TEXTURE_EXTERNAL_OES if the texture_external extension is
         // supported
@@ -375,6 +375,15 @@
             return false;
         }
     }
+    else if (target == GL_TEXTURE_RECTANGLE_ANGLE)
+    {
+        ASSERT(level == 0);
+        if (static_cast<GLuint>(width) > caps.maxRectangleTextureSize ||
+            static_cast<GLuint>(height) > caps.maxRectangleTextureSize)
+        {
+            return false;
+        }
+    }
     else if (IsCubeMapTextureTarget(target))
     {
         if (static_cast<GLuint>(width) > (caps.maxCubeMapTextureSize >> level) ||
@@ -1002,6 +1011,22 @@
             return false;
         }
     }
+    else if (target == GL_TEXTURE_RECTANGLE_ANGLE)
+    {
+        ASSERT(level == 0);
+        if (static_cast<GLuint>(width) > caps.maxRectangleTextureSize ||
+            static_cast<GLuint>(height) > caps.maxRectangleTextureSize)
+        {
+            context->handleError(InvalidValue());
+            return false;
+        }
+        if (isCompressed)
+        {
+            context->handleError(InvalidEnum()
+                                 << "Rectangle texture cannot have a compressed format.");
+            return false;
+        }
+    }
     else if (IsCubeMapTextureTarget(target))
     {
         if (!isSubImage && width != height)
@@ -1480,7 +1505,8 @@
                                      GLsizei width,
                                      GLsizei height)
 {
-    if (target != GL_TEXTURE_2D && target != GL_TEXTURE_CUBE_MAP)
+    if (target != GL_TEXTURE_2D && target != GL_TEXTURE_CUBE_MAP &&
+        target != GL_TEXTURE_RECTANGLE_ANGLE)
     {
         context->handleError(InvalidEnum());
         return false;
@@ -1523,6 +1549,20 @@
                 return false;
             }
             break;
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            if (static_cast<GLuint>(width) > caps.maxRectangleTextureSize ||
+                static_cast<GLuint>(height) > caps.maxRectangleTextureSize || levels != 1)
+            {
+                context->handleError(InvalidValue());
+                return false;
+            }
+            if (formatInfo.compressed)
+            {
+                context->handleError(InvalidEnum()
+                                     << "Rectangle texture cannot have a compressed format.");
+                return false;
+            }
+            break;
         case GL_TEXTURE_CUBE_MAP:
             if (static_cast<GLuint>(width) > caps.maxCubeMapTextureSize ||
                 static_cast<GLuint>(height) > caps.maxCubeMapTextureSize)
@@ -2601,6 +2641,12 @@
         return false;
     }
 
+    if (target == GL_TEXTURE_RECTANGLE_ANGLE)
+    {
+        context->handleError(InvalidEnum() << "Rectangle texture cannot have a compressed format.");
+        return false;
+    }
+
     return true;
 }
 
@@ -2796,6 +2842,15 @@
         case GL_TEXTURE_CUBE_MAP:
             break;
 
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            if (!context->getExtensions().textureRectangle)
+            {
+                context->handleError(InvalidEnum()
+                                     << "Context does not support GL_ANGLE_texture_rectangle");
+                return false;
+            }
+            break;
+
         case GL_TEXTURE_3D:
         case GL_TEXTURE_2D_ARRAY:
             if (context->getClientMajorVersion() < 3)
@@ -5807,6 +5862,22 @@
             }
             break;
 
+            case GL_TEXTURE_RECTANGLE_ANGLE:
+            {
+                if (level != 0)
+                {
+                    context->handleError(InvalidValue());
+                    return false;
+                }
+                if (tex->getTarget() != GL_TEXTURE_RECTANGLE_ANGLE)
+                {
+                    context->handleError(InvalidOperation()
+                                         << "Textarget must match the texture target type.");
+                    return false;
+                }
+            }
+            break;
+
             case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
             case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
             case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
@@ -5954,7 +6025,8 @@
         (!isPow2(static_cast<int>(texture->getWidth(baseTarget, 0))) ||
          !isPow2(static_cast<int>(texture->getHeight(baseTarget, 0)))))
     {
-        ASSERT(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP);
+        ASSERT(target == GL_TEXTURE_2D || target == GL_TEXTURE_RECTANGLE_ANGLE ||
+               target == GL_TEXTURE_CUBE_MAP);
         ANGLE_VALIDATION_ERR(context, InvalidOperation(), TextureNotPow2);
         return false;
     }
diff --git a/src/libANGLE/validationES3.cpp b/src/libANGLE/validationES3.cpp
index 6cc1827..3a97b17 100644
--- a/src/libANGLE/validationES3.cpp
+++ b/src/libANGLE/validationES3.cpp
@@ -199,6 +199,22 @@
             }
             break;
 
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+            ASSERT(level == 0);
+            if (static_cast<GLuint>(width) > caps.maxRectangleTextureSize ||
+                static_cast<GLuint>(height) > caps.maxRectangleTextureSize)
+            {
+                context->handleError(InvalidValue());
+                return false;
+            }
+            if (isCompressed)
+            {
+                context->handleError(InvalidEnum()
+                                     << "Rectangle texture cannot have a compressed format.");
+                return false;
+            }
+            break;
+
         case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
         case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
         case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
@@ -851,6 +867,17 @@
         }
         break;
 
+        case GL_TEXTURE_RECTANGLE_ANGLE:
+        {
+            if (static_cast<GLuint>(width) > caps.maxRectangleTextureSize ||
+                static_cast<GLuint>(height) > caps.maxRectangleTextureSize || levels != 1)
+            {
+                context->handleError(InvalidValue());
+                return false;
+            }
+        }
+        break;
+
         case GL_TEXTURE_CUBE_MAP:
         {
             if (width != height)
@@ -922,6 +949,12 @@
         return false;
     }
 
+    if (formatInfo.compressed && target == GL_TEXTURE_RECTANGLE_ANGLE)
+    {
+        context->handleError(InvalidEnum() << "Rectangle texture cannot have a compressed format.");
+        return false;
+    }
+
     return true;
 }
 
diff --git a/src/tests/angle_end2end_tests.gypi b/src/tests/angle_end2end_tests.gypi
index c7b2373..13b1361 100644
--- a/src/tests/angle_end2end_tests.gypi
+++ b/src/tests/angle_end2end_tests.gypi
@@ -80,6 +80,7 @@
             '<(angle_path)/src/tests/gl_tests/SwizzleTest.cpp',
             '<(angle_path)/src/tests/gl_tests/SyncQueriesTest.cpp',
             '<(angle_path)/src/tests/gl_tests/TextureMultisampleTest.cpp',
+            '<(angle_path)/src/tests/gl_tests/TextureRectangleTest.cpp',
             '<(angle_path)/src/tests/gl_tests/TextureTest.cpp',
             '<(angle_path)/src/tests/gl_tests/TimerQueriesTest.cpp',
             '<(angle_path)/src/tests/gl_tests/TransformFeedbackTest.cpp',
diff --git a/src/tests/angle_unittests.gypi b/src/tests/angle_unittests.gypi
index 99b1867..145d8f9 100644
--- a/src/tests/angle_unittests.gypi
+++ b/src/tests/angle_unittests.gypi
@@ -49,6 +49,7 @@
             '<(angle_path)/src/tests/angle_unittests_utils.h',
             '<(angle_path)/src/tests/compiler_tests/API_test.cpp',
             '<(angle_path)/src/tests/compiler_tests/AppendixALimitations_test.cpp',
+            '<(angle_path)/src/tests/compiler_tests/ARB_texture_rectangle_test.cpp',
             '<(angle_path)/src/tests/compiler_tests/AtomicCounter_test.cpp',
             '<(angle_path)/src/tests/compiler_tests/BufferVariables_test.cpp',
             '<(angle_path)/src/tests/compiler_tests/CollectVariables_test.cpp',
diff --git a/src/tests/compiler_tests/ARB_texture_rectangle_test.cpp b/src/tests/compiler_tests/ARB_texture_rectangle_test.cpp
new file mode 100644
index 0000000..9b88375
--- /dev/null
+++ b/src/tests/compiler_tests/ARB_texture_rectangle_test.cpp
@@ -0,0 +1,97 @@
+//
+// Copyright (c) 2014 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.
+//
+// ARB_texture_rectangle_test.cpp:
+//   Test for the ARB_texture_rectangle extension
+//
+
+#include "GLSLANG/ShaderLang.h"
+#include "angle_gl.h"
+#include "gtest/gtest.h"
+#include "tests/test_utils/ShaderCompileTreeTest.h"
+
+using namespace sh;
+
+class ARBTextureRectangleTestNoExt : public ShaderCompileTreeTest
+{
+  protected:
+    ::GLenum getShaderType() const override { return GL_FRAGMENT_SHADER; }
+    ShShaderSpec getShaderSpec() const override { return SH_GLES3_SPEC; }
+};
+
+class ARBTextureRectangleTest : public ARBTextureRectangleTestNoExt
+{
+  protected:
+    void initResources(ShBuiltInResources *resources) override
+    {
+        resources->ARB_texture_rectangle = 1;
+    }
+};
+
+// Check that new types and builtins are disallowed if the extension isn't present in the translator
+// resources
+TEST_F(ARBTextureRectangleTest, NewTypeAndBuiltinsWithoutTranslatorResourceExtension)
+{
+    // The new builtins require Sampler2DRect so we can't test them independently.
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "uniform sampler2DRect tex;\n"
+        "void main() {\n"
+        "}\n";
+    ASSERT_TRUE(compile(shaderString));
+}
+
+// Check that new types and builtins are usable even with the #extension directive
+// Issue #15 of ARB_texture_rectangle explains that the extension was specified before the
+// #extension mechanism was in place so it doesn't require explicit enabling.
+TEST_F(ARBTextureRectangleTest, NewTypeAndBuiltinsWithoutExtensionDirective)
+{
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "uniform sampler2DRect tex;\n"
+        "void main() {\n"
+        "    vec4 color = texture2DRect(tex, vec2(1.0));"
+        "    color = texture2DRectProj(tex, vec3(1.0));"
+        "    color = texture2DRectProj(tex, vec4(1.0));"
+        "}\n";
+    ASSERT_TRUE(compile(shaderString));
+}
+
+// Test valid usage of the new types and builtins
+TEST_F(ARBTextureRectangleTest, NewTypeAndBuiltingsWithExtensionDirective)
+{
+    const std::string &shaderString =
+        "#extension GL_ARB_texture_rectangle : require\n"
+        "precision mediump float;\n"
+        "uniform sampler2DRect tex;\n"
+        "void main() {\n"
+        "    vec4 color = texture2DRect(tex, vec2(1.0));"
+        "    color = texture2DRectProj(tex, vec3(1.0));"
+        "    color = texture2DRectProj(tex, vec4(1.0));"
+        "}\n";
+    ASSERT_TRUE(compile(shaderString));
+}
+
+// Check that it is not possible to pass a sampler2DRect where sampler2D is expected, and vice versa
+TEST_F(ARBTextureRectangleTest, Rect2DVs2DMismatch)
+{
+    const std::string &shaderString1 =
+        "#extension GL_ARB_texture_rectangle : require\n"
+        "precision mediump float;\n"
+        "uniform sampler2DRect tex;\n"
+        "void main() {\n"
+        "    vec4 color = texture2D(tex, vec2(1.0));"
+        "}\n";
+    ASSERT_FALSE(compile(shaderString1));
+
+    const std::string &shaderString2 =
+        "#extension GL_ARB_texture_rectangle : require\n"
+        "precision mediump float;\n"
+        "uniform sampler2D tex;\n"
+        "void main() {\n"
+        "    vec4 color = texture2DRect(tex, vec2(1.0));"
+        "}\n";
+    ASSERT_FALSE(compile(shaderString2));
+}
diff --git a/src/tests/compiler_tests/VariablePacker_test.cpp b/src/tests/compiler_tests/VariablePacker_test.cpp
index dd9b0b5..bdbc379 100644
--- a/src/tests/compiler_tests/VariablePacker_test.cpp
+++ b/src/tests/compiler_tests/VariablePacker_test.cpp
@@ -10,48 +10,48 @@
 #include "compiler/translator/VariablePacker.h"
 
 static sh::GLenum types[] = {
-  GL_FLOAT_MAT4,            // 0
-  GL_FLOAT_MAT2,            // 1
-  GL_FLOAT_VEC4,            // 2
-  GL_INT_VEC4,              // 3
-  GL_BOOL_VEC4,             // 4
-  GL_FLOAT_MAT3,            // 5
-  GL_FLOAT_VEC3,            // 6
-  GL_INT_VEC3,              // 7
-  GL_BOOL_VEC3,             // 8
-  GL_FLOAT_VEC2,            // 9
-  GL_INT_VEC2,              // 10
-  GL_BOOL_VEC2,             // 11
-  GL_FLOAT,                 // 12
-  GL_INT,                   // 13
-  GL_BOOL,                  // 14
-  GL_SAMPLER_2D,            // 15
-  GL_SAMPLER_CUBE,          // 16
-  GL_SAMPLER_EXTERNAL_OES,  // 17
-  GL_SAMPLER_2D_RECT_ARB,   // 18
-  GL_UNSIGNED_INT,          // 19
-  GL_UNSIGNED_INT_VEC2,     // 20
-  GL_UNSIGNED_INT_VEC3,     // 21
-  GL_UNSIGNED_INT_VEC4,     // 22
-  GL_FLOAT_MAT2x3,          // 23
-  GL_FLOAT_MAT2x4,          // 24
-  GL_FLOAT_MAT3x2,          // 25
-  GL_FLOAT_MAT3x4,          // 26
-  GL_FLOAT_MAT4x2,          // 27
-  GL_FLOAT_MAT4x3,          // 28
-  GL_SAMPLER_3D,            // 29
-  GL_SAMPLER_2D_ARRAY,      // 30
-  GL_SAMPLER_2D_SHADOW,     // 31
-  GL_SAMPLER_CUBE_SHADOW,   // 32
-  GL_SAMPLER_2D_ARRAY_SHADOW, // 33
-  GL_INT_SAMPLER_2D,        // 34
-  GL_INT_SAMPLER_CUBE,      // 35
-  GL_INT_SAMPLER_3D,        // 36
-  GL_INT_SAMPLER_2D_ARRAY,  // 37
-  GL_UNSIGNED_INT_SAMPLER_2D, // 38
-  GL_UNSIGNED_INT_SAMPLER_CUBE, // 39
-  GL_UNSIGNED_INT_SAMPLER_3D, // 40
-  GL_UNSIGNED_INT_SAMPLER_2D_ARRAY, // 41
+    GL_FLOAT_MAT4,                     // 0
+    GL_FLOAT_MAT2,                     // 1
+    GL_FLOAT_VEC4,                     // 2
+    GL_INT_VEC4,                       // 3
+    GL_BOOL_VEC4,                      // 4
+    GL_FLOAT_MAT3,                     // 5
+    GL_FLOAT_VEC3,                     // 6
+    GL_INT_VEC3,                       // 7
+    GL_BOOL_VEC3,                      // 8
+    GL_FLOAT_VEC2,                     // 9
+    GL_INT_VEC2,                       // 10
+    GL_BOOL_VEC2,                      // 11
+    GL_FLOAT,                          // 12
+    GL_INT,                            // 13
+    GL_BOOL,                           // 14
+    GL_SAMPLER_2D,                     // 15
+    GL_SAMPLER_CUBE,                   // 16
+    GL_SAMPLER_EXTERNAL_OES,           // 17
+    GL_SAMPLER_2D_RECT_ANGLE,          // 18
+    GL_UNSIGNED_INT,                   // 19
+    GL_UNSIGNED_INT_VEC2,              // 20
+    GL_UNSIGNED_INT_VEC3,              // 21
+    GL_UNSIGNED_INT_VEC4,              // 22
+    GL_FLOAT_MAT2x3,                   // 23
+    GL_FLOAT_MAT2x4,                   // 24
+    GL_FLOAT_MAT3x2,                   // 25
+    GL_FLOAT_MAT3x4,                   // 26
+    GL_FLOAT_MAT4x2,                   // 27
+    GL_FLOAT_MAT4x3,                   // 28
+    GL_SAMPLER_3D,                     // 29
+    GL_SAMPLER_2D_ARRAY,               // 30
+    GL_SAMPLER_2D_SHADOW,              // 31
+    GL_SAMPLER_CUBE_SHADOW,            // 32
+    GL_SAMPLER_2D_ARRAY_SHADOW,        // 33
+    GL_INT_SAMPLER_2D,                 // 34
+    GL_INT_SAMPLER_CUBE,               // 35
+    GL_INT_SAMPLER_3D,                 // 36
+    GL_INT_SAMPLER_2D_ARRAY,           // 37
+    GL_UNSIGNED_INT_SAMPLER_2D,        // 38
+    GL_UNSIGNED_INT_SAMPLER_CUBE,      // 39
+    GL_UNSIGNED_INT_SAMPLER_3D,        // 40
+    GL_UNSIGNED_INT_SAMPLER_2D_ARRAY,  // 41
 };
 
 static sh::GLenum nonSqMatTypes[] = {
diff --git a/src/tests/gl_tests/TextureRectangleTest.cpp b/src/tests/gl_tests/TextureRectangleTest.cpp
new file mode 100644
index 0000000..7da0161
--- /dev/null
+++ b/src/tests/gl_tests/TextureRectangleTest.cpp
@@ -0,0 +1,398 @@
+//
+// Copyright 2017 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.
+//
+
+// TextureRectangleTest: Tests of GL_ANGLE_texture_rectangle
+
+#include "test_utils/ANGLETest.h"
+#include "test_utils/gl_raii.h"
+
+using namespace angle;
+
+namespace
+{
+
+class TextureRectangleTest : public ANGLETest
+{
+  protected:
+    TextureRectangleTest()
+    {
+        setWindowWidth(64);
+        setWindowHeight(64);
+        setConfigRedBits(8);
+        setConfigGreenBits(8);
+        setConfigBlueBits(8);
+        setConfigAlphaBits(8);
+    }
+
+    bool checkExtensionSupported() const
+    {
+        if (!extensionEnabled("GL_ANGLE_texture_rectangle"))
+        {
+            std::cout << "Test skipped because GL_ANGLE_texture_rectangle is not available."
+                      << std::endl;
+            return false;
+        }
+        return true;
+    }
+};
+
+class TextureRectangleTestES3 : public TextureRectangleTest
+{
+};
+
+class TextureRectangleTestES31 : public TextureRectangleTest
+{
+};
+
+// Test using TexImage2D to define a rectangle texture
+TEST_P(TextureRectangleTest, TexImage2D)
+{
+    ANGLE_SKIP_TEST_IF(!checkExtensionSupported());
+
+    GLTexture tex;
+    glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+
+    // Defining level 0 is allowed
+    glTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 nullptr);
+    ASSERT_GL_NO_ERROR();
+
+    // Defining level other than 0 is not allowed
+    glTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 1, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 nullptr);
+    ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+    GLint maxSize = 0;
+    glGetIntegerv(GL_MAX_RECTANGLE_TEXTURE_SIZE_ANGLE, &maxSize);
+
+    // Defining a texture of the max size is allowed
+    glTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_RGBA, maxSize, maxSize, 0, GL_RGBA,
+                 GL_UNSIGNED_BYTE, nullptr);
+    ASSERT_GL_NO_ERROR();
+
+    // Defining a texture of the max size is allowed
+    glTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_RGBA, maxSize + 1, maxSize, 0, GL_RGBA,
+                 GL_UNSIGNED_BYTE, nullptr);
+    ASSERT_GL_ERROR(GL_INVALID_VALUE);
+    glTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_RGBA, maxSize, maxSize + 1, 0, GL_RGBA,
+                 GL_UNSIGNED_BYTE, nullptr);
+    ASSERT_GL_ERROR(GL_INVALID_VALUE);
+}
+
+// Test using CompressedTexImage2D cannot be used on a retangle texture
+TEST_P(TextureRectangleTest, CompressedTexImage2DDisallowed)
+{
+    ANGLE_SKIP_TEST_IF(!checkExtensionSupported());
+    ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_texture_compression_dxt1"));
+
+    const char data[128] = {0};
+
+    // Control case: 2D texture
+    {
+        GLTexture tex;
+        glBindTexture(GL_TEXTURE_2D, tex);
+        glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 16, 16, 0, 128,
+                               data);
+        ASSERT_GL_NO_ERROR();
+    }
+
+    // Rectangle textures cannot be compressed
+    {
+        GLTexture tex;
+        glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+        glCompressedTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 16,
+                               16, 0, 128, data);
+        ASSERT_GL_ERROR(GL_INVALID_ENUM);
+    }
+}
+
+// Test using TexStorage2D to define a rectangle texture
+TEST_P(TextureRectangleTest, TexStorage2D)
+{
+    ANGLE_SKIP_TEST_IF(!checkExtensionSupported());
+    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && !extensionEnabled("GL_EXT_texture_storage"));
+
+    bool useES3       = getClientMajorVersion() >= 3;
+    auto TexStorage2D = [useES3](GLenum target, GLint levels, GLenum format, GLint width,
+                                 GLint height) {
+        if (useES3)
+        {
+            glTexStorage2D(target, levels, format, width, height);
+        }
+        else
+        {
+            glTexStorage2DEXT(target, levels, format, width, height);
+        }
+    };
+
+    // Defining one level is allowed
+    {
+        GLTexture tex;
+        glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+        TexStorage2D(GL_TEXTURE_RECTANGLE_ANGLE, 1, GL_RGBA8UI, 16, 16);
+        ASSERT_GL_NO_ERROR();
+    }
+
+    // Having more than one level is not allowed
+    {
+        GLTexture tex;
+        glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+        // Use 5 levels because the EXT_texture_storage extension requires a mip chain all the way
+        // to a 1x1 mip.
+        TexStorage2D(GL_TEXTURE_RECTANGLE_ANGLE, 5, GL_RGBA8UI, 16, 16);
+        ASSERT_GL_ERROR(GL_INVALID_VALUE);
+    }
+
+    GLint maxSize = 0;
+    glGetIntegerv(GL_MAX_RECTANGLE_TEXTURE_SIZE_ANGLE, &maxSize);
+
+    // Defining a texture of the max size is allowed
+    {
+        GLTexture tex;
+        glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+        TexStorage2D(GL_TEXTURE_RECTANGLE_ANGLE, 1, GL_RGBA8UI, maxSize, maxSize);
+        ASSERT_GL_NO_ERROR();
+    }
+
+    // Defining a texture of the max size is allowed
+    {
+        GLTexture tex;
+        glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+        TexStorage2D(GL_TEXTURE_RECTANGLE_ANGLE, 1, GL_RGBA8UI, maxSize + 1, maxSize);
+        ASSERT_GL_ERROR(GL_INVALID_VALUE);
+        TexStorage2D(GL_TEXTURE_RECTANGLE_ANGLE, 1, GL_RGBA8UI, maxSize, maxSize + 1);
+        ASSERT_GL_ERROR(GL_INVALID_VALUE);
+    }
+
+    // Compressed formats are disallowed
+    if (extensionEnabled("GL_EXT_texture_compression_dxt1"))
+    {
+        GLTexture tex;
+        glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+        TexStorage2D(GL_TEXTURE_RECTANGLE_ANGLE, 1, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 16, 16);
+        ASSERT_GL_ERROR(GL_INVALID_ENUM);
+    }
+}
+
+// Test validation of disallowed texture parameters
+TEST_P(TextureRectangleTest, TexParameterRestriction)
+{
+    ANGLE_SKIP_TEST_IF(!checkExtensionSupported());
+
+    GLTexture tex;
+    glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+
+    // Only wrap mode CLAMP_TO_EDGE is supported
+    // Wrap S
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    ASSERT_GL_NO_ERROR();
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_S, GL_REPEAT);
+    ASSERT_GL_ERROR(GL_INVALID_ENUM);
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
+    ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+    // Wrap T
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    ASSERT_GL_NO_ERROR();
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_T, GL_REPEAT);
+    ASSERT_GL_ERROR(GL_INVALID_ENUM);
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
+    ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+    // Min filter has to be nearest or linear
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    ASSERT_GL_NO_ERROR();
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
+    ASSERT_GL_ERROR(GL_INVALID_ENUM);
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
+    ASSERT_GL_ERROR(GL_INVALID_ENUM);
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+    ASSERT_GL_ERROR(GL_INVALID_ENUM);
+    glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+    ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+    // Base level has to be 0
+    if (getClientMajorVersion() >= 3)
+    {
+        glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_BASE_LEVEL, 0);
+        ASSERT_GL_NO_ERROR();
+        glTexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_BASE_LEVEL, 1);
+        ASSERT_GL_ERROR(GL_INVALID_OPERATION);
+    }
+}
+
+// Test validation of 'level' in GetTexParameter
+TEST_P(TextureRectangleTestES31, GetTexLevelParameter)
+{
+    ANGLE_SKIP_TEST_IF(!checkExtensionSupported());
+
+    GLTexture tex;
+    glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+    glTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 nullptr);
+    ASSERT_GL_NO_ERROR();
+
+    GLint param;
+    // Control case: level 0 is ok.
+    glGetTexLevelParameteriv(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_TEXTURE_INTERNAL_FORMAT, &param);
+    ASSERT_GL_NO_ERROR();
+
+    // Level 1 is not ok.
+    glGetTexLevelParameteriv(GL_TEXTURE_RECTANGLE_ANGLE, 1, GL_TEXTURE_INTERNAL_FORMAT, &param);
+    ASSERT_GL_ERROR(GL_INVALID_VALUE);
+}
+
+// Test validation of "level" in FramebufferTexture2D
+TEST_P(TextureRectangleTest, FramebufferTexture2DLevel)
+{
+    ANGLE_SKIP_TEST_IF(!checkExtensionSupported());
+
+    GLTexture tex;
+    glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+    glTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 nullptr);
+    ASSERT_GL_NO_ERROR();
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+    // Using level 0 of a rectangle texture is valid.
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ANGLE, tex,
+                           0);
+    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+    ASSERT_GL_NO_ERROR();
+
+    // Setting level != 0 is invalid
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ANGLE, tex,
+                           1);
+    ASSERT_GL_ERROR(GL_INVALID_VALUE);
+}
+
+// Test sampling from a rectangle texture
+TEST_P(TextureRectangleTest, SamplingFromRectangle)
+{
+    ANGLE_SKIP_TEST_IF(!checkExtensionSupported());
+
+    GLTexture tex;
+    glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+    glTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 &GLColor::green);
+
+    const std::string vs =
+        "attribute vec4 position;\n"
+        "void main()\n"
+        "{\n"
+        "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
+        "}\n";
+
+    const std::string fs =
+        "#extension GL_ARB_texture_rectangle : require\n"
+        "precision mediump float;\n"
+        "uniform sampler2DRect tex;\n"
+        "void main()\n"
+        "{\n"
+        "    gl_FragColor = texture2DRect(tex, vec2(0, 0));\n"
+        "}\n";
+
+    ANGLE_GL_PROGRAM(program, vs, fs);
+
+    glClearColor(0.0, 0.0, 0.0, 0.0);
+    glClear(GL_COLOR_BUFFER_BIT);
+    drawQuad(program, "position", 0.5f, 1.0f, false);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test attaching a rectangle texture and rendering to it.
+TEST_P(TextureRectangleTest, RenderToRectangle)
+{
+    ANGLE_SKIP_TEST_IF(!checkExtensionSupported());
+
+    GLTexture tex;
+    glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+    glTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 &GLColor::black);
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ANGLE, tex,
+                           0);
+    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+    ASSERT_GL_NO_ERROR();
+
+    // Clearing a texture is just as good as checking we can render to it, right?
+    glClearColor(0.0, 1.0, 0.0, 1.0);
+    glClear(GL_COLOR_BUFFER_BIT);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test glCopyTexImage with rectangle textures
+TEST_P(TextureRectangleTestES3, CopyTexImage)
+{
+    ANGLE_SKIP_TEST_IF(!checkExtensionSupported());
+
+    GLTexture tex;
+    glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+    glClearColor(0, 1, 0, 1);
+    glClear(GL_COLOR_BUFFER_BIT);
+    ASSERT_GL_NO_ERROR();
+
+    // Error case: level != 0
+    glCopyTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 1, GL_RGBA8, 0, 0, 1, 1, 0);
+    ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+    // level = 0 works and defines the texture.
+    glCopyTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_RGBA8, 0, 0, 1, 1, 0);
+    ASSERT_GL_NO_ERROR();
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ANGLE, tex,
+                           0);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+    ASSERT_GL_NO_ERROR();
+}
+
+// Test glCopyTexSubImage with rectangle textures
+TEST_P(TextureRectangleTestES3, CopyTexSubImage)
+{
+    ANGLE_SKIP_TEST_IF(!checkExtensionSupported());
+
+    GLTexture tex;
+    glBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, tex);
+    glTexImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 &GLColor::black);
+
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+    glClearColor(0, 1, 0, 1);
+    glClear(GL_COLOR_BUFFER_BIT);
+    ASSERT_GL_NO_ERROR();
+
+    // Error case: level != 0
+    glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 1, 0, 0, 0, 0, 1, 1);
+    ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+    // level = 0 works and defines the texture.
+    glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ANGLE, 0, 0, 0, 0, 0, 1, 1);
+    ASSERT_GL_NO_ERROR();
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ANGLE, tex,
+                           0);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+    ASSERT_GL_NO_ERROR();
+}
+
+ANGLE_INSTANTIATE_TEST(TextureRectangleTest, ES2_OPENGL(), ES3_OPENGL());
+ANGLE_INSTANTIATE_TEST(TextureRectangleTestES3, ES3_OPENGL());
+ANGLE_INSTANTIATE_TEST(TextureRectangleTestES31, ES31_OPENGL());
+} // anonymous namespace