[GLESv3] Texture state tracking upgrades

- TextureUtils to get the low down on all offset/size related calculations
(ripped from SwiftShader)
    - This involves track of all PBO fields
- Move TextureRec and friends to GLSharedGroup
- Implement new encoder utils to use in encoder

Change-Id: Ic6585f49d2928fe965181ec706d5fefa8713e0eb
diff --git a/shared/OpenglCodecCommon/Android.mk b/shared/OpenglCodecCommon/Android.mk
index e6aee8a..b9cf6e5 100644
--- a/shared/OpenglCodecCommon/Android.mk
+++ b/shared/OpenglCodecCommon/Android.mk
@@ -5,6 +5,7 @@
 
 commonSources := \
         GLClientState.cpp \
+        GLESTextureUtils.cpp \
         ChecksumCalculator.cpp \
         GLSharedGroup.cpp \
         glUtils.cpp \
diff --git a/shared/OpenglCodecCommon/GLClientState.cpp b/shared/OpenglCodecCommon/GLClientState.cpp
index ac97fcf..a4d81ea 100644
--- a/shared/OpenglCodecCommon/GLClientState.cpp
+++ b/shared/OpenglCodecCommon/GLClientState.cpp
@@ -14,6 +14,7 @@
 * limitations under the License.
 */
 #include "GLClientState.h"
+#include "GLESTextureUtils.h"
 #include "ErrorLog.h"
 #include <stdio.h>
 #include <stdlib.h>
@@ -25,6 +26,9 @@
 #define MAX(a, b) ((a) < (b) ? (b) : (a))
 #endif
 
+// Don't include these in the .h file, or we get weird compile errors.
+#include <GLES3/gl3.h>
+#include <GLES3/gl31.h>
 GLClientState::GLClientState(int nLocations)
 {
     if (nLocations < LAST_LOCATION) {
@@ -60,11 +64,19 @@
     m_pixelStore.unpack_alignment = 4;
     m_pixelStore.pack_alignment = 4;
 
+    m_pixelStore.unpack_row_length = 0;
+    m_pixelStore.unpack_image_height = 0;
+    m_pixelStore.unpack_skip_pixels = 0;
+    m_pixelStore.unpack_skip_rows = 0;
+    m_pixelStore.unpack_skip_images = 0;
+
+    m_pixelStore.pack_row_length = 0;
+    m_pixelStore.pack_skip_pixels = 0;
+    m_pixelStore.pack_skip_rows = 0;
+
     memset(m_tex.unit, 0, sizeof(m_tex.unit));
     m_tex.activeUnit = &m_tex.unit[0];
-    m_tex.textures = NULL;
-    m_tex.numTextures = 0;
-    m_tex.allocTextures = 0;
+    m_tex.textureRecs = NULL;
 
     mRboState.boundRenderbuffer = 0;
     mRboState.boundRenderbufferIndex = 0;
@@ -212,46 +224,138 @@
     int retval = 0;
     switch(param) {
     case GL_UNPACK_ALIGNMENT:
-        if (value == 1 || value == 2 || value == 4 || value == 8) {
-            m_pixelStore.unpack_alignment = value;
-        } else {
-            retval =  GL_INVALID_VALUE;
-        }
+        m_pixelStore.unpack_alignment = value;
         break;
     case GL_PACK_ALIGNMENT:
-        if (value == 1 || value == 2 || value == 4 || value == 8) {
-            m_pixelStore.pack_alignment = value;
-        } else {
-            retval =  GL_INVALID_VALUE;
-        }
+        m_pixelStore.pack_alignment = value;
         break;
-        default:
-            retval = GL_INVALID_ENUM;
+    case GL_UNPACK_ROW_LENGTH:
+        m_pixelStore.unpack_row_length = value;
+        break;
+    case GL_UNPACK_IMAGE_HEIGHT:
+        m_pixelStore.unpack_image_height = value;
+        break;
+    case GL_UNPACK_SKIP_PIXELS:
+        m_pixelStore.unpack_skip_pixels = value;
+        break;
+    case GL_UNPACK_SKIP_ROWS:
+        m_pixelStore.unpack_skip_rows = value;
+        break;
+    case GL_UNPACK_SKIP_IMAGES:
+        m_pixelStore.unpack_skip_images = value;
+        break;
+    case GL_PACK_ROW_LENGTH:
+        m_pixelStore.pack_row_length = value;
+        break;
+    case GL_PACK_SKIP_PIXELS:
+        m_pixelStore.pack_skip_pixels = value;
+        break;
+    case GL_PACK_SKIP_ROWS:
+        m_pixelStore.pack_skip_rows = value;
+        break;
+    default:
+        retval = GL_INVALID_ENUM;
     }
     return retval;
 }
 
 
-
-
-size_t GLClientState::pixelDataSize(GLsizei width, GLsizei height, GLenum format, GLenum type, int pack) const
+size_t GLClientState::pixelDataSize(GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, int pack) const
 {
-    if (width <= 0 || height <= 0) return 0;
+    if (width <= 0 || height <= 0 || depth <= 0) return 0;
 
-    int pixelsize = glUtilsPixelBitSize(format, type) >> 3;
-
-    int alignment = pack ? m_pixelStore.pack_alignment : m_pixelStore.unpack_alignment;
-
-    if (pixelsize == 0 ) {
-        ERR("unknown pixel size: width: %d height: %d format: %d type: %d pack: %d align: %d\n",
-             width, height, format, type, pack, alignment);
+    ALOGV("%s: pack? %d", __FUNCTION__, pack);
+    if (pack) {
+        ALOGV("%s: pack stats", __FUNCTION__);
+        ALOGV("%s: pack align %d", __FUNCTION__, m_pixelStore.pack_alignment);
+        ALOGV("%s: pack rowlen %d", __FUNCTION__, m_pixelStore.pack_row_length);
+        ALOGV("%s: pack skippixels %d", __FUNCTION__, m_pixelStore.pack_skip_pixels);
+        ALOGV("%s: pack skiprows %d", __FUNCTION__, m_pixelStore.pack_skip_rows);
+    } else {
+        ALOGV("%s: unpack stats", __FUNCTION__);
+        ALOGV("%s: unpack align %d", __FUNCTION__, m_pixelStore.unpack_alignment);
+        ALOGV("%s: unpack rowlen %d", __FUNCTION__, m_pixelStore.unpack_row_length);
+        ALOGV("%s: unpack imgheight %d", __FUNCTION__, m_pixelStore.unpack_image_height);
+        ALOGV("%s: unpack skippixels %d", __FUNCTION__, m_pixelStore.unpack_skip_pixels);
+        ALOGV("%s: unpack skiprows %d", __FUNCTION__, m_pixelStore.unpack_skip_rows);
+        ALOGV("%s: unpack skipimages %d", __FUNCTION__, m_pixelStore.unpack_skip_images);
     }
-    size_t linesize = pixelsize * width;
-    size_t aligned_linesize = int(linesize / alignment) * alignment;
-    if (aligned_linesize < linesize) {
-        aligned_linesize += alignment;
+    return GLESTextureUtils::computeTotalImageSize(
+            width, height, depth,
+            format, type,
+            pack ? m_pixelStore.pack_alignment : m_pixelStore.unpack_alignment,
+            pack ? m_pixelStore.pack_row_length : m_pixelStore.unpack_row_length,
+            pack ? 0 : m_pixelStore.unpack_image_height,
+            pack ? m_pixelStore.pack_skip_pixels : m_pixelStore.unpack_skip_pixels,
+            pack ? m_pixelStore.pack_skip_rows : m_pixelStore.unpack_skip_rows,
+            pack ? 0 : m_pixelStore.unpack_skip_images);
+}
+
+size_t GLClientState::pboNeededDataSize(GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, int pack) const
+{
+    if (width <= 0 || height <= 0 || depth <= 0) return 0;
+
+    ALOGV("%s: pack? %d", __FUNCTION__, pack);
+    if (pack) {
+        ALOGV("%s: pack stats", __FUNCTION__);
+        ALOGV("%s: pack align %d", __FUNCTION__, m_pixelStore.pack_alignment);
+        ALOGV("%s: pack rowlen %d", __FUNCTION__, m_pixelStore.pack_row_length);
+        ALOGV("%s: pack skippixels %d", __FUNCTION__, m_pixelStore.pack_skip_pixels);
+        ALOGV("%s: pack skiprows %d", __FUNCTION__, m_pixelStore.pack_skip_rows);
+    } else {
+        ALOGV("%s: unpack stats", __FUNCTION__);
+        ALOGV("%s: unpack align %d", __FUNCTION__, m_pixelStore.unpack_alignment);
+        ALOGV("%s: unpack rowlen %d", __FUNCTION__, m_pixelStore.unpack_row_length);
+        ALOGV("%s: unpack imgheight %d", __FUNCTION__, m_pixelStore.unpack_image_height);
+        ALOGV("%s: unpack skippixels %d", __FUNCTION__, m_pixelStore.unpack_skip_pixels);
+        ALOGV("%s: unpack skiprows %d", __FUNCTION__, m_pixelStore.unpack_skip_rows);
+        ALOGV("%s: unpack skipimages %d", __FUNCTION__, m_pixelStore.unpack_skip_images);
     }
-    return aligned_linesize * height;
+    return GLESTextureUtils::computeNeededBufferSize(
+            width, height, depth,
+            format, type,
+            pack ? m_pixelStore.pack_alignment : m_pixelStore.unpack_alignment,
+            pack ? m_pixelStore.pack_row_length : m_pixelStore.unpack_row_length,
+            pack ? 0 : m_pixelStore.unpack_image_height,
+            pack ? m_pixelStore.pack_skip_pixels : m_pixelStore.unpack_skip_pixels,
+            pack ? m_pixelStore.pack_skip_rows : m_pixelStore.unpack_skip_rows,
+            pack ? 0 : m_pixelStore.unpack_skip_images);
+}
+
+
+size_t GLClientState::clearBufferNumElts(GLenum buffer) const
+{
+    switch (buffer) {
+    case GL_COLOR:
+        return 4;
+    case GL_DEPTH:
+    case GL_STENCIL:
+        return 1;
+    }
+    return 1;
+}
+
+void GLClientState::setNumActiveUniformsInUniformBlock(GLuint program, GLuint uniformBlockIndex, GLint numActiveUniforms) {
+    UniformBlockInfoKey key;
+    key.program = program;
+    key.uniformBlockIndex = uniformBlockIndex;
+
+    UniformBlockUniformInfo info;
+    info.numActiveUniforms = (size_t)numActiveUniforms;
+
+    m_uniformBlockInfoMap[key] = info;
+}
+
+size_t GLClientState::numActiveUniformsInUniformBlock(GLuint program, GLuint uniformBlockIndex) const {
+    UniformBlockInfoKey key;
+    key.program = program;
+    key.uniformBlockIndex = uniformBlockIndex;
+    UniformBlockInfoMap::const_iterator it =
+        m_uniformBlockInfoMap.find(key);
+    if (it == m_uniformBlockInfoMap.end()) return 0;
+    return it->second.numActiveUniforms;
+}
+
 }
 
 GLenum GLClientState::setActiveTextureUnit(GLenum texture)
@@ -315,22 +419,18 @@
 GLenum GLClientState::bindTexture(GLenum target, GLuint texture,
         GLboolean* firstUse)
 {
+    assert(m_tex.textureRecs);
     GLboolean first = GL_FALSE;
-    TextureRec* texrec = NULL;
-    if (texture != 0) {
-        if (m_tex.textures) {
-            texrec = (TextureRec*)bsearch(&texture, m_tex.textures,
-                    m_tex.numTextures, sizeof(TextureRec), compareTexId);
-        }
-        if (!texrec) {
-            if (!(texrec = addTextureRec(texture, target))) {
-                return GL_OUT_OF_MEMORY;
-            }
-            first = GL_TRUE;
-        }
-        if (target != texrec->target) {
-            return GL_INVALID_OPERATION;
-        }
+
+    TextureRec* texrec = getTextureRec(texture);
+    if (!texrec) {
+        texrec = addTextureRec(texture, target);
+    }
+
+    if (texture && target != texrec->target &&
+        (target != GL_TEXTURE_EXTERNAL_OES &&
+         texrec->target != GL_TEXTURE_EXTERNAL_OES)) {
+        ALOGD("%s: issue GL_INVALID_OPERATION: target 0x%x texrectarget 0x%x texture %u", __FUNCTION__, target, texrec->target, texture);
     }
 
     switch (target) {
@@ -340,6 +440,18 @@
     case GL_TEXTURE_EXTERNAL_OES:
         m_tex.activeUnit->texture[TEXTURE_EXTERNAL] = texture;
         break;
+    case GL_TEXTURE_CUBE_MAP:
+        m_tex.activeUnit->texture[TEXTURE_CUBE_MAP] = texture;
+        break;
+    case GL_TEXTURE_2D_ARRAY:
+        m_tex.activeUnit->texture[TEXTURE_2D_ARRAY] = texture;
+        break;
+    case GL_TEXTURE_3D:
+        m_tex.activeUnit->texture[TEXTURE_3D] = texture;
+        break;
+    case GL_TEXTURE_2D_MULTISAMPLE:
+        m_tex.activeUnit->texture[TEXTURE_2D_MULTISAMPLE] = texture;
+        break;
     }
 
     if (firstUse) {
@@ -349,78 +461,117 @@
     return GL_NO_ERROR;
 }
 
-GLClientState::TextureRec* GLClientState::addTextureRec(GLuint id,
-        GLenum target)
+void GLClientState::setBoundEGLImage(GLenum target, GLeglImageOES image) {
+    GLuint texture = getBoundTexture(target);
+    TextureRec* texrec = getTextureRec(texture);
+    if (!texrec) return;
+    texrec->boundEGLImage = true;
+}
+
+TextureRec* GLClientState::addTextureRec(GLuint id, GLenum target)
 {
-    if (m_tex.numTextures == m_tex.allocTextures) {
-        const GLuint MAX_TEXTURES = 0xFFFFFFFFu;
-
-        GLuint newAlloc;
-        if (MAX_TEXTURES - m_tex.allocTextures >= m_tex.allocTextures) {
-            newAlloc = MAX(4, 2 * m_tex.allocTextures);
-        } else {
-            if (m_tex.allocTextures == MAX_TEXTURES) {
-                return NULL;
-            }
-            newAlloc = MAX_TEXTURES;
-        }
-
-        TextureRec* newTextures = (TextureRec*)realloc(m_tex.textures,
-                newAlloc * sizeof(TextureRec));
-        if (!newTextures) {
-            return NULL;
-        }
-
-        m_tex.textures = newTextures;
-        m_tex.allocTextures = newAlloc;
-    }
-
-    TextureRec* tex = m_tex.textures + m_tex.numTextures;
-    TextureRec* prev = tex - 1;
-    while (tex != m_tex.textures && id < prev->id) {
-        *tex-- = *prev--;
-    }
+    TextureRec* tex = new TextureRec;
     tex->id = id;
     tex->target = target;
     tex->format = -1;
-    m_tex.numTextures++;
+    tex->multisamples = 0;
+    tex->immutable = false;
+    tex->boundEGLImage = false;
+    tex->dims = new TextureDims;
 
+    (*(m_tex.textureRecs))[id] = tex;
     return tex;
 }
 
+TextureRec* GLClientState::getTextureRec(GLuint id) const {
+    SharedTextureDataMap::const_iterator it =
+        m_tex.textureRecs->find(id);
+    if (it == m_tex.textureRecs->end()) {
+        return NULL;
+    }
+    return it->second;
+}
+
 void GLClientState::setBoundTextureInternalFormat(GLenum target, GLint internalformat) {
     GLuint texture = getBoundTexture(target);
-    TextureRec* texrec = NULL;
-    texrec = (TextureRec*)bsearch(&texture, m_tex.textures,
-                                  m_tex.numTextures,
-                                  sizeof(TextureRec),
-                                  compareTexId);
+    TextureRec* texrec = getTextureRec(texture);
     if (!texrec) return;
     texrec->internalformat = internalformat;
 }
 
 void GLClientState::setBoundTextureFormat(GLenum target, GLenum format) {
     GLuint texture = getBoundTexture(target);
-    TextureRec* texrec = NULL;
-    texrec = (TextureRec*)bsearch(&texture, m_tex.textures,
-                                  m_tex.numTextures,
-                                  sizeof(TextureRec),
-                                  compareTexId);
+    TextureRec* texrec = getTextureRec(texture);
     if (!texrec) return;
     texrec->format = format;
 }
 
 void GLClientState::setBoundTextureType(GLenum target, GLenum type) {
     GLuint texture = getBoundTexture(target);
-    TextureRec* texrec = NULL;
-    texrec = (TextureRec*)bsearch(&texture, m_tex.textures,
-                                  m_tex.numTextures,
-                                  sizeof(TextureRec),
-                                  compareTexId);
+    TextureRec* texrec = getTextureRec(texture);
     if (!texrec) return;
     texrec->type = type;
 }
 
+void GLClientState::setBoundTextureDims(GLenum target, GLsizei level, GLsizei width, GLsizei height, GLsizei depth) {
+    GLuint texture = getBoundTexture(target);
+    TextureRec* texrec = getTextureRec(texture);
+    if (!texrec) {
+        return;
+    }
+
+    if (level == -1) {
+        GLsizei curr_width = width;
+        GLsizei curr_height = height;
+        GLsizei curr_depth = depth;
+        GLsizei curr_level = 0;
+
+        while (true) {
+            texrec->dims->widths[curr_level] = curr_width;
+            texrec->dims->heights[curr_level] = curr_height;
+            texrec->dims->depths[curr_level] = curr_depth;
+            if (curr_width >> 1 == 0 &&
+                curr_height >> 1 == 0 &&
+                ((target == GL_TEXTURE_3D && curr_depth == 0) ||
+                 true)) {
+                break;
+            }
+            curr_width = (curr_width >> 1) ? (curr_width >> 1) : 1;
+            curr_height = (curr_height >> 1) ? (curr_height >> 1) : 1;
+            if (target == GL_TEXTURE_3D) {
+                curr_depth = (curr_depth >> 1) ? (curr_depth >> 1) : 1;
+            }
+            curr_level++;
+        }
+
+    } else {
+        texrec->dims->widths[level] = width;
+        texrec->dims->heights[level] = height;
+        texrec->dims->depths[level] = depth;
+    }
+}
+
+void GLClientState::setBoundTextureSamples(GLenum target, GLsizei samples) {
+    GLuint texture = getBoundTexture(target);
+    TextureRec* texrec = getTextureRec(texture);
+    if (!texrec) return;
+    texrec->multisamples = samples;
+}
+
+void GLClientState::setBoundTextureImmutableFormat(GLenum target) {
+    GLuint texture = getBoundTexture(target);
+    TextureRec* texrec = getTextureRec(texture);
+    if (!texrec) return;
+    texrec->immutable = true;
+}
+
+bool GLClientState::isBoundTextureImmutableFormat(GLenum target) const {
+    GLuint texture = getBoundTexture(target);
+    TextureRec* texrec = getTextureRec(texture);
+    if (!texrec) return false;
+    return texrec->immutable;
+}
+
 GLuint GLClientState::getBoundTexture(GLenum target) const
 {
     switch (target) {
@@ -428,6 +579,14 @@
         return m_tex.activeUnit->texture[TEXTURE_2D];
     case GL_TEXTURE_EXTERNAL_OES:
         return m_tex.activeUnit->texture[TEXTURE_EXTERNAL];
+    case GL_TEXTURE_CUBE_MAP:
+        return m_tex.activeUnit->texture[TEXTURE_CUBE_MAP];
+    case GL_TEXTURE_2D_ARRAY:
+        return m_tex.activeUnit->texture[TEXTURE_2D_ARRAY];
+    case GL_TEXTURE_3D:
+        return m_tex.activeUnit->texture[TEXTURE_3D];
+    case GL_TEXTURE_2D_MULTISAMPLE:
+        return m_tex.activeUnit->texture[TEXTURE_2D_MULTISAMPLE];
     default:
         return 0;
     }
@@ -508,14 +667,13 @@
     // - could swap deleted textures to the end and re-sort.
     TextureRec* texrec;
     for (const GLuint* texture = textures; texture != textures + n; texture++) {
-        texrec = (TextureRec*)bsearch(texture, m_tex.textures,
-                m_tex.numTextures, sizeof(TextureRec), compareTexId);
+        texrec = getTextureRec(*texture);
+        if (texrec && texrec->dims) {
+            delete texrec->dims;
+        }
         if (texrec) {
-            const TextureRec* end = m_tex.textures + m_tex.numTextures;
-            memmove(texrec, texrec + 1,
-                    (end - texrec - 1) * sizeof(TextureRec));
-            m_tex.numTextures--;
-
+            m_tex.textureRecs->erase(*texture);
+            delete texrec;
             for (TextureUnit* unit = m_tex.unit;
                  unit != m_tex.unit + MAX_TEXTURE_UNITS;
                  unit++)
@@ -632,29 +790,61 @@
 }
 
 GLint GLClientState::queryTexInternalFormat(GLuint tex_name) const {
-    TextureRec* texrec = NULL;
-    texrec = (TextureRec*)bsearch(&tex_name, m_tex.textures,
-                                  m_tex.numTextures, sizeof(TextureRec), compareTexId);
+    TextureRec* texrec = getTextureRec(tex_name);
     if (!texrec) return -1;
     return texrec->internalformat;
 }
 
+GLsizei GLClientState::queryTexWidth(GLsizei level, GLuint tex_name) const {
+    TextureRec* texrec = getTextureRec(tex_name);
+    if (!texrec) {
+        return 0;
+    }
+    return texrec->dims->widths[level];
+}
+
+GLsizei GLClientState::queryTexHeight(GLsizei level, GLuint tex_name) const {
+    TextureRec* texrec = getTextureRec(tex_name);
+    if (!texrec) return 0;
+    return texrec->dims->heights[level];
+}
+
+GLsizei GLClientState::queryTexDepth(GLsizei level, GLuint tex_name) const {
+    TextureRec* texrec = getTextureRec(tex_name);
+    if (!texrec) return 0;
+    return texrec->dims->depths[level];
+}
+
+bool GLClientState::queryTexEGLImageBacked(GLuint tex_name) const {
+    TextureRec* texrec = getTextureRec(tex_name);
+    if (!texrec) return false;
+    return texrec->boundEGLImage;
+}
+
 GLenum GLClientState::queryTexFormat(GLuint tex_name) const {
-    TextureRec* texrec = NULL;
-    texrec = (TextureRec*)bsearch(&tex_name, m_tex.textures,
-                                  m_tex.numTextures, sizeof(TextureRec), compareTexId);
+    TextureRec* texrec = getTextureRec(tex_name);
     if (!texrec) return -1;
     return texrec->format;
 }
 
 GLenum GLClientState::queryTexType(GLuint tex_name) const {
-    TextureRec* texrec = NULL;
-    texrec = (TextureRec*)bsearch(&tex_name, m_tex.textures,
-                                  m_tex.numTextures, sizeof(TextureRec), compareTexId);
+    TextureRec* texrec = getTextureRec(tex_name);
     if (!texrec) return -1;
     return texrec->type;
 }
 
+GLsizei GLClientState::queryTexSamples(GLuint tex_name) const {
+    TextureRec* texrec = getTextureRec(tex_name);
+    if (!texrec) return 0;
+    return texrec->multisamples;
+}
+
+GLenum GLClientState::queryTexLastBoundTarget(GLuint tex_name) const {
+    TextureRec* texrec = getTextureRec(tex_name);
+    if (!texrec) return GL_NONE;
+    return texrec->target;
+}
+
 void GLClientState::getBoundFramebufferFormat(
         GLenum attachment, FboFormatInfo* res_info) const {
     const FboProps& props = boundFboProps_const();
diff --git a/shared/OpenglCodecCommon/GLClientState.h b/shared/OpenglCodecCommon/GLClientState.h
index 9eee2f1..e1fb64d 100644
--- a/shared/OpenglCodecCommon/GLClientState.h
+++ b/shared/OpenglCodecCommon/GLClientState.h
@@ -22,6 +22,8 @@
 #define GL_APIENTRYP
 #endif
 
+#include "TextureSharedData.h"
+
 #include <GLES/gl.h>
 #include <GLES/glext.h>
 #include <GLES2/gl2.h>
@@ -33,6 +35,7 @@
 #include "codec_defs.h"
 
 #include <vector>
+#include <map>
 #include <set>
 
 // Tracking framebuffer objects:
@@ -83,6 +86,7 @@
     GLint tex_internalformat;
     GLenum tex_format;
     GLenum tex_type;
+    GLsizei tex_multisamples;
 };
 
 class GLClientState {
@@ -120,11 +124,22 @@
 
     typedef struct {
         int unpack_alignment;
+
+        int unpack_row_length;
+        int unpack_image_height;
+        int unpack_skip_pixels;
+        int unpack_skip_rows;
+        int unpack_skip_images;
+
         int pack_alignment;
+
+        int pack_row_length;
+        int pack_skip_pixels;
+        int pack_skip_rows;
     } PixelStoreState;
 
     enum {
-        MAX_TEXTURE_UNITS = 32,
+        MAX_TEXTURE_UNITS = 256,
     };
 
 public:
@@ -185,7 +200,9 @@
       }
       return ret;
     }
-    size_t pixelDataSize(GLsizei width, GLsizei height, GLenum format, GLenum type, int pack) const;
+    size_t pixelDataSize(GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, int pack) const;
+    size_t pboNeededDataSize(GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, int pack) const;
+    size_t clearBufferNumElts(GLenum buffer) const;
 
     void setCurrentProgram(GLint program) { m_currentProgram = program; }
     GLint currentProgram() const { return m_currentProgram; }
@@ -232,9 +249,18 @@
     // For accurate error detection, bindTexture should be called for *all*
     // targets, not just 2D and EXTERNAL_OES.
     GLenum bindTexture(GLenum target, GLuint texture, GLboolean* firstUse);
+    void setBoundEGLImage(GLenum target, GLeglImageOES image);
 
     // Return the texture currently bound to GL_TEXTURE_(2D|EXTERNAL_OES).
     GLuint getBoundTexture(GLenum target) const;
+    // Other publicly-visible texture queries
+    GLenum queryTexLastBoundTarget(GLuint name) const;
+    GLenum queryTexFormat(GLuint name) const;
+    GLint queryTexInternalFormat(GLuint name) const;
+    GLsizei queryTexWidth(GLsizei level, GLuint name) const;
+    GLsizei queryTexHeight(GLsizei level, GLuint name) const;
+    GLsizei queryTexDepth(GLsizei level, GLuint name) const;
+    bool queryTexEGLImageBacked(GLuint name) const;
 
     // For AMD GPUs, it is easy for the emulator to segfault
     // (esp. in dEQP) when a cube map is defined using glCopyTexImage2D
@@ -253,6 +279,12 @@
     void setBoundTextureInternalFormat(GLenum target, GLint format);
     void setBoundTextureFormat(GLenum target, GLenum format);
     void setBoundTextureType(GLenum target, GLenum type);
+    void setBoundTextureDims(GLenum target, GLsizei level, GLsizei width, GLsizei height, GLsizei depth);
+    void setBoundTextureSamples(GLenum target, GLsizei samples);
+
+    // glTexStorage2D disallows any change in texture format after it is set for a particular texture.
+    void setBoundTextureImmutableFormat(GLenum target);
+    bool isBoundTextureImmutableFormat(GLenum target) const;
 
     // glDeleteTextures(...)
     // Remove references to the to-be-deleted textures.
@@ -286,6 +318,7 @@
     // FBO attachments in general
     bool attachmentHasObject(GLenum attachment) const;
 
+    void setTextureData(SharedTextureDataMap* sharedTexData);
     // set eglsurface property on default framebuffer
     // if coming from eglMakeCurrent
     void fromMakeCurrent();
@@ -299,6 +332,8 @@
 private:
     PixelStoreState m_pixelStore;
     VertexAttribState *m_states;
+    int m_glesMajorVersion;
+    int m_glesMinorVersion;
     int m_maxVertexAttribs;
     bool m_maxVertexAttribsDirty;
     int m_nLocations;
@@ -312,25 +347,21 @@
     enum TextureTarget {
         TEXTURE_2D = 0,
         TEXTURE_EXTERNAL = 1,
+        TEXTURE_CUBE_MAP = 2,
+        TEXTURE_2D_ARRAY = 3,
+        TEXTURE_3D = 4,
+        TEXTURE_2D_MULTISAMPLE = 5,
         TEXTURE_TARGET_COUNT
     };
     struct TextureUnit {
         unsigned int enables;
         GLuint texture[TEXTURE_TARGET_COUNT];
     };
-    struct TextureRec {
-        GLuint id;
-        GLenum target;
-        GLint internalformat;
-        GLenum format;
-        GLenum type;
-    };
     struct TextureState {
         TextureUnit unit[MAX_TEXTURE_UNITS];
         TextureUnit* activeUnit;
-        TextureRec* textures;
-        GLuint numTextures;
-        GLuint allocTextures;
+        // Initialized from shared group.
+        SharedTextureDataMap* textureRecs;
     };
     TextureState m_tex;
 
@@ -388,12 +419,12 @@
 
     // Querying framebuffer format
     GLenum queryRboFormat(GLuint name) const;
-    GLint queryTexInternalFormat(GLuint name) const;
-    GLenum queryTexFormat(GLuint name) const;
     GLenum queryTexType(GLuint name) const;
+    GLsizei queryTexSamples(GLuint name) const;
 
     static int compareTexId(const void* pid, const void* prec);
     TextureRec* addTextureRec(GLuint id, GLenum target);
+    TextureRec* getTextureRec(GLuint id) const;
 
 public:
     void getClientStatePointer(GLenum pname, GLvoid** params);
diff --git a/shared/OpenglCodecCommon/GLESTextureUtils.cpp b/shared/OpenglCodecCommon/GLESTextureUtils.cpp
new file mode 100644
index 0000000..1aef8cb
--- /dev/null
+++ b/shared/OpenglCodecCommon/GLESTextureUtils.cpp
@@ -0,0 +1,287 @@
+#include "GLESTextureUtils.h"
+
+#include "glUtils.h"
+
+#include <cutils/log.h>
+
+namespace GLESTextureUtils {
+
+// Based on computations in
+// https://swiftshader.googlesource.com/SwiftShader/+/master/src/OpenGL/common/Image.cpp
+// such as Image::loadImageData,
+// ComputePitch/ComputePackingOffset
+
+#define HIGHEST_MULTIPLE_OF(align, x) \
+    (( ( x ) + ( align ) - 1) & ~( ( align ) - 1)) \
+
+static int computePixelSize(GLenum format, GLenum type) {
+
+#define FORMAT_ERROR(format, type) \
+    ALOGE("%s:%d unknown format/type 0x%x 0x%x", __FUNCTION__, __LINE__, format, type) \
+
+    switch(type) {
+    case GL_BYTE:
+        switch(format) {
+        case GL_R8:
+        case GL_R8I:
+        case GL_R8_SNORM:
+        case GL_RED:             return sizeof(char);
+        case GL_RED_INTEGER:     return sizeof(char);
+        case GL_RG8:
+        case GL_RG8I:
+        case GL_RG8_SNORM:
+        case GL_RG:              return sizeof(char) * 2;
+        case GL_RG_INTEGER:      return sizeof(char) * 2;
+        case GL_RGB8:
+        case GL_RGB8I:
+        case GL_RGB8_SNORM:
+        case GL_RGB:             return sizeof(char) * 3;
+        case GL_RGB_INTEGER:     return sizeof(char) * 3;
+        case GL_RGBA8:
+        case GL_RGBA8I:
+        case GL_RGBA8_SNORM:
+        case GL_RGBA:            return sizeof(char) * 4;
+        case GL_RGBA_INTEGER:    return sizeof(char) * 4;
+        default: FORMAT_ERROR(format, type);
+        }
+        break;
+    case GL_UNSIGNED_BYTE:
+        switch(format) {
+        case GL_R8:
+        case GL_R8UI:
+        case GL_RED:             return sizeof(unsigned char);
+        case GL_RED_INTEGER:     return sizeof(unsigned char);
+        case GL_ALPHA8_EXT:
+        case GL_ALPHA:           return sizeof(unsigned char);
+        case GL_LUMINANCE8_EXT:
+        case GL_LUMINANCE:       return sizeof(unsigned char);
+        case GL_LUMINANCE8_ALPHA8_EXT:
+        case GL_LUMINANCE_ALPHA: return sizeof(unsigned char) * 2;
+        case GL_RG8:
+        case GL_RG8UI:
+        case GL_RG:              return sizeof(unsigned char) * 2;
+        case GL_RG_INTEGER:      return sizeof(unsigned char) * 2;
+        case GL_RGB8:
+        case GL_RGB8UI:
+        case GL_SRGB8:
+        case GL_RGB:             return sizeof(unsigned char) * 3;
+        case GL_RGB_INTEGER:     return sizeof(unsigned char) * 3;
+        case GL_RGBA8:
+        case GL_RGBA8UI:
+        case GL_SRGB8_ALPHA8:
+        case GL_RGBA:            return sizeof(unsigned char) * 4;
+        case GL_RGBA_INTEGER:    return sizeof(unsigned char) * 4;
+        case GL_BGRA_EXT:
+        case GL_BGRA8_EXT:       return sizeof(unsigned char)* 4;
+        default: FORMAT_ERROR(format, type);
+        }
+        break;
+    case GL_SHORT:
+        switch(format) {
+        case GL_R16I:
+        case GL_RED_INTEGER:     return sizeof(short);
+        case GL_RG16I:
+        case GL_RG_INTEGER:      return sizeof(short) * 2;
+        case GL_RGB16I:
+        case GL_RGB_INTEGER:     return sizeof(short) * 3;
+        case GL_RGBA16I:
+        case GL_RGBA_INTEGER:    return sizeof(short) * 4;
+        default: FORMAT_ERROR(format, type);
+        }
+        break;
+    case GL_UNSIGNED_SHORT:
+        switch(format) {
+        case GL_DEPTH_COMPONENT16:
+        case GL_DEPTH_COMPONENT: return sizeof(unsigned short);
+        case GL_R16UI:
+        case GL_RED_INTEGER:     return sizeof(unsigned short);
+        case GL_RG16UI:
+        case GL_RG_INTEGER:      return sizeof(unsigned short) * 2;
+        case GL_RGB16UI:
+        case GL_RGB_INTEGER:     return sizeof(unsigned short) * 3;
+        case GL_RGBA16UI:
+        case GL_RGBA_INTEGER:    return sizeof(unsigned short) * 4;
+        default: FORMAT_ERROR(format, type);
+        }
+        break;
+    case GL_INT:
+        switch(format) {
+        case GL_R32I:
+        case GL_RED_INTEGER:     return sizeof(int);
+        case GL_RG32I:
+        case GL_RG_INTEGER:      return sizeof(int) * 2;
+        case GL_RGB32I:
+        case GL_RGB_INTEGER:     return sizeof(int) * 3;
+        case GL_RGBA32I:
+        case GL_RGBA_INTEGER:    return sizeof(int) * 4;
+        default: FORMAT_ERROR(format, type);
+        }
+        break;
+    case GL_UNSIGNED_INT:
+        switch(format) {
+        case GL_DEPTH_COMPONENT16:
+        case GL_DEPTH_COMPONENT24:
+        case GL_DEPTH_COMPONENT32_OES:
+        case GL_DEPTH_COMPONENT: return sizeof(unsigned int);
+        case GL_R32UI:
+        case GL_RED_INTEGER:     return sizeof(unsigned int);
+        case GL_RG32UI:
+        case GL_RG_INTEGER:      return sizeof(unsigned int) * 2;
+        case GL_RGB32UI:
+        case GL_RGB_INTEGER:     return sizeof(unsigned int) * 3;
+        case GL_RGBA32UI:
+        case GL_RGBA_INTEGER:    return sizeof(unsigned int) * 4;
+        default: FORMAT_ERROR(format, type);
+        }
+        break;
+    case GL_UNSIGNED_SHORT_4_4_4_4:
+    case GL_UNSIGNED_SHORT_5_5_5_1:
+    case GL_UNSIGNED_SHORT_5_6_5:
+    case GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT:
+    case GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT:
+        return sizeof(unsigned short);
+    case GL_UNSIGNED_INT_10F_11F_11F_REV:
+    case GL_UNSIGNED_INT_5_9_9_9_REV:
+    case GL_UNSIGNED_INT_2_10_10_10_REV:
+    case GL_UNSIGNED_INT_24_8_OES:
+        return sizeof(unsigned int);
+    case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
+        return sizeof(float) + sizeof(unsigned int);
+    case GL_FLOAT:
+        switch(format) {
+        case GL_DEPTH_COMPONENT32F:
+        case GL_DEPTH_COMPONENT: return sizeof(float);
+        case GL_ALPHA32F_EXT:
+        case GL_ALPHA:           return sizeof(float);
+        case GL_LUMINANCE32F_EXT:
+        case GL_LUMINANCE:       return sizeof(float);
+        case GL_LUMINANCE_ALPHA32F_EXT:
+        case GL_LUMINANCE_ALPHA: return sizeof(float) * 2;
+        case GL_RED:             return sizeof(float);
+        case GL_R32F:            return sizeof(float);
+        case GL_RG:              return sizeof(float) * 2;
+        case GL_RG32F:           return sizeof(float) * 2;
+        case GL_RGB:             return sizeof(float) * 3;
+        case GL_RGB32F:          return sizeof(float) * 3;
+        case GL_RGBA:            return sizeof(float) * 4;
+        case GL_RGBA32F:         return sizeof(float) * 4;
+        default: FORMAT_ERROR(format, type);
+        }
+        break;
+    case GL_HALF_FLOAT:
+    case GL_HALF_FLOAT_OES:
+        switch(format) {
+        case GL_ALPHA16F_EXT:
+        case GL_ALPHA:           return sizeof(unsigned short);
+        case GL_LUMINANCE16F_EXT:
+        case GL_LUMINANCE:       return sizeof(unsigned short);
+        case GL_LUMINANCE_ALPHA16F_EXT:
+        case GL_LUMINANCE_ALPHA: return sizeof(unsigned short) * 2;
+        case GL_RED:             return sizeof(unsigned short);
+        case GL_R16F:            return sizeof(unsigned short);
+        case GL_RG:              return sizeof(unsigned short) * 2;
+        case GL_RG16F:           return sizeof(unsigned short) * 2;
+        case GL_RGB:             return sizeof(unsigned short) * 3;
+        case GL_RGB16F:          return sizeof(unsigned short) * 3;
+        case GL_RGBA:            return sizeof(unsigned short) * 4;
+        case GL_RGBA16F:         return sizeof(unsigned short) * 4;
+        default: FORMAT_ERROR(format, type);
+        }
+        break;
+    default: FORMAT_ERROR(format, type);
+    }
+
+    return 0;
+}
+
+static int computePitch(GLsizei inputWidth, GLenum format, GLenum type, int align) {
+    GLsizei unaligned_width = computePixelSize(format, type) * inputWidth;
+    return HIGHEST_MULTIPLE_OF(align, unaligned_width);
+}
+
+static int computePackingOffset(GLenum format, GLenum type, GLsizei width, GLsizei height, int align, int skipPixels, int skipRows, int skipImages) {
+    GLsizei alignedPitch = computePitch(width, format, type, align);
+    int packingOffsetRows =
+        (skipImages * height + skipRows);
+    return packingOffsetRows * alignedPitch + skipPixels * computePixelSize(format, type);
+}
+
+void computeTextureStartEnd(
+        GLsizei width, GLsizei height, GLsizei depth,
+        GLenum format, GLenum type,
+        int unpackAlignment,
+        int unpackRowLength,
+        int unpackImageHeight,
+        int unpackSkipPixels,
+        int unpackSkipRows,
+        int unpackSkipImages,
+        int* start,
+        int* end) {
+
+    GLsizei inputWidth = (unpackRowLength == 0) ? width : unpackRowLength;
+    GLsizei inputPitch = computePitch(inputWidth, format, type, unpackAlignment);
+    GLsizei inputHeight = (unpackImageHeight == 0) ? height : unpackImageHeight;
+
+    ALOGV("%s: input idim %d %d %d w p h %d %d %d:", __FUNCTION__, width, height, depth, inputWidth, inputPitch, inputHeight);
+
+    int startVal = computePackingOffset(format, type, inputWidth, inputHeight, unpackAlignment, unpackSkipPixels, unpackSkipRows, unpackSkipImages);
+    int endVal = startVal + inputPitch * inputHeight * depth;
+
+    if (start) *start = startVal;
+    if (end) *end = endVal;
+
+    ALOGV("%s: start/end: %d %d", __FUNCTION__, *start, *end);
+
+}
+
+int computeTotalImageSize(
+        GLsizei width, GLsizei height, GLsizei depth,
+        GLenum format, GLenum type,
+        int unpackAlignment,
+        int unpackRowLength,
+        int unpackImageHeight,
+        int unpackSkipPixels,
+        int unpackSkipRows,
+        int unpackSkipImages) {
+
+    int start, end;
+    computeTextureStartEnd(
+            width, height, depth,
+            format, type,
+            unpackAlignment,
+            unpackRowLength,
+            unpackImageHeight,
+            unpackSkipPixels,
+            unpackSkipRows,
+            unpackSkipImages,
+            &start,
+            &end);
+    return end;
+}
+
+int computeNeededBufferSize(
+        GLsizei width, GLsizei height, GLsizei depth,
+        GLenum format, GLenum type,
+        int unpackAlignment,
+        int unpackRowLength,
+        int unpackImageHeight,
+        int unpackSkipPixels,
+        int unpackSkipRows,
+        int unpackSkipImages) {
+
+    int start, end;
+    computeTextureStartEnd(
+            width, height, depth,
+            format, type,
+            unpackAlignment,
+            unpackRowLength,
+            unpackImageHeight,
+            unpackSkipPixels,
+            unpackSkipRows,
+            unpackSkipImages,
+            &start,
+            &end);
+    return end - start;
+}
+
+} // namespace GLESTextureUtils
diff --git a/shared/OpenglCodecCommon/GLESTextureUtils.h b/shared/OpenglCodecCommon/GLESTextureUtils.h
new file mode 100644
index 0000000..906e590
--- /dev/null
+++ b/shared/OpenglCodecCommon/GLESTextureUtils.h
@@ -0,0 +1,42 @@
+#ifndef GLES_TEXTURE_UTILS_H
+#define GLES_TEXTURE_UTILS_H
+
+#include <GLES3/gl31.h>
+
+namespace GLESTextureUtils {
+
+void computeTextureStartEnd(
+        GLsizei width, GLsizei height, GLsizei depth,
+        GLenum format, GLenum type,
+        int unpackAlignment,
+        int unpackRowLength,
+        int unpackImageHeight,
+        int unpackSkipPixels,
+        int unpackSkipRows,
+        int unpackSkipImages,
+        int* start,
+        int* end);
+
+int computeTotalImageSize(
+        GLsizei width, GLsizei height, GLsizei depth,
+        GLenum format, GLenum type,
+        int unpackAlignment,
+        int unpackRowLength,
+        int unpackImageHeight,
+        int unpackSkipPixels,
+        int unpackSkipRows,
+        int unpackSkipImages);
+
+int computeNeededBufferSize(
+        GLsizei width, GLsizei height, GLsizei depth,
+        GLenum format, GLenum type,
+        int unpackAlignment,
+        int unpackRowLength,
+        int unpackImageHeight,
+        int unpackSkipPixels,
+        int unpackSkipRows,
+        int unpackSkipImages);
+
+
+} // namespace GLESTextureUtils
+#endif
diff --git a/shared/OpenglCodecCommon/GLSharedGroup.cpp b/shared/OpenglCodecCommon/GLSharedGroup.cpp
index 3b5211f..1b8facf 100755
--- a/shared/OpenglCodecCommon/GLSharedGroup.cpp
+++ b/shared/OpenglCodecCommon/GLSharedGroup.cpp
@@ -252,6 +252,10 @@
     return m_buffers.valueFor(bufferId);
 }
 
+SharedTextureDataMap* GLSharedGroup::getTextureData() {
+    return &m_textureRecs;
+}
+
 void GLSharedGroup::addBufferData(GLuint bufferId, GLsizeiptr size, void * data)
 {
     android::AutoMutex _lock(m_lock);
diff --git a/shared/OpenglCodecCommon/GLSharedGroup.h b/shared/OpenglCodecCommon/GLSharedGroup.h
index 4057b52..2de300f 100755
--- a/shared/OpenglCodecCommon/GLSharedGroup.h
+++ b/shared/OpenglCodecCommon/GLSharedGroup.h
@@ -22,11 +22,14 @@
 #define GL_APIENTRYP
 #endif
 
+#include "TextureSharedData.h"
+
 #include <GLES/gl.h>
 #include <GLES/glext.h>
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
+#include <map>
 #include <stdio.h>
 #include <stdlib.h>
 #include "ErrorLog.h"
@@ -101,6 +104,7 @@
 
 class GLSharedGroup {
 private:
+    SharedTextureDataMap m_textureRecs;
     android::DefaultKeyedVector<GLuint, BufferData*> m_buffers;
     android::DefaultKeyedVector<GLuint, ProgramData*> m_programs;
     android::DefaultKeyedVector<GLuint, ShaderData*> m_shaders;
@@ -114,6 +118,7 @@
     ~GLSharedGroup();
     bool isObject(GLuint obj);
     BufferData * getBufferData(GLuint bufferId);
+    SharedTextureDataMap* getTextureData();
     void    addBufferData(GLuint bufferId, GLsizeiptr size, void * data);
     void    updateBufferData(GLuint bufferId, GLsizeiptr size, void * data);
     GLenum  subUpdateBufferData(GLuint bufferId, GLintptr offset, GLsizeiptr size, void * data);
diff --git a/shared/OpenglCodecCommon/TextureSharedData.h b/shared/OpenglCodecCommon/TextureSharedData.h
new file mode 100644
index 0000000..1372f7a
--- /dev/null
+++ b/shared/OpenglCodecCommon/TextureSharedData.h
@@ -0,0 +1,42 @@
+/*
+* Copyright (C) 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+#ifndef _GL_TEXTURE_SHARED_DATA_H_
+#define _GL_TEXTURE_SHARED_DATA_H_
+
+#include <GLES/gl.h>
+#include <map>
+
+struct TextureDims {
+    std::map<GLsizei, GLsizei> widths;
+    std::map<GLsizei, GLsizei> heights;
+    std::map<GLsizei, GLsizei> depths;
+};
+
+struct TextureRec {
+    GLuint id;
+    GLenum target;
+    GLint internalformat;
+    GLenum format;
+    GLenum type;
+    GLsizei multisamples;
+    TextureDims* dims;
+    bool immutable;
+    bool boundEGLImage;
+};
+
+typedef std::map<GLuint, TextureRec*> SharedTextureDataMap;
+
+#endif
diff --git a/system/GLESv1_enc/GLEncoder.h b/system/GLESv1_enc/GLEncoder.h
index c9c83fa..1518030 100644
--- a/system/GLESv1_enc/GLEncoder.h
+++ b/system/GLESv1_enc/GLEncoder.h
@@ -30,7 +30,11 @@
     void setClientState(GLClientState *state) {
         m_state = state;
     }
-    void setSharedGroup(GLSharedGroupPtr shared) { m_shared = shared; }
+    void setSharedGroup(GLSharedGroupPtr shared) {
+        m_shared = shared;
+        if (m_state && m_shared.Ptr())
+            m_state->setTextureData(m_shared->getTextureData());
+    }
     void flush() { m_stream->flush(); }
     size_t pixelDataSize(GLsizei width, GLsizei height, GLenum format, GLenum type, int pack);
 
diff --git a/system/GLESv2_enc/GL2Encoder.h b/system/GLESv2_enc/GL2Encoder.h
index 11c9312..f99dcf1 100644
--- a/system/GLESv2_enc/GL2Encoder.h
+++ b/system/GLESv2_enc/GL2Encoder.h
@@ -34,8 +34,11 @@
         m_currMajorVersion = majorVersion;
         m_currMinorVersion = minorVersion;
     }
+    void setSharedGroup(GLSharedGroupPtr shared) {
+        m_shared = shared;
+        if (m_state && m_shared.Ptr())
+            m_state->setTextureData(m_shared->getTextureData());
     }
-    void setSharedGroup(GLSharedGroupPtr shared){ m_shared = shared; }
     const GLClientState *state() { return m_state; }
     const GLSharedGroupPtr shared() { return m_shared; }
     void flush() { m_stream->flush(); }