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

#if defined(OS_MACOSX)
#include <CoreGraphics/CGDisplayConfiguration.h>
#endif

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/string_number_conversions.h"
#include "base/stringprintf.h"
#include "base/sys_info.h"
#include "base/values.h"
#include "base/version.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/common/gpu/gpu_messages.h"
#include "content/gpu/gpu_info_collector.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/gpu_data_manager_observer.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "ui/gfx/gl/gl_implementation.h"
#include "ui/gfx/gl/gl_switches.h"
#include "webkit/plugins/plugin_switches.h"

using content::BrowserThread;
using content::GpuDataManagerObserver;
using content::GpuFeatureType;

namespace {

#if defined(OS_MACOSX)
void DisplayReconfigCallback(CGDirectDisplayID display,
                             CGDisplayChangeSummaryFlags flags,
                             void* gpu_data_manager) {
  // TODO(zmo): this logging is temporary for crbug 88008 and will be removed.
  LOG(INFO) << "Display re-configuration: flags = 0x"
            << base::StringPrintf("%04x", flags);
  if (flags & kCGDisplayAddFlag) {
    GpuDataManagerImpl* manager =
        reinterpret_cast<GpuDataManagerImpl*>(gpu_data_manager);
    DCHECK(manager);
    manager->HandleGpuSwitch();
  }
}
#endif

}  // namespace anonymous

// static
content::GpuDataManager* content::GpuDataManager::GetInstance() {
  return GpuDataManagerImpl::GetInstance();
}

// static
GpuDataManagerImpl* GpuDataManagerImpl::GetInstance() {
  return Singleton<GpuDataManagerImpl>::get();
}

GpuDataManagerImpl::GpuDataManagerImpl()
    : complete_gpu_info_already_requested_(false),
      complete_gpu_info_available_(false),
      gpu_feature_type_(content::GPU_FEATURE_TYPE_UNKNOWN),
      preliminary_gpu_feature_type_(content::GPU_FEATURE_TYPE_UNKNOWN),
      observer_list_(new GpuDataManagerObserverList),
      software_rendering_(false),
      card_blacklisted_(false) {
  Initialize();
}

void GpuDataManagerImpl::Initialize() {
  CommandLine* command_line = CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kDisableAcceleratedCompositing)) {
    command_line->AppendSwitch(switches::kDisableAccelerated2dCanvas);
    command_line->AppendSwitch(switches::kDisableAcceleratedLayers);
  }

  if (!command_line->HasSwitch(switches::kSkipGpuDataLoading)) {
    content::GPUInfo gpu_info;
    gpu_info_collector::CollectPreliminaryGraphicsInfo(&gpu_info);
    {
      base::AutoLock auto_lock(gpu_info_lock_);
      gpu_info_ = gpu_info;
    }
  }

#if defined(OS_MACOSX)
  CGDisplayRegisterReconfigurationCallback(DisplayReconfigCallback, this);
#endif
}

GpuDataManagerImpl::~GpuDataManagerImpl() {
#if defined(OS_MACOSX)
  CGDisplayRemoveReconfigurationCallback(DisplayReconfigCallback, this);
#endif
}

void GpuDataManagerImpl::RequestCompleteGpuInfoIfNeeded() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  if (complete_gpu_info_already_requested_ || complete_gpu_info_available_)
    return;
  complete_gpu_info_already_requested_ = true;

  GpuProcessHost::SendOnIO(
      0,
      content::CAUSE_FOR_GPU_LAUNCH_GPUDATAMANAGER_REQUESTCOMPLETEGPUINFOIFNEEDED,
      new GpuMsg_CollectGraphicsInfo());
}

bool GpuDataManagerImpl::IsCompleteGPUInfoAvailable() const {
  return complete_gpu_info_available_;
}

void GpuDataManagerImpl::UpdateGpuInfo(const content::GPUInfo& gpu_info) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  complete_gpu_info_available_ =
      complete_gpu_info_available_ || gpu_info.finalized;
  complete_gpu_info_already_requested_ =
      complete_gpu_info_already_requested_ || gpu_info.finalized;
  {
    base::AutoLock auto_lock(gpu_info_lock_);
    if (!Merge(&gpu_info_, gpu_info))
      return;
    content::GetContentClient()->SetGpuInfo(gpu_info_);
  }

  // We have to update GpuFeatureType before notify all the observers.
  NotifyGpuInfoUpdate();
}

content::GPUInfo GpuDataManagerImpl::GetGPUInfo() const {
  return gpu_info_;
}

void GpuDataManagerImpl::AddLogMessage(Value* msg) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  log_messages_.Append(msg);
}

GpuFeatureType GpuDataManagerImpl::GetGpuFeatureType() {
  if (software_rendering_) {
    GpuFeatureType flags;

    // Skia's software rendering is probably more efficient than going through
    // software emulation of the GPU, so use that.
    flags = content::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS;
    return flags;
  }

  return gpu_feature_type_;
}

bool GpuDataManagerImpl::GpuAccessAllowed() {
  if (software_rendering_)
    return true;

  // We only need to block GPU process if more features are disallowed other
  // than those in the preliminary gpu feature flags because the latter work
  // through renderer commandline switches.
  uint32 mask = ~(preliminary_gpu_feature_type_);
  return (gpu_feature_type_ & mask) == 0;
}

void GpuDataManagerImpl::AddObserver(GpuDataManagerObserver* observer) {
  observer_list_->AddObserver(observer);
}

void GpuDataManagerImpl::RemoveObserver(GpuDataManagerObserver* observer) {
  observer_list_->RemoveObserver(observer);
}

void GpuDataManagerImpl::AppendRendererCommandLine(
    CommandLine* command_line) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(command_line);

  uint32 flags = GetGpuFeatureType();
  if ((flags & content::GPU_FEATURE_TYPE_WEBGL)) {
    if (!command_line->HasSwitch(switches::kDisableExperimentalWebGL))
      command_line->AppendSwitch(switches::kDisableExperimentalWebGL);
    if (!command_line->HasSwitch(switches::kDisablePepper3dForUntrustedUse))
      command_line->AppendSwitch(switches::kDisablePepper3dForUntrustedUse);
  }
  if ((flags & content::GPU_FEATURE_TYPE_MULTISAMPLING) &&
      !command_line->HasSwitch(switches::kDisableGLMultisampling))
    command_line->AppendSwitch(switches::kDisableGLMultisampling);
  if ((flags & content::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING) &&
      !command_line->HasSwitch(switches::kDisableAcceleratedCompositing))
    command_line->AppendSwitch(switches::kDisableAcceleratedCompositing);
  if ((flags & content::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS) &&
      !command_line->HasSwitch(switches::kDisableAccelerated2dCanvas))
    command_line->AppendSwitch(switches::kDisableAccelerated2dCanvas);
}

void GpuDataManagerImpl::AppendGpuCommandLine(
    CommandLine* command_line) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(command_line);

  std::string use_gl =
      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(switches::kUseGL);
  uint32 flags = GetGpuFeatureType();
  if ((flags & content::GPU_FEATURE_TYPE_MULTISAMPLING) &&
      !command_line->HasSwitch(switches::kDisableGLMultisampling))
    command_line->AppendSwitch(switches::kDisableGLMultisampling);

  if (software_rendering_) {
    command_line->AppendSwitchASCII(switches::kUseGL, "swiftshader");
    command_line->AppendSwitchPath(switches::kSwiftShaderPath,
                                   swiftshader_path_);
  } else if ((flags & (content::GPU_FEATURE_TYPE_WEBGL |
                content::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING |
                content::GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS)) &&
      (use_gl == "any")) {
    command_line->AppendSwitchASCII(
        switches::kUseGL, gfx::kGLImplementationOSMesaName);
  } else if (!use_gl.empty()) {
    command_line->AppendSwitchASCII(switches::kUseGL, use_gl);
  }

  {
    base::AutoLock auto_lock(gpu_info_lock_);
    if (gpu_info_.optimus)
      command_line->AppendSwitch(switches::kReduceGpuSandbox);
  }
}

void GpuDataManagerImpl::SetGpuFeatureType(GpuFeatureType feature_type) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  UpdateGpuFeatureType(feature_type);
  preliminary_gpu_feature_type_ = gpu_feature_type_;
}

void GpuDataManagerImpl::HandleGpuSwitch() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  content::GPUInfo gpu_info;
  gpu_info_collector::CollectVideoCardInfo(&gpu_info);
  LOG(INFO) << "Switching to use GPU: vendor_id = 0x"
            << base::StringPrintf("%04x", gpu_info.vendor_id)
            << ", device_id = 0x"
            << base::StringPrintf("%04x", gpu_info.device_id);
  // TODO(zmo): update gpu_info_, re-run blacklist logic, maybe close and
  // relaunch GPU process.
}

void GpuDataManagerImpl::NotifyGpuInfoUpdate() {
  observer_list_->Notify(&GpuDataManagerObserver::OnGpuInfoUpdate);
}

void GpuDataManagerImpl::UpdateGpuFeatureType(
    GpuFeatureType embedder_feature_type) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  CommandLine* command_line = CommandLine::ForCurrentProcess();
  int flags = embedder_feature_type;

  // Force disable using the GPU for these features, even if they would
  // otherwise be allowed.
  if (card_blacklisted_ ||
      command_line->HasSwitch(switches::kBlacklistAcceleratedCompositing)) {
    flags |= content::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING;
  }
  if (card_blacklisted_ ||
      command_line->HasSwitch(switches::kBlacklistWebGL)) {
    flags |= content::GPU_FEATURE_TYPE_WEBGL;
  }
  gpu_feature_type_ = static_cast<GpuFeatureType>(flags);

  EnableSoftwareRenderingIfNecessary();
}

void GpuDataManagerImpl::RegisterSwiftShaderPath(FilePath path) {
  swiftshader_path_ = path;
  EnableSoftwareRenderingIfNecessary();
}

const base::ListValue& GpuDataManagerImpl::GetLogMessages() const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  return log_messages_;
}

void GpuDataManagerImpl::EnableSoftwareRenderingIfNecessary() {
  if (!GpuAccessAllowed() ||
      (gpu_feature_type_ & content::GPU_FEATURE_TYPE_WEBGL)) {
#if defined(ENABLE_SWIFTSHADER)
    if (!swiftshader_path_.empty() &&
        !CommandLine::ForCurrentProcess()->HasSwitch(
             switches::kDisableSoftwareRasterizer))
      software_rendering_ = true;
#endif
  }
}

bool GpuDataManagerImpl::ShouldUseSoftwareRendering() {
  return software_rendering_;
}

void GpuDataManagerImpl::BlacklistCard() {
  card_blacklisted_ = true;

  {
    base::AutoLock auto_lock(gpu_info_lock_);
    int flags = gpu_feature_type_;
    flags |= content::GPU_FEATURE_TYPE_ACCELERATED_COMPOSITING |
             content::GPU_FEATURE_TYPE_WEBGL;
    gpu_feature_type_ = static_cast<GpuFeatureType>(flags);
  }

  EnableSoftwareRenderingIfNecessary();
  NotifyGpuInfoUpdate();
}

bool GpuDataManagerImpl::Merge(content::GPUInfo* object,
                               const content::GPUInfo& other) {
  if (object->device_id != other.device_id ||
      object->vendor_id != other.vendor_id) {
    *object = other;
    return true;
  }

  bool changed = false;
  if (!object->finalized) {
    object->finalized = other.finalized;
    object->initialization_time = other.initialization_time;
    object->optimus |= other.optimus;

    if (object->driver_vendor.empty()) {
      changed |= object->driver_vendor != other.driver_vendor;
      object->driver_vendor = other.driver_vendor;
    }
    if (object->driver_version.empty()) {
      changed |= object->driver_version != other.driver_version;
      object->driver_version = other.driver_version;
    }
    if (object->driver_date.empty()) {
      changed |= object->driver_date != other.driver_date;
      object->driver_date = other.driver_date;
    }
    if (object->pixel_shader_version.empty()) {
      changed |= object->pixel_shader_version != other.pixel_shader_version;
      object->pixel_shader_version = other.pixel_shader_version;
    }
    if (object->vertex_shader_version.empty()) {
      changed |= object->vertex_shader_version != other.vertex_shader_version;
      object->vertex_shader_version = other.vertex_shader_version;
    }
    if (object->gl_version.empty()) {
      changed |= object->gl_version != other.gl_version;
      object->gl_version = other.gl_version;
    }
    if (object->gl_version_string.empty()) {
      changed |= object->gl_version_string != other.gl_version_string;
      object->gl_version_string = other.gl_version_string;
    }
    if (object->gl_vendor.empty()) {
      changed |= object->gl_vendor != other.gl_vendor;
      object->gl_vendor = other.gl_vendor;
    }
    if (object->gl_renderer.empty()) {
      changed |= object->gl_renderer != other.gl_renderer;
      object->gl_renderer = other.gl_renderer;
    }
    if (object->gl_extensions.empty()) {
      changed |= object->gl_extensions != other.gl_extensions;
      object->gl_extensions = other.gl_extensions;
    }
    object->can_lose_context = other.can_lose_context;
#if defined(OS_WIN)
    if (object->dx_diagnostics.values.size() == 0 &&
        object->dx_diagnostics.children.size() == 0) {
      object->dx_diagnostics = other.dx_diagnostics;
      changed = true;
    }
#endif
  }
  return changed;
}