// 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 <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>
#include <GLES3/gl3.h>
#include <stdint.h>

#include "base/containers/heap_array.h"
#include "build/build_config.h"
#include "gpu/command_buffer/service/feature_info.h"
#include "gpu/command_buffer/tests/gl_manager.h"
#include "gpu/command_buffer/tests/gl_test_service_helper.h"
#include "gpu/command_buffer/tests/gl_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gl/buildflags.h"

namespace gpu {

class GLTest : public testing::Test {
 protected:
  void SetUp() override { gl_.Initialize(GLManager::Options()); }

  void TearDown() override { gl_.Destroy(); }

  GLManager gl_;
};

// Test that GL is at least minimally working.
TEST_F(GLTest, Basic) {
  glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);
  uint8_t expected[] = {
      0, 255, 0, 255,
  };
  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, expected, nullptr));
  GLTestHelper::CheckGLError("no errors", __LINE__);
}

TEST_F(GLTest, BasicFBO) {
  GLuint tex = 0;
  glGenTextures(1, &tex);
  GLuint fbo = 0;
  glGenFramebuffers(1, &fbo);
  glBindTexture(GL_TEXTURE_2D, tex);
  auto pixels = base::HeapArray<uint8_t>::WithSize(16 * 16 * 4);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
               pixels.data());
  glGenerateMipmap(GL_TEXTURE_2D);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glBindFramebuffer(GL_FRAMEBUFFER, fbo);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                         tex, 0);
  EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
            glCheckFramebufferStatus(GL_FRAMEBUFFER));
  glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);
  uint8_t expected[] = {
      0, 255, 0, 255,
  };
  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 16, 16, 0, expected, nullptr));
  glDeleteFramebuffers(1, &fbo);
  glDeleteTextures(1, &tex);
  GLTestHelper::CheckGLError("no errors", __LINE__);
}

TEST_F(GLTest, SimpleShader) {
  static const char* v_shader_str =
      "attribute vec4 a_Position;\n"
      "void main()\n"
      "{\n"
      "   gl_Position = a_Position;\n"
      "}\n";
  static const char* f_shader_str =
      "precision mediump float;\n"
      "void main()\n"
      "{\n"
      "  gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
      "}\n";

  GLuint program = GLTestHelper::LoadProgram(v_shader_str, f_shader_str);
  glUseProgram(program);
  GLuint position_loc = glGetAttribLocation(program, "a_Position");

  GLTestHelper::SetupUnitQuad(position_loc);

  uint8_t expected_clear[] = {
      127, 0, 255, 0,
  };
  glClearColor(0.5f, 0.0f, 1.0f, 0.0f);
  glClear(GL_COLOR_BUFFER_BIT);
  EXPECT_TRUE(
      GLTestHelper::CheckPixels(0, 0, 1, 1, 1, expected_clear, nullptr));
  uint8_t expected_draw[] = {
      0, 255, 0, 255,
  };
  glDrawArrays(GL_TRIANGLES, 0, 6);
  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, expected_draw, nullptr));
}

TEST_F(GLTest, FeatureFlagsMatchCapabilities) {
  scoped_refptr<gles2::FeatureInfo> features =
      new gles2::FeatureInfo(gl_.workarounds(), GpuFeatureInfo());
  features->InitializeForTesting();
  const auto& caps = gl_.GetCapabilities();
  const auto& gl_caps = gl_.GetGLCapabilities();
  const auto& flags = features->feature_flags();
  EXPECT_EQ(caps.egl_image_external, flags.oes_egl_image_external);
  EXPECT_EQ(caps.texture_format_bgra8888, flags.ext_texture_format_bgra8888);
  EXPECT_EQ(gl_caps.sync_query, flags.chromium_sync_query);
  EXPECT_EQ(caps.sync_query, flags.chromium_sync_query);
  EXPECT_EQ(caps.texture_rg, flags.ext_texture_rg);
  EXPECT_EQ(caps.image_ar30, flags.chromium_image_ar30);
  EXPECT_EQ(caps.image_ab30, flags.chromium_image_ab30);
  EXPECT_EQ(caps.render_buffer_format_bgra8888,
            flags.ext_render_buffer_format_bgra8888);
  EXPECT_EQ(gl_caps.occlusion_query_boolean, flags.occlusion_query_boolean);
}

TEST_F(GLTest, GetString) {
  EXPECT_STREQ(
      "OpenGL ES 2.0 Chromium",
      reinterpret_cast<const char*>(glGetString(GL_VERSION)));
  EXPECT_STREQ(
      "OpenGL ES GLSL ES 1.0 Chromium",
      reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION)));
}

#if BUILDFLAG(ENABLE_VALIDATING_COMMAND_DECODER)
class GL3Test : public GLTest {
 protected:
  void SetUp() override {
    if (gl_.gpu_preferences().use_passthrough_cmd_decoder) {
      GTEST_SKIP() << "Test only applies to validating decoder";
    }
    GLManager::Options options;
    options.context_type = CONTEXT_TYPE_OPENGLES3;
    gl_.Initialize(options);
  }
};

// Tests that glCopyTexImage2D correctly updates the internal service-side
// texture level size when using the workaround path that temporarily clamps
// base/max levels to prevent FBO incompleteness. Specifically, it verifies
// that the workaround uses the correct base target (GL_TEXTURE_CUBE_MAP)
// instead of the specific face target (e.g., GL_TEXTURE_CUBE_MAP_POSITIVE_X)
// which would cause driver-side GL_INVALID_ENUM errors and result in a state
// desynchronization between the decoder and the GPU driver.
//
// TODO(crbug.com/513543143): Goldfish GLES emulator driver on 32-bit x86
// Android bots has a known driver bug where it incorrectly rejects
// glCopyTexImage2D on cubemaps with GL_INVALID_ENUM.
#if !(BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86))
TEST_F(GL3Test, CopyTexImage2DCubeMapStateDesync) {
  GLuint tex = 0;
  glGenTextures(1, &tex);
  glBindTexture(GL_TEXTURE_CUBE_MAP, tex);

  // We manually specify all mip levels instead of calling glGenerateMipmap
  // because various GPU drivers (e.g., the Android x64 emulator) have bugs
  // when generating cubemap texture mipmaps, which can cause FBO
  // incompleteness.
  auto pixels = base::HeapArray<uint8_t>::WithSize(32 * 32 * 4);
  for (int level = 0; level < 6; ++level) {
    int size = 32 >> level;
    for (int i = 0; i < 6; ++i) {
      glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, level, GL_RGBA8, size,
                   size, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
    }
  }

  GLuint fbo = 0;
  glGenFramebuffers(1, &fbo);
  glBindFramebuffer(GL_FRAMEBUFFER, fbo);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                         GL_TEXTURE_CUBE_MAP_POSITIVE_Y, tex, 3);
  EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
            glCheckFramebufferStatus(GL_FRAMEBUFFER));

  glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 5, GL_RGBA8, 0, 0, 2, 2, 0);

  EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());

  // Verify that the decoder's internal LevelInfo tracking has been correctly
  // updated to match the new 2x2 size. If the workaround had triggered a driver
  // error, the decoder would have skipped updating this state while the driver-
  // side allocation would still be updated, leading to a security
  // vulnerability.
  int tracked_width = 0;
  int tracked_height = 0;
  bool defined =
      InspectTextureLevelSize(&gl_, tex, GL_TEXTURE_CUBE_MAP_POSITIVE_X, 5,
                              &tracked_width, &tracked_height);
  EXPECT_TRUE(defined);
  EXPECT_EQ(2, tracked_width);
  EXPECT_EQ(2, tracked_height);

  glDeleteFramebuffers(1, &fbo);
  glDeleteTextures(1, &tex);
}
#endif  // !(BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86))

class GL3MultisampleCopyTexImageTest : public GL3Test {
 protected:
  void SetUp() override { GL3Test::SetUp(); }

  void TearDown() override {
    if (sample_fbo_) {
      glDeleteFramebuffers(1, &sample_fbo_);
    }
    if (sample_tex_) {
      glDeleteTextures(1, &sample_tex_);
    }
    if (sample_rb_) {
      glDeleteRenderbuffers(1, &sample_rb_);
    }
    if (dest_tex_) {
      glDeleteTextures(1, &dest_tex_);
    }
    GL3Test::TearDown();
  }

  void SetUpImplicitResolveTextureFBO() {
    glGenTextures(1, &sample_tex_);
    glBindTexture(GL_TEXTURE_2D, sample_tex_);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 16, 16, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glGenFramebuffers(1, &sample_fbo_);
    glBindFramebuffer(GL_FRAMEBUFFER, sample_fbo_);
    glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                         GL_TEXTURE_2D, sample_tex_, 0, 4);
    EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
              glCheckFramebufferStatus(GL_FRAMEBUFFER));
    GLTestHelper::CheckGLError("SetUpImplicitResolveTextureFBO", __LINE__);
  }

  void SetUpMultisampleRenderbufferFBO(GLsizei samples = 4) {
    glGenRenderbuffers(1, &sample_rb_);
    glBindRenderbuffer(GL_RENDERBUFFER, sample_rb_);
    glRenderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, samples, GL_RGBA8,
                                             16, 16);
    glGenFramebuffers(1, &sample_fbo_);
    glBindFramebuffer(GL_FRAMEBUFFER, sample_fbo_);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER, sample_rb_);
    EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
              glCheckFramebufferStatus(GL_FRAMEBUFFER));
    GLTestHelper::CheckGLError("SetUpMultisampleRenderbufferFBO", __LINE__);
  }

  void SetUpImplicitResolveRenderbufferFBO() {
    glGenRenderbuffers(1, &sample_rb_);
    glBindRenderbuffer(GL_RENDERBUFFER, sample_rb_);
    glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_RGBA8, 16, 16);
    glGenFramebuffers(1, &sample_fbo_);
    glBindFramebuffer(GL_FRAMEBUFFER, sample_fbo_);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER, sample_rb_);
    EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
              glCheckFramebufferStatus(GL_FRAMEBUFFER));
    GLTestHelper::CheckGLError("SetUpImplicitResolveRenderbufferFBO", __LINE__);
  }

  void SetUpDestTexture2D() {
    glGenTextures(1, &dest_tex_);
    glBindTexture(GL_TEXTURE_2D, dest_tex_);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 16, 16, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    GLTestHelper::CheckGLError("SetUpDestTexture2D", __LINE__);
  }

  void SetUpDestTexture3D() {
    glGenTextures(1, &dest_tex_);
    glBindTexture(GL_TEXTURE_3D, dest_tex_);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 16, 16, 16, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    GLTestHelper::CheckGLError("SetUpDestTexture3D", __LINE__);
  }

  void VerifyCopySucceeded(GLenum dest_target,
                           int expected_width,
                           int expected_height) {
    EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
    VerifyTextureSize(dest_target, expected_width, expected_height);
  }

  void VerifyCopyBlocked(GLenum dest_target) {
    EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
    VerifyTextureSize(dest_target, 16, 16);
  }

  void VerifyTextureSize(GLenum target,
                         int expected_width,
                         int expected_height) {
    int tracked_width = 0;
    int tracked_height = 0;
    bool defined = InspectTextureLevelSize(&gl_, dest_tex_, target, 0,
                                           &tracked_width, &tracked_height);
    EXPECT_TRUE(defined);
    EXPECT_EQ(expected_width, tracked_width);
    EXPECT_EQ(expected_height, tracked_height);
  }

  GLuint sample_fbo_ = 0;
  GLuint sample_tex_ = 0;
  GLuint sample_rb_ = 0;
  GLuint dest_tex_ = 0;
};

// Implicit Resolve Texture
TEST_F(GL3MultisampleCopyTexImageTest, CopyTexImage2DImplicitResolveTexture) {
  if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) {
    GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported";
  }
  SetUpImplicitResolveTextureFBO();
  SetUpDestTexture2D();
  glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, 2, 2, 0);
  VerifyCopySucceeded(GL_TEXTURE_2D, 2, 2);
}
TEST_F(GL3MultisampleCopyTexImageTest,
       CopyTexSubImage2DImplicitResolveTexture) {
  if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) {
    GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported";
  }
  SetUpImplicitResolveTextureFBO();
  SetUpDestTexture2D();
  glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2);
  VerifyCopySucceeded(GL_TEXTURE_2D, 16, 16);
}
TEST_F(GL3MultisampleCopyTexImageTest,
       CopyTexSubImage3DImplicitResolveTexture) {
  if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) {
    GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported";
  }
  SetUpImplicitResolveTextureFBO();
  SetUpDestTexture3D();
  glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 2, 2);
  VerifyCopySucceeded(GL_TEXTURE_3D, 16, 16);
}

// Multisample Renderbuffer
TEST_F(GL3MultisampleCopyTexImageTest, CopyTexImage2DMultisampleRenderbuffer) {
  if (!GLTestHelper::HasExtension("GL_CHROMIUM_framebuffer_multisample")) {
    GTEST_SKIP() << "GL_CHROMIUM_framebuffer_multisample not supported";
  }
  SetUpMultisampleRenderbufferFBO();
  SetUpDestTexture2D();
  glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, 2, 2, 0);
  VerifyCopyBlocked(GL_TEXTURE_2D);
}
TEST_F(GL3MultisampleCopyTexImageTest,
       CopyTexSubImage2DMultisampleRenderbuffer) {
  if (!GLTestHelper::HasExtension("GL_CHROMIUM_framebuffer_multisample")) {
    GTEST_SKIP() << "GL_CHROMIUM_framebuffer_multisample not supported";
  }
  SetUpMultisampleRenderbufferFBO();
  SetUpDestTexture2D();
  glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2);
  VerifyCopyBlocked(GL_TEXTURE_2D);
}
TEST_F(GL3MultisampleCopyTexImageTest,
       CopyTexSubImage3DMultisampleRenderbuffer) {
  if (!GLTestHelper::HasExtension("GL_CHROMIUM_framebuffer_multisample")) {
    GTEST_SKIP() << "GL_CHROMIUM_framebuffer_multisample not supported";
  }
  SetUpMultisampleRenderbufferFBO();
  SetUpDestTexture3D();
  glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 2, 2);
  VerifyCopyBlocked(GL_TEXTURE_3D);
}

// Multisample Renderbuffer with Sample Count 1
TEST_F(GL3MultisampleCopyTexImageTest,
       CopyTexImage2DMultisampleRenderbufferSampleCount1) {
  if (!GLTestHelper::HasExtension("GL_CHROMIUM_framebuffer_multisample")) {
    GTEST_SKIP() << "GL_CHROMIUM_framebuffer_multisample not supported";
  }
  SetUpMultisampleRenderbufferFBO(/*samples=*/1);
  SetUpDestTexture2D();
  glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, 2, 2, 0);
  VerifyCopyBlocked(GL_TEXTURE_2D);
}
TEST_F(GL3MultisampleCopyTexImageTest,
       CopyTexSubImage2DMultisampleRenderbufferSampleCount1) {
  if (!GLTestHelper::HasExtension("GL_CHROMIUM_framebuffer_multisample")) {
    GTEST_SKIP() << "GL_CHROMIUM_framebuffer_multisample not supported";
  }
  SetUpMultisampleRenderbufferFBO(/*samples=*/1);
  SetUpDestTexture2D();
  glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2);
  VerifyCopyBlocked(GL_TEXTURE_2D);
}
TEST_F(GL3MultisampleCopyTexImageTest,
       CopyTexSubImage3DMultisampleRenderbufferSampleCount1) {
  if (!GLTestHelper::HasExtension("GL_CHROMIUM_framebuffer_multisample")) {
    GTEST_SKIP() << "GL_CHROMIUM_framebuffer_multisample not supported";
  }
  SetUpMultisampleRenderbufferFBO(/*samples=*/1);
  SetUpDestTexture3D();
  glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 2, 2);
  VerifyCopyBlocked(GL_TEXTURE_3D);
}

// Implicit Resolve Renderbuffer
TEST_F(GL3MultisampleCopyTexImageTest,
       CopyTexImage2DImplicitResolveRenderbuffer) {
  if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) {
    GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported";
  }
  SetUpImplicitResolveRenderbufferFBO();
  SetUpDestTexture2D();
  glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, 2, 2, 0);
  VerifyCopyBlocked(GL_TEXTURE_2D);
}
TEST_F(GL3MultisampleCopyTexImageTest,
       CopyTexSubImage2DImplicitResolveRenderbuffer) {
  if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) {
    GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported";
  }
  SetUpImplicitResolveRenderbufferFBO();
  SetUpDestTexture2D();
  glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2);
  VerifyCopyBlocked(GL_TEXTURE_2D);
}
TEST_F(GL3MultisampleCopyTexImageTest,
       CopyTexSubImage3DImplicitResolveRenderbuffer) {
  if (!GLTestHelper::HasExtension("GL_EXT_multisample_render_to_texture")) {
    GTEST_SKIP() << "GL_EXT_multisample_render_to_texture not supported";
  }
  SetUpImplicitResolveRenderbufferFBO();
  SetUpDestTexture3D();
  glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 0, 0, 2, 2);
  VerifyCopyBlocked(GL_TEXTURE_3D);
}
#endif  // BUILDFLAG(ENABLE_VALIDATING_COMMAND_DECODER)

}  // namespace gpu
