// Copyright (c) 2013 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 "cc/output/output_surface.h"
#include <algorithm>
#include <set>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "cc/output/compositor_frame.h"
#include "cc/output/compositor_frame_ack.h"
#include "cc/output/managed_memory_policy.h"
#include "cc/output/output_surface_client.h"
#include "cc/scheduler/delay_based_time_source.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/context_support.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "third_party/khronos/GLES2/gl2ext.h"
#include "ui/gfx/frame_time.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
using std::set;
using std::string;
using std::vector;
namespace {
const size_t kGpuLatencyHistorySize = 60;
const double kGpuLatencyEstimationPercentile = 100.0;
}
namespace cc {
OutputSurface::OutputSurface(scoped_refptr<ContextProvider> context_provider)
: context_provider_(context_provider),
device_scale_factor_(-1),
max_frames_pending_(0),
pending_swap_buffers_(0),
needs_begin_impl_frame_(false),
client_ready_for_begin_impl_frame_(true),
client_(NULL),
check_for_retroactive_begin_impl_frame_pending_(false),
external_stencil_test_enabled_(false),
weak_ptr_factory_(this),
gpu_latency_history_(kGpuLatencyHistorySize) {}
OutputSurface::OutputSurface(scoped_ptr<SoftwareOutputDevice> software_device)
: software_device_(software_device.Pass()),
device_scale_factor_(-1),
max_frames_pending_(0),
pending_swap_buffers_(0),
needs_begin_impl_frame_(false),
client_ready_for_begin_impl_frame_(true),
client_(NULL),
check_for_retroactive_begin_impl_frame_pending_(false),
external_stencil_test_enabled_(false),
weak_ptr_factory_(this),
gpu_latency_history_(kGpuLatencyHistorySize) {}
OutputSurface::OutputSurface(scoped_refptr<ContextProvider> context_provider,
scoped_ptr<SoftwareOutputDevice> software_device)
: context_provider_(context_provider),
software_device_(software_device.Pass()),
device_scale_factor_(-1),
max_frames_pending_(0),
pending_swap_buffers_(0),
needs_begin_impl_frame_(false),
client_ready_for_begin_impl_frame_(true),
client_(NULL),
check_for_retroactive_begin_impl_frame_pending_(false),
external_stencil_test_enabled_(false),
weak_ptr_factory_(this),
gpu_latency_history_(kGpuLatencyHistorySize) {}
void OutputSurface::InitializeBeginImplFrameEmulation(
base::SingleThreadTaskRunner* task_runner,
bool throttle_frame_production,
base::TimeDelta interval) {
if (throttle_frame_production) {
scoped_refptr<DelayBasedTimeSource> time_source;
if (gfx::FrameTime::TimestampsAreHighRes())
time_source = DelayBasedTimeSourceHighRes::Create(interval, task_runner);
else
time_source = DelayBasedTimeSource::Create(interval, task_runner);
frame_rate_controller_.reset(new FrameRateController(time_source));
} else {
frame_rate_controller_.reset(new FrameRateController(task_runner));
}
frame_rate_controller_->SetClient(this);
frame_rate_controller_->SetMaxSwapsPending(max_frames_pending_);
frame_rate_controller_->SetDeadlineAdjustment(
capabilities_.adjust_deadline_for_parent ?
BeginFrameArgs::DefaultDeadlineAdjustment() : base::TimeDelta());
// The new frame rate controller will consume the swap acks of the old
// frame rate controller, so we set that expectation up here.
for (int i = 0; i < pending_swap_buffers_; i++)
frame_rate_controller_->DidSwapBuffers();
}
void OutputSurface::SetMaxFramesPending(int max_frames_pending) {
if (frame_rate_controller_)
frame_rate_controller_->SetMaxSwapsPending(max_frames_pending);
max_frames_pending_ = max_frames_pending;
}
void OutputSurface::OnVSyncParametersChanged(base::TimeTicks timebase,
base::TimeDelta interval) {
TRACE_EVENT2("cc", "OutputSurface::OnVSyncParametersChanged",
"timebase", (timebase - base::TimeTicks()).InSecondsF(),
"interval", interval.InSecondsF());
if (frame_rate_controller_)
frame_rate_controller_->SetTimebaseAndInterval(timebase, interval);
}
void OutputSurface::FrameRateControllerTick(bool throttled,
const BeginFrameArgs& args) {
DCHECK(frame_rate_controller_);
if (throttled)
skipped_begin_impl_frame_args_ = args;
else
BeginImplFrame(args);
}
// Forwarded to OutputSurfaceClient
void OutputSurface::SetNeedsRedrawRect(gfx::Rect damage_rect) {
TRACE_EVENT0("cc", "OutputSurface::SetNeedsRedrawRect");
client_->SetNeedsRedrawRect(damage_rect);
}
void OutputSurface::SetNeedsBeginImplFrame(bool enable) {
TRACE_EVENT1("cc", "OutputSurface::SetNeedsBeginImplFrame", "enable", enable);
needs_begin_impl_frame_ = enable;
client_ready_for_begin_impl_frame_ = true;
if (frame_rate_controller_) {
BeginFrameArgs skipped = frame_rate_controller_->SetActive(enable);
if (skipped.IsValid())
skipped_begin_impl_frame_args_ = skipped;
}
if (needs_begin_impl_frame_)
PostCheckForRetroactiveBeginImplFrame();
}
void OutputSurface::BeginImplFrame(const BeginFrameArgs& args) {
TRACE_EVENT2("cc", "OutputSurface::BeginImplFrame",
"client_ready_for_begin_impl_frame_",
client_ready_for_begin_impl_frame_,
"pending_swap_buffers_", pending_swap_buffers_);
if (!needs_begin_impl_frame_ || !client_ready_for_begin_impl_frame_ ||
(pending_swap_buffers_ >= max_frames_pending_ &&
max_frames_pending_ > 0)) {
skipped_begin_impl_frame_args_ = args;
} else {
client_ready_for_begin_impl_frame_ = false;
client_->BeginImplFrame(args);
// args might be an alias for skipped_begin_impl_frame_args_.
// Do not reset it before calling BeginImplFrame!
skipped_begin_impl_frame_args_ = BeginFrameArgs();
}
}
base::TimeTicks OutputSurface::RetroactiveBeginImplFrameDeadline() {
// TODO(brianderson): Remove the alternative deadline once we have better
// deadline estimations.
base::TimeTicks alternative_deadline =
skipped_begin_impl_frame_args_.frame_time +
BeginFrameArgs::DefaultRetroactiveBeginFramePeriod();
return std::max(skipped_begin_impl_frame_args_.deadline,
alternative_deadline);
}
void OutputSurface::PostCheckForRetroactiveBeginImplFrame() {
if (!skipped_begin_impl_frame_args_.IsValid() ||
check_for_retroactive_begin_impl_frame_pending_)
return;
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&OutputSurface::CheckForRetroactiveBeginImplFrame,
weak_ptr_factory_.GetWeakPtr()));
check_for_retroactive_begin_impl_frame_pending_ = true;
}
void OutputSurface::CheckForRetroactiveBeginImplFrame() {
TRACE_EVENT0("cc", "OutputSurface::CheckForRetroactiveBeginImplFrame");
check_for_retroactive_begin_impl_frame_pending_ = false;
if (gfx::FrameTime::Now() < RetroactiveBeginImplFrameDeadline())
BeginImplFrame(skipped_begin_impl_frame_args_);
}
void OutputSurface::DidSwapBuffers() {
pending_swap_buffers_++;
TRACE_EVENT1("cc", "OutputSurface::DidSwapBuffers",
"pending_swap_buffers_", pending_swap_buffers_);
client_->DidSwapBuffers();
if (frame_rate_controller_)
frame_rate_controller_->DidSwapBuffers();
PostCheckForRetroactiveBeginImplFrame();
}
void OutputSurface::OnSwapBuffersComplete() {
pending_swap_buffers_--;
TRACE_EVENT1("cc", "OutputSurface::OnSwapBuffersComplete",
"pending_swap_buffers_", pending_swap_buffers_);
client_->OnSwapBuffersComplete();
if (frame_rate_controller_)
frame_rate_controller_->DidSwapBuffersComplete();
PostCheckForRetroactiveBeginImplFrame();
}
void OutputSurface::ReclaimResources(const CompositorFrameAck* ack) {
client_->ReclaimResources(ack);
}
void OutputSurface::DidLoseOutputSurface() {
TRACE_EVENT0("cc", "OutputSurface::DidLoseOutputSurface");
client_ready_for_begin_impl_frame_ = true;
pending_swap_buffers_ = 0;
skipped_begin_impl_frame_args_ = BeginFrameArgs();
if (frame_rate_controller_)
frame_rate_controller_->SetActive(false);
pending_gpu_latency_query_ids_.clear();
available_gpu_latency_query_ids_.clear();
client_->DidLoseOutputSurface();
}
void OutputSurface::SetExternalStencilTest(bool enabled) {
external_stencil_test_enabled_ = enabled;
}
void OutputSurface::SetExternalDrawConstraints(const gfx::Transform& transform,
gfx::Rect viewport,
gfx::Rect clip,
bool valid_for_tile_management) {
client_->SetExternalDrawConstraints(
transform, viewport, clip, valid_for_tile_management);
}
OutputSurface::~OutputSurface() {
if (frame_rate_controller_)
frame_rate_controller_->SetActive(false);
ResetContext3d();
}
bool OutputSurface::HasExternalStencilTest() const {
return external_stencil_test_enabled_;
}
bool OutputSurface::ForcedDrawToSoftwareDevice() const { return false; }
bool OutputSurface::BindToClient(OutputSurfaceClient* client) {
DCHECK(client);
client_ = client;
bool success = true;
if (context_provider_) {
success = context_provider_->BindToCurrentThread();
if (success)
SetUpContext3d();
}
if (!success)
client_ = NULL;
return success;
}
bool OutputSurface::InitializeAndSetContext3d(
scoped_refptr<ContextProvider> context_provider,
scoped_refptr<ContextProvider> offscreen_context_provider) {
DCHECK(!context_provider_);
DCHECK(context_provider);
DCHECK(client_);
bool success = false;
if (context_provider->BindToCurrentThread()) {
context_provider_ = context_provider;
SetUpContext3d();
if (client_->DeferredInitialize(offscreen_context_provider))
success = true;
}
if (!success)
ResetContext3d();
return success;
}
void OutputSurface::ReleaseGL() {
DCHECK(client_);
DCHECK(context_provider_);
client_->ReleaseGL();
ResetContext3d();
}
void OutputSurface::SetUpContext3d() {
DCHECK(context_provider_);
DCHECK(client_);
context_provider_->SetLostContextCallback(
base::Bind(&OutputSurface::DidLoseOutputSurface,
base::Unretained(this)));
context_provider_->ContextSupport()->SetSwapBuffersCompleteCallback(
base::Bind(&OutputSurface::OnSwapBuffersComplete,
base::Unretained(this)));
context_provider_->SetMemoryPolicyChangedCallback(
base::Bind(&OutputSurface::SetMemoryPolicy,
base::Unretained(this)));
}
void OutputSurface::ResetContext3d() {
if (context_provider_.get()) {
while (!pending_gpu_latency_query_ids_.empty()) {
unsigned query_id = pending_gpu_latency_query_ids_.front();
pending_gpu_latency_query_ids_.pop_front();
context_provider_->Context3d()->deleteQueryEXT(query_id);
}
while (!available_gpu_latency_query_ids_.empty()) {
unsigned query_id = available_gpu_latency_query_ids_.front();
available_gpu_latency_query_ids_.pop_front();
context_provider_->Context3d()->deleteQueryEXT(query_id);
}
context_provider_->SetLostContextCallback(
ContextProvider::LostContextCallback());
context_provider_->SetMemoryPolicyChangedCallback(
ContextProvider::MemoryPolicyChangedCallback());
if (gpu::ContextSupport* support = context_provider_->ContextSupport())
support->SetSwapBuffersCompleteCallback(base::Closure());
}
context_provider_ = NULL;
}
void OutputSurface::EnsureBackbuffer() {
if (software_device_)
software_device_->EnsureBackbuffer();
}
void OutputSurface::DiscardBackbuffer() {
if (context_provider_)
context_provider_->ContextGL()->DiscardBackbufferCHROMIUM();
if (software_device_)
software_device_->DiscardBackbuffer();
}
void OutputSurface::Reshape(gfx::Size size, float scale_factor) {
if (size == surface_size_ && scale_factor == device_scale_factor_)
return;
surface_size_ = size;
device_scale_factor_ = scale_factor;
if (context_provider_) {
context_provider_->Context3d()->reshapeWithScaleFactor(
size.width(), size.height(), scale_factor);
}
if (software_device_)
software_device_->Resize(size);
}
gfx::Size OutputSurface::SurfaceSize() const {
return surface_size_;
}
void OutputSurface::BindFramebuffer() {
DCHECK(context_provider_);
context_provider_->Context3d()->bindFramebuffer(GL_FRAMEBUFFER, 0);
}
void OutputSurface::SwapBuffers(CompositorFrame* frame) {
if (frame->software_frame_data) {
PostSwapBuffersComplete();
DidSwapBuffers();
return;
}
DCHECK(context_provider_);
DCHECK(frame->gl_frame_data);
UpdateAndMeasureGpuLatency();
if (frame->gl_frame_data->sub_buffer_rect ==
gfx::Rect(frame->gl_frame_data->size)) {
context_provider_->ContextSupport()->Swap();
} else {
context_provider_->ContextSupport()->PartialSwapBuffers(
frame->gl_frame_data->sub_buffer_rect);
}
DidSwapBuffers();
}
base::TimeDelta OutputSurface::GpuLatencyEstimate() {
if (context_provider_ && !capabilities_.adjust_deadline_for_parent)
return gpu_latency_history_.Percentile(kGpuLatencyEstimationPercentile);
else
return base::TimeDelta();
}
void OutputSurface::UpdateAndMeasureGpuLatency() {
return; // http://crbug.com/306690 tracks re-enabling latency queries.
// We only care about GPU latency for surfaces that do not have a parent
// compositor, since surfaces that do have a parent compositor can use
// mailboxes or delegated rendering to send frames to their parent without
// incurring GPU latency.
if (capabilities_.adjust_deadline_for_parent)
return;
while (pending_gpu_latency_query_ids_.size()) {
unsigned query_id = pending_gpu_latency_query_ids_.front();
unsigned query_complete = 1;
context_provider_->Context3d()->getQueryObjectuivEXT(
query_id, GL_QUERY_RESULT_AVAILABLE_EXT, &query_complete);
if (!query_complete)
break;
unsigned value = 0;
context_provider_->Context3d()->getQueryObjectuivEXT(
query_id, GL_QUERY_RESULT_EXT, &value);
pending_gpu_latency_query_ids_.pop_front();
available_gpu_latency_query_ids_.push_back(query_id);
base::TimeDelta latency = base::TimeDelta::FromMicroseconds(value);
base::TimeDelta latency_estimate = GpuLatencyEstimate();
gpu_latency_history_.InsertSample(latency);
base::TimeDelta latency_overestimate;
base::TimeDelta latency_underestimate;
if (latency > latency_estimate)
latency_underestimate = latency - latency_estimate;
else
latency_overestimate = latency_estimate - latency;
UMA_HISTOGRAM_CUSTOM_TIMES("Renderer.GpuLatency",
latency,
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMilliseconds(100),
50);
UMA_HISTOGRAM_CUSTOM_TIMES("Renderer.GpuLatencyUnderestimate",
latency_underestimate,
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMilliseconds(100),
50);
UMA_HISTOGRAM_CUSTOM_TIMES("Renderer.GpuLatencyOverestimate",
latency_overestimate,
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMilliseconds(100),
50);
}
unsigned gpu_latency_query_id;
if (available_gpu_latency_query_ids_.size()) {
gpu_latency_query_id = available_gpu_latency_query_ids_.front();
available_gpu_latency_query_ids_.pop_front();
} else {
gpu_latency_query_id = context_provider_->Context3d()->createQueryEXT();
}
context_provider_->Context3d()->beginQueryEXT(GL_LATENCY_QUERY_CHROMIUM,
gpu_latency_query_id);
context_provider_->Context3d()->endQueryEXT(GL_LATENCY_QUERY_CHROMIUM);
pending_gpu_latency_query_ids_.push_back(gpu_latency_query_id);
}
void OutputSurface::PostSwapBuffersComplete() {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&OutputSurface::OnSwapBuffersComplete,
weak_ptr_factory_.GetWeakPtr()));
}
void OutputSurface::SetMemoryPolicy(const ManagedMemoryPolicy& policy) {
TRACE_EVENT1("cc", "OutputSurface::SetMemoryPolicy",
"bytes_limit_when_visible", policy.bytes_limit_when_visible);
// Just ignore the memory manager when it says to set the limit to zero
// bytes. This will happen when the memory manager thinks that the renderer
// is not visible (which the renderer knows better).
if (policy.bytes_limit_when_visible)
client_->SetMemoryPolicy(policy);
}
} // namespace cc