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

#include <array>
#include <cmath>
#include <memory>
#include <string_view>
#include <vector>

#include "base/bit_cast.h"
#include "base/compiler_specific.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "gpu/command_buffer/tests/gl_manager.h"
#include "gpu/command_buffer/tests/gl_test_utils.h"
#include "gpu/config/gpu_test_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace gpu {

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

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

  void WaitForQueryCallback(int q, base::OnceClosure cb) {
    unsigned int done = 0;
    gl_.PerformIdleWork();
    glGetQueryObjectuivEXT(q, GL_QUERY_RESULT_AVAILABLE_EXT, &done);
    if (done) {
      std::move(cb).Run();
    } else {
      base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&GLReadbackTest::WaitForQueryCallback,
                         base::Unretained(this), q, std::move(cb)),
          base::Milliseconds(3));
    }
  }

  void WaitForQuery(int q) {
    base::RunLoop run_loop;
    WaitForQueryCallback(q, run_loop.QuitClosure());
    run_loop.Run();
  }

  GLManager gl_;
};

TEST_F(GLReadbackTest, ReadPixelsWithPBOAndQuery) {
  const GLint kBytesPerPixel = 4;
  const GLint kWidth = 2;
  const GLint kHeight = 2;

  GLuint b, q;
  glClearColor(0.0, 0.0, 1.0, 1.0);
  glClear(GL_COLOR_BUFFER_BIT);
  glGenBuffers(1, &b);
  glGenQueriesEXT(1, &q);
  glBindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, b);
  glBufferData(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
               kWidth * kHeight * kBytesPerPixel, nullptr, GL_STREAM_READ);
  glBeginQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM, q);
  glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, 0);
  glEndQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM);
  glFlush();
  WaitForQuery(q);

  // TODO(hubbe): Check that glMapBufferCHROMIUM does not block here.
  const unsigned char* data =
      static_cast<const unsigned char*>(glMapBufferCHROMIUM(
          GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, GL_READ_ONLY));
  EXPECT_TRUE(data);
  // SAFETY: The mapped read-only PBO is guaranteed to be large enough to
  // contain the mapped pixels, meaning it holds at least `kWidth * kHeight *
  // kBytesPerPixel` bytes.
  auto data_span = UNSAFE_BUFFERS(
      base::span(data, static_cast<size_t>(kWidth * kHeight * kBytesPerPixel)));
  EXPECT_EQ(data_span[0], 0);    // red
  EXPECT_EQ(data_span[1], 0);    // green
  EXPECT_EQ(data_span[2], 255);  // blue
  glUnmapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM);
  glBindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
  glDeleteBuffers(1, &b);
  glDeleteQueriesEXT(1, &q);
  GLTestHelper::CheckGLError("no errors", __LINE__);
}

static float HalfToFloat32(uint16_t value) {
  int32_t s = (value >> 15) & 0x00000001;
  int32_t e = (value >> 10) & 0x0000001f;
  int32_t m = value & 0x000003ff;

  if (e == 0) {
    if (m == 0) {
      uint32_t result = s << 31;
      return base::bit_cast<float>(result);
    } else {
      while (!(m & 0x00000400)) {
        m <<= 1;
        e -=  1;
      }

      e += 1;
      m &= ~0x00000400;
    }
  } else if (e == 31) {
    if (m == 0) {
      uint32_t result = (s << 31) | 0x7f800000;
      return base::bit_cast<float>(result);
    } else {
      uint32_t result = (s << 31) | 0x7f800000 | (m << 13);
      return base::bit_cast<float>(result);
    }
  }

  e = e + (127 - 15);
  m = m << 13;

  uint32_t result = (s << 31) | (e << 23) | m;
  return base::bit_cast<float>(result);
}

static GLuint CompileShader(GLenum type, const char *data) {
  const char *shaderStrings[1] = { data };

  GLuint shader = glCreateShader(type);
  glShaderSource(shader, 1, shaderStrings, nullptr);
  glCompileShader(shader);

  GLint compile_status = 0;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
  if (compile_status != GL_TRUE) {
    glDeleteShader(shader);
    shader = 0;
  }

  return shader;
}

// TODO(zmo): ReadPixels with float type isn't implemented in ANGLE ES2
// backend. crbug.com/607283.
// TODO(zmo): This test also fails on some android devices when the readback
// type is HALF_FLOAT_OES. Likely it's due to a driver bug. crbug.com/607936.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
#define MAYBE_ReadPixelsFloat DISABLED_ReadPixelsFloat
#else
#define MAYBE_ReadPixelsFloat ReadPixelsFloat
#endif
TEST_F(GLReadbackTest, MAYBE_ReadPixelsFloat) {
  const GLsizei kTextureSize = 4;
  const std::array<GLfloat, 4> kDrawColor = {-10.9f, 0.5f, 10.5f, 100.12f};
  const GLfloat kEpsilon = 0.01f;

  struct TestFormat {
    GLint format;
    GLint type;
    uint32_t comp_count;
  };
  std::vector<TestFormat> test_formats;
  test_formats.reserve(4);
  const char* extensions_cstr =
      reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
  std::string_view extensions(extensions_cstr ? extensions_cstr : "");
  if (extensions.find("GL_OES_texture_half_float") != std::string_view::npos) {
    test_formats.push_back({GL_RGB, GL_HALF_FLOAT_OES, 3});
    test_formats.push_back({GL_RGBA, GL_HALF_FLOAT_OES, 4});
  }
  if (extensions.find("GL_OES_texture_float") != std::string_view::npos) {
    test_formats.push_back({GL_RGB, GL_FLOAT, 3});
    test_formats.push_back({GL_RGBA, GL_FLOAT, 4});
  }

  const char *vs_source =
      "precision mediump float;\n"
      "attribute vec4 a_position;\n"
      "void main() {\n"
      "  gl_Position =  a_position;\n"
      "}\n";

  GLuint vertex_shader = CompileShader(GL_VERTEX_SHADER, vs_source);
  ASSERT_NE(vertex_shader, GLuint(0));

  const char *fs_source =
      "precision mediump float;\n"
      "uniform vec4 u_color;\n"
      "void main() {\n"
      "  gl_FragColor = u_color;\n"
      "}\n";

  GLuint fragment_shader = CompileShader(GL_FRAGMENT_SHADER, fs_source);
  ASSERT_NE(fragment_shader, GLuint(0));

  GLuint program = glCreateProgram();
  glAttachShader(program, vertex_shader);
  glDeleteShader(vertex_shader);
  glAttachShader(program, fragment_shader);
  glDeleteShader(fragment_shader);
  glLinkProgram(program);

  GLint link_status = 0;
  glGetProgramiv(program, GL_LINK_STATUS, &link_status);
  if (link_status != GL_TRUE) {
    glDeleteProgram(program);
    program = 0;
  }
  ASSERT_NE(program, GLuint(0));

  EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));

  float quad_vertices[] = {
      -1.0, -1.0,
      1.0, -1.0,
      1.0, 1.0,
      -1.0, 1.0
  };

  GLuint vertex_buffer;
  glGenBuffers(1, &vertex_buffer);
  glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
  glBufferData(GL_ARRAY_BUFFER, sizeof(quad_vertices), quad_vertices,
               GL_STATIC_DRAW);

  GLint position_location = glGetAttribLocation(program, "a_position");
  glVertexAttribPointer(position_location, 2, GL_FLOAT, GL_FALSE,
                        2 * sizeof(float), nullptr);
  glEnableVertexAttribArray(position_location);

  glUseProgram(program);
  glUniform4fv(glGetUniformLocation(program, "u_color"), 1, kDrawColor.data());

  EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));

  for (const auto& test_format : test_formats) {
    GLuint texture_id = 0;
    glGenTextures(1, &texture_id);
    glBindTexture(GL_TEXTURE_2D, texture_id);
    glTexImage2D(GL_TEXTURE_2D, 0, test_format.format, kTextureSize,
                 kTextureSize, 0, test_format.format, test_format.type,
                 nullptr);

    GLuint framebuffer = 0;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(
        GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0);

    EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));

    // Make sure this floating point framebuffer is supported
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
      // Check if this implementation supports reading floats back from this
      // framebuffer
      GLint read_format = 0;
      glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &read_format);
      GLint read_type = 0;
      glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &read_type);

      EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));

      if ((read_format == GL_RGB || read_format == GL_RGBA) &&
          read_type == test_format.type) {
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

        uint32_t read_comp_count = 0;
        switch (read_format) {
          case GL_RGB:
            read_comp_count = 3;
            break;
          case GL_RGBA:
            read_comp_count = 4;
            break;
        }

        switch (read_type) {
          case GL_HALF_FLOAT_OES: {
            auto buf = base::HeapArray<GLushort>::Uninit(
                kTextureSize * kTextureSize * read_comp_count);
            glReadPixels(0, 0, kTextureSize, kTextureSize, read_format,
                         read_type, buf.data());
            EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));
            base::span<const GLushort> buf_span = buf;
            for (uint32_t jj = 0; jj < kTextureSize * kTextureSize; ++jj) {
              auto pixel =
                  buf_span.subspan(jj * read_comp_count, read_comp_count);
              // Only compare components that exist in both the texture and the
              // read-back format.
              for (uint32_t kk = 0;
                   kk < std::min(test_format.comp_count, read_comp_count);
                   ++kk) {
                EXPECT_LE(std::abs(HalfToFloat32(pixel[kk]) - kDrawColor[kk]),
                          std::abs(kDrawColor[kk] * kEpsilon));
              }
            }
            break;
          }
          case GL_FLOAT: {
            auto buf = base::HeapArray<GLfloat>::Uninit(
                kTextureSize * kTextureSize * read_comp_count);
            glReadPixels(0, 0, kTextureSize, kTextureSize, read_format,
                         read_type, buf.data());
            EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));
            base::span<const GLfloat> buf_span = buf;
            for (uint32_t jj = 0; jj < kTextureSize * kTextureSize; ++jj) {
              auto pixel =
                  buf_span.subspan(jj * read_comp_count, read_comp_count);
              // Only compare components that exist in both the texture and the
              // read-back format.
              for (uint32_t kk = 0;
                   kk < std::min(test_format.comp_count, read_comp_count);
                   ++kk) {
                EXPECT_LE(std::abs(pixel[kk] - kDrawColor[kk]),
                          std::abs(kDrawColor[kk] * kEpsilon));
              }
            }
            break;
          }
        }
      }
    }

    glDeleteFramebuffers(1, &framebuffer);
    glDeleteTextures(1, &texture_id);
  }

  glDeleteBuffers(1, &vertex_buffer);
  glDeleteProgram(program);
}

TEST_F(GLReadbackTest, PackLargeRowLengthSeparatelyPackBuffer) {
  // Test fails on these configurations due to underlying driver or ANGLE
  // implementation bugs.
#if BUILDFLAG(IS_ANDROID)
  return;
#else
#if BUILDFLAG(IS_MAC)
  if (GPUTestBotConfig::CurrentConfigMatches("Intel") ||
      GPUTestBotConfig::CurrentConfigMatches("AMD")) {
    return;
  }
#endif

  GLManager::Options options;
  options.context_type = CONTEXT_TYPE_OPENGLES3;
  GpuDriverBugWorkarounds workarounds;
  workarounds.pack_large_row_length_separately_pack_buffer = true;
  gl_.Destroy();
  gl_.InitializeWithWorkarounds(options, workarounds);

  if (!gl_.IsInitialized()) {
    return;
  }

  const uint8_t kExpectedColor[4] = {65, 128, 192, 255};
  constexpr GLint kLargeRowLength = 0x7fffffc;
  constexpr GLsizeiptr kByteOffsetToVerify = 0x1ffffff0;
  constexpr GLsizeiptr kBufferSize = kByteOffsetToVerify + 256;

  GLuint pbo = 0;
  glGenBuffers(1, &pbo);
  glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
  glBufferData(GL_PIXEL_PACK_BUFFER, kBufferSize, nullptr, GL_STREAM_READ);
  if (glGetError() == GL_OUT_OF_MEMORY) {
    glDeleteBuffers(1, &pbo);
    return;
  }
  EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());

  glClearColor(kExpectedColor[0] / 255.0f, kExpectedColor[1] / 255.0f,
               kExpectedColor[2] / 255.0f, kExpectedColor[3] / 255.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  glPixelStorei(GL_PACK_ROW_LENGTH, kLargeRowLength);
  glPixelStorei(GL_PACK_ALIGNMENT, 4);
  glReadPixels(0, 0, 1, 2, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
  EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());

  uint8_t actualColorRow0[4] = {0};
  uint8_t actualColorRow1[4] = {0};

  glGetBufferSubDataCHROMIUM(GL_PIXEL_PACK_BUFFER, 0, sizeof(actualColorRow0),
                             actualColorRow0);
  glGetBufferSubDataCHROMIUM(GL_PIXEL_PACK_BUFFER, kByteOffsetToVerify,
                             sizeof(actualColorRow1), actualColorRow1);
  EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());

  glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
  glDeleteBuffers(1, &pbo);

  EXPECT_EQ(kExpectedColor[0], actualColorRow0[0]);
  EXPECT_EQ(kExpectedColor[1], actualColorRow0[1]);
  EXPECT_EQ(kExpectedColor[2], actualColorRow0[2]);
  EXPECT_EQ(kExpectedColor[3], actualColorRow0[3]);

  EXPECT_EQ(kExpectedColor[0], actualColorRow1[0]);
  EXPECT_EQ(kExpectedColor[1], actualColorRow1[1]);
  EXPECT_EQ(kExpectedColor[2], actualColorRow1[2]);
  EXPECT_EQ(kExpectedColor[3], actualColorRow1[3]);
#endif
}

}  // namespace gpu
