// 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.

#ifndef GPU_CONFIG_GPU_CONTROL_LIST_H_
#define GPU_CONFIG_GPU_CONTROL_LIST_H_

#include <stddef.h>

#include <set>
#include <string>
#include <unordered_map>
#include <vector>

#include "base/containers/span.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/raw_span.h"
#include "base/values.h"
#include "gpu/config/gpu_config_export.h"
#include "gpu/config/gpu_info.h"

namespace gpu {
struct GPUInfo;

class GPU_CONFIG_EXPORT GpuControlList {
 public:
  typedef std::unordered_map<int, std::string> FeatureMap;

  enum OsType {
    kOsLinux,
    kOsMacosx,
    kOsWin,
    kOsChromeOS,
    kOsAndroid,
    kOsFuchsia,
    kOsIOS,
    kOsOHOS,
    kOsAny
  };

  enum OsFilter {
    // In loading, ignore all entries that belong to other OS.
    kCurrentOsOnly,
    // In loading, keep all entries. This is for testing only.
    kAllOs
  };

  enum NumericOp {
    kBetween,  // <= * <=
    kEQ,  // =
    kLT,  // <
    kLE,  // <=
    kGT,  // >
    kGE,  // >=
    kAny,
    kUnknown  // Indicates the data is invalid.
  };

  enum MultiGpuStyle {
    kMultiGpuStyleOptimus,
    kMultiGpuStyleAMDSwitchable,
    kMultiGpuStyleAMDSwitchableIntegrated,
    kMultiGpuStyleAMDSwitchableDiscrete,
    kMultiGpuStyleNone
  };

  enum MultiGpuCategory {
    // This entry applies if this is the primary GPU on the system.
    kMultiGpuCategoryPrimary,
    // This entry applies if this is a secondary GPU on the system.
    kMultiGpuCategorySecondary,
    // This entry applies if this is the NPU on the system.
    kMultiGpuCategoryNpu,
    // This entry applies if this is the active GPU on the system.
    kMultiGpuCategoryActive,
    // This entry applies if this is any of the GPUs on the system.
    kMultiGpuCategoryAny,
    kMultiGpuCategoryNone
  };

  enum GLType {
    kGLTypeGLES,
    kGLTypeANGLE_GL,
    kGLTypeANGLE_GLES,
    kGLTypeANGLE_VULKAN,
    kGLTypeNone
  };

  enum VersionStyle {
    kVersionStyleNumerical,
    kVersionStyleLexical,
    kVersionStyleUnknown
  };

  enum VersionSchema {
    // All digits are meaningful when distinguishing versions.
    kVersionSchemaCommon,
    // The version format of Intel graphics driver is AA.BB.CCC.DDDD.
    // DDDD(old schema) or CCC.DDDD(new schema) is the build number.
    // That is, indicates the actual driver number.
    kVersionSchemaIntelDriver,
    // The version format of Nvidia drivers is XX.XX.XXXA.AAAA where the X's
    // can be any digits, and the A's are the actual version.  The workaround
    // list specifies them as AAA.AA to match how Nvidia publishes them.
    kVersionSchemaNvidiaDriver,
  };

  enum SupportedOrNot {
    kSupported,
    kUnsupported,
    kDontCare,
  };

  struct GPU_CONFIG_EXPORT Version {
    NumericOp op;
    VersionStyle style;
    VersionSchema schema;
    const char* value1;
    const char* value2;

    bool IsSpecified() const { return op != kUnknown; }

    bool Contains(const std::string& version_string, char splitter) const;

    bool Contains(const std::string& version_string) const {
      return Contains(version_string, '.');
    }

    // Compare two version strings.
    // Return 1 if version > version_ref,
    //        0 if version = version_ref,
    //       -1 if version < version_ref.
    // Note that we only compare as many segments as both versions contain.
    // For example: Compare("10.3.1", "10.3") returns 0,
    //              Compare("10.3", "10.3.1") returns 0.
    // If "version_style" is Lexical, the first segment is compared
    // numerically, all other segments are compared lexically.
    // Lexical is used for AMD Linux driver versions only.
    static int Compare(const std::vector<std::string>& version,
                       const std::vector<std::string>& version_ref,
                       VersionStyle version_style);
  };

  struct GPU_CONFIG_EXPORT DriverInfo {
    const char* driver_vendor;
    Version driver_version;

    bool Contains(const std::vector<GPUInfo::GPUDevice>& gpus) const;
  };

  struct GPU_CONFIG_EXPORT GLStrings {
    const char* gl_vendor;
    const char* gl_renderer;
    const char* gl_extensions;
    const char* gl_version;

    bool Contains(const GPUInfo& gpu_info) const;
  };

  struct GPU_CONFIG_EXPORT MachineModelInfo {
    base::raw_span<const char* const> machine_model_names;
    Version machine_model_version;

    bool Contains(const GPUInfo& gpu_info) const;
  };

  struct GPU_CONFIG_EXPORT More {
    // These are just part of Entry fields that are less common.
    // Putting them to a separate struct to save Entry data size.
    GLType gl_type;
    Version gl_version;
    Version pixel_shader_version;
    Version d3d11_feature_level;
    bool in_process_gpu;
    uint32_t gl_reset_notification_strategy;
    Version direct_rendering_version;
    Version gpu_count;
    SupportedOrNot hardware_overlay;

    uint32_t test_group;

    SupportedOrNot subpixel_font_rendering;

    // Return true if GL_VERSION string does not fit the entry info
    // on GL version.
    bool GLVersionInfoMismatch(const std::string& gl_version_string) const;

    bool Contains(const GPUInfo& gpu_info) const;
  };

  struct GPU_CONFIG_EXPORT Device {
    uint32_t device_id;
    uint32_t revision = 0u;
  };

  struct GPU_CONFIG_EXPORT IntelConditions {
    base::raw_span<const IntelGpuSeriesType> intel_gpu_series_list;
    Version intel_gpu_generation;

    bool Contains(const std::vector<GPUInfo::GPUDevice>& candidates,
                  const GPUInfo& gpu_info) const;
  };

  struct GPU_CONFIG_EXPORT Conditions {
    OsType os_type;
    Version os_version;
    uint32_t vendor_id;
    // TODO(367764863) Rewrite to base::raw_span.
    RAW_PTR_EXCLUSION base::span<const Device> devices;
    MultiGpuCategory multi_gpu_category;
    MultiGpuStyle multi_gpu_style;
    // RAW_PTR_EXCLUSION: since these pointers only ever point to other
    // globals, and `Conditions` itself is used to construct globals, using
    // raw_ptr would add additional (unnecessary) complexity with
    // `NoDestructor`.
    RAW_PTR_EXCLUSION const DriverInfo* driver_info;
    RAW_PTR_EXCLUSION const GLStrings* gl_strings;
    RAW_PTR_EXCLUSION const MachineModelInfo* machine_model_info;
    RAW_PTR_EXCLUSION const IntelConditions* intel_conditions;
    RAW_PTR_EXCLUSION const More* more;

    Conditions(OsType os_type,
               Version os_version,
               uint32_t vendor_id,
               base::span<const Device> devices,
               MultiGpuCategory multi_gpu_category,
               MultiGpuStyle multi_gpu_style,
               const DriverInfo* driver_info,
               const GLStrings* gl_strings,
               const MachineModelInfo* machine_model_info,
               const IntelConditions* intel_conditions,
               const More* more);
    Conditions(const Conditions& other);

    bool Contains(OsType os_type,
                  const std::string& os_version,
                  const GPUInfo& gpu_info) const;

    // Determines whether we needs more gpu info to make the blocklisting
    // decision.  It should only be checked if Contains() returns true.
    bool NeedsMoreInfo(const GPUInfo& gpu_info) const;
  };

  struct GPU_CONFIG_EXPORT Entry {
    uint32_t id;
    const char* description;
    // `Entry` is used extensively in
    // `gen/gpu/config/software_rendering_list_autogen.cc`, where making these
    // `raw_span` would cause a warning:
    //
    // > declaration requires an exit-time destructor
    //
    // These only seem to be set in files generated by
    // gpu/config/process_json.py, which only ever sets them to point at
    // statically-allocated data which is never freed. Thus these can never
    // dangle, so making them raw_span is unnecessary.
    RAW_PTR_EXCLUSION base::span<const int> features;
    RAW_PTR_EXCLUSION base::span<const char* const> disabled_extensions;
    RAW_PTR_EXCLUSION base::span<const char* const> disabled_webgl_extensions;
    RAW_PTR_EXCLUSION base::span<const uint32_t> cr_bugs;
    Conditions conditions;
    RAW_PTR_EXCLUSION base::span<const Conditions> exceptions;

    bool Contains(OsType os_type,
                  const std::string& os_version,
                  const GPUInfo& gpu_info) const;

    bool AppliesToTestGroup(uint32_t target_test_group) const;

    // Determines whether we needs more gpu info to make the blocklisting
    // decision.  It should only be checked if Contains() returns true.
    bool NeedsMoreInfo(const GPUInfo& gpu_info, bool consider_exceptions) const;

    base::Value::List GetFeatureNames(const FeatureMap& feature_map) const;

    // Logs a control list match for this rule in the list identified by
    // |control_list_logging_name|.
    void LogControlListMatch(
        const std::string& control_list_logging_name) const;
  };

  explicit GpuControlList(base::span<const GpuControlList::Entry> data);
  virtual ~GpuControlList();

  // Collects system information and combines them with gpu_info and control
  // list information to decide which entries are applied to the current
  // system and returns the union of features specified in each entry.
  // If os is kOsAny, use the current OS; if os_version is empty, use the
  // current OS version.
  std::set<int32_t> MakeDecision(OsType os,
                                 const std::string& os_version,
                                 const GPUInfo& gpu_info);
  // Same as the above function, but instead of using the entries with no
  // "test_group" specified or "test_group" = 0, using the entries with
  // "test_group" = |target_test_group|.
  std::set<int32_t> MakeDecision(OsType os,
                                 const std::string& os_version,
                                 const GPUInfo& gpu_info,
                                 uint32_t target_test_group);

  // Return the active entry indices from the last MakeDecision() call.
  const std::vector<uint32_t>& GetActiveEntries() const;
  // Return corresponding entry IDs from entry indices.
  std::vector<uint32_t> GetEntryIDsFromIndices(
      const std::vector<uint32_t>& entry_indices) const;

  // Collects all disabled extensions.
  std::vector<std::string> GetDisabledExtensions();
  // Collects all disabled WebGL extensions.
  std::vector<std::string> GetDisabledWebGLExtensions();

  // Returns the description and bugs from active entries provided.
  // Each problems has:
  // {
  //    "description": "Your GPU is too old",
  //    "crBugs": [1234],
  // }
  // The use case is we compute the entries from GPU process and send them to
  // browser process, and call GetReasons() in browser process.
  void GetReasons(base::Value::List& problem_list,
                  const std::string& tag,
                  const std::vector<uint32_t>& entries) const;

  // Return the largest entry id.  This is used for histogramming.
  uint32_t max_entry_id() const;

  // Check if we need more gpu info to make the decisions.
  // This is computed from the last MakeDecision() call.
  // If yes, we should create a gl context and do a full gpu info collection.
  bool needs_more_info() const { return needs_more_info_; }

  // Returns the number of entries.  This is only for tests.
  size_t num_entries() const;

  // Register a feature to FeatureMap.
  void AddSupportedFeature(const std::string& feature_name, int feature_id);

  // Enables logging of control list decisions.
  void EnableControlListLogging(const std::string& control_list_logging_name) {
    control_list_logging_enabled_ = true;
    control_list_logging_name_ = control_list_logging_name;
  }

 protected:
  // Return false if an entry index goes beyond |total_entries|.
  static bool AreEntryIndicesValid(const std::vector<uint32_t>& entry_indices,
                                   size_t total_entries);

 private:
  // Returns kGLTypeNone if gl_renderer is empty.
  // Returns kGLTypeGLES if gl_renderer isn't in the format of ANGLE(_,_,_).
  // Returns kGLTypeANGLE_VULKAN or kGLTypeANGLE_GLES otherwise.
  static GLType ProcessANGLEGLRenderer(const std::string& gl_renderer,
                                       std::string* vendor = nullptr,
                                       std::string* renderer = nullptr,
                                       std::string* version = nullptr);

  friend class GpuControlListEntryTest;
  friend class VersionInfoTest;
  friend class GpuControlListTest;

  // Gets the current OS type.
  static OsType GetOsType();

  // These always point to built-in arrays of constants, so raw_ptr doesn't
  // add any protection but costs some overhead.
  base::raw_span<const Entry> entries_;

  // This records all the entries that are applicable to the current user
  // machine.  It is updated everytime MakeDecision() is called and is used
  // later by GetDecisionEntries().
  std::vector<uint32_t> active_entries_;

  uint32_t max_entry_id_ = 0;

  bool needs_more_info_ = false;

  // The features a GpuControlList recognizes and handles.
  FeatureMap feature_map_;

  bool control_list_logging_enabled_ = false;
  std::string control_list_logging_name_;
};

}  // namespace gpu

#endif  // GPU_CONFIG_GPU_CONTROL_LIST_H_