| 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 d52ef5a250e7c0f35da9e94f40725465be62dfd9..bab1167fc09b19a13784ad6f148458fb79916f9b 100644
|
| --- a/services/gfx/compositor/backend/gpu_output.cc
|
| +++ b/services/gfx/compositor/backend/gpu_output.cc
|
| @@ -18,9 +18,9 @@
|
| namespace compositor {
|
| namespace {
|
| constexpr const char* kPipelineDepthSwitch = "pipeline-depth";
|
| -constexpr uint32_t kDefaultPipelineDepth = 2u; // ideally should be 1
|
| +constexpr uint32_t kDefaultPipelineDepth = 1u;
|
| constexpr uint32_t kMinPipelineDepth = 1u;
|
| -constexpr uint32_t kMaxPipelineDepth = 10u;
|
| +constexpr uint32_t kMaxPipelineDepth = 10u; // for experimentation
|
|
|
| scoped_ptr<base::MessagePump> CreateMessagePumpMojo() {
|
| return base::MessageLoop::CreateMessagePumpForType(
|
| @@ -86,24 +86,48 @@ void GpuOutput::SubmitFrame(const scoped_refptr<RenderFrame>& 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
|
| +
|
| + // 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);
|
|
|
| - 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.";
|
| + // 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.";
|
| }
|
|
|
| - // TODO(jeffbrown): If the draw queue is full, we should pause
|
| + 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_.rasterizer_ready &&
|
| - shared_state_.drawn_frames_awaiting_finish.size() < pipeline_depth_)
|
| + if (shared_state_.frames.size() <= pipeline_depth_)
|
| ScheduleDrawLocked();
|
| }
|
| }
|
| @@ -134,15 +158,14 @@ void GpuOutput::OnRasterizerReady(int64_t vsync_timebase,
|
| if (shared_state_.rasterizer_ready)
|
| return;
|
|
|
| - DCHECK(shared_state_.drawn_frames_awaiting_finish.empty());
|
| shared_state_.rasterizer_ready = true;
|
|
|
| - if (!shared_state_.current_frame_data)
|
| + if (shared_state_.frames.empty())
|
| return;
|
|
|
| - shared_state_.current_frame_data->Recycle();
|
| + shared_state_.frames.back()->ResetDrawState();
|
| TRACE_EVENT_FLOW_BEGIN0("gfx", "Frame Queued",
|
| - shared_state_.current_frame_data.get());
|
| + shared_state_.frames.back().get());
|
| ScheduleDrawLocked();
|
| }
|
| }
|
| @@ -169,8 +192,9 @@ void GpuOutput::OnRasterizerError() {
|
| }
|
|
|
| void GpuOutput::ScheduleDrawLocked() {
|
| - DCHECK(shared_state_.current_frame_data);
|
| - DCHECK(!shared_state_.current_frame_data->drawn);
|
| + 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;
|
| @@ -184,32 +208,31 @@ void GpuOutput::OnDraw() {
|
| DCHECK(rasterizer_task_runner_->RunsTasksOnCurrentThread());
|
| TRACE_EVENT0("gfx", "GpuOutput::OnDraw");
|
|
|
| - scoped_refptr<FrameData> frame_data; // used outside lock
|
| + FrameData* frame_data; // used outside lock
|
| {
|
| 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);
|
| + DCHECK(!shared_state_.frames.empty());
|
| + DCHECK(shared_state_.frames.back()->state == FrameData::State::Pending);
|
|
|
| shared_state_.draw_scheduled = false;
|
| -
|
| - if (!shared_state_.rasterizer_ready ||
|
| - shared_state_.drawn_frames_awaiting_finish.size() >= pipeline_depth_)
|
| + if (!shared_state_.rasterizer_ready)
|
| 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);
|
| + 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->wait_time = MojoGetTimeTicksNow();
|
| + frame_data->draw_issued_time = MojoGetTimeTicksNow();
|
| }
|
|
|
| void GpuOutput::OnRasterizerFinishedDraw(bool presented) {
|
| @@ -217,20 +240,24 @@ void GpuOutput::OnRasterizerFinishedDraw(bool presented) {
|
| TRACE_EVENT0("gfx", "GpuOutput::OnRasterizerFinishedDraw");
|
|
|
| const int64_t finish_time = MojoGetTimeTicksNow();
|
| - scoped_refptr<FrameData> frame_data; // drop outside lock
|
| +
|
| + // 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_.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(!shared_state_.frames.empty());
|
| +
|
| + FrameData* frame_data = shared_state_.frames.front().get();
|
| DCHECK(frame_data);
|
| - DCHECK(frame_data->drawn);
|
| - TRACE_EVENT_ASYNC_END1("gfx", "Rasterize", frame_data.get(), "presented",
|
| + 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).
|
| @@ -242,16 +269,17 @@ void GpuOutput::OnRasterizerFinishedDraw(bool presented) {
|
| 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 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_time - submit_time) << " us, draw latency "
|
| - << (wait_time - draw_time) << " us, GPU latency "
|
| - << (finish_time - wait_time) << " us, total 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;
|
| @@ -259,9 +287,12 @@ void GpuOutput::OnRasterizerFinishedDraw(bool presented) {
|
| DVLOG(2) << "Rasterizer dropped frame.";
|
| }
|
|
|
| - DCHECK(shared_state_.current_frame_data);
|
| - if (!shared_state_.current_frame_data->drawn)
|
| - ScheduleDrawLocked();
|
| + 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();
|
| + }
|
| }
|
| }
|
|
|
| @@ -295,10 +326,10 @@ GpuOutput::FrameData::FrameData(const scoped_refptr<RenderFrame>& frame,
|
|
|
| GpuOutput::FrameData::~FrameData() {}
|
|
|
| -void GpuOutput::FrameData::Recycle() {
|
| - drawn = false;
|
| - draw_time = 0;
|
| - wait_time = 0;
|
| +void GpuOutput::FrameData::ResetDrawState() {
|
| + state = State::Pending;
|
| + draw_started_time = 0;
|
| + draw_issued_time = 0;
|
| }
|
|
|
| } // namespace compositor
|
|
|