// 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 <limits.h>
#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <memory>

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/containers/heap_array.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#if defined(ADDRESS_SANITIZER)
#include <sanitizer/asan_interface.h>
#endif
#include "gpu/command_buffer/common/gles2_cmd_format.h"
#include "gpu/command_buffer/common/gles2_cmd_utils.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/gpu_switches.h"
#include "gpu/command_buffer/service/mocks.h"
#include "gpu/command_buffer/service/program_manager.h"
#include "gpu/command_buffer/service/test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.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

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::StrEq;
using ::testing::StrictMock;

namespace gpu {
namespace gles2 {

class GLES2DecoderTestWithExtensionsOnGLES2 : public GLES2DecoderTest {
 public:
  GLES2DecoderTestWithExtensionsOnGLES2() = default;

  void SetUp() override {}
  void Init(const char* extensions) {
    InitState init;
    init.extensions = extensions;
    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);
  }
};

TEST_P(GLES2DecoderTest, CheckFramebufferStatusWithNoBoundTarget) {
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(_)).Times(0);
  auto* result = static_cast<cmds::CheckFramebufferStatus::Result*>(
      shared_memory_address_);
  *result = 0;
  cmds::CheckFramebufferStatus cmd;
  cmd.Init(GL_FRAMEBUFFER, shared_memory_id_, shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE), *result);
}

TEST_P(GLES2DecoderWithShaderTest, BindAndDeleteFramebuffer) {
  SetupTexture();
  SetupExpectationsForApplyingDefaultDirtyState();
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoDeleteFramebuffer(client_framebuffer_id_,
                      kServiceFramebufferId,
                      true,
                      GL_FRAMEBUFFER,
                      0,
                      true,
                      GL_FRAMEBUFFER,
                      0);
  EXPECT_CALL(*gl_, DrawArrays(GL_TRIANGLES, 0, kNumVertices))
      .Times(1)
      .RetiresOnSaturation();
  cmds::DrawArrays cmd;
  cmd.Init(GL_TRIANGLES, 0, kNumVertices);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, FramebufferRenderbufferWithNoBoundTarget) {
  EXPECT_CALL(*gl_, FramebufferRenderbufferEXT(_, _, _, _)).Times(0);
  cmds::FramebufferRenderbuffer cmd;
  cmd.Init(GL_FRAMEBUFFER,
           GL_COLOR_ATTACHMENT0,
           GL_RENDERBUFFER,
           client_renderbuffer_id_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderTest, FramebufferTexture2DWithNoBoundTarget) {
  EXPECT_CALL(*gl_, FramebufferTexture2DEXT(_, _, _, _, _)).Times(0);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER,
           GL_COLOR_ATTACHMENT0,
           GL_TEXTURE_2D,
           client_texture_id_,
           0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTexture2DWithNoBoundTarget) {
  EXPECT_CALL(*gl_, FramebufferTexture2DEXT(_, _, _, _, _)).Times(0);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER,
           GL_COLOR_ATTACHMENT0,
           GL_TEXTURE_2D,
           client_texture_id_,
           1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderTest, FramebufferTexture2DValidArgs) {
  EXPECT_CALL(*gl_,
              FramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                      GL_TEXTURE_2D, kServiceTextureId, 0));
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
           client_texture_id_, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTexture2DValidArgs) {
  EXPECT_CALL(*gl_,
              FramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                      GL_TEXTURE_2D, kServiceTextureId, 1));
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
           client_texture_id_, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTexture2DDepthStencil) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              FramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                                      GL_TEXTURE_2D, kServiceTextureId, 4))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              FramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                                      GL_TEXTURE_2D, kServiceTextureId, 4))
      .Times(1)
      .RetiresOnSaturation();
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
           client_texture_id_, 4);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  Framebuffer* framebuffer = GetFramebuffer(client_framebuffer_id_);
  ASSERT_TRUE(framebuffer);
  ASSERT_FALSE(framebuffer->GetAttachment(GL_DEPTH_STENCIL_ATTACHMENT));
  ASSERT_TRUE(framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT));
  ASSERT_TRUE(framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT));
}

TEST_P(GLES2DecoderTest, FramebufferTexture2DInvalidArgs0_0) {
  EXPECT_CALL(*gl_, FramebufferTexture2DEXT(_, _, _, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_RENDERBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
           client_texture_id_, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTexture2DInvalidArgs0_0) {
  EXPECT_CALL(*gl_, FramebufferTexture2DEXT(_, _, _, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_RENDERBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
           client_texture_id_, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderTest, FramebufferTexture2DInvalidArgs1_0) {
  EXPECT_CALL(*gl_, FramebufferTexture2DEXT(_, _, _, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR, GL_TEXTURE_2D, client_texture_id_, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTexture2DInvalidArgs1_0) {
  EXPECT_CALL(*gl_, FramebufferTexture2DEXT(_, _, _, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR, GL_TEXTURE_2D, client_texture_id_, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderTest, FramebufferTexture2DInvalidArgs2_0) {
  EXPECT_CALL(*gl_, FramebufferTexture2DEXT(_, _, _, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
           0x851B /*GL_PROXY_TEXTURE_CUBE_MAP*/, client_texture_id_, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTexture2DInvalidArgs2_0) {
  EXPECT_CALL(*gl_, FramebufferTexture2DEXT(_, _, _, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
           0x851B /*GL_PROXY_TEXTURE_CUBE_MAP*/, client_texture_id_, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES2DecoderTest, FramebufferTexture2DInvalidArgs4_0) {
  EXPECT_CALL(*gl_, FramebufferTexture2DEXT(_, _, _, _, _)).Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
           client_texture_id_, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTexture2DValidArgs4_0) {
  EXPECT_CALL(*gl_,
              FramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                      GL_TEXTURE_2D, kServiceTextureId, 0));
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
           client_texture_id_, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, FramebufferTexture2DMismatchedTexture1) {
  EXPECT_CALL(*gl_, GetFramebufferAttachmentParameterivEXT(_, _, _, _))
      .Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
           client_texture_id_, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderTest, FramebufferTexture2DMismatchedTexture2) {
  EXPECT_CALL(*gl_, GetFramebufferAttachmentParameterivEXT(_, _, _, _))
      .Times(0);
  DoBindTexture(GL_TEXTURE_CUBE_MAP, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  cmds::FramebufferTexture2D cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
           client_texture_id_, 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderTest, GetFramebufferAttachmentParameterivWithNoBoundTarget) {
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetFramebufferAttachmentParameterivEXT(_, _, _, _))
      .Times(0);
  cmds::GetFramebufferAttachmentParameteriv cmd;
  cmd.Init(GL_FRAMEBUFFER,
           GL_COLOR_ATTACHMENT0,
           GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
           shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderTest, GetFramebufferAttachmentParameterivWithRenderbuffer) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  EXPECT_CALL(*gl_, GetError())
      .WillRepeatedly(Return(GL_NO_ERROR));
  EXPECT_CALL(*gl_,
              FramebufferRenderbufferEXT(GL_FRAMEBUFFER,
                                         GL_COLOR_ATTACHMENT0,
                                         GL_RENDERBUFFER,
                                         kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  auto* result =
      static_cast<cmds::GetFramebufferAttachmentParameteriv::Result*>(
          shared_memory_address_);
  result->size = 0;
  const GLint* result_value = result->GetData();
  cmds::FramebufferRenderbuffer fbrb_cmd;
  cmds::GetFramebufferAttachmentParameteriv cmd;
  fbrb_cmd.Init(GL_FRAMEBUFFER,
                GL_COLOR_ATTACHMENT0,
                GL_RENDERBUFFER,
                client_renderbuffer_id_);
  cmd.Init(GL_FRAMEBUFFER,
           GL_COLOR_ATTACHMENT0,
           GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
           shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(fbrb_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(client_renderbuffer_id_, static_cast<GLuint>(*result_value));
}

TEST_P(GLES2DecoderTest, GetFramebufferAttachmentParameterivWithTexture) {
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  EXPECT_CALL(*gl_, GetError())
      .WillRepeatedly(Return(GL_NO_ERROR));
  EXPECT_CALL(*gl_,
              FramebufferTexture2DEXT(GL_FRAMEBUFFER,
                                      GL_COLOR_ATTACHMENT0,
                                      GL_TEXTURE_2D,
                                      kServiceTextureId,
                                      0))
      .Times(1)
      .RetiresOnSaturation();
  auto* result =
      static_cast<cmds::GetFramebufferAttachmentParameteriv::Result*>(
          shared_memory_address_);
  result->SetNumResults(0);
  const GLint* result_value = result->GetData();
  cmds::FramebufferTexture2D fbtex_cmd;
  cmds::GetFramebufferAttachmentParameteriv cmd;
  fbtex_cmd.Init(GL_FRAMEBUFFER,
                 GL_COLOR_ATTACHMENT0,
                 GL_TEXTURE_2D,
                 client_texture_id_,
                 0);
  cmd.Init(GL_FRAMEBUFFER,
           GL_COLOR_ATTACHMENT0,
           GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
           shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(fbtex_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(client_texture_id_, static_cast<GLuint>(*result_value));
}

TEST_P(GLES2DecoderWithShaderTest,
       GetRenderbufferParameterivRebindRenderbuffer) {
  SetupTexture();
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 1, 1, GL_NO_ERROR);

  cmds::GetRenderbufferParameteriv cmd;
  cmd.Init(GL_RENDERBUFFER,
           GL_RENDERBUFFER_RED_SIZE,
           shared_memory_id_,
           shared_memory_offset_);

  RestoreRenderbufferBindings();
  EnsureRenderbufferBound(true);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              GetRenderbufferParameterivEXT(
                  GL_RENDERBUFFER, GL_RENDERBUFFER_RED_SIZE, _));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, GetRenderbufferParameterivWithNoBoundTarget) {
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetRenderbufferParameterivEXT(_, _, _)).Times(0);
  cmds::GetRenderbufferParameteriv cmd;
  cmd.Init(GL_RENDERBUFFER,
           GL_RENDERBUFFER_WIDTH,
           shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderWithShaderTest, RenderbufferStorageRebindRenderbuffer) {
  SetupTexture();
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  RestoreRenderbufferBindings();
  EnsureRenderbufferBound(true);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 1, 1, GL_NO_ERROR);
}

TEST_P(GLES2DecoderTest, RenderbufferStorageWithNoBoundTarget) {
  EXPECT_CALL(*gl_, RenderbufferStorageEXT(_, _, _, _)).Times(0);
  cmds::RenderbufferStorage cmd;
  cmd.Init(GL_RENDERBUFFER, GL_RGBA4, 3, 4);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

namespace {

// A class to emulate glReadPixels
class ReadPixelsEmulator {
 public:
  // pack_alignment is the alignment you want ReadPixels to use
  // when copying. The actual data passed in pixels should be contiguous.
  ReadPixelsEmulator(GLsizei width,
                     GLsizei height,
                     GLint bytes_per_pixel,
                     const void* src_pixels,
                     const void* expected_pixels,
                     GLint pack_alignment)
      : width_(width),
        height_(height),
        pack_alignment_(pack_alignment),
        bytes_per_pixel_(bytes_per_pixel),
        src_pixels_(reinterpret_cast<const int8_t*>(src_pixels)),
        expected_pixels_(reinterpret_cast<const int8_t*>(expected_pixels)) {}

  void ReadPixels(GLint x,
                  GLint y,
                  GLsizei width,
                  GLsizei height,
                  GLenum format,
                  GLenum type,
                  void* pixels) const {
    DCHECK_GE(x, 0);
    DCHECK_GE(y, 0);
    DCHECK_LE(x + width, width_);
    DCHECK_LE(y + height, height_);
    for (GLint yy = 0; yy < height; ++yy) {
      const int8_t* src = GetPixelAddress(src_pixels_, x, y + yy);
      const void* dst = ComputePackAlignmentAddress(0, yy, width, pixels);
      UNSAFE_TODO(
          memcpy(const_cast<void*>(dst), src, width * bytes_per_pixel_));
    }
  }

  bool CompareRowSegment(GLint x,
                         GLint y,
                         GLsizei width,
                         const void* data) const {
    DCHECK(x + width <= width_ || width == 0);
    return UNSAFE_TODO(memcmp(data, GetPixelAddress(expected_pixels_, x, y),
                              width * bytes_per_pixel_)) == 0;
  }

  // Helper to compute address of pixel in pack aligned data.
  const void* ComputePackAlignmentAddress(GLint x,
                                          GLint y,
                                          GLsizei width,
                                          const void* address) const {
    GLint unpadded_row_size = ComputeImageDataSize(width, 1);
    GLint two_rows_size = ComputeImageDataSize(width, 2);
    GLsizei padded_row_size = two_rows_size - unpadded_row_size;
    GLint offset = y * padded_row_size + x * bytes_per_pixel_;
    return UNSAFE_TODO(static_cast<const int8_t*>(address) + offset);
  }

  GLint ComputeImageDataSize(GLint width, GLint height) const {
    GLint row_size = width * bytes_per_pixel_;
    if (height > 1) {
      GLint temp = row_size + pack_alignment_ - 1;
      GLint padded_row_size = (temp / pack_alignment_) * pack_alignment_;
      GLint size_of_all_but_last_row = (height - 1) * padded_row_size;
      return size_of_all_but_last_row + row_size;
    } else {
      return height * row_size;
    }
  }

 private:
  const int8_t* GetPixelAddress(const int8_t* base, GLint x, GLint y) const {
    return UNSAFE_TODO(base + (width_ * y + x)) * bytes_per_pixel_;
  }

  GLsizei width_;
  GLsizei height_;
  GLint pack_alignment_;
  GLint bytes_per_pixel_;
  raw_ptr<const int8_t> src_pixels_;
  raw_ptr<const int8_t> expected_pixels_;
};

}  // anonymous namespace

void GLES2DecoderTest::CheckReadPixelsOutOfRange(GLint in_read_x,
                                                 GLint in_read_y,
                                                 GLsizei in_read_width,
                                                 GLsizei in_read_height,
                                                 bool init) {
  const GLsizei kWidth = 5;
  const GLsizei kHeight = 3;
  const GLint kBytesPerPixel = 4;
  const GLint kPackAlignment = 4;
  const GLenum kFormat = GL_RGBA;
  static const uint8_t kSrcPixels[kWidth * kHeight * kBytesPerPixel] = {
      12, 13, 14, 255, 18, 19, 18, 255, 19, 12, 13, 255, 14, 18, 19, 255,
      18, 19, 13, 255, 29, 28, 23, 255, 22, 21, 22, 255, 21, 29, 28, 255,
      23, 22, 21, 255, 22, 21, 28, 255, 31, 34, 39, 255, 37, 32, 37, 255,
      32, 31, 34, 255, 39, 37, 32, 255, 37, 32, 34, 255};

  ClearSharedMemory();

  // We need to setup an FBO so we can know the max size that ReadPixels will
  // access
  if (init) {
    DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
    DoTexImage2D(GL_TEXTURE_2D, 0, kFormat, kWidth, kHeight, 0, kFormat,
                 GL_UNSIGNED_BYTE, shared_memory_id_, kSharedMemoryOffset);
    DoBindFramebuffer(
        GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
    DoFramebufferTexture2D(GL_FRAMEBUFFER,
                           GL_COLOR_ATTACHMENT0,
                           GL_TEXTURE_2D,
                           client_texture_id_,
                           kServiceTextureId,
                           0,
                           GL_NO_ERROR);
    EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
        .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
        .RetiresOnSaturation();
  }

  ReadPixelsEmulator emu(
      kWidth, kHeight, kBytesPerPixel, kSrcPixels, kSrcPixels, kPackAlignment);
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);
  void* dest = UNSAFE_TODO(&result[1]);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  // ReadPixels will be called for valid size only even though the command
  // is requesting a larger size.
  GLint read_x = std::max(0, in_read_x);
  GLint read_y = std::max(0, in_read_y);
  GLint read_end_x = std::clamp(in_read_x + in_read_width, 0, kWidth);
  GLint read_end_y = std::clamp(in_read_y + in_read_height, 0, kHeight);
  GLint read_width = read_end_x - read_x;
  GLint read_height = read_end_y - read_y;
  if (read_width > 0 && read_height > 0) {
    for (GLint yy = read_y; yy < read_end_y; ++yy) {
      EXPECT_CALL(
          *gl_,
          ReadPixels(read_x, yy, read_width, 1, kFormat, GL_UNSIGNED_BYTE, _))
          .WillOnce(Invoke(&emu, &ReadPixelsEmulator::ReadPixels))
          .RetiresOnSaturation();
    }
  }
  cmds::ReadPixels cmd;
  cmd.Init(in_read_x,
           in_read_y,
           in_read_width,
           in_read_height,
           kFormat,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));

  GLint unpadded_row_size = emu.ComputeImageDataSize(in_read_width, 1);
  auto zero = base::HeapArray<int8_t>::Uninit(unpadded_row_size);
  auto pack = base::HeapArray<int8_t>::Uninit(kPackAlignment);
  UNSAFE_TODO(memset(zero.data(), kInitialMemoryValue, unpadded_row_size));
  UNSAFE_TODO(memset(pack.data(), kInitialMemoryValue, kPackAlignment));
  for (GLint yy = 0; yy < in_read_height; ++yy) {
    const int8_t* row = static_cast<const int8_t*>(
        emu.ComputePackAlignmentAddress(0, yy, in_read_width, dest));
    GLint y = in_read_y + yy;
    if (y < 0 || y >= kHeight) {
      UNSAFE_TODO(EXPECT_EQ(0, memcmp(zero.data(), row, unpadded_row_size)));
    } else {
      // check off left.
      GLint num_left_pixels = std::max(-in_read_x, 0);
      GLint num_left_bytes = num_left_pixels * kBytesPerPixel;
      UNSAFE_TODO(EXPECT_EQ(0, memcmp(zero.data(), row, num_left_bytes)));

      // check off right.
      GLint num_right_pixels = std::max(in_read_x + in_read_width - kWidth, 0);
      GLint num_right_bytes = num_right_pixels * kBytesPerPixel;
      UNSAFE_TODO(EXPECT_EQ(
          0, memcmp(zero.data(), row + unpadded_row_size - num_right_bytes,
                    num_right_bytes)));

      // check middle.
      GLint x = std::max(in_read_x, 0);
      GLint num_middle_pixels =
          std::max(in_read_width - num_left_pixels - num_right_pixels, 0);
      UNSAFE_TODO(EXPECT_TRUE(emu.CompareRowSegment(x, y, num_middle_pixels,
                                                    row + num_left_bytes)));
    }

    // check padding
    if (yy != in_read_height - 1) {
      GLint temp = unpadded_row_size + kPackAlignment - 1;
      GLint padded_row_size = (temp / kPackAlignment ) * kPackAlignment;
      GLint num_padding_bytes = padded_row_size - unpadded_row_size;
      if (num_padding_bytes) {
        UNSAFE_TODO(EXPECT_EQ(0, memcmp(pack.data(), row + unpadded_row_size,
                                        num_padding_bytes)));
      }
    }
  }
}

TEST_P(GLES2DecoderTest, ReadPixels) {
  const GLsizei kWidth = 5;
  const GLsizei kHeight = 3;
  const GLint kBytesPerPixel = 4;
  const GLint kPackAlignment = 4;
  static const uint8_t kSrcPixels[kWidth * kHeight * kBytesPerPixel] = {
      12, 13, 14, 255, 18, 19, 18, 255, 19, 12, 13, 255, 14, 18, 19, 255,
      18, 19, 13, 255, 29, 28, 23, 255, 22, 21, 22, 255, 21, 29, 28, 255,
      23, 22, 21, 255, 22, 21, 28, 255, 31, 34, 39, 255, 37, 32, 37, 255,
      32, 31, 34, 255, 39, 37, 32, 255, 37, 32, 34, 255};

  surface_->SetSize(gfx::Size(INT_MAX, INT_MAX));

  ReadPixelsEmulator emu(
      kWidth, kHeight, kBytesPerPixel, kSrcPixels, kSrcPixels, kPackAlignment);
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);
  void* dest = UNSAFE_TODO(&result[1]);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              ReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, _))
      .WillOnce(Invoke(&emu, &ReadPixelsEmulator::ReadPixels));
  cmds::ReadPixels cmd;
  cmd.Init(0,
           0,
           kWidth,
           kHeight,
           GL_RGBA,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 1;
  EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(cmd));
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  for (GLint yy = 0; yy < kHeight; ++yy) {
    EXPECT_TRUE(emu.CompareRowSegment(
        0, yy, kWidth, emu.ComputePackAlignmentAddress(0, yy, kWidth, dest)));
  }
}

TEST_P(GLES3DecoderTest, ReadPixels2PixelPackBufferNoBufferBound) {
  const GLsizei kWidth = 5;
  const GLsizei kHeight = 3;
  EXPECT_CALL(*gl_, ReadPixels(_, _, _, _, _, _, _)).Times(0);

  cmds::ReadPixels cmd;
  cmd.Init(0,
           0,
           kWidth,
           kHeight,
           GL_RGBA,
           GL_UNSIGNED_BYTE,
           0,
           0,
           0,
           0,
           false);
  EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, ReadPixelsBufferBound) {
  const GLsizei kWidth = 5;
  const GLsizei kHeight = 3;
  const GLint kBytesPerPixel = 4;
  GLint size = kWidth * kHeight * kBytesPerPixel;
  EXPECT_CALL(*gl_, ReadPixels(_, _, _, _, _, _, _)).Times(0);
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);

  DoBindBuffer(GL_PIXEL_PACK_BUFFER, client_buffer_id_, kServiceBufferId);
  DoBufferData(GL_PIXEL_PACK_BUFFER, size);

  cmds::ReadPixels cmd;
  cmd.Init(0,
           0,
           kWidth,
           kHeight,
           GL_RGBA,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_EQ(error::kInvalidArguments, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, ReadPixels2PixelPackBuffer) {
  const GLsizei kWidth = 5;
  const GLsizei kHeight = 3;
  const GLint kBytesPerPixel = 4;
  GLint size = kWidth * kHeight * kBytesPerPixel;

  DoBindBuffer(GL_PIXEL_PACK_BUFFER, client_buffer_id_, kServiceBufferId);
  DoBufferData(GL_PIXEL_PACK_BUFFER, size);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              ReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, _));
  cmds::ReadPixels cmd;
  cmd.Init(0,
           0,
           kWidth,
           kHeight,
           GL_RGBA,
           GL_UNSIGNED_BYTE,
           0,
           0,
           0,
           0,
           false);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderManualInitTest, ReadPixelsR16ExtZeroSizeValidationOOB) {
  InitState init;
  init.extensions = "GL_EXT_texture_norm16";
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  InitDecoder(init);

  // Step 1: directly demonstrate the validator/size-calculator desync.
  uint32_t computed = 0xdeadbeef;
  EXPECT_TRUE(GLES2Util::ComputeImageDataSizes(64, 64, 1, GL_R16_EXT,
                                               GL_UNSIGNED_SHORT, 4, &computed,
                                               nullptr, nullptr));
  EXPECT_EQ(8192u, computed);

  // Step 2: send a hostile ReadPixels that a compromised renderer could craft.
  // pixels_shm_offset = end-of-buffer; the decoder validates 0 bytes so this
  // passes and returns base + kSharedBufferSize (one-past-end).
  surface_->SetSize(gfx::Size(INT_MAX, INT_MAX));
  const GLsizei kWidth = 32;
  const GLsizei kHeight = 32;
  // Native R16 + UNSIGNED_SHORT = 2 bytes/pixel -> 32*32*2 = 2048 bytes
  // written by the driver, all of it past the validated buffer end.
  const uint32_t kPixelsOffset = kSharedBufferSize;  // one-past-end

#if defined(ADDRESS_SANITIZER)
  // The transfer buffer is mmap'd shared memory (no ASAN redzones). Poison the
  // page slack past the logical buffer end so ASAN deterministically reports
  // the OOB write instead of relying on whatever mapping happens to follow.
  // kSharedBufferSize = 2048; the mapping is at least one 4096-byte page, so
  // [base+2048, base+4096) is mapped slack we can safely poison.

  // SAFETY: this is the only feasible way to get the pointer we need.
  // This test is temporary regardless and will be removed along with
  // the validating command decoder.
  uint8_t* poison_at = UNSAFE_BUFFERS(
      static_cast<uint8_t*>(shared_memory_base_.get()) + kSharedBufferSize);
  static_assert(kSharedBufferSize == 2048);
  const size_t kPoisonLen = 2048;
  ASAN_POISON_MEMORY_REGION(poison_at, kPoisonLen);
  absl::Cleanup unpoison = [poison_at] {
    ASAN_UNPOISON_MEMORY_REGION(poison_at, kPoisonLen);
  };
#endif

  // The decoder falls through to the native-driver IMPLEMENTATION_COLOR_READ_*
  // query because (GL_R16_EXT, GL_UNSIGNED_SHORT) is not in the hardcoded
  // accepted list. Simulate an Android native driver that returns the sized
  // enum (the precondition documented in the report).
  EXPECT_CALL(*gl_, GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, _))
      .Times(AnyNumber())
      .WillRepeatedly(SetArgPointee<1>(GL_R16_EXT));
  EXPECT_CALL(*gl_, GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, _))
      .Times(AnyNumber())
      .WillRepeatedly(SetArgPointee<1>(GL_UNSIGNED_SHORT));
  EXPECT_CALL(*gl_, GetError())
      .Times(AnyNumber())
      .WillRepeatedly(Return(GL_NO_ERROR));

  // The "native driver" computes its own write size for R16 + UNSIGNED_SHORT
  // (2 bytes/pixel) and writes into the pointer the decoder validated for 0
  // bytes. With kWidth*kHeight*2 = 8192 bytes starting at one-past-end of a
  // 2048-byte transfer buffer, this is a hard OOB write past the shm mapping.
  EXPECT_CALL(
      *gl_, ReadPixels(0, 0, kWidth, kHeight, GL_R16_EXT, GL_UNSIGNED_SHORT, _))
      .Times(AnyNumber())
      .WillRepeatedly([](GLint, GLint, GLsizei w, GLsizei h, GLenum, GLenum,
                         void* pixels) {
        // What a native R16 glReadPixels does: write w*h*2 bytes.

        // SAFETY: taking a shortcut to fill this memory since this
        // test is temporary and will be removed soon along with the
        // validating command decoder.
        UNSAFE_BUFFERS(memset(pixels, 0xAB, static_cast<size_t>(w) * h * 2u));
      });

  cmds::ReadPixels cmd;
  cmd.Init(0, 0, kWidth, kHeight, GL_R16_EXT, GL_UNSIGNED_SHORT,
           shared_memory_id_, kPixelsOffset, /*result_shm_id=*/0,
           /*result_shm_offset=*/0, /*async=*/false);
  // OOB write fires inside this call: ASAN reports a 2048-byte write past the
  // end of the transfer buffer (the region the decoder validated for 0 bytes).
  // After the bandaid fix, the command is rejected with GL_INVALID_OPERATION
  // before reaching glReadPixelsFn.
  ExecuteCmd(cmd);
  GetGLError();  // Consume the INVALID_OPERATION emitted by the fixed decoder.
}

TEST_P(GLES3DecoderTest, ReadPixelsPixelPackBufferIsNotLargeEnough) {
  const GLsizei kWidth = 5;
  const GLsizei kHeight = 3;
  const GLint kBytesPerPixel = 4;
  GLint size = kWidth * kHeight * kBytesPerPixel;

  DoBindBuffer(GL_PIXEL_PACK_BUFFER, client_buffer_id_, kServiceBufferId);

  DoBufferData(GL_PIXEL_PACK_BUFFER, size - 4);
  EXPECT_CALL(*gl_, ReadPixels(_, _, _, _, _, _, _)).Times(0);

  cmds::ReadPixels cmd;
  cmd.Init(0,
           0,
           kWidth,
           kHeight,
           GL_RGBA,
           GL_UNSIGNED_BYTE,
           0,
           0,
           0,
           0,
           false);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

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

  const GLsizei kWidth = 5;
  const GLsizei kHeight = 3;
  const GLint kBytesPerPixel = 4;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;
  const GLint kRowLength = 4;
  GLint size = (kRowLength * (kHeight - 1) + kWidth) * kBytesPerPixel;

  DoBindBuffer(GL_PIXEL_PACK_BUFFER, client_buffer_id_, kServiceBufferId);
  DoBufferData(GL_PIXEL_PACK_BUFFER, size);

  DoPixelStorei(GL_PACK_ROW_LENGTH, kRowLength);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  for (GLint ii = 0; ii < kHeight; ++ii) {
    if (ii + 1 == kHeight) {
      EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ROW_LENGTH, kWidth))
          .Times(1)
          .RetiresOnSaturation();
    }
    void* offset = reinterpret_cast<void*>(ii * kRowLength * kBytesPerPixel);
    EXPECT_CALL(*gl_, ReadPixels(0, ii, kWidth, 1, kFormat, kType, offset))
        .Times(1)
        .RetiresOnSaturation();
    if (ii + 1 == kHeight) {
      EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ROW_LENGTH, kRowLength))
          .Times(1)
          .RetiresOnSaturation();
    }
  }

  cmds::ReadPixels cmd;
  cmd.Init(0, 0, kWidth, kHeight,
           kFormat, kType,
           0, 0, 0, 0,
           false);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

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

  const GLsizei kWidth = 1;
  const GLsizei kHeight = 2;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;
  const GLint kLargeRowLength = 0x7fffffc;
  constexpr GLsizeiptr kByteOffsetToVerify = 0x1ffffff0;
  GLsizeiptr size = kByteOffsetToVerify + 256;

  DoBindBuffer(GL_PIXEL_PACK_BUFFER, client_buffer_id_, kServiceBufferId);
  DoBufferData(GL_PIXEL_PACK_BUFFER, size);

  DoPixelStorei(GL_PACK_ROW_LENGTH, kLargeRowLength);

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

  EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ROW_LENGTH, 0))
      .Times(1)
      .RetiresOnSaturation();
  for (GLint ii = 0; ii < kHeight; ++ii) {
    void* offset = reinterpret_cast<void*>(ii * kByteOffsetToVerify);
    EXPECT_CALL(*gl_, ReadPixels(0, ii, kWidth, 1, kFormat, kType, offset))
        .Times(1)
        .RetiresOnSaturation();
  }
  EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ROW_LENGTH, kLargeRowLength))
      .Times(1)
      .RetiresOnSaturation();

  cmds::ReadPixels cmd;
  cmd.Init(0, 0, kWidth, kHeight, kFormat, kType, 0, 0, 0, 0, false);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

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

  const GLsizei kWidth = 5;
  const GLsizei kHeight = 3;
  const GLint kBytesPerPixel = 4;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;
  const GLint kAlignment = 8;
  const GLint kPadding = 4;
  GLint size = kWidth * kBytesPerPixel * kHeight + kPadding * (kHeight - 1);

  DoBindBuffer(GL_PIXEL_PACK_BUFFER, client_buffer_id_, kServiceBufferId);
  DoBufferData(GL_PIXEL_PACK_BUFFER, size);

  DoPixelStorei(GL_PACK_ALIGNMENT, kAlignment);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  uint8_t* offset = reinterpret_cast<uint8_t*>(0);
  EXPECT_CALL(*gl_,
              ReadPixels(0, 0, kWidth, kHeight - 1, kFormat, kType, offset))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ALIGNMENT, 1))
      .Times(1)
      .RetiresOnSaturation();
  UNSAFE_TODO(offset += (kWidth * kBytesPerPixel + kPadding) * (kHeight - 1));
  EXPECT_CALL(*gl_,
              ReadPixels(0, kHeight - 1, kWidth, 1, kFormat, kType, offset))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ALIGNMENT, kAlignment))
      .Times(1)
      .RetiresOnSaturation();

  cmds::ReadPixels cmd;
  cmd.Init(0, 0, kWidth, kHeight,
           kFormat, kType,
           0, 0, 0, 0,
           false);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

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

  const GLsizei kWidth = 5;
  const GLsizei kHeight = 3;
  const GLint kBytesPerPixel = 4;
  const GLenum kFormat = GL_RGBA;
  const GLenum kType = GL_UNSIGNED_BYTE;
  const GLint kAlignment = 8;
  const GLint kRowLength = 3;
  const GLint kPadding = 4;
  GLint padded_row_size = kRowLength * kBytesPerPixel + kPadding;
  GLint unpadded_row_size = kWidth * kBytesPerPixel;
  GLint size = padded_row_size * (kHeight - 1) + unpadded_row_size;

  DoBindBuffer(GL_PIXEL_PACK_BUFFER, client_buffer_id_, kServiceBufferId);
  DoBufferData(GL_PIXEL_PACK_BUFFER, size);

  DoPixelStorei(GL_PACK_ALIGNMENT, kAlignment);
  DoPixelStorei(GL_PACK_ROW_LENGTH, kRowLength);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  for (GLint ii = 0; ii < kHeight - 1; ++ii) {
    void* offset = reinterpret_cast<void*>(ii * padded_row_size);
    EXPECT_CALL(*gl_, ReadPixels(0, ii, kWidth, 1, kFormat, kType, offset))
        .Times(1)
        .RetiresOnSaturation();
  }
  EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ALIGNMENT, 1))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ROW_LENGTH, kWidth))
      .Times(1)
      .RetiresOnSaturation();
  void* offset = reinterpret_cast<void*>((kHeight - 1) * padded_row_size);
  EXPECT_CALL(*gl_,
              ReadPixels(0, kHeight - 1, kWidth, 1, kFormat, kType, offset))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ALIGNMENT, kAlignment))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ROW_LENGTH, kRowLength))
      .Times(1)
      .RetiresOnSaturation();

  cmds::ReadPixels cmd;
  cmd.Init(0, 0, kWidth, kHeight,
           kFormat, kType,
           0, 0, 0, 0,
           false);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderRGBBackbufferTest, ReadPixelsNoAlphaBackbuffer) {
  const GLsizei kWidth = 3;
  const GLsizei kHeight = 3;
  const GLint kBytesPerPixel = 4;
  const GLint kPackAlignment = 4;
  static const uint8_t kSrcPixels[kWidth * kHeight * kBytesPerPixel] = {
      12, 13, 14, 18, 19, 18, 19, 12, 13, 14, 18, 19, 29, 28, 23, 22, 21, 22,
      21, 29, 28, 23, 22, 21, 31, 34, 39, 37, 32, 37, 32, 31, 34, 39, 37, 32,
  };

  surface_->SetSize(gfx::Size(INT_MAX, INT_MAX));

  ReadPixelsEmulator emu(kWidth, kHeight, kBytesPerPixel, kSrcPixels,
                         kSrcPixels, kPackAlignment);
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);
  void* dest = UNSAFE_TODO(&result[1]);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              ReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, _))
      .WillOnce(Invoke(&emu, &ReadPixelsEmulator::ReadPixels));
  cmds::ReadPixels cmd;
  cmd.Init(0,
           0,
           kWidth,
           kHeight,
           GL_RGBA,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  for (GLint yy = 0; yy < kHeight; ++yy) {
    EXPECT_TRUE(emu.CompareRowSegment(
        0, yy, kWidth, emu.ComputePackAlignmentAddress(0, yy, kWidth, dest)));
  }
}

TEST_P(GLES2DecoderTest, ReadPixelsOutOfRange) {
  static GLint tests[][4] = {
      {
       -2, -1, 9, 5,
      },  // out of range on all sides
      {
       2, 1, 9, 5,
      },  // out of range on right, bottom
      {
       -7, -4, 9, 5,
      },  // out of range on left, top
      {
       0, -5, 9, 5,
      },  // completely off top
      {
       0, 3, 9, 5,
      },  // completely off bottom
      {
       -9, 0, 9, 5,
      },  // completely off left
      {
       5, 0, 9, 5,
      },  // completely off right
  };

  for (size_t tt = 0; tt < std::size(tests); ++tt) {
    CheckReadPixelsOutOfRange(
        UNSAFE_TODO(tests[tt])[0], UNSAFE_TODO(tests[tt])[1],
        UNSAFE_TODO(tests[tt])[2], UNSAFE_TODO(tests[tt])[3], tt == 0);
  }
}

TEST_P(GLES2DecoderTest, ReadPixelsInvalidArgs) {
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);
  EXPECT_CALL(*gl_, ReadPixels(_, _, _, _, _, _, _)).Times(0);
  cmds::ReadPixels cmd;
  cmd.Init(0,
           0,
           -1,
           1,
           GL_RGB,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(0,
           0,
           1,
           -1,
           GL_RGB,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(0,
           0,
           1,
           1,
           GL_RGB,
           GL_INT,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
  cmd.Init(0,
           0,
           1,
           1,
           GL_RGB,
           GL_UNSIGNED_BYTE,
           kInvalidSharedMemoryId,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
  cmd.Init(0,
           0,
           1,
           1,
           GL_RGB,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           kInvalidSharedMemoryOffset,
           result_shm_id,
           result_shm_offset,
           false);
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
  cmd.Init(0,
           0,
           1,
           1,
           GL_RGB,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           kInvalidSharedMemoryId,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
  cmd.Init(0,
           0,
           1,
           1,
           GL_RGB,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           kInvalidSharedMemoryOffset,
           false);
  result->success = 0;
  EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}

TEST_P(GLES2DecoderManualInitTest, ReadPixelsAsyncError) {
  InitState init;
  init.extensions = "GL_ARB_sync";
  init.gl_version = "OpenGL ES 3.0";
  init.has_alpha = true;
  init.request_alpha = true;
  InitDecoder(init);

  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 4;
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);

  EXPECT_CALL(*gl_, GetError())
      // first error check must pass to get to the test
      .WillOnce(Return(GL_NO_ERROR))
      // second check is after BufferData, simulate fail here
      .WillOnce(Return(GL_INVALID_OPERATION))
      // third error check is fall-through call to sync ReadPixels
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();

  EXPECT_CALL(*gl_,
              ReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, _))
      .Times(1);
  EXPECT_CALL(*gl_, GenBuffersARB(1, _)).Times(1);
  EXPECT_CALL(*gl_, DeleteBuffersARB(1, _)).Times(1);
  EXPECT_CALL(*gl_, BindBuffer(GL_PIXEL_PACK_BUFFER, _)).Times(2);
  EXPECT_CALL(*gl_,
              BufferData(GL_PIXEL_PACK_BUFFER, _, nullptr, GL_STREAM_READ))
      .Times(1);

  cmds::ReadPixels cmd;
  cmd.Init(0,
           0,
           kWidth,
           kHeight,
           GL_RGBA,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           true);
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}

class GLES2ReadPixelsAsyncTest : public GLES2DecoderManualInitTest {
 public:
  void SetUp() override {
    InitState init;
    init.extensions = "GL_ARB_sync";
    init.gl_version = "OpenGL ES 3.0";
    init.has_alpha = true;
    init.request_alpha = true;
    InitDecoder(init);
  }

  void SetupReadPixelsAsyncExpectation(GLsizei width, GLsizei height) {
    const size_t kBufferSize = width * height * 4;
    EXPECT_CALL(*gl_, GetError())
        .WillRepeatedly(Return(GL_NO_ERROR))
        .RetiresOnSaturation();

    EXPECT_CALL(*gl_,
                ReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, _))
        .Times(1);
    EXPECT_CALL(*gl_, GenBuffersARB(1, _))
        .WillOnce(SetArgPointee<1>(kServiceBufferId))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl_, BindBuffer(GL_PIXEL_PACK_BUFFER, kServiceBufferId))
        .Times(1);
    EXPECT_CALL(*gl_, BindBuffer(GL_PIXEL_PACK_BUFFER, 0)).Times(1);
    EXPECT_CALL(*gl_, BufferData(GL_PIXEL_PACK_BUFFER, kBufferSize, nullptr,
                                 GL_STREAM_READ))
        .Times(1);
    GLsync sync = reinterpret_cast<GLsync>(kServiceSyncId);
    EXPECT_CALL(*gl_, FenceSync(0x9117, 0)).WillOnce(Return(sync));
    EXPECT_CALL(*gl_, IsSync(sync)).WillRepeatedly(Return(GL_TRUE));
    EXPECT_CALL(*gl_, Flush()).Times(1);
  }

  void FinishReadPixelsAndCheckResult(GLsizei width,
                                      GLsizei height,
                                      char* pixels) {
    EXPECT_TRUE(decoder_->HasMoreIdleWork());

    const size_t kBufferSize = width * height * 4;
    auto buffer = std::make_unique<char[]>(kBufferSize);
    for (size_t i = 0; i < kBufferSize; ++i)
      UNSAFE_TODO(buffer[i]) = i;

    GLsync sync = reinterpret_cast<GLsync>(kServiceSyncId);
    EXPECT_CALL(*gl_, ClientWaitSync(sync, 0, 0))
        .WillOnce(Return(GL_CONDITION_SATISFIED));
    EXPECT_CALL(*gl_, BindBuffer(GL_PIXEL_PACK_BUFFER, kServiceBufferId))
        .Times(1);
    EXPECT_CALL(*gl_, MapBufferRange(GL_PIXEL_PACK_BUFFER, 0, kBufferSize,
                                     GL_MAP_READ_BIT))
        .WillOnce(Return(buffer.get()))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl_, UnmapBuffer(GL_PIXEL_PACK_BUFFER)).Times(1);
    EXPECT_CALL(*gl_, BindBuffer(GL_PIXEL_PACK_BUFFER, 0)).Times(1);
    EXPECT_CALL(*gl_, DeleteBuffersARB(1, _)).Times(1);
    EXPECT_CALL(*gl_, DeleteSync(sync)).Times(1);
    decoder_->PerformIdleWork();
    EXPECT_FALSE(decoder_->HasMoreIdleWork());
    UNSAFE_TODO(EXPECT_EQ(0, memcmp(pixels, buffer.get(), kBufferSize)));
  }
};

TEST_P(GLES2ReadPixelsAsyncTest, ReadPixelsAsync) {
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 4;
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);
  char* pixels = reinterpret_cast<char*>(UNSAFE_TODO(result + 1));

  SetupReadPixelsAsyncExpectation(kWidth, kHeight);

  cmds::ReadPixels cmd;
  cmd.Init(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels_shm_id,
           pixels_shm_offset, result_shm_id, result_shm_offset, true);
  result->success = 0;

  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_TRUE(decoder_->HasMoreIdleWork());

  GLsync sync = reinterpret_cast<GLsync>(kServiceSyncId);
  EXPECT_CALL(*gl_, ClientWaitSync(sync, 0, 0))
      .WillOnce(Return(GL_TIMEOUT_EXPIRED));
  decoder_->PerformIdleWork();

  FinishReadPixelsAndCheckResult(kWidth, kHeight, pixels);
}

TEST_P(GLES2ReadPixelsAsyncTest, ReadPixelsAsyncModifyCommand) {
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  const GLsizei kWidth = 4;
  const GLsizei kHeight = 4;
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);
  char* pixels = reinterpret_cast<char*>(UNSAFE_TODO(result + 1));

  SetupReadPixelsAsyncExpectation(kWidth, kHeight);

  cmds::ReadPixels cmd;
  cmd.Init(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels_shm_id,
           pixels_shm_offset, result_shm_id, result_shm_offset, true);
  result->success = 0;

  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_TRUE(decoder_->HasMoreIdleWork());

  // Change command after ReadPixels issued, but before we finish the read,
  // should have no impact.
  cmd.Init(1, 2, 6, 7, GL_RGB, GL_UNSIGNED_SHORT, pixels_shm_id,
           pixels_shm_offset, result_shm_id, result_shm_offset, false);

  FinishReadPixelsAndCheckResult(kWidth, kHeight, pixels);
}

TEST_P(GLES2ReadPixelsAsyncTest, ReadPixelsAsyncChangePackAlignment) {
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  const GLsizei kWidth = 1;
  const GLsizei kHeight = 4;
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);
  char* pixels = reinterpret_cast<char*>(UNSAFE_TODO(result + 1));

  SetupReadPixelsAsyncExpectation(kWidth, kHeight);

  {
    cmds::ReadPixels cmd;
    cmd.Init(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels_shm_id,
             pixels_shm_offset, result_shm_id, result_shm_offset, true);
    result->success = 0;

    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }
  EXPECT_TRUE(decoder_->HasMoreIdleWork());

  // Changing the pack alignment after the ReadPixels is issued but before we
  // finish the read should have no impact.
  {
    cmds::PixelStorei cmd;
    cmd.Init(GL_PACK_ALIGNMENT, 8);

    EXPECT_CALL(*gl_, PixelStorei(GL_PACK_ALIGNMENT, 8)).Times(1);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  }

  FinishReadPixelsAndCheckResult(kWidth, kHeight, pixels);
}

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

// Check that if a renderbuffer is attached and GL returns
// GL_FRAMEBUFFER_COMPLETE that the buffer is cleared and state is restored.
TEST_P(GLES2DecoderTest, FramebufferRenderbufferClearColor) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  cmds::ClearColor color_cmd;
  cmds::ColorMask color_mask_cmd;
  cmds::Enable enable_cmd;
  cmds::FramebufferRenderbuffer cmd;
  color_cmd.Init(0.1f, 0.2f, 0.3f, 0.4f);
  color_mask_cmd.Init(0, 1, 0, 1);
  enable_cmd.Init(GL_SCISSOR_TEST);
  cmd.Init(GL_FRAMEBUFFER,
           GL_COLOR_ATTACHMENT0,
           GL_RENDERBUFFER,
           client_renderbuffer_id_);
  InSequence sequence;
  EXPECT_CALL(*gl_, ClearColor(0.1f, 0.2f, 0.3f, 0.4f))
      .Times(1)
      .RetiresOnSaturation();
  SetupExpectationsForEnableDisable(GL_SCISSOR_TEST, true);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              FramebufferRenderbufferEXT(GL_FRAMEBUFFER,
                                         GL_COLOR_ATTACHMENT0,
                                         GL_RENDERBUFFER,
                                         kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(color_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(color_mask_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(enable_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}

TEST_P(GLES2DecoderTest, FramebufferRenderbufferClearDepth) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  cmds::ClearDepthf depth_cmd;
  cmds::DepthMask depth_mask_cmd;
  cmds::FramebufferRenderbuffer cmd;
  depth_cmd.Init(0.5f);
  depth_mask_cmd.Init(false);
  cmd.Init(GL_FRAMEBUFFER,
           GL_DEPTH_ATTACHMENT,
           GL_RENDERBUFFER,
           client_renderbuffer_id_);
  InSequence sequence;
  EXPECT_CALL(*gl_, ClearDepth(0.5f)).Times(1).RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              FramebufferRenderbufferEXT(GL_FRAMEBUFFER,
                                         GL_DEPTH_ATTACHMENT,
                                         GL_RENDERBUFFER,
                                         kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(depth_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(depth_mask_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}

TEST_P(GLES2DecoderTest, FramebufferRenderbufferClearStencil) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  cmds::ClearStencil stencil_cmd;
  cmds::StencilMaskSeparate stencil_mask_separate_cmd;
  cmds::FramebufferRenderbuffer cmd;
  stencil_cmd.Init(123);
  stencil_mask_separate_cmd.Init(GL_BACK, 0x1234u);
  cmd.Init(GL_FRAMEBUFFER,
           GL_STENCIL_ATTACHMENT,
           GL_RENDERBUFFER,
           client_renderbuffer_id_);
  InSequence sequence;
  EXPECT_CALL(*gl_, ClearStencil(123)).Times(1).RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              FramebufferRenderbufferEXT(GL_FRAMEBUFFER,
                                         GL_STENCIL_ATTACHMENT,
                                         GL_RENDERBUFFER,
                                         kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(stencil_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(stencil_mask_separate_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}

TEST_P(GLES3DecoderTest, FramebufferRenderbufferClearDepthStencil) {
  DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);
  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  cmds::ClearDepthf depth_cmd;
  cmds::ClearStencil stencil_cmd;
  cmds::FramebufferRenderbuffer cmd;
  depth_cmd.Init(0.5f);
  stencil_cmd.Init(123);
  cmd.Init(
      GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
      client_renderbuffer_id_);
  InSequence sequence;
  EXPECT_CALL(*gl_, ClearDepth(0.5f))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, ClearStencil(123))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, FramebufferRenderbufferEXT(
      GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
      kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, FramebufferRenderbufferEXT(
      GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
      kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(depth_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(stencil_cmd));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  Framebuffer* framebuffer = GetFramebuffer(client_framebuffer_id_);
  ASSERT_TRUE(framebuffer);
  ASSERT_FALSE(framebuffer->GetAttachment(GL_DEPTH_STENCIL_ATTACHMENT));
  ASSERT_TRUE(framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT));
  ASSERT_TRUE(framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT));
}

TEST_P(GLES2DecoderManualInitTest, ActualAlphaMatchesRequestedAlpha) {
  InitState init;
  init.has_alpha = true;
  init.request_alpha = true;
  InitDecoder(init);

  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_);
  result->size = 0;
  cmds::GetIntegerv cmd2;
  cmd2.Init(GL_ALPHA_BITS, shared_memory_id_, shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(decoder_->GetGLES2Util()->GLGetNumValuesReturned(GL_ALPHA_BITS),
            result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(8, result->GetData()[0]);
}

TEST_P(GLES2DecoderManualInitTest, PackedDepthStencilReportsCorrectValues) {
  InitState init;
  init.extensions = "GL_OES_packed_depth_stencil";
  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);

  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_);
  result->size = 0;
  cmds::GetIntegerv cmd2;
  cmd2.Init(GL_STENCIL_BITS, shared_memory_id_, shared_memory_offset_);
  EXPECT_CALL(*gl_, GetIntegerv(GL_STENCIL_BITS, _))
      .WillOnce(SetArgPointee<1>(8))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(decoder_->GetGLES2Util()->GLGetNumValuesReturned(GL_STENCIL_BITS),
            result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(8, result->GetData()[0]);
  result->size = 0;
  cmd2.Init(GL_DEPTH_BITS, shared_memory_id_, shared_memory_offset_);
  EXPECT_CALL(*gl_, GetIntegerv(GL_DEPTH_BITS, _))
      .WillOnce(SetArgPointee<1>(24))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(decoder_->GetGLES2Util()->GLGetNumValuesReturned(GL_DEPTH_BITS),
            result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(24, result->GetData()[0]);
}

TEST_P(GLES2DecoderManualInitTest, PackedDepthStencilRenderbufferDepth) {
  InitState init;
  init.extensions = "GL_OES_packed_depth_stencil";
  init.gl_version = "OpenGL ES 2.0";
  InitDecoder(init);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);

  EnsureRenderbufferBound(false);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))  // for RenderbufferStoage
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))  // for FramebufferRenderbuffer
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))  // for GetIntegerv
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))  // for GetIntegerv
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();

  EXPECT_CALL(
      *gl_,
      RenderbufferStorageEXT(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 100, 50))
      .Times(1)
      .RetiresOnSaturation();
  cmds::RenderbufferStorage cmd;
  cmd.Init(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 100, 50);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_CALL(*gl_,
              FramebufferRenderbufferEXT(GL_FRAMEBUFFER,
                                         GL_DEPTH_ATTACHMENT,
                                         GL_RENDERBUFFER,
                                         kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  cmds::FramebufferRenderbuffer fbrb_cmd;
  fbrb_cmd.Init(GL_FRAMEBUFFER,
                GL_DEPTH_ATTACHMENT,
                GL_RENDERBUFFER,
                client_renderbuffer_id_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(fbrb_cmd));

  auto* result =
      static_cast<cmds::GetIntegerv::Result*>(shared_memory_address_);
  result->size = 0;
  cmds::GetIntegerv cmd2;
  cmd2.Init(GL_STENCIL_BITS, shared_memory_id_, shared_memory_offset_);
  EXPECT_CALL(*gl_, GetIntegerv(GL_STENCIL_BITS, _))
      .WillOnce(SetArgPointee<1>(8))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(decoder_->GetGLES2Util()->GLGetNumValuesReturned(GL_STENCIL_BITS),
            result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(0, result->GetData()[0]);
  result->size = 0;
  cmd2.Init(GL_DEPTH_BITS, shared_memory_id_, shared_memory_offset_);
  EXPECT_CALL(*gl_, GetIntegerv(GL_DEPTH_BITS, _))
      .WillOnce(SetArgPointee<1>(24))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(decoder_->GetGLES2Util()->GLGetNumValuesReturned(GL_DEPTH_BITS),
            result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(24, result->GetData()[0]);
}

TEST_P(GLES2DecoderManualInitTest, PackedDepthStencilRenderbufferStencil) {
  InitState init;
  init.extensions = "GL_OES_packed_depth_stencil";
  init.gl_version = "OpenGL ES 2.0";
  InitDecoder(init);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);

  EnsureRenderbufferBound(false);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))  // for RenderbufferStoage
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))  // for FramebufferRenderbuffer
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))  // for GetIntegerv
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))  // for GetIntegerv
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();

  EXPECT_CALL(
      *gl_,
      RenderbufferStorageEXT(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 100, 50))
      .Times(1)
      .RetiresOnSaturation();
  cmds::RenderbufferStorage cmd;
  cmd.Init(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 100, 50);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_CALL(*gl_,
              FramebufferRenderbufferEXT(GL_FRAMEBUFFER,
                                         GL_STENCIL_ATTACHMENT,
                                         GL_RENDERBUFFER,
                                         kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  cmds::FramebufferRenderbuffer fbrb_cmd;
  fbrb_cmd.Init(GL_FRAMEBUFFER,
                GL_STENCIL_ATTACHMENT,
                GL_RENDERBUFFER,
                client_renderbuffer_id_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(fbrb_cmd));

  auto* result =
      static_cast<cmds::GetIntegerv::Result*>(shared_memory_address_);
  result->size = 0;
  cmds::GetIntegerv cmd2;
  cmd2.Init(GL_STENCIL_BITS, shared_memory_id_, shared_memory_offset_);
  EXPECT_CALL(*gl_, GetIntegerv(GL_STENCIL_BITS, _))
      .WillOnce(SetArgPointee<1>(8))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(decoder_->GetGLES2Util()->GLGetNumValuesReturned(GL_STENCIL_BITS),
            result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(8, result->GetData()[0]);
  result->size = 0;
  cmd2.Init(GL_DEPTH_BITS, shared_memory_id_, shared_memory_offset_);
  EXPECT_CALL(*gl_, GetIntegerv(GL_DEPTH_BITS, _))
      .WillOnce(SetArgPointee<1>(24))
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd2));
  EXPECT_EQ(decoder_->GetGLES2Util()->GLGetNumValuesReturned(GL_DEPTH_BITS),
            result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(0, result->GetData()[0]);
}

TEST_P(GLES2DecoderTest, FramebufferRenderbufferGLError) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  cmds::FramebufferRenderbuffer cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
           client_renderbuffer_id_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              FramebufferRenderbufferEXT(GL_FRAMEBUFFER,
                                         GL_COLOR_ATTACHMENT0,
                                         GL_RENDERBUFFER,
                                         kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
}

TEST_P(GLES2DecoderTest, FramebufferTexture2DGLError) {
  const GLsizei kWidth = 5;
  const GLsizei kHeight = 3;
  const GLenum kFormat = GL_RGB;
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D,
               0,
               kFormat,
               kWidth,
               kHeight,
               0,
               kFormat,
               GL_UNSIGNED_BYTE,
               0,
               0);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              FramebufferTexture2DEXT(GL_FRAMEBUFFER,
                                      GL_COLOR_ATTACHMENT0,
                                      GL_TEXTURE_2D,
                                      kServiceTextureId,
                                      0))
      .Times(1)
      .RetiresOnSaturation();
  cmds::FramebufferTexture2D fbtex_cmd;
  fbtex_cmd.Init(GL_FRAMEBUFFER,
                 GL_COLOR_ATTACHMENT0,
                 GL_TEXTURE_2D,
                 client_texture_id_,
                 0);
  EXPECT_EQ(error::kNoError, ExecuteCmd(fbtex_cmd));
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
}

TEST_P(GLES2DecoderTest, RenderbufferStorageGLError) {
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  EnsureRenderbufferBound(false);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, RenderbufferStorageEXT(GL_RENDERBUFFER, GL_RGBA4, 100, 50))
      .Times(1)
      .RetiresOnSaturation();
  cmds::RenderbufferStorage cmd;
  cmd.Init(GL_RENDERBUFFER, GL_RGBA4, 100, 50);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
}

TEST_P(GLES2DecoderTest, RenderbufferStorageBadArgs) {
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  EXPECT_CALL(*gl_, RenderbufferStorageEXT(_, _, _, _))
      .Times(0)
      .RetiresOnSaturation();
  cmds::RenderbufferStorage cmd;
  cmd.Init(GL_RENDERBUFFER, GL_RGBA4, TestHelper::kMaxRenderbufferSize + 1, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
  cmd.Init(GL_RENDERBUFFER, GL_RGBA4, 1, TestHelper::kMaxRenderbufferSize + 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
}

TEST_P(GLES3DecoderTest, ClearBufferivImmediateValidArgs) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8I, 1, 1, GL_NO_ERROR);
  DoFramebufferRenderbuffer(
      GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
      client_renderbuffer_id_, kServiceRenderbufferId, GL_NO_ERROR);

  // TODO(zmo): Set up expectations for the path where the attachment isn't
  // marked as cleared.
  Framebuffer* framebuffer = GetFramebuffer(client_framebuffer_id_);
  framebuffer->MarkAttachmentAsCleared(
      group().renderbuffer_manager(), nullptr, GL_COLOR_ATTACHMENT0, true);

  auto& cmd = *GetImmediateAs<cmds::ClearBufferivImmediate>();
  GLint temp[4] = { 0 };
  cmd.Init(GL_COLOR, 0, &temp[0]);
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  SetupExpectationsForApplyingDirtyState(
      false, false, false, 0x1111, false, false, 0, 0, false);
  EXPECT_CALL(*gl_, ClearBufferiv(GL_COLOR, 0, PointsToArray(temp, 4)));
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(temp)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, ClearBufferuivImmediateValidArgs) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8UI, 1, 1, GL_NO_ERROR);
  DoFramebufferRenderbuffer(
      GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
      client_renderbuffer_id_, kServiceRenderbufferId, GL_NO_ERROR);

  // TODO(zmo): Set up expectations for the path where the attachment isn't
  // marked as cleared.
  Framebuffer* framebuffer = GetFramebuffer(client_framebuffer_id_);
  framebuffer->MarkAttachmentAsCleared(
      group().renderbuffer_manager(), nullptr, GL_COLOR_ATTACHMENT0, true);

  auto& cmd = *GetImmediateAs<cmds::ClearBufferuivImmediate>();
  GLuint temp[4] = { 0u };
  cmd.Init(GL_COLOR, 0, &temp[0]);
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  SetupExpectationsForApplyingDirtyState(
      false, false, false, 0x1111, false, false, 0, 0, false);
  EXPECT_CALL(*gl_, ClearBufferuiv(GL_COLOR, 0, PointsToArray(temp, 4)));
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(temp)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, ClearBufferfvImmediateValidArgs) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32F, 1, 1,
                        GL_NO_ERROR);
  DoFramebufferRenderbuffer(
      GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
      client_renderbuffer_id_, kServiceRenderbufferId, GL_NO_ERROR);

  // TODO(zmo): Set up expectations for the path where the attachment isn't
  // marked as cleared.
  Framebuffer* framebuffer = GetFramebuffer(client_framebuffer_id_);
  framebuffer->MarkAttachmentAsCleared(
      group().renderbuffer_manager(), nullptr, GL_DEPTH_ATTACHMENT, true);

  cmds::Enable cmd_enable;
  cmd_enable.Init(GL_DEPTH_TEST);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd_enable));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  auto& cmd = *GetImmediateAs<cmds::ClearBufferfvImmediate>();
  GLfloat temp[4] = { 1.0f };
  cmd.Init(GL_DEPTH, 0, &temp[0]);
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  SetupExpectationsForApplyingDirtyState(
      true, true, false, 0x1110, true, true, 0, 0, false);
  EXPECT_CALL(*gl_, ClearBufferfv(GL_DEPTH, 0, PointsToArray(temp, 4)));
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(temp)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, ClearBufferfiValidArgs) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1,
                        GL_NO_ERROR);
  DoFramebufferRenderbuffer(
      GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
      client_renderbuffer_id_, kServiceRenderbufferId, GL_NO_ERROR);

  // TODO(zmo): Set up expectations for the path where the attachment isn't
  // marked as cleared.
  Framebuffer* framebuffer = GetFramebuffer(client_framebuffer_id_);
  framebuffer->MarkAttachmentAsCleared(group().renderbuffer_manager(), nullptr,
                                       GL_DEPTH_ATTACHMENT, true);
  framebuffer->MarkAttachmentAsCleared(group().renderbuffer_manager(), nullptr,
                                       GL_STENCIL_ATTACHMENT, true);

  cmds::Enable cmd_enable;
  cmd_enable.Init(GL_STENCIL_TEST);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd_enable));
  cmd_enable.Init(GL_DEPTH_TEST);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd_enable));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  cmds::ClearBufferfi cmd;
  cmd.Init(GL_DEPTH_STENCIL, 0, 1.0f, 0);
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  SetupExpectationsForApplyingDirtyState(
      true, true, true, 0x1110, true, true,
      GLES2Decoder::kDefaultStencilMask, GLES2Decoder::kDefaultStencilMask,
      true);
  EXPECT_CALL(*gl_, ClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0));
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest,
       RenderbufferStorageMultisampleCHROMIUMGLError) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  InitDecoder(init);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  EnsureRenderbufferBound(false);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  SetupExpectationsForInternalFormatSampleCountsHelper(
      GL_RENDERBUFFER, GL_RGBA4, 1, TestHelper::kMaxSamples);
  EXPECT_CALL(*gl_, RenderbufferStorageMultisample(GL_RENDERBUFFER, 1, GL_RGBA4,
                                                   100, 50))
      .Times(1)
      .RetiresOnSaturation();
  cmds::RenderbufferStorageMultisampleCHROMIUM cmd;
  cmd.Init(GL_RENDERBUFFER, 1, GL_RGBA4, 100, 50);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest,
       RenderbufferStorageMultisampleCHROMIUMBadArgs) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  InitDecoder(init);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  EXPECT_CALL(*gl_, RenderbufferStorageMultisample(_, _, _, _, _))
      .Times(0)
      .RetiresOnSaturation();
  cmds::RenderbufferStorageMultisampleCHROMIUM cmd;
  cmd.Init(GL_RENDERBUFFER,
           TestHelper::kMaxSamples + 1,
           GL_RGBA4,
           TestHelper::kMaxRenderbufferSize,
           1);
  SetupExpectationsForInternalFormatSampleCountsHelper(
      GL_RENDERBUFFER, GL_RGBA4, 1, TestHelper::kMaxSamples);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  cmd.Init(GL_RENDERBUFFER,
           TestHelper::kMaxSamples,
           GL_RGBA4,
           TestHelper::kMaxRenderbufferSize + 1,
           1);
  SetupExpectationsForInternalFormatSampleCountsHelper(
      GL_RENDERBUFFER, GL_RGBA4, 1, TestHelper::kMaxSamples);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());

  cmd.Init(GL_RENDERBUFFER,
           TestHelper::kMaxSamples,
           GL_RGBA4,
           1,
           TestHelper::kMaxRenderbufferSize + 1);
  SetupExpectationsForInternalFormatSampleCountsHelper(
      GL_RENDERBUFFER, GL_RGBA4, 1, TestHelper::kMaxSamples);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_VALUE, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, RenderbufferStorageMultisampleCHROMIUM) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  InitDecoder(init);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  InSequence sequence;
  DoRenderbufferStorageMultisampleCHROMIUM(
      GL_RENDERBUFFER, TestHelper::kMaxSamples, GL_RGBA4,
      TestHelper::kMaxRenderbufferSize, 1, false);
}

TEST_P(GLES2DecoderManualInitTest,
       RenderbufferStorageMultisampleCHROMIUMRebindRenderbuffer) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  InitDecoder(init);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  RestoreRenderbufferBindings();
  InSequence sequence;
  DoRenderbufferStorageMultisampleCHROMIUM(
      GL_RENDERBUFFER, TestHelper::kMaxSamples, GL_RGBA4,
      TestHelper::kMaxRenderbufferSize, 1, true);
}

TEST_P(GLES2DecoderManualInitTest,
       RenderbufferStorageMultisampleEXTNotSupported) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  InitDecoder(init);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  InSequence sequence;
  // GL_CHROMIUM_framebuffer_multisample uses
  // RenderbufferStorageMultisampleCHROMIUM.
  cmds::RenderbufferStorageMultisampleEXT cmd;
  cmd.Init(GL_RENDERBUFFER,
           TestHelper::kMaxSamples,
           GL_RGBA4,
           TestHelper::kMaxRenderbufferSize,
           1);
  EXPECT_EQ(error::kUnknownCommand, ExecuteCmd(cmd));
}

class GLES2DecoderMultisampledRenderToTextureTest
    : public GLES2DecoderTestWithExtensionsOnGLES2 {
 public:
  void TestNotCompatibleWithRenderbufferStorageMultisampleCHROMIUM() {
    DoBindRenderbuffer(
        GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
    cmds::RenderbufferStorageMultisampleCHROMIUM cmd;
    cmd.Init(GL_RENDERBUFFER,
             TestHelper::kMaxSamples,
             GL_RGBA4,
             TestHelper::kMaxRenderbufferSize,
             1);
    EXPECT_EQ(error::kUnknownCommand, ExecuteCmd(cmd));
  }

  void TestRenderbufferStorageMultisampleEXT(const char* extension,
                                             bool rb_rebind) {
    DoBindRenderbuffer(
        GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
    InSequence sequence;

    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    if (rb_rebind) {
      RestoreRenderbufferBindings();
      EnsureRenderbufferBound(true);
    } else {
      EnsureRenderbufferBound(false);
    }
    EXPECT_CALL(*gl_, RenderbufferStorageMultisampleEXT(
                          GL_RENDERBUFFER, TestHelper::kMaxSamples, GL_RGBA4,
                          TestHelper::kMaxRenderbufferSize, 1))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl_, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
    cmds::RenderbufferStorageMultisampleEXT cmd;
    cmd.Init(GL_RENDERBUFFER,
             TestHelper::kMaxSamples,
             GL_RGBA4,
             TestHelper::kMaxRenderbufferSize,
             1);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
  }
};

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

TEST_P(GLES2DecoderMultisampledRenderToTextureTest,
       NotCompatibleWithRenderbufferStorageMultisampleCHROMIUM_EXT) {
  Init("GL_EXT_multisampled_render_to_texture");
  TestNotCompatibleWithRenderbufferStorageMultisampleCHROMIUM();
}

TEST_P(GLES2DecoderMultisampledRenderToTextureTest,
       NotCompatibleWithRenderbufferStorageMultisampleCHROMIUM_IMG) {
  Init("GL_IMG_multisampled_render_to_texture");
  TestNotCompatibleWithRenderbufferStorageMultisampleCHROMIUM();
}

TEST_P(GLES2DecoderMultisampledRenderToTextureTest,
       RenderbufferStorageMultisampleEXT_EXT) {
  Init("GL_EXT_multisampled_render_to_texture");
  TestRenderbufferStorageMultisampleEXT("GL_EXT_multisampled_render_to_texture",
                                        false);
}

TEST_P(GLES2DecoderMultisampledRenderToTextureTest,
       RenderbufferStorageMultisampleEXT_IMG) {
  Init("GL_IMG_multisampled_render_to_texture");
  TestRenderbufferStorageMultisampleEXT("GL_IMG_multisampled_render_to_texture",
                                        false);
}

TEST_P(GLES2DecoderMultisampledRenderToTextureTest,
       RenderbufferStorageMultisampleEXT_EXT_RebindRenderbuffer) {
  Init("GL_EXT_multisampled_render_to_texture");
  TestRenderbufferStorageMultisampleEXT("GL_EXT_multisampled_render_to_texture",
                                        true);
}

TEST_P(GLES2DecoderMultisampledRenderToTextureTest,
       RenderbufferStorageMultisampleEXT_IMG_RebindRenderbuffer) {
  Init("GL_IMG_multisampled_render_to_texture");
  TestRenderbufferStorageMultisampleEXT("GL_IMG_multisampled_render_to_texture",
                                        true);
}

TEST_P(GLES2DecoderTest, ReadPixelsGLError) {
  GLenum kFormat = GL_RGBA;
  GLint x = 0;
  GLint y = 0;
  GLsizei width = 2;
  GLsizei height = 4;
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_OUT_OF_MEMORY))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_,
              ReadPixels(x, y, width, height, kFormat, GL_UNSIGNED_BYTE, _))
      .Times(1)
      .RetiresOnSaturation();
  cmds::ReadPixels cmd;
  cmd.Init(x,
           y,
           width,
           height,
           kFormat,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
}

TEST_P(GLES2DecoderWithShaderTest, UnClearedAttachmentsGetClearedOnClear) {
  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  // Register a texture id.
  EXPECT_CALL(*gl_, GenTextures(_, _))
      .WillOnce(SetArgPointee<1>(kFBOServiceTextureId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kFBOClientTextureId);

  // Setup "render to" texture.
  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);
  // Set scissor rect and enable GL_SCISSOR_TEST to make sure we re-enable it
  // and restore the rect again after the clear.
  DoEnableDisable(GL_SCISSOR_TEST, true);
  DoScissor(0, 0, 64, 64);

  // Setup "render from" texture.
  SetupTexture();

  SetupExpectationsForFramebufferClearing(GL_FRAMEBUFFER,       // target
                                          GL_COLOR_BUFFER_BIT,  // clear bits
                                          0, 0, 0,
                                          0,      // color
                                          0,      // stencil
                                          1.0f,   // depth
                                          true,  // scissor test
                                          0, 0, 64, 64);
  SetupExpectationsForApplyingDirtyState(false,    // Framebuffer is RGB
                                         false,    // Framebuffer has depth
                                         false,    // Framebuffer has stencil
                                         0x1111,   // color bits
                                         false,    // depth mask
                                         false,    // depth enabled
                                         0,        // front stencil mask
                                         0,        // back stencil mask
                                         false);   // stencil enabled

  EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation();

  cmds::Clear cmd;
  cmd.Init(GL_COLOR_BUFFER_BIT);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderWithShaderTest, UnClearedAttachmentsGetClearedOnReadPixels) {
  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  // Register a texture id.
  EXPECT_CALL(*gl_, GenTextures(_, _))
      .WillOnce(SetArgPointee<1>(kFBOServiceTextureId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kFBOClientTextureId);

  // Setup "render to" texture.
  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);
  DoEnableDisable(GL_SCISSOR_TEST, false);
  DoScissor(0, 0, 1, 1);

  // Setup "render from" texture.
  SetupTexture();

  SetupExpectationsForFramebufferClearing(GL_FRAMEBUFFER,       // target
                                          GL_COLOR_BUFFER_BIT,  // clear bits
                                          0, 0, 0,
                                          0,      // color
                                          0,      // stencil
                                          1.0f,   // depth
                                          false,  // scissor test
                                          0, 0, 1, 1);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, ReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, _))
      .Times(1)
      .RetiresOnSaturation();
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);
  cmds::ReadPixels cmd;
  cmd.Init(0,
           0,
           1,
           1,
           GL_RGBA,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, CopyTexImage2DValidInternalFormat) {
  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLenum internal_format = GL_RG32I;
  GLenum format = GL_RG_INTEGER;
  GLenum type = GL_INT;
  GLsizei width = 16;
  GLsizei height = 8;
  GLint border = 0;

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

  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, level, internal_format, width, height, 0, format,
               type, shared_memory_id_, kSharedMemoryOffset);
  DoBindFramebuffer(
      GL_READ_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_READ_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();

  EXPECT_CALL(*gl_,
              CopyTexImage2D(
                  target, level, internal_format, 0, 0, width, height, border))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();

  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  cmds::CopyTexImage2D cmd;
  cmd.Init(target, level, internal_format, 0, 0, width, height);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderManualInitTest, CopyTexImage2DValidInternalFormat_FloatEXT) {
  InitState init;
  init.extensions = "GL_EXT_color_buffer_float";
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  InitDecoder(init);

  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLenum internal_format = GL_RG16F;
  GLenum format = GL_RGBA;
  GLenum type = GL_HALF_FLOAT;
  GLsizei width = 16;
  GLsizei height = 8;
  GLint border = 0;

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

  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, level, GL_RGBA16F, width, height, 0, format, type,
               shared_memory_id_, kSharedMemoryOffset);
  DoBindFramebuffer(
      GL_READ_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_READ_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);
  EXPECT_CALL(*gl_,
              CopyTexImage2D(
                  target, level, internal_format, 0, 0, width, height, border))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  cmds::CopyTexImage2D cmd;
  cmd.Init(target, level, internal_format, 0, 0, width, height);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderManualInitTest,
    CopyTexImage2DInvalidInternalFormat_FloatEXT) {
  InitState init;
  init.extensions = "GL_EXT_color_buffer_float";
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;
  InitDecoder(init);

  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLenum internal_format = GL_RG16F;
  GLenum format = GL_RGBA;
  GLenum type = GL_UNSIGNED_BYTE;
  GLsizei width = 16;
  GLsizei height = 8;
  GLint border = 0;

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

  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, width, height, 0, format, type,
               shared_memory_id_, kSharedMemoryOffset);
  DoBindFramebuffer(
      GL_READ_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_READ_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);
  EXPECT_CALL(*gl_,
              CopyTexImage2D(
                  target, level, internal_format, 0, 0, width, height, border))
      .Times(0);
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  cmds::CopyTexImage2D cmd;
  cmd.Init(target, level, internal_format, 0, 0, width, height);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, CopyTexImage2DInvalidInternalFormat) {
  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLenum internal_format = GL_RG_INTEGER;
  GLenum format = GL_RG;
  GLenum type = GL_UNSIGNED_BYTE;
  GLsizei width = 16;
  GLsizei height = 8;
  GLint border = 0;

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

  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, level, GL_RG8, width, height, 0, format, type,
               shared_memory_id_, kSharedMemoryOffset);
  DoBindFramebuffer(
      GL_READ_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_READ_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);
  EXPECT_CALL(*gl_,
              CopyTexImage2D(
                  target, level, internal_format, 0, 0, width, height, border))
      .Times(0);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  cmds::CopyTexImage2D cmd;
  cmd.Init(target, level, internal_format, 0, 0, width, height);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
}

TEST_P(GLES3DecoderTest, CopyTexImage2DInvalidInternalFormat_Float) {
  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLenum internal_format = GL_RG16F;
  GLenum format = GL_RGBA;
  GLenum type = GL_UNSIGNED_BYTE;
  GLsizei width = 16;
  GLsizei height = 8;
  GLint border = 0;

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

  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, width, height, 0, format, type,
               shared_memory_id_, kSharedMemoryOffset);
  DoBindFramebuffer(
      GL_READ_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_READ_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);
  EXPECT_CALL(*gl_,
              CopyTexImage2D(
                  target, level, internal_format, 0, 0, width, height, border))
      .Times(0);
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  cmds::CopyTexImage2D cmd;
  cmd.Init(target, level, internal_format, 0, 0, width, height);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, CopyTexImage2DInvalidInternalFormat_Integer) {
  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLenum internal_format = GL_RG8I;
  GLenum format = GL_RG_INTEGER;
  GLenum type = GL_UNSIGNED_BYTE;
  GLsizei width = 16;
  GLsizei height = 8;
  GLint border = 0;

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

  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, level, GL_RG8UI, width, height, 0, format, type,
               shared_memory_id_, kSharedMemoryOffset);
  DoBindFramebuffer(
      GL_READ_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_READ_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);
  EXPECT_CALL(*gl_,
              CopyTexImage2D(
                  target, level, internal_format, 0, 0, width, height, border))
      .Times(0);
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  cmds::CopyTexImage2D cmd;
  cmd.Init(target, level, internal_format, 0, 0, width, height);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, CopyTexImage2DInvalidInternalFormat_sRGB) {
  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLenum internal_format = GL_SRGB8;
  GLenum format = GL_RGB;
  GLenum type = GL_UNSIGNED_BYTE;
  GLsizei width = 16;
  GLsizei height = 8;
  GLint border = 0;

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

  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, level, GL_RGB, width, height, 0, format, type,
               shared_memory_id_, kSharedMemoryOffset);
  DoBindFramebuffer(
      GL_READ_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_READ_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);
  EXPECT_CALL(*gl_,
              CopyTexImage2D(
                  target, level, internal_format, 0, 0, width, height, border))
      .Times(0);
  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  cmds::CopyTexImage2D cmd;
  cmd.Init(target, level, internal_format, 0, 0, width, height);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest,
       UnClearedAttachmentsGetClearedOnReadPixelsAndDrawBufferGetsRestored) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  InitDecoder(init);
  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  // Register a texture id.
  EXPECT_CALL(*gl_, GenTextures(_, _))
      .WillOnce(SetArgPointee<1>(kFBOServiceTextureId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kFBOClientTextureId);

  // Setup "render from" texture.
  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);
  DoBindFramebuffer(
      GL_READ_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_READ_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);

  // Set scissor rect and disable GL_SCISSOR_TEST to make sure we enable it in
  // the clear, then disable it and restore the rect again.
  DoScissor(0, 0, 32, 32);
  DoEnableDisable(GL_SCISSOR_TEST, false);

  SetupExpectationsForFramebufferClearingMulti(
      kServiceFramebufferId,  // read framebuffer service id
      0,                      // backbuffer service id
      GL_READ_FRAMEBUFFER,    // target
      GL_COLOR_BUFFER_BIT,    // clear bits
      0, 0, 0,
      0,      // color
      0,      // stencil
      1.0f,   // depth
      false,  // scissor test
      0, 0, 32, 32);

  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, ReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, _))
      .Times(1)
      .RetiresOnSaturation();
  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  uint32_t result_shm_id = shared_memory_id_;
  uint32_t result_shm_offset = kSharedMemoryOffset;
  uint32_t pixels_shm_id = shared_memory_id_;
  uint32_t pixels_shm_offset = kSharedMemoryOffset + sizeof(*result);
  cmds::ReadPixels cmd;
  cmd.Init(0,
           0,
           1,
           1,
           GL_RGBA,
           GL_UNSIGNED_BYTE,
           pixels_shm_id,
           pixels_shm_offset,
           result_shm_id,
           result_shm_offset,
           false);
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

// Regression test: when lazily clearing a READ FBO (with a different DRAW
// FBO), the decoder must rebind the lazy-clear target as DRAW before
// calling PrepareDrawBuffersForClearingUninitializedAttachments().
// Otherwise the wrong FBO's state is mutated, leading to skipped clears
// and potential memory disclosure.
TEST_P(GLES3DecoderTest, LazyClearReadFBORebindsDrawBeforePrepareDrawBuffers) {
  // Setup: fboA with uncleared RGBA8 renderbuffer and drawBuffers=NONE
  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR);

  DoBindFramebuffer(GL_DRAW_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);
  DoFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                            GL_RENDERBUFFER, client_renderbuffer_id_,
                            kServiceRenderbufferId, GL_NO_ERROR);
  {
    // Set drawBuffers to NONE on fboA.
    const GLenum bufs[] = {GL_NONE};
    EXPECT_CALL(*gl_, DrawBuffersARB(1, _)).Times(1).RetiresOnSaturation();
    auto& db = *GetImmediateAs<cmds::DrawBuffersEXTImmediate>();
    db.Init(1, bufs);
    EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(db, sizeof(bufs)));
  }

  // Bind fboB as DRAW (draw_framebuffer != fboA).
  EXPECT_CALL(*gl_, GenFramebuffersEXT(1, _))
      .WillOnce(SetArgPointee<1>(kNewServiceId))
      .RetiresOnSaturation();
  GLuint fbo_b_client = client_framebuffer_id_ + 1;
  GenHelper<cmds::GenFramebuffersImmediate>(fbo_b_client);
  DoBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_b_client, kNewServiceId);

  // Bind fboA as READ.
  DoBindFramebuffer(GL_READ_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);

  // Trigger ReadPixels, which should lazy clear fboA.
  struct Tracker {
    GLuint bound_draw_fbo;
    GLuint fbo_at_prepare_drawbuffers;
    GLuint fbo_at_clear;
    int drawbuffers_calls;
  };
  auto t = std::make_shared<Tracker>();
  t->bound_draw_fbo = kNewServiceId;
  t->fbo_at_prepare_drawbuffers = 0xDEADBEEF;
  t->fbo_at_clear = 0xDEADBEEF;
  t->drawbuffers_calls = 0;

  EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER))
      .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
      .RetiresOnSaturation();

  EXPECT_CALL(*gl_, BindFramebufferEXT(_, _))
      .WillRepeatedly([t](GLenum target, GLuint id) {
        if (target == GL_DRAW_FRAMEBUFFER || target == GL_FRAMEBUFFER) {
          t->bound_draw_fbo = id;
        }
      });

  EXPECT_CALL(*gl_, DrawBuffersARB(_, _))
      .WillRepeatedly([t](GLsizei, const GLenum*) {
        // First call is PrepareDrawBuffers, second is RestoreDrawBuffers.
        if (t->drawbuffers_calls++ == 0) {
          t->fbo_at_prepare_drawbuffers = t->bound_draw_fbo;
        }
      });

  EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT))
      .WillOnce([t](GLbitfield) { t->fbo_at_clear = t->bound_draw_fbo; })
      .RetiresOnSaturation();

  // Absorb the rest of the lazy-clear / restore-state noise.
  EXPECT_CALL(*gl_, ClearColor(_, _, _, _)).Times(AnyNumber());
  EXPECT_CALL(*gl_, ColorMask(_, _, _, _)).Times(AnyNumber());
  EXPECT_CALL(*gl_, ClearStencil(_)).Times(AnyNumber());
  EXPECT_CALL(*gl_, ClearDepth(_)).Times(AnyNumber());
  EXPECT_CALL(*gl_, StencilMaskSeparate(_, _)).Times(AnyNumber());
  EXPECT_CALL(*gl_, DepthMask(_)).Times(AnyNumber());
  EXPECT_CALL(*gl_, Disable(_)).Times(AnyNumber());
  EXPECT_CALL(*gl_, Enable(_)).Times(AnyNumber());
  EXPECT_CALL(*gl_, Scissor(_, _, _, _)).Times(AnyNumber());
  EXPECT_CALL(*gl_, WindowRectanglesEXT(_, _, _)).Times(AnyNumber());
  EXPECT_CALL(*gl_, GetError()).WillRepeatedly(Return(GL_NO_ERROR));
  EXPECT_CALL(*gl_, ReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, _))
      .Times(1)
      .RetiresOnSaturation();

  auto* result = GetSharedMemoryAs<cmds::ReadPixels::Result*>();
  cmds::ReadPixels rp;
  rp.Init(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, shared_memory_id_,
          kSharedMemoryOffset + sizeof(*result), shared_memory_id_,
          kSharedMemoryOffset, false);
  result->success = 0;
  EXPECT_EQ(error::kNoError, ExecuteCmd(rp));

  // Assertions
  // PrepareDrawBuffers should have been called.
  EXPECT_GE(t->drawbuffers_calls, 1)
      << "PrepareDrawBuffers should have issued glDrawBuffersARB.";

  // glClear must target fboA.
  EXPECT_EQ(static_cast<GLuint>(kServiceFramebufferId), t->fbo_at_clear)
      << "glClear targetted wrong FBO.";

  // Verify fboA was bound as DRAW when PrepareDrawBuffers was called.
  EXPECT_EQ(static_cast<GLuint>(kServiceFramebufferId),
            t->fbo_at_prepare_drawbuffers)
      << "PrepareDrawBuffers called with wrong DRAW framebuffer bound: "
      << t->fbo_at_prepare_drawbuffers << " instead of "
      << kServiceFramebufferId;
}

TEST_P(GLES2DecoderWithShaderTest, CopyTexImageWithInCompleteFBOFails) {
  GLenum target = GL_TEXTURE_2D;
  GLint level = 0;
  GLenum internal_format = GL_RGBA;
  GLsizei width = 2;
  GLsizei height = 4;
  SetupTexture();
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 0, 0, GL_NO_ERROR);
  DoFramebufferRenderbuffer(GL_FRAMEBUFFER,
                            GL_COLOR_ATTACHMENT0,
                            GL_RENDERBUFFER,
                            client_renderbuffer_id_,
                            kServiceRenderbufferId,
                            GL_NO_ERROR);

  EXPECT_CALL(*gl_, CopyTexImage2D(_, _, _, _, _, _, _, _))
      .Times(0)
      .RetiresOnSaturation();
  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());
}

void GLES2DecoderWithShaderTest::CheckRenderbufferChangesMarkFBOAsNotComplete(
    bool bound_fbo) {
  FramebufferManager* framebuffer_manager = GetFramebufferManager();
  SetupTexture();
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 1, 1, GL_NO_ERROR);
  DoFramebufferRenderbuffer(GL_FRAMEBUFFER,
                            GL_COLOR_ATTACHMENT0,
                            GL_RENDERBUFFER,
                            client_renderbuffer_id_,
                            kServiceRenderbufferId,
                            GL_NO_ERROR);

  if (!bound_fbo) {
    DoBindFramebuffer(GL_FRAMEBUFFER, 0, 0);
  }

  Framebuffer* framebuffer =
      framebuffer_manager->GetFramebuffer(client_framebuffer_id_);
  ASSERT_TRUE(framebuffer != nullptr);
  framebuffer_manager->MarkAsComplete(framebuffer);
  EXPECT_TRUE(framebuffer_manager->IsComplete(framebuffer));

  // Test that renderbufferStorage marks fbo as not complete.
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, 1, 1, GL_NO_ERROR);
  EXPECT_FALSE(framebuffer_manager->IsComplete(framebuffer));
  framebuffer_manager->MarkAsComplete(framebuffer);
  EXPECT_TRUE(framebuffer_manager->IsComplete(framebuffer));

  // Test deleting renderbuffer marks fbo as not complete.
  DoDeleteRenderbuffer(client_renderbuffer_id_, kServiceRenderbufferId);
  if (bound_fbo) {
    EXPECT_FALSE(framebuffer_manager->IsComplete(framebuffer));
  } else {
    EXPECT_TRUE(framebuffer_manager->IsComplete(framebuffer));
  }
  // Cleanup
  DoDeleteFramebuffer(client_framebuffer_id_,
                      kServiceFramebufferId,
                      bound_fbo,
                      GL_FRAMEBUFFER,
                      0,
                      bound_fbo,
                      GL_FRAMEBUFFER,
                      0);
}

TEST_P(GLES2DecoderWithShaderTest,
       RenderbufferChangesMarkFBOAsNotCompleteBoundFBO) {
  CheckRenderbufferChangesMarkFBOAsNotComplete(true);
}

TEST_P(GLES2DecoderWithShaderTest,
       RenderbufferChangesMarkFBOAsNotCompleteUnboundFBO) {
  CheckRenderbufferChangesMarkFBOAsNotComplete(false);
}

void GLES2DecoderWithShaderTest::CheckTextureChangesMarkFBOAsNotComplete(
    bool bound_fbo) {
  FramebufferManager* framebuffer_manager = GetFramebufferManager();
  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  // Register a texture id.
  EXPECT_CALL(*gl_, GenTextures(_, _))
      .WillOnce(SetArgPointee<1>(kFBOServiceTextureId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kFBOClientTextureId);

  SetupTexture();

  // Setup "render to" texture.
  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);

  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER,
                        GL_DEPTH_COMPONENT16,
                        1,
                        1,
                        GL_NO_ERROR);
  DoFramebufferRenderbuffer(GL_FRAMEBUFFER,
                            GL_DEPTH_ATTACHMENT,
                            GL_RENDERBUFFER,
                            client_renderbuffer_id_,
                            kServiceRenderbufferId,
                            GL_NO_ERROR);

  if (!bound_fbo) {
    DoBindFramebuffer(GL_FRAMEBUFFER, 0, 0);
  }

  Framebuffer* framebuffer =
      framebuffer_manager->GetFramebuffer(client_framebuffer_id_);
  ASSERT_TRUE(framebuffer != nullptr);
  framebuffer_manager->MarkAsComplete(framebuffer);
  EXPECT_TRUE(framebuffer_manager->IsComplete(framebuffer));

  // Test TexImage2D marks fbo as not complete.
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, 0, 0);
  EXPECT_FALSE(framebuffer_manager->IsComplete(framebuffer));
  framebuffer_manager->MarkAsComplete(framebuffer);
  EXPECT_TRUE(framebuffer_manager->IsComplete(framebuffer));

  // Test CopyImage2D marks fbo as not complete.
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, CopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 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_RGB, 0, 0, 1, 1);
  // Unbind fbo and bind again after CopyTexImage2D tp avoid feedback loops.
  if (bound_fbo) {
    DoBindFramebuffer(GL_FRAMEBUFFER, 0, 0);
  }
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  if (bound_fbo) {
    DoBindFramebuffer(
        GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  }
  EXPECT_FALSE(framebuffer_manager->IsComplete(framebuffer));

  // Test deleting texture marks fbo as not complete.
  framebuffer_manager->MarkAsComplete(framebuffer);
  EXPECT_TRUE(framebuffer_manager->IsComplete(framebuffer));
  DoDeleteTexture(kFBOClientTextureId, kFBOServiceTextureId);

  if (bound_fbo) {
    EXPECT_FALSE(framebuffer_manager->IsComplete(framebuffer));
  } else {
    EXPECT_TRUE(framebuffer_manager->IsComplete(framebuffer));
  }
  // Cleanup
  DoDeleteFramebuffer(client_framebuffer_id_,
                      kServiceFramebufferId,
                      bound_fbo,
                      GL_FRAMEBUFFER,
                      0,
                      bound_fbo,
                      GL_FRAMEBUFFER,
                      0);
}

TEST_P(GLES2DecoderWithShaderTest, TextureChangesMarkFBOAsNotCompleteBoundFBO) {
  CheckTextureChangesMarkFBOAsNotComplete(true);
}

TEST_P(GLES2DecoderWithShaderTest,
       TextureChangesMarkFBOAsNotCompleteUnboundFBO) {
  CheckTextureChangesMarkFBOAsNotComplete(false);
}

TEST_P(GLES2DecoderTest, CanChangeSurface) {
  scoped_refptr<GLSurfaceMock> other_surface(new GLSurfaceMock);
  EXPECT_CALL(*other_surface.get(), GetBackingFramebufferObject())
      .WillOnce(Return(7));
  EXPECT_CALL(*gl_, BindFramebufferEXT(GL_FRAMEBUFFER, 7));

  decoder_->SetSurface(other_surface);
}

TEST_P(GLES3DecoderTest, DrawBuffersEXTImmediateSucceeds) {
  const GLsizei count = 1;
  const GLenum bufs[] = {GL_COLOR_ATTACHMENT0};
  auto& cmd = *GetImmediateAs<cmds::DrawBuffersEXTImmediate>();
  cmd.Init(count, bufs);

  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  EXPECT_CALL(*gl_, DrawBuffersARB(count, _)).Times(1).RetiresOnSaturation();
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(bufs)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, DrawBuffersEXTImmediateFails) {
  const GLsizei count = 1;
  const GLenum bufs[] = {GL_COLOR_ATTACHMENT1_EXT};
  auto& cmd = *GetImmediateAs<cmds::DrawBuffersEXTImmediate>();
  cmd.Init(count, bufs);

  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(bufs)));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, DrawBuffersEXTImmediateBackbuffer) {
  const GLsizei count = 1;
  const GLenum bufs[] = {GL_BACK};
  auto& cmd = *GetImmediateAs<cmds::DrawBuffersEXTImmediate>();
  cmd.Init(count, bufs);

  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(bufs)));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  DoBindFramebuffer(GL_FRAMEBUFFER, 0, 0);  // unbind

  EXPECT_CALL(*gl_, DrawBuffersARB(count, _)).Times(1).RetiresOnSaturation();

  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(bufs)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, DrawBuffersEXTMainFramebuffer) {
  auto& cmd = *GetImmediateAs<cmds::DrawBuffersEXTImmediate>();
  {
    const GLenum bufs[] = {GL_BACK};
    const GLsizei count = std::size(bufs);
    cmd.Init(count, bufs);

    EXPECT_CALL(*gl_, DrawBuffersARB(count, Pointee(GL_BACK)))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(bufs)));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
  }
  {
    const GLsizei count = 0;
    cmd.Init(count, nullptr);

    EXPECT_CALL(*gl_, DrawBuffersARB(_, _))
        .Times(0)
        .RetiresOnSaturation();
    EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, 0));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
  }
  {
    const GLenum bufs[] = {GL_BACK, GL_NONE};
    const GLsizei count = std::size(bufs);
    cmd.Init(count, bufs);

    EXPECT_CALL(*gl_, DrawBuffersARB(_, _)).Times(0).RetiresOnSaturation();
    EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(bufs)));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
  }
}

TEST_P(GLES2DecoderManualInitTest, InvalidateFramebufferBinding) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  InitDecoder(init);

  // EXPECT_EQ can't be used to compare function pointers
  EXPECT_TRUE(
      gl::MockGLInterface::GetGLProcAddress("glInvalidateFramebuffer") !=
      reinterpret_cast<gl::GLFunctionPointerType>(
          gl::g_current_gl_driver->fn.glDiscardFramebufferEXTFn));
  EXPECT_TRUE(
      gl::MockGLInterface::GetGLProcAddress("glInvalidateFramebuffer") !=
      gl::MockGLInterface::GetGLProcAddress("glDiscardFramebufferEXT"));
}

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

  // EXPECT_EQ can't be used to compare function pointers
  EXPECT_TRUE(
      gl::MockGLInterface::GetGLProcAddress("glDiscardFramebufferEXT") ==
      reinterpret_cast<gl::GLFunctionPointerType>(
          gl::g_current_gl_driver->fn.glDiscardFramebufferEXTFn));

  const GLenum target = GL_FRAMEBUFFER;
  const GLsizei count = 1;
  const GLenum attachments[] = {GL_COLOR_ATTACHMENT0};

  SetupTexture();
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         client_texture_id_,
                         kServiceTextureId,
                         0,
                         GL_NO_ERROR);
  FramebufferManager* framebuffer_manager = GetFramebufferManager();
  Framebuffer* framebuffer =
      framebuffer_manager->GetFramebuffer(client_framebuffer_id_);
  EXPECT_TRUE(framebuffer->IsCleared());

  EXPECT_CALL(*gl_, DiscardFramebufferEXT(target, count, _))
      .Times(1)
      .RetiresOnSaturation();
  auto& cmd = *GetImmediateAs<cmds::DiscardFramebufferEXTImmediate>();
  cmd.Init(target, count, attachments);

  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(attachments)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_FALSE(framebuffer->IsCleared());
}

TEST_P(GLES2DecoderManualInitTest,
       InvalidateFramebufferIncompleteFBOMarkUncleared) {
  InitState init;
  init.gl_version = "OpenGL ES 3.0";
  init.context_type = CONTEXT_TYPE_OPENGLES3;

  gpu::GpuDriverBugWorkarounds workarounds;
  workarounds.dont_invalidate_incomplete_fbos = true;
  InitDecoderWithWorkarounds(init, workarounds);

  const GLenum target = GL_FRAMEBUFFER;
  const GLsizei count = 1;
  const GLenum attachments[] = {GL_COLOR_ATTACHMENT0};

  // Setup texture 1 (1x1)
  SetupTexture();

  // Setup texture 2 (2x2)
  const GLuint kAnotherClientTextureId = 200;
  const GLuint kAnotherServiceTextureId = 201;
  EXPECT_CALL(*gl_, GenTextures(1, _))
      .WillOnce(SetArgPointee<1>(kAnotherServiceTextureId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kAnotherClientTextureId);

  DoBindTexture(GL_TEXTURE_2D, kAnotherClientTextureId,
                kAnotherServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
               shared_memory_id_, kSharedMemoryOffset);

  DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);

  // Attach 1x1 to COLOR_ATTACHMENT0
  DoFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                         client_texture_id_, kServiceTextureId, 0, GL_NO_ERROR);
  // Attach 2x2 to COLOR_ATTACHMENT1 -> makes it incomplete
  // (INCOMPLETE_DIMENSIONS)
  DoFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
                         kAnotherClientTextureId, kAnotherServiceTextureId, 0,
                         GL_NO_ERROR);

  FramebufferManager* framebuffer_manager = GetFramebufferManager();
  Framebuffer* framebuffer =
      framebuffer_manager->GetFramebuffer(client_framebuffer_id_);
  ASSERT_TRUE(framebuffer != nullptr);

  // Ensure it is incomplete
  EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS),
            DoCheckFramebufferStatus(GL_FRAMEBUFFER));

  // Mark attachments as cleared initially
  GetFramebufferManager()->MarkAttachmentsAsCleared(
      framebuffer, group().renderbuffer_manager(), group().texture_manager());
  EXPECT_TRUE(framebuffer->IsCleared());

  // We expect the driver call to be skipped (Times(0)) because FBO is
  // incomplete
  EXPECT_CALL(*gl_, InvalidateFramebuffer(target, count, _)).Times(0);

  auto& cmd = *GetImmediateAs<cmds::InvalidateFramebufferImmediate>();
  cmd.Init(target, count, attachments);

  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(attachments)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // BUT we expect the attachment to be marked as uncleared!
  EXPECT_FALSE(framebuffer->IsCleared());
  const Framebuffer::Attachment* attachment =
      framebuffer->GetAttachment(GL_COLOR_ATTACHMENT0);
  ASSERT_TRUE(attachment != nullptr);
  EXPECT_FALSE(attachment->cleared());

  // Clean up texture 2
  DoDeleteTexture(kAnotherClientTextureId, kAnotherServiceTextureId);
}

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

  // EXPECT_EQ can't be used to compare function pointers.
  EXPECT_TRUE(
      gl::MockGLInterface::GetGLProcAddress("glDiscardFramebufferEXT") ==
      reinterpret_cast<gl::GLFunctionPointerType>(
          gl::g_current_gl_driver->fn.glDiscardFramebufferEXTFn));

  const GLenum target = GL_FRAMEBUFFER;
  const GLsizei count = 1;
  GLenum attachments[] = {GL_COLOR_EXT};

  EXPECT_CALL(*gl_, DiscardFramebufferEXT(target, count, _))
      .Times(1)
      .RetiresOnSaturation();
  auto& cmd = *GetImmediateAs<cmds::DiscardFramebufferEXTImmediate>();
  cmd.Init(target, count, attachments);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(attachments)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(static_cast<uint32_t>(GL_COLOR_BUFFER_BIT),
            GetAndClearBackbufferClearBitsForTest());

  attachments[0] = GL_DEPTH_EXT;
  EXPECT_CALL(*gl_, DiscardFramebufferEXT(target, count, _))
      .Times(1)
      .RetiresOnSaturation();
  cmd.Init(target, count, attachments);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(attachments)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(static_cast<uint32_t>(GL_DEPTH_BUFFER_BIT),
            GetAndClearBackbufferClearBitsForTest());

  attachments[0] = GL_STENCIL_EXT;
  EXPECT_CALL(*gl_, DiscardFramebufferEXT(target, count, _))
      .Times(1)
      .RetiresOnSaturation();
  cmd.Init(target, count, attachments);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(attachments)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(static_cast<uint32_t>(GL_STENCIL_BUFFER_BIT),
            GetAndClearBackbufferClearBitsForTest());

  const GLsizei count0 = 3;
  const GLenum attachments0[] = {GL_COLOR_EXT, GL_DEPTH_EXT, GL_STENCIL_EXT};
  EXPECT_CALL(*gl_, DiscardFramebufferEXT(target, count0, _))
      .Times(1)
      .RetiresOnSaturation();
  cmd.Init(target, count0, attachments0);
  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(attachments0)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_EQ(static_cast<uint32_t>(GL_COLOR_BUFFER_BIT |
                                  GL_DEPTH_BUFFER_BIT |
                                  GL_STENCIL_BUFFER_BIT),
            GetAndClearBackbufferClearBitsForTest());
}

TEST_P(GLES3DecoderTest,
       InvalidatedDefaultReadFramebufferClearedBeforeCopyTexImage2D) {
  // Invalidate the color buffer of the default framebuffer.
  const GLsizei count = 1;
  GLenum attachments[] = {GL_COLOR_EXT};
  EXPECT_CALL(*gl_, InvalidateFramebuffer(GL_FRAMEBUFFER, count, _))
      .Times(1)
      .RetiresOnSaturation();
  auto& invalidate_cmd =
      *GetImmediateAs<cmds::InvalidateFramebufferImmediate>();
  invalidate_cmd.Init(GL_FRAMEBUFFER, count, attachments);
  EXPECT_EQ(error::kNoError,
            ExecuteImmediateCmd(invalidate_cmd, sizeof(attachments)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Bind a client framebuffer as the draw framebuffer; the default
  // framebuffer remains bound as the read framebuffer.
  DoBindFramebuffer(GL_DRAW_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);

  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);

  // CopyTexImage2D reads from the default framebuffer. The decoder must
  // bind it to GL_DRAW_FRAMEBUFFER while clearing it after invalidation
  // and restore the client draw framebuffer afterwards.
  EXPECT_CALL(*gl_, BindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, ClearColor(0, 0, 0, 1.0f)).Times(1).RetiresOnSaturation();
  SetupExpectationsForColorMask(true, true, true, true);
  EXPECT_CALL(*gl_, ClearStencil(0)).Times(1).RetiresOnSaturation();
  SetupExpectationsForStencilMask(GLES2Decoder::kDefaultStencilMask,
                                  GLES2Decoder::kDefaultStencilMask);
  EXPECT_CALL(*gl_, ClearDepth(1.0f)).Times(1).RetiresOnSaturation();
  SetupExpectationsForDepthMask(true);
  SetupExpectationsForEnableDisable(GL_SCISSOR_TEST, false);
  EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation();
  SetupExpectationsForRestoreClearState(0.0f, 0.0f, 0.0f, 0.0f, 0, 1.0f, false,
                                        0, 0, kBackBufferWidth,
                                        kBackBufferHeight);
  EXPECT_CALL(*gl_,
              BindFramebufferEXT(GL_DRAW_FRAMEBUFFER, kServiceFramebufferId))
      .Times(1)
      .RetiresOnSaturation();

  EXPECT_CALL(*gl_, CopyTexImage2D(GL_TEXTURE_2D, 0, _, 0, 0, 1, 1, 0))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  cmds::CopyTexImage2D copy_cmd;
  copy_cmd.Init(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 1, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(copy_cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderTest, DiscardFramebufferEXTUnsupported) {
  const GLenum target = GL_FRAMEBUFFER;
  const GLsizei count = 1;
  const GLenum attachments[] = {GL_COLOR_EXT};
  auto& cmd = *GetImmediateAs<cmds::DiscardFramebufferEXTImmediate>();
  cmd.Init(target, count, attachments);

  // Should not result into a call into GL.
  EXPECT_EQ(error::kUnknownCommand,
            ExecuteImmediateCmd(cmd, sizeof(attachments)));
}

TEST_P(GLES3DecoderTest, DiscardFramebufferEXTInvalidTarget) {
  const GLenum target = GL_RED;  // Invalid
  const GLsizei count = 1;
  const GLenum attachments[] = {GL_COLOR_ATTACHMENT0};

  SetupTexture();
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         client_texture_id_,
                         kServiceTextureId,
                         0,
                         GL_NO_ERROR);
  FramebufferManager* framebuffer_manager = GetFramebufferManager();
  Framebuffer* framebuffer =
      framebuffer_manager->GetFramebuffer(client_framebuffer_id_);
  EXPECT_TRUE(framebuffer->IsCleared());

  EXPECT_CALL(*gl_, InvalidateFramebuffer(target, count, _)).Times(0);
  auto& cmd = *GetImmediateAs<cmds::DiscardFramebufferEXTImmediate>();
  cmd.Init(target, count, attachments);

  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(attachments)));
  EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
  EXPECT_TRUE(framebuffer->IsCleared());
}

TEST_P(GLES3DecoderTest, DiscardFramebufferEXTUseCorrectTarget) {
  const GLenum target = GL_READ_FRAMEBUFFER;
  const GLsizei count = 1;
  const GLenum attachments[] = {GL_COLOR_ATTACHMENT0};

  SetupTexture();
  DoBindFramebuffer(
      GL_READ_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_READ_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         client_texture_id_,
                         kServiceTextureId,
                         0,
                         GL_NO_ERROR);

  GLuint draw_fbo = client_framebuffer_id_ + 1;
  GLuint draw_fbo_service = kServiceFramebufferId + 1;
  EXPECT_CALL(*gl_, GenFramebuffersEXT(_, _))
      .WillOnce(SetArgPointee<1>(kServiceFramebufferId + 1))
      .RetiresOnSaturation();
  GenHelper<cmds::GenFramebuffersImmediate>(draw_fbo);
  DoBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_fbo, draw_fbo_service);

  GLuint other_texture_id_client = client_texture_id_ + 1;
  GLuint other_texture_id_service = kServiceTextureId + 1;
  EXPECT_CALL(*gl_, GenTextures(_, _))
      .WillOnce(SetArgPointee<1>(other_texture_id_service))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(other_texture_id_client);
  DoBindTexture(GL_TEXTURE_2D, other_texture_id_client,
                other_texture_id_service);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
               shared_memory_id_, kSharedMemoryOffset);
  DoFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D, other_texture_id_client,
                         other_texture_id_service, 0, GL_NO_ERROR);

  FramebufferManager* framebuffer_manager = GetFramebufferManager();
  Framebuffer* framebuffer =
      framebuffer_manager->GetFramebuffer(client_framebuffer_id_);
  EXPECT_TRUE(framebuffer->IsCleared());
  Framebuffer* other_framebuffer =
      framebuffer_manager->GetFramebuffer(draw_fbo);
  EXPECT_TRUE(other_framebuffer->IsCleared());

  EXPECT_CALL(*gl_, InvalidateFramebuffer(target, count, _))
      .Times(1)
      .RetiresOnSaturation();
  auto& cmd = *GetImmediateAs<cmds::DiscardFramebufferEXTImmediate>();
  cmd.Init(target, count, attachments);

  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(attachments)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_FALSE(framebuffer->IsCleared());
  EXPECT_TRUE(other_framebuffer->IsCleared());
}

TEST_P(GLES2DecoderManualInitTest,
       DiscardedAttachmentsEXTMarksFramebufferIncomplete) {
  InitState init;
  init.extensions = "GL_EXT_discard_framebuffer";
  init.gl_version = "OpenGL ES 2.0";
  init.has_alpha = true;
  init.request_alpha = true;
  InitDecoder(init);

  const GLuint kFBOClientTextureId = 4100;
  const GLuint kFBOServiceTextureId = 4101;

  // Register a texture id.
  EXPECT_CALL(*gl_, GenTextures(_, _))
      .WillOnce(SetArgPointee<1>(kFBOServiceTextureId))
      .RetiresOnSaturation();
  GenHelper<cmds::GenTexturesImmediate>(kFBOClientTextureId);

  // Setup "render to" texture.
  DoBindTexture(GL_TEXTURE_2D, kFBOClientTextureId, kFBOServiceTextureId);
  DoTexImage2D(
      GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 0);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         kFBOClientTextureId,
                         kFBOServiceTextureId,
                         0,
                         GL_NO_ERROR);
  DoEnableDisable(GL_SCISSOR_TEST, false);
  DoScissor(0, 0, 1, 1);

  // Setup "render from" texture.
  SetupTexture();

  SetupExpectationsForFramebufferClearing(GL_FRAMEBUFFER,       // target
                                          GL_COLOR_BUFFER_BIT,  // clear bits
                                          0, 0, 0,
                                          0,      // color
                                          0,      // stencil
                                          1.0f,   // depth
                                          false,  // scissor test
                                          0, 0, 1, 1);
  SetupExpectationsForApplyingDirtyState(false,    // Framebuffer is RGB
                                         false,    // Framebuffer has depth
                                         false,    // Framebuffer has stencil
                                         0x1111,   // color bits
                                         false,    // depth mask
                                         false,    // depth enabled
                                         0,        // front stencil mask
                                         0,        // back stencil mask
                                         false);   // stencil enabled

  EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation();

  cmds::Clear clear_cmd;
  clear_cmd.Init(GL_COLOR_BUFFER_BIT);
  EXPECT_EQ(error::kNoError, ExecuteCmd(clear_cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  // Check that framebuffer is cleared and complete.
  FramebufferManager* framebuffer_manager = GetFramebufferManager();
  Framebuffer* framebuffer =
      framebuffer_manager->GetFramebuffer(client_framebuffer_id_);
  EXPECT_TRUE(framebuffer->IsCleared());
  EXPECT_TRUE(framebuffer_manager->IsComplete(framebuffer));

  // Check that Discard GL_COLOR_ATTACHMENT0, sets the attachment as uncleared
  // and the framebuffer as incomplete.
  EXPECT_TRUE(
      gl::MockGLInterface::GetGLProcAddress("glDiscardFramebufferEXT") ==
      reinterpret_cast<gl::GLFunctionPointerType>(
          gl::g_current_gl_driver->fn.glDiscardFramebufferEXTFn));

  const GLenum target = GL_FRAMEBUFFER;
  const GLsizei count = 1;
  const GLenum attachments[] = {GL_COLOR_ATTACHMENT0};

  auto& discard_cmd = *GetImmediateAs<cmds::DiscardFramebufferEXTImmediate>();
  discard_cmd.Init(target, count, attachments);

  EXPECT_CALL(*gl_, DiscardFramebufferEXT(target, count, _))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_EQ(error::kNoError,
            ExecuteImmediateCmd(discard_cmd, sizeof(attachments)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  EXPECT_FALSE(framebuffer->IsCleared());
  EXPECT_FALSE(framebuffer_manager->IsComplete(framebuffer));
}

TEST_P(GLES3DecoderTest, ImplementationReadColorFormatAndType) {
  ClearSharedMemory();
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
               shared_memory_id_, kSharedMemoryOffset);
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoFramebufferTexture2D(GL_FRAMEBUFFER,
                         GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_2D,
                         client_texture_id_,
                         kServiceTextureId,
                         0,
                         GL_NO_ERROR);

  auto* result =
      static_cast<cmds::GetIntegerv::Result*>(shared_memory_address_);
  cmds::GetIntegerv cmd;

  result->size = 0;
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, _))
      .WillOnce(SetArgPointee<1>(GL_RGBA))
      .RetiresOnSaturation();
  cmd.Init(GL_IMPLEMENTATION_COLOR_READ_FORMAT,
           shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(1, result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());

  result->size = 0;
  EXPECT_CALL(*gl_, GetError())
      .WillOnce(Return(GL_NO_ERROR))
      .WillOnce(Return(GL_NO_ERROR))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl_, GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, _))
      .WillOnce(SetArgPointee<1>(GL_UNSIGNED_BYTE))
      .RetiresOnSaturation();
  cmd.Init(GL_IMPLEMENTATION_COLOR_READ_TYPE,
           shared_memory_id_,
           shared_memory_offset_);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(1, result->GetNumResults());
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTextureLayerNoBoundFramebuffer) {
  DoBindTexture(GL_TEXTURE_3D, client_texture_id_, kServiceTextureId);
  EXPECT_CALL(*gl_, FramebufferTextureLayer(_, _, _, _, _)).Times(0);
  cmds::FramebufferTextureLayer cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, client_texture_id_, 4, 5);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTextureLayerInvalidTextureTarget) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
  EXPECT_CALL(*gl_, FramebufferTextureLayer(_, _, _, _, _)).Times(0);
  cmds::FramebufferTextureLayer cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, client_texture_id_, 4, 5);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTextureLayerValidArgs) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindTexture(GL_TEXTURE_3D, client_texture_id_, kServiceTextureId);
  EXPECT_CALL(*gl_, FramebufferTextureLayer(GL_FRAMEBUFFER,
                                            GL_COLOR_ATTACHMENT0,
                                            kServiceTextureId, 4, 5))
      .Times(1)
      .RetiresOnSaturation();
  cmds::FramebufferTextureLayer cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, client_texture_id_, 4, 5);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, FramebufferTextureLayerDepthStencil) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindTexture(GL_TEXTURE_2D_ARRAY, client_texture_id_, kServiceTextureId);
  EXPECT_CALL(*gl_, FramebufferTextureLayer(GL_FRAMEBUFFER,
                                            GL_DEPTH_STENCIL_ATTACHMENT,
                                            kServiceTextureId, 4, 5))
      .Times(1)
      .RetiresOnSaturation();
  cmds::FramebufferTextureLayer cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, client_texture_id_, 4,
           5);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  Framebuffer* framebuffer = GetFramebuffer(client_framebuffer_id_);
  ASSERT_TRUE(framebuffer);
  ASSERT_FALSE(framebuffer->GetAttachment(GL_DEPTH_STENCIL_ATTACHMENT));
  ASSERT_TRUE(framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT));
  ASSERT_TRUE(framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT));
}

TEST_P(GLES3DecoderTest, InvalidateFramebufferDepthStencilAttachment) {
  DoBindFramebuffer(
      GL_FRAMEBUFFER, client_framebuffer_id_, kServiceFramebufferId);
  DoBindRenderbuffer(
      GL_RENDERBUFFER, client_renderbuffer_id_, kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1,
                        GL_NO_ERROR);
  DoFramebufferRenderbuffer(
      GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
      client_renderbuffer_id_, kServiceRenderbufferId, GL_NO_ERROR);

  Framebuffer* framebuffer = GetFramebuffer(client_framebuffer_id_);
  ASSERT_TRUE(framebuffer);
  ASSERT_FALSE(framebuffer->GetAttachment(GL_DEPTH_STENCIL_ATTACHMENT));
  ASSERT_TRUE(framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT));
  ASSERT_TRUE(framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT));
  framebuffer->MarkAttachmentAsCleared(group().renderbuffer_manager(), nullptr,
                                       GL_DEPTH_ATTACHMENT, true);
  framebuffer->MarkAttachmentAsCleared(group().renderbuffer_manager(), nullptr,
                                       GL_STENCIL_ATTACHMENT, true);
  EXPECT_TRUE(framebuffer->IsCleared());

  const GLenum target = GL_FRAMEBUFFER;
  const GLsizei count = 1;
  GLenum attachments[] = {GL_DEPTH_ATTACHMENT};
  EXPECT_CALL(*gl_, InvalidateFramebuffer(target, 0, _))
      .Times(1)
      .RetiresOnSaturation();
  auto& cmd = *GetImmediateAs<cmds::InvalidateFramebufferImmediate>();
  cmd.Init(target, count, attachments);

  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(attachments)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  // Invalidating part of DEPTH_STENCIL attachment doesn't change framebuffer
  // clearance status.
  EXPECT_TRUE(framebuffer->IsCleared());
  EXPECT_FALSE(framebuffer->HasUnclearedAttachment(GL_DEPTH_ATTACHMENT));
  EXPECT_FALSE(framebuffer->HasUnclearedAttachment(GL_STENCIL_ATTACHMENT));

  attachments[0] = GL_DEPTH_STENCIL_ATTACHMENT;
  EXPECT_CALL(*gl_, InvalidateFramebuffer(target, 2, _))
      .Times(1)
      .RetiresOnSaturation();
  cmd.Init(target, count, attachments);

  EXPECT_EQ(error::kNoError, ExecuteImmediateCmd(cmd, sizeof(attachments)));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
  // Invalidating DEPTH_STENCIL attachment should make framebuffer uncleared.
  EXPECT_FALSE(framebuffer->IsCleared());
  EXPECT_TRUE(framebuffer->HasUnclearedAttachment(GL_DEPTH_ATTACHMENT));
  EXPECT_TRUE(framebuffer->HasUnclearedAttachment(GL_STENCIL_ATTACHMENT));
}

TEST_P(GLES3DecoderTest, BlitFramebufferFeedbackLoopDefaultFramebuffer) {
  // Run BlitFramebufferCHROMIUM targetting the default framebuffer for both
  // read and draw, should result in a feedback loop.
  cmds::BlitFramebufferCHROMIUM cmd;
  cmd.Init(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_LINEAR);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  cmd.Init(0, 0, 1, 1, 0, 0, 1, 1, GL_DEPTH_BUFFER_BIT, GL_LINEAR);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());

  cmd.Init(0, 0, 1, 1, 0, 0, 1, 1, GL_STENCIL_BUFFER_BIT, GL_LINEAR);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
}

TEST_P(GLES3DecoderTest, BlitFramebufferDisabledReadBuffer) {
  // Run BlitFramebufferCHROMIUM from a disabled read buffer. Even though the
  // read and draw framebuffer use the same attachment, since the read buffer is
  // disabled, no feedback loop happens.
  DoBindFramebuffer(GL_DRAW_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);
  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR);
  DoFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                            GL_RENDERBUFFER, client_renderbuffer_id_,
                            kServiceRenderbufferId, GL_NO_ERROR);

  EXPECT_CALL(*gl_, GenFramebuffersEXT(1, _))
      .WillOnce(SetArgPointee<1>(kNewServiceId))
      .RetiresOnSaturation();
  GLuint read_fbo = client_framebuffer_id_ + 1;
  GenHelper<cmds::GenFramebuffersImmediate>(read_fbo);
  DoBindFramebuffer(GL_READ_FRAMEBUFFER, read_fbo, kNewServiceId);
  DoFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                            GL_RENDERBUFFER, client_renderbuffer_id_,
                            kServiceRenderbufferId, GL_NO_ERROR);
  {
    EXPECT_CALL(*gl_, ReadBuffer(GL_NONE)).Times(1).RetiresOnSaturation();
    cmds::ReadBuffer cmd;
    cmd.Init(GL_NONE);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
  }

  {
    EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER))
        .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
        .RetiresOnSaturation();
    SetupExpectationsForFramebufferClearing(GL_DRAW_FRAMEBUFFER,  // target
                                            GL_COLOR_BUFFER_BIT,  // clear bits
                                            0, 0, 0, 0,           // color
                                            0,                    // stencil
                                            1.0f,                 // depth
                                            false,  // scissor test
                                            0, 0, 128, 64);
    cmds::BlitFramebufferCHROMIUM cmd;
    cmd.Init(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_LINEAR);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    // Generate INVALID_OPERATION because of missing read buffer image.
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
  }
}

TEST_P(GLES3DecoderTest, BlitFramebufferMissingDepthOrStencil) {
  // Run BlitFramebufferCHROMIUM with depth or stencil bits, from/to a read/draw
  // framebuffer that doesn't have depth/stencil. It should generate
  // INVALID_OPERATION.
  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1,
                        GL_NO_ERROR);

  GLuint color_renderbuffer = client_renderbuffer_id_ + 1;
  GLuint color_renderbuffer_service = kServiceRenderbufferId + 1;
  EXPECT_CALL(*gl_, GenRenderbuffersEXT(1, _))
      .WillOnce(SetArgPointee<1>(color_renderbuffer_service))
      .RetiresOnSaturation();
  GenHelper<cmds::GenRenderbuffersImmediate>(color_renderbuffer);
  DoBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer,
                     color_renderbuffer_service);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR);
  DoBindFramebuffer(GL_DRAW_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);
  DoFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                            GL_RENDERBUFFER, client_renderbuffer_id_,
                            kServiceRenderbufferId, GL_NO_ERROR);
  DoFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                            GL_RENDERBUFFER, client_renderbuffer_id_,
                            kServiceRenderbufferId, GL_NO_ERROR);
  DoFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                            GL_RENDERBUFFER, color_renderbuffer,
                            color_renderbuffer_service, GL_NO_ERROR);

  EXPECT_CALL(*gl_, GenFramebuffersEXT(1, _))
      .WillOnce(SetArgPointee<1>(kNewServiceId))
      .RetiresOnSaturation();
  GLuint color_fbo = client_framebuffer_id_ + 1;
  GenHelper<cmds::GenFramebuffersImmediate>(color_fbo);
  DoBindFramebuffer(GL_READ_FRAMEBUFFER, color_fbo, kNewServiceId);
  DoFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                            GL_RENDERBUFFER, color_renderbuffer,
                            color_renderbuffer_service, GL_NO_ERROR);

  {
    SetupExpectationsForFramebufferClearing(
        GL_DRAW_FRAMEBUFFER,  // target
        GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
            GL_STENCIL_BUFFER_BIT,  // clear bits
        0,
        0, 0, 0,  // color
        0,        // stencil
        1.0f,     // depth
        false,    // scissor test
        0, 0, 128, 64);
    EXPECT_CALL(*gl_, CheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER))
        .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
        .RetiresOnSaturation();
    cmds::BlitFramebufferCHROMIUM cmd;
    cmd.Init(0, 0, 1, 1, 0, 0, 1, 1, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
    cmd.Init(0, 0, 1, 1, 0, 0, 1, 1, GL_STENCIL_BUFFER_BIT, GL_NEAREST);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
  }

  // Switch FBOs and try the same.
  DoBindFramebuffer(GL_READ_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);
  DoBindFramebuffer(GL_DRAW_FRAMEBUFFER, color_fbo, kNewServiceId);
  {
    EXPECT_CALL(*gl_, BlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, _, _)).Times(0);
    cmds::BlitFramebufferCHROMIUM cmd;
    cmd.Init(0, 0, 1, 1, 0, 0, 1, 1, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
    cmd.Init(0, 0, 1, 1, 0, 0, 1, 1, GL_STENCIL_BUFFER_BIT, GL_NEAREST);
    EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
    EXPECT_EQ(GL_NO_ERROR, GetGLError());
  }
}

TEST_P(GLES3DecoderTest, ClearDepthStencilRenderbufferAttachedAtDepthOnly) {
  // A DEPTH24_STENCIL8 renderbuffer attached at GL_DEPTH_ATTACHMENT only must
  // have both its depth and stencil components cleared before being marked as
  // cleared.
  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1,
                        GL_NO_ERROR);

  GLuint color_renderbuffer = client_renderbuffer_id_ + 1;
  GLuint color_renderbuffer_service = kServiceRenderbufferId + 1;
  EXPECT_CALL(*gl_, GenRenderbuffersEXT(1, _))
      .WillOnce(SetArgPointee<1>(color_renderbuffer_service))
      .RetiresOnSaturation();
  GenHelper<cmds::GenRenderbuffersImmediate>(color_renderbuffer);
  DoBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer,
                     color_renderbuffer_service);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR);

  DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);
  DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                            GL_RENDERBUFFER, color_renderbuffer,
                            color_renderbuffer_service, GL_NO_ERROR);
  DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                            GL_RENDERBUFFER, client_renderbuffer_id_,
                            kServiceRenderbufferId, GL_NO_ERROR);

  // The packed renderbuffer is bound at the stencil point for the duration of
  // the implicit clear so that glClear writes both components.
  EXPECT_CALL(*gl_, FramebufferRenderbufferEXT(
                        GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                        GL_RENDERBUFFER, kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(
      *gl_, FramebufferRenderbufferEXT(
                GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0))
      .Times(1)
      .RetiresOnSaturation();
  SetupExpectationsForFramebufferClearing(
      GL_DRAW_FRAMEBUFFER,  // target
      GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
          GL_STENCIL_BUFFER_BIT,  // clear bits
      0, 0, 0, 0,                 // color
      0,                          // stencil
      1.0f,                       // depth
      false,                      // scissor test
      0, 0, 128, 64);
  SetupExpectationsForApplyingDirtyState(false,   // Framebuffer is RGB
                                         true,    // Framebuffer has depth
                                         false,   // Framebuffer has stencil
                                         0x1111,  // color bits
                                         true,    // depth mask
                                         false,   // depth enabled
                                         0,       // front stencil mask
                                         0,       // back stencil mask
                                         false);  // stencil enabled
  EXPECT_CALL(*gl_, DrawBuffersARB(_, _)).Times(1).RetiresOnSaturation();
  EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation();

  cmds::Clear cmd;
  cmd.Init(GL_COLOR_BUFFER_BIT);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES3DecoderTest, ClearDepthStencilRenderbufferAttachedAtStencilOnly) {
  // A DEPTH24_STENCIL8 renderbuffer attached at GL_STENCIL_ATTACHMENT only
  // must have both its depth and stencil components cleared before being
  // marked as cleared.
  DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
                     kServiceRenderbufferId);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1,
                        GL_NO_ERROR);

  GLuint color_renderbuffer = client_renderbuffer_id_ + 1;
  GLuint color_renderbuffer_service = kServiceRenderbufferId + 1;
  EXPECT_CALL(*gl_, GenRenderbuffersEXT(1, _))
      .WillOnce(SetArgPointee<1>(color_renderbuffer_service))
      .RetiresOnSaturation();
  GenHelper<cmds::GenRenderbuffersImmediate>(color_renderbuffer);
  DoBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer,
                     color_renderbuffer_service);
  DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR);

  DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);
  DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                            GL_RENDERBUFFER, color_renderbuffer,
                            color_renderbuffer_service, GL_NO_ERROR);
  DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                            GL_RENDERBUFFER, client_renderbuffer_id_,
                            kServiceRenderbufferId, GL_NO_ERROR);

  // The packed renderbuffer is bound at the depth point for the duration of
  // the implicit clear so that glClear writes both components.
  EXPECT_CALL(
      *gl_, FramebufferRenderbufferEXT(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                                       GL_RENDERBUFFER, kServiceRenderbufferId))
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(
      *gl_, FramebufferRenderbufferEXT(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                                       GL_RENDERBUFFER, 0))
      .Times(1)
      .RetiresOnSaturation();
  SetupExpectationsForFramebufferClearing(
      GL_DRAW_FRAMEBUFFER,  // target
      GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
          GL_STENCIL_BUFFER_BIT,  // clear bits
      0, 0, 0, 0,                 // color
      0,                          // stencil
      1.0f,                       // depth
      false,                      // scissor test
      0, 0, 128, 64);
  SetupExpectationsForApplyingDirtyState(false,   // Framebuffer is RGB
                                         false,   // Framebuffer has depth
                                         true,    // Framebuffer has stencil
                                         0x1111,  // color bits
                                         false,   // depth mask
                                         false,   // depth enabled
                                         0xFFFFFFFFU,  // front stencil mask
                                         0xFFFFFFFFU,  // back stencil mask
                                         false);       // stencil enabled
  EXPECT_CALL(*gl_, DrawBuffersARB(_, _)).Times(1).RetiresOnSaturation();
  EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation();

  cmds::Clear cmd;
  cmd.Init(GL_COLOR_BUFFER_BIT);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, MESAFramebufferFlipYExtensionEnabled) {
  InitState init;
  init.gl_version = "OpenGL ES 3.1";
  init.context_type = CONTEXT_TYPE_WEBGL1;
  init.extensions = "GL_MESA_framebuffer_flip_y";
  InitDecoder(init);

  EXPECT_TRUE(feature_info()->validators()->framebuffer_parameter.IsValid(
      GL_FRAMEBUFFER_FLIP_Y_MESA));

  EXPECT_CALL(*gl_, FramebufferParameteriMESA(_, _, _))
      .Times(1)
      .RetiresOnSaturation();

  DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);
  cmds::FramebufferParameteri cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
  EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

TEST_P(GLES2DecoderManualInitTest, MESAFramebufferFlipYExtensionDisabled) {
  InitState init;
  init.gl_version = "OpenGL ES 3.1";
  init.context_type = CONTEXT_TYPE_WEBGL1;
  InitDecoder(init);

  EXPECT_FALSE(feature_info()->validators()->framebuffer_parameter.IsValid(
      GL_FRAMEBUFFER_FLIP_Y_MESA));

  EXPECT_CALL(*gl_, FramebufferParameteriMESA(_, _, _))
      .Times(0)
      .RetiresOnSaturation();

  DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
                    kServiceFramebufferId);
  cmds::FramebufferParameteri cmd;
  cmd.Init(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
  EXPECT_EQ(error::kUnknownCommand, ExecuteCmd(cmd));
  EXPECT_EQ(GL_NO_ERROR, GetGLError());
}

// TODO(gman): PixelStorei

}  // namespace gles2
}  // namespace gpu
