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 |