普通文本  |  1109行  |  32.96 KB

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

#include <windows.h>
#include <algorithm>

#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/files/file_path.h"
#include "base/lazy_instance.h"
#include "base/metrics/histogram.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/scoped_native_library.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "base/win/wrapped_window_proc.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/win/shell.h"
#include "ui/events/latency_info.h"
#include "ui/gfx/frame_time.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/win/dpi.h"
#include "ui/gfx/win/hwnd_util.h"
#include "ui/gl/gl_switches.h"
#include "ui/surface/accelerated_surface_transformer_win.h"
#include "ui/surface/d3d9_utils_win.h"
#include "ui/surface/surface_switches.h"

namespace d3d_utils = ui_surface_d3d9_utils;

namespace {

UINT GetPresentationInterval() {
  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync))
    return D3DPRESENT_INTERVAL_IMMEDIATE;
  else
    return D3DPRESENT_INTERVAL_ONE;
}

bool DoFirstShowPresentWithGDI() {
  return CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kDoFirstShowPresentWithGDI);
}

bool DoAllShowPresentWithGDI() {
  return CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kDoAllShowPresentWithGDI);
}

// Use a SurfaceReader to copy into one plane of the VideoFrame.
bool CopyPlane(AcceleratedSurfaceTransformer* gpu_ops,
               IDirect3DSurface9* src_surface,
               media::VideoFrame* dst_frame,
               size_t plane_id) {
  int width_in_bytes = dst_frame->row_bytes(plane_id);
  return gpu_ops->ReadFast(src_surface, dst_frame->data(plane_id),
                           width_in_bytes, dst_frame->rows(plane_id),
                           dst_frame->row_bytes(plane_id));
}

}  // namespace

// A PresentThread is a thread that is dedicated to presenting surfaces to a
// window. It owns a Direct3D device and a Direct3D query for this purpose.
class PresentThread : public base::Thread,
                      public base::RefCountedThreadSafe<PresentThread> {
 public:
  PresentThread(const char* name, uint64 adapter_luid);

  IDirect3DDevice9Ex* device() { return device_.get(); }
  IDirect3DQuery9* query() { return query_.get(); }
  AcceleratedSurfaceTransformer* surface_transformer() {
    return &surface_transformer_;
  }

  void SetAdapterLUID(uint64 adapter_luid);
  void InitDevice();
  void LockAndResetDevice();
  void ResetDevice();
  bool IsDeviceLost();

  base::Lock* lock() {
    return &lock_;
  }

 protected:
  virtual void Init();
  virtual void CleanUp();

 private:
  friend class base::RefCountedThreadSafe<PresentThread>;

  ~PresentThread();

  // The lock is taken while any thread is calling an AcceleratedPresenter
  // associated with this thread.
  base::Lock lock_;

  base::ScopedNativeLibrary d3d_module_;
  uint64 adapter_luid_;
  base::win::ScopedComPtr<IDirect3DDevice9Ex> device_;

  // This query is used to wait until a certain amount of progress has been
  // made by the GPU and it is safe for the producer to modify its shared
  // texture again.
  base::win::ScopedComPtr<IDirect3DQuery9> query_;
  AcceleratedSurfaceTransformer surface_transformer_;

  DISALLOW_COPY_AND_ASSIGN(PresentThread);
};

// There is a fixed sized pool of PresentThreads and therefore the maximum
// number of Direct3D devices owned by those threads is bounded.
class PresentThreadPool {
 public:
  static const int kNumPresentThreads = 4;

  PresentThreadPool();
  PresentThread* NextThread();

  void SetAdapterLUID(uint64 adapter_luid);

 private:
  base::Lock lock_;
  int next_thread_;
  scoped_refptr<PresentThread> present_threads_[kNumPresentThreads];
  uint64 adapter_luid_;

  DISALLOW_COPY_AND_ASSIGN(PresentThreadPool);
};

// A thread safe map of presenters by surface ID that returns presenters via
// a scoped_refptr to keep them alive while they are referenced.
class AcceleratedPresenterMap {
 public:
  AcceleratedPresenterMap();
  scoped_refptr<AcceleratedPresenter> CreatePresenter(
      gfx::PluginWindowHandle window);
  void RemovePresenter(const scoped_refptr<AcceleratedPresenter>& presenter);
  scoped_refptr<AcceleratedPresenter> GetPresenter(
      gfx::PluginWindowHandle window);

  // Destroy any D3D resources owned by the given present thread. Called on
  // the given present thread.
  void ResetPresentThread(PresentThread* present_thread);

 private:
  base::Lock lock_;
  typedef std::map<gfx::PluginWindowHandle, AcceleratedPresenter*> PresenterMap;
  PresenterMap presenters_;
  uint64 adapter_luid_;
  DISALLOW_COPY_AND_ASSIGN(AcceleratedPresenterMap);
};

base::LazyInstance<PresentThreadPool>
    g_present_thread_pool = LAZY_INSTANCE_INITIALIZER;

base::LazyInstance<AcceleratedPresenterMap>
    g_accelerated_presenter_map = LAZY_INSTANCE_INITIALIZER;

PresentThread::PresentThread(const char* name, uint64 adapter_luid)
    : base::Thread(name),
      adapter_luid_(adapter_luid) {
}

void PresentThread::SetAdapterLUID(uint64 adapter_luid) {
  base::AutoLock locked(lock_);

  CHECK(message_loop() == base::MessageLoop::current());

  if (adapter_luid_ == adapter_luid)
    return;

  adapter_luid_ = adapter_luid;
  if (device_)
    ResetDevice();
}

void PresentThread::InitDevice() {
  lock_.AssertAcquired();

  if (device_)
    return;

  TRACE_EVENT0("gpu", "PresentThread::Init");
  d3d_utils::LoadD3D9(&d3d_module_);
  ResetDevice();
}

void PresentThread::LockAndResetDevice() {
  base::AutoLock locked(lock_);
  ResetDevice();
}

void PresentThread::ResetDevice() {
  TRACE_EVENT0("gpu", "PresentThread::ResetDevice");

  lock_.AssertAcquired();

  // The D3D device must be created on the present thread.
  CHECK(message_loop() == base::MessageLoop::current());

  // This will crash some Intel drivers but we can't render anything without
  // reseting the device, which would be disappointing.
  query_ = NULL;
  device_ = NULL;
  surface_transformer_.ReleaseAll();

  g_accelerated_presenter_map.Pointer()->ResetPresentThread(this);

  if (!d3d_utils::CreateDevice(d3d_module_,
                               adapter_luid_,
                               D3DDEVTYPE_HAL,
                               GetPresentationInterval(),
                               device_.Receive())) {
    return;
  }

  HRESULT hr = device_->CreateQuery(D3DQUERYTYPE_EVENT, query_.Receive());
  if (FAILED(hr)) {
    LOG(ERROR) << "Failed to create query";
    device_ = NULL;
    return;
  }

  if (!surface_transformer_.Init(device_)) {
    LOG(ERROR) << "Failed to initialize surface transformer";
    query_ = NULL;
    device_ = NULL;
    return;
  }
}

bool PresentThread::IsDeviceLost() {
  lock_.AssertAcquired();

  HRESULT hr = device_->CheckDeviceState(NULL);
  return FAILED(hr) || hr == S_PRESENT_MODE_CHANGED;
}

void PresentThread::Init() {
  TRACE_EVENT0("gpu", "Initialize thread");
}

void PresentThread::CleanUp() {
  // The D3D device and query are leaked because destroying the associated D3D
  // query crashes some Intel drivers.
  surface_transformer_.DetachAll();
  device_.Detach();
  query_.Detach();
}

PresentThread::~PresentThread() {
  Stop();
}

PresentThreadPool::PresentThreadPool() : next_thread_(0) {
}

PresentThread* PresentThreadPool::NextThread() {
  base::AutoLock locked(lock_);

  next_thread_ = (next_thread_ + 1) % kNumPresentThreads;
  PresentThread* thread = present_threads_[next_thread_].get();
  if (!thread) {
    thread = new PresentThread(
        base::StringPrintf("PresentThread #%d", next_thread_).c_str(),
        adapter_luid_);
    thread->Start();
    present_threads_[next_thread_] = thread;
  }

  return thread;
}

void PresentThreadPool::SetAdapterLUID(uint64 adapter_luid) {
  base::AutoLock locked(lock_);

  adapter_luid_ = adapter_luid;

  for (int i = 0; i < kNumPresentThreads; ++i) {
    if (!present_threads_[i])
      continue;

    present_threads_[i]->message_loop()->PostTask(
        FROM_HERE,
        base::Bind(&PresentThread::SetAdapterLUID,
                   present_threads_[i],
                   adapter_luid));
  }
}

AcceleratedPresenterMap::AcceleratedPresenterMap() {
}

scoped_refptr<AcceleratedPresenter> AcceleratedPresenterMap::CreatePresenter(
    gfx::PluginWindowHandle window) {
  scoped_refptr<AcceleratedPresenter> presenter(
      new AcceleratedPresenter(window));

  base::AutoLock locked(lock_);
  DCHECK(presenters_.find(window) == presenters_.end());
  presenters_[window] = presenter.get();

  return presenter;
}

void AcceleratedPresenterMap::RemovePresenter(
    const scoped_refptr<AcceleratedPresenter>& presenter) {
  base::AutoLock locked(lock_);
  for (PresenterMap::iterator it = presenters_.begin();
      it != presenters_.end();
      ++it) {
    if (it->second == presenter.get()) {
      presenters_.erase(it);
      return;
    }
  }

  NOTREACHED();
}

scoped_refptr<AcceleratedPresenter> AcceleratedPresenterMap::GetPresenter(
    gfx::PluginWindowHandle window) {
  base::AutoLock locked(lock_);
  PresenterMap::iterator it = presenters_.find(window);
  if (it == presenters_.end())
    return scoped_refptr<AcceleratedPresenter>();

  return it->second;
}

void AcceleratedPresenterMap::ResetPresentThread(
    PresentThread* present_thread) {
  base::AutoLock locked(lock_);

  for (PresenterMap::iterator it = presenters_.begin();
      it != presenters_.end();
      ++it) {
    it->second->ResetPresentThread(present_thread);
  }
}

AcceleratedPresenter::AcceleratedPresenter(gfx::PluginWindowHandle window)
    : present_thread_(g_present_thread_pool.Pointer()->NextThread()),
      window_(window),
      event_(false, false),
      hidden_(true),
      do_present_with_GDI_(DoAllShowPresentWithGDI() ||
                           DoFirstShowPresentWithGDI()),
      is_session_locked_(false) {
}

// static
void AcceleratedPresenter::SetAdapterLUID(uint64 adapter_luid) {
  return g_present_thread_pool.Pointer()->SetAdapterLUID(adapter_luid);
}


// static
scoped_refptr<AcceleratedPresenter> AcceleratedPresenter::GetForWindow(
    gfx::PluginWindowHandle window) {
  return g_accelerated_presenter_map.Pointer()->GetPresenter(window);
}

void AcceleratedPresenter::AsyncPresentAndAcknowledge(
    const gfx::Size& size,
    int64 surface_handle,
    const ui::LatencyInfo& latency_info,
    const CompletionTask& completion_task) {
  if (!surface_handle) {
    TRACE_EVENT1("gpu", "EarlyOut_ZeroSurfaceHandle",
                 "surface_handle", surface_handle);
    completion_task.Run(
        true, base::TimeTicks(), base::TimeDelta(), ui::LatencyInfo());
    return;
  }

  present_thread_->message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&AcceleratedPresenter::DoPresentAndAcknowledge,
                 this,
                 size,
                 surface_handle,
                 latency_info,
                 completion_task));
}

void AcceleratedPresenter::Present(HDC dc) {
  TRACE_EVENT0("gpu", "Present");

  base::AutoLock locked(*present_thread_->lock());

  // If invalidated, do nothing. The window is gone.
  if (!window_)
    return;

  // Suspended or nothing has ever been presented.
  if (!swap_chain_)
    return;

  PresentWithGDI(dc);
}

void AcceleratedPresenter::AsyncCopyTo(
    const gfx::Rect& requested_src_subrect,
    const gfx::Size& dst_size,
    const base::Callback<void(bool, const SkBitmap&)>& callback) {
  present_thread_->message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&AcceleratedPresenter::DoCopyToAndAcknowledge,
                 this,
                 requested_src_subrect,
                 dst_size,
                 base::MessageLoopProxy::current(),
                 callback));
}

void AcceleratedPresenter::AsyncCopyToVideoFrame(
    const gfx::Rect& requested_src_subrect,
    const scoped_refptr<media::VideoFrame>& target,
    const base::Callback<void(bool)>& callback) {
  present_thread_->message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&AcceleratedPresenter::DoCopyToVideoFrameAndAcknowledge,
                 this,
                 requested_src_subrect,
                 target,
                 base::MessageLoopProxy::current(),
                 callback));
}

void AcceleratedPresenter::DoCopyToAndAcknowledge(
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    scoped_refptr<base::SingleThreadTaskRunner> callback_runner,
    const base::Callback<void(bool, const SkBitmap&)>& callback) {
  SkBitmap target;
  bool result = DoCopyToARGB(src_subrect, dst_size, &target);
  if (!result)
    target.reset();
  callback_runner->PostTask(FROM_HERE, base::Bind(callback, result, target));
}

void AcceleratedPresenter::DoCopyToVideoFrameAndAcknowledge(
    const gfx::Rect& src_subrect,
    const scoped_refptr<media::VideoFrame>& target,
    const scoped_refptr<base::SingleThreadTaskRunner>& callback_runner,
    const base::Callback<void(bool)>& callback) {

  bool result = DoCopyToYUV(src_subrect, target);
  callback_runner->PostTask(FROM_HERE, base::Bind(callback, result));
}

bool AcceleratedPresenter::DoCopyToARGB(const gfx::Rect& requested_src_subrect,
                                        const gfx::Size& dst_size,
                                        SkBitmap* bitmap) {
  TRACE_EVENT2(
      "gpu", "CopyTo",
      "width", dst_size.width(),
      "height", dst_size.height());

  base::AutoLock locked(*present_thread_->lock());

  if (!swap_chain_)
    return false;

  AcceleratedSurfaceTransformer* gpu_ops =
      present_thread_->surface_transformer();

  base::win::ScopedComPtr<IDirect3DSurface9> back_buffer;
  HRESULT hr = swap_chain_->GetBackBuffer(0,
                                          D3DBACKBUFFER_TYPE_MONO,
                                          back_buffer.Receive());
  if (FAILED(hr)) {
    LOG(ERROR) << "Failed to get back buffer";
    return false;
  }

  D3DSURFACE_DESC desc;
  hr = back_buffer->GetDesc(&desc);
  if (FAILED(hr)) {
    LOG(ERROR) << "Failed to get buffer description";
    return false;
  }

  const gfx::Size back_buffer_size(desc.Width, desc.Height);
  if (back_buffer_size.IsEmpty())
    return false;

  // With window resizing, it's possible that the back buffer is smaller than
  // the requested src subset. Clip to the actual back buffer.
  gfx::Rect src_subrect = requested_src_subrect;
  src_subrect.Intersect(gfx::Rect(back_buffer_size));
  base::win::ScopedComPtr<IDirect3DSurface9> final_surface;
  {
    if (!d3d_utils::CreateOrReuseLockableSurface(present_thread_->device(),
                                                 dst_size,
                                                 &final_surface)) {
      LOG(ERROR) << "Failed to create temporary lockable surface";
      return false;
    }
  }

  {
    // Let the surface transformer start the resize into |final_surface|.
    TRACE_EVENT0("gpu", "ResizeBilinear");
    if (!gpu_ops->ResizeBilinear(back_buffer, src_subrect,
                                 final_surface, gfx::Rect(dst_size))) {
      LOG(ERROR) << "Failed to resize bilinear";
      return false;
    }
  }

  bitmap->setConfig(SkBitmap::kARGB_8888_Config, dst_size.width(),
                    dst_size.height(), 0, kOpaque_SkAlphaType);
  if (!bitmap->allocPixels())
    return false;

  // Copy |final_surface| to |bitmap|. This is always a synchronous operation.
  return gpu_ops->ReadFast(final_surface,
                           reinterpret_cast<uint8*>(bitmap->getPixels()),
                           bitmap->width() * bitmap->bytesPerPixel(),
                           bitmap->height(),
                           static_cast<int>(bitmap->rowBytes()));
}

bool AcceleratedPresenter::DoCopyToYUV(
    const gfx::Rect& requested_src_subrect,
    const scoped_refptr<media::VideoFrame>& frame) {
  gfx::Size dst_size = frame->coded_size();
  TRACE_EVENT2(
      "gpu", "CopyToYUV",
      "width", dst_size.width(),
      "height", dst_size.height());

  base::AutoLock locked(*present_thread_->lock());

  if (!swap_chain_)
    return false;

  AcceleratedSurfaceTransformer* gpu_ops =
      present_thread_->surface_transformer();

  base::win::ScopedComPtr<IDirect3DSurface9> back_buffer;
  HRESULT hr = swap_chain_->GetBackBuffer(0,
                                          D3DBACKBUFFER_TYPE_MONO,
                                          back_buffer.Receive());
  if (FAILED(hr))
    return false;

  D3DSURFACE_DESC desc;
  hr = back_buffer->GetDesc(&desc);
  if (FAILED(hr))
    return false;

  const gfx::Size back_buffer_size(desc.Width, desc.Height);
  if (back_buffer_size.IsEmpty())
    return false;

  // With window resizing, it's possible that the back buffer is smaller than
  // the requested src subset. Clip to the actual back buffer.
  gfx::Rect src_subrect = requested_src_subrect;
  src_subrect.Intersect(gfx::Rect(back_buffer_size));
  if (src_subrect.IsEmpty())
    return false;

  base::win::ScopedComPtr<IDirect3DSurface9> resized;
  base::win::ScopedComPtr<IDirect3DTexture9> resized_as_texture;
  if (!gpu_ops->GetIntermediateTexture(dst_size,
                                       resized_as_texture.Receive(),
                                       resized.Receive())) {
    return false;
  }

  // Shrink the source to fit entirely in the destination while preserving
  // aspect ratio. Fill in any margin with black.
  // TODO(nick): It would be more efficient all around to implement
  // letterboxing as a memset() on the dst.
  gfx::Rect letterbox = media::ComputeLetterboxRegion(gfx::Rect(dst_size),
                                                      src_subrect.size());
  if (letterbox != gfx::Rect(dst_size)) {
    TRACE_EVENT0("gpu", "Letterbox");
    present_thread_->device()->ColorFill(resized, NULL, 0xFF000000);
  }

  {
    TRACE_EVENT0("gpu", "ResizeBilinear");
    if (!gpu_ops->ResizeBilinear(back_buffer, src_subrect, resized, letterbox))
      return false;
  }

  base::win::ScopedComPtr<IDirect3DSurface9> y, u, v;
  {
    TRACE_EVENT0("gpu", "TransformRGBToYV12");
    if (!gpu_ops->TransformRGBToYV12(resized_as_texture,
                                     dst_size,
                                     y.Receive(), u.Receive(), v.Receive())) {
      return false;
    }
  }

  if (!CopyPlane(gpu_ops, y, frame, media::VideoFrame::kYPlane))
    return false;
  if (!CopyPlane(gpu_ops, u, frame, media::VideoFrame::kUPlane))
    return false;
  if (!CopyPlane(gpu_ops, v, frame, media::VideoFrame::kVPlane))
    return false;
  return true;
}

void AcceleratedPresenter::Suspend() {
  present_thread_->message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&AcceleratedPresenter::DoSuspend,
                 this));
}

void AcceleratedPresenter::WasHidden() {
  base::AutoLock locked(*present_thread_->lock());
  hidden_ = true;
}

void AcceleratedPresenter::ReleaseSurface() {
  present_thread_->message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&AcceleratedPresenter::DoReleaseSurface,
                 this));
}

void AcceleratedPresenter::SetIsSessionLocked(bool locked) {
  is_session_locked_ = locked;
}

void AcceleratedPresenter::Invalidate() {
  // Make any pending or future presentation tasks do nothing. Once the last
  // last pending task has been ignored, the reference count on the presenter
  // will go to zero and the presenter, and potentially also the present thread
  // it has a reference count on, will be destroyed.
  base::AutoLock locked(*present_thread_->lock());
  window_ = NULL;
}

void AcceleratedPresenter::ResetPresentThread(
    PresentThread* present_thread) {
  TRACE_EVENT0("gpu", "ResetPresentThread");

  // present_thread_ can be accessed without the lock because it is immutable.
  if (present_thread_ != present_thread)
    return;

  present_thread_->lock()->AssertAcquired();

  source_texture_ = NULL;
  swap_chain_ = NULL;
  quantized_size_ = gfx::Size();
}

AcceleratedPresenter::~AcceleratedPresenter() {
}

bool AcceleratedPresenter::IsSwapChainInitialized() const {
  base::AutoLock locked(*present_thread_->lock());

  return !!swap_chain_;
}

void AcceleratedPresenter::DoPresentAndAcknowledge(
    const gfx::Size& size,
    int64 surface_handle,
    const ui::LatencyInfo& latency_info,
    const CompletionTask& completion_task) {
  TRACE_EVENT2(
      "gpu", "DoPresentAndAcknowledge",
      "width", size.width(),
      "height", size.height());

  HRESULT hr;

  base::AutoLock locked(*present_thread_->lock());

  latency_info_.MergeWith(latency_info);

  // Initialize the device lazily since calling Direct3D can crash bots.
  present_thread_->InitDevice();

  if (!present_thread_->device()) {
    completion_task.Run(
        false, base::TimeTicks(), base::TimeDelta(), ui::LatencyInfo());
    TRACE_EVENT0("gpu", "EarlyOut_NoDevice");
    return;
  }

  // Ensure the task is acknowledged on early out after this point.
  base::ScopedClosureRunner scoped_completion_runner(
      base::Bind(completion_task,
                 true,
                 base::TimeTicks(),
                 base::TimeDelta(),
                 ui::LatencyInfo()));

  // If invalidated, do nothing, the window is gone.
  if (!window_) {
    TRACE_EVENT0("gpu", "EarlyOut_NoWindow");
    return;
  }

#if !defined(USE_AURA)
  // If the window is a different size than the swap chain that is being
  // presented then drop the frame.
  gfx::Size window_size = GetWindowSize();
  bool size_mismatch = size != window_size;
  if (gfx::IsInHighDPIMode()) {
    // Check if the size mismatch is within allowable round off or truncation
    // error.
    gfx::Size dip_size = gfx::win::ScreenToDIPSize(window_size);
    gfx::Size pixel_size = gfx::win::DIPToScreenSize(dip_size);
    size_mismatch = abs(window_size.width() - size.width()) >
        abs(window_size.width() - pixel_size.width()) ||
        abs(window_size.height() - size.height()) >
        abs(window_size.height() - pixel_size.height());
  }
  if (hidden_ && size_mismatch) {
    TRACE_EVENT2("gpu", "EarlyOut_WrongWindowSize",
                 "backwidth", size.width(), "backheight", size.height());
    TRACE_EVENT2("gpu", "EarlyOut_WrongWindowSize2",
                 "windowwidth", window_size.width(),
                 "windowheight", window_size.height());
    return;
  }
#endif
  // Round up size so the swap chain is not continuously resized with the
  // surface, which could lead to memory fragmentation.
  const int kRound = 64;
  gfx::Size quantized_size(
      std::max(1, (size.width() + kRound - 1) / kRound * kRound),
      std::max(1, (size.height() + kRound - 1) / kRound * kRound));

  // Ensure the swap chain exists and is the same size (rounded up) as the
  // surface to be presented.
  if (!swap_chain_ || quantized_size_ != quantized_size) {
    TRACE_EVENT0("gpu", "CreateAdditionalSwapChain");
    quantized_size_ = quantized_size;

    D3DPRESENT_PARAMETERS parameters = { 0 };
    parameters.BackBufferWidth = quantized_size.width();
    parameters.BackBufferHeight = quantized_size.height();
    parameters.BackBufferCount = 1;
    parameters.BackBufferFormat = D3DFMT_A8R8G8B8;
    parameters.hDeviceWindow = window_;
    parameters.Windowed = TRUE;
    parameters.Flags = 0;
    parameters.PresentationInterval = GetPresentationInterval();
    parameters.SwapEffect = D3DSWAPEFFECT_COPY;

    swap_chain_ = NULL;
    HRESULT hr = present_thread_->device()->CreateAdditionalSwapChain(
        &parameters,
        swap_chain_.Receive());
    if (FAILED(hr)) {
      LOG(ERROR) << "Failed to create swap chain "
                 << quantized_size.width() << " x " <<quantized_size.height();
      return;
    }
  }

  if (!source_texture_.get()) {
    TRACE_EVENT0("gpu", "OpenSharedTexture");
    if (!d3d_utils::OpenSharedTexture(present_thread_->device(),
                                      surface_handle,
                                      size,
                                      source_texture_.Receive())) {
      LOG(ERROR) << "Failed to open shared texture";
      return;
    }
  }

  base::win::ScopedComPtr<IDirect3DSurface9> source_surface;
  hr = source_texture_->GetSurfaceLevel(0, source_surface.Receive());
  if (FAILED(hr)) {
    TRACE_EVENT0("gpu", "EarlyOut_NoSurfaceLevel");
    LOG(ERROR) << "Failed to get source surface";
    return;
  }

  base::win::ScopedComPtr<IDirect3DSurface9> dest_surface;
  hr = swap_chain_->GetBackBuffer(0,
                                  D3DBACKBUFFER_TYPE_MONO,
                                  dest_surface.Receive());
  if (FAILED(hr)) {
    TRACE_EVENT0("gpu", "EarlyOut_NoBackbuffer");
    LOG(ERROR) << "Failed to get back buffer";
    return;
  }

  RECT rect = {
    0, 0,
    size.width(), size.height()
  };

  {
    TRACE_EVENT0("gpu", "Copy");

    // Copy while flipping the source texture on the vertical axis.
    bool result = present_thread_->surface_transformer()->CopyInverted(
        source_texture_, dest_surface, size);
    if (!result) {
      LOG(ERROR) << "Failed to copy shared texture";
      return;
    }
  }

  hr = present_thread_->query()->Issue(D3DISSUE_END);
  if (FAILED(hr)) {
    LOG(ERROR) << "Failed to issue query";
    return;
  }

  present_size_ = size;

  // If it is expected that Direct3D cannot be used reliably because the window
  // is resizing, fall back to presenting with GDI.
  if (CheckDirect3DWillWork()) {
    TRACE_EVENT0("gpu", "PresentD3D");

    hr = swap_chain_->Present(&rect, &rect, window_, NULL, 0);

    if (FAILED(hr)) {
      if (present_thread_->IsDeviceLost())
        present_thread_->ResetDevice();
      return;
    }
  } else {
    HDC dc = GetDC(window_);
    PresentWithGDI(dc);
    ReleaseDC(window_, dc);
  }

  latency_info_.AddLatencyNumber(
      ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);

  hidden_ = false;

  D3DDISPLAYMODE display_mode;
  hr = present_thread_->device()->GetDisplayMode(0, &display_mode);
  if (FAILED(hr)) {
    LOG(ERROR) << "Failed to get display mode";
    return;
  }

  D3DRASTER_STATUS raster_status;
  hr = swap_chain_->GetRasterStatus(&raster_status);
  if (FAILED(hr)) {
    LOG(ERROR) << "Failed to get raster status";
    return;
  }

  UMA_HISTOGRAM_CUSTOM_COUNTS("GPU.AcceleratedSurfaceRefreshRate",
                              display_mode.RefreshRate, 0, 121, 122);

  // I can't figure out how to determine how many scanlines are in the
  // vertical blank so clamp it such that scanline / height <= 1.
  int clamped_scanline = std::min(raster_status.ScanLine, display_mode.Height);

  // The Internet says that on some GPUs, the scanline is not available
  // while in the vertical blank.
  if (raster_status.InVBlank)
    clamped_scanline = display_mode.Height;

  // Figure out approximately how far back in time the last vsync was based on
  // the ratio of the raster scanline to the display height.
  base::TimeTicks last_vsync_time;
  base::TimeDelta refresh_period;

  if (display_mode.Height) {
    refresh_period = base::TimeDelta::FromMicroseconds(
        1000000 / display_mode.RefreshRate);
    // If FrameTime is not high resolution, we use a timebase of zero to avoid
    // introducing jitter into our frame start times.
    if (gfx::FrameTime::TimestampsAreHighRes()) {
      base::TimeTicks current_time = gfx::FrameTime::Now();
      last_vsync_time = current_time -
        base::TimeDelta::FromMilliseconds((clamped_scanline * 1000) /
            (display_mode.RefreshRate * display_mode.Height));
    }
  }

  // Wait for the StretchRect to complete before notifying the GPU process
  // that it is safe to write to its backing store again.
  {
    TRACE_EVENT0("gpu", "spin");

    do {
      hr = present_thread_->query()->GetData(NULL, 0, D3DGETDATA_FLUSH);
      if (hr == S_FALSE) {
        Sleep(1);

        if (present_thread_->IsDeviceLost()) {
          present_thread_->ResetDevice();
          return;
        }
      }
    } while (hr == S_FALSE);
  }

  scoped_completion_runner.Release();
  completion_task.Run(true, last_vsync_time, refresh_period, latency_info_);
  latency_info_.Clear();
}

void AcceleratedPresenter::DoSuspend() {
  base::AutoLock locked(*present_thread_->lock());
  swap_chain_ = NULL;
}

void AcceleratedPresenter::DoReleaseSurface() {
  base::AutoLock locked(*present_thread_->lock());
  present_thread_->InitDevice();
  source_texture_.Release();
}

void AcceleratedPresenter::PresentWithGDI(HDC dc) {
  TRACE_EVENT0("gpu", "PresentWithGDI");

  if (!present_thread_->device()) {
    LOG(ERROR) << "No device";
    return;
  }

  if (!swap_chain_) {
    LOG(ERROR) << "No swap chain";
    return;
  }

  base::win::ScopedComPtr<IDirect3DTexture9> system_texture;
  {
    TRACE_EVENT0("gpu", "CreateSystemTexture");
    HRESULT hr = present_thread_->device()->CreateTexture(
        quantized_size_.width(),
        quantized_size_.height(),
        1,
        0,
        D3DFMT_A8R8G8B8,
        D3DPOOL_SYSTEMMEM,
        system_texture.Receive(),
        NULL);
    if (FAILED(hr)) {
      LOG(ERROR) << "Failed to create system memory texture";
      return;
    }
  }

  base::win::ScopedComPtr<IDirect3DSurface9> system_surface;
  HRESULT hr = system_texture->GetSurfaceLevel(0, system_surface.Receive());
  DCHECK(SUCCEEDED(hr));

  base::win::ScopedComPtr<IDirect3DSurface9> back_buffer;
  hr = swap_chain_->GetBackBuffer(0,
                                  D3DBACKBUFFER_TYPE_MONO,
                                  back_buffer.Receive());
  DCHECK(SUCCEEDED(hr));

  {
    TRACE_EVENT0("gpu", "GetRenderTargetData");
    hr = present_thread_->device()->GetRenderTargetData(back_buffer,
                                                        system_surface);

    if (FAILED(hr)) {
      if (present_thread_->IsDeviceLost()) {
        present_thread_->message_loop()->PostTask(
            FROM_HERE,
            base::Bind(&PresentThread::LockAndResetDevice, present_thread_));
      }
      return;
    }

    DCHECK(SUCCEEDED(hr));
  }

  D3DLOCKED_RECT locked_surface;
  hr = system_surface->LockRect(&locked_surface, NULL, D3DLOCK_READONLY);
  DCHECK(SUCCEEDED(hr));

  BITMAPINFO bitmap_info = {
    {
      sizeof(BITMAPINFOHEADER),
      quantized_size_.width(),
      -quantized_size_.height(),
      1,  // planes
      32,  // bitcount
      BI_RGB
    },
    {
      {0, 0, 0, 0}
    }
  };

  {
    TRACE_EVENT0("gpu", "StretchDIBits");
    StretchDIBits(dc,
                  0, 0,
                  present_size_.width(),
                  present_size_.height(),
                  0, 0,
                  present_size_.width(),
                  present_size_.height(),
                  locked_surface.pBits,
                  &bitmap_info,
                  DIB_RGB_COLORS,
                  SRCCOPY);
  }

  system_surface->UnlockRect();
}

gfx::Size AcceleratedPresenter::GetWindowSize() {
  RECT rect;
  GetClientRect(window_, &rect);
  return gfx::Rect(rect).size();
}

bool AcceleratedPresenter::CheckDirect3DWillWork() {
  // On a composited desktop, when the screen saver or logon screen are
  // active, D3D presents never make it to the window but GDI presents
  // do. If the session is locked GDI presents can be avoided since
  // the window gets a message on unlock and forces a repaint.
  if (!is_session_locked_ && ui::win::IsAeroGlassEnabled()) {
    // Failure to open the input desktop is a sign of running with a non-default
    // desktop.
    HDESK input_desktop = ::OpenInputDesktop(0, 0, GENERIC_READ);
    if (!input_desktop)
      return false;
    ::CloseDesktop(input_desktop);
  }

  gfx::Size window_size = GetWindowSize();
  if (window_size != last_window_size_ && last_window_size_.GetArea() != 0) {
    last_window_size_ = window_size;
    last_window_resize_time_ = base::Time::Now();
    return false;
  }

  if (do_present_with_GDI_ && hidden_) {
    if (DoFirstShowPresentWithGDI())
      do_present_with_GDI_ = false;

    return false;
  }

  return base::Time::Now() - last_window_resize_time_ >
      base::TimeDelta::FromMilliseconds(100);
}

AcceleratedSurface::AcceleratedSurface(gfx::PluginWindowHandle window)
    : presenter_(g_accelerated_presenter_map.Pointer()->CreatePresenter(
          window)) {
}

AcceleratedSurface::~AcceleratedSurface() {
  g_accelerated_presenter_map.Pointer()->RemovePresenter(presenter_);
  presenter_->Invalidate();
}

void AcceleratedSurface::Present(HDC dc) {
  presenter_->Present(dc);
}

bool AcceleratedSurface::IsReadyForCopy() const {
  return !!presenter_ && presenter_->IsSwapChainInitialized();
}


void AcceleratedSurface::AsyncCopyTo(
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    const base::Callback<void(bool, const SkBitmap&)>& callback) {
  presenter_->AsyncCopyTo(src_subrect, dst_size, callback);
}

void AcceleratedSurface::AsyncCopyToVideoFrame(
    const gfx::Rect& src_subrect,
    const scoped_refptr<media::VideoFrame>& target,
    const base::Callback<void(bool)>& callback) {
  presenter_->AsyncCopyToVideoFrame(src_subrect, target, callback);
}

void AcceleratedSurface::Suspend() {
  presenter_->Suspend();
}

void AcceleratedSurface::WasHidden() {
  presenter_->WasHidden();
}

void AcceleratedSurface::SetIsSessionLocked(bool locked) {
  presenter_->SetIsSessionLocked(locked);
}