// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/gpu/compositor_util.h"

#include <stddef.h>

#include <memory>
#include <utility>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/strings/string_number_conversions.h"
#include "base/sys_info.h"
#include "build/build_config.h"
#include "cc/base/math_util.h"
#include "cc/base/switches.h"
#include "content/browser/gpu/browser_gpu_memory_buffer_manager.h"
#include "content/browser/gpu/gpu_data_manager_impl.h"
#include "content/public/browser/gpu_utils.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "gpu/config/gpu_feature_type.h"
#include "ui/gl/gl_switches.h"

namespace content {

namespace {

static bool IsGpuRasterizationBlacklisted() {
  GpuDataManagerImpl* manager = GpuDataManagerImpl::GetInstance();
  return manager->IsFeatureBlacklisted(
        gpu::GPU_FEATURE_TYPE_GPU_RASTERIZATION);
}

const char kGpuCompositingFeatureName[] = "gpu_compositing";
const char kWebGLFeatureName[] = "webgl";
const char kRasterizationFeatureName[] = "rasterization";
const char kMultipleRasterThreadsFeatureName[] = "multiple_raster_threads";
const char kNativeGpuMemoryBuffersFeatureName[] = "native_gpu_memory_buffers";
const char kWebGL2FeatureName[] = "webgl2";

const int kMinRasterThreads = 1;
const int kMaxRasterThreads = 4;

const int kMinMSAASampleCount = 0;

struct GpuFeatureInfo {
  std::string name;
  bool blocked;
  bool disabled;
  std::string disabled_description;
  bool fallback_to_software;
};

const GpuFeatureInfo GetGpuFeatureInfo(size_t index, bool* eof) {
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();
  GpuDataManagerImpl* manager = GpuDataManagerImpl::GetInstance();
  gpu::GpuPreferences gpu_preferences = GetGpuPreferencesFromCommandLine();

  bool accelerated_vpx_disabled =
      command_line.HasSwitch(switches::kDisableAcceleratedVideoDecode);
#if defined(OS_WIN)
  accelerated_vpx_disabled |= !gpu_preferences.enable_accelerated_vpx_decode;
#endif

  const GpuFeatureInfo kGpuFeatureInfo[] = {
    {"2d_canvas",
     manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS),
     command_line.HasSwitch(switches::kDisableAccelerated2dCanvas),
     "Accelerated 2D canvas is unavailable: either disabled via blacklist or"
     " the command line.",
     true},
    {kGpuCompositingFeatureName,
     manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_GPU_COMPOSITING),
     command_line.HasSwitch(switches::kDisableGpuCompositing),
     "Gpu compositing has been disabled, either via blacklist, about:flags"
     " or the command line. The browser will fall back to software compositing"
     " and hardware acceleration will be unavailable.",
     true},
    {kWebGLFeatureName,
     manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_WEBGL),
     command_line.HasSwitch(switches::kDisableExperimentalWebGL),
     "WebGL has been disabled via blacklist or the command line.", false},
    {"flash_3d", manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_FLASH3D),
     command_line.HasSwitch(switches::kDisableFlash3d),
     "Using 3d in flash has been disabled, either via blacklist, about:flags or"
     " the command line.",
     true},
    {"flash_stage3d",
     manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_FLASH_STAGE3D),
     command_line.HasSwitch(switches::kDisableFlashStage3d),
     "Using Stage3d in Flash has been disabled, either via blacklist,"
     " about:flags or the command line.",
     true},
    {"flash_stage3d_baseline",
     manager->IsFeatureBlacklisted(
         gpu::GPU_FEATURE_TYPE_FLASH_STAGE3D_BASELINE) ||
         manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_FLASH_STAGE3D),
     command_line.HasSwitch(switches::kDisableFlashStage3d),
     "Using Stage3d Baseline profile in Flash has been disabled, either"
     " via blacklist, about:flags or the command line.",
     true},
    {"video_decode", manager->IsFeatureBlacklisted(
                         gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE),
     command_line.HasSwitch(switches::kDisableAcceleratedVideoDecode),
     "Accelerated video decode has been disabled, either via blacklist,"
     " about:flags or the command line.",
     true},
#if defined(ENABLE_WEBRTC)
    {"video_encode", manager->IsFeatureBlacklisted(
                         gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_ENCODE),
     command_line.HasSwitch(switches::kDisableWebRtcHWEncoding),
     "Accelerated video encode has been disabled, either via blacklist,"
     " about:flags or the command line.",
     true},
#endif
#if defined(OS_CHROMEOS)
    {"panel_fitting",
     manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_PANEL_FITTING),
     command_line.HasSwitch(switches::kDisablePanelFitting),
     "Panel fitting has been disabled, either via blacklist, about:flags or"
     " the command line.",
     false},
#endif
    {kRasterizationFeatureName,
     IsGpuRasterizationBlacklisted() && !IsGpuRasterizationEnabled() &&
         !IsForceGpuRasterizationEnabled(),
     !IsGpuRasterizationEnabled() && !IsForceGpuRasterizationEnabled() &&
         !IsGpuRasterizationBlacklisted(),
     "Accelerated rasterization has been disabled, either via blacklist,"
     " about:flags or the command line.",
     true},
    {kMultipleRasterThreadsFeatureName, false,
     NumberOfRendererRasterThreads() == 1, "Raster is using a single thread.",
     false},
    {kNativeGpuMemoryBuffersFeatureName, false,
     !BrowserGpuMemoryBufferManager::IsNativeGpuMemoryBuffersEnabled(),
     "Native GpuMemoryBuffers have been disabled, either via about:flags"
     " or command line.",
     true},
    {"vpx_decode",
     manager->IsFeatureBlacklisted(
         gpu::GPU_FEATURE_TYPE_ACCELERATED_VPX_DECODE) ||
     manager->IsFeatureBlacklisted(
         gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE),
     accelerated_vpx_disabled,
     "Accelerated VPx video decode has been disabled, either via blacklist"
     " or the command line.",
     true},
    {kWebGL2FeatureName,
     manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_WEBGL2),
     command_line.HasSwitch(switches::kDisableES3APIs),
     "WebGL2 has been disabled via blacklist or the command line.",
     false},
  };
  DCHECK(index < arraysize(kGpuFeatureInfo));
  *eof = (index == arraysize(kGpuFeatureInfo) - 1);
  return kGpuFeatureInfo[index];
}

}  // namespace

int NumberOfRendererRasterThreads() {
  int num_processors = base::SysInfo::NumberOfProcessors();

#if defined(OS_ANDROID) || \
    (defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY))
  // Android and ChromeOS ARM devices may report 6 to 8 CPUs for big.LITTLE
  // configurations. Limit the number of raster threads based on maximum of
  // 4 big cores.
  num_processors = std::min(num_processors, 4);
#endif

  int num_raster_threads = num_processors / 2;

#if defined(OS_ANDROID)
  // Limit the number of raster threads to 1 on Android.
  // TODO(reveman): Remove this when we have a better mechanims to prevent
  // pre-paint raster work from slowing down non-raster work. crbug.com/504515
  num_raster_threads = 1;
#endif

  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();

  if (command_line.HasSwitch(switches::kNumRasterThreads)) {
    std::string string_value = command_line.GetSwitchValueASCII(
        switches::kNumRasterThreads);
    if (!base::StringToInt(string_value, &num_raster_threads)) {
      DLOG(WARNING) << "Failed to parse switch " <<
          switches::kNumRasterThreads  << ": " << string_value;
    }
  }

  return cc::MathUtil::ClampToRange(num_raster_threads, kMinRasterThreads,
                                    kMaxRasterThreads);
}

bool IsZeroCopyUploadEnabled() {
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();
#if defined(OS_MACOSX)
  return !command_line.HasSwitch(switches::kDisableZeroCopy);
#else
  return command_line.HasSwitch(switches::kEnableZeroCopy);
#endif
}

bool IsPartialRasterEnabled() {
  const auto& command_line = *base::CommandLine::ForCurrentProcess();
  return !command_line.HasSwitch(switches::kDisablePartialRaster);
}

bool IsGpuMemoryBufferCompositorResourcesEnabled() {
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();
  if (command_line.HasSwitch(
          switches::kEnableGpuMemoryBufferCompositorResources)) {
    return true;
  }
  if (command_line.HasSwitch(
          switches::kDisableGpuMemoryBufferCompositorResources)) {
    return false;
  }

  // Native GPU memory buffers are required.
  if (!BrowserGpuMemoryBufferManager::IsNativeGpuMemoryBuffersEnabled())
    return false;

#if defined(OS_MACOSX)
  return true;
#else
  return false;
#endif
}

bool IsGpuRasterizationEnabled() {
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();

  if (command_line.HasSwitch(switches::kDisableGpuRasterization))
    return false;
  else if (command_line.HasSwitch(switches::kEnableGpuRasterization))
    return true;

  if (IsGpuRasterizationBlacklisted()) {
    return false;
  }

  // Gpu Rasterization on platforms that are not fully enabled is controlled by
  // a finch experiment.
  return base::FeatureList::IsEnabled(features::kDefaultEnableGpuRasterization);
}

bool IsAsyncWorkerContextEnabled() {
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();

  if (command_line.HasSwitch(switches::kDisableGpuAsyncWorkerContext))
    return false;
  else if (command_line.HasSwitch(switches::kEnableGpuAsyncWorkerContext))
    return true;

  return false;
}

bool IsForceGpuRasterizationEnabled() {
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();
  return command_line.HasSwitch(switches::kForceGpuRasterization);
}

int GpuRasterizationMSAASampleCount() {
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();

  if (!command_line.HasSwitch(switches::kGpuRasterizationMSAASampleCount))
#if defined(OS_ANDROID)
    return 4;
#else
    // Desktop platforms will compute this automatically based on DPI.
    return -1;
#endif
  std::string string_value = command_line.GetSwitchValueASCII(
      switches::kGpuRasterizationMSAASampleCount);
  int msaa_sample_count = 0;
  if (base::StringToInt(string_value, &msaa_sample_count) &&
      msaa_sample_count >= kMinMSAASampleCount) {
    return msaa_sample_count;
  } else {
    DLOG(WARNING) << "Failed to parse switch "
                  << switches::kGpuRasterizationMSAASampleCount << ": "
                  << string_value;
    return 0;
  }
}

bool IsMainFrameBeforeActivationEnabled() {
  if (base::SysInfo::NumberOfProcessors() < 4)
    return false;

  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();

  if (command_line.HasSwitch(cc::switches::kDisableMainFrameBeforeActivation))
    return false;

  if (command_line.HasSwitch(cc::switches::kEnableMainFrameBeforeActivation))
    return true;

  return true;
}

base::DictionaryValue* GetFeatureStatus() {
  GpuDataManagerImpl* manager = GpuDataManagerImpl::GetInstance();
  std::string gpu_access_blocked_reason;
  bool gpu_access_blocked =
      !manager->GpuAccessAllowed(&gpu_access_blocked_reason);

  base::DictionaryValue* feature_status_dict = new base::DictionaryValue();

  bool eof = false;
  for (size_t i = 0; !eof; ++i) {
    const GpuFeatureInfo gpu_feature_info = GetGpuFeatureInfo(i, &eof);
    std::string status;
    if (gpu_feature_info.disabled) {
      status = "disabled";
      if (gpu_feature_info.fallback_to_software)
        status += "_software";
      else
        status += "_off";
    } else if (gpu_feature_info.blocked ||
               gpu_access_blocked) {
      status = "unavailable";
      if (gpu_feature_info.fallback_to_software)
        status += "_software";
      else
        status += "_off";
    } else {
      status = "enabled";
      if (gpu_feature_info.name == kWebGLFeatureName &&
          manager->IsFeatureBlacklisted(gpu::GPU_FEATURE_TYPE_GPU_COMPOSITING))
        status += "_readback";
      if (gpu_feature_info.name == kRasterizationFeatureName) {
        if (IsForceGpuRasterizationEnabled())
          status += "_force";
      }
      if (gpu_feature_info.name == kMultipleRasterThreadsFeatureName) {
        const base::CommandLine& command_line =
            *base::CommandLine::ForCurrentProcess();
        if (command_line.HasSwitch(switches::kNumRasterThreads))
          status += "_force";
      }
      if (gpu_feature_info.name == kMultipleRasterThreadsFeatureName)
        status += "_on";
    }
    if (gpu_feature_info.name == kWebGLFeatureName &&
        (gpu_feature_info.blocked || gpu_access_blocked) &&
        manager->ShouldUseSwiftShader()) {
      status = "unavailable_software";
    }

    feature_status_dict->SetString(
        gpu_feature_info.name.c_str(), status.c_str());
  }
  return feature_status_dict;
}

base::Value* GetProblems() {
  GpuDataManagerImpl* manager = GpuDataManagerImpl::GetInstance();
  std::string gpu_access_blocked_reason;
  bool gpu_access_blocked =
      !manager->GpuAccessAllowed(&gpu_access_blocked_reason);

  base::ListValue* problem_list = new base::ListValue();
  manager->GetBlacklistReasons(problem_list);

  if (gpu_access_blocked) {
    auto problem = base::MakeUnique<base::DictionaryValue>();
    problem->SetString("description",
        "GPU process was unable to boot: " + gpu_access_blocked_reason);
    problem->Set("crBugs", new base::ListValue());
    problem->Set("webkitBugs", new base::ListValue());
    base::ListValue* disabled_features = new base::ListValue();
    disabled_features->AppendString("all");
    problem->Set("affectedGpuSettings", disabled_features);
    problem->SetString("tag", "disabledFeatures");
    problem_list->Insert(0, std::move(problem));
  }

  bool eof = false;
  for (size_t i = 0; !eof; ++i) {
    const GpuFeatureInfo gpu_feature_info = GetGpuFeatureInfo(i, &eof);
    if (gpu_feature_info.disabled) {
      std::unique_ptr<base::DictionaryValue> problem(
          new base::DictionaryValue());
      problem->SetString(
          "description", gpu_feature_info.disabled_description);
      problem->Set("crBugs", new base::ListValue());
      problem->Set("webkitBugs", new base::ListValue());
      base::ListValue* disabled_features = new base::ListValue();
      disabled_features->AppendString(gpu_feature_info.name);
      problem->Set("affectedGpuSettings", disabled_features);
      problem->SetString("tag", "disabledFeatures");
      problem_list->Append(std::move(problem));
    }
  }
  return problem_list;
}

std::vector<std::string> GetDriverBugWorkarounds() {
  return GpuDataManagerImpl::GetInstance()->GetDriverBugWorkarounds();
}

}  // namespace content
