diff options
Diffstat (limited to 'ui/surface/accelerated_surface_win.cc')
-rw-r--r-- | ui/surface/accelerated_surface_win.cc | 683 |
1 files changed, 683 insertions, 0 deletions
diff --git a/ui/surface/accelerated_surface_win.cc b/ui/surface/accelerated_surface_win.cc new file mode 100644 index 0000000..ee1b6c6 --- /dev/null +++ b/ui/surface/accelerated_surface_win.cc @@ -0,0 +1,683 @@ +// 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/file_path.h" +#include "base/lazy_instance.h" +#include "base/memory/scoped_ptr.h" +#include "base/scoped_native_library.h" +#include "base/string_number_conversions.h" +#include "base/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "base/threading/thread_restrictions.h" +#include "base/time.h" +#include "base/tracked_objects.h" +#include "base/win/wrapped_window_proc.h" +#include "ui/base/win/hwnd_util.h" +#include "ui/gfx/gl/gl_switches.h" + +namespace { + +typedef HRESULT (WINAPI *Direct3DCreate9ExFunc)(UINT sdk_version, + IDirect3D9Ex **d3d); + +const wchar_t kD3D9ModuleName[] = L"d3d9.dll"; +const char kCreate3D9DeviceExName[] = "Direct3DCreate9Ex"; + +UINT GetPresentationInterval() { + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync)) + return D3DPRESENT_INTERVAL_IMMEDIATE; + else + return D3DPRESENT_INTERVAL_ONE; +} + +// Calculate the number necessary to transform |source_size| into |dest_size| +// by repeating downsampling of the image of |source_size| by a factor no more +// than 2. +int GetResampleCount(const gfx::Size& source_size, const gfx::Size& dest_size) { + int width_count = 0; + int width = source_size.width(); + while (width > dest_size.width()) { + ++width_count; + width >>= 1; + } + int height_count = 0; + int height = source_size.height(); + while (height > dest_size.height()) { + ++height_count; + height >>= 1; + } + return std::max(width_count, height_count); +} + +// Returns half the size of |size| no smaller than |min_size|. +gfx::Size GetHalfSizeNoLessThan(const gfx::Size& size, + const gfx::Size& min_size) { + return gfx::Size(std::max(min_size.width(), size.width() / 2), + std::max(min_size.height(), size.height() / 2)); +} + +bool CreateTemporarySurface(IDirect3DDevice9* device, + const gfx::Size& size, + IDirect3DSurface9** surface) { + HRESULT hr = device->CreateRenderTarget( + size.width(), + size.height(), + D3DFMT_A8R8G8B8, + D3DMULTISAMPLE_NONE, + 0, + TRUE, + surface, + NULL); + return SUCCEEDED(hr); +} + +} // namespace anonymous + +// 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: + explicit PresentThread(const char* name); + + IDirect3DDevice9Ex* device() { return device_.get(); } + IDirect3DQuery9* query() { return query_.get(); } + + void InitDevice(); + void ResetDevice(); + + protected: + virtual void CleanUp(); + + private: + friend class base::RefCountedThreadSafe<PresentThread>; + + ~PresentThread(); + + base::ScopedNativeLibrary d3d_module_; + 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_; + + 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(); + + private: + int next_thread_; + scoped_refptr<PresentThread> present_threads_[kNumPresentThreads]; + + 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::NativeWindow window); + void RemovePresenter(const scoped_refptr<AcceleratedPresenter>& presenter); + scoped_refptr<AcceleratedPresenter> GetPresenter(gfx::NativeWindow window); + private: + base::Lock lock_; + typedef std::map<gfx::NativeWindow, AcceleratedPresenter*> PresenterMap; + PresenterMap presenters_; + 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) : base::Thread(name) { +} + +void PresentThread::InitDevice() { + if (device_) + return; + + TRACE_EVENT0("surface", "PresentThread::Init"); + d3d_module_.Reset(base::LoadNativeLibrary(FilePath(kD3D9ModuleName), NULL)); + ResetDevice(); +} + +void PresentThread::ResetDevice() { + TRACE_EVENT0("surface", "PresentThread::ResetDevice"); + + // This will crash some Intel drivers but we can't render anything without + // reseting the device, which would be disappointing. + query_ = NULL; + device_ = NULL; + + Direct3DCreate9ExFunc create_func = reinterpret_cast<Direct3DCreate9ExFunc>( + d3d_module_.GetFunctionPointer(kCreate3D9DeviceExName)); + if (!create_func) + return; + + base::win::ScopedComPtr<IDirect3D9Ex> d3d; + HRESULT hr = create_func(D3D_SDK_VERSION, d3d.Receive()); + if (FAILED(hr)) + return; + + // Any old window will do to create the device. In practice the window to + // present to is an argument to IDirect3DDevice9::Present. + HWND window = GetShellWindow(); + + D3DPRESENT_PARAMETERS parameters = { 0 }; + parameters.BackBufferWidth = 1; + parameters.BackBufferHeight = 1; + parameters.BackBufferCount = 1; + parameters.BackBufferFormat = D3DFMT_A8R8G8B8; + parameters.hDeviceWindow = window; + parameters.Windowed = TRUE; + parameters.Flags = 0; + parameters.PresentationInterval = GetPresentationInterval(); + parameters.SwapEffect = D3DSWAPEFFECT_COPY; + + hr = d3d->CreateDeviceEx( + D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, + window, + D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING | + D3DCREATE_DISABLE_PSGP_THREADING | D3DCREATE_MULTITHREADED, + ¶meters, + NULL, + device_.Receive()); + if (FAILED(hr)) + return; + + hr = device_->CreateQuery(D3DQUERYTYPE_EVENT, query_.Receive()); + if (FAILED(hr)) + device_ = NULL; +} + +void PresentThread::CleanUp() { + // The D3D device and query are leaked because destroying the associated D3D + // query crashes some Intel drivers. + device_.Detach(); + query_.Detach(); +} + +PresentThread::~PresentThread() { + Stop(); +} + +PresentThreadPool::PresentThreadPool() : next_thread_(0) { + // Do this in the constructor so present_threads_ is initialized before any + // other thread sees it. See LazyInstance documentation. + for (int i = 0; i < kNumPresentThreads; ++i) { + present_threads_[i] = new PresentThread( + base::StringPrintf("PresentThread #%d", i).c_str()); + present_threads_[i]->Start(); + } +} + +PresentThread* PresentThreadPool::NextThread() { + next_thread_ = (next_thread_ + 1) % kNumPresentThreads; + return present_threads_[next_thread_].get(); +} + +AcceleratedPresenterMap::AcceleratedPresenterMap() { +} + +scoped_refptr<AcceleratedPresenter> AcceleratedPresenterMap::CreatePresenter( + gfx::NativeWindow 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::NativeWindow window) { + base::AutoLock locked(lock_); + PresenterMap::iterator it = presenters_.find(window); + if (it == presenters_.end()) + return scoped_refptr<AcceleratedPresenter>(); + + return it->second; +} + +AcceleratedPresenter::AcceleratedPresenter(gfx::NativeWindow window) + : present_thread_(g_present_thread_pool.Pointer()->NextThread()), + window_(window), + event_(false, false) { +} + +scoped_refptr<AcceleratedPresenter> AcceleratedPresenter::GetForWindow( + gfx::NativeWindow window) { + return g_accelerated_presenter_map.Pointer()->GetPresenter(window); +} + +void AcceleratedPresenter::AsyncPresentAndAcknowledge( + const gfx::Size& size, + int64 surface_handle, + const base::Callback<void(bool)>& completion_task) { + if (!surface_handle) { + completion_task.Run(true); + return; + } + + present_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&AcceleratedPresenter::DoPresentAndAcknowledge, + this, + size, + surface_handle, + completion_task)); +} + +bool AcceleratedPresenter::Present() { + TRACE_EVENT0("surface", "Present"); + + bool result; + + present_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&AcceleratedPresenter::DoPresent, + this, + &result)); + // http://crbug.com/125391 + base::ThreadRestrictions::ScopedAllowWait allow_wait; + event_.Wait(); + return result; +} + +void AcceleratedPresenter::DoPresent(bool* result) +{ + *result = DoRealPresent(); + event_.Signal(); +} + +bool AcceleratedPresenter::DoRealPresent() +{ + TRACE_EVENT0("surface", "DoRealPresent"); + HRESULT hr; + + base::AutoLock locked(lock_); + + // Signal the caller to recomposite if the presenter has been suspended or no + // surface has ever been presented. + if (!swap_chain_) + return false; + + // If invalidated, do nothing. The window is gone. + if (!window_) + return true; + + RECT rect = { + 0, 0, + size_.width(), size_.height() + }; + + { + TRACE_EVENT0("surface", "PresentEx"); + hr = swap_chain_->Present(&rect, + &rect, + window_, + NULL, + D3DPRESENT_INTERVAL_IMMEDIATE); + if (FAILED(hr)) + return false; + } + + return true; +} + +bool AcceleratedPresenter::CopyTo(const gfx::Size& size, void* buf) { + base::AutoLock locked(lock_); + + if (!swap_chain_) + return false; + + 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; + + // Set up intermediate buffers needed for downsampling. + const int resample_count = + GetResampleCount(gfx::Size(desc.Width, desc.Height), size); + base::win::ScopedComPtr<IDirect3DSurface9> final_surface; + base::win::ScopedComPtr<IDirect3DSurface9> temp_buffer[2]; + if (resample_count == 0) + final_surface = back_buffer; + if (resample_count > 0) { + if (!CreateTemporarySurface(present_thread_->device(), + size, + final_surface.Receive())) + return false; + } + const gfx::Size half_size = GetHalfSizeNoLessThan(back_buffer_size, size); + if (resample_count > 1) { + if (!CreateTemporarySurface(present_thread_->device(), + half_size, + temp_buffer[0].Receive())) + return false; + } + if (resample_count > 2) { + const gfx::Size quarter_size = GetHalfSizeNoLessThan(half_size, size); + if (!CreateTemporarySurface(present_thread_->device(), + quarter_size, + temp_buffer[1].Receive())) + return false; + } + + // Repeat downsampling the surface until its size becomes identical to + // |size|. We keep the factor of each downsampling no more than two because + // using a factor more than two can introduce aliasing. + gfx::Size read_size = back_buffer_size; + gfx::Size write_size = half_size; + int read_buffer_index = 1; + int write_buffer_index = 0; + for (int i = 0; i < resample_count; ++i) { + base::win::ScopedComPtr<IDirect3DSurface9> read_buffer = + (i == 0) ? back_buffer : temp_buffer[read_buffer_index]; + base::win::ScopedComPtr<IDirect3DSurface9> write_buffer = + (i == resample_count - 1) ? final_surface : + temp_buffer[write_buffer_index]; + RECT read_rect = {0, 0, read_size.width(), read_size.height()}; + RECT write_rect = {0, 0, write_size.width(), write_size.height()}; + hr = present_thread_->device()->StretchRect(read_buffer, + &read_rect, + write_buffer, + &write_rect, + D3DTEXF_LINEAR); + if (FAILED(hr)) + return false; + read_size = write_size; + write_size = GetHalfSizeNoLessThan(write_size, size); + std::swap(read_buffer_index, write_buffer_index); + } + + DCHECK(size == read_size); + + base::win::ScopedComPtr<IDirect3DSurface9> temp_surface; + HANDLE handle = reinterpret_cast<HANDLE>(buf); + hr = present_thread_->device()->CreateOffscreenPlainSurface( + size.width(), + size.height(), + D3DFMT_A8R8G8B8, + D3DPOOL_SYSTEMMEM, + temp_surface.Receive(), + &handle); + if (FAILED(hr)) + return false; + + // Copy the data in the temporary buffer to the surface backed by |buf|. + hr = present_thread_->device()->GetRenderTargetData(final_surface, + temp_surface); + if (FAILED(hr)) + return false; + + return true; +} + +void AcceleratedPresenter::Suspend() { + present_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&AcceleratedPresenter::DoSuspend, + this)); +} + +void AcceleratedPresenter::ReleaseSurface() { + present_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&AcceleratedPresenter::DoReleaseSurface, + this)); +} + +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(lock_); + window_ = NULL; +} + +AcceleratedPresenter::~AcceleratedPresenter() { +} + +static base::TimeDelta GetSwapDelay() { + CommandLine* cmd_line = CommandLine::ForCurrentProcess(); + int delay = 0; + if (cmd_line->HasSwitch(switches::kGpuSwapDelay)) { + base::StringToInt(cmd_line->GetSwitchValueNative( + switches::kGpuSwapDelay).c_str(), &delay); + } + return base::TimeDelta::FromMilliseconds(delay); +} + +void AcceleratedPresenter::DoPresentAndAcknowledge( + const gfx::Size& size, + int64 surface_handle, + const base::Callback<void(bool)>& completion_task) { + TRACE_EVENT1( + "surface", "DoPresentAndAcknowledge", "surface_handle", surface_handle); + + HRESULT hr; + + base::AutoLock locked(lock_); + + // Initialize the device lazily since calling Direct3D can crash bots. + present_thread_->InitDevice(); + + if (!present_thread_->device()) { + if (!completion_task.is_null()) + completion_task.Run(false); + return; + } + + // Ensure the task is always run and while the lock is taken. + base::ScopedClosureRunner scoped_completion_runner(base::Bind(completion_task, + true)); + + // If invalidated, do nothing, the window is gone. + if (!window_) + 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_ || size_ != quantized_size) { + TRACE_EVENT0("surface", "CreateAdditionalSwapChain"); + 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 = GetShellWindow(); + 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)) + return; + } + + if (!source_texture_.get()) { + TRACE_EVENT0("surface", "CreateTexture"); + HANDLE handle = reinterpret_cast<HANDLE>(surface_handle); + hr = present_thread_->device()->CreateTexture(size.width(), + size.height(), + 1, + D3DUSAGE_RENDERTARGET, + D3DFMT_A8R8G8B8, + D3DPOOL_DEFAULT, + source_texture_.Receive(), + &handle); + if (FAILED(hr)) + return; + } + + base::win::ScopedComPtr<IDirect3DSurface9> source_surface; + hr = source_texture_->GetSurfaceLevel(0, source_surface.Receive()); + if (FAILED(hr)) + return; + + base::win::ScopedComPtr<IDirect3DSurface9> dest_surface; + hr = swap_chain_->GetBackBuffer(0, + D3DBACKBUFFER_TYPE_MONO, + dest_surface.Receive()); + if (FAILED(hr)) + return; + + RECT rect = { + 0, 0, + size.width(), size.height() + }; + + { + TRACE_EVENT0("surface", "StretchRect"); + hr = present_thread_->device()->StretchRect(source_surface, + &rect, + dest_surface, + &rect, + D3DTEXF_NONE); + if (FAILED(hr)) + return; + } + + hr = present_thread_->query()->Issue(D3DISSUE_END); + if (FAILED(hr)) + return; + + // Flush so the StretchRect can be processed by the GPU while the window is + // being resized. + present_thread_->query()->GetData(NULL, 0, D3DGETDATA_FLUSH); + + ::SetWindowPos( + window_, + NULL, + 0, 0, + size.width(), size.height(), + SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE |SWP_NOOWNERZORDER | + SWP_NOREDRAW | SWP_NOSENDCHANGING | SWP_NOSENDCHANGING | + SWP_ASYNCWINDOWPOS | SWP_NOZORDER); + + // Wait for the StretchRect to complete before notifying the GPU process + // that it is safe to write to its backing store again. + { + TRACE_EVENT0("surface", "spin"); + do { + hr = present_thread_->query()->GetData(NULL, 0, D3DGETDATA_FLUSH); + + if (hr == S_FALSE) + Sleep(1); + } while (hr == S_FALSE); + } + + static const base::TimeDelta swap_delay = GetSwapDelay(); + if (swap_delay.ToInternalValue()) + base::PlatformThread::Sleep(swap_delay); + + { + TRACE_EVENT0("surface", "Present"); + hr = swap_chain_->Present(&rect, &rect, window_, NULL, 0); + if (FAILED(hr) && + FAILED(present_thread_->device()->CheckDeviceState(window_))) { + present_thread_->ResetDevice(); + } + } +} + +void AcceleratedPresenter::DoSuspend() { + base::AutoLock locked(lock_); + swap_chain_ = NULL; +} + +void AcceleratedPresenter::DoReleaseSurface() { + base::AutoLock locked(lock_); + source_texture_.Release(); +} + +AcceleratedSurface::AcceleratedSurface(gfx::NativeWindow window) + : presenter_(g_accelerated_presenter_map.Pointer()->CreatePresenter( + window)) { +} + +AcceleratedSurface::~AcceleratedSurface() { + g_accelerated_presenter_map.Pointer()->RemovePresenter(presenter_); + presenter_->Invalidate(); +} + +bool AcceleratedSurface::Present() { + return presenter_->Present(); +} + +bool AcceleratedSurface::CopyTo(const gfx::Size& size, void* buf) { + return presenter_->CopyTo(size, buf); +} + +void AcceleratedSurface::Suspend() { + presenter_->Suspend(); +} |