// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <array>

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/strings/string_number_conversions.h"
#include "gpu/command_buffer/common/gles2_cmd_format.h"
#include "gpu/command_buffer/common/gles2_cmd_utils.h"
#include "gpu/command_buffer/common/id_allocator.h"
#include "gpu/command_buffer/common/shared_image_info.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/context_group.h"
#include "gpu/command_buffer/service/context_state.h"
#include "gpu/command_buffer/service/gl_surface_mock.h"
#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
#include "gpu/command_buffer/service/gles2_cmd_decoder_unittest.h"
#include "gpu/command_buffer/service/mocks.h"
#include "gpu/command_buffer/service/program_manager.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "gpu/command_buffer/service/shared_image/test_image_backing.h"
#include "gpu/command_buffer/service/test_helper.h"
#include "gpu/config/gpu_switches.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_mock.h"
#include "ui/gl/gl_surface_stub.h"

#if !defined(GL_DEPTH24_STENCIL8)
#define GL_DEPTH24_STENCIL8 0x88F0
#endif

namespace gpu::gles2 {

using ::gl::MockGLInterface;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::MatcherCast;
using ::testing::Mock;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SetArrayArgument;
using ::testing::SetArgPointee;
using ::testing::SetArgPointee;
using ::testing::StrEq;
using ::testing::StrictMock;

TEST_P(GLES2DecoderTest, GenerateMipmapWrongFormatsFails) {
  EXPECT_CALL(*gl_, GenerateMipmapEXT(_)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA, 16, 17, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);
  cmds::GenerateMipmap cmd;
  cmd.Init(GL_TEXTURE_2D);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderTest, GenerateMipmapHandlesOutOfMemory) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  GLint width = 0;
  GLint height = 0;
  EXPECT_FALSE(
      texture->GetLevelSize(GL_TEXTURE_2D, 2, &width, &height, nullptr));
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
               shared_memory_id_, kSharedMemoryOffset);
  EXPECT_CALL(*gl_, GenerateMipmapEXT(GL_TEXTURE_2D)).Times(1);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  cmds::GenerateMipmap cmd;
  cmd.Init(GL_TEXTURE_2D);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
  EXPECT_FALSE(
      texture->GetLevelSize(GL_TEXTURE_2D, 2, &width, &height, nullptr));
}

TEST_P(GLES2DecoderTest, GenerateMipmapClearsUnclearedTexture) {
  EXPECT_CALL(*gl_, GenerateMipmapEXT(_)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);
  SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
                                GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
                                GL_UNSIGNED_BYTE, 0, 0, 2, 2, 0);
  EXPECT_CALL(*gl_, GenerateMipmapEXT(GL_TEXTURE_2D));
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::GenerateMipmap cmd;
  cmd.Init(GL_TEXTURE_2D);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, GenerateMipmapBaseLevel) {
  EXPECT_CALL(*gl_, GenerateMipmapEXT(_)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 2, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);

  {
    EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 2));
    cmds::TexParameteri cmd;
    cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 2);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
  }

  SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
                                GL_TEXTURE_2D, GL_TEXTURE_2D, 2, GL_RGBA,
                                GL_UNSIGNED_BYTE, 0, 0, 2, 2, 0);
  EXPECT_CALL(*gl_, GenerateMipmapEXT(GL_TEXTURE_2D));
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::GenerateMipmap cmd;
  cmd.Init(GL_TEXTURE_2D);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, ActiveTextureValidArgs) {
  EXPECT_CALL(*gl_, ActiveTexture(GL_TEXTURE1));
  SpecializedSetup<cmds::ActiveTexture, 0>(true);
  cmds::ActiveTexture cmd;
  cmd.Init(GL_TEXTURE1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, ActiveTextureInvalidArgs) {
  EXPECT_CALL(*gl_, ActiveTexture(_)).Times(0);
  SpecializedSetup<cmds::ActiveTexture, 0>(false);
  cmds::ActiveTexture cmd;
  cmd.Init(GL_TEXTURE0 - 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
  cmd.Init(kNumTextureUnits);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderTest, TexSubImage2DValidArgs) {
  const int kWidth = 16;
  const int kHeight = 8;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, kWidth, kHeight, 0, GL_RGBA,
               GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset);
  EXPECT_CALL(
      *gl_, TexSubImage2D(GL_TEXTURE_2D, 1, 1, 0, kWidth - 1, kHeight, GL_RGBA,
                          GL_UNSIGNED_BYTE, shared_memory_address_.get()))
      .Times(1)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 1, 1, 0, kWidth - 1, kHeight, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // 0 size with null SHM
  EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 0, 0, GL_RGBA,
                                  GL_UNSIGNED_BYTE, nullptr))
      .Times(1)
      .RetiresOnSaturation();
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0,
           GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, TexSubImage2DBadArgs) {
  const int kWidth = 8;
  const int kHeight = 4;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D,
               1,
               GL_RGBA,
               kWidth,
               kHeight,
               0,
               GL_RGBA,
               GL_UNSIGNED_BYTE,
               0,
               0);
  cmds::TexSubImage2D cmd;

  // Invalid target.
  cmd.Init(GL_TEXTURE0, 1, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  // Invalid format / type.
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth, kHeight, GL_TRUE, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_INT,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  // Invalid offsets/sizes.
  cmd.Init(GL_TEXTURE_2D, 1, -1, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 1, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, -1, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, 1, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth + 1, kHeight, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth, kHeight + 1, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // Incompatible format / type.
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth, kHeight, GL_RGB, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth, kHeight, GL_RGBA,
           GL_UNSIGNED_SHORT_4_4_4_4, shared_memory_id_, kSharedMemoryOffset,
           GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Above errors should still happen with NULL data.
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_INT, 0,
           0, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, -1, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
           0, 0, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth + 1, kHeight, GL_RGBA,
           GL_UNSIGNED_BYTE, 0, 0, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // Invalid SHM / offset.
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
           kInvalidSharedMemoryId, kSharedMemoryOffset, GL_FALSE);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kInvalidSharedMemoryOffset, GL_FALSE);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
           0, 0, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, TexSubImage3DValidArgs) {
  const int kWidth = 8;
  const int kHeight = 4;
  const int kDepth = 2;
  DoBindTexture(GL_TEXTURE_3D, client_texture_id_, kServiceTextureId);
  DoTexImage3D(GL_TEXTURE_3D, 1, GL_RGBA, kWidth, kHeight, kDepth, 0, GL_RGBA,
               GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset);
  EXPECT_CALL(*gl_,
              TexSubImage3DWithData(GL_TEXTURE_3D, 1, 1, 0, 0, kWidth - 1,
                                    kHeight, kDepth, GL_RGBA, GL_UNSIGNED_BYTE))
      .Times(1)
      .RetiresOnSaturation();
  cmds::TexSubImage3D cmd;
  cmd.Init(GL_TEXTURE_3D, 1, 1, 0, 0, kWidth - 1, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // 0 size with null SHM
  EXPECT_CALL(*gl_, TexSubImage3DNoData(GL_TEXTURE_3D, 1, 1, 0, 0, 0, 0, 0,
                                        GL_RGBA, GL_UNSIGNED_BYTE))
      .Times(1)
      .RetiresOnSaturation();
  cmd.Init(GL_TEXTURE_3D, 1, 1, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0,
           GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, TexSubImage3DBadArgs) {
  const int kWidth = 4;
  const int kHeight = 4;
  const int kDepth = 2;
  DoBindTexture(GL_TEXTURE_3D, client_texture_id_, kServiceTextureId);
  DoTexImage3D(GL_TEXTURE_3D, 1, GL_RGBA, kWidth, kHeight, kDepth, 0, GL_RGBA,
               GL_UNSIGNED_BYTE, 0, 0);
  cmds::TexSubImage3D cmd;

  // Invalid target.
  cmd.Init(GL_TEXTURE0, 1, 0, 0, 0, kWidth, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  // Invalid format.
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth, kHeight, kDepth, GL_TRUE,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  // Invalid offsets/sizes.
  cmd.Init(GL_TEXTURE_3D, 1, -1, 0, 0, kWidth, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_3D, 1, 1, 0, 0, kWidth, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_3D, 1, 0, -1, 0, kWidth, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_3D, 1, 0, 1, 0, kWidth, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, -1, kWidth, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 1, kWidth, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth + 1, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth, kHeight + 1, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth, kHeight, kDepth + 1, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // Incompatible format.
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth, kHeight, kDepth, GL_RGB,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Above errors should still happen with NULL data.
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth, kHeight, kDepth, GL_TRUE,
           GL_UNSIGNED_BYTE, 0, 0, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth, kHeight, kDepth + 1, GL_RGBA,
           GL_UNSIGNED_BYTE, 0, 0, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth, kHeight, kDepth, GL_RGB,
           GL_UNSIGNED_BYTE, 0, 0, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Invalid SHM / offset.
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, kInvalidSharedMemoryId, kSharedMemoryOffset,
           GL_FALSE);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kInvalidSharedMemoryOffset,
           GL_FALSE);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
  cmd.Init(GL_TEXTURE_3D, 1, 0, 0, 0, kWidth, kHeight, kDepth, GL_RGBA,
           GL_UNSIGNED_BYTE, 0, 0, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, TexSubImage2DTypesDoNotMatchUnsizedFormat) {
  const int kWidth = 16;
  const int kHeight = 8;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, kWidth, kHeight, 0, GL_RGBA,
               GL_UNSIGNED_SHORT_4_4_4_4, shared_memory_id_,
               kSharedMemoryOffset);
  EXPECT_CALL(
      *gl_, TexSubImage2D(GL_TEXTURE_2D, 1, 1, 0, kWidth - 1, kHeight, GL_RGBA,
                          GL_UNSIGNED_BYTE, shared_memory_address_.get()))
      .Times(1)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 1, 1, 0, kWidth - 1, kHeight, GL_RGBA,
           GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, TexSubImage2DTypesDoNotMatchSizedFormat) {
  const int kWidth = 16;
  const int kHeight = 8;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA4, kWidth, kHeight, 0, GL_RGBA,
               GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset);
  EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 1, 1, 0, kWidth - 1, kHeight,
                                  GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4,
                                  shared_memory_address_.get()))
      .Times(1)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 1, 1, 0, kWidth - 1, kHeight, GL_RGBA,
           GL_UNSIGNED_SHORT_4_4_4_4, shared_memory_id_, kSharedMemoryOffset,
           GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, CopyTexSubImage2DValidArgs) {
  const int kWidth = 16;
  const int kHeight = 8;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, kWidth, kHeight, 0, GL_RGBA,
               GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset);
  EXPECT_CALL(*gl_,
              CopyTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 0, 0, kWidth, kHeight))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::CopyTexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, 0, 0, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, CopyTexSubImage2DBadArgs) {
  const int kWidth = 16;
  const int kHeight = 8;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D,
               1,
               GL_RGBA,
               kWidth,
               kHeight,
               0,
               GL_RGBA,
               GL_UNSIGNED_BYTE,
               0,
               0);
  cmds::CopyTexSubImage2D cmd;
  cmd.Init(GL_TEXTURE0, 1, 0, 0, 0, 0, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, -1, 0, 0, 0, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 1, 0, 0, 0, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, -1, 0, 0, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, 1, 0, 0, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, 0, 0, kWidth + 1, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_TEXTURE_2D, 1, 0, 0, 0, 0, kWidth, kHeight + 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
}

TEST_P(GLES2DecoderTest, TexImage2DRedefinitionSucceeds) {
  const int kWidth = 16;
  const int kHeight = 8;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  EXPECT_CALL(*gl_, GetError()).WillRepeatedly(Return(GL_NO_ERROR));
  for (int ii = 0; ii < 2; ++ii) {
    cmds::TexImage2D cmd;
    if (ii == 0) {
      EXPECT_CALL(*gl_,
                  TexImage2D(GL_TEXTURE_2D,
                             0,
                             GL_RGBA,
                             kWidth,
                             kHeight,
                             0,
                             GL_RGBA,
                             GL_UNSIGNED_BYTE,
                             _))
          .Times(1)
          .RetiresOnSaturation();
      cmd.Init(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, GL_RGBA,
               GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset);
    } else {
      cmd.Init(GL_TEXTURE_2D,
               0,
               GL_RGBA,
               kWidth,
               kHeight,
               GL_RGBA,
               GL_UNSIGNED_BYTE,
               0,
               0);
    }
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight - 1,
                                    GL_RGBA, GL_UNSIGNED_BYTE,
                                    shared_memory_address_.get()))
        .Times(1)
        .RetiresOnSaturation();
    // Consider this TexSubImage2D command part of the previous TexImage2D
    // (last GL_TRUE argument). It will be skipped if there are bugs in the
    // redefinition case.
    cmds::TexSubImage2D cmd2;
    cmd2.Init(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight - 1, GL_RGBA,
              GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset,
              GL_TRUE);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  }
}

TEST_P(GLES2DecoderTest, TexImage2DGLError) {
  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLenum internal_format = GL_RGBA;
  GLsizei width = 2;
  GLsizei height = 4;
  GLint border = 0;
  GLenum format = GL_RGBA;
  GLenum type = GL_UNSIGNED_BYTE;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_FALSE(
      texture->GetLevelSize(GL_TEXTURE_2D, level, &width, &height, nullptr));
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              TexImage2D(target,
                         level,
                         internal_format,
                         width,
                         height,
                         border,
                         format,
                         type,
                         _))
      .Times(1)
      .RetiresOnSaturation();
  cmds::TexImage2D cmd;
  cmd.Init(target, level, internal_format, width, height, format, type,
           shared_memory_id_, kSharedMemoryOffset);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
  EXPECT_FALSE(
      texture->GetLevelSize(GL_TEXTURE_2D, level, &width, &height, nullptr));
}

TEST_P(GLES2DecoderManualInitTest,
       TexImage2DUnpackOverlappingRowsWorkaroundGLError) {
  gpu::GpuDriverBugWorkarounds workarounds;
  workarounds.unpack_overlapping_rows_separately_unpack_buffer = true;
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  InitDecoderWithWorkarounds(init, workarounds);

  const GLsizei kWidth = 2;
  const GLsizei kHeight = 2;
  const GLint kRowLength = 1;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;
  const GLint kBufferSize = 16;

  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);

  cmds::PixelStorei pixel_store_cmd;
  pixel_store_cmd.Init(GL_UNPACK_ROW_LENGTH, kRowLength);
  EXPECT_EQ(error::kNoError, ExecuteCmd(pixel_store_cmd));

  EXPECT_CALL(*gl_, PixelStorei(GL_UNPACK_ROW_LENGTH, kRowLength))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0))
      .Times(1)
      .RetiresOnSaturation();
  DoBindBuffer(GL_PIXEL_UNPACK_BUFFER, client_buffer_id_, kServiceBufferId);
  DoBufferData(GL_PIXEL_UNPACK_BUFFER, kBufferSize);

  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();

  EXPECT_CALL(*gl_, PixelStorei(_, _)).Times(AnyNumber());
  EXPECT_CALL(*gl_, BindBuffer(GL_PIXEL_UNPACK_BUFFER, _)).Times(AnyNumber());
  EXPECT_CALL(*gl_, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0,
                               kFormat, kType, nullptr))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(
      *gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, kWidth, 1, kFormat, kType, _))
      .Times(kHeight)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  cmds::TexImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, kFormat, kType, 0, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_FALSE(texture->IsLevelCleared(GL_TEXTURE_2D, 0));
  EXPECT_FALSE(texture->SafeToRenderFrom());
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, TexImage2DUnpackAlignmentWorkaroundGLError) {
  gpu::GpuDriverBugWorkarounds workarounds;
  workarounds.unpack_alignment_workaround_with_unpack_buffer = true;
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  InitDecoderWithWorkarounds(init, workarounds);

  const GLsizei kWidth = 2;
  const GLsizei kHeight = 2;
  const GLenum kFormat = GL_RGB;
  const GLenum kType = GL_UNSIGNED_BYTE;
  // Padded row size 8 (alignment 4), unpadded row size 6; pixels_size = 14.
  const GLint kBufferSize = 14;

  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);

  EXPECT_CALL(*gl_, PixelStorei(GL_UNPACK_ROW_LENGTH, 0))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0))
      .Times(1)
      .RetiresOnSaturation();
  DoBindBuffer(GL_PIXEL_UNPACK_BUFFER, client_buffer_id_, kServiceBufferId);
  DoBufferData(GL_PIXEL_UNPACK_BUFFER, kBufferSize);

  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();

  EXPECT_CALL(*gl_, PixelStorei(_, _)).Times(AnyNumber());
  EXPECT_CALL(*gl_, BindBuffer(GL_PIXEL_UNPACK_BUFFER, _)).Times(AnyNumber());
  EXPECT_CALL(*gl_, TexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kWidth, kHeight, 0,
                               kFormat, kType, nullptr))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(
      *gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, kWidth, _, kFormat, kType, _))
      .Times(2)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  cmds::TexImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, GL_RGB, kWidth, kHeight, kFormat, kType, 0, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_FALSE(texture->IsLevelCleared(GL_TEXTURE_2D, 0));
  EXPECT_FALSE(texture->SafeToRenderFrom());
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
}

TEST_P(GLES2DecoderTest, TexSubImage2DGLErrorDoesNotMarkLevelAsCleared) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0,
               0);

  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_FALSE(texture->SafeToRenderFrom());

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA,
                               GL_UNSIGNED_BYTE, shared_memory_address_.get()))
      .Times(1)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
  EXPECT_FALSE(texture->SafeToRenderFrom());
}

TEST_P(GLES2DecoderTest, TexSubImage2DGLErrorDoesNotExpandClearedRect) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0,
               0);

  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_EQ(gfx::Rect(), texture->GetLevelClearedRect(GL_TEXTURE_2D, 0));

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA,
                            GL_UNSIGNED_BYTE, shared_memory_address_.get()))
      .Times(1)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
  EXPECT_EQ(gfx::Rect(), texture->GetLevelClearedRect(GL_TEXTURE_2D, 0));
  EXPECT_FALSE(texture->SafeToRenderFrom());
}

TEST_P(GLES2DecoderTest, CopyTexImage2DGLError) {
  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLenum internal_format = GL_RGBA;
  GLsizei width = 2;
  GLsizei height = 4;
  GLint border = 0;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_FALSE(
      texture->GetLevelSize(GL_TEXTURE_2D, level, &width, &height, nullptr));
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              CopyTexImage2D(
                  target, level, internal_format, 0, 0, width, height, border))
      .Times(1)
      .RetiresOnSaturation();
  cmds::CopyTexImage2D cmd;
  cmd.Init(target, level, internal_format, 0, 0, width, height);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
  EXPECT_FALSE(
      texture->GetLevelSize(GL_TEXTURE_2D, level, &width, &height, nullptr));
}

TEST_P(GLES2DecoderManualInitTest, CopyTexImage2DUnsizedInternalFormat) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.extensions = "GL_APPLE_texture_format_BGRA8888 GL_EXT_sRGB";
  init.has_alpha = true;
  init.request_alpha = true;
  init.context_type = CONTEXT_TYPE_OPENGLES2;
  InitDecoder(init);

  constexpr auto kUnsizedInternalFormats = std::to_array<GLenum>({
      GL_RED,
      GL_RG,
      GL_RGB,
      GL_RGBA,
      GL_BGRA_EXT,
      GL_LUMINANCE,
      GL_LUMINANCE_ALPHA,
      GL_SRGB,
      GL_SRGB_ALPHA_EXT,
  });
  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLsizei width = 2;
  GLsizei height = 4;
  GLint border = 0;
  EXPECT_CALL(*gl_, GenTextures(_, _))
      .WillOnce(SetArgPointee<1>(kNewServiceId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kNewClientId);

  TextureManager* manager = group().texture_manager();

  EXPECT_CALL(*gl_, GetError()).WillRepeatedly(Return(GL_NO_ERROR));
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(_))
      .WillRepeatedly(Return(GL_FRAMEBUFFER_COMPLETE));
  for (GLenum internal_format : kUnsizedInternalFormats) {
    // Copy from main framebuffer to texture, using the unsized internal format.
    DoBindFramebuffer(GL_FRAMEBUFFER, 0, 0);
    DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
    DoCopyTexImage2D(target, level, internal_format, 0, 0, width, height, border);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    TextureRef* ref = manager->GetTexture(client_texture_id_);
    ASSERT_TRUE(ref != nullptr);
    Texture* texture = ref->texture();
    GLenum chosen_type = 0;
    GLenum chosen_internal_format = 0;
    texture->GetLevelType(target, level, &chosen_type, &chosen_internal_format);
    EXPECT_NE(0u, chosen_type);
    EXPECT_NE(0u, chosen_internal_format);

    // Attach texture to FBO, and copy into second texture.
    DoBindFramebuffer(
        GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
    DoFramebufferTexture2D(GL_FRAMEBUFFER,
                           GL_COLOR_ATTACHMENT0,
                           GL_TEXTURE_2D,
                           client_texture_id_,
                           kServiceTextureId,
                           0,
                           GL_NO_ERROR);
    DoBindTexture(GL_TEXTURE_2D, kNewClientId, kNewServiceId);

    bool complete =
        (DoCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
    if (complete) {
      DoCopyTexImage2D(target, level, internal_format,
                       0, 0, width, height, border);
      EXPECT_EQ(GL_NO_ERROR, GetGLError());
    } else {
      cmds::CopyTexImage2D cmd;
      cmd.Init(target, level, internal_format, 0, 0, width, height);
      EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
      EXPECT_EQ(GL_INVALID_FRAMEBUFFER_OPERATION, GetGLError());
    }
  }
}

TEST_P(GLES2DecoderManualInitTest, CopyTexImage2DUnsizedInternalFormatES3) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.extensions = "GL_APPLE_texture_format_BGRA8888";
  init.has_alpha = true;
  init.request_alpha = true;
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  InitDecoder(init);

  struct UnsizedSizedInternalFormat {
    GLenum unsized;
    GLenum sized;
  };
  constexpr auto kUnsizedInternalFormats =
      std::to_array<UnsizedSizedInternalFormat>({
          // GL_RED and GL_RG should not work.
          {GL_RGB, GL_RGB8},
          {GL_RGBA, GL_RGBA8},
          {GL_BGRA_EXT, GL_RGBA8},
          {GL_LUMINANCE, GL_RGB8},
          {GL_LUMINANCE_ALPHA, GL_RGBA8},
      });
  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLsizei width = 2;
  GLsizei height = 4;
  GLint border = 0;
  EXPECT_CALL(*gl_, GenTextures(_, _))
      .WillOnce(SetArgPointee<1>(kNewServiceId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kNewClientId);

  TextureManager* manager = group().texture_manager();

  EXPECT_CALL(*gl_, GetError()).WillRepeatedly(Return(GL_NO_ERROR));
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(_))
      .WillRepeatedly(Return(GL_FRAMEBUFFER_COMPLETE));
  for (const auto& format_pair : kUnsizedInternalFormats) {
    // Copy from main framebuffer to texture, using the unsized internal format.
    DoBindFramebuffer(GL_FRAMEBUFFER, 0, 0);
    GLenum internal_format = format_pair.unsized;
    DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
    DoCopyTexImage2D(target, level, internal_format,
                     0, 0, width, height, border);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    TextureRef* ref = manager->GetTexture(client_texture_id_);
    ASSERT_TRUE(ref != nullptr);
    Texture* texture = ref->texture();
    GLenum chosen_type = 0;
    GLenum chosen_internal_format = 0;
    texture->GetLevelType(target, level, &chosen_type, &chosen_internal_format);
    EXPECT_NE(0u, chosen_type);
    EXPECT_NE(0u, chosen_internal_format);

    // Attach texture to FBO, and copy into second texture using the sized
    // internal format.
    DoBindFramebuffer(
        GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
    DoFramebufferTexture2D(GL_FRAMEBUFFER,
                           GL_COLOR_ATTACHMENT0,
                           GL_TEXTURE_2D,
                           client_texture_id_,
                           kServiceTextureId,
                           0,
                           GL_NO_ERROR);
    if (DoCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
      continue;

    internal_format = format_pair.sized;
    DoBindTexture(GL_TEXTURE_2D, kNewClientId, kNewServiceId);

    bool complete =
        (DoCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
    if (complete) {
      DoCopyTexImage2D(target, level, internal_format,
                       0, 0, width, height, border);
      EXPECT_EQ(GL_NO_ERROR, GetGLError());
    } else {
      cmds::CopyTexImage2D cmd;
      cmd.Init(target, level, internal_format, 0, 0, width, height);
      EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
      EXPECT_EQ(GL_INVALID_FRAMEBUFFER_OPERATION, GetGLError());
    }
  }
}

TEST_P(GLES3DecoderTest, CompressedTexImage3DBucket) {
  const uint32_t kBucketId = 123;
  const uint32_t kBadBucketId = 99;
  const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
  const GLint kLevel = 0;
  const GLenum kInternalFormat = GL_COMPRESSED_R11_EAC;
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 4;
  const GLsizei kDepth = 4;
  const GLint kBorder = 0;
  CommonDecoder::Bucket* bucket = decoder_->CreateBucket(kBucketId);
  ASSERT_TRUE(bucket != nullptr);
  const GLsizei kImageSize = 32;
  bucket->SetSize(kImageSize);

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);

  cmds::CompressedTexImage3DBucket cmd;
  cmd.Init(kTarget,
           kLevel,
           kInternalFormat,
           kWidth,
           kHeight,
           kDepth,
           kBadBucketId);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

  cmd.Init(kTarget,
           kLevel,
           kInternalFormat,
           kWidth,
           kHeight,
           kDepth,
           kBucketId);
  EXPECT_CALL(*gl_,
              CompressedTexImage3D(kTarget, kLevel, kInternalFormat, kWidth,
                                   kHeight, kDepth, kBorder, kImageSize, _))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, CompressedTexImage3DBucketBucketSizeIsZero) {
  const uint32_t kBucketId = 123;
  const uint32_t kBadBucketId = 99;
  const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
  const GLint kLevel = 0;
  const GLenum kInternalFormat = GL_COMPRESSED_R11_EAC;
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 4;
  const GLsizei kDepth = 4;
  const GLint kBorder = 0;
  CommonDecoder::Bucket* bucket = decoder_->CreateBucket(kBucketId);
  ASSERT_TRUE(bucket != nullptr);
  const GLsizei kImageSize = 0;
  bucket->SetSize(kImageSize);

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);

  // Bad bucket
  cmds::CompressedTexImage3DBucket cmd;
  cmd.Init(kTarget,
           kLevel,
           kInternalFormat,
           0,
           kHeight,
           kDepth,
           kBadBucketId);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

  // Bucket size is zero. Height or width or depth is zero too.
  cmd.Init(kTarget,
           kLevel,
           kInternalFormat,
           0,
           kHeight,
           kDepth,
           kBucketId);
  EXPECT_CALL(*gl_,
              CompressedTexImage3D(kTarget, kLevel, kInternalFormat, 0,
                                   kHeight, kDepth, kBorder, kImageSize, _))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Bucket size is zero. But height, width and depth are not zero.
  cmd.Init(kTarget,
           kLevel,
           kInternalFormat,
           kWidth,
           kHeight,
           kDepth,
           kBucketId);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
}

TEST_P(GLES2DecoderTest, CompressedTexImage3DFailsOnES2) {
  const uint32_t kBucketId = 123;
  const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
  const GLint kLevel = 0;
  const GLenum kInternalFormat = GL_COMPRESSED_R11_EAC;
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 4;
  const GLsizei kDepth = 4;
  CommonDecoder::Bucket* bucket = decoder_->CreateBucket(kBucketId);
  ASSERT_TRUE(bucket != nullptr);
  const GLsizei kImageSize = 32;
  bucket->SetSize(kImageSize);

  {
    cmds::CompressedTexImage3DBucket cmd;
    cmd.Init(kTarget,
             kLevel,
             kInternalFormat,
             kWidth,
             kHeight,
             kDepth,
             kBucketId);
    EXPECT_EQ(error::kUnknownCommand, ExecuteCmd(cmd));
  }

  {
    cmds::CompressedTexSubImage3DBucket cmd;
    cmd.Init(kTarget,
             kLevel,
             0, 0, 0,
             kWidth,
             kHeight,
             kDepth,
             kInternalFormat,
             kBucketId);
    EXPECT_EQ(error::kUnknownCommand, ExecuteCmd(cmd));
  }
}

TEST_P(GLES2DecoderTest, CopyTexSubImage3DFailsOnES2) {
  const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
  const GLint kLevel = 0;
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 4;

  cmds::CopyTexSubImage3D cmd;
  cmd.Init(kTarget,
           kLevel,
           0, 0, 0,
           0, 0,
           kWidth,
           kHeight);
  EXPECT_EQ(error::kUnknownCommand, ExecuteCmd(cmd));
}

TEST_P(GLES3DecoderTest, CopyTexSubImage3DFaiures) {
  const GLenum kTarget = GL_TEXTURE_3D;
  const GLint kLevel = 1;
  const GLint kXoffset = 0;
  const GLint kYoffset = 0;
  const GLint kZoffset = 0;
  const GLint kX = 0;
  const GLint kY = 0;
  const GLsizei kWidth = 2;
  const GLsizei kHeight = 2;
  const GLsizei kDepth = 2;

  cmds::CopyTexSubImage3D cmd;

  // No texture bound
  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Incompatible format / type
  // The default format/type of default read buffer is RGB/UNSIGNED_BYTE
  const GLint kInternalFormat = GL_RGBA8;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;
  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth, 0,
               kFormat, kType, shared_memory_id_, kSharedMemoryOffset);

  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, CopyTexSubImage3DCheckArgs) {
  const GLenum kTarget = GL_TEXTURE_3D;
  const GLint kLevel = 1;
  const GLint kInternalFormat = GL_RGB8;
  const GLint kXoffset = 0;
  const GLint kYoffset = 0;
  const GLint kZoffset = 0;
  const GLint kX = 0;
  const GLint kY = 0;
  const GLsizei kWidth = 2;
  const GLsizei kHeight = 2;
  const GLsizei kDepth = 2;
  const GLsizei kBorder = 0;
  const GLenum kFormat = GL_RGB;
  const GLenum kType = GL_UNSIGNED_BYTE;

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth,
               kBorder, kFormat, kType, shared_memory_id_, kSharedMemoryOffset);

  // Valid args
  EXPECT_CALL(*gl_,
              CopyTexSubImage3D(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
                                kX, kY, kWidth, kHeight))
      .Times(1)
      .RetiresOnSaturation();
  cmds::CopyTexSubImage3D cmd;
  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Bad target
  cmd.Init(GL_TEXTURE_2D, kLevel, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  // Bad Level
  cmd.Init(kTarget, -1, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(kTarget, 0, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // Bad xoffest / yoffset of 3D texture
  cmd.Init(kTarget, kLevel, -1, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(kTarget, kLevel, 1, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(kTarget, kLevel, kXoffset, -1, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(kTarget, kLevel, kXoffset, 1, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // Bad zoffset: zoffset specifies the layer of the 3D texture to be replaced
  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, -1,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, kDepth,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // Bad width / height
  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth + 1, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight + 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
}

TEST_P(GLES3DecoderTest, CopyTexSubImage3DFeedbackLoopSucceeds0) {
  const GLenum kTarget = GL_TEXTURE_3D;
  const GLint kInternalFormat = GL_RGB8;
  const GLint kXoffset = 0;
  const GLint kYoffset = 0;
  const GLint kZoffset = 0;
  const GLint kX = 0;
  const GLint kY = 0;
  const GLsizei kWidth = 2;
  const GLsizei kHeight = 2;
  const GLsizei kDepth = 2;
  const GLsizei kBorder = 0;
  const GLenum kFormat = GL_RGB;
  const GLenum kType = GL_UNSIGNED_BYTE;

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);

  cmds::FramebufferTextureLayer tex_layer;
  cmds::CopyTexSubImage3D cmd;

  // The source and the target for CopyTexSubImage3D are the same 3d texture.
  // But level of 3D texture != level of read attachment in fbo.
  GLint kLevel = 0;
  GLint kLayer = 0; // kZoffset is 0
  EXPECT_CALL(*gl_, FramebufferTextureLayer(GL_FRAMEBUFFER,
                                            GL_COLOR_ATTACHMENT0,
                                            kServiceTextureId, kLevel, kLayer))
      .Times(1)
      .RetiresOnSaturation();
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth,
               kBorder, kFormat, kType, shared_memory_id_, kSharedMemoryOffset);
  tex_layer.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, client_texture_id_,
                 kLevel, kLayer);
  EXPECT_EQ(error::kNoError, ExecuteCmd(tex_layer));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  kLevel = 1;
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(_))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              CopyTexSubImage3D(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
                                kX, kY, kWidth, kHeight))
      .Times(1)
      .RetiresOnSaturation();
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth,
               kBorder, kFormat, kType, shared_memory_id_, kSharedMemoryOffset);
  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, CopyTexSubImage3DFeedbackLoopSucceeds1) {
  const GLenum kTarget = GL_TEXTURE_3D;
  const GLint kInternalFormat = GL_RGB8;
  const GLint kXoffset = 0;
  const GLint kYoffset = 0;
  const GLint kZoffset = 0;
  const GLint kX = 0;
  const GLint kY = 0;
  const GLsizei kWidth = 2;
  const GLsizei kHeight = 2;
  const GLsizei kDepth = 2;
  const GLsizei kBorder = 0;
  const GLenum kFormat = GL_RGB;
  const GLenum kType = GL_UNSIGNED_BYTE;

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);

  cmds::FramebufferTextureLayer tex_layer;
  cmds::CopyTexSubImage3D cmd;

  // The source and the target for CopyTexSubImage3D are the same 3d texture.
  // But zoffset of 3D texture != layer of read attachment in fbo.
  GLint kLevel = 0;
  GLint kLayer = 1; // kZoffset is 0
  EXPECT_CALL(*gl_, FramebufferTextureLayer(GL_FRAMEBUFFER,
                                            GL_COLOR_ATTACHMENT0,
                                            kServiceTextureId, kLevel, kLayer))
      .Times(1)
      .RetiresOnSaturation();
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth,
               kBorder, kFormat, kType, shared_memory_id_, kSharedMemoryOffset);
  tex_layer.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, client_texture_id_,
                 kLevel, kLayer);
  EXPECT_EQ(error::kNoError, ExecuteCmd(tex_layer));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(_))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              CopyTexSubImage3D(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
                                kX, kY, kWidth, kHeight))
      .Times(1)
      .RetiresOnSaturation();
  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, CopyTexSubImage3DFeedbackLoopFails) {
  const GLenum kTarget = GL_TEXTURE_3D;
  const GLint kInternalFormat = GL_RGB8;
  const GLint kXoffset = 0;
  const GLint kYoffset = 0;
  const GLint kZoffset = 0;
  const GLint kX = 0;
  const GLint kY = 0;
  const GLsizei kWidth = 2;
  const GLsizei kHeight = 2;
  const GLsizei kDepth = 2;
  const GLsizei kBorder = 0;
  const GLenum kFormat = GL_RGB;
  const GLenum kType = GL_UNSIGNED_BYTE;

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);

  cmds::FramebufferTextureLayer tex_layer;
  cmds::CopyTexSubImage3D cmd;

  // The source and the target for CopyTexSubImage3D are the same 3d texture.
  // And level / zoffset of 3D texture equal to level / layer of read attachment
  // in fbo.
  GLint kLevel = 0;  // This has to be base level, or fbo is incomplete.
  GLint kLayer = 0; // kZoffset is 0
  EXPECT_CALL(*gl_, FramebufferTextureLayer(GL_FRAMEBUFFER,
                                            GL_COLOR_ATTACHMENT0,
                                            kServiceTextureId, kLevel, kLayer))
      .Times(1)
      .RetiresOnSaturation();
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth,
               kBorder, kFormat, kType, shared_memory_id_, kSharedMemoryOffset);
  tex_layer.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, client_texture_id_,
                 kLevel, kLayer);
  EXPECT_EQ(error::kNoError, ExecuteCmd(tex_layer));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(_))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, CopyTexSubImage3DClearTheUncleared3DTexture) {
  const GLenum kTarget = GL_TEXTURE_3D;
  const GLint kLevel = 0;
  const GLint kXoffset = 0;
  const GLint kYoffset = 0;
  const GLint kZoffset = 0;
  const GLint kX = 0;
  const GLint kY = 0;
  const GLint kInternalFormat = GL_RGB8;
  const GLsizei kWidth = 2;
  const GLsizei kHeight = 2;
  const GLsizei kDepth = 2;
  const GLenum kFormat = GL_RGB;
  const GLenum kType = GL_UNSIGNED_BYTE;
  const uint32_t kBufferSize = kWidth * kHeight * kDepth * 4;

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth, 0,
               kFormat, kType, 0, 0);
  TextureRef* texture_ref =
      group().texture_manager()->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();

  EXPECT_FALSE(texture->SafeToRenderFrom());
  EXPECT_FALSE(texture->IsLevelCleared(kTarget, kLevel));

  // CopyTexSubImage3D will clear the uncleared texture
  GLint xoffset[] = {kXoffset};
  GLint yoffset[] = {kYoffset};
  GLint zoffset[] = {kZoffset};
  GLsizei width[] = {kWidth};
  GLsizei height[] = {kHeight};
  GLsizei depth[] = {kDepth};
  SetupClearTexture3DExpectations(kBufferSize, kTarget, kServiceTextureId,
                                  kLevel, kFormat, kType, 1, xoffset, yoffset,
                                  zoffset, width, height, depth, 0);
  EXPECT_CALL(*gl_,
              CopyTexSubImage3D(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
                                kX, kY, kWidth, kHeight))
      .Times(1)
      .RetiresOnSaturation();

  cmds::CopyTexSubImage3D cmd;
  cmd.Init(kTarget, kLevel, kXoffset, kYoffset, kZoffset,
           kX, kY, kWidth, kHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_TRUE(texture->SafeToRenderFrom());
  EXPECT_TRUE(texture->IsLevelCleared(kTarget, kLevel));
}

TEST_P(GLES3DecoderTest, CompressedTexImage3DFailsWithBadImageSize) {
  const uint32_t kBucketId = 123;
  const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
  const GLint kLevel = 0;
  const GLenum kInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC;
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 8;
  const GLsizei kDepth = 4;
  CommonDecoder::Bucket* bucket = decoder_->CreateBucket(kBucketId);
  ASSERT_TRUE(bucket != nullptr);
  const GLsizei kBadImageSize = 64;
  bucket->SetSize(kBadImageSize);

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);

  cmds::CompressedTexImage3DBucket cmd;
  cmd.Init(kTarget,
           kLevel,
           kInternalFormat,
           kWidth,
           kHeight,
           kDepth,
           kBucketId);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
}

TEST_P(GLES3DecoderTest, CompressedTexSubImage3DFails) {
  const uint32_t kBucketId = 123;
  const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
  const GLint kLevel = 0;
  const GLenum kInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC;
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 8;
  const GLsizei kDepth = 4;
  const GLint kBorder = 0;
  CommonDecoder::Bucket* bucket = decoder_->CreateBucket(kBucketId);
  ASSERT_TRUE(bucket != nullptr);
  const GLsizei kImageSize = 128;
  bucket->SetSize(kImageSize);

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);

  cmds::CompressedTexImage3DBucket tex_cmd;
  tex_cmd.Init(kTarget,
               kLevel,
               kInternalFormat,
               kWidth,
               kHeight,
               kDepth,
               kBucketId);
  EXPECT_CALL(*gl_,
              CompressedTexImage3D(kTarget, kLevel, kInternalFormat, kWidth,
                                   kHeight, kDepth, kBorder, kImageSize, _))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(tex_cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  const GLint kXOffset = 0;
  const GLint kYOffset = 0;
  const GLint kZOffset = 0;
  const GLint kSubWidth = 4;
  const GLint kSubHeight = 4;
  const GLint kSubDepth = 4;
  const GLenum kFormat = kInternalFormat;
  cmds::CompressedTexSubImage3DBucket cmd;

  // Incorrect image size.
  cmd.Init(kTarget,
           kLevel,
           kXOffset,
           kYOffset,
           kZOffset,
           kSubWidth,
           kSubHeight,
           kSubDepth,
           kFormat,
           kBucketId);
  const GLsizei kBadSubImageSize = 32;
  const GLsizei kSubImageSize = 64;
  bucket->SetSize(kBadSubImageSize);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // Incorrect format.
  const GLenum kBadFormat = GL_COMPRESSED_R11_EAC;
  cmd.Init(kTarget,
           kLevel,
           kXOffset,
           kYOffset,
           kZOffset,
           kSubWidth,
           kSubHeight,
           kSubDepth,
           kBadFormat,
           kBucketId);
  bucket->SetSize(kSubImageSize);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Negative offset.
  cmd.Init(kTarget,
           kLevel,
           kXOffset,
           -4,
           kZOffset,
           kSubWidth,
           kSubHeight,
           kSubDepth,
           kFormat,
           kBucketId);
  bucket->SetSize(kSubImageSize);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // offset + size > texture size
  cmd.Init(kTarget,
           kLevel,
           kXOffset,
           kYOffset + 8,
           kZOffset,
           kSubWidth,
           kSubHeight,
           kSubDepth,
           kFormat,
           kBucketId);
  bucket->SetSize(kSubImageSize);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // offset not a multiple of 4.
  cmd.Init(kTarget,
           kLevel,
           kXOffset,
           kYOffset + 1,
           kZOffset,
           kSubWidth,
           kSubHeight,
           kSubDepth,
           kFormat,
           kBucketId);
  bucket->SetSize(kSubImageSize);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // offset + width not a multlple of 4 .
  cmd.Init(kTarget,
           kLevel,
           kXOffset,
           kYOffset,
           kZOffset,
           kSubWidth,
           kSubHeight + 3,
           kSubDepth,
           kFormat,
           kBucketId);
  const GLsizei kSubImageSize2 = 128;
  bucket->SetSize(kSubImageSize2);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Bad bucket id.
  const uint32_t kBadBucketId = 444;
  cmd.Init(kTarget,
           kLevel,
           kXOffset,
           kYOffset,
           kZOffset,
           kSubWidth,
           kSubHeight,
           kSubDepth,
           kFormat,
           kBadBucketId);
  bucket->SetSize(kSubImageSize);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

  // Bad target
  cmd.Init(GL_RGBA,
           kLevel,
           kXOffset,
           kYOffset,
           kZOffset,
           kSubWidth,
           kSubHeight,
           kSubDepth,
           kFormat,
           kBucketId);
  bucket->SetSize(kSubImageSize);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  // Bad format
  cmd.Init(kTarget,
           kLevel,
           kXOffset,
           kYOffset,
           kZOffset,
           kSubWidth,
           kSubHeight,
           kSubDepth,
           GL_ONE,
           kBucketId);
  bucket->SetSize(kSubImageSize);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES3DecoderTest, CompressedTexSubImage3DBadSHM) {
  const uint32_t kBucketId = 123;
  const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
  const GLint kLevel = 0;
  const GLenum kInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC;
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 8;
  const GLsizei kDepth = 4;
  const GLint kBorder = 0;
  CommonDecoder::Bucket* bucket = decoder_->CreateBucket(kBucketId);
  ASSERT_TRUE(bucket != nullptr);
  const GLsizei kImageSize = 128;
  bucket->SetSize(kImageSize);

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);

  cmds::CompressedTexImage3DBucket tex_cmd;
  tex_cmd.Init(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth,
               kBucketId);
  EXPECT_CALL(*gl_,
              CompressedTexImage3D(kTarget, kLevel, kInternalFormat, kWidth,
                                   kHeight, kDepth, kBorder, kImageSize, _))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(tex_cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  const GLint kXOffset = 0;
  const GLint kYOffset = 0;
  const GLint kZOffset = 0;
  const GLint kSubWidth = 4;
  const GLint kSubHeight = 4;
  const GLint kSubDepth = 4;
  const GLenum kFormat = kInternalFormat;
  const GLsizei kSubImageSize = 64;
  const GLsizei kBadSubImageSize = 65;
  cmds::CompressedTexSubImage3D cmd;

  // Invalid args + NULL SHM -> GL error
  cmd.Init(kTarget, kLevel, kXOffset, kYOffset, kZOffset, kSubWidth, kSubHeight,
           kSubDepth, kFormat, kBadSubImageSize, 0, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // Valid args + non-empty size + NULL SHM -> command buffer error
  cmd.Init(kTarget, kLevel, kXOffset, kYOffset, kZOffset, kSubWidth, kSubHeight,
           kSubDepth, kFormat, kSubImageSize, 0, 0);
  EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(cmd));
  // Valid args + non-empty size + invalid SHM -> command buffer error
  cmd.Init(kTarget, kLevel, kXOffset, kYOffset, kZOffset, kSubWidth, kSubHeight,
           kSubDepth, kFormat, kSubImageSize, 0, kSharedMemoryOffset);
  EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(cmd));

  // Valid args + empty size + NULL SHM -> no error (NOOP).
  EXPECT_CALL(*gl_,
              CompressedTexSubImage3DNoData(kTarget, kLevel, kXOffset, kYOffset,
                                            kZOffset, 0, 0, 0, kFormat, 0))
      .Times(1)
      .RetiresOnSaturation();
  cmd.Init(kTarget, kLevel, kXOffset, kYOffset, kZOffset, 0, 0, 0, kFormat, 0,
           0, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, CompressedTexImage2DBucketBucketSizeIsZero) {
  const uint32_t kBucketId = 123;
  const uint32_t kBadBucketId = 99;
  const GLenum kTarget = GL_TEXTURE_2D;
  const GLint kLevel = 0;
  const GLenum kInternalFormat = GL_COMPRESSED_R11_EAC;
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 4;
  const GLint kBorder = 0;
  CommonDecoder::Bucket* bucket = decoder_->CreateBucket(kBucketId);
  ASSERT_TRUE(bucket != nullptr);
  const GLsizei kImageSize = 0;
  bucket->SetSize(kImageSize);

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);

  // Bad bucket
  cmds::CompressedTexImage2DBucket cmd;
  cmd.Init(kTarget,
           kLevel,
           kInternalFormat,
           0,
           kHeight,
           kBadBucketId);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));

  // Bucket size is zero. Height or width is zero too.
  cmd.Init(kTarget,
           kLevel,
           kInternalFormat,
           0,
           kHeight,
           kBucketId);
  EXPECT_CALL(*gl_,
              CompressedTexImage2D(kTarget, kLevel, kInternalFormat, 0,
                                   kHeight, kBorder, kImageSize, _))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Bucket size is zero. But height and width are not zero.
  cmd.Init(kTarget,
           kLevel,
           kInternalFormat,
           kWidth,
           kHeight,
           kBucketId);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, CompressedTexImage2DBucketBadBucket) {
  InitState init;
  init.extensions = "GL_EXT_texture_compression_s3tc";
  InitDecoder(init);

  const uint32_t kBadBucketId = 123;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  cmds::CompressedTexImage2DBucket cmd;
  cmd.Init(GL_TEXTURE_2D,
           0,
           GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
           4,
           4,
           kBadBucketId);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
  cmds::CompressedTexSubImage2DBucket cmd2;
  cmd2.Init(GL_TEXTURE_2D,
            0,
            0,
            0,
            4,
            4,
            GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
            kBadBucketId);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}

namespace {

struct S3TCTestData {
  GLenum format;
  size_t block_size;
};

}  // anonymous namespace.

TEST_P(GLES2DecoderManualInitTest, CompressedTexImage2DS3TCWebGL) {
  InitState init;
  init.extensions = "GL_EXT_texture_compression_s3tc";
  init.context_type = CONTEXT_TYPE_WEBGL1;
  InitDecoder(init);
  const uint32_t kBucketId = 123;
  CommonDecoder::Bucket* bucket = decoder_->CreateBucket(kBucketId);
  ASSERT_TRUE(bucket != nullptr);

  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);

  constexpr auto test_data = std::to_array<S3TCTestData>({
      {
          GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
          8,
      },
      {
          GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
          8,
      },
      {
          GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
          16,
      },
      {
          GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
          16,
      },
  });

  for (const auto& test : test_data) {
    cmds::CompressedTexImage2DBucket cmd;
    // test small width.
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 1, test.format, 2, 4, 0, test.block_size, kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    // test bad width.
    cmd.Init(GL_TEXTURE_2D, 0, test.format, 5, 4, kBucketId);
    bucket->SetSize(test.block_size * 2);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // test small height.
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 1, test.format, 4, 2, 0, test.block_size, kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    // test too bad height.
    cmd.Init(GL_TEXTURE_2D, 0, test.format, 4, 5, kBucketId);
    bucket->SetSize(test.block_size * 2);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // test small for level 0.
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 1, test.format, 1, 1, 0, test.block_size, kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    // test small for level 0.
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 1, test.format, 2, 2, 0, test.block_size, kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    // test size too large.
    cmd.Init(GL_TEXTURE_2D, 0, test.format, 4, 4, kBucketId);
    bucket->SetSize(test.block_size * 2);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

    // test size too small.
    cmd.Init(GL_TEXTURE_2D, 0, test.format, 4, 4, kBucketId);
    bucket->SetSize(test.block_size / 2);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

    // test with 3 mips.
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 0, test.format, 4, 4, 0, test.block_size, kBucketId);
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 1, test.format, 2, 2, 0, test.block_size, kBucketId);
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 2, test.format, 1, 1, 0, test.block_size, kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    // Test a 16x16
    DoCompressedTexImage2D(GL_TEXTURE_2D,
                           0,
                           test.format,
                           16,
                           16,
                           0,
                           test.block_size * 4 * 4,
                           kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    cmds::CompressedTexSubImage2DBucket sub_cmd;
    bucket->SetSize(test.block_size);
    // Test sub image bad xoffset
    sub_cmd.Init(GL_TEXTURE_2D, 0, 1, 0, 4, 4, test.format, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // Test sub image bad yoffset
    sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 2, 4, 4, test.format, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // Test sub image bad width
    bucket->SetSize(test.block_size * 2);
    sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 5, 4, test.format, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // Test sub image bad height
    sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 4, 5, test.format, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // Test sub image bad size
    bucket->SetSize(test.block_size + 1);
    sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 4, 4, test.format, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

    for (GLint yoffset = 0; yoffset <= 8; yoffset += 4) {
      for (GLint xoffset = 0; xoffset <= 8; xoffset += 4) {
        for (GLsizei height = 4; height <= 8; height += 4) {
          for (GLsizei width = 4; width <= 8; width += 4) {
            GLsizei size = test.block_size * (width / 4) * (height / 4);
            bucket->SetSize(size);
            EXPECT_CALL(*gl_,
                        CompressedTexSubImage2D(GL_TEXTURE_2D,
                                                0,
                                                xoffset,
                                                yoffset,
                                                width,
                                                height,
                                                test.format,
                                                size,
                                                _))
                .Times(1)
                .RetiresOnSaturation();
            sub_cmd.Init(GL_TEXTURE_2D,
                         0,
                         xoffset,
                         yoffset,
                         width,
                         height,
                         test.format,
                         kBucketId);
            EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
            EXPECT_EQ(GL_NO_ERROR, GetGLError());
          }
        }
      }
    }
  }
}

TEST_P(GLES2DecoderManualInitTest, CompressedTexImage2DS3TC) {
  InitState init;
  init.extensions = "GL_EXT_texture_compression_s3tc";
  InitDecoder(init);
  const uint32_t kBucketId = 123;
  CommonDecoder::Bucket* bucket = decoder_->CreateBucket(kBucketId);
  ASSERT_TRUE(bucket != nullptr);

  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);

  constexpr auto test_data = std::to_array<S3TCTestData>({
      {
          GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
          8,
      },
      {
          GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
          8,
      },
      {
          GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
          16,
      },
      {
          GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
          16,
      },
  });

  for (const auto& test : test_data) {
    cmds::CompressedTexImage2DBucket cmd;
    // test small width.
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 1, test.format, 2, 4, 0, test.block_size, kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    // test non-block-size width.
    bucket->SetSize(test.block_size * 2);
    cmd.Init(GL_TEXTURE_2D, 0, test.format, 5, 4, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // test small height.
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 1, test.format, 4, 2, 0, test.block_size, kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    // test non-block-size height.
    cmd.Init(GL_TEXTURE_2D, 0, test.format, 4, 5, kBucketId);
    bucket->SetSize(test.block_size * 2);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // test small for level 0.
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 1, test.format, 1, 1, 0, test.block_size, kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    // test small for level 0.
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 1, test.format, 2, 2, 0, test.block_size, kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    // test size too large.
    cmd.Init(GL_TEXTURE_2D, 0, test.format, 4, 4, kBucketId);
    bucket->SetSize(test.block_size * 2);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

    // test size too small.
    cmd.Init(GL_TEXTURE_2D, 0, test.format, 4, 4, kBucketId);
    bucket->SetSize(test.block_size / 2);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

    // test with 3 mips.
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 0, test.format, 4, 4, 0, test.block_size, kBucketId);
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 1, test.format, 2, 2, 0, test.block_size, kBucketId);
    DoCompressedTexImage2D(
        GL_TEXTURE_2D, 2, test.format, 1, 1, 0, test.block_size, kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    // Test a 16x16
    DoCompressedTexImage2D(GL_TEXTURE_2D,
                           0,
                           test.format,
                           16,
                           16,
                           0,
                           test.block_size * 4 * 4,
                           kBucketId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    cmds::CompressedTexSubImage2DBucket sub_cmd;
    bucket->SetSize(test.block_size);
    // Test sub image bad xoffset
    sub_cmd.Init(GL_TEXTURE_2D, 0, 1, 0, 4, 4, test.format, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // Test sub image bad yoffset
    sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 2, 4, 4, test.format, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // Test sub image bad width
    bucket->SetSize(test.block_size * 2);
    sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 5, 4, test.format, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // Test sub image bad height
    sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 4, 5, test.format, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

    // Test sub image bad size
    bucket->SetSize(test.block_size + 1);
    sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 4, 4, test.format, kBucketId);
    EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

    for (GLint yoffset = 0; yoffset <= 8; yoffset += 4) {
      for (GLint xoffset = 0; xoffset <= 8; xoffset += 4) {
        for (GLsizei height = 4; height <= 8; height += 4) {
          for (GLsizei width = 4; width <= 8; width += 4) {
            GLsizei size = test.block_size * (width / 4) * (height / 4);
            bucket->SetSize(size);
            EXPECT_CALL(*gl_,
                        CompressedTexSubImage2D(GL_TEXTURE_2D,
                                                0,
                                                xoffset,
                                                yoffset,
                                                width,
                                                height,
                                                test.format,
                                                size,
                                                _))
                .Times(1)
                .RetiresOnSaturation();
            sub_cmd.Init(GL_TEXTURE_2D,
                         0,
                         xoffset,
                         yoffset,
                         width,
                         height,
                         test.format,
                         kBucketId);
            EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
            EXPECT_EQ(GL_NO_ERROR, GetGLError());
          }
        }
      }
    }
  }
}

TEST_P(GLES2DecoderManualInitTest, CompressedTexImage2DETC1) {
  InitState init;
  init.extensions = "GL_OES_compressed_ETC1_RGB8_texture";
  init.gl_version = "OpenGL ES 2.0";
  InitDecoder(init);
  const uint32_t kBucketId = 123;
  CommonDecoder::Bucket* bucket = decoder_->CreateBucket(kBucketId);
  ASSERT_TRUE(bucket != nullptr);

  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);

  const GLenum kFormat = GL_ETC1_RGB8_OES;
  const size_t kBlockSize = 8;

  cmds::CompressedTexImage2DBucket cmd;
  // test small width.
  DoCompressedTexImage2D(GL_TEXTURE_2D, 0, kFormat, 4, 8, 0, 16, kBucketId);
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // test small height.
  DoCompressedTexImage2D(GL_TEXTURE_2D, 0, kFormat, 8, 4, 0, 16, kBucketId);
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // test size too large.
  cmd.Init(GL_TEXTURE_2D, 0, kFormat, 4, 4, kBucketId);
  bucket->SetSize(kBlockSize * 2);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // test size too small.
  cmd.Init(GL_TEXTURE_2D, 0, kFormat, 4, 4, kBucketId);
  bucket->SetSize(kBlockSize / 2);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  // Test a 16x16
  DoCompressedTexImage2D(
      GL_TEXTURE_2D, 0, kFormat, 16, 16, 0, kBlockSize * 16, kBucketId);
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Test CompressedTexSubImage not allowed
  cmds::CompressedTexSubImage2DBucket sub_cmd;
  bucket->SetSize(kBlockSize);
  sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 4, 4, kFormat, kBucketId);
  EXPECT_EQ(error::kNoError, ExecuteCmd(sub_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Test TexSubImage not allowed for ETC1 compressed texture
  TextureRef* texture_ref = GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  GLenum type, internal_format;
  EXPECT_TRUE(texture->GetLevelType(GL_TEXTURE_2D, 0, &type, &internal_format));
  EXPECT_EQ(kFormat, internal_format);
  cmds::TexSubImage2D texsub_cmd;
  texsub_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE,
                  shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(texsub_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Test CopyTexSubImage not allowed for ETC1 compressed texture
  cmds::CopyTexSubImage2D copy_cmd;
  copy_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 4, 4);
  EXPECT_EQ(error::kNoError, ExecuteCmd(copy_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderTest, CopyTextureCHROMIUMBadTarget) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 17, 0, GL_RGBA, GL_UNSIGNED_BYTE,
               0, 0);

  EXPECT_CALL(*gl_, GenTextures(_, _))
      .WillOnce(SetArgPointee<1>(kNewServiceId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kNewClientId);

  const GLenum kBadTarget = GL_RGB;
  cmds::CopyTextureCHROMIUM cmd;
  cmd.Init(client_texture_id_, 0, kBadTarget, kNewClientId, 0, GL_RGBA,
           GL_UNSIGNED_BYTE, GL_FALSE, GL_FALSE, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderTest, CopySubTextureCHROMIUMBadTarget) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 17, 0, GL_RGBA, GL_UNSIGNED_BYTE,
               0, 0);

  EXPECT_CALL(*gl_, GenTextures(_, _))
      .WillOnce(SetArgPointee<1>(kNewServiceId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kNewClientId);
  DoBindTexture(GL_TEXTURE_2D, kNewClientId, kNewServiceId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 17, 0, GL_RGBA, GL_UNSIGNED_BYTE,
               0, 0);

  const GLenum kBadTarget = GL_RGB;
  cmds::CopySubTextureCHROMIUM cmd;
  cmd.Init(client_texture_id_, 0, kBadTarget, kNewClientId, 0, 1, 1, 2, 2, 3, 3,
           GL_FALSE, GL_FALSE, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, EGLImageExternalBindTexture) {
  InitState init;
  init.extensions = "GL_OES_EGL_image_external";
  init.gl_version = "OpenGL ES 2.0";
  InitDecoder(init);
  EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_EXTERNAL_OES, kNewServiceId));
  EXPECT_CALL(*gl_, GenTextures(1, _))
      .WillOnce(SetArgPointee<1>(kNewServiceId));
  GenHelper<cmds::GenTexturesImmediate>(kNewClientId);
  cmds::BindTexture cmd;
  cmd.Init(GL_TEXTURE_EXTERNAL_OES, kNewClientId);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  TextureRef* texture_ref = GetTexture(kNewClientId);
  EXPECT_TRUE(texture_ref != nullptr);
  EXPECT_TRUE(texture_ref->texture()->target() == GL_TEXTURE_EXTERNAL_OES);
}

TEST_P(GLES2DecoderManualInitTest, EGLImageExternalGetBinding) {
  InitState init;
  init.extensions = "GL_OES_EGL_image_external";
  init.gl_version = "OpenGL ES 2.0";
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_EXTERNAL_OES, client_texture_id_, kServiceTextureId);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  auto* result =
      static_cast<cmds::GetIntegerv::Result*>(shared_memory_address_);
  EXPECT_CALL(*gl_,
              GetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, result->GetData()))
      .Times(0);
  result->size = 0;
  cmds::GetIntegerv cmd;
  cmd.Init(GL_TEXTURE_BINDING_EXTERNAL_OES,
           shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(decoder_->GetGLES2Util()->GLGetNumValuesReturned(
                GL_TEXTURE_BINDING_EXTERNAL_OES),
            result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(client_texture_id_, (uint32_t)*result->GetData());
}

TEST_P(GLES2DecoderManualInitTest, EGLImageExternalTextureDefaults) {
  InitState init;
  init.extensions = "GL_OES_EGL_image_external";
  init.gl_version = "OpenGL ES 2.0";
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_EXTERNAL_OES, client_texture_id_, kServiceTextureId);

  TextureRef* texture_ref = GetTexture(client_texture_id_);
  EXPECT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_TRUE(texture->target() == GL_TEXTURE_EXTERNAL_OES);
  EXPECT_TRUE(texture->min_filter() == GL_LINEAR);
  EXPECT_TRUE(texture->wrap_s() == GL_CLAMP_TO_EDGE);
  EXPECT_TRUE(texture->wrap_t() == GL_CLAMP_TO_EDGE);
}

TEST_P(GLES2DecoderManualInitTest, EGLImageExternalTextureParam) {
  InitState init;
  init.extensions = "GL_OES_EGL_image_external";
  init.gl_version = "OpenGL ES 2.0";
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_EXTERNAL_OES, client_texture_id_, kServiceTextureId);

  EXPECT_CALL(*gl_,
              TexParameteri(
                  GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
  EXPECT_CALL(
      *gl_,
      TexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  EXPECT_CALL(
      *gl_,
      TexParameteri(
          GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
  EXPECT_CALL(
      *gl_,
      TexParameteri(
          GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
  cmds::TexParameteri cmd;
  cmd.Init(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmd.Init(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmd.Init(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmd.Init(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  TextureRef* texture_ref = GetTexture(client_texture_id_);
  EXPECT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_TRUE(texture->target() == GL_TEXTURE_EXTERNAL_OES);
  EXPECT_TRUE(texture->min_filter() == GL_LINEAR);
  EXPECT_TRUE(texture->wrap_s() == GL_CLAMP_TO_EDGE);
  EXPECT_TRUE(texture->wrap_t() == GL_CLAMP_TO_EDGE);
}

TEST_P(GLES2DecoderManualInitTest, EGLImageExternalTextureParamInvalid) {
  InitState init;
  init.extensions = "GL_OES_EGL_image_external";
  init.gl_version = "OpenGL ES 2.0";
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_EXTERNAL_OES, client_texture_id_, kServiceTextureId);

  cmds::TexParameteri cmd;
  cmd.Init(GL_TEXTURE_EXTERNAL_OES,
           GL_TEXTURE_MIN_FILTER,
           GL_NEAREST_MIPMAP_NEAREST);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  cmd.Init(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_REPEAT);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  cmd.Init(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_REPEAT);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  TextureRef* texture_ref = GetTexture(client_texture_id_);
  EXPECT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_TRUE(texture->target() == GL_TEXTURE_EXTERNAL_OES);
  EXPECT_TRUE(texture->min_filter() == GL_LINEAR);
  EXPECT_TRUE(texture->wrap_s() == GL_CLAMP_TO_EDGE);
  EXPECT_TRUE(texture->wrap_t() == GL_CLAMP_TO_EDGE);
}

TEST_P(GLES2DecoderManualInitTest, EGLImageExternalTexImage2DError) {
  InitState init;
  init.extensions = "GL_OES_EGL_image_external";
  init.gl_version = "OpenGL ES 2.0";
  InitDecoder(init);

  GLenum target = GL_TEXTURE_EXTERNAL_OES;
  GLint level = 0;
  GLenum internal_format = GL_RGBA;
  GLsizei width = 2;
  GLsizei height = 4;
  GLenum format = GL_RGBA;
  GLenum type = GL_UNSIGNED_BYTE;
  DoBindTexture(GL_TEXTURE_EXTERNAL_OES, client_texture_id_, kServiceTextureId);
  ASSERT_TRUE(GetTexture(client_texture_id_) != nullptr);
  cmds::TexImage2D cmd;
  cmd.Init(target, level, internal_format, width, height, format, type,
           shared_memory_id_, kSharedMemoryOffset);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));

  // TexImage2D is not allowed with GL_TEXTURE_EXTERNAL_OES targets.
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, DefaultTextureZero) {
  InitState init;
  InitDecoder(init);

  cmds::BindTexture cmd1;
  cmd1.Init(GL_TEXTURE_2D, 0);
  EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_2D, 0));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmds::BindTexture cmd2;
  cmd2.Init(GL_TEXTURE_CUBE_MAP, 0);
  EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_CUBE_MAP, 0));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, DefaultTextureBGR) {
  InitState init;
  InitDecoder(init);

  cmds::BindTexture cmd1;
  cmd1.Init(GL_TEXTURE_2D, 0);
  EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_2D, 0));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmds::BindTexture cmd2;
  cmd2.Init(GL_TEXTURE_CUBE_MAP, 0);
  EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_CUBE_MAP, 0));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

// Test that default texture 0 is immutable.
TEST_P(GLES2DecoderManualInitTest, NoDefaultTexParameterf) {
  InitState init;
  InitDecoder(init);

  {
    cmds::BindTexture cmd1;
    cmd1.Init(GL_TEXTURE_2D, 0);
    EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_2D, 0));
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    cmds::TexParameterf cmd2;
    cmd2.Init(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }

  {
    cmds::BindTexture cmd1;
    cmd1.Init(GL_TEXTURE_CUBE_MAP, 0);
    EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_CUBE_MAP, 0));
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    cmds::TexParameterf cmd2;
    cmd2.Init(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }
}

TEST_P(GLES2DecoderManualInitTest, NoDefaultTexParameteri) {
  InitState init;
  InitDecoder(init);

  {
    cmds::BindTexture cmd1;
    cmd1.Init(GL_TEXTURE_2D, 0);
    EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_2D, 0));
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    cmds::TexParameteri cmd2;
    cmd2.Init(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }

  {
    cmds::BindTexture cmd1;
    cmd1.Init(GL_TEXTURE_CUBE_MAP, 0);
    EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_CUBE_MAP, 0));
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    cmds::TexParameteri cmd2;
    cmd2.Init(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }
}

TEST_P(GLES2DecoderManualInitTest, NoDefaultTexParameterfv) {
  InitState init;
  InitDecoder(init);

  {
    cmds::BindTexture cmd1;
    cmd1.Init(GL_TEXTURE_2D, 0);
    EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_2D, 0));
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    GLfloat data = GL_NEAREST;
    auto& cmd2 = *GetImmediateAs<cmds::TexParameterfvImmediate>();
    cmd2.Init(GL_TEXTURE_2D,
              GL_TEXTURE_MAG_FILTER,
              &data);
    EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd2, sizeof(data)));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }

  {
    cmds::BindTexture cmd1;
    cmd1.Init(GL_TEXTURE_CUBE_MAP, 0);
    EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_CUBE_MAP, 0));
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    GLfloat data = GL_NEAREST;
    auto& cmd2 = *GetImmediateAs<cmds::TexParameterfvImmediate>();
    cmd2.Init(GL_TEXTURE_CUBE_MAP,
              GL_TEXTURE_MAG_FILTER,
              &data);
    EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd2, sizeof(data)));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }
}

TEST_P(GLES2DecoderManualInitTest, NoDefaultTexParameteriv) {
  InitState init;
  InitDecoder(init);

  {
    cmds::BindTexture cmd1;
    cmd1.Init(GL_TEXTURE_2D, 0);
    EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_2D, 0));
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    GLfloat data = GL_NEAREST;
    auto& cmd2 = *GetImmediateAs<cmds::TexParameterfvImmediate>();
    cmd2.Init(GL_TEXTURE_2D,
              GL_TEXTURE_MAG_FILTER,
              &data);
    EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd2, sizeof(data)));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }

  {
    cmds::BindTexture cmd1;
    cmd1.Init(GL_TEXTURE_CUBE_MAP, 0);
    EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_CUBE_MAP, 0));
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    GLfloat data = GL_NEAREST;
    auto& cmd2 = *GetImmediateAs<cmds::TexParameterfvImmediate>();
    cmd2.Init(GL_TEXTURE_CUBE_MAP,
              GL_TEXTURE_MAG_FILTER,
              &data);
    EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd2, sizeof(data)));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }
}

TEST_P(GLES2DecoderManualInitTest, NoDefaultTexImage2D) {
  InitState init;
  InitDecoder(init);

  cmds::BindTexture cmd1;
  cmd1.Init(GL_TEXTURE_2D, 0);
  EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_2D, 0));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmds::TexImage2D cmd2;
  cmd2.Init(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE,
            shared_memory_id_, kSharedMemoryOffset);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, NoDefaultTexSubImage2D) {
  InitState init;
  InitDecoder(init);

  cmds::BindTexture cmd1;
  cmd1.Init(GL_TEXTURE_2D, 0);
  EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_2D, 0));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd1));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmds::TexSubImage2D cmd2;
  cmd2.Init(GL_TEXTURE_2D, 0, 1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
            shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, ARBTextureRectangleBindTexture) {
  InitState init;
  init.extensions = "GL_ANGLE_texture_rectangle";
  InitDecoder(init);
  EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_RECTANGLE_ANGLE, kServiceTextureId));
  cmds::BindTexture cmd;
  cmd.Init(GL_TEXTURE_RECTANGLE_ANGLE, client_texture_id_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  Texture* texture = GetTexture(client_texture_id_)->texture();
  EXPECT_TRUE(texture != nullptr);
  EXPECT_TRUE(texture->target() == GL_TEXTURE_RECTANGLE_ANGLE);
}

TEST_P(GLES2DecoderManualInitTest, ARBTextureRectangleGetBinding) {
  InitState init;
  init.extensions = "GL_ANGLE_texture_rectangle";
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, client_texture_id_,
                kServiceTextureId);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  auto* result =
      static_cast<cmds::GetIntegerv::Result*>(shared_memory_address_);
  EXPECT_CALL(
      *gl_, GetIntegerv(GL_TEXTURE_BINDING_RECTANGLE_ANGLE, result->GetData()))
      .Times(0);
  result->size = 0;
  cmds::GetIntegerv cmd;
  cmd.Init(GL_TEXTURE_BINDING_RECTANGLE_ANGLE, shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(decoder_->GetGLES2Util()->GLGetNumValuesReturned(
                GL_TEXTURE_BINDING_RECTANGLE_ANGLE),
            result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(client_texture_id_, (uint32_t)*result->GetData());
}

TEST_P(GLES2DecoderManualInitTest, ARBTextureRectangleTextureDefaults) {
  InitState init;
  init.extensions = "GL_ANGLE_texture_rectangle";
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, client_texture_id_,
                kServiceTextureId);

  Texture* texture = GetTexture(client_texture_id_)->texture();
  EXPECT_TRUE(texture != nullptr);
  EXPECT_TRUE(texture->target() == GL_TEXTURE_RECTANGLE_ANGLE);
  EXPECT_TRUE(texture->min_filter() == GL_LINEAR);
  EXPECT_TRUE(texture->wrap_s() == GL_CLAMP_TO_EDGE);
  EXPECT_TRUE(texture->wrap_t() == GL_CLAMP_TO_EDGE);
}

TEST_P(GLES2DecoderManualInitTest, ARBTextureRectangleTextureParam) {
  InitState init;
  init.extensions = "GL_ANGLE_texture_rectangle";
  InitDecoder(init);

  DoBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, client_texture_id_,
                kServiceTextureId);

  EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_RECTANGLE_ANGLE,
                                  GL_TEXTURE_MIN_FILTER, GL_NEAREST));
  EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_RECTANGLE_ANGLE,
                                  GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_S,
                                  GL_CLAMP_TO_EDGE));
  EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_T,
                                  GL_CLAMP_TO_EDGE));
  cmds::TexParameteri cmd;
  cmd.Init(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmd.Init(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmd.Init(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmd.Init(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  Texture* texture = GetTexture(client_texture_id_)->texture();
  EXPECT_TRUE(texture != nullptr);
  EXPECT_TRUE(texture->target() == GL_TEXTURE_RECTANGLE_ANGLE);
  EXPECT_TRUE(texture->min_filter() == GL_LINEAR);
  EXPECT_TRUE(texture->wrap_s() == GL_CLAMP_TO_EDGE);
  EXPECT_TRUE(texture->wrap_t() == GL_CLAMP_TO_EDGE);
}

TEST_P(GLES2DecoderManualInitTest, ARBTextureRectangleTextureParamInvalid) {
  InitState init;
  init.extensions = "GL_ANGLE_texture_rectangle";
  InitDecoder(init);

  DoBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, client_texture_id_,
                kServiceTextureId);

  cmds::TexParameteri cmd;
  cmd.Init(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_MIN_FILTER,
           GL_NEAREST_MIPMAP_NEAREST);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  cmd.Init(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_S, GL_REPEAT);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  cmd.Init(GL_TEXTURE_RECTANGLE_ANGLE, GL_TEXTURE_WRAP_T, GL_REPEAT);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  Texture* texture = GetTexture(client_texture_id_)->texture();
  EXPECT_TRUE(texture != nullptr);
  EXPECT_TRUE(texture->target() == GL_TEXTURE_RECTANGLE_ANGLE);
  EXPECT_TRUE(texture->min_filter() == GL_LINEAR);
  EXPECT_TRUE(texture->wrap_s() == GL_CLAMP_TO_EDGE);
  EXPECT_TRUE(texture->wrap_t() == GL_CLAMP_TO_EDGE);
}

TEST_P(GLES2DecoderManualInitTest, ARBTextureRectangleTexImage2D) {
  InitState init;
  init.extensions = "GL_ANGLE_texture_rectangle";
  InitDecoder(init);

  GLenum target = GL_TEXTURE_RECTANGLE_ANGLE;
  GLint level = 0;
  GLenum internal_format = GL_RGBA;
  GLsizei width = 2;
  GLsizei height = 4;
  GLenum format = GL_RGBA;
  GLenum type = GL_UNSIGNED_BYTE;

  DoBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, client_texture_id_,
                kServiceTextureId);
  ASSERT_TRUE(GetTexture(client_texture_id_) != nullptr);

  cmds::TexImage2D cmd;
  cmd.Init(target, level, internal_format, width, height, format, type,
           shared_memory_id_, kSharedMemoryOffset);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));

  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, ARBTextureRectangleTexImage2DInvalid) {
  InitState init;
  init.extensions = "GL_ANGLE_texture_rectangle";
  InitDecoder(init);

  GLenum target = GL_TEXTURE_RECTANGLE_ANGLE;
  GLint level = 1;
  GLenum internal_format = GL_RGBA;
  GLsizei width = 2;
  GLsizei height = 4;
  GLenum format = GL_RGBA;
  GLenum type = GL_UNSIGNED_BYTE;

  DoBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, client_texture_id_,
                kServiceTextureId);
  ASSERT_TRUE(GetTexture(client_texture_id_) != nullptr);

  cmds::TexImage2D cmd;
  cmd.Init(target, level, internal_format, width, height, format, type,
           shared_memory_id_, kSharedMemoryOffset);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));

  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderTest, TexSubImage2DClearsAfterTexImage2DNULL) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);
  SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
                                GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
                                GL_UNSIGNED_BYTE, 0, 1, 2, 1, 0);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, _, 1, GL_RGBA,
                            GL_UNSIGNED_BYTE, shared_memory_address_.get()))
      .Times(2)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  cmd.Init(GL_TEXTURE_2D, 0, 0, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  // Test if we call it again it does not clear.
  EXPECT_CALL(*gl_,
              TexSubImage2D(GL_TEXTURE_2D, 0, 0, 1, 1, 1, GL_RGBA,
                            GL_UNSIGNED_BYTE, shared_memory_address_.get()))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}

TEST_P(GLES2DecoderTest, TexSubImage2DDoesNotClearAfterTexImage2DNULLThenData) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
               shared_memory_id_, kSharedMemoryOffset);
  EXPECT_CALL(*gl_,
              TexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 1, 1, GL_RGBA,
                            GL_UNSIGNED_BYTE, shared_memory_address_.get()))
      .Times(1)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, 1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  // Test if we call it again it does not clear.
  EXPECT_CALL(*gl_,
              TexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 1, 1, GL_RGBA,
                            GL_UNSIGNED_BYTE, shared_memory_address_.get()))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}

TEST_P(GLES2DecoderTest, TexSubImage2DClearsAfterTexImage2DWithDataThenNULL) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  // Put in data (so it should be marked as cleared)
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
               shared_memory_id_, kSharedMemoryOffset);
  // Put in no data.
  cmds::TexImage2D tex_cmd;
  tex_cmd.Init(
      GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);
  // It won't actually call TexImage2D, just mark it as uncleared.
  EXPECT_EQ(error::kNoError, ExecuteCmd(tex_cmd));
  // Next call to TexSubImage2d should clear.
  SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
                                GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
                                GL_UNSIGNED_BYTE, 0, 1, 2, 1, 0);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, _, 1, GL_RGBA,
                            GL_UNSIGNED_BYTE, shared_memory_address_.get()))
      .Times(2)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  cmd.Init(GL_TEXTURE_2D, 0, 0, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}

TEST_P(GLES3DecoderTest, ClearLevelWithBoundUnpackBuffer) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0,
               0);

  EXPECT_CALL(*gl_, PixelStorei(GL_UNPACK_ROW_LENGTH, 0))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0))
      .Times(1)
      .RetiresOnSaturation();
  DoBindBuffer(GL_PIXEL_UNPACK_BUFFER, client_buffer_id_, kServiceBufferId);
  DoBufferData(GL_PIXEL_UNPACK_BUFFER, 8);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA,
                                  GL_UNSIGNED_BYTE, 0))
      .Times(1)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0,
           GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));

  // This TexSubImage2D can't be coalesced with the previous one, so it will
  // force a clear.
  SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
                                GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
                                GL_UNSIGNED_BYTE, 0, 1, 2, 1, kServiceBufferId);

  EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, 1, 1, 1, GL_RGBA,
                                  GL_UNSIGNED_BYTE, 0))
      .Times(1)
      .RetiresOnSaturation();
  cmd.Init(GL_TEXTURE_2D, 0, 0, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0,
           GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}

TEST_P(GLES2DecoderTest, CopyTexImage2DMarksTextureAsCleared) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);

  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, CopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 1, 1, 0))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::CopyTexImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 1, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));

  EXPECT_TRUE(texture->SafeToRenderFrom());
}

TEST_P(GLES2DecoderTest, CopyTexSubImage2DTwiceMarksTextureAsCleared) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);

  // This will initialize the top part.
  {
    EXPECT_CALL(*gl_, CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 1))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    cmds::CopyTexSubImage2D cmd;
    cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 1);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }

  // This will initialize the bottom part.
  {
    EXPECT_CALL(*gl_, CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 1, 0, 0, 2, 1))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    cmds::CopyTexSubImage2D cmd;
    cmd.Init(GL_TEXTURE_2D, 0, 0, 1, 0, 0, 2, 1);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }

  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_TRUE(texture->SafeToRenderFrom());
}

TEST_P(GLES2DecoderTest, CopyTexSubImage2DTwiceClearsUnclearedTexture) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0,
               0);

  // This will initialize the top part.
  {
    EXPECT_CALL(*gl_, CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 1))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    cmds::CopyTexSubImage2D cmd;
    cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 1);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }

  SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
                                GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
                                GL_UNSIGNED_BYTE, 0, 1, 2, 1, 0);

  // This will clear the bottom part as a rectangle is not sufficient to keep
  // track of the initialized area.
  {
    EXPECT_CALL(*gl_, CopyTexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 0, 0, 1, 1))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    cmds::CopyTexSubImage2D cmd;
    cmd.Init(GL_TEXTURE_2D, 0, 1, 1, 0, 0, 1, 1);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }

  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_TRUE(texture->SafeToRenderFrom());
}

TEST_P(GLES2DecoderTest, CopyTexSubImage2DClearsUnclearedBackBufferSizedTexture) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kBackBufferWidth, kBackBufferHeight,
               0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);

  EXPECT_CALL(*gl_, CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0,
                                      kBackBufferWidth, kBackBufferHeight))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::CopyTexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kBackBufferWidth, kBackBufferHeight);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));

  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_TRUE(texture->SafeToRenderFrom());
}

TEST_P(GLES2DecoderManualInitTest, CompressedImage2DMarksTextureAsCleared) {
  InitState init;
  init.extensions = "GL_EXT_texture_compression_s3tc";
  InitDecoder(init);

  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(
      *gl_,
      CompressedTexImage2D(
          GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 0, 8, _))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::CompressedTexImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 8,
           shared_memory_id_, kSharedMemoryOffset);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  EXPECT_TRUE(texture_ref->texture()->SafeToRenderFrom());
}

TEST_P(GLES2DecoderTest, TextureUsageAngleExtNotEnabledByDefault) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);

  cmds::TexParameteri cmd;
  cmd.Init(
      GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderTest, CreateAndTexStorage2DSharedImageCHROMIUM) {
  MemoryTypeTracker memory_tracker(memory_tracker_.get());
  Mailbox mailbox = Mailbox::Generate();
  auto format = viz::SinglePlaneFormat::kRGBA_8888;
  constexpr size_t kEstimatedSize = 0;
  std::unique_ptr<SharedImageRepresentationFactoryRef> shared_image =
      GetSharedImageManager()->Register(
          std::make_unique<TestImageBacking>(
              mailbox,
              SharedImageInfo(format, gfx::Size(10, 10), gfx::ColorSpace(),
                              kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType,
                              {SHARED_IMAGE_USAGE_GLES2_READ,
                               SHARED_IMAGE_USAGE_GLES2_WRITE},
                              "TestLabel"),
              kEstimatedSize, kNewServiceId),
          &memory_tracker);

  auto& cmd = *GetImmediateAs<
      cmds::CreateAndTexStorage2DSharedImageINTERNALImmediate>();
  cmd.Init(kNewClientId, mailbox.name);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(mailbox.name)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Make sure the new client ID is associated with the produced service ID.
  auto* texture_ref = group().texture_manager()->GetTexture(kNewClientId);
  ASSERT_NE(texture_ref, nullptr);
  EXPECT_EQ(kNewServiceId, texture_ref->texture()->service_id());

  // Delete the texture and make sure it is no longer accessible.
  DoDeleteTexture(kNewClientId, kNewServiceId);
  texture_ref = group().texture_manager()->GetTexture(kNewClientId);
  EXPECT_EQ(texture_ref, nullptr);

  shared_image.reset();
}

TEST_P(GLES2DecoderTest,
       CreateAndTexStorage2DSharedImageCHROMIUMInvalidMailbox) {
  MemoryTypeTracker memory_tracker(memory_tracker_.get());

  // Attempt to use an invalid mailbox.
  Mailbox mailbox;
  // We will generate a new texture.
  EXPECT_CALL(*gl_, GenTextures(1, _))
      .WillOnce(SetArgPointee<1>(kNewServiceId))
      .RetiresOnSaturation();

  auto& cmd = *GetImmediateAs<
      cmds::CreateAndTexStorage2DSharedImageINTERNALImmediate>();
  cmd.Init(kNewClientId, mailbox.name);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(mailbox.name)));

  // CreateAndTexStorage2DSharedImage should fail if the mailbox is invalid.
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Make sure the new client_id is associated with a texture ref even though
  // CreateAndTexStorage2DSharedImage failed.
  TextureRef* texture_ref = group().texture_manager()->GetTexture(kNewClientId);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  // New texture should be unbound to a target.
  EXPECT_TRUE(texture->target() == GL_NONE);
  // New texture should have a valid service_id.
  EXPECT_EQ(kNewServiceId, texture->service_id());
}

TEST_P(GLES2DecoderTest,
       CreateAndTexStorage2DSharedImageCHROMIUMPreexistingTexture) {
  // Try to create a mailbox with kNewClientId.
  MemoryTypeTracker memory_tracker(memory_tracker_.get());
  Mailbox mailbox = Mailbox::Generate();
  auto format = viz::SinglePlaneFormat::kRGBA_8888;
  constexpr size_t kEstimatedSize = 0;
  std::unique_ptr<SharedImageRepresentationFactoryRef> shared_image =
      GetSharedImageManager()->Register(
          std::make_unique<TestImageBacking>(
              mailbox,
              SharedImageInfo(format, gfx::Size(10, 10), gfx::ColorSpace(),
                              kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType,
                              {SHARED_IMAGE_USAGE_GLES2_READ,
                               SHARED_IMAGE_USAGE_GLES2_WRITE},
                              "TestLabel"),
              kEstimatedSize, kNewServiceId),
          &memory_tracker);

  auto& cmd = *GetImmediateAs<
      cmds::CreateAndTexStorage2DSharedImageINTERNALImmediate>();
  cmd.Init(client_texture_id_, mailbox.name);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(mailbox.name)));

  // CreateAndTexStorage2DSharedImage should fail.
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // We delete a texture when calling |shared_image| reset().
  EXPECT_CALL(*gl_, DeleteTextures(1, Pointee(kNewServiceId)));
  shared_image.reset();
}

TEST_P(GLES2DecoderTest, BeginEndSharedImageAccessCHROMIUM) {
  MemoryTypeTracker memory_tracker(memory_tracker_.get());
  Mailbox mailbox = Mailbox::Generate();
  auto format = viz::SinglePlaneFormat::kRGBA_8888;
  std::unique_ptr<SharedImageRepresentationFactoryRef> shared_image =
      GetSharedImageManager()->Register(
          std::make_unique<TestImageBacking>(
              mailbox,
              SharedImageInfo(format, gfx::Size(10, 10), gfx::ColorSpace(),
                              kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType,
                              {SHARED_IMAGE_USAGE_GLES2_READ,
                               SHARED_IMAGE_USAGE_GLES2_WRITE},
                              "TestLabel"),
              0, kNewServiceId),
          &memory_tracker);

  auto& cmd = *GetImmediateAs<
      cmds::CreateAndTexStorage2DSharedImageINTERNALImmediate>();
  cmd.Init(kNewClientId, mailbox.name);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(mailbox.name)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Begin/end read access for the created image.
  cmds::BeginSharedImageAccessDirectCHROMIUM read_access_cmd;
  read_access_cmd.Init(kNewClientId, GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
  EXPECT_EQ(error::kNoError, ExecuteCmd(read_access_cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  cmds::EndSharedImageAccessDirectCHROMIUM read_end_cmd;
  read_end_cmd.Init(kNewClientId);
  EXPECT_EQ(error::kNoError, ExecuteCmd(read_end_cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Begin/end read/write access for the created image.
  cmds::BeginSharedImageAccessDirectCHROMIUM readwrite_access_cmd;
  readwrite_access_cmd.Init(kNewClientId,
                            GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
  EXPECT_EQ(error::kNoError, ExecuteCmd(readwrite_access_cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  cmds::EndSharedImageAccessDirectCHROMIUM readwrite_end_cmd;
  readwrite_end_cmd.Init(kNewClientId);
  EXPECT_EQ(error::kNoError, ExecuteCmd(readwrite_end_cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Cleanup
  DoDeleteTexture(kNewClientId, kNewServiceId);
  shared_image.reset();
}

TEST_P(GLES2DecoderTest, BeginSharedImageAccessDirectCHROMIUMInvalidMode) {
  // Try to begin access with an invalid mode.
  cmds::BeginSharedImageAccessDirectCHROMIUM bad_mode_access_cmd;
  bad_mode_access_cmd.Init(client_texture_id_, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(bad_mode_access_cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderTest, BeginSharedImageAccessDirectCHROMIUMNotSharedImage) {
  // Try to begin access with a texture that is not a shared image.
  cmds::BeginSharedImageAccessDirectCHROMIUM not_shared_image_access_cmd;
  not_shared_image_access_cmd.Init(
      client_texture_id_, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
  EXPECT_EQ(error::kNoError, ExecuteCmd(not_shared_image_access_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderTest, BeginSharedImageAccessDirectCHROMIUMCantBeginAccess) {
  // Create a shared image.
  MemoryTypeTracker memory_tracker(memory_tracker_.get());
  Mailbox mailbox = Mailbox::Generate();
  auto format = viz::SinglePlaneFormat::kRGBA_8888;
  auto shared_image_backing = std::make_unique<TestImageBacking>(
      mailbox,
      SharedImageInfo(format, gfx::Size(10, 10), gfx::ColorSpace(),
                      kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType,
                      {SHARED_IMAGE_USAGE_GLES2_READ}, "TestLabel"),
      /*estimated_size=*/0, kNewServiceId);
  // Set the shared image to fail BeginAccess.
  shared_image_backing->set_can_access(false);
  std::unique_ptr<SharedImageRepresentationFactoryRef> shared_image =
      GetSharedImageManager()->Register(std::move(shared_image_backing),
                                        &memory_tracker);

  auto& cmd = *GetImmediateAs<
      cmds::CreateAndTexStorage2DSharedImageINTERNALImmediate>();
  cmd.Init(kNewClientId, mailbox.name);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(mailbox.name)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Try to begin access with a shared image representation that fails
  // BeginAccess.
  cmds::BeginSharedImageAccessDirectCHROMIUM read_access_cmd;
  read_access_cmd.Init(kNewClientId, GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
  EXPECT_EQ(error::kNoError, ExecuteCmd(read_access_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Cleanup
  DoDeleteTexture(kNewClientId, kNewServiceId);
  shared_image.reset();
}

TEST_P(GLES2DecoderTest, EndSharedImageAccessDirectCHROMIUMNotSharedImage) {
  // Try to end access with a texture that is not a shared image.
  cmds::EndSharedImageAccessDirectCHROMIUM not_shared_image_end_cmd;
  not_shared_image_end_cmd.Init(client_texture_id_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(not_shared_image_end_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, DepthTextureBadArgs) {
  InitState init;
  init.extensions = "GL_ANGLE_depth_texture";
  init.gl_version = "OpenGL ES 2.0";
  init.has_depth = true;
  init.has_stencil = true;
  init.request_depth = true;
  init.request_stencil = true;
  InitDecoder(init);

  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  // Check trying to upload data fails.
  cmds::TexImage2D tex_cmd;
  tex_cmd.Init(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 1, 1, GL_DEPTH_COMPONENT,
               GL_UNSIGNED_INT, shared_memory_id_, kSharedMemoryOffset);
  EXPECT_EQ(error::kNoError, ExecuteCmd(tex_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
  // Try level > 0.
  tex_cmd.Init(GL_TEXTURE_2D,
               1,
               GL_DEPTH_COMPONENT,
               1,
               1,
               GL_DEPTH_COMPONENT,
               GL_UNSIGNED_INT,
               0,
               0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(tex_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
  // Make a 1 pixel depth texture.
  DoTexImage2D(GL_TEXTURE_2D,
               0,
               GL_DEPTH_COMPONENT,
               1,
               1,
               0,
               GL_DEPTH_COMPONENT,
               GL_UNSIGNED_INT,
               0,
               0);
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Check that trying to update it fails.
  cmds::TexSubImage2D tex_sub_cmd;
  tex_sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_DEPTH_COMPONENT,
                   GL_UNSIGNED_INT, shared_memory_id_, kSharedMemoryOffset,
                   GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(tex_sub_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Check that trying to CopyTexImage2D fails
  cmds::CopyTexImage2D copy_tex_cmd;
  copy_tex_cmd.Init(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, 1, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(copy_tex_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  // Check that trying to CopyTexSubImage2D fails
  cmds::CopyTexSubImage2D copy_sub_cmd;
  copy_sub_cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 1, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(copy_sub_cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, GenerateMipmapDepthTexture) {
  InitState init;
  init.extensions = "GL_ANGLE_depth_texture";
  init.gl_version = "OpenGL ES 2.0";
  init.has_depth = true;
  init.has_stencil = true;
  init.request_depth = true;
  init.request_stencil = true;
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D,
               0,
               GL_DEPTH_COMPONENT,
               2,
               2,
               0,
               GL_DEPTH_COMPONENT,
               GL_UNSIGNED_INT,
               0,
               0);
  cmds::GenerateMipmap cmd;
  cmd.Init(GL_TEXTURE_2D);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

class GLES2DecoderEnsurePrevFboWorkaroundTest
    : public GLES2DecoderManualInitTest {
 public:
  GLES2DecoderEnsurePrevFboWorkaroundTest() = default;

  void SetupMockGLBehaviors() override {
    GLES2DecoderManualInitTest::SetupMockGLBehaviors();
    if (enable_workaround_expectations_) {
      EXPECT_CALL(*gl_, GenTextures(1, _))
          .WillOnce(SetArgPointee<1>(kCompleteFboTextureId))
          .RetiresOnSaturation();
      EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_2D, kCompleteFboTextureId))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl_, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA,
                                   GL_UNSIGNED_BYTE, nullptr))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl_, GenFramebuffersEXT(1, _))
          .WillOnce(SetArgPointee<1>(kCompleteFboId))
          .RetiresOnSaturation();
      EXPECT_CALL(*gl_, BindFramebufferEXT(GL_FRAMEBUFFER, kCompleteFboId))
          .Times(3)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl_, FramebufferTexture2DEXT(
                            GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                            kCompleteFboTextureId, 0))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
          .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
          .RetiresOnSaturation();
      EXPECT_CALL(*gl_, DeleteTextures(1, Pointee(kCompleteFboTextureId)))
          .Times(1)
          .RetiresOnSaturation();
    }
  }

 protected:
  bool enable_workaround_expectations_ = false;
  static constexpr GLuint kCompleteFboTextureId = 999;
  static constexpr GLuint kCompleteFboId = 998;
};

// The test uses a depth texture to force GLES2DecoderImpl::ClearLevel to use
// the ClearLevelUsingGL path, which is required to trigger the FBO restoration
// workaround logic. For color textures, ClearLevel may fallback to the
// TexSubImage2D clear path which does not use a temporary FBO.
TEST_P(GLES2DecoderEnsurePrevFboWorkaroundTest, ClearLevelUsingGLOrder) {
  enable_workaround_expectations_ = true;
  gpu::GpuDriverBugWorkarounds workarounds;
  workarounds.ensure_previous_framebuffer_not_deleted = true;
  InitState init;
  init.extensions = "GL_ANGLE_depth_texture";
  init.gl_version = "OpenGL ES 2.0";
  init.has_depth = true;
  init.request_depth = true;
  InitDecoderWithWorkarounds(init, workarounds);

  // Set up texture
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 1, 1, 0,
               GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0, 0);

  TextureRef* texture_ref =
      group().texture_manager()->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();

  // Expectations for ClearLevel
  const GLuint kTempFboId = 777;
  InSequence seq;

  // 1. Gen temp FBO
  EXPECT_CALL(*gl_, GenFramebuffersEXT(1, _))
      .WillOnce(SetArgPointee<1>(kTempFboId))
      .RetiresOnSaturation();

  // 2. Bind temp FBO (wrapper triggers complete_fbo bind first)
  EXPECT_CALL(*gl_, BindFramebufferEXT(GL_FRAMEBUFFER, kCompleteFboId))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, BindFramebufferEXT(GL_FRAMEBUFFER, kTempFboId))
      .Times(1)
      .RetiresOnSaturation();

  // 3. Attach texture
  EXPECT_CALL(*gl_,
              FramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                                      GL_TEXTURE_2D, kServiceTextureId, 0))
      .Times(1)
      .RetiresOnSaturation();

  // 4. Check status
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();

  // 5. Clear calls
  if (GetParam()) {
    EXPECT_CALL(*gl_, ColorMask(true, true, true, true))
        .Times(1)
        .RetiresOnSaturation();
  }
  EXPECT_CALL(*gl_, ClearColor(0.0f, 0.0f, 0.0f, 0.0f))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, ClearStencil(0)).Times(1).RetiresOnSaturation();
  if (GetParam()) {
    EXPECT_CALL(*gl_, StencilMaskSeparate(GL_FRONT, 0xFFFFFFFF))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl_, StencilMaskSeparate(GL_BACK, 0xFFFFFFFF))
        .Times(1)
        .RetiresOnSaturation();
  }
  EXPECT_CALL(*gl_, ClearDepth(1.0f)).Times(1).RetiresOnSaturation();
  if (GetParam()) {
    EXPECT_CALL(*gl_, DepthMask(true)).Times(1).RetiresOnSaturation();
  }
  EXPECT_CALL(*gl_, Enable(GL_SCISSOR_TEST)).Times(1).RetiresOnSaturation();
  EXPECT_CALL(*gl_, Scissor(0, 0, 1, 1)).Times(1).RetiresOnSaturation();
  EXPECT_CALL(*gl_, Clear(GL_DEPTH_BUFFER_BIT)).Times(1).RetiresOnSaturation();

  // 6. Restore state
  EXPECT_CALL(*gl_, ClearColor(0.0f, 0.0f, 0.0f, 0.0f))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, ClearStencil(0)).Times(1).RetiresOnSaturation();
  EXPECT_CALL(*gl_, ClearDepth(1.0f)).Times(1).RetiresOnSaturation();
  EXPECT_CALL(*gl_, Disable(GL_SCISSOR_TEST)).Times(1).RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              Scissor(0, 0, 128, 64))  // 128x64 is the default backbuffer size
      .Times(1)
      .RetiresOnSaturation();

  // 7. Restore FBO binding (CRITICAL: Must happen BEFORE delete)
  // Wrapper triggers complete_fbo bind first
  EXPECT_CALL(*gl_, BindFramebufferEXT(GL_FRAMEBUFFER, kCompleteFboId))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, BindFramebufferEXT(GL_FRAMEBUFFER, 0))
      .Times(1)
      .RetiresOnSaturation();

  // 8. Delete temp FBO
  EXPECT_CALL(*gl_, DeleteFramebuffersEXT(1, Pointee(kTempFboId)))
      .Times(1)
      .RetiresOnSaturation();

  // Call ClearLevel
  EXPECT_TRUE(decoder_->ClearLevel(texture, GL_TEXTURE_2D, 0,
                                   GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0, 0, 1,
                                   1));

  DoDeleteTexture(client_texture_id_, kServiceTextureId);
}

// The boolean parameter controls whether ignore_cached_state_for_test is
// enabled in the base fixture GLES2DecoderTestBase. This forces GL state
// restoration and helps test state-dirtying paths.
INSTANTIATE_TEST_SUITE_P(Service,
                         GLES2DecoderEnsurePrevFboWorkaroundTest,
                         ::testing::Bool());

TEST_P(GLES2DecoderManualInitTest, DrawWithGLImageExternal) {
  InitState init;
  init.extensions = "GL_OES_EGL_image_external";
  init.gl_version = "OpenGL ES 2.0";
  init.has_alpha = true;
  init.has_depth = true;
  init.request_alpha = true;
  init.request_depth = true;
  InitDecoder(init);

  TextureRef* texture_ref = GetTexture(client_texture_id_);
  group().texture_manager()->SetTarget(texture_ref, GL_TEXTURE_EXTERNAL_OES);
  group().texture_manager()->SetLevelInfo(texture_ref, GL_TEXTURE_EXTERNAL_OES,
                                          0, GL_RGBA, 1, 1, 1, 0, GL_RGBA,
                                          GL_UNSIGNED_BYTE, gfx::Rect(1, 1));
  DoBindTexture(GL_TEXTURE_EXTERNAL_OES, client_texture_id_, kServiceTextureId);
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  SetupSamplerExternalProgram();
  SetupIndexBuffer();
  SetupExpectationsForApplyingDefaultDirtyState();
  EXPECT_TRUE(group().texture_manager()->CanRender(texture_ref));

  InSequence s;
  EXPECT_CALL(*gl_, DrawElements(_, _, _, _)).Times(1);
  cmds::DrawElements cmd;
  cmd.Init(GL_TRIANGLES,
           kValidIndexRangeCount,
           GL_UNSIGNED_SHORT,
           kValidIndexRangeStart * 2);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, TexImage2DFloatOnGLES2) {
  InitState init;
  init.extensions = "GL_OES_texture_float";
  init.gl_version = "OpenGL ES 2.0";
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 17, 0, GL_RGBA, GL_FLOAT, 0, 0);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 16, 17, 0, GL_RGB, GL_FLOAT, 0, 0);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_LUMINANCE, 16, 17, 0, GL_LUMINANCE, GL_FLOAT, 0, 0);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 16, 17, 0, GL_ALPHA, GL_FLOAT, 0, 0);
  DoTexImage2D(GL_TEXTURE_2D,
               0,
               GL_LUMINANCE_ALPHA,
               16,
               17,
               0,
               GL_LUMINANCE_ALPHA,
               GL_FLOAT,
               0,
               0);
}

TEST_P(GLES2DecoderManualInitTest, TexImage2DFloatOnGLES3) {
  InitState init;
  init.extensions = "GL_OES_texture_float GL_EXT_color_buffer_float";
  init.gl_version = "OpenGL ES 3.0";
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2DConvertInternalFormat(GL_TEXTURE_2D, 0, GL_RGBA, 16, 17, 0,
                                    GL_RGBA, GL_FLOAT, 0, 0, GL_RGBA32F);
  DoTexImage2DConvertInternalFormat(GL_TEXTURE_2D, 0, GL_RGB, 16, 17, 0, GL_RGB,
                                    GL_FLOAT, 0, 0, GL_RGB32F);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA32F, 16, 17, 0, GL_RGBA, GL_FLOAT, 0, 0);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_LUMINANCE, 16, 17, 0, GL_LUMINANCE, GL_FLOAT, 0, 0);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 16, 17, 0, GL_ALPHA, GL_FLOAT, 0, 0);
  DoTexImage2D(GL_TEXTURE_2D,
               0,
               GL_LUMINANCE_ALPHA,
               16,
               17,
               0,
               GL_LUMINANCE_ALPHA,
               GL_FLOAT,
               0,
               0);
}

TEST_P(GLES2DecoderManualInitTest, TexSubImage2DFloatOnGLES3) {
  InitState init;
  init.extensions = "GL_OES_texture_float GL_EXT_color_buffer_float";
  init.gl_version = "OpenGL ES 3.0";
  InitDecoder(init);
  const int kWidth = 8;
  const int kHeight = 4;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D,
               0,
               GL_RGBA32F,
               kWidth,
               kHeight,
               0,
               GL_RGBA,
               GL_FLOAT,
               0,
               0);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, kWidth, kHeight, 0,
                               GL_RGBA, GL_FLOAT, shared_memory_address_.get()))
      .Times(1)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RGBA, GL_FLOAT,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, TexSubImage2DFloatDoesClearOnGLES3) {
  InitState init;
  init.extensions = "GL_OES_texture_float GL_EXT_color_buffer_float";
  init.gl_version = "OpenGL ES 3.0";
  InitDecoder(init);
  const int kWidth = 8;
  const int kHeight = 4;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D,
               0,
               GL_RGBA32F,
               kWidth,
               kHeight,
               0,
               GL_RGBA,
               GL_FLOAT,
               0,
               0);
  SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
                                GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
                                GL_FLOAT, 0, kHeight - 1, kWidth, 1, 0);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, _, _, GL_RGBA,
                                  GL_FLOAT, shared_memory_address_.get()))
      .Times(2)
      .RetiresOnSaturation();
  cmds::TexSubImage2D cmd;
  cmd.Init(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight - 1, GL_RGBA, GL_FLOAT,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  cmd.Init(GL_TEXTURE_2D, 0, 0, kHeight - 1, kWidth - 1, 1, GL_RGBA, GL_FLOAT,
           shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, TexImage2Dnorm16OnGLES3) {
  InitState init;
  init.extensions = "GL_EXT_texture_norm16";
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_R16_EXT, 16, 17, 0, GL_RED,
               GL_UNSIGNED_SHORT, 0, 0);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RG16_EXT, 16, 17, 0, GL_RG,
               GL_UNSIGNED_SHORT, 0, 0);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16_EXT, 16, 17, 0, GL_RGB,
               GL_UNSIGNED_SHORT, 0, 0);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16_EXT, 16, 17, 0, GL_RGBA,
               GL_UNSIGNED_SHORT, 0, 0);
}

class GLES2DecoderCompressedFormatsTest : public GLES2DecoderManualInitTest {
 public:
  GLES2DecoderCompressedFormatsTest() = default;

  void CheckFormats(const char* extension, base::span<const GLenum> formats) {
    // ES3 has 10 built-in compressed texture formats.
    const int kES3FormatCount = 10;

    InitState init;
    init.extensions = extension;
    InitDecoder(init);

    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();

    auto* result =
        static_cast<cmds::GetIntegerv::Result*>(shared_memory_address_);
    cmds::GetIntegerv cmd;
    result->size = 0;
    EXPECT_CALL(*gl_, GetIntegerv(_, _)).Times(0).RetiresOnSaturation();
    cmd.Init(GL_NUM_COMPRESSED_TEXTURE_FORMATS,
             shared_memory_id_,
             shared_memory_offset_);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(1, result->GetNumResults());
    GLint num_formats = *result->GetData();
    // Since we don't emulate ES3 compressed formats on top of Desktop GL,
    // so totally supported formats may or may not include the 10 ES3 formats.
    GLint count = static_cast<GLint>(formats.size());
    EXPECT_TRUE(count == num_formats || count + kES3FormatCount == num_formats);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());

    result->size = 0;
    cmd.Init(GL_COMPRESSED_TEXTURE_FORMATS,
             shared_memory_id_,
             shared_memory_offset_);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(num_formats, result->GetNumResults());

    // SAFETY: The command buffer returns results in a buffer where the valid
    // size is guaranteed to be at least `GetNumResults()` by the command
    // execution.
    auto data_span = UNSAFE_BUFFERS(base::span<const GLint>(
        result->GetData(), static_cast<size_t>(result->GetNumResults())));
    for (GLenum format : formats) {
      EXPECT_TRUE(std::ranges::contains(data_span, static_cast<GLint>(format)));
    }

    EXPECT_EQ(GL_NO_ERROR, GetGLError());
  }
};

INSTANTIATE_TEST_SUITE_P(Service,
                         GLES2DecoderCompressedFormatsTest,
                         ::testing::Bool());

TEST_P(GLES2DecoderCompressedFormatsTest, GetCompressedTextureFormatsS3TC) {
  constexpr auto formats = std::to_array<GLenum>(
      {GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
       GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT});
  CheckFormats("GL_EXT_texture_compression_s3tc", formats);
}

TEST_P(GLES2DecoderCompressedFormatsTest, GetCompressedTextureFormatsATC) {
  constexpr auto formats =
      std::to_array<GLenum>({GL_ATC_RGB_AMD, GL_ATC_RGBA_EXPLICIT_ALPHA_AMD,
                             GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD});
  CheckFormats("GL_AMD_compressed_ATC_texture", formats);
}

TEST_P(GLES2DecoderCompressedFormatsTest, GetCompressedTextureFormatsPVRTC) {
  constexpr auto formats = std::to_array<GLenum>(
      {GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG,
       GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG,
       GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG});
  CheckFormats("GL_IMG_texture_compression_pvrtc", formats);
}

TEST_P(GLES2DecoderCompressedFormatsTest, GetCompressedTextureFormatsETC1) {
  constexpr auto formats = std::to_array<GLenum>({GL_ETC1_RGB8_OES});
  CheckFormats("GL_OES_compressed_ETC1_RGB8_texture", formats);
}

TEST_P(GLES2DecoderCompressedFormatsTest, GetCompressedTextureFormatsASTC) {
  constexpr auto formats =
      std::to_array<GLenum>({GL_COMPRESSED_RGBA_ASTC_4x4_KHR,
                             GL_COMPRESSED_RGBA_ASTC_5x4_KHR,
                             GL_COMPRESSED_RGBA_ASTC_5x5_KHR,
                             GL_COMPRESSED_RGBA_ASTC_6x5_KHR,
                             GL_COMPRESSED_RGBA_ASTC_6x6_KHR,
                             GL_COMPRESSED_RGBA_ASTC_8x5_KHR,
                             GL_COMPRESSED_RGBA_ASTC_8x6_KHR,
                             GL_COMPRESSED_RGBA_ASTC_8x8_KHR,
                             GL_COMPRESSED_RGBA_ASTC_10x5_KHR,
                             GL_COMPRESSED_RGBA_ASTC_10x6_KHR,
                             GL_COMPRESSED_RGBA_ASTC_10x8_KHR,
                             GL_COMPRESSED_RGBA_ASTC_10x10_KHR,
                             GL_COMPRESSED_RGBA_ASTC_12x10_KHR,
                             GL_COMPRESSED_RGBA_ASTC_12x12_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR,
                             GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR});
  CheckFormats("GL_KHR_texture_compression_astc_ldr", formats);
}

TEST_P(GLES2DecoderCompressedFormatsTest, GetCompressedTextureFormatsBPTC) {
  constexpr auto formats =
      std::to_array<GLenum>({GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
                             GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT,
                             GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT,
                             GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT});
  CheckFormats("GL_EXT_texture_compression_bptc", formats);
}

TEST_P(GLES2DecoderCompressedFormatsTest, GetCompressedTextureFormatsRGTC) {
  constexpr auto formats = std::to_array<GLenum>(
      {GL_COMPRESSED_RED_RGTC1_EXT, GL_COMPRESSED_SIGNED_RED_RGTC1_EXT,
       GL_COMPRESSED_RED_GREEN_RGTC2_EXT,
       GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT});
  CheckFormats("GL_EXT_texture_compression_rgtc", formats);
}

TEST_P(GLES2DecoderManualInitTest, GetNoCompressedTextureFormats) {
  // ES3 has 10 built-in compressed texture formats.
  const int kES3FormatCount = 10;

  InitState init;
  InitDecoder(init);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();

  auto* result =
      static_cast<cmds::GetIntegerv::Result*>(shared_memory_address_);
  cmds::GetIntegerv cmd;
  result->size = 0;
  EXPECT_CALL(*gl_, GetIntegerv(_, _)).Times(0).RetiresOnSaturation();
  cmd.Init(GL_NUM_COMPRESSED_TEXTURE_FORMATS,
           shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(1, result->GetNumResults());
  GLint num_formats = *result->GetData();
  // Since we don't emulate ES3 compressed formats on top of Desktop GL,
  // so totally supported formats may or may not include the 10 ES3 formats.
  EXPECT_TRUE(0 == num_formats || kES3FormatCount == num_formats);
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  result->size = 0;
  cmd.Init(
      GL_COMPRESSED_TEXTURE_FORMATS, shared_memory_id_, shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(num_formats, result->GetNumResults());

  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, TexStorageInvalidLevels) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.extensions = "GL_ANGLE_texture_rectangle GL_EXT_texture_storage";
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_RECTANGLE_ANGLE, client_texture_id_,
                kServiceTextureId);
  cmds::TexStorage2DEXT cmd;
  cmd.Init(GL_TEXTURE_RECTANGLE_ANGLE, 2, GL_RGBA8, 4, 4);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, TexStorageInvalidSize) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.extensions = "GL_EXT_texture_storage";
  InitDecoder(init);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  {
    cmds::TexStorage2DEXT cmd;
    cmd.Init(GL_TEXTURE_2D, 1, GL_RGBA8, 0, 4);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }
  {
    cmds::TexStorage2DEXT cmd;
    cmd.Init(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 0);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }
  {
    cmds::TexStorage2DEXT cmd;
    cmd.Init(GL_TEXTURE_2D, 1, GL_RGBA8, 0, 0);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  }
}

class GLES2DecoderTexStorageFormatAndTypeTest
    : public GLES2DecoderManualInitTest {
 public:
  GLES2DecoderTexStorageFormatAndTypeTest() = default;

  void DoTexStorageFormatAndType(const InitState& init,
                                 GLenum format,
                                 GLenum adjusted_internal_format) {
    GLsizei kWidth = 512;
    GLsizei kHeight = 512;
    // Purposely set kLevels to be smaller than 10 = log2(512) + 1.
    GLsizei kLevels = 5;
    InitDecoder(init);
    DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
    EXPECT_CALL(
        *gl_, TexStorage2DEXT(GL_TEXTURE_2D, kLevels, format, kWidth, kHeight))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    cmds::TexStorage2DEXT cmd;
    cmd.Init(GL_TEXTURE_2D, kLevels, format, kWidth, kHeight);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
    TextureRef* texture_ref =
        group().texture_manager()->GetTexture(client_texture_id_);
    Texture* texture = texture_ref->texture();
    for (GLsizei ii = 0; ii < kLevels; ++ii) {
      GLenum type = 0, internal_format = 0;
      GLsizei level_width = 0, level_height = 0;
      EXPECT_TRUE(texture->GetLevelType(GL_TEXTURE_2D, static_cast<GLint>(ii),
                                        &type, &internal_format));
      EXPECT_EQ(static_cast<GLenum>(adjusted_internal_format), internal_format);
      EXPECT_EQ(static_cast<GLenum>(GL_UNSIGNED_BYTE), type);
      EXPECT_TRUE(texture->GetLevelSize(GL_TEXTURE_2D, static_cast<GLint>(ii),
                                        &level_width, &level_height, nullptr));
      EXPECT_EQ(kWidth >> ii, level_width);
      EXPECT_EQ(kHeight >> ii, level_height);
    }
    EXPECT_TRUE(texture->texture_complete());
  }
};

INSTANTIATE_TEST_SUITE_P(Service,
                         GLES2DecoderTexStorageFormatAndTypeTest,
                         ::testing::Bool());

TEST_P(GLES2DecoderTexStorageFormatAndTypeTest, ES2) {
  InitState init;
  init.gl_version = "OpenGL ES 2.0";
  init.extensions = "GL_EXT_texture_storage";
  init.context_type = CONTEXT_TYPE_OPENGLES2;
  DoTexStorageFormatAndType(init, GL_RGBA8_OES, GL_RGBA);
}

TEST_P(GLES2DecoderTexStorageFormatAndTypeTest, WebGL1) {
  InitState init;
  init.gl_version = "OpenGL ES 2.0";
  init.extensions = "GL_EXT_texture_storage";
  init.context_type = CONTEXT_TYPE_WEBGL1;
  DoTexStorageFormatAndType(init, GL_RGBA8_OES, GL_RGBA);
}

TEST_P(GLES2DecoderTexStorageFormatAndTypeTest, ES3) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  DoTexStorageFormatAndType(init, GL_RGBA8, GL_RGBA8);
}

TEST_P(GLES2DecoderTexStorageFormatAndTypeTest, WebGL2) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_WEBGL2;
  DoTexStorageFormatAndType(init, GL_RGBA8, GL_RGBA8);
}

TEST_P(GLES3DecoderTest, TexStorage3DValidArgs) {
  DoBindTexture(GL_TEXTURE_3D, client_texture_id_, kServiceTextureId);
  EXPECT_CALL(*gl_, TexStorage3D(GL_TEXTURE_3D, 2, GL_RGB565, 4, 5, 6))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::TexStorage3D cmd;
  cmd.Init(GL_TEXTURE_3D, 2, GL_RGB565, 4, 5, 6);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderManualInitTest, ResetTexStorageBaseLevelWorkaround) {
  InitState init;
  init.extensions = "GL_EXT_texture_storage";
  init.gl_version = "OpenGL ES 3.0";
  init.has_alpha = true;
  init.has_depth = true;
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  GpuDriverBugWorkarounds workarounds;
  workarounds.reset_tex_storage_base_level = true;
  InitDecoderWithWorkarounds(init, workarounds);

  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);

  // Set BASE_LEVEL to 1 before calling TexStorage2D.
  cmds::TexParameteri param_cmd;
  param_cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
  EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(param_cmd));

  // Calling TexStorage2DEXT should reset BASE_LEVEL to 0 first, call
  // TexStorage2DEXT, and then restore BASE_LEVEL to 1.
  EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, TexStorage2DEXT(GL_TEXTURE_2D, 2, GL_RGBA8, 16, 16))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();

  cmds::TexStorage2DEXT storage_cmd;
  storage_cmd.Init(GL_TEXTURE_2D, 2, GL_RGBA8, 16, 16);
  EXPECT_EQ(error::kNoError, ExecuteCmd(storage_cmd));
}

TEST_P(GLES3DecoderTest, TexImage3DValidArgs) {
  const GLenum kTarget = GL_TEXTURE_3D;
  const GLint kLevel = 2;
  const GLint kInternalFormat = GL_RGBA8;
  const GLsizei kWidth = 2;
  const GLsizei kHeight = 2;
  const GLsizei kDepth = 2;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth, 0,
               kFormat, kType, shared_memory_id_, kSharedMemoryOffset);
}

TEST_P(GLES3DecoderTest, ClearLevel3DSingleCall) {
  const GLenum kTarget = GL_TEXTURE_3D;
  const GLint kLevel = 0;
  const GLint kInternalFormat = GL_RGBA8;
  const GLsizei kWidth = 2;
  const GLsizei kHeight = 2;
  const GLsizei kDepth = 2;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;
  const uint32_t kBufferSize = kWidth * kHeight * kDepth * 4;

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth, 0,
               kFormat, kType, 0, 0);
  TextureRef* texture_ref =
      group().texture_manager()->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();

  // It takes 1 call to clear the entire 3D texture.
  GLint xoffset[] = {0};
  GLint yoffset[] = {0};
  GLint zoffset[] = {0};
  GLsizei width[] = {kWidth};
  GLsizei height[] = {kHeight};
  GLsizei depth[] = {kDepth};
  SetupClearTexture3DExpectations(kBufferSize, kTarget, kServiceTextureId,
                                  kLevel, kFormat, kType, 1, xoffset, yoffset,
                                  zoffset, width, height, depth, 0);

  EXPECT_TRUE(decoder_->ClearLevel3D(
      texture, kTarget, kLevel, kFormat, kType, kWidth, kHeight, kDepth));
}

TEST_P(GLES3DecoderTest, ClearLevel3DMultipleLayersPerCall) {
  const GLenum kTarget = GL_TEXTURE_3D;
  const GLint kLevel = 0;
  const GLint kInternalFormat = GL_RGBA8;
  const GLsizei kWidth = 512;
  const GLsizei kHeight = 512;
  const GLsizei kDepth = 7;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;
  const uint32_t kBufferSize = 1024 * 1024 * 2;  // Max buffer size per call.

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth, 0,
               kFormat, kType, 0, 0);
  TextureRef* texture_ref =
      group().texture_manager()->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();

  // It takes 4 calls to clear the 3D texture, each clears 2/2/2/1 layers.
  GLint xoffset[] = {0, 0, 0, 0};
  GLint yoffset[] = {0, 0, 0, 0};
  GLint zoffset[] = {0, 2, 4, 6};
  GLsizei width[] = {kWidth, kWidth, kWidth, kWidth};
  GLsizei height[] = {kHeight, kHeight, kHeight, kHeight};
  GLsizei depth[] = {2, 2, 2, 1};
  SetupClearTexture3DExpectations(kBufferSize, kTarget, kServiceTextureId,
                                  kLevel, kFormat, kType, 4, xoffset, yoffset,
                                  zoffset, width, height, depth, 0);

  EXPECT_TRUE(decoder_->ClearLevel3D(
      texture, kTarget, kLevel, kFormat, kType, kWidth, kHeight, kDepth));
}

TEST_P(GLES3DecoderTest, ClearLevel3DMultipleCallsPerLayer) {
  const GLenum kTarget = GL_TEXTURE_3D;
  const GLint kLevel = 0;
  const GLint kInternalFormat = GL_RGBA8;
  const GLsizei kWidth = 1024;
  const GLsizei kHeight = 1000;
  const GLsizei kDepth = 2;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;
  const uint32_t kBufferSize = 1024 * 1024 * 2;  // Max buffer size per call.

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth, 0,
               kFormat, kType, 0, 0);
  TextureRef* texture_ref =
      group().texture_manager()->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();

  // It takes two calls to clear one layer of the 3D texture, each clears
  // 512/488 rows.
  GLint xoffset[] = {0, 0, 0, 0};
  GLint yoffset[] = {0, 512, 0, 512};
  GLint zoffset[] = {0, 0, 1, 1};
  GLsizei width[] = {kWidth, kWidth, kWidth, kWidth};
  GLsizei height[] = {512, 488, 512, 488};
  GLsizei depth[] = {1, 1, 1, 1};
  SetupClearTexture3DExpectations(kBufferSize, kTarget, kServiceTextureId,
                                  kLevel, kFormat, kType, 4, xoffset, yoffset,
                                  zoffset, width, height, depth, 0);

  EXPECT_TRUE(decoder_->ClearLevel3D(
      texture, kTarget, kLevel, kFormat, kType, kWidth, kHeight, kDepth));
}

TEST_P(GLES3DecoderTest, BindSamplerInvalidUnit) {
  cmds::BindSampler cmd;
  cmd.Init(kNumTextureUnits, client_texture_id_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  cmd.Init(kNumTextureUnits, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
}

TEST_P(GLES2DecoderTest, BindTextureValidArgs) {
  EXPECT_CALL(*gl_, BindTexture(GL_TEXTURE_2D, kServiceTextureId))
      .Times(1)
      .RetiresOnSaturation();
  cmds::BindTexture cmd;
  cmd.Init(GL_TEXTURE_2D, client_texture_id_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, BindTextureInvalidArgs) {
  EXPECT_CALL(*gl_, BindTexture(_, _)).Times(0);
  cmds::BindTexture cmd;
  cmd.Init(0x0DE0 /* GL_TEXTURE_1D*/, client_texture_id_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());

  cmd.Init(GL_TEXTURE_3D, client_texture_id_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderTest, GetTexParameterfvValidArgs) {
  EXPECT_CALL(*gl_, GetError()).WillRepeatedly(Return(GL_NO_ERROR));
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  SpecializedSetup<cmds::GetTexParameterfv, 0>(true);
  typedef cmds::GetTexParameterfv::Result Result;
  Result* result = static_cast<Result*>(shared_memory_address_);
  EXPECT_CALL(*gl_, GetTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                                      result->GetData()));
  result->size = 0;
  cmds::GetTexParameterfv cmd;
  cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(
      decoder_->GetGLES2Util()->GLGetNumValuesReturned(GL_TEXTURE_MAG_FILTER),
      result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, GetTexParameterfvInvalidArgs2_0) {
  EXPECT_CALL(*gl_, GetTexParameterfv(_, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  SpecializedSetup<cmds::GetTexParameterfv, 0>(false);
  cmds::GetTexParameterfv::Result* result =
      static_cast<cmds::GetTexParameterfv::Result*>(shared_memory_address_);
  result->size = 0;
  cmds::GetTexParameterfv cmd;
  cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, kInvalidSharedMemoryId, 0);
  EXPECT_EQ(error::kOutOfBounds, ExecuteCmd(cmd));
  EXPECT_EQ(0u, result->size);
}

TEST_P(GLES2DecoderTest, GetTexParameterfvInvalidArgs2_1) {
  EXPECT_CALL(*gl_, GetTexParameterfv(_, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  SpecializedSetup<cmds::GetTexParameterfv, 0>(false);
  cmds::GetTexParameterfv::Result* result =
      static_cast<cmds::GetTexParameterfv::Result*>(shared_memory_address_);
  result->size = 0;
  cmds::GetTexParameterfv cmd;
  cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shared_memory_id_,
           kInvalidSharedMemoryOffset);
  EXPECT_EQ(error::kOutOfBounds, ExecuteCmd(cmd));
  EXPECT_EQ(0u, result->size);
}

TEST_P(GLES2DecoderTest, GetTexParameterivValidArgs) {
  EXPECT_CALL(*gl_, GetError()).WillRepeatedly(Return(GL_NO_ERROR));
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  SpecializedSetup<cmds::GetTexParameteriv, 0>(true);
  typedef cmds::GetTexParameteriv::Result Result;
  Result* result = static_cast<Result*>(shared_memory_address_);
  EXPECT_CALL(*gl_, GetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                                      result->GetData()));
  result->size = 0;
  cmds::GetTexParameteriv cmd;
  cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(
      decoder_->GetGLES2Util()->GLGetNumValuesReturned(GL_TEXTURE_MAG_FILTER),
      result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, GetTexParameterivInvalidArgs2_0) {
  EXPECT_CALL(*gl_, GetTexParameteriv(_, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  SpecializedSetup<cmds::GetTexParameteriv, 0>(false);
  cmds::GetTexParameteriv::Result* result =
      static_cast<cmds::GetTexParameteriv::Result*>(shared_memory_address_);
  result->size = 0;
  cmds::GetTexParameteriv cmd;
  cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, kInvalidSharedMemoryId, 0);
  EXPECT_EQ(error::kOutOfBounds, ExecuteCmd(cmd));
  EXPECT_EQ(0u, result->size);
}

TEST_P(GLES2DecoderTest, GetTexParameterivInvalidArgs2_1) {
  EXPECT_CALL(*gl_, GetTexParameteriv(_, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  SpecializedSetup<cmds::GetTexParameteriv, 0>(false);
  cmds::GetTexParameteriv::Result* result =
      static_cast<cmds::GetTexParameteriv::Result*>(shared_memory_address_);
  result->size = 0;
  cmds::GetTexParameteriv cmd;
  cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shared_memory_id_,
           kInvalidSharedMemoryOffset);
  EXPECT_EQ(error::kOutOfBounds, ExecuteCmd(cmd));
  EXPECT_EQ(0u, result->size);
}

TEST_P(GLES3DecoderTest, TexSwizzleAllowed) {
  const GLenum kTarget = GL_TEXTURE_2D;
  const GLenum kSwizzleParam = GL_TEXTURE_SWIZZLE_R;
  const GLenum kSwizzleValue = GL_BLUE;
  const GLenum kInvalidSwizzleValue = GL_RG;

  {
    DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
  }

  {
    EXPECT_CALL(*gl_, TexParameteri(kTarget, kSwizzleParam, kSwizzleValue));
    cmds::TexParameteri cmd;
    cmd.Init(kTarget, kSwizzleParam, kSwizzleValue);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
  }

  {
    cmds::TexParameteri cmd;
    cmd.Init(kTarget, kSwizzleParam, kInvalidSwizzleValue);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
  }

  {
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    auto* result =
        static_cast<cmds::GetTexParameteriv::Result*>(shared_memory_address_);
    result->size = 0;
    cmds::GetTexParameteriv cmd;
    cmd.Init(kTarget, kSwizzleParam, shared_memory_id_, shared_memory_offset_);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(decoder_->GetGLES2Util()->GLGetNumValuesReturned(kSwizzleParam),
              result->GetNumResults());
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
    EXPECT_EQ(kSwizzleValue, static_cast<GLenum>(*result->GetData()));
  }
}

TEST_P(WebGL2DecoderTest, TexSwizzleDisabled) {
  const GLenum kTarget = GL_TEXTURE_2D;
  const GLenum kSwizzleParam = GL_TEXTURE_SWIZZLE_R;
  const GLenum kSwizzleValue = GL_BLUE;

  {
    cmds::TexParameteri cmd;
    cmd.Init(kTarget, kSwizzleParam, kSwizzleValue);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
  }

  {
    auto* result =
        static_cast<cmds::GetTexParameteriv::Result*>(shared_memory_address_);
    result->size = 0;
    cmds::GetTexParameteriv cmd;
    cmd.Init(kTarget, kSwizzleParam, shared_memory_id_, shared_memory_offset_);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
  }
}

TEST_P(GLES2DecoderTest, CopySubTextureCHROMIUMTwiceClearsUnclearedTexture) {
  // Create uninitialized source texture.
  EXPECT_CALL(*gl_, GenTextures(1, _))
      .WillOnce(SetArgPointee<1>(kNewServiceId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kNewClientId);
  DoBindTexture(GL_TEXTURE_2D, kNewClientId, kNewServiceId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0,
               0);

  // Create uninitialized dest texture.
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0,
               0);

  // This will write the top half of the destination.
  {
    // Source is undefined, so first call to CopySubTexture will clear the
    // source.
    SetupClearTextureExpectations(kNewServiceId, kServiceTextureId,
                                  GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
                                  GL_UNSIGNED_BYTE, 0, 0, 2, 2, 0);
    cmds::CopySubTextureCHROMIUM cmd;
    cmd.Init(kNewClientId /* source_id */, 0 /* source_level */,
             GL_TEXTURE_2D /* dest_target */, client_texture_id_ /* dest_id */,
             0 /* dest_level */, 0 /* xoffset */, 0 /* yoffset */, 0 /* x */,
             0 /* y */, 2 /* width */, 1 /* height */,
             false /* unpack_flip_y */, false /* unpack_premultiply_alpha */,
             false /* unpack_unmultiply_alpha */);
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }

  // This will write the bottom right pixel of the destination.
  {
    // This will clear the bottom part of destination as a rectangle is not
    // sufficient to keep track of the initialized area.
    SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
                                  GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
                                  GL_UNSIGNED_BYTE, 0, 1, 2, 1, 0);
    cmds::CopySubTextureCHROMIUM cmd;
    cmd.Init(kNewClientId /* source_id */, 0 /* source_level */,
             GL_TEXTURE_2D /* dest_target */, client_texture_id_ /* dest_id */,
             0 /* dest_level */, 1 /* xoffset */, 1 /* yoffset */, 0 /* x */,
             0 /* y */, 1 /* width */, 1 /* height */,
             false /* unpack_flip_y */, false /* unpack_premultiply_alpha */,
             false /* unpack_unmultiply_alpha */);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }

  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();
  EXPECT_TRUE(texture->SafeToRenderFrom());
}

TEST_P(GLES3DecoderTest, ImmutableTextureBaseLevelMaxLevelClamping) {
  GLenum kTarget = GL_TEXTURE_3D;
  GLint kBaseLevel = 1416354905;
  GLint kMaxLevel = 800;
  GLsizei kLevels = 4;
  GLenum kInternalFormat = GL_R8;
  GLsizei kWidth = 20;
  GLsizei kHeight = 20;
  GLsizei kDepth = 20;
  GLint kClampedBaseLevel = kLevels - 1;
  GLint kClampedMaxLevel = kLevels - 1;
  GLint kMax3DLevel = 10;  // log2(kMax3DTextureSize (1024))

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  TextureRef* texture_ref =
      group().texture_manager()->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);
  Texture* texture = texture_ref->texture();

  // Before TexStorage3D call, base/max levels are clamped to target max levels.
  {
    EXPECT_CALL(*gl_,
                TexParameteri(kTarget, GL_TEXTURE_BASE_LEVEL, kMax3DLevel))
        .Times(1)
        .RetiresOnSaturation();
    cmds::TexParameteri cmd;
    cmd.Init(kTarget, GL_TEXTURE_BASE_LEVEL, kBaseLevel);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }
  {
    EXPECT_CALL(*gl_, TexParameteri(kTarget, GL_TEXTURE_MAX_LEVEL, kMax3DLevel))
        .Times(1)
        .RetiresOnSaturation();
    cmds::TexParameteri cmd;
    cmd.Init(kTarget, GL_TEXTURE_MAX_LEVEL, kMaxLevel);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(kMax3DLevel, texture->base_level());
  EXPECT_EQ(kMax3DLevel, texture->max_level());

  {
    EXPECT_CALL(*gl_, TexStorage3D(kTarget, kLevels, kInternalFormat, kWidth,
                                   kHeight, kDepth))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    cmds::TexStorage3D cmd;
    cmd.Init(kTarget, kLevels, kInternalFormat, kWidth, kHeight, kDepth);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
  }
  EXPECT_EQ(kClampedBaseLevel, texture->base_level());
  EXPECT_EQ(kClampedMaxLevel, texture->max_level());

  GLint kNewBaseLevel = 827344;
  GLint kNewMaxLevel = 17619;
  // After TexStorage3D call, base/max levels are clamped.
  {
    EXPECT_CALL(
        *gl_, TexParameteri(kTarget, GL_TEXTURE_BASE_LEVEL, kClampedBaseLevel))
        .Times(1)
        .RetiresOnSaturation();
    cmds::TexParameteri cmd;
    cmd.Init(kTarget, GL_TEXTURE_BASE_LEVEL, kNewBaseLevel);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }
  {
    EXPECT_CALL(*gl_,
                TexParameteri(kTarget, GL_TEXTURE_MAX_LEVEL, kClampedMaxLevel))
        .Times(1)
        .RetiresOnSaturation();
    cmds::TexParameteri cmd;
    cmd.Init(kTarget, GL_TEXTURE_MAX_LEVEL, kNewMaxLevel);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(kClampedBaseLevel, texture->base_level());
  EXPECT_EQ(kClampedMaxLevel, texture->max_level());

  // GetTexParameteriv still returns unclamped values.
  {
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    auto* result =
        static_cast<cmds::GetTexParameteriv::Result*>(shared_memory_address_);
    result->size = 0;
    cmds::GetTexParameteriv cmd;
    cmd.Init(kTarget, GL_TEXTURE_BASE_LEVEL, shared_memory_id_,
             shared_memory_offset_);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(kNewBaseLevel, static_cast<GLint>(*result->GetData()));
  }
  {
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    auto* result =
        static_cast<cmds::GetTexParameteriv::Result*>(shared_memory_address_);
    result->size = 0;
    cmds::GetTexParameteriv cmd;
    cmd.Init(kTarget, GL_TEXTURE_MAX_LEVEL, shared_memory_id_,
             shared_memory_offset_);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(kNewMaxLevel, static_cast<GLint>(*result->GetData()));
  }
}

TEST_P(GLES3DecoderTest, ClearRenderableLevelsWithOutOfRangeBaseLevel) {
  // Regression test for https://crbug.com/983938
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0,
               0);
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  TextureManager* manager = group().texture_manager();
  TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
  ASSERT_TRUE(texture_ref != nullptr);

  {
    // GL_TEXTURE_2D max levels is log2(kMaxTextureSize (2048)) = 11.
    // 55 is clamped to 11.
    EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 11));
    cmds::TexParameteri cmd;
    cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 55);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
  }

  // The following call will trigger out-of-bounds access in asan build
  // without fixing the bug.
  manager->ClearRenderableLevels(GetDecoder(), texture_ref);
}

class GLES3DecoderNPOTImmutableTest : public GLES3DecoderManualInitTest {};

INSTANTIATE_TEST_SUITE_P(Service,
                         GLES3DecoderNPOTImmutableTest,
                         ::testing::Bool());

TEST_P(GLES3DecoderNPOTImmutableTest,
       DontChangeBaseLevelForNPOTImmutableTextures) {
  InitState init;
  init.extensions = "GL_EXT_texture_storage ";
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  gpu::GpuDriverBugWorkarounds workarounds;
  workarounds.dont_change_base_level_for_npot_immutable_textures = true;
  InitDecoderWithWorkarounds(init, workarounds);

  // Set up an NPOT immutable texture via TexStorage2D.
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  EXPECT_CALL(*gl_, TexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, 5, 5))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::TexStorage2DEXT storage_cmd;
  storage_cmd.Init(GL_TEXTURE_2D, 1, GL_RGBA8, 5, 5);
  EXPECT_EQ(error::kNoError, ExecuteCmd(storage_cmd));

  // Setting BASE_LEVEL to 0 (same value) should succeed.
  cmds::TexParameteri same_param_cmd;
  same_param_cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
  EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(same_param_cmd));

  // Changing BASE_LEVEL to 1 should lose context when workaround is enabled.
  cmds::TexParameteri param_cmd;
  param_cmd.Init(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
  EXPECT_EQ(error::kLostContext, ExecuteCmd(param_cmd));
}

class GLES3DecoderReattachTextureAfterLayerIncreaseTest
    : public GLES3DecoderManualInitTest {};

INSTANTIATE_TEST_SUITE_P(Service,
                         GLES3DecoderReattachTextureAfterLayerIncreaseTest,
                         ::testing::Bool());

TEST_P(GLES3DecoderReattachTextureAfterLayerIncreaseTest,
       ReattachTextureAfterLayerIncrease) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  gpu::GpuDriverBugWorkarounds workarounds;
  workarounds.reattach_texture_to_fbo_after_layer_increase = GetParam();
  InitDecoderWithWorkarounds(init, workarounds);

  const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
  const GLint kLevel = 0;
  const GLint kInternalFormat = GL_RGBA8;
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 4;
  const GLsizei kDepth = 1;
  const GLsizei kNewDepth = 2;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth, 0,
               kFormat, kType, 0, 0);

  DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);

  EXPECT_CALL(*gl_,
              FramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                      kServiceTextureId, kLevel, 0))
      .Times(1)
      .RetiresOnSaturation();
  cmds::FramebufferTextureLayer attach_cmd;
  attach_cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, client_texture_id_,
                  kLevel, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(attach_cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Calling TexImage3D with increased depth should trigger detach, TexImage3D,
  // and re-attach when the workaround is enabled.
  EXPECT_CALL(*gl_, BindFramebufferEXT(_, _)).Times(::testing::AnyNumber());
  ::testing::InSequence sequence;
  if (GetParam()) {
    EXPECT_CALL(*gl_, FramebufferTextureLayer(GL_FRAMEBUFFER,
                                              GL_COLOR_ATTACHMENT0, 0, 0, 0))
        .Times(1)
        .RetiresOnSaturation();
  }
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, TexImage3D(kTarget, kLevel, _, kWidth, kHeight, kNewDepth,
                               0, kFormat, kType, _))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  if (GetParam()) {
    EXPECT_CALL(*gl_,
                FramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                        kServiceTextureId, kLevel, 0))
        .Times(1)
        .RetiresOnSaturation();
  }

  cmds::TexImage3D cmd;
  cmd.Init(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kNewDepth,
           kFormat, kType, 0, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderReattachTextureAfterLayerIncreaseTest,
       ReattachTextureAfterLayerIncrease_TexStorage3D) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  gpu::GpuDriverBugWorkarounds workarounds;
  // Instantiation parameter is used to enable/disable the workaround.
  workarounds.reattach_texture_to_fbo_after_layer_increase = GetParam();
  InitDecoderWithWorkarounds(init, workarounds);

  const GLenum kTarget = GL_TEXTURE_2D_ARRAY;
  const GLint kLevel = 0;
  const GLint kLevels = 1;
  const GLint kInternalFormat = GL_RGBA8;
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 4;
  const GLsizei kDepth = 1;
  const GLsizei kNewDepth = 2;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;

  DoBindTexture(kTarget, client_texture_id_, kServiceTextureId);
  DoTexImage3D(kTarget, kLevel, kInternalFormat, kWidth, kHeight, kDepth, 0,
               kFormat, kType, 0, 0);

  DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);

  EXPECT_CALL(*gl_,
              FramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                      kServiceTextureId, kLevel, 0))
      .Times(1)
      .RetiresOnSaturation();
  cmds::FramebufferTextureLayer attach_cmd;
  attach_cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, client_texture_id_,
                  kLevel, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(attach_cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Calling TexStorage3D with increased depth should trigger detach,
  // TexStorage3D, and re-attach when the workaround is enabled.
  EXPECT_CALL(*gl_, BindFramebufferEXT(_, _)).Times(::testing::AnyNumber());
  ::testing::InSequence sequence;
  if (workarounds.reattach_texture_to_fbo_after_layer_increase) {
    EXPECT_CALL(*gl_, FramebufferTextureLayer(GL_FRAMEBUFFER,
                                              GL_COLOR_ATTACHMENT0, 0, 0, 0))
        .Times(1)
        .RetiresOnSaturation();
  }
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, TexStorage3D(kTarget, kLevels, kInternalFormat, kWidth,
                                 kHeight, kNewDepth))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  if (workarounds.reattach_texture_to_fbo_after_layer_increase) {
    EXPECT_CALL(*gl_,
                FramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                        kServiceTextureId, kLevel, 0))
        .Times(1)
        .RetiresOnSaturation();
  }

  cmds::TexStorage3D cmd;
  cmd.Init(kTarget, kLevels, kInternalFormat, kWidth, kHeight, kNewDepth);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

// TODO(gman): Complete this test.
// TEST_P(GLES2DecoderTest, CompressedTexImage2DGLError) {
// }

// TODO(gman): CompressedTexImage2D

// TODO(gman): CompressedTexImage2DImmediate

// TODO(gman): CompressedTexSubImage2DImmediate

// TODO(gman): TexImage2D

// TODO(gman): TexImage2DImmediate

// TODO(gman): TexSubImage2DImmediate

}  // namespace gpu::gles2
