// 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 "ui/gl/gpu_switching_manager.h"

#include "base/command_line.h"
#include "base/logging.h"
#include "build/build_config.h"
#include "ui/gl/gl_switches.h"

#if defined(OS_MACOSX)
#include <OpenGL/OpenGL.h>
#include "ui/gl/gl_context_cgl.h"
#endif  // OS_MACOSX

namespace ui {

struct GpuSwitchingManager::PlatformSpecific {
#if defined(OS_MACOSX)
  CGLPixelFormatObj discrete_pixel_format;
#endif  // OS_MACOSX
};

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

GpuSwitchingManager::GpuSwitchingManager()
    : gpu_switching_option_(gl::PreferIntegratedGpu),
      gpu_switching_option_set_(false),
      supports_dual_gpus_(false),
      supports_dual_gpus_set_(false),
      platform_specific_(new PlatformSpecific) {
#if defined(OS_MACOSX)
  platform_specific_->discrete_pixel_format = nullptr;
#endif  // OS_MACOSX
}

GpuSwitchingManager::~GpuSwitchingManager() {
#if defined(OS_MACOSX)
  if (platform_specific_->discrete_pixel_format)
    CGLReleasePixelFormat(platform_specific_->discrete_pixel_format);
#endif  // OS_MACOSX
}

void GpuSwitchingManager::ForceUseOfIntegratedGpu() {
  DCHECK(SupportsDualGpus());
  if (gpu_switching_option_set_) {
    DCHECK_EQ(gpu_switching_option_, gl::PreferIntegratedGpu);
  } else {
    gpu_switching_option_ = gl::PreferIntegratedGpu;
    gpu_switching_option_set_ = true;
  }
}

void GpuSwitchingManager::ForceUseOfDiscreteGpu() {
  DCHECK(SupportsDualGpus());
  if (gpu_switching_option_set_) {
    DCHECK_EQ(gpu_switching_option_, gl::PreferDiscreteGpu);
  } else {
    gpu_switching_option_ = gl::PreferDiscreteGpu;
    gpu_switching_option_set_ = true;
#if defined(OS_MACOSX)
    // Create a pixel format that lasts the lifespan of Chrome, so Chrome
    // stays on the discrete GPU.
    SwitchToDiscreteGpuMac();
#endif  // OS_MACOSX
  }
}

bool GpuSwitchingManager::SupportsDualGpus() {
  if (!supports_dual_gpus_set_) {
    const base::CommandLine& command_line =
        *base::CommandLine::ForCurrentProcess();
    bool flag = false;
    if (command_line.HasSwitch(switches::kSupportsDualGpus)) {
      // GPU process, flag is passed down from browser process.
      std::string flag_string = command_line.GetSwitchValueASCII(
          switches::kSupportsDualGpus);
      if (flag_string == "true") {
        flag = true;
      } else if (flag_string == "false") {
        flag = false;
      } else {
        NOTIMPLEMENTED();
      }
    } else {
      // Browser process.
      // We only compute this flag in the browser process.
#if defined(OS_MACOSX)
      flag = (vendor_ids_.size() == 2);
      if (flag && command_line.HasSwitch(switches::kUseGL) &&
          command_line.GetSwitchValueASCII(switches::kUseGL) !=
              gl::kGLImplementationDesktopName)
        flag = false;

      if (flag) {
        // Only advertise that we have two GPUs to the rest of
        // Chrome's code if we find an Intel GPU and some other
        // vendor's GPU. Otherwise we don't understand the
        // configuration and don't deal well with it (an example being
        // the dual AMD GPUs in recent Mac Pros).
        const uint32_t intel = 0x8086;
        flag = ((vendor_ids_[0] == intel && vendor_ids_[1] != intel) ||
                (vendor_ids_[0] != intel && vendor_ids_[1] == intel));
      }
#endif  // OS_MACOSX
    }
    supports_dual_gpus_ = flag;
    supports_dual_gpus_set_ = true;
  }
  return supports_dual_gpus_;
}

void GpuSwitchingManager::SetGpuVendorIds(
    const std::vector<uint32_t>& vendor_ids) {
  vendor_ids_ = vendor_ids;
}

void GpuSwitchingManager::AddObserver(GpuSwitchingObserver* observer) {
  observer_list_.AddObserver(observer);
}

void GpuSwitchingManager::RemoveObserver(GpuSwitchingObserver* observer) {
  observer_list_.RemoveObserver(observer);
}

void GpuSwitchingManager::NotifyGpuSwitched() {
  for (GpuSwitchingObserver& observer : observer_list_)
    observer.OnGpuSwitched();
}

gl::GpuPreference GpuSwitchingManager::AdjustGpuPreference(
    gl::GpuPreference gpu_preference) {
  if (!gpu_switching_option_set_)
    return gpu_preference;
  return gpu_switching_option_;
}

#if defined(OS_MACOSX)
void GpuSwitchingManager::SwitchToDiscreteGpuMac() {
  if (platform_specific_->discrete_pixel_format)
    return;
  CGLPixelFormatAttribute attribs[1];
  attribs[0] = static_cast<CGLPixelFormatAttribute>(0);
  GLint num_pixel_formats = 0;
  CGLChoosePixelFormat(attribs, &platform_specific_->discrete_pixel_format,
                       &num_pixel_formats);
}
#endif  // OS_MACOSX

}  // namespace ui
