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

#include "ui/gl/init/gl_factory.h"

#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/gl/gl_share_group.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/gl_version_info.h"
#include "ui/gl/init/gl_initializer.h"

#if BUILDFLAG(IS_OZONE)
#include "ui/base/ui_base_features.h"
#include "ui/ozone/public/ozone_platform.h"
#endif

namespace gl {
namespace init {

namespace {

bool g_is_angle_enabled = true;

bool ShouldFallbackToSoftwareGL() {
  const base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
  std::string requested_implementation_gl_name =
      cmd->GetSwitchValueASCII(switches::kUseGL);

  if (cmd->HasSwitch(switches::kUseGL) &&
      requested_implementation_gl_name == "any") {
    return true;
  } else {
    return false;
  }
}

GLImplementationParts GetRequestedGLImplementation(
    bool* fallback_to_software_gl) {
  const base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
  std::string requested_implementation_gl_name =
      cmd->GetSwitchValueASCII(switches::kUseGL);

  // If --use-angle was specified but --use-gl was not, assume --use-gl=angle
  if (cmd->HasSwitch(switches::kUseANGLE) &&
      !cmd->HasSwitch(switches::kUseGL)) {
    requested_implementation_gl_name = kGLImplementationANGLEName;
  }

  if (requested_implementation_gl_name == kGLImplementationDisabledName) {
    return GLImplementationParts(kGLImplementationDisabled);
  }

  std::vector<GLImplementationParts> allowed_impls =
      GetAllowedGLImplementations();

  if (GetGlWorkarounds().disable_es3gl_context_for_testing) {
    GLVersionInfo::DisableES3ForTesting();
  }

  // If the passthrough command decoder is enabled, put ANGLE first if allowed
  if (g_is_angle_enabled && UsePassthroughCommandDecoder(cmd)) {
    std::vector<GLImplementationParts> angle_impls = {};
    bool software_gl_allowed = false;
    auto iter = allowed_impls.begin();
    while (iter != allowed_impls.end()) {
      if ((*iter) == GetSoftwareGLImplementation()) {
        software_gl_allowed = true;
        allowed_impls.erase(iter);
      } else if (iter->gl == kGLImplementationEGLANGLE) {
        angle_impls.emplace_back(*iter);
        allowed_impls.erase(iter);
      } else {
        iter++;
      }
    }
    allowed_impls.insert(allowed_impls.begin(), angle_impls.begin(),
                         angle_impls.end());
    // Insert software implementations at the end, after all other hardware
    // implementations
    if (software_gl_allowed) {
      allowed_impls.emplace_back(GetSoftwareGLImplementation());
    }
  }

  if (allowed_impls.empty()) {
    LOG(ERROR) << "List of allowed GL implementations is empty.";
    return GLImplementationParts(kGLImplementationNone);
  }

  *fallback_to_software_gl = false;
  absl::optional<GLImplementationParts> impl_from_cmdline =
      GetRequestedGLImplementationFromCommandLine(cmd, fallback_to_software_gl);

  // The default implementation is always the first one in list.
  if (!impl_from_cmdline)
    return allowed_impls[0];

  if (IsSoftwareGLImplementation(*impl_from_cmdline))
    return *impl_from_cmdline;

  if (impl_from_cmdline->IsAllowed(allowed_impls))
    return *impl_from_cmdline;

  std::vector<std::string> allowed_impl_strs;
  for (const auto& allowed_impl : allowed_impls) {
    allowed_impl_strs.push_back(allowed_impl.ToString());
  }
  LOG(ERROR) << "Requested GL implementation " << impl_from_cmdline->ToString()
             << " not found in allowed implementations: ["
             << base::JoinString(allowed_impl_strs, ",") << "].";
  return GLImplementationParts(kGLImplementationNone);
}

GLDisplay* InitializeGLOneOffPlatformHelper(bool init_extensions,
                                            gl::GpuPreference gpu_preference) {
  TRACE_EVENT1("gpu,startup", "gl::init::InitializeGLOneOffPlatformHelper",
               "init_extensions", init_extensions);

  bool fallback_to_software_gl = ShouldFallbackToSoftwareGL();
  const base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
  bool disable_gl_drawing = cmd->HasSwitch(switches::kDisableGLDrawingForTests);

  return InitializeGLOneOffPlatformImplementation(
      fallback_to_software_gl, disable_gl_drawing, init_extensions,
      gpu_preference);
}

}  // namespace

GLDisplay* InitializeGLOneOff(gl::GpuPreference gpu_preference) {
  TRACE_EVENT0("gpu,startup", "gl::init::InitializeOneOff");

  if (!InitializeStaticGLBindingsOneOff())
    return nullptr;
  if (GetGLImplementation() == kGLImplementationDisabled) {
    return GetDefaultDisplayEGL();
  }

  return InitializeGLOneOffPlatformHelper(true, gpu_preference);
}

GLDisplay* InitializeGLNoExtensionsOneOff(bool init_bindings,
                                          gl::GpuPreference gpu_preference) {
  TRACE_EVENT1("gpu,startup", "gl::init::InitializeNoExtensionsOneOff",
               "init_bindings", init_bindings);
  if (init_bindings) {
    if (!InitializeStaticGLBindingsOneOff())
      return nullptr;
    if (GetGLImplementation() == kGLImplementationDisabled) {
      return GetDefaultDisplayEGL();
    }
  }

  return InitializeGLOneOffPlatformHelper(false, gpu_preference);
}

bool InitializeStaticGLBindingsOneOff() {
  DCHECK_EQ(kGLImplementationNone, GetGLImplementation());

  bool fallback_to_software_gl = false;
  GLImplementationParts impl =
      GetRequestedGLImplementation(&fallback_to_software_gl);
  if (impl.gl == kGLImplementationDisabled) {
    SetGLImplementation(kGLImplementationDisabled);
    return true;
  } else if (impl.gl == kGLImplementationNone) {
    return false;
  }

  return InitializeStaticGLBindingsImplementation(impl,
                                                  fallback_to_software_gl);
}

bool InitializeStaticGLBindingsImplementation(GLImplementationParts impl,
                                              bool fallback_to_software_gl) {
  if (IsSoftwareGLImplementation(impl))
    fallback_to_software_gl = false;

  bool initialized = InitializeStaticGLBindings(impl);
  if (!initialized && fallback_to_software_gl) {
    ShutdownGL(nullptr, /*due_to_fallback*/ true);
    initialized = InitializeStaticGLBindings(GetSoftwareGLImplementation());
  }
  if (!initialized) {
    ShutdownGL(nullptr, /*due_to_fallback*/ false);
    return false;
  }
  return true;
}

GLDisplay* InitializeGLOneOffPlatformImplementation(
    bool fallback_to_software_gl,
    bool disable_gl_drawing,
    bool init_extensions,
    gl::GpuPreference gpu_preference) {
  if (IsSoftwareGLImplementation(GetGLImplementationParts()))
    fallback_to_software_gl = false;

  GLDisplay* display = InitializeGLOneOffPlatform(gpu_preference);
  bool initialized = !!display;

  if (!initialized) {
    DVLOG(1) << "Initialization failed. Attempting to initialize default "
                "GLDisplayEGL.";
    RemoveGpuPreferenceEGL(gpu_preference);
    display = InitializeGLOneOffPlatform(gl::GpuPreference::kDefault);
    initialized = !!display;
  }
  if (!initialized && fallback_to_software_gl) {
    ShutdownGL(nullptr, /*due_to_fallback=*/true);
    if (InitializeStaticGLBindings(GetSoftwareGLImplementation())) {
      display = InitializeGLOneOffPlatform(gpu_preference);
      initialized = !!display;
    }
  }
  if (initialized && init_extensions) {
    initialized = InitializeExtensionSettingsOneOffPlatform(display);
  }

  if (!initialized) {
    ShutdownGL(display, false);
    return nullptr;
  }

  DVLOG(1) << "Using " << GetGLImplementationGLName(GetGLImplementationParts())
           << " GL implementation.";
  SetNullDrawGLBindings(disable_gl_drawing);
  return display;
}

GLDisplay* GetOrInitializeGLOneOffPlatformImplementation(
    bool fallback_to_software_gl,
    bool disable_gl_drawing,
    bool init_extensions,
    gl::GpuPreference gpu_preference) {
  gl::GLDisplay* display = gl::GetDisplay(gpu_preference);
  DCHECK(display);

  if (display->IsInitialized()) {
    return display;
  }

  display = gl::init::InitializeGLOneOffPlatformImplementation(
      /*fallback_to_software_gl=*/false, /*disable_gl_drawing=*/false,
      /*init_extensions=*/true,
      /*gpu_preference=*/gpu_preference);

  return display;
}

void ShutdownGL(GLDisplay* display, bool due_to_fallback) {
  ShutdownGLPlatform(display);

  UnloadGLNativeLibraries(due_to_fallback);
  SetGLImplementation(kGLImplementationNone);
}

scoped_refptr<GLSurface> CreateOffscreenGLSurface(gl::GLDisplay* display,
                                                  const gfx::Size& size) {
  return CreateOffscreenGLSurfaceWithFormat(display, size, GLSurfaceFormat());
}

void DisableANGLE() {
  DCHECK_NE(GetGLImplementation(), kGLImplementationEGLANGLE);
  g_is_angle_enabled = false;
}

}  // namespace init
}  // namespace gl