// 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_context_provider.h"

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

#include <array>
#include <limits>
#include <memory>
#include <set>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "components/viz/common/gpu/context_cache_controller.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "components/viz/test/test_gles2_interface.h"
#include "components/viz/test/test_raster_interface.h"
#include "gpu/command_buffer/common/shared_image_capabilities.h"
#include "gpu/config/skia_limits.h"
#include "third_party/skia/include/gpu/ganesh/gl/GrGLInterface.h"

namespace viz {

namespace {

// Various tests rely on functionality (capabilities) enabled by these extension
// strings.
constexpr auto kExtensions = std::to_array<const char*>({
    "GL_ARB_texture_rectangle",
    "GL_EXT_stencil_wrap",
    "GL_EXT_texture_format_BGRA8888",
    "GL_OES_rgb8_rgba8",
    "GL_EXT_texture_norm16",
    "GL_CHROMIUM_framebuffer_multisample",
    "GL_CHROMIUM_renderbuffer_format_BGRA8888",
    "GL_OES_texture_half_float",
    "GL_OES_texture_half_float_linear",
    "GL_EXT_color_buffer_half_float",
});

class TestGLES2InterfaceForContextProvider : public TestGLES2Interface {
 public:
  TestGLES2InterfaceForContextProvider(
      std::string additional_extensions = std::string())
      : extension_string_(
            BuildExtensionString(std::move(additional_extensions))) {}

  TestGLES2InterfaceForContextProvider(
      const TestGLES2InterfaceForContextProvider&) = delete;
  TestGLES2InterfaceForContextProvider& operator=(
      const TestGLES2InterfaceForContextProvider&) = delete;

  ~TestGLES2InterfaceForContextProvider() override = default;

  // TestGLES2Interface:
  const GLubyte* GetString(GLenum name) override {
    switch (name) {
      case GL_EXTENSIONS:
        return reinterpret_cast<const GLubyte*>(extension_string_.c_str());
      case GL_VERSION:
        return reinterpret_cast<const GrGLubyte*>("4.0 Null GL");
      case GL_SHADING_LANGUAGE_VERSION:
        return reinterpret_cast<const GrGLubyte*>("4.20.8 Null GLSL");
      case GL_VENDOR:
        return reinterpret_cast<const GrGLubyte*>("Null Vendor");
      case GL_RENDERER:
        return reinterpret_cast<const GrGLubyte*>("The Null (Non-)Renderer");
    }
    return nullptr;
  }
  const GrGLubyte* GetStringi(GrGLenum name, GrGLuint i) override {
    if (name == GL_EXTENSIONS && i < std::size(kExtensions))
      return reinterpret_cast<const GLubyte*>(kExtensions[i]);
    return nullptr;
  }
  void GetIntegerv(GLenum name, GLint* params) override {
    switch (name) {
      case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
        *params = 8;
        return;
      case GL_MAX_RENDERBUFFER_SIZE:
        *params = 2048;
        break;
      case GL_MAX_TEXTURE_SIZE:
        *params = 2048;
        break;
      case GL_MAX_TEXTURE_IMAGE_UNITS:
        *params = 8;
        break;
      case GL_MAX_VERTEX_ATTRIBS:
        *params = 8;
        break;
      case GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
        *params = 0;
        return;
      default:
        break;
    }
    TestGLES2Interface::GetIntegerv(name, params);
  }

 private:
  static std::string BuildExtensionString(std::string additional_extensions) {
    std::string extension_string = kExtensions[0];
    for (size_t i = 1; i < std::size(kExtensions); ++i) {
      extension_string += " ";
      extension_string += kExtensions[i];
    }
    if (!additional_extensions.empty()) {
      extension_string += " ";
      extension_string += additional_extensions;
    }
    return extension_string;
  }

  const std::string extension_string_;
};

}  // namespace

// static
scoped_refptr<TestContextProvider> TestContextProvider::CreateGLES(
    std::string additional_extensions) {
  constexpr bool support_locking = false;
  return new TestContextProvider(
      std::make_unique<TestContextSupport>(),
      std::make_unique<TestGLES2InterfaceForContextProvider>(
          std::move(additional_extensions)),
      support_locking);
}

// static
scoped_refptr<TestContextProvider> TestContextProvider::CreateRaster() {
  return CreateRaster(std::make_unique<TestContextSupport>());
}

// static
scoped_refptr<TestContextProvider> TestContextProvider::CreateRaster(
    std::unique_ptr<TestRasterInterface> raster) {
  CHECK(raster);
  return new TestContextProvider(std::make_unique<TestContextSupport>(),
                                 std::move(raster),
                                 /*support_locking=*/false);
}

// static
scoped_refptr<TestContextProvider> TestContextProvider::CreateRaster(
    std::unique_ptr<TestContextSupport> context_support) {
  CHECK(context_support);
  return new TestContextProvider(std::move(context_support),
                                 std::make_unique<TestRasterInterface>(),
                                 /*support_locking=*/false);
}

// static
scoped_refptr<TestContextProvider> TestContextProvider::CreateWorker() {
  return CreateWorker(std::make_unique<TestContextSupport>());
}

// static
scoped_refptr<TestContextProvider> TestContextProvider::CreateWorker(
    std::unique_ptr<TestContextSupport> support) {
  return CreateWorker(std::move(support),
                      std::make_unique<TestRasterInterface>());
}

// static
scoped_refptr<TestContextProvider> TestContextProvider::CreateWorker(
    std::unique_ptr<TestContextSupport> support,
    std::unique_ptr<TestRasterInterface> raster) {
  DCHECK(support);

  auto worker_context_provider = base::WrapRefCounted<TestContextProvider>(
      new TestContextProvider(std::move(support), std::move(raster),
                              /*support_locking=*/true));

  // Worker contexts are bound to the thread they are created on.
  auto result = worker_context_provider->BindToCurrentSequence();
  if (result != gpu::ContextResult::kSuccess)
    return nullptr;
  return worker_context_provider;
}

TestContextProvider::TestContextProvider(
    std::unique_ptr<TestContextSupport> support,
    std::unique_ptr<TestRasterInterface> raster,
    bool support_locking)
    : support_(std::move(support)),
      raster_context_(std::move(raster)),
      shared_image_interface_(
          base::MakeRefCounted<gpu::TestSharedImageInterface>()),
      support_locking_(support_locking) {
  DCHECK(main_thread_checker_.CalledOnValidThread());
  DCHECK(raster_context_);

  context_thread_checker_.DetachFromThread();
  raster_context_->set_test_support(support_.get());

  // Some tests exercise production codepaths that require this cap; enable it
  // by default for convenience.
  raster_context_->set_texture_rg(true);

  // Just pass nullptr to the ContextCacheController for its task runner.
  // Idle handling is tested directly in ContextCacheController's
  // unittests, and isn't needed here.
  cache_controller_ =
      std::make_unique<ContextCacheController>(support_.get(), nullptr);
}

TestContextProvider::TestContextProvider(
    std::unique_ptr<TestContextSupport> support,
    std::unique_ptr<TestGLES2Interface> gl,
    bool support_locking)
    : support_(std::move(support)),
      context_gl_(std::move(gl)),
      support_locking_(support_locking) {
  DCHECK(main_thread_checker_.CalledOnValidThread());
  DCHECK(context_gl_);
  context_thread_checker_.DetachFromThread();
  context_gl_->set_test_support(support_.get());
  // Just pass nullptr to the ContextCacheController for its task runner.
  // Idle handling is tested directly in ContextCacheController's
  // unittests, and isn't needed here.
  cache_controller_ =
      std::make_unique<ContextCacheController>(support_.get(), nullptr);

  shared_image_interface_ =
      base::MakeRefCounted<gpu::TestSharedImageInterface>();
}

TestContextProvider::~TestContextProvider() {
  DCHECK(main_thread_checker_.CalledOnValidThread() ||
         context_thread_checker_.CalledOnValidThread());
}

void TestContextProvider::AddRef() const {
  base::RefCountedThreadSafe<TestContextProvider>::AddRef();
}

void TestContextProvider::Release() const {
  base::RefCountedThreadSafe<TestContextProvider>::Release();
}

gpu::ContextResult TestContextProvider::BindToCurrentSequence() {
  // This is called on the thread the context will be used.
  DCHECK(context_thread_checker_.CalledOnValidThread());

  if (!bound_) {
    if (context_gl_) {
      if (context_gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR)
        return gpu::ContextResult::kTransientFailure;

      context_gl_->set_context_lost_callback(base::BindOnce(
          &TestContextProvider::OnLostContext, base::Unretained(this)));
    } else {
      if (raster_context_->GetGraphicsResetStatusKHR() != GL_NO_ERROR)
        return gpu::ContextResult::kTransientFailure;

      raster_context_->set_context_lost_callback(base::BindOnce(
          &TestContextProvider::OnLostContext, base::Unretained(this)));
    }
  }
  bound_ = true;
  return gpu::ContextResult::kSuccess;
}

const gpu::Capabilities& TestContextProvider::ContextCapabilities() const {
  DCHECK(bound_);
  CheckValidThreadOrLockAcquired();
  if (context_gl_)
    return context_gl_->test_capabilities();

  return raster_context_->capabilities();
}

const gpu::GpuFeatureInfo& TestContextProvider::GetGpuFeatureInfo() const {
  DCHECK(bound_);
  CheckValidThreadOrLockAcquired();
  return gpu_feature_info_;
}

gpu::gles2::GLES2Interface* TestContextProvider::ContextGL() {
  DCHECK(bound_);
  CheckValidThreadOrLockAcquired();

  return context_gl_.get();
}

gpu::raster::RasterInterface* TestContextProvider::RasterInterface() {
  return raster_context_.get();
}

gpu::ContextSupport* TestContextProvider::ContextSupport() {
  return support();
}

gpu::TestSharedImageInterface* TestContextProvider::SharedImageInterface() {
  return shared_image_interface_.get();
}

ContextCacheController* TestContextProvider::CacheController() {
  CheckValidThreadOrLockAcquired();
  return cache_controller_.get();
}

base::Lock* TestContextProvider::GetLock() {
  if (!support_locking_)
    return nullptr;
  return &context_lock_;
}

void TestContextProvider::OnLostContext() {
  CheckValidThreadOrLockAcquired();
  for (auto& observer : observers_)
    observer.OnContextLost();
}

TestGLES2Interface* TestContextProvider::TestContextGL() {
  DCHECK(bound_);
  CheckValidThreadOrLockAcquired();
  return context_gl_.get();
}

TestRasterInterface* TestContextProvider::GetTestRasterInterface() {
  DCHECK(bound_);
  CheckValidThreadOrLockAcquired();
  return raster_context_.get();
}

TestRasterInterface* TestContextProvider::UnboundTestRasterInterface() {
  return raster_context_.get();
}

void TestContextProvider::AddObserver(ContextLostObserver* obs) {
  observers_.AddObserver(obs);
}

void TestContextProvider::RemoveObserver(ContextLostObserver* obs) {
  observers_.RemoveObserver(obs);
}

bool TestContextProvider::IsLost() {
  if (context_gl_) {
    return context_gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR;
  } else {
    return raster_context_->GetGraphicsResetStatusKHR() != GL_NO_ERROR;
  }
}

}  // namespace viz
