blob: bab1167fc09b19a13784ad6f148458fb79916f9b [file] [log] [blame]
// Copyright 2015 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 "services/gfx/compositor/backend/gpu_output.h"
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_event.h"
#include "services/gfx/compositor/render/render_frame.h"
namespace compositor {
namespace {
constexpr const char* kPipelineDepthSwitch = "pipeline-depth";
constexpr uint32_t kDefaultPipelineDepth = 1u;
constexpr uint32_t kMinPipelineDepth = 1u;
constexpr uint32_t kMaxPipelineDepth = 10u; // for experimentation
scoped_ptr<base::MessagePump> CreateMessagePumpMojo() {
return base::MessageLoop::CreateMessagePumpForType(
base::MessageLoop::TYPE_DEFAULT);
}
} // namespace
GpuOutput::GpuOutput(
mojo::InterfaceHandle<mojo::ContextProvider> context_provider,
const SchedulerCallbacks& scheduler_callbacks,
const base::Closure& error_callback)
: compositor_task_runner_(base::MessageLoop::current()->task_runner()),
vsync_scheduler_(
new VsyncScheduler(compositor_task_runner_, scheduler_callbacks)),
error_callback_(error_callback),
rasterizer_thread_(new base::Thread("gpu_rasterizer")),
rasterizer_initialized_(true, false) {
DCHECK(context_provider);
pipeline_depth_ = kDefaultPipelineDepth;
auto command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(kPipelineDepthSwitch)) {
std::string str(command_line->GetSwitchValueASCII(kPipelineDepthSwitch));
unsigned value;
if (base::StringToUint(str, &value) && value >= kMinPipelineDepth &&
value <= kMaxPipelineDepth) {
pipeline_depth_ = value;
} else {
LOG(ERROR) << "Invalid value for --" << kPipelineDepthSwitch << ": \""
<< str << "\"";
PostErrorCallback();
}
}
DVLOG(2) << "Using pipeline depth " << pipeline_depth_;
base::Thread::Options options;
options.message_pump_factory = base::Bind(&CreateMessagePumpMojo);
rasterizer_thread_->StartWithOptions(options);
rasterizer_task_runner_ = rasterizer_thread_->message_loop()->task_runner();
rasterizer_task_runner_->PostTask(
FROM_HERE,
base::Bind(&GpuOutput::InitializeRasterizer, base::Unretained(this),
base::Passed(std::move(context_provider))));
rasterizer_initialized_.Wait();
DCHECK(rasterizer_);
}
GpuOutput::~GpuOutput() {
// Ensure rasterizer destruction happens on the rasterizer thread.
rasterizer_task_runner_->PostTask(
FROM_HERE,
base::Bind(&GpuOutput::DestroyRasterizer, base::Unretained(this)));
rasterizer_thread_->Stop();
DCHECK(!rasterizer_);
}
Scheduler* GpuOutput::GetScheduler() {
return vsync_scheduler_.get();
}
void GpuOutput::SubmitFrame(const scoped_refptr<RenderFrame>& frame) {
DCHECK(frame);
TRACE_EVENT0("gfx", "GpuOutput::SubmitFrame");
const int64_t submit_time = MojoGetTimeTicksNow();
// Note: we may swap an old frame into |frame_data| to keep alive until
// we exit the lock.
std::unique_ptr<FrameData> frame_data(new FrameData(frame, submit_time));
{
std::lock_guard<std::mutex> lock(shared_state_.mutex);
// Enqueue the frame, ensuring that the queue only contains at most
// one pending or scheduled frame. If the last frame hasn't been drawn by
// now then the rasterizer must be falling behind.
if (shared_state_.frames.empty() ||
shared_state_.frames.back()->state == FrameData::State::Drawing) {
// The queue is busy drawing. Enqueue the new frame at the end.
shared_state_.frames.emplace(std::move(frame_data));
} else if (shared_state_.frames.back()->state ==
FrameData::State::Finished) {
// The queue contains a finished frame which we had retained to prevent
// the queue from becoming empty and losing track of the current frame.
// Replace it with the new frame.
DCHECK(shared_state_.frames.size() == 1u);
shared_state_.frames.back().swap(frame_data);
} else {
// The queue already contains a pending frame which means the rasterizer
// has gotten so far behind it wasn't even able to issue the previous
// undrawn frame. Replace it with the new frame, thereby ensuring
// the queue never contains more than one pending frame at a time.
DCHECK(shared_state_.frames.back()->state == FrameData::State::Pending);
shared_state_.frames.back().swap(frame_data);
TRACE_EVENT_FLOW_END1("gfx", "Frame Queued", frame_data.get(), "drawn",
false);
DVLOG(2) << "Rasterizer stalled, dropped a frame to catch up.";
}
TRACE_EVENT_FLOW_BEGIN0("gfx", "Frame Queued",
shared_state_.frames.back().get());
if (!shared_state_.rasterizer_ready)
return;
// TODO(jeffbrown): If the draw queue is overfull, we should pause
// scheduling until the queue drains.
if (shared_state_.frames.size() <= pipeline_depth_)
ScheduleDrawLocked();
}
}
void GpuOutput::OnRasterizerReady(int64_t vsync_timebase,
int64_t vsync_interval) {
DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
// TODO(jeffbrown): This shouldn't be hardcoded.
// Need to do some real tuning and possibly determine values adaptively.
// We should probably split the Start() method in two to separate the
// process of setting parameters from starting / stopping scheduling.
const int64_t update_phase = -vsync_interval;
const int64_t snapshot_phase = -vsync_interval / 6;
// TODO(jeffbrown): Determine the presentation phase based on queue depth.
const int64_t presentation_phase = vsync_interval * pipeline_depth_;
if (!vsync_scheduler_->Start(vsync_timebase, vsync_interval, update_phase,
snapshot_phase, presentation_phase)) {
LOG(ERROR) << "Received invalid vsync parameters: timebase="
<< vsync_timebase << ", interval=" << vsync_interval;
PostErrorCallback();
return;
}
{
std::lock_guard<std::mutex> lock(shared_state_.mutex);
if (shared_state_.rasterizer_ready)
return;
shared_state_.rasterizer_ready = true;
if (shared_state_.frames.empty())
return;
shared_state_.frames.back()->ResetDrawState();
TRACE_EVENT_FLOW_BEGIN0("gfx", "Frame Queued",
shared_state_.frames.back().get());
ScheduleDrawLocked();
}
}
void GpuOutput::OnRasterizerSuspended() {
DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
vsync_scheduler_->Stop();
{
std::lock_guard<std::mutex> lock(shared_state_.mutex);
if (!shared_state_.rasterizer_ready)
return;
shared_state_.rasterizer_ready = false;
}
}
void GpuOutput::OnRasterizerError() {
DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
PostErrorCallback();
}
void GpuOutput::ScheduleDrawLocked() {
DCHECK(!shared_state_.frames.empty());
DCHECK(shared_state_.frames.back()->state == FrameData::State::Pending);
DCHECK(shared_state_.frames.size() <= pipeline_depth_);
if (shared_state_.draw_scheduled)
return;
shared_state_.draw_scheduled = true;
rasterizer_task_runner_->PostTask(
FROM_HERE, base::Bind(&GpuOutput::OnDraw, base::Unretained(this)));
}
void GpuOutput::OnDraw() {
DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
TRACE_EVENT0("gfx", "GpuOutput::OnDraw");
FrameData* frame_data; // used outside lock
{
std::lock_guard<std::mutex> lock(shared_state_.mutex);
DCHECK(shared_state_.draw_scheduled);
DCHECK(!shared_state_.frames.empty());
DCHECK(shared_state_.frames.back()->state == FrameData::State::Pending);
shared_state_.draw_scheduled = false;
if (!shared_state_.rasterizer_ready)
return;
frame_data = shared_state_.frames.back().get();
frame_data->state = FrameData::State::Drawing;
frame_data->draw_started_time = MojoGetTimeTicksNow();
TRACE_EVENT_FLOW_END1("gfx", "Frame Queued", frame_data, "drawn", true);
}
// It is safe to access |frame_data| outside of the lock here because
// it will not be dequeued until |OnRasterizerFinishedDraw| gets posted
// to this thread's message loop. Moreover |SubmitFrame| will not discard
// or replace the frame because its state is |Drawing|.
TRACE_EVENT_ASYNC_BEGIN0("gfx", "Rasterize", frame_data);
rasterizer_->DrawFrame(frame_data->frame);
frame_data->draw_issued_time = MojoGetTimeTicksNow();
}
void GpuOutput::OnRasterizerFinishedDraw(bool presented) {
DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
TRACE_EVENT0("gfx", "GpuOutput::OnRasterizerFinishedDraw");
const int64_t finish_time = MojoGetTimeTicksNow();
// Note: we may swap an old frame into |old_frame_data| to keep alive until
// we exit the lock.
std::unique_ptr<FrameData> old_frame_data;
{
std::lock_guard<std::mutex> lock(shared_state_.mutex);
DCHECK(shared_state_.rasterizer_ready);
DCHECK(!shared_state_.frames.empty());
FrameData* frame_data = shared_state_.frames.front().get();
DCHECK(frame_data);
DCHECK(frame_data->state == FrameData::State::Drawing);
TRACE_EVENT_ASYNC_END1("gfx", "Rasterize", frame_data, "presented",
presented);
frame_data->state = FrameData::State::Finished;
// TODO(jeffbrown): Adjust scheduler behavior based on observed timing.
// Note: These measurements don't account for systematic downstream delay
// in the display pipeline (how long it takes pixels to actually light up).
if (presented) {
const RenderFrame::Metadata& frame_metadata =
frame_data->frame->metadata();
const mojo::gfx::composition::FrameInfo& frame_info =
frame_metadata.frame_info();
const int64_t frame_time = frame_info.frame_time;
const int64_t presentation_time = frame_info.presentation_time;
const int64_t composition_time = frame_metadata.composition_time();
const int64_t draw_started_time = frame_data->draw_started_time;
const int64_t draw_issued_time = frame_data->draw_issued_time;
const int64_t submit_time = frame_data->submit_time;
const size_t draw_queue_depth = shared_state_.frames.size();
DVLOG(2) << "Presented frame: composition latency "
<< (composition_time - frame_time) << " us, submission latency "
<< (submit_time - composition_time) << " us, queue latency "
<< (draw_started_time - submit_time) << " us, draw latency "
<< (draw_issued_time - draw_started_time) << " us, GPU latency "
<< (finish_time - draw_issued_time) << " us, total latency "
<< (finish_time - frame_time) << " us, presentation time error "
<< (finish_time - presentation_time) << " us"
<< ", draw queue depth " << draw_queue_depth;
} else {
DVLOG(2) << "Rasterizer dropped frame.";
}
if (shared_state_.frames.size() > 1u) {
shared_state_.frames.front().swap(old_frame_data);
shared_state_.frames.pop();
if (shared_state_.frames.back()->state == FrameData::State::Pending)
ScheduleDrawLocked();
}
}
}
void GpuOutput::InitializeRasterizer(
mojo::InterfaceHandle<mojo::ContextProvider> context_provider) {
DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
DCHECK(!rasterizer_);
TRACE_EVENT0("gfx", "GpuOutput::InitializeRasterizer");
rasterizer_.reset(new GpuRasterizer(
mojo::ContextProviderPtr::Create(std::move(context_provider)), this));
rasterizer_initialized_.Signal();
}
void GpuOutput::DestroyRasterizer() {
DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
DCHECK(rasterizer_);
TRACE_EVENT0("gfx", "GpuOutput::DestroyRasterizer");
rasterizer_.reset();
rasterizer_initialized_.Reset();
}
void GpuOutput::PostErrorCallback() {
compositor_task_runner_->PostTask(FROM_HERE, error_callback_);
}
GpuOutput::FrameData::FrameData(const scoped_refptr<RenderFrame>& frame,
int64_t submit_time)
: frame(frame), submit_time(submit_time) {}
GpuOutput::FrameData::~FrameData() {}
void GpuOutput::FrameData::ResetDrawState() {
state = State::Pending;
draw_started_time = 0;
draw_issued_time = 0;
}
} // namespace compositor