| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "services/gfx/compositor/backend/gpu_output.h" | 5 #include "services/gfx/compositor/backend/gpu_output.h" |
| 6 | 6 |
| 7 #include <utility> | 7 #include <utility> |
| 8 | 8 |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #include "base/command_line.h" |
| 10 #include "base/location.h" | 11 #include "base/location.h" |
| 11 #include "base/logging.h" | 12 #include "base/logging.h" |
| 12 #include "base/message_loop/message_loop.h" | 13 #include "base/message_loop/message_loop.h" |
| 14 #include "base/strings/string_number_conversions.h" |
| 13 #include "base/trace_event/trace_event.h" | 15 #include "base/trace_event/trace_event.h" |
| 14 #include "services/gfx/compositor/backend/gpu_rasterizer.h" | |
| 15 #include "services/gfx/compositor/render/render_frame.h" | 16 #include "services/gfx/compositor/render/render_frame.h" |
| 16 | 17 |
| 17 namespace compositor { | 18 namespace compositor { |
| 18 namespace { | 19 namespace { |
| 19 // Maximum number of frames to hold in the queue for rendering. | 20 constexpr const char* kPipelineDepthSwitch = "pipeline-depth"; |
| 20 constexpr size_t kMaxPipelineDepth = 1; | 21 constexpr uint32_t kDefaultPipelineDepth = 2u; // ideally should be 1 |
| 21 } | 22 constexpr uint32_t kMinPipelineDepth = 1u; |
| 22 | 23 constexpr uint32_t kMaxPipelineDepth = 10u; |
| 23 template <typename T> | 24 |
| 24 static void Drop(scoped_ptr<T> ptr) {} | 25 scoped_ptr<base::MessagePump> CreateMessagePumpMojo() { |
| 25 | |
| 26 static scoped_ptr<base::MessagePump> CreateMessagePumpMojo() { | |
| 27 return base::MessageLoop::CreateMessagePumpForType( | 26 return base::MessageLoop::CreateMessagePumpForType( |
| 28 base::MessageLoop::TYPE_DEFAULT); | 27 base::MessageLoop::TYPE_DEFAULT); |
| 29 } | 28 } |
| 29 } // namespace |
| 30 | 30 |
| 31 GpuOutput::GpuOutput( | 31 GpuOutput::GpuOutput( |
| 32 mojo::InterfaceHandle<mojo::ContextProvider> context_provider, | 32 mojo::InterfaceHandle<mojo::ContextProvider> context_provider, |
| 33 const SchedulerCallbacks& scheduler_callbacks, | 33 const SchedulerCallbacks& scheduler_callbacks, |
| 34 const base::Closure& error_callback) | 34 const base::Closure& error_callback) |
| 35 : scheduler_(new VsyncScheduler(base::MessageLoop::current()->task_runner(), | 35 : compositor_task_runner_(base::MessageLoop::current()->task_runner()), |
| 36 scheduler_callbacks)), | 36 vsync_scheduler_( |
| 37 rasterizer_delegate_(make_scoped_ptr(new RasterizerDelegate())) { | 37 new VsyncScheduler(compositor_task_runner_, scheduler_callbacks)), |
| 38 rasterizer_thread_(new base::Thread("gpu_rasterizer")), |
| 39 rasterizer_initialized_(true, false) { |
| 38 DCHECK(context_provider); | 40 DCHECK(context_provider); |
| 39 | 41 |
| 40 rasterizer_delegate_->PostInitialize( | 42 pipeline_depth_ = kDefaultPipelineDepth; |
| 41 std::move(context_provider), scheduler_, | 43 auto command_line = base::CommandLine::ForCurrentProcess(); |
| 42 base::MessageLoop::current()->task_runner(), error_callback); | 44 if (command_line->HasSwitch(kPipelineDepthSwitch)) { |
| 43 } | 45 std::string str(command_line->GetSwitchValueASCII(kPipelineDepthSwitch)); |
| 44 | 46 unsigned value; |
| 45 GpuOutput::~GpuOutput() { | 47 if (base::StringToUint(str, &value) && value >= kMinPipelineDepth && |
| 46 // Ensure destruction happens on the correct thread. | 48 value <= kMaxPipelineDepth) { |
| 47 rasterizer_delegate_->PostDestroy(rasterizer_delegate_.Pass()); | 49 pipeline_depth_ = value; |
| 48 } | 50 } else { |
| 49 | 51 LOG(ERROR) << "Invalid value for --" << kPipelineDepthSwitch << ": \"" |
| 50 Scheduler* GpuOutput::GetScheduler() { | 52 << str << "\""; |
| 51 return scheduler_.get(); | 53 PostErrorCallback(); |
| 52 } | 54 } |
| 53 | 55 } |
| 54 void GpuOutput::SubmitFrame(const scoped_refptr<RenderFrame>& frame) { | 56 DVLOG(2) << "Using pipeline depth " << pipeline_depth_; |
| 55 rasterizer_delegate_->PostFrame(frame); | 57 |
| 56 } | |
| 57 | |
| 58 GpuOutput::RasterizerDelegate::RasterizerDelegate() { | |
| 59 base::Thread::Options options; | 58 base::Thread::Options options; |
| 60 options.message_pump_factory = base::Bind(&CreateMessagePumpMojo); | 59 options.message_pump_factory = base::Bind(&CreateMessagePumpMojo); |
| 61 | 60 rasterizer_thread_->StartWithOptions(options); |
| 62 thread_.reset(new base::Thread("gpu_rasterizer")); | 61 rasterizer_task_runner_ = rasterizer_thread_->message_loop()->task_runner(); |
| 63 thread_->StartWithOptions(options); | 62 rasterizer_task_runner_->PostTask( |
| 64 task_runner_ = thread_->message_loop()->task_runner(); | |
| 65 } | |
| 66 | |
| 67 GpuOutput::RasterizerDelegate::~RasterizerDelegate() {} | |
| 68 | |
| 69 void GpuOutput::RasterizerDelegate::PostInitialize( | |
| 70 mojo::InterfaceHandle<mojo::ContextProvider> context_provider, | |
| 71 const scoped_refptr<VsyncScheduler>& scheduler, | |
| 72 const scoped_refptr<base::TaskRunner>& task_runner, | |
| 73 const base::Closure& error_callback) { | |
| 74 task_runner_->PostTask( | |
| 75 FROM_HERE, | 63 FROM_HERE, |
| 76 base::Bind(&RasterizerDelegate::InitializeTask, base::Unretained(this), | 64 base::Bind(&GpuOutput::InitializeRasterizer, base::Unretained(this), |
| 77 base::Passed(std::move(context_provider)), scheduler, | 65 base::Passed(std::move(context_provider)))); |
| 78 base::MessageLoop::current()->task_runner(), error_callback)); | 66 rasterizer_initialized_.Wait(); |
| 79 } | 67 DCHECK(rasterizer_); |
| 80 | 68 } |
| 81 void GpuOutput::RasterizerDelegate::PostDestroy( | 69 |
| 82 scoped_ptr<RasterizerDelegate> self) { | 70 GpuOutput::~GpuOutput() { |
| 83 task_runner_->PostTask( | 71 // Ensure rasterizer destruction happens on the rasterizer thread. |
| 84 FROM_HERE, base::Bind(&Drop<RasterizerDelegate>, base::Passed(&self))); | 72 rasterizer_task_runner_->PostTask( |
| 85 } | 73 FROM_HERE, |
| 86 | 74 base::Bind(&GpuOutput::DestroyRasterizer, base::Unretained(this))); |
| 87 void GpuOutput::RasterizerDelegate::PostFrame( | 75 rasterizer_thread_->Stop(); |
| 88 const scoped_refptr<RenderFrame>& frame) { | 76 DCHECK(!rasterizer_); |
| 89 bool was_empty; | 77 } |
| 90 scoped_refptr<RenderFrame> dropped_frame; | 78 |
| 91 { | 79 Scheduler* GpuOutput::GetScheduler() { |
| 92 std::lock_guard<std::mutex> lock(mutex_); | 80 return vsync_scheduler_.get(); |
| 93 was_empty = frames_.empty(); | 81 } |
| 94 if (frames_.size() == kMaxPipelineDepth) { | 82 |
| 95 // TODO(jeffbrown): Adjust scheduler behavior to compensate. | 83 void GpuOutput::SubmitFrame(const scoped_refptr<RenderFrame>& frame) { |
| 96 LOG(ERROR) << "Renderer pipeline stalled, dropping a frame to catch up."; | 84 DCHECK(frame); |
| 97 dropped_frame = frames_.front(); // drop an old frame outside the lock | 85 TRACE_EVENT0("gfx", "GpuOutput::SubmitFrame"); |
| 98 frames_.pop(); | 86 |
| 87 const int64_t submit_time = MojoGetTimeTicksNow(); |
| 88 scoped_refptr<FrameData> frame_data( |
| 89 new FrameData(frame, submit_time)); // drop outside lock |
| 90 { |
| 91 std::lock_guard<std::mutex> lock(shared_state_.mutex); |
| 92 |
| 93 TRACE_EVENT_FLOW_BEGIN0("gfx", "Frame Queued", frame_data.get()); |
| 94 shared_state_.current_frame_data.swap(frame_data); |
| 95 if (frame_data && !frame_data->drawn) { |
| 96 // Dropped an undrawn frame. |
| 97 DVLOG(2) << "Rasterizer stalled, dropping frame to catch up."; |
| 98 TRACE_EVENT_FLOW_END1("gfx", "Frame Queued", frame_data.get(), "drawn", |
| 99 false); |
| 99 } | 100 } |
| 100 frames_.push(frame); | 101 |
| 101 } | 102 // TODO(jeffbrown): If the draw queue is full, we should pause |
| 102 | 103 // scheduling until the queue drains. |
| 103 if (was_empty) | 104 if (shared_state_.rasterizer_ready && |
| 104 PostSubmit(); | 105 shared_state_.drawn_frames_awaiting_finish.size() < pipeline_depth_) |
| 105 } | 106 ScheduleDrawLocked(); |
| 106 | 107 } |
| 107 void GpuOutput::RasterizerDelegate::PostSubmit() { | 108 } |
| 108 TRACE_EVENT0("gfx", "GpuOutput::RasterizerDelegate::PostSubmit"); | 109 |
| 109 task_runner_->PostTask(FROM_HERE, base::Bind(&RasterizerDelegate::SubmitTask, | 110 void GpuOutput::OnRasterizerReady(int64_t vsync_timebase, |
| 110 base::Unretained(this))); | 111 int64_t vsync_interval) { |
| 111 } | 112 DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread()); |
| 112 | 113 |
| 113 void GpuOutput::RasterizerDelegate::InitializeTask( | 114 // TODO(jeffbrown): This shouldn't be hardcoded. |
| 114 mojo::InterfaceHandle<mojo::ContextProvider> context_provider, | 115 // Need to do some real tuning and possibly determine values adaptively. |
| 115 const scoped_refptr<VsyncScheduler>& scheduler, | 116 // We should probably split the Start() method in two to separate the |
| 116 const scoped_refptr<base::TaskRunner>& task_runner, | 117 // process of setting parameters from starting / stopping scheduling. |
| 117 const base::Closure& error_callback) { | 118 const int64_t update_phase = -vsync_interval; |
| 119 const int64_t snapshot_phase = -vsync_interval / 6; |
| 120 // TODO(jeffbrown): Determine the presentation phase based on queue depth. |
| 121 const int64_t presentation_phase = vsync_interval * pipeline_depth_; |
| 122 if (!vsync_scheduler_->Start(vsync_timebase, vsync_interval, update_phase, |
| 123 snapshot_phase, presentation_phase)) { |
| 124 LOG(ERROR) << "Received invalid vsync parameters: timebase=" |
| 125 << vsync_timebase << ", interval=" << vsync_interval; |
| 126 PostErrorCallback(); |
| 127 return; |
| 128 } |
| 129 |
| 130 { |
| 131 std::lock_guard<std::mutex> lock(shared_state_.mutex); |
| 132 |
| 133 if (shared_state_.rasterizer_ready) |
| 134 return; |
| 135 |
| 136 DCHECK(shared_state_.drawn_frames_awaiting_finish.empty()); |
| 137 shared_state_.rasterizer_ready = true; |
| 138 |
| 139 if (!shared_state_.current_frame_data) |
| 140 return; |
| 141 |
| 142 shared_state_.current_frame_data->Recycle(); |
| 143 TRACE_EVENT_FLOW_BEGIN0("gfx", "Frame Queued", |
| 144 shared_state_.current_frame_data.get()); |
| 145 ScheduleDrawLocked(); |
| 146 } |
| 147 } |
| 148 |
| 149 void GpuOutput::OnRasterizerSuspended() { |
| 150 DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread()); |
| 151 |
| 152 vsync_scheduler_->Stop(); |
| 153 |
| 154 { |
| 155 std::lock_guard<std::mutex> lock(shared_state_.mutex); |
| 156 |
| 157 if (!shared_state_.rasterizer_ready) |
| 158 return; |
| 159 |
| 160 shared_state_.rasterizer_ready = false; |
| 161 } |
| 162 } |
| 163 |
| 164 void GpuOutput::OnRasterizerError() { |
| 165 DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread()); |
| 166 |
| 167 PostErrorCallback(); |
| 168 } |
| 169 |
| 170 void GpuOutput::ScheduleDrawLocked() { |
| 171 DCHECK(shared_state_.current_frame_data); |
| 172 DCHECK(!shared_state_.current_frame_data->drawn); |
| 173 |
| 174 if (shared_state_.draw_scheduled) |
| 175 return; |
| 176 |
| 177 shared_state_.draw_scheduled = true; |
| 178 rasterizer_task_runner_->PostTask( |
| 179 FROM_HERE, base::Bind(&GpuOutput::OnDraw, base::Unretained(this))); |
| 180 } |
| 181 |
| 182 void GpuOutput::OnDraw() { |
| 183 DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread()); |
| 184 TRACE_EVENT0("gfx", "GpuOutput::OnDraw"); |
| 185 |
| 186 scoped_refptr<FrameData> frame_data; // used outside lock |
| 187 { |
| 188 std::lock_guard<std::mutex> lock(shared_state_.mutex); |
| 189 |
| 190 DCHECK(shared_state_.draw_scheduled); |
| 191 DCHECK(shared_state_.current_frame_data); |
| 192 DCHECK(!shared_state_.current_frame_data->drawn); |
| 193 |
| 194 shared_state_.draw_scheduled = false; |
| 195 |
| 196 if (!shared_state_.rasterizer_ready || |
| 197 shared_state_.drawn_frames_awaiting_finish.size() >= pipeline_depth_) |
| 198 return; |
| 199 |
| 200 frame_data = shared_state_.current_frame_data; |
| 201 frame_data->drawn = true; |
| 202 frame_data->draw_time = MojoGetTimeTicksNow(); |
| 203 TRACE_EVENT_FLOW_END1("gfx", "Frame Queued", frame_data.get(), "drawn", |
| 204 true); |
| 205 |
| 206 TRACE_EVENT_ASYNC_BEGIN0("gfx", "Rasterize", frame_data.get()); |
| 207 shared_state_.drawn_frames_awaiting_finish.emplace(frame_data); |
| 208 } |
| 209 |
| 210 rasterizer_->DrawFrame(frame_data->frame); |
| 211 frame_data->wait_time = MojoGetTimeTicksNow(); |
| 212 } |
| 213 |
| 214 void GpuOutput::OnRasterizerFinishedDraw(bool presented) { |
| 215 DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread()); |
| 216 TRACE_EVENT0("gfx", "GpuOutput::OnRasterizerFinishedDraw"); |
| 217 |
| 218 const int64_t finish_time = MojoGetTimeTicksNow(); |
| 219 scoped_refptr<FrameData> frame_data; // drop outside lock |
| 220 { |
| 221 std::lock_guard<std::mutex> lock(shared_state_.mutex); |
| 222 |
| 223 DCHECK(shared_state_.rasterizer_ready); |
| 224 DCHECK(!shared_state_.drawn_frames_awaiting_finish.empty()); |
| 225 size_t draw_queue_depth = shared_state_.drawn_frames_awaiting_finish.size(); |
| 226 shared_state_.drawn_frames_awaiting_finish.front().swap(frame_data); |
| 227 shared_state_.drawn_frames_awaiting_finish.pop(); |
| 228 DCHECK(frame_data); |
| 229 DCHECK(frame_data->drawn); |
| 230 TRACE_EVENT_ASYNC_END1("gfx", "Rasterize", frame_data.get(), "presented", |
| 231 presented); |
| 232 |
| 233 // TODO(jeffbrown): Adjust scheduler behavior based on observed timing. |
| 234 // Note: These measurements don't account for systematic downstream delay |
| 235 // in the display pipeline (how long it takes pixels to actually light up). |
| 236 if (presented) { |
| 237 const RenderFrame::Metadata& frame_metadata = |
| 238 frame_data->frame->metadata(); |
| 239 const mojo::gfx::composition::FrameInfo& frame_info = |
| 240 frame_metadata.frame_info(); |
| 241 const int64_t frame_time = frame_info.frame_time; |
| 242 const int64_t presentation_time = frame_info.presentation_time; |
| 243 const int64_t composition_time = frame_metadata.composition_time(); |
| 244 const int64_t draw_time = frame_data->draw_time; |
| 245 const int64_t wait_time = frame_data->wait_time; |
| 246 const int64_t submit_time = frame_data->submit_time; |
| 247 |
| 248 DVLOG(2) << "Presented frame: composition latency " |
| 249 << (composition_time - frame_time) << " us, submission latency " |
| 250 << (submit_time - composition_time) << " us, queue latency " |
| 251 << (draw_time - submit_time) << " us, draw latency " |
| 252 << (wait_time - draw_time) << " us, GPU latency " |
| 253 << (finish_time - wait_time) << " us, total latency " |
| 254 << (finish_time - frame_time) << " us, presentation time error " |
| 255 << (finish_time - presentation_time) << " us" |
| 256 << ", draw queue depth " << draw_queue_depth; |
| 257 } else { |
| 258 DVLOG(2) << "Rasterizer dropped frame."; |
| 259 } |
| 260 |
| 261 DCHECK(shared_state_.current_frame_data); |
| 262 if (!shared_state_.current_frame_data->drawn) |
| 263 ScheduleDrawLocked(); |
| 264 } |
| 265 } |
| 266 |
| 267 void GpuOutput::InitializeRasterizer( |
| 268 mojo::InterfaceHandle<mojo::ContextProvider> context_provider) { |
| 269 DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread()); |
| 270 DCHECK(!rasterizer_); |
| 271 TRACE_EVENT0("gfx", "GpuOutput::InitializeRasterizer"); |
| 272 |
| 118 rasterizer_.reset(new GpuRasterizer( | 273 rasterizer_.reset(new GpuRasterizer( |
| 119 mojo::ContextProviderPtr::Create(std::move(context_provider)), scheduler, | 274 mojo::ContextProviderPtr::Create(std::move(context_provider)), this)); |
| 120 task_runner, error_callback)); | 275 rasterizer_initialized_.Signal(); |
| 121 } | 276 } |
| 122 | 277 |
| 123 void GpuOutput::RasterizerDelegate::SubmitTask() { | 278 void GpuOutput::DestroyRasterizer() { |
| 124 TRACE_EVENT0("gfx", "GpuOutput::RasterizerDelegate::SubmitTask"); | 279 DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread()); |
| 125 bool have_more; | 280 DCHECK(rasterizer_); |
| 126 scoped_refptr<RenderFrame> frame; | 281 TRACE_EVENT0("gfx", "GpuOutput::DestroyRasterizer"); |
| 127 { | 282 |
| 128 std::lock_guard<std::mutex> lock(mutex_); | 283 rasterizer_.reset(); |
| 129 DCHECK(!frames_.empty()); | 284 rasterizer_initialized_.Reset(); |
| 130 frame = frames_.front(); | 285 } |
| 131 frames_.pop(); | 286 |
| 132 have_more = !frames_.empty(); | 287 void GpuOutput::PostErrorCallback() { |
| 133 } | 288 compositor_task_runner_->PostTask(FROM_HERE, error_callback_); |
| 134 | 289 } |
| 135 if (have_more) | 290 |
| 136 PostSubmit(); | 291 GpuOutput::FrameData::FrameData(const scoped_refptr<RenderFrame>& frame, |
| 137 | 292 int64_t submit_time) |
| 138 int64_t submit_time = MojoGetTimeTicksNow(); | 293 : frame(frame), submit_time(submit_time) {} |
| 139 rasterizer_->SubmitFrame( | 294 |
| 140 frame, base::Bind(&RasterizerDelegate::OnFrameSubmitted, | 295 GpuOutput::FrameData::~FrameData() {} |
| 141 base::Unretained(this), frame->frame_info().frame_time, | 296 |
| 142 frame->frame_info().presentation_time, submit_time)); | 297 void GpuOutput::FrameData::Recycle() { |
| 143 } | 298 drawn = false; |
| 144 | 299 draw_time = 0; |
| 145 void GpuOutput::RasterizerDelegate::OnFrameSubmitted(int64_t frame_time, | 300 wait_time = 0; |
| 146 int64_t presentation_time, | |
| 147 int64_t submit_time, | |
| 148 bool presented) { | |
| 149 TRACE_EVENT0("gfx", "GpuOutput::RasterizerDelegate::OnFrameSubmitted"); | |
| 150 // TODO(jeffbrown): Adjust scheduler behavior based on observed timing. | |
| 151 // Note: These measurements don't account for systematic downstream delay | |
| 152 // in the display pipeline (how long it takes pixels to actually light up). | |
| 153 int64_t complete_time = MojoGetTimeTicksNow(); | |
| 154 if (presented) { | |
| 155 DVLOG(3) << "Frame presented: submission latency " | |
| 156 << (submit_time - frame_time) << " us, rasterization latency " | |
| 157 << (complete_time - submit_time) << " us, total latency " | |
| 158 << (complete_time - frame_time) << " us, presentation time error " | |
| 159 << (complete_time - presentation_time); | |
| 160 } else { | |
| 161 DVLOG(3) << "Frame deferred."; | |
| 162 } | |
| 163 } | 301 } |
| 164 | 302 |
| 165 } // namespace compositor | 303 } // namespace compositor |
| OLD | NEW |