// 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 "gpu/command_buffer/service/service_utils.h"

#include <cmath>
#include <string>
#include <string_view>

#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory_coordinator/memory_consumer.h"
#include "base/memory_coordinator/utils.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "gpu/command_buffer/common/gles2_cmd_utils.h"
#include "gpu/command_buffer/service/context_group.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "gpu/config/gpu_finch_features.h"
#include "gpu/config/gpu_switches.h"
#include "skia/buildflags.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_features.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/gl_switches.h"
#include "ui/gl/gl_utils.h"

namespace gpu {
namespace gles2 {

namespace {

bool GetUintFromSwitch(const base::CommandLine* command_line,
                       std::string_view switch_string,
                       uint32_t* value) {
  if (!command_line->HasSwitch(switch_string)) {
    return false;
  }
  std::string switch_value(command_line->GetSwitchValueASCII(switch_string));
  return base::StringToUint(switch_value, value);
}

// Parse the value of --use-vulkan from the command line. If unspecified and
// features::kVulkan is enabled (GrContext is going to use vulkan), default to
// the native implementation.
VulkanImplementationName ParseVulkanImplementationName(
    const base::CommandLine* command_line) {
#if BUILDFLAG(IS_ANDROID)
  if (command_line->HasSwitch(switches::kWebViewDrawFunctorUsesVulkan)) {
    return VulkanImplementationName::kForcedNative;
  }
#endif

  if (command_line->HasSwitch(switches::kUseVulkan)) {
    auto value = command_line->GetSwitchValueASCII(switches::kUseVulkan);
    if (value.empty() || value == switches::kVulkanImplementationNameNative) {
      return VulkanImplementationName::kForcedNative;
    } else if (value == switches::kVulkanImplementationNameSwiftshader) {
      return VulkanImplementationName::kSwiftshader;
    }
  }

  if (features::IsUsingVulkan()) {
    // If the vulkan feature is enabled from command line, we will force to use
    // vulkan even if it is blocklisted.
    return base::FeatureList::GetInstance()->IsFeatureOverriddenFromCommandLine(
               features::kVulkan.name,
               base::FeatureList::OVERRIDE_ENABLE_FEATURE)
               ? VulkanImplementationName::kForcedNative
               : VulkanImplementationName::kNative;
  }

  // GrContext is not going to use Vulkan.
  return VulkanImplementationName::kNone;
}

WebGPUAdapterName ParseWebGPUAdapterName(
    const base::CommandLine* command_line) {
  if (command_line->HasSwitch(switches::kUseWebGPUAdapter)) {
    auto value = command_line->GetSwitchValueASCII(switches::kUseWebGPUAdapter);

    static const struct {
      const char* name;
      WebGPUAdapterName value;
    } kAdapterNames[] = {
        {"", WebGPUAdapterName::kDefault},
        {"default", WebGPUAdapterName::kDefault},
        {"d3d11", WebGPUAdapterName::kD3D11},
        {"opengles", WebGPUAdapterName::kOpenGLES},
        {"swiftshader", WebGPUAdapterName::kSwiftShader},
    };

    for (const auto& adapter_name : kAdapterNames) {
      if (value == adapter_name.name) {
        return adapter_name.value;
      }
    }

    DLOG(ERROR) << "Invalid switch " << switches::kUseWebGPUAdapter << "="
                << value << ".";
  }
  return WebGPUAdapterName::kDefault;
}

WebGPUPowerPreference ParseWebGPUPowerPreference(
    const base::CommandLine* command_line) {
  if (command_line->HasSwitch(switches::kUseWebGPUPowerPreference)) {
    auto value =
        command_line->GetSwitchValueASCII(switches::kUseWebGPUPowerPreference);
    if (value.empty()) {
      return WebGPUPowerPreference::kNone;
    } else if (value == "none") {
      return WebGPUPowerPreference::kNone;
    } else if (value == "default-low-power") {
      return WebGPUPowerPreference::kDefaultLowPower;
    } else if (value == "default-high-performance") {
      return WebGPUPowerPreference::kDefaultHighPerformance;
    } else if (value == "force-low-power") {
      return WebGPUPowerPreference::kForceLowPower;
    } else if (value == "force-high-performance") {
      return WebGPUPowerPreference::kForceHighPerformance;
    } else {
      DLOG(ERROR) << "Invalid switch " << switches::kUseWebGPUPowerPreference
                  << "=" << value << ".";
    }
  }
  return WebGPUPowerPreference::kNone;
}

}  // namespace

gl::GLContextAttribs GenerateGLContextAttribsForDecoder(
    ContextType context_type,
    gl::GpuPreference gpu_preference,
    const ContextGroup* context_group) {
  gl::GLContextAttribs attribs;
  attribs.gpu_preference = gpu_preference;
  if (context_group->use_passthrough_cmd_decoder()) {
    attribs.webgl_compatibility_context = IsWebGLContextType(context_type);
    attribs.enable_all_extensions = false;
    attribs.hardened_context = !attribs.webgl_compatibility_context;

    // Always use the global texture and semaphore share group for the
    // passthrough command decoder
    attribs.global_texture_share_group = true;
    attribs.global_semaphore_share_group = true;

    attribs.robust_resource_initialization = true;
    attribs.robust_buffer_access = true;
    attribs.allow_client_arrays = false;

    // Request a specific context version instead of always 3.0
    if (IsWebGL2OrES3ContextType(context_type)) {
      attribs.client_major_es_version = 3;
      attribs.client_minor_es_version = 0;
      attribs.allow_es_version_fallback =
          !features::ShouldFallbackToSWIfGLES3NotSupported();
    } else {
      DCHECK(IsWebGL1OrES2ContextType(context_type));
      attribs.client_major_es_version = 2;
      attribs.client_minor_es_version = 0;
      attribs.allow_es_version_fallback = false;
    }
  } else {
    attribs.client_major_es_version = 3;
    attribs.client_minor_es_version = 0;
  }

  return attribs;
}

gl::GLContextAttribs GenerateGLContextAttribsForCompositor(
    bool use_passthrough_cmd_decoder) {
  gl::GLContextAttribs attribs;
  if (use_passthrough_cmd_decoder) {
    // Always use the global texture and semaphore share group for the
    // passthrough command decoder
    attribs.global_texture_share_group = true;
    attribs.global_semaphore_share_group = true;

    attribs.passthrough_shaders = features::IsANGLEPassthroughShadersAllowed();

    // Disable resource initialization and buffer bounds checks for trusted
    // contexts.
    attribs.robust_resource_initialization = false;
    attribs.robust_buffer_access = false;
    attribs.allow_client_arrays = true;
  }

  attribs.client_major_es_version = 3;
  attribs.client_minor_es_version = 0;

  return attribs;
}

bool UsePassthroughCommandDecoder(const base::CommandLine* command_line) {
  return gl::UsePassthroughCommandDecoder(command_line);
}

GpuPreferences ParseGpuPreferences(const base::CommandLine* command_line) {
  GpuPreferences gpu_preferences;
  gpu_preferences.compile_shader_always_succeeds =
      command_line->HasSwitch(switches::kCompileShaderAlwaysSucceeds);
  gpu_preferences.disable_gl_error_limit =
      command_line->HasSwitch(switches::kDisableGLErrorLimit);
  gpu_preferences.disable_glsl_translator =
      command_line->HasSwitch(switches::kDisableGLSLTranslator);
  gpu_preferences.disable_shader_name_hashing =
      command_line->HasSwitch(switches::kDisableShaderNameHashing);
  gpu_preferences.enable_gpu_command_logging =
      command_line->HasSwitch(switches::kEnableGPUCommandLogging);
  gpu_preferences.enable_gpu_debugging =
      command_line->HasSwitch(switches::kEnableGPUDebugging);
  gpu_preferences.enable_gpu_service_logging_gpu =
      command_line->HasSwitch(switches::kEnableGPUServiceLoggingGPU);
  gpu_preferences.enable_gpu_driver_debug_logging =
      command_line->HasSwitch(switches::kEnableGPUDriverDebugLogging);
  gpu_preferences.disable_gpu_program_cache =
      !features::IsShaderDiskCacheEnabled(command_line);
  gpu_preferences.enforce_gl_minimums =
      command_line->HasSwitch(switches::kEnforceGLMinimums);
  if (GetUintFromSwitch(
          command_line, switches::kForceGpuMemDiscardableLimitMb,
          &gpu_preferences.force_gpu_mem_discardable_limit_bytes)) {
    gpu_preferences.force_gpu_mem_discardable_limit_bytes *= 1024 * 1024;
  }
  GetUintFromSwitch(command_line, switches::kForceMaxTextureSize,
                    &gpu_preferences.force_max_texture_size);
  if (GetUintFromSwitch(command_line, switches::kGpuProgramCacheSizeKb,
                        &gpu_preferences.gpu_program_cache_size)) {
    gpu_preferences.gpu_program_cache_size *= 1024;
  }
  gpu_preferences.disable_gpu_shader_disk_cache =
      command_line->HasSwitch(switches::kDisableGpuShaderDiskCache);
  gpu_preferences.enable_threaded_texture_mailboxes =
      command_line->HasSwitch(switches::kEnableThreadedTextureMailboxes);
  gpu_preferences.gl_shader_interm_output =
      command_line->HasSwitch(switches::kGLShaderIntermOutput);
  gpu_preferences.enable_gpu_service_logging =
      command_line->HasSwitch(switches::kEnableGPUServiceLogging);
  gpu_preferences.enable_gpu_service_tracing =
      command_line->HasSwitch(switches::kEnableGPUServiceTracing);
  gpu_preferences.use_passthrough_cmd_decoder =
      gpu::gles2::UsePassthroughCommandDecoder(command_line);
  if (command_line->HasSwitch(switches::kIgnoreGpuBlocklist)) {
    std::string value =
        command_line->GetSwitchValueASCII(switches::kIgnoreGpuBlocklist);
    if (value.empty()) {
      gpu_preferences.ignore_gpu_blocklist = true;
    } else {
      auto pieces = base::SplitStringPiece(value, ",", base::TRIM_WHITESPACE,
                                           base::SPLIT_WANT_NONEMPTY);
      std::vector<uint32_t> ignored_entries;
      bool success = true;
      for (const auto& piece : pieces) {
        uint32_t entry_id;
        if (!base::StringToUint(piece, &entry_id)) {
          success = false;
          break;
        }
        ignored_entries.push_back(entry_id);
      }
      if (success) {
        gpu_preferences.ignored_gpu_blocklist_entries =
            std::move(ignored_entries);
      }
    }
  }
  gpu_preferences.enable_webgpu =
      command_line->HasSwitch(switches::kEnableUnsafeWebGPU) ||
      base::FeatureList::IsEnabled(features::kWebGPUService);
  gpu_preferences.enable_unsafe_webgpu =
      command_line->HasSwitch(switches::kEnableUnsafeWebGPU);
  gpu_preferences.enable_webgpu_developer_features =
      command_line->HasSwitch(switches::kEnableWebGPUDeveloperFeatures);
  gpu_preferences.use_webgpu_adapter = ParseWebGPUAdapterName(command_line);
  gpu_preferences.use_webgpu_power_preference =
      ParseWebGPUPowerPreference(command_line);
  gpu_preferences.force_webgpu_compat =
      command_line->HasSwitch(switches::kForceWebGPUCompat);
  if (command_line->HasSwitch(switches::kEnableDawnBackendValidation)) {
    auto value = command_line->GetSwitchValueASCII(
        switches::kEnableDawnBackendValidation);
    if (value.empty() || value == "full") {
      gpu_preferences.enable_dawn_backend_validation =
          DawnBackendValidationLevel::kFull;
    } else if (value == "partial") {
      gpu_preferences.enable_dawn_backend_validation =
          DawnBackendValidationLevel::kPartial;
    }
  }
  if (command_line->HasSwitch(switches::kEnableDawnFeatures)) {
    gpu_preferences.enabled_dawn_features_list = base::SplitString(
        command_line->GetSwitchValueASCII(switches::kEnableDawnFeatures), ",",
        base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
  }
  if (command_line->HasSwitch(switches::kDisableDawnFeatures)) {
    gpu_preferences.disabled_dawn_features_list = base::SplitString(
        command_line->GetSwitchValueASCII(switches::kDisableDawnFeatures), ",",
        base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
  }
  gpu_preferences.gr_context_type = ParseDefaultGrContextType(command_line);
  if (gpu_preferences.gr_context_type == GrContextType::kGraphiteDawn ||
      gpu_preferences.gr_context_type == GrContextType::kVulkan) {
    // Set the default fallback type to GL so that the tests can fall back to GL
    // without GpuDataManagerImplPrivate.
    gpu_preferences.fallback_gr_context_types = {GrContextType::kGL};
  } else {
    gpu_preferences.fallback_gr_context_types.clear();
  }
  gpu_preferences.use_vulkan = features::IsUsingVulkan()
                                   ? ParseVulkanImplementationName(command_line)
                                   : VulkanImplementationName::kNone;

#if BUILDFLAG(IS_FUCHSIA)
  // Vulkan Surface is not used on Fuchsia.
  gpu_preferences.disable_vulkan_surface = true;
#else
  gpu_preferences.disable_vulkan_surface =
      command_line->HasSwitch(switches::kDisableVulkanSurface);
#endif

  gpu_preferences.enable_gpu_blocked_time_metric =
      command_line->HasSwitch(switches::kEnableGpuBlockedTime);

  return gpu_preferences;
}

GrContextType ParseDefaultGrContextType(const base::CommandLine* command_line) {
#if BUILDFLAG(SKIA_USE_DAWN)
  if (base::FeatureList::IsEnabled(features::kLateGraphiteFeatureCheck)) {
    // With late check, only the disable flag gates Graphite; the full
    // feature/device check is deferred to the GPU process post-blocklist.
    if (!command_line->HasSwitch(switches::kDisableSkiaGraphite)) {
      return GrContextType::kGraphiteDawn;
    }
  } else {
    if (features::IsSkiaGraphiteEnabled(command_line)) {
      auto value =
          command_line->GetSwitchValueASCII(switches::kSkiaGraphiteDawnBackend);
      if (value.empty() || value == switches::kSkiaGraphiteDawnBackendD3D11 ||
          value == switches::kSkiaGraphiteDawnBackendD3D12 ||
          value == switches::kSkiaGraphiteDawnBackendMetal ||
          value == switches::kSkiaGraphiteDawnBackendOpenGLES ||
          value == switches::kSkiaGraphiteDawnBackendSwiftshader ||
          value == switches::kSkiaGraphiteDawnBackendVulkan) {
        return GrContextType::kGraphiteDawn;
      }
      LOG(ERROR)
          << "Skia Graphite enabled but no valid Dawn backend found for \""
          << value << "\" - falling back to Ganesh!";
    }
  }
#endif  // BUILDFLAG(SKIA_USE_DAWN)
  if (features::IsUsingVulkan()) {
    return GrContextType::kVulkan;
  }
  return GrContextType::kGL;
}

bool MSAAIsSlow(const GpuDriverBugWorkarounds& workarounds) {
  // Only query the kEnableMSAAOnNewIntelGPUs feature flag if the host device
  // is affected by the experiment (i.e. is a new Intel GPU).
  // This is to avoid activating the experiment on hosts that are irrelevant
  // to the study in order to boost statistical power.
  bool affected_by_experiment =
      workarounds.msaa_is_slow && !workarounds.msaa_is_slow_2;

  return affected_by_experiment ? !base::FeatureList::IsEnabled(
                                      features::kEnableMSAAOnNewIntelGPUs)
                                : workarounds.msaa_is_slow;
}

}  // namespace gles2

namespace {

// Multiplier policy for kAggressiveShaderCacheLimits enabled.
double GetAggressiveMemoryLimitMultiplier(int memory_limit) {
#if BUILDFLAG(IS_ANDROID)
  // Android ignores pressure notifications in aggressive mode.
  return 1.0;
#else
  // Desktop ignores pressure above the Moderate threshold (50%).
  if (memory_limit >= base::kModerateMemoryPressureThreshold) {
    return 1.0;
  }
  // Interpolate multiplier from 1.0 down to 0.25 between Moderate (50%) and
  // Critical (0%).
  double t = static_cast<double>(base::kModerateMemoryPressureThreshold -
                                 memory_limit) /
             base::kModerateMemoryPressureThreshold;
  return std::lerp(1.0, 0.25, t);
#endif
}

// Multiplier policy for kAggressiveShaderCacheLimits disabled.
double GetDefaultMemoryLimitMultiplier(int memory_limit) {
  // Scale quadratically (e.g., 25% cache size at 50% memory limit).
  double ratio =
      static_cast<double>(memory_limit) / base::kNoMemoryPressureThreshold;
  return ratio * ratio;
}

}  // namespace

size_t UpdateShaderCacheSizeOnMemoryPressure(
    size_t max_cache_size,
    base::MemoryPressureLevel memory_pressure_level) {
  int memory_limit = base::kNoMemoryPressureThreshold;
  switch (memory_pressure_level) {
    case base::MEMORY_PRESSURE_LEVEL_NONE:
      memory_limit = base::kNoMemoryPressureThreshold;
      break;
    case base::MEMORY_PRESSURE_LEVEL_MODERATE:
      memory_limit = base::kModerateMemoryPressureThreshold;
      break;
    case base::MEMORY_PRESSURE_LEVEL_CRITICAL:
      memory_limit = base::kCriticalMemoryPressureThreshold;
      break;
  }
  return UpdateShaderCacheSizeOnMemoryLimit(max_cache_size, memory_limit);
}

size_t UpdateShaderCacheSizeOnMemoryLimit(size_t max_cache_size,
                                          int memory_limit) {
  CHECK_GE(memory_limit, 0);

  // Handle limits greater than 100%. Scales linearly.
  if (memory_limit > base::kNoMemoryPressureThreshold) {
    return base::ScaleByMemoryLimit(max_cache_size, memory_limit);
  }

  double multiplier =
      base::FeatureList::IsEnabled(features::kAggressiveShaderCacheLimits)
          ? GetAggressiveMemoryLimitMultiplier(memory_limit)
          : GetDefaultMemoryLimitMultiplier(memory_limit);

  return static_cast<size_t>(max_cache_size * multiplier);
}

}  // namespace gpu
