Mozart: Reduce pipeline depth and unify frame queue.
Reduced default pipeline depth to 1, which is what it really
should be (if not zero!).
Entirely eliminate scheduling unnecessary draws.
Eliminated the use of a ref-counted pointers for FrameData
by retaining the current frame within the queue.
BUG=
R=mikejurka@google.com
Review URL: https://codereview.chromium.org/2009503003 .
diff --git a/services/gfx/compositor/backend/gpu_output.cc b/services/gfx/compositor/backend/gpu_output.cc
index d52ef5a..bab1167 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 @@
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 @@
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::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 @@
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 @@
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 @@
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 @@
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() {}
-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
diff --git a/services/gfx/compositor/backend/gpu_output.h b/services/gfx/compositor/backend/gpu_output.h
index d7fe5f5..74aa3e7 100644
--- a/services/gfx/compositor/backend/gpu_output.h
+++ b/services/gfx/compositor/backend/gpu_output.h
@@ -34,22 +34,25 @@
void SubmitFrame(const scoped_refptr<RenderFrame>& frame) override;
private:
- struct FrameData : public base::RefCountedThreadSafe<FrameData> {
- FrameData(const scoped_refptr<RenderFrame>& frame, int64_t submit_time);
+ struct FrameData {
+ enum class State {
+ Pending, // initial state waiting for draw to start
+ Drawing, // draw has started
+ Finished // draw has finished
+ };
- void Recycle();
+ FrameData(const scoped_refptr<RenderFrame>& frame, int64_t submit_time);
+ ~FrameData();
+
+ void ResetDrawState();
const scoped_refptr<RenderFrame> frame;
const int64_t submit_time;
- bool drawn = false; // set when DrawFrame is called
- int64_t draw_time = 0; // 0 if not drawn
- int64_t wait_time = 0; // 0 if not drawn
+ State state = State::Pending;
+ int64_t draw_started_time = 0; // time when drawing began
+ int64_t draw_issued_time = 0; // time when awaiting for finish began
private:
- friend class base::RefCountedThreadSafe<FrameData>;
-
- ~FrameData();
-
DISALLOW_COPY_AND_ASSIGN(FrameData);
};
@@ -86,12 +89,20 @@
struct {
std::mutex mutex; // guards all shared state
- // The most recently submitted frame.
- // Only null until the first frame has been submitted.
- scoped_refptr<FrameData> current_frame_data;
-
- // Frames drawn and awaiting completion by the rasterizer.
- std::queue<scoped_refptr<FrameData>> drawn_frames_awaiting_finish;
+ // Queue of frames.
+ //
+ // The head of this queue consists of up to |pipeline_depth| frames
+ // which are drawn and awaiting finish. These frames are popped off
+ // the queue when finished unless the queue would become empty (such
+ // that we always retain the current frame as the tail).
+ //
+ // The tail of this queue is a single frame which is either drawn or
+ // finished and represents the current (most recently submitted)
+ // content.
+ //
+ // The queue is only ever empty until the first frame is submitted.
+ // Subsequently, it always contains at least one frame.
+ std::queue<std::unique_ptr<FrameData>> frames;
// Set to true when the rasterizer is ready to draw.
bool rasterizer_ready = false;