// 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/compositor/compositor.h" #include #include #include "base/bind.h" #include "base/command_line.h" #include "base/memory/singleton.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/string_util.h" #include "base/threading/thread.h" #include "base/threading/thread_restrictions.h" #include "cc/base/switches.h" #include "cc/input/input_handler.h" #include "cc/layers/layer.h" #include "cc/output/context_provider.h" #include "cc/output/output_surface.h" #include "cc/trees/layer_tree_host.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/compositor/compositor_observer.h" #include "ui/compositor/compositor_switches.h" #include "ui/compositor/context_provider_from_context_factory.h" #include "ui/compositor/dip_util.h" #include "ui/compositor/layer.h" #include "ui/compositor/reflector.h" #include "ui/compositor/test_web_graphics_context_3d.h" #include "ui/gl/gl_context.h" #include "ui/gl/gl_implementation.h" #include "ui/gl/gl_surface.h" #include "ui/gl/gl_switches.h" #include "webkit/common/gpu/grcontext_for_webgraphicscontext3d.h" #include "webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.h" #if defined(OS_CHROMEOS) #include "base/chromeos/chromeos_version.h" #endif namespace { const double kDefaultRefreshRate = 60.0; const double kTestRefreshRate = 200.0; enum SwapType { DRAW_SWAP, READPIXELS_SWAP, }; base::Thread* g_compositor_thread = NULL; bool g_test_compositor_enabled = false; ui::ContextFactory* g_implicit_factory = NULL; ui::ContextFactory* g_context_factory = NULL; const int kCompositorLockTimeoutMs = 67; class PendingSwap { public: PendingSwap(SwapType type, ui::PostedSwapQueue* posted_swaps); ~PendingSwap(); SwapType type() const { return type_; } bool posted() const { return posted_; } private: friend class ui::PostedSwapQueue; SwapType type_; bool posted_; ui::PostedSwapQueue* posted_swaps_; DISALLOW_COPY_AND_ASSIGN(PendingSwap); }; void SetupImplicitFactory() { // We leak the implicit factory so that we don't race with the tear down of // the gl_bindings. DCHECK(!g_context_factory); DCHECK(!g_implicit_factory); if (g_test_compositor_enabled) { g_implicit_factory = new ui::TestContextFactory; } else { DVLOG(1) << "Using DefaultContextFactory"; scoped_ptr instance( new ui::DefaultContextFactory()); if (instance->Initialize()) g_implicit_factory = instance.release(); } g_context_factory = g_implicit_factory; } void ResetImplicitFactory() { if (!g_implicit_factory || g_context_factory != g_implicit_factory) return; delete g_implicit_factory; g_implicit_factory = NULL; g_context_factory = NULL; } } // namespace namespace ui { // static ContextFactory* ContextFactory::GetInstance() { if (!g_context_factory) SetupImplicitFactory(); return g_context_factory; } // static void ContextFactory::SetInstance(ContextFactory* instance) { g_context_factory = instance; } DefaultContextFactory::DefaultContextFactory() { } DefaultContextFactory::~DefaultContextFactory() { } bool DefaultContextFactory::Initialize() { if (!gfx::GLSurface::InitializeOneOff() || gfx::GetGLImplementation() == gfx::kGLImplementationNone) { LOG(ERROR) << "Could not load the GL bindings"; return false; } return true; } scoped_ptr DefaultContextFactory::CreateOutputSurface( Compositor* compositor) { return make_scoped_ptr(new cc::OutputSurface( CreateContextCommon(compositor, false))); } scoped_ptr DefaultContextFactory::CreateOffscreenContext() { return CreateContextCommon(NULL, true); } scoped_refptr DefaultContextFactory::CreateReflector( Compositor* mirroed_compositor, Layer* mirroring_layer) { return NULL; } void DefaultContextFactory::RemoveReflector( scoped_refptr reflector) { } scoped_refptr DefaultContextFactory::OffscreenContextProviderForMainThread() { if (!offscreen_contexts_main_thread_.get() || !offscreen_contexts_main_thread_->DestroyedOnMainThread()) { offscreen_contexts_main_thread_ = ContextProviderFromContextFactory::CreateForOffscreen(this); if (offscreen_contexts_main_thread_.get() && !offscreen_contexts_main_thread_->BindToCurrentThread()) offscreen_contexts_main_thread_ = NULL; } return offscreen_contexts_main_thread_; } scoped_refptr DefaultContextFactory::OffscreenContextProviderForCompositorThread() { if (!offscreen_contexts_compositor_thread_.get() || !offscreen_contexts_compositor_thread_->DestroyedOnMainThread()) { offscreen_contexts_compositor_thread_ = ContextProviderFromContextFactory::CreateForOffscreen(this); } return offscreen_contexts_compositor_thread_; } void DefaultContextFactory::RemoveCompositor(Compositor* compositor) { } scoped_ptr DefaultContextFactory::CreateContextCommon(Compositor* compositor, bool offscreen) { DCHECK(offscreen || compositor); WebKit::WebGraphicsContext3D::Attributes attrs; attrs.depth = false; attrs.stencil = false; attrs.antialias = false; attrs.shareResources = true; using webkit::gpu::WebGraphicsContext3DInProcessCommandBufferImpl; if (offscreen) { return WebGraphicsContext3DInProcessCommandBufferImpl:: CreateOffscreenContext(attrs); } return WebGraphicsContext3DInProcessCommandBufferImpl::CreateViewContext( attrs, compositor->widget()); } TestContextFactory::TestContextFactory() {} TestContextFactory::~TestContextFactory() {} scoped_ptr TestContextFactory::CreateOutputSurface( Compositor* compositor) { return make_scoped_ptr(new cc::OutputSurface(CreateOffscreenContext())); } scoped_ptr TestContextFactory::CreateOffscreenContext() { scoped_ptr context( new ui::TestWebGraphicsContext3D); context->Initialize(); return context.PassAs(); } scoped_refptr TestContextFactory::CreateReflector( Compositor* mirrored_compositor, Layer* mirroring_layer) { return new Reflector(); } void TestContextFactory::RemoveReflector(scoped_refptr reflector) { } scoped_refptr TestContextFactory::OffscreenContextProviderForMainThread() { if (!offscreen_contexts_main_thread_.get() || offscreen_contexts_main_thread_->DestroyedOnMainThread()) { offscreen_contexts_main_thread_ = ContextProviderFromContextFactory::CreateForOffscreen(this); CHECK(offscreen_contexts_main_thread_->BindToCurrentThread()); } return offscreen_contexts_main_thread_; } scoped_refptr TestContextFactory::OffscreenContextProviderForCompositorThread() { if (!offscreen_contexts_compositor_thread_.get() || offscreen_contexts_compositor_thread_->DestroyedOnMainThread()) { offscreen_contexts_compositor_thread_ = ContextProviderFromContextFactory::CreateForOffscreen(this); } return offscreen_contexts_compositor_thread_; } void TestContextFactory::RemoveCompositor(Compositor* compositor) { } Texture::Texture(bool flipped, const gfx::Size& size, float device_scale_factor) : size_(size), flipped_(flipped), device_scale_factor_(device_scale_factor) { } Texture::~Texture() { } std::string Texture::Produce() { return EmptyString(); } CompositorLock::CompositorLock(Compositor* compositor) : compositor_(compositor) { base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&CompositorLock::CancelLock, AsWeakPtr()), base::TimeDelta::FromMilliseconds(kCompositorLockTimeoutMs)); } CompositorLock::~CompositorLock() { CancelLock(); } void CompositorLock::CancelLock() { if (!compositor_) return; compositor_->UnlockCompositor(); compositor_ = NULL; } // static void DrawWaiterForTest::Wait(Compositor* compositor) { DrawWaiterForTest waiter; waiter.wait_for_commit_ = false; waiter.WaitImpl(compositor); } // static void DrawWaiterForTest::WaitForCommit(Compositor* compositor) { DrawWaiterForTest waiter; waiter.wait_for_commit_ = true; waiter.WaitImpl(compositor); } DrawWaiterForTest::DrawWaiterForTest() { } DrawWaiterForTest::~DrawWaiterForTest() { } void DrawWaiterForTest::WaitImpl(Compositor* compositor) { compositor->AddObserver(this); wait_run_loop_.reset(new base::RunLoop()); wait_run_loop_->Run(); compositor->RemoveObserver(this); } void DrawWaiterForTest::OnCompositingDidCommit(Compositor* compositor) { if (wait_for_commit_) wait_run_loop_->Quit(); } void DrawWaiterForTest::OnCompositingStarted(Compositor* compositor, base::TimeTicks start_time) { } void DrawWaiterForTest::OnCompositingEnded(Compositor* compositor) { if (!wait_for_commit_) wait_run_loop_->Quit(); } void DrawWaiterForTest::OnCompositingAborted(Compositor* compositor) { } void DrawWaiterForTest::OnCompositingLockStateChanged(Compositor* compositor) { } void DrawWaiterForTest::OnUpdateVSyncParameters(Compositor* compositor, base::TimeTicks timebase, base::TimeDelta interval) { } class PostedSwapQueue { public: PostedSwapQueue() : pending_swap_(NULL) { } ~PostedSwapQueue() { DCHECK(!pending_swap_); } SwapType NextPostedSwap() const { return queue_.front(); } bool AreSwapsPosted() const { return !queue_.empty(); } int NumSwapsPosted(SwapType type) const { int count = 0; for (std::deque::const_iterator it = queue_.begin(); it != queue_.end(); ++it) { if (*it == type) count++; } return count; } void PostSwap() { DCHECK(pending_swap_); queue_.push_back(pending_swap_->type()); pending_swap_->posted_ = true; } void EndSwap() { queue_.pop_front(); } private: friend class ::PendingSwap; PendingSwap* pending_swap_; std::deque queue_; DISALLOW_COPY_AND_ASSIGN(PostedSwapQueue); }; } // namespace ui namespace { PendingSwap::PendingSwap(SwapType type, ui::PostedSwapQueue* posted_swaps) : type_(type), posted_(false), posted_swaps_(posted_swaps) { // Only one pending swap in flight. DCHECK_EQ(static_cast(NULL), posted_swaps_->pending_swap_); posted_swaps_->pending_swap_ = this; } PendingSwap::~PendingSwap() { DCHECK_EQ(this, posted_swaps_->pending_swap_); posted_swaps_->pending_swap_ = NULL; } } // namespace namespace ui { Compositor::Compositor(CompositorDelegate* delegate, gfx::AcceleratedWidget widget) : delegate_(delegate), root_layer_(NULL), widget_(widget), posted_swaps_(new PostedSwapQueue()), device_scale_factor_(0.0f), last_started_frame_(0), last_ended_frame_(0), next_draw_is_resize_(false), disable_schedule_composite_(false), compositor_lock_(NULL) { root_web_layer_ = cc::Layer::Create(); root_web_layer_->SetAnchorPoint(gfx::PointF(0.f, 0.f)); CommandLine* command_line = CommandLine::ForCurrentProcess(); cc::LayerTreeSettings settings; settings.refresh_rate = g_test_compositor_enabled ? kTestRefreshRate : kDefaultRefreshRate; settings.partial_swap_enabled = !command_line->HasSwitch(cc::switches::kUIDisablePartialSwap); settings.per_tile_painting_enabled = command_line->HasSwitch(cc::switches::kUIEnablePerTilePainting); // These flags should be mirrored by renderer versions in content/renderer/. settings.initial_debug_state.show_debug_borders = command_line->HasSwitch(cc::switches::kUIShowCompositedLayerBorders); settings.initial_debug_state.show_fps_counter = command_line->HasSwitch(cc::switches::kUIShowFPSCounter); settings.initial_debug_state.show_paint_rects = command_line->HasSwitch(switches::kUIShowPaintRects); settings.initial_debug_state.show_property_changed_rects = command_line->HasSwitch(cc::switches::kUIShowPropertyChangedRects); settings.initial_debug_state.show_surface_damage_rects = command_line->HasSwitch(cc::switches::kUIShowSurfaceDamageRects); settings.initial_debug_state.show_screen_space_rects = command_line->HasSwitch(cc::switches::kUIShowScreenSpaceRects); settings.initial_debug_state.show_replica_screen_space_rects = command_line->HasSwitch(cc::switches::kUIShowReplicaScreenSpaceRects); settings.initial_debug_state.show_occluding_rects = command_line->HasSwitch(cc::switches::kUIShowOccludingRects); settings.initial_debug_state.show_non_occluding_rects = command_line->HasSwitch(cc::switches::kUIShowNonOccludingRects); scoped_refptr compositor_task_runner = g_compositor_thread ? g_compositor_thread->message_loop_proxy() : NULL; host_ = cc::LayerTreeHost::Create(this, settings, compositor_task_runner); host_->SetRootLayer(root_web_layer_); host_->SetLayerTreeHostClientReady(); } Compositor::~Compositor() { CancelCompositorLock(); DCHECK(!compositor_lock_); // Don't call |CompositorDelegate::ScheduleDraw| from this point. delegate_ = NULL; if (root_layer_) root_layer_->SetCompositor(NULL); // Stop all outstanding draws before telling the ContextFactory to tear // down any contexts that the |host_| may rely upon. host_.reset(); ContextFactory::GetInstance()->RemoveCompositor(this); } // static void Compositor::Initialize() { #if defined(OS_CHROMEOS) bool use_thread = !CommandLine::ForCurrentProcess()->HasSwitch( switches::kUIDisableThreadedCompositing); #else bool use_thread = CommandLine::ForCurrentProcess()->HasSwitch( switches::kUIEnableThreadedCompositing) && !CommandLine::ForCurrentProcess()->HasSwitch( switches::kUIDisableThreadedCompositing); #endif if (use_thread) { g_compositor_thread = new base::Thread("Browser Compositor"); g_compositor_thread->Start(); } } // static bool Compositor::WasInitializedWithThread() { return !!g_compositor_thread; } // static scoped_refptr Compositor::GetCompositorMessageLoop() { scoped_refptr proxy; if (g_compositor_thread) proxy = g_compositor_thread->message_loop_proxy(); return proxy; } // static void Compositor::Terminate() { if (g_compositor_thread) { g_compositor_thread->Stop(); delete g_compositor_thread; g_compositor_thread = NULL; } } void Compositor::ScheduleDraw() { if (g_compositor_thread) host_->Composite(base::TimeTicks::Now()); else if (delegate_) delegate_->ScheduleDraw(); } void Compositor::SetRootLayer(Layer* root_layer) { if (root_layer_ == root_layer) return; if (root_layer_) root_layer_->SetCompositor(NULL); root_layer_ = root_layer; if (root_layer_ && !root_layer_->GetCompositor()) root_layer_->SetCompositor(this); root_web_layer_->RemoveAllChildren(); if (root_layer_) root_web_layer_->AddChild(root_layer_->cc_layer()); } void Compositor::SetHostHasTransparentBackground( bool host_has_transparent_background) { host_->set_has_transparent_background(host_has_transparent_background); } void Compositor::Draw() { DCHECK(!g_compositor_thread); if (!root_layer_) return; last_started_frame_++; PendingSwap pending_swap(DRAW_SWAP, posted_swaps_.get()); if (!IsLocked()) { // TODO(nduca): Temporary while compositor calls // compositeImmediately() directly. Layout(); host_->Composite(base::TimeTicks::Now()); #if defined(OS_WIN) // While we resize, we are usually a few frames behind. By blocking // the UI thread here we minize the area that is mis-painted, specially // in the non-client area. See RenderWidgetHostViewAura::SetBounds for // more details and bug 177115. if (next_draw_is_resize_ && (last_ended_frame_ > 1)) { next_draw_is_resize_ = false; host_->FinishAllRendering(); } #endif } if (!pending_swap.posted()) NotifyEnd(); } void Compositor::ScheduleFullRedraw() { host_->SetNeedsRedraw(); } void Compositor::ScheduleRedrawRect(const gfx::Rect& damage_rect) { host_->SetNeedsRedrawRect(damage_rect); } void Compositor::SetLatencyInfo(const ui::LatencyInfo& latency_info) { host_->SetLatencyInfo(latency_info); } bool Compositor::ReadPixels(SkBitmap* bitmap, const gfx::Rect& bounds_in_pixel) { if (bounds_in_pixel.right() > size().width() || bounds_in_pixel.bottom() > size().height()) return false; bitmap->setConfig(SkBitmap::kARGB_8888_Config, bounds_in_pixel.width(), bounds_in_pixel.height()); bitmap->allocPixels(); SkAutoLockPixels lock_image(*bitmap); unsigned char* pixels = static_cast(bitmap->getPixels()); CancelCompositorLock(); PendingSwap pending_swap(READPIXELS_SWAP, posted_swaps_.get()); return host_->CompositeAndReadback(pixels, bounds_in_pixel); } void Compositor::SetScaleAndSize(float scale, const gfx::Size& size_in_pixel) { DCHECK_GT(scale, 0); if (!size_in_pixel.IsEmpty()) { size_ = size_in_pixel; host_->SetViewportSize(size_in_pixel); root_web_layer_->SetBounds(size_in_pixel); next_draw_is_resize_ = true; } if (device_scale_factor_ != scale) { device_scale_factor_ = scale; if (root_layer_) root_layer_->OnDeviceScaleFactorChanged(scale); } } void Compositor::SetBackgroundColor(SkColor color) { host_->set_background_color(color); ScheduleDraw(); } void Compositor::AddObserver(CompositorObserver* observer) { observer_list_.AddObserver(observer); } void Compositor::RemoveObserver(CompositorObserver* observer) { observer_list_.RemoveObserver(observer); } bool Compositor::HasObserver(CompositorObserver* observer) { return observer_list_.HasObserver(observer); } void Compositor::OnSwapBuffersPosted() { DCHECK(!g_compositor_thread); posted_swaps_->PostSwap(); } void Compositor::OnSwapBuffersComplete() { DCHECK(!g_compositor_thread); DCHECK(posted_swaps_->AreSwapsPosted()); DCHECK_GE(1, posted_swaps_->NumSwapsPosted(DRAW_SWAP)); if (posted_swaps_->NextPostedSwap() == DRAW_SWAP) NotifyEnd(); posted_swaps_->EndSwap(); } void Compositor::OnSwapBuffersAborted() { if (!g_compositor_thread) { DCHECK_GE(1, posted_swaps_->NumSwapsPosted(DRAW_SWAP)); // We've just lost the context, so unwind all posted_swaps. while (posted_swaps_->AreSwapsPosted()) { if (posted_swaps_->NextPostedSwap() == DRAW_SWAP) NotifyEnd(); posted_swaps_->EndSwap(); } } FOR_EACH_OBSERVER(CompositorObserver, observer_list_, OnCompositingAborted(this)); } void Compositor::OnUpdateVSyncParameters(base::TimeTicks timebase, base::TimeDelta interval) { FOR_EACH_OBSERVER(CompositorObserver, observer_list_, OnUpdateVSyncParameters(this, timebase, interval)); } void Compositor::Layout() { // We're sending damage that will be addressed during this composite // cycle, so we don't need to schedule another composite to address it. disable_schedule_composite_ = true; if (root_layer_) root_layer_->SendDamagedRects(); disable_schedule_composite_ = false; } scoped_ptr Compositor::CreateOutputSurface(bool fallback) { return ContextFactory::GetInstance()->CreateOutputSurface(this); } void Compositor::DidCommit() { DCHECK(!IsLocked()); FOR_EACH_OBSERVER(CompositorObserver, observer_list_, OnCompositingDidCommit(this)); } void Compositor::DidCommitAndDrawFrame() { base::TimeTicks start_time = base::TimeTicks::Now(); FOR_EACH_OBSERVER(CompositorObserver, observer_list_, OnCompositingStarted(this, start_time)); } void Compositor::DidCompleteSwapBuffers() { DCHECK(g_compositor_thread); NotifyEnd(); } void Compositor::ScheduleComposite() { if (!disable_schedule_composite_) ScheduleDraw(); } scoped_refptr Compositor::OffscreenContextProviderForMainThread() { return ContextFactory::GetInstance()->OffscreenContextProviderForMainThread(); } scoped_refptr Compositor::OffscreenContextProviderForCompositorThread() { return ContextFactory::GetInstance()-> OffscreenContextProviderForCompositorThread(); } const cc::LayerTreeDebugState& Compositor::GetLayerTreeDebugState() const { return host_->debug_state(); } void Compositor::SetLayerTreeDebugState( const cc::LayerTreeDebugState& debug_state) { host_->SetDebugState(debug_state); } scoped_refptr Compositor::GetCompositorLock() { if (!compositor_lock_) { compositor_lock_ = new CompositorLock(this); if (g_compositor_thread) host_->SetDeferCommits(true); FOR_EACH_OBSERVER(CompositorObserver, observer_list_, OnCompositingLockStateChanged(this)); } return compositor_lock_; } void Compositor::UnlockCompositor() { DCHECK(compositor_lock_); compositor_lock_ = NULL; if (g_compositor_thread) host_->SetDeferCommits(false); FOR_EACH_OBSERVER(CompositorObserver, observer_list_, OnCompositingLockStateChanged(this)); } void Compositor::CancelCompositorLock() { if (compositor_lock_) compositor_lock_->CancelLock(); } void Compositor::NotifyEnd() { last_ended_frame_++; FOR_EACH_OBSERVER(CompositorObserver, observer_list_, OnCompositingEnded(this)); } COMPOSITOR_EXPORT void SetupTestCompositor() { if (!CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableTestCompositor)) { g_test_compositor_enabled = true; } #if defined(OS_CHROMEOS) // If the test is running on the chromeos envrionment (such as // device or vm bots), use the real compositor. if (base::chromeos::IsRunningOnChromeOS()) g_test_compositor_enabled = false; #endif ResetImplicitFactory(); } COMPOSITOR_EXPORT void DisableTestCompositor() { ResetImplicitFactory(); g_test_compositor_enabled = false; } COMPOSITOR_EXPORT bool IsTestCompositorEnabled() { return g_test_compositor_enabled; } } // namespace ui