| Index: services/gfx/compositor/backend/gpu_output.cc
|
| diff --git a/services/gfx/compositor/backend/gpu_output.cc b/services/gfx/compositor/backend/gpu_output.cc
|
| index 37a51e4a264736907239b7b4dc824e07670937d3..531f2ee45f5ae9ef777c0e90988984231d446d7c 100644
|
| --- a/services/gfx/compositor/backend/gpu_output.cc
|
| +++ b/services/gfx/compositor/backend/gpu_output.cc
|
| @@ -7,159 +7,297 @@
|
| #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/backend/gpu_rasterizer.h"
|
| #include "services/gfx/compositor/render/render_frame.h"
|
|
|
| namespace compositor {
|
| namespace {
|
| -// Maximum number of frames to hold in the queue for rendering.
|
| -constexpr size_t kMaxPipelineDepth = 1;
|
| -}
|
| -
|
| -template <typename T>
|
| -static void Drop(scoped_ptr<T> ptr) {}
|
| +constexpr const char* kPipelineDepthSwitch = "pipeline-depth";
|
| +constexpr uint32_t kDefaultPipelineDepth = 2u; // ideally should be 1
|
| +constexpr uint32_t kMinPipelineDepth = 1u;
|
| +constexpr uint32_t kMaxPipelineDepth = 10u;
|
|
|
| -static scoped_ptr<base::MessagePump> CreateMessagePumpMojo() {
|
| +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)
|
| - : scheduler_(new VsyncScheduler(base::MessageLoop::current()->task_runner(),
|
| - scheduler_callbacks)),
|
| - rasterizer_delegate_(make_scoped_ptr(new RasterizerDelegate())) {
|
| + : compositor_task_runner_(base::MessageLoop::current()->task_runner()),
|
| + vsync_scheduler_(
|
| + new VsyncScheduler(compositor_task_runner_, scheduler_callbacks)),
|
| + rasterizer_thread_(new base::Thread("gpu_rasterizer")),
|
| + rasterizer_initialized_(true, false) {
|
| DCHECK(context_provider);
|
|
|
| - rasterizer_delegate_->PostInitialize(
|
| - std::move(context_provider), scheduler_,
|
| - base::MessageLoop::current()->task_runner(), error_callback);
|
| + 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 destruction happens on the correct thread.
|
| - rasterizer_delegate_->PostDestroy(rasterizer_delegate_.Pass());
|
| + // 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 scheduler_.get();
|
| + return vsync_scheduler_.get();
|
| }
|
|
|
| void GpuOutput::SubmitFrame(const scoped_refptr<RenderFrame>& frame) {
|
| - rasterizer_delegate_->PostFrame(frame);
|
| + DCHECK(frame);
|
| + TRACE_EVENT0("gfx", "GpuOutput::SubmitFrame");
|
| +
|
| + const int64_t submit_time = MojoGetTimeTicksNow();
|
| + scoped_refptr<FrameData> frame_data(
|
| + new FrameData(frame, submit_time)); // drop outside lock
|
| + {
|
| + std::lock_guard<std::mutex> lock(shared_state_.mutex);
|
| +
|
| + TRACE_EVENT_FLOW_BEGIN0("gfx", "Frame Queued", frame_data.get());
|
| + shared_state_.current_frame_data.swap(frame_data);
|
| + if (frame_data && !frame_data->drawn) {
|
| + // Dropped an undrawn frame.
|
| + DVLOG(2) << "Rasterizer stalled, dropping frame to catch up.";
|
| + TRACE_EVENT_FLOW_END1("gfx", "Frame Queued", frame_data.get(), "drawn",
|
| + false);
|
| + }
|
| +
|
| + // TODO(jeffbrown): If the draw queue is full, we should pause
|
| + // scheduling until the queue drains.
|
| + if (shared_state_.rasterizer_ready &&
|
| + shared_state_.drawn_frames_awaiting_finish.size() < pipeline_depth_)
|
| + ScheduleDrawLocked();
|
| + }
|
| }
|
|
|
| -GpuOutput::RasterizerDelegate::RasterizerDelegate() {
|
| - base::Thread::Options options;
|
| - options.message_pump_factory = base::Bind(&CreateMessagePumpMojo);
|
| +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;
|
| +
|
| + DCHECK(shared_state_.drawn_frames_awaiting_finish.empty());
|
| + shared_state_.rasterizer_ready = true;
|
|
|
| - thread_.reset(new base::Thread("gpu_rasterizer"));
|
| - thread_->StartWithOptions(options);
|
| - task_runner_ = thread_->message_loop()->task_runner();
|
| + if (!shared_state_.current_frame_data)
|
| + return;
|
| +
|
| + shared_state_.current_frame_data->Recycle();
|
| + TRACE_EVENT_FLOW_BEGIN0("gfx", "Frame Queued",
|
| + shared_state_.current_frame_data.get());
|
| + ScheduleDrawLocked();
|
| + }
|
| }
|
|
|
| -GpuOutput::RasterizerDelegate::~RasterizerDelegate() {}
|
| +void GpuOutput::OnRasterizerSuspended() {
|
| + DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
|
|
|
| -void GpuOutput::RasterizerDelegate::PostInitialize(
|
| - mojo::InterfaceHandle<mojo::ContextProvider> context_provider,
|
| - const scoped_refptr<VsyncScheduler>& scheduler,
|
| - const scoped_refptr<base::TaskRunner>& task_runner,
|
| - const base::Closure& error_callback) {
|
| - task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&RasterizerDelegate::InitializeTask, base::Unretained(this),
|
| - base::Passed(std::move(context_provider)), scheduler,
|
| - base::MessageLoop::current()->task_runner(), error_callback));
|
| + 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::RasterizerDelegate::PostDestroy(
|
| - scoped_ptr<RasterizerDelegate> self) {
|
| - task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&Drop<RasterizerDelegate>, base::Passed(&self)));
|
| +void GpuOutput::ScheduleDrawLocked() {
|
| + DCHECK(shared_state_.current_frame_data);
|
| + DCHECK(!shared_state_.current_frame_data->drawn);
|
| +
|
| + 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::RasterizerDelegate::PostFrame(
|
| - const scoped_refptr<RenderFrame>& frame) {
|
| - bool was_empty;
|
| - scoped_refptr<RenderFrame> dropped_frame;
|
| +void GpuOutput::OnDraw() {
|
| + DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
|
| + TRACE_EVENT0("gfx", "GpuOutput::OnDraw");
|
| +
|
| + scoped_refptr<FrameData> frame_data; // used outside lock
|
| {
|
| - std::lock_guard<std::mutex> lock(mutex_);
|
| - was_empty = frames_.empty();
|
| - if (frames_.size() == kMaxPipelineDepth) {
|
| - // TODO(jeffbrown): Adjust scheduler behavior to compensate.
|
| - LOG(ERROR) << "Renderer pipeline stalled, dropping a frame to catch up.";
|
| - dropped_frame = frames_.front(); // drop an old frame outside the lock
|
| - frames_.pop();
|
| - }
|
| - frames_.push(frame);
|
| + std::lock_guard<std::mutex> lock(shared_state_.mutex);
|
| +
|
| + DCHECK(shared_state_.draw_scheduled);
|
| + DCHECK(shared_state_.current_frame_data);
|
| + DCHECK(!shared_state_.current_frame_data->drawn);
|
| +
|
| + shared_state_.draw_scheduled = false;
|
| +
|
| + if (!shared_state_.rasterizer_ready ||
|
| + shared_state_.drawn_frames_awaiting_finish.size() >= pipeline_depth_)
|
| + return;
|
| +
|
| + frame_data = shared_state_.current_frame_data;
|
| + frame_data->drawn = true;
|
| + frame_data->draw_time = MojoGetTimeTicksNow();
|
| + TRACE_EVENT_FLOW_END1("gfx", "Frame Queued", frame_data.get(), "drawn",
|
| + true);
|
| +
|
| + TRACE_EVENT_ASYNC_BEGIN0("gfx", "Rasterize", frame_data.get());
|
| + shared_state_.drawn_frames_awaiting_finish.emplace(frame_data);
|
| }
|
|
|
| - if (was_empty)
|
| - PostSubmit();
|
| + rasterizer_->DrawFrame(frame_data->frame);
|
| + frame_data->wait_time = MojoGetTimeTicksNow();
|
| }
|
|
|
| -void GpuOutput::RasterizerDelegate::PostSubmit() {
|
| - TRACE_EVENT0("gfx", "GpuOutput::RasterizerDelegate::PostSubmit");
|
| - task_runner_->PostTask(FROM_HERE, base::Bind(&RasterizerDelegate::SubmitTask,
|
| - base::Unretained(this)));
|
| +void GpuOutput::OnRasterizerFinishedDraw(bool presented) {
|
| + DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
|
| + TRACE_EVENT0("gfx", "GpuOutput::OnRasterizerFinishedDraw");
|
| +
|
| + const int64_t finish_time = MojoGetTimeTicksNow();
|
| + scoped_refptr<FrameData> frame_data; // drop outside lock
|
| + {
|
| + std::lock_guard<std::mutex> lock(shared_state_.mutex);
|
| +
|
| + DCHECK(shared_state_.rasterizer_ready);
|
| + DCHECK(!shared_state_.drawn_frames_awaiting_finish.empty());
|
| + size_t draw_queue_depth = shared_state_.drawn_frames_awaiting_finish.size();
|
| + shared_state_.drawn_frames_awaiting_finish.front().swap(frame_data);
|
| + shared_state_.drawn_frames_awaiting_finish.pop();
|
| + DCHECK(frame_data);
|
| + DCHECK(frame_data->drawn);
|
| + TRACE_EVENT_ASYNC_END1("gfx", "Rasterize", frame_data.get(), "presented",
|
| + presented);
|
| +
|
| + // 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_time = frame_data->draw_time;
|
| + const int64_t wait_time = frame_data->wait_time;
|
| + const int64_t submit_time = frame_data->submit_time;
|
| +
|
| + DVLOG(2) << "Presented frame: composition latency "
|
| + << (composition_time - frame_time) << " us, submission latency "
|
| + << (submit_time - composition_time) << " us, queue latency "
|
| + << (draw_time - submit_time) << " us, draw latency "
|
| + << (wait_time - draw_time) << " us, GPU latency "
|
| + << (finish_time - wait_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.";
|
| + }
|
| +
|
| + DCHECK(shared_state_.current_frame_data);
|
| + if (!shared_state_.current_frame_data->drawn)
|
| + ScheduleDrawLocked();
|
| + }
|
| }
|
|
|
| -void GpuOutput::RasterizerDelegate::InitializeTask(
|
| - mojo::InterfaceHandle<mojo::ContextProvider> context_provider,
|
| - const scoped_refptr<VsyncScheduler>& scheduler,
|
| - const scoped_refptr<base::TaskRunner>& task_runner,
|
| - const base::Closure& error_callback) {
|
| +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)), scheduler,
|
| - task_runner, error_callback));
|
| + mojo::ContextProviderPtr::Create(std::move(context_provider)), this));
|
| + rasterizer_initialized_.Signal();
|
| }
|
|
|
| -void GpuOutput::RasterizerDelegate::SubmitTask() {
|
| - TRACE_EVENT0("gfx", "GpuOutput::RasterizerDelegate::SubmitTask");
|
| - bool have_more;
|
| - scoped_refptr<RenderFrame> frame;
|
| - {
|
| - std::lock_guard<std::mutex> lock(mutex_);
|
| - DCHECK(!frames_.empty());
|
| - frame = frames_.front();
|
| - frames_.pop();
|
| - have_more = !frames_.empty();
|
| - }
|
| +void GpuOutput::DestroyRasterizer() {
|
| + DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
|
| + DCHECK(rasterizer_);
|
| + TRACE_EVENT0("gfx", "GpuOutput::DestroyRasterizer");
|
|
|
| - if (have_more)
|
| - PostSubmit();
|
| + rasterizer_.reset();
|
| + rasterizer_initialized_.Reset();
|
| +}
|
|
|
| - int64_t submit_time = MojoGetTimeTicksNow();
|
| - rasterizer_->SubmitFrame(
|
| - frame, base::Bind(&RasterizerDelegate::OnFrameSubmitted,
|
| - base::Unretained(this), frame->frame_info().frame_time,
|
| - frame->frame_info().presentation_time, submit_time));
|
| +void GpuOutput::PostErrorCallback() {
|
| + compositor_task_runner_->PostTask(FROM_HERE, error_callback_);
|
| }
|
|
|
| -void GpuOutput::RasterizerDelegate::OnFrameSubmitted(int64_t frame_time,
|
| - int64_t presentation_time,
|
| - int64_t submit_time,
|
| - bool presented) {
|
| - TRACE_EVENT0("gfx", "GpuOutput::RasterizerDelegate::OnFrameSubmitted");
|
| - // 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).
|
| - int64_t complete_time = MojoGetTimeTicksNow();
|
| - if (presented) {
|
| - DVLOG(3) << "Frame presented: submission latency "
|
| - << (submit_time - frame_time) << " us, rasterization latency "
|
| - << (complete_time - submit_time) << " us, total latency "
|
| - << (complete_time - frame_time) << " us, presentation time error "
|
| - << (complete_time - presentation_time);
|
| - } else {
|
| - DVLOG(3) << "Frame deferred.";
|
| - }
|
| +GpuOutput::FrameData::FrameData(const scoped_refptr<RenderFrame>& frame,
|
| + int64_t submit_time)
|
| + : frame(frame), submit_time(submit_time) {}
|
| +
|
| +GpuOutput::FrameData::~FrameData() {}
|
| +
|
| +void GpuOutput::FrameData::Recycle() {
|
| + drawn = false;
|
| + draw_time = 0;
|
| + wait_time = 0;
|
| }
|
|
|
| } // namespace compositor
|
|
|