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

#include "components/viz/test/test_gles2_interface.h"

#include "base/compiler_specific.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "components/viz/test/test_context_support.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/common/gles2_cmd_utils.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace viz {

static unsigned NextContextId() {
  static uint16_t s_context_id = 1;
  // We need to ensure that the context_id fits in 16 bits since it is placed on
  // the top 16 bits of the 32 bit identifiers (program_id, framebuffer_id,
  // shader_id, etc.) generated by the context.
  if (s_context_id == std::numeric_limits<uint16_t>::max()) {
    LOG(ERROR) << "Exceeded max context id count; wrapping around";
    s_context_id = 1;
  }
  return s_context_id++;
}

TestGLES2Interface::TestGLES2Interface() : context_id_(NextContextId()) {
  // For stream textures.
  test_capabilities_.egl_image_external = true;
  set_max_texture_size(2048);
}

TestGLES2Interface::~TestGLES2Interface() = default;

void TestGLES2Interface::GenTextures(GLsizei n, GLuint* textures) {
  for (int i = 0; i < n; ++i) {
    UNSAFE_TODO(textures[i]) = NextTextureId();
    textures_.insert(UNSAFE_TODO(textures[i]));
  }
}

void TestGLES2Interface::GenBuffers(GLsizei n, GLuint* buffers) {
  for (int i = 0; i < n; ++i)
    UNSAFE_TODO(buffers[i]) = NextBufferId();
}

void TestGLES2Interface::GenFramebuffers(GLsizei n, GLuint* framebuffers) {
  for (int i = 0; i < n; ++i)
    UNSAFE_TODO(framebuffers[i]) = NextFramebufferId();
}

void TestGLES2Interface::GenRenderbuffers(GLsizei n, GLuint* renderbuffers) {
  for (int i = 0; i < n; ++i)
    UNSAFE_TODO(renderbuffers[i]) = NextRenderbufferId();
}

void TestGLES2Interface::GenQueriesEXT(GLsizei n, GLuint* queries) {
  for (GLsizei i = 0; i < n; ++i) {
    UNSAFE_TODO(queries[i]) = 1u;
  }
}

void TestGLES2Interface::DeleteTextures(GLsizei n, const GLuint* textures) {
  for (int i = 0; i < n; ++i) {
    RetireTextureId(UNSAFE_TODO(textures[i]));
    textures_.erase(UNSAFE_TODO(textures[i]));
  }
}

void TestGLES2Interface::DeleteBuffers(GLsizei n, const GLuint* buffers) {
  for (int i = 0; i < n; ++i)
    RetireBufferId(UNSAFE_TODO(buffers[i]));
}

void TestGLES2Interface::DeleteFramebuffers(GLsizei n,
                                            const GLuint* framebuffers) {
  for (int i = 0; i < n; ++i) {
    if (UNSAFE_TODO(framebuffers[i])) {
      RetireFramebufferId(UNSAFE_TODO(framebuffers[i]));
      if (UNSAFE_TODO(framebuffers[i]) == current_framebuffer_) {
        current_framebuffer_ = 0;
      }
    }
  }
}

void TestGLES2Interface::DeleteQueriesEXT(GLsizei n, const GLuint* queries) {
}

GLuint TestGLES2Interface::CreateShader(GLenum type) {
  unsigned shader = next_shader_id_++ | context_id_ << 16;
  shader_set_.insert(shader);
  return shader;
}

GLuint TestGLES2Interface::CreateProgram() {
  unsigned program = next_program_id_++ | context_id_ << 16;
  program_set_.insert(program);
  return program;
}

void TestGLES2Interface::BindTexture(GLenum target, GLuint texture) {
  if (!texture)
    return;
  DCHECK(textures_.contains(texture));
  used_textures_.insert(texture);
}

void TestGLES2Interface::GetIntegerv(GLenum pname, GLint* params) {
  if (pname == GL_MAX_TEXTURE_SIZE)
    *params = test_gl_capabilities_.max_texture_size;
  else if (pname == GL_ACTIVE_TEXTURE)
    *params = GL_TEXTURE0;
  else if (pname == GL_UNPACK_ALIGNMENT)
    *params = unpack_alignment_;
  else if (pname == GL_FRAMEBUFFER_BINDING)
    *params = current_framebuffer_;
  else if (pname == GL_MAX_SAMPLES)
    *params = test_gl_capabilities_.max_samples;
}

void TestGLES2Interface::GetShaderiv(GLuint shader,
                                     GLenum pname,
                                     GLint* params) {
  if (pname == GL_COMPILE_STATUS)
    *params = 1;
}

void TestGLES2Interface::GetProgramiv(GLuint program,
                                      GLenum pname,
                                      GLint* params) {
  if (pname == GL_LINK_STATUS)
    *params = 1;
}

void TestGLES2Interface::GetShaderPrecisionFormat(GLenum shadertype,
                                                  GLenum precisiontype,
                                                  GLint* range,
                                                  GLint* precision) {
  // Return the minimum precision requirements of the GLES2
  // specification.
  switch (precisiontype) {
    case GL_LOW_INT:
      range[0] = 8;
      UNSAFE_TODO(range[1]) = 8;
      *precision = 0;
      break;
    case GL_MEDIUM_INT:
      range[0] = 10;
      UNSAFE_TODO(range[1]) = 10;
      *precision = 0;
      break;
    case GL_HIGH_INT:
      range[0] = 16;
      UNSAFE_TODO(range[1]) = 16;
      *precision = 0;
      break;
    case GL_LOW_FLOAT:
      range[0] = 8;
      UNSAFE_TODO(range[1]) = 8;
      *precision = 8;
      break;
    case GL_MEDIUM_FLOAT:
      range[0] = 14;
      UNSAFE_TODO(range[1]) = 14;
      *precision = 10;
      break;
    case GL_HIGH_FLOAT:
      range[0] = 62;
      UNSAFE_TODO(range[1]) = 62;
      *precision = 16;
      break;
    default:
      NOTREACHED();
  }
}

void TestGLES2Interface::UseProgram(GLuint program) {
  if (!program)
    return;
  if (!program_set_.count(program))
    ADD_FAILURE() << "useProgram called on unknown program " << program;
}

GLenum TestGLES2Interface::CheckFramebufferStatus(GLenum target) {
  if (context_lost_)
    return GL_FRAMEBUFFER_UNDEFINED_OES;
  return GL_FRAMEBUFFER_COMPLETE;
}

void TestGLES2Interface::Flush() {
  test_support_->CallAllSyncPointCallbacks();
}

void TestGLES2Interface::Finish() {
  test_support_->CallAllSyncPointCallbacks();
}

void TestGLES2Interface::BindRenderbuffer(GLenum target, GLuint renderbuffer) {
  if (!renderbuffer)
    return;
  if (renderbuffer != 0 &&
      renderbuffer_set_.find(renderbuffer) == renderbuffer_set_.end()) {
    ADD_FAILURE() << "bindRenderbuffer called with unknown renderbuffer";
  } else if ((renderbuffer >> 16) != context_id_) {
    ADD_FAILURE()
        << "bindRenderbuffer called with renderbuffer from other context";
  }
}

void TestGLES2Interface::BindFramebuffer(GLenum target, GLuint framebuffer) {
  if (framebuffer != 0 &&
      framebuffer_set_.find(framebuffer) == framebuffer_set_.end()) {
    ADD_FAILURE() << "bindFramebuffer called with unknown framebuffer";
  } else if (framebuffer != 0 && (framebuffer >> 16) != context_id_) {
    ADD_FAILURE()
        << "bindFramebuffer called with framebuffer from other context";
  } else {
    current_framebuffer_ = framebuffer;
  }
}

void TestGLES2Interface::BindBuffer(GLenum target, GLuint buffer) {
  bound_buffer_[target] = buffer;
  if (!buffer)
    return;
  unsigned context_id = buffer >> 16;
  unsigned buffer_id = buffer & 0xffff;
  DCHECK(buffer_id);
  DCHECK_LT(buffer_id, next_buffer_id_);
  DCHECK_EQ(context_id, context_id_);

  if (buffers_.count(bound_buffer_[target]) == 0)
    buffers_[bound_buffer_[target]] = std::make_unique<Buffer>();

  buffers_[bound_buffer_[target]]->target = target;
}

void TestGLES2Interface::PixelStorei(GLenum pname, GLint param) {
  switch (pname) {
    case GL_UNPACK_ALIGNMENT:
      // Param should be a power of two <= 8.
      EXPECT_EQ(0, param & (param - 1));
      EXPECT_GE(8, param);
      switch (param) {
        case 1:
        case 2:
        case 4:
        case 8:
          unpack_alignment_ = param;
          break;
        default:
          break;
      }
      break;
    default:
      break;
  }
}

void* TestGLES2Interface::MapBufferCHROMIUM(GLuint target, GLenum access) {
  DCHECK_GT(bound_buffer_.count(target), 0u);
  DCHECK_GT(buffers_.count(bound_buffer_[target]), 0u);
  DCHECK_EQ(target, buffers_[bound_buffer_[target]]->target);
  if (times_map_buffer_chromium_succeeds_ >= 0) {
    if (!times_map_buffer_chromium_succeeds_) {
      return nullptr;
    }
    --times_map_buffer_chromium_succeeds_;
  }

  return buffers_[bound_buffer_[target]]->pixels.data();
}

GLboolean TestGLES2Interface::UnmapBufferCHROMIUM(GLuint target) {
  DCHECK_GT(bound_buffer_.count(target), 0u);
  DCHECK_GT(buffers_.count(bound_buffer_[target]), 0u);
  DCHECK_EQ(target, buffers_[bound_buffer_[target]]->target);
  buffers_[bound_buffer_[target]]->pixels = {};
  return true;
}

void TestGLES2Interface::BufferData(GLenum target,
                                    GLsizeiptr size,
                                    const void* data,
                                    GLenum usage) {
  DCHECK_GT(bound_buffer_.count(target), 0u);
  DCHECK_GT(buffers_.count(bound_buffer_[target]), 0u);
  DCHECK_EQ(target, buffers_[bound_buffer_[target]]->target);
  Buffer* buffer = buffers_[bound_buffer_[target]].get();
  if (context_lost_) {
    buffer->pixels = {};
    return;
  }

  buffer->pixels = base::HeapArray<uint8_t>::Uninit(size);
  if (data != nullptr) {
    buffer->pixels.as_span().copy_from(UNSAFE_TODO(
        base::span<const uint8_t>(reinterpret_cast<const uint8_t*>(data),
                                  base::checked_cast<size_t>(size))));
  }
}

void TestGLES2Interface::GenSyncTokenCHROMIUM(GLbyte* sync_token) {
  // Don't return a valid sync token if context is lost. This matches behavior
  // of CommandBufferProxyImpl.
  if (context_lost_)
    return;
  gpu::SyncToken sync_token_data(
      gpu::CommandBufferNamespace::GPU_IO,
      gpu::CommandBufferId::FromUnsafeValue(test_command_buffer_id_),
      next_insert_fence_sync_++);
  sync_token_data.SetVerifyFlush();
  UNSAFE_TODO(memcpy(sync_token, &sync_token_data, sizeof(sync_token_data)));
}

void TestGLES2Interface::GenUnverifiedSyncTokenCHROMIUM(GLbyte* sync_token) {
  // Don't return a valid sync token if context is lost. This matches behavior
  // of CommandBufferProxyImpl.
  if (context_lost_)
    return;
  gpu::SyncToken sync_token_data(gpu::CommandBufferNamespace::GPU_IO,
                                 gpu::CommandBufferId(),
                                 next_insert_fence_sync_++);
  UNSAFE_TODO(memcpy(sync_token, &sync_token_data, sizeof(sync_token_data)));
}

void TestGLES2Interface::VerifySyncTokensCHROMIUM(GLbyte** sync_tokens,
                                                  GLsizei count) {
  for (GLsizei i = 0; i < count; ++i) {
    gpu::SyncToken sync_token_data;
    UNSAFE_TODO(memcpy(sync_token_data.GetData(), sync_tokens[i],
                       sizeof(sync_token_data)));
    sync_token_data.SetVerifyFlush();
    UNSAFE_TODO(
        memcpy(sync_tokens[i], &sync_token_data, sizeof(sync_token_data)));
  }
}

void TestGLES2Interface::WaitSyncTokenCHROMIUM(const GLbyte* sync_token) {
  gpu::SyncToken sync_token_data;
  if (sync_token)
    UNSAFE_TODO(memcpy(&sync_token_data, sync_token, sizeof(sync_token_data)));

  if (sync_token_data.release_count() >
      last_waited_sync_token_.release_count()) {
    last_waited_sync_token_ = sync_token_data;
  }
}

void TestGLES2Interface::BeginQueryEXT(GLenum target, GLuint id) {}

void TestGLES2Interface::EndQueryEXT(GLenum target) {
  if (times_end_query_succeeds_ >= 0) {
    if (!times_end_query_succeeds_) {
      LoseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
                          GL_INNOCENT_CONTEXT_RESET_ARB);
    }
    --times_end_query_succeeds_;
  }
}

void TestGLES2Interface::GetQueryObjectuivEXT(GLuint id,
                                              GLenum pname,
                                              GLuint* params) {
  // If the context is lost, behave as if result is available.
  if (pname == GL_QUERY_RESULT_AVAILABLE_EXT ||
      pname == GL_QUERY_RESULT_AVAILABLE_NO_FLUSH_CHROMIUM_EXT) {
    *params = 1;
  }
}

GLuint TestGLES2Interface::CreateAndTexStorage2DSharedImageCHROMIUM(
    const GLbyte* mailbox) {
  GLuint texture_id;
  GenTextures(1, &texture_id);
  return texture_id;
}

void TestGLES2Interface::LoseContextCHROMIUM(GLenum current, GLenum other) {
  if (context_lost_)
    return;
  context_lost_ = true;
  if (!context_lost_callback_.is_null())
    std::move(context_lost_callback_).Run();

  for (size_t i = 0; i < shared_contexts_.size(); ++i)
    shared_contexts_[i]->LoseContextCHROMIUM(current, other);
  shared_contexts_.clear();
}

GLenum TestGLES2Interface::GetGraphicsResetStatusKHR() {
  if (IsContextLost())
    return GL_UNKNOWN_CONTEXT_RESET_KHR;
  return GL_NO_ERROR;
}

void TestGLES2Interface::ReadPixels(GLint x,
                                    GLint y,
                                    GLsizei width,
                                    GLsizei height,
                                    GLenum format,
                                    GLenum type,
                                    void* pixels) {
  // Zero-initialize the destination buffer to appease MSAN. Note that we don't
  // support non-default alignment or ES3 pixel store parameters, but that's ok
  // since this is test-only code and MSAN will catch any uninitialized access.
  uint32_t pixels_size = 0;
  gpu::gles2::GLES2Util::ComputeImageDataSizes(
      width, height, /*depth=*/1, format, type, /*alignment=*/4, &pixels_size,
      /*opt_unpadded_row_size=*/nullptr, /*opt_padded_row_size=*/nullptr);
  UNSAFE_TODO(memset(pixels, 0, pixels_size));
}

void TestGLES2Interface::set_support_texture_half_float_linear(bool support) {
  test_capabilities_.texture_half_float_linear = support;
}

void TestGLES2Interface::set_support_texture_norm16(bool support) {
  test_capabilities_.texture_norm16 = support;
}

void TestGLES2Interface::set_max_texture_size(int size) {
  test_gl_capabilities_.max_texture_size = size;
  test_capabilities_.max_texture_size = size;
}

void TestGLES2Interface::set_supports_texture_rg(bool support) {
  test_capabilities_.texture_rg = support;
}

size_t TestGLES2Interface::NumTextures() const {
  return textures_.size();
}

GLuint TestGLES2Interface::NextTextureId() {
  GLuint texture_id = next_texture_id_++;
  DCHECK(texture_id < (1 << 16));
  texture_id |= context_id_ << 16;
  return texture_id;
}

void TestGLES2Interface::RetireTextureId(GLuint id) {
  unsigned context_id = id >> 16;
  unsigned texture_id = id & 0xffff;
  DCHECK(texture_id);
  DCHECK_LT(texture_id, next_texture_id_);
  DCHECK_EQ(context_id, context_id_);
}

GLuint TestGLES2Interface::NextBufferId() {
  GLuint buffer_id = next_buffer_id_++;
  DCHECK(buffer_id < (1 << 16));
  buffer_id |= context_id_ << 16;
  return buffer_id;
}

void TestGLES2Interface::RetireBufferId(GLuint id) {
  unsigned context_id = id >> 16;
  unsigned buffer_id = id & 0xffff;
  DCHECK(buffer_id);
  DCHECK_LT(buffer_id, next_buffer_id_);
  DCHECK_EQ(context_id, context_id_);
}

GLuint TestGLES2Interface::NextImageId() {
  GLuint image_id = next_image_id_++;
  DCHECK(image_id < (1 << 16));
  image_id |= context_id_ << 16;
  return image_id;
}

void TestGLES2Interface::RetireImageId(GLuint id) {
  unsigned context_id = id >> 16;
  unsigned image_id = id & 0xffff;
  DCHECK(image_id);
  DCHECK_LT(image_id, next_image_id_);
  DCHECK_EQ(context_id, context_id_);
}

GLuint TestGLES2Interface::NextFramebufferId() {
  GLuint id = next_framebuffer_id_++;
  DCHECK(id < (1 << 16));
  id |= context_id_ << 16;
  framebuffer_set_.insert(id);
  return id;
}

void TestGLES2Interface::RetireFramebufferId(GLuint id) {
  DCHECK(framebuffer_set_.contains(id));
  framebuffer_set_.erase(id);
}

GLuint TestGLES2Interface::NextRenderbufferId() {
  GLuint id = next_renderbuffer_id_++;
  DCHECK(id < (1 << 16));
  id |= context_id_ << 16;
  renderbuffer_set_.insert(id);
  return id;
}

void TestGLES2Interface::RetireRenderbufferId(GLuint id) {
  DCHECK(renderbuffer_set_.contains(id));
  renderbuffer_set_.erase(id);
}

size_t TestGLES2Interface::NumFramebuffers() const {
  return framebuffer_set_.size();
}

size_t TestGLES2Interface::NumRenderbuffers() const {
  return renderbuffer_set_.size();
}

TestGLES2Interface::Buffer::Buffer() : target(0) {}

TestGLES2Interface::Buffer::~Buffer() = default;

}  // namespace viz
