| // 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/bind_helpers.h" |
| #include "base/callback.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/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/latency_info.h" |
| #include "ui/base/win/dpi.h" |
| #include "ui/base/win/hwnd_util.h" |
| #include "ui/base/win/shell.h" |
| #include "ui/gfx/rect.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_); |
| |
| #if defined(USE_AURA) |
| if (!window) |
| return presenters_.begin()->second; |
| #endif |
| |
| 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()); |
| if (!bitmap->allocPixels()) |
| return false; |
| bitmap->setIsOpaque(true); |
| |
| // 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(); |
| } |
| |
| #if defined(USE_AURA) |
| void AcceleratedPresenter::SetNewTargetWindow(gfx::PluginWindowHandle window) { |
| window_ = window; |
| swap_chain_ = NULL; |
| } |
| #endif |
| |
| 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 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 (ui::IsInHighDPIMode()) { |
| // Check if the size mismatch is within allowable round off or truncation |
| // error. |
| gfx::Size dip_size = ui::win::ScreenToDIPSize(window_size); |
| gfx::Size pixel_size = ui::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; |
| } |
| |
| // 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( |
| ¶meters, |
| 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_.swap_timestamp = base::TimeTicks::HighResNow(); |
| |
| 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; |
| } |
| |
| // 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; |
| |
| base::TimeTicks current_time = base::TimeTicks::HighResNow(); |
| |
| // 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) { |
| last_vsync_time = current_time - |
| base::TimeDelta::FromMilliseconds((clamped_scanline * 1000) / |
| (display_mode.RefreshRate * display_mode.Height)); |
| refresh_period = base::TimeDelta::FromMicroseconds( |
| 1000000 / display_mode.RefreshRate); |
| } |
| |
| // 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); |
| } |