// 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 #include #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 { 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(); // 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 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 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 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 CreatePresenter( gfx::PluginWindowHandle window); void RemovePresenter(const scoped_refptr& presenter); scoped_refptr 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 PresenterMap; PresenterMap presenters_; uint64 adapter_luid_; DISALLOW_COPY_AND_ASSIGN(AcceleratedPresenterMap); }; base::LazyInstance g_present_thread_pool = LAZY_INSTANCE_INITIALIZER; base::LazyInstance 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 AcceleratedPresenterMap::CreatePresenter( gfx::PluginWindowHandle window) { scoped_refptr 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& 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 AcceleratedPresenterMap::GetPresenter( gfx::PluginWindowHandle window) { base::AutoLock locked(lock_); PresenterMap::iterator it = presenters_.find(window); if (it == presenters_.end()) return scoped_refptr(); 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::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& 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& target, const base::Callback& 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 callback_runner, const base::Callback& 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& target, const scoped_refptr& callback_runner, const base::Callback& 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 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 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(bitmap->getPixels()), bitmap->width() * bitmap->bytesPerPixel(), bitmap->height(), static_cast(bitmap->rowBytes())); } bool AcceleratedPresenter::DoCopyToYUV( const gfx::Rect& requested_src_subrect, const scoped_refptr& 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 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 resized; base::win::ScopedComPtr 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 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( ¶meters, swap_chain_.Receive()); if (FAILED(hr)) { LOG(ERROR) << "Failed to create swap chain " << quantized_size.width() << " x " <device(), surface_handle, size, source_texture_.Receive())) { LOG(ERROR) << "Failed to open shared texture"; return; } } base::win::ScopedComPtr 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 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 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 system_surface; HRESULT hr = system_texture->GetSurfaceLevel(0, system_surface.Receive()); DCHECK(SUCCEEDED(hr)); base::win::ScopedComPtr 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& callback) { presenter_->AsyncCopyTo(src_subrect, dst_size, callback); } void AcceleratedSurface::AsyncCopyToVideoFrame( const gfx::Rect& src_subrect, const scoped_refptr& target, const base::Callback& 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); }