Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(627)

Unified Diff: remoting/client/plugin/pepper_view.cc

Issue 9331003: Improving the decoder pipeline. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: CR feedback. Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: remoting/client/plugin/pepper_view.cc
diff --git a/remoting/client/plugin/pepper_view.cc b/remoting/client/plugin/pepper_view.cc
index fe404c22a9e7bc77443b37b18688e85022ef1975..783521ae11d3e0daad723a52009e06dcf7872ffc 100644
--- a/remoting/client/plugin/pepper_view.cc
+++ b/remoting/client/plugin/pepper_view.cc
@@ -4,8 +4,11 @@
#include "remoting/client/plugin/pepper_view.h"
+#include <functional>
+
#include "base/message_loop.h"
#include "base/string_util.h"
+#include "base/synchronization/waitable_event.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/graphics_2d.h"
#include "ppapi/cpp/image_data.h"
@@ -15,13 +18,19 @@
#include "remoting/base/util.h"
#include "remoting/client/chromoting_stats.h"
#include "remoting/client/client_context.h"
+#include "remoting/client/frame_producer.h"
#include "remoting/client/plugin/chromoting_instance.h"
#include "remoting/client/plugin/pepper_util.h"
+using base::Passed;
+
namespace remoting {
namespace {
+// The maximum number of image buffers to be allocated at any point of time.
+const size_t kMaxPendingBuffersCount = 2;
+
ChromotingScriptableObject::ConnectionError ConvertConnectionError(
protocol::ConnectionToHost::Error error) {
switch (error) {
@@ -42,16 +51,27 @@ ChromotingScriptableObject::ConnectionError ConvertConnectionError(
} // namespace
-PepperView::PepperView(ChromotingInstance* instance, ClientContext* context)
+PepperView::PepperView(ChromotingInstance* instance,
+ ClientContext* context,
+ FrameProducer* producer)
: instance_(instance),
context_(context),
- flush_blocked_(false),
- is_static_fill_(false),
+ producer_(producer),
+ merge_buffer_(NULL),
+ merge_clip_area_(SkIRect::MakeEmpty()),
+ view_size_(SkISize::Make(0, 0)),
+ clip_area_(SkIRect::MakeEmpty()),
+ source_size_(SkISize::Make(0, 0)),
static_fill_color_(0),
+ flush_pending_(false),
+ producer_disabled_(true),
+ solid_fill_requested_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
}
PepperView::~PepperView() {
+ DCHECK(merge_buffer_ == NULL);
+ DCHECK(buffers_.empty());
}
bool PepperView::Initialize() {
@@ -61,35 +81,41 @@ bool PepperView::Initialize() {
void PepperView::TearDown() {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
+ // The producer should now return any pending buffers. At this point, however,
+ // ReturnBuffer() tasks scheduled by the producer will not be delivered,
+ // so we free all the buffers once the producer's queue is empty.
+ producer_disabled_ = true;
+ base::WaitableEvent done_event(true, false);
+ producer_->RequestReturnBuffers(
+ base::Bind(&base::WaitableEvent::Signal, base::Unretained(&done_event)));
+ done_event.Wait();
+
+ merge_buffer_ = NULL;
+ while (!buffers_.empty()) {
+ FreeBuffer(buffers_.front());
+ }
+
weak_factory_.InvalidateWeakPtrs();
}
void PepperView::Paint() {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
- if (is_static_fill_) {
+ if (producer_disabled_ && !clip_area_.isEmpty()) {
VLOG(1) << "Static filling " << static_fill_color_;
- pp::ImageData image(instance_, pp::ImageData::GetNativeImageDataFormat(),
- pp::Size(graphics2d_.size().width(),
- graphics2d_.size().height()),
- false);
- if (image.is_null()) {
- LOG(ERROR) << "Unable to allocate image of size: "
- << graphics2d_.size().width() << " x "
- << graphics2d_.size().height();
- return;
- }
-
- for (int y = 0; y < image.size().height(); y++) {
- for (int x = 0; x < image.size().width(); x++) {
- *image.GetAddr32(pp::Point(x, y)) = static_fill_color_;
+ pp::ImageData* buffer = AllocateBuffer();
+ if (buffer) {
+ solid_fill_requested_ = false;
+ for (int y = 0; y < buffer->size().height(); y++) {
+ for (int x = 0; x < buffer->size().width(); x++) {
+ *buffer->GetAddr32(pp::Point(x, y)) = static_fill_color_;
+ }
}
- }
- // For ReplaceContents, make sure the image size matches the device context
- // size! Otherwise, this will just silently do nothing.
- graphics2d_.ReplaceContents(&image);
- FlushGraphics(base::Time::Now());
+ SkRegion region;
+ region.op(clip_area_, SkRegion::kUnion_Op);
+ FlushBuffer(clip_area_, buffer, region);
+ }
} else {
// TODO(ajwong): We need to keep a backing store image of the host screen
// that has the data here which can be redrawn.
@@ -97,132 +123,26 @@ void PepperView::Paint() {
}
}
-void PepperView::SetHostSize(const SkISize& host_size) {
- DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
-
- if (host_size_ == host_size)
- return;
-
- host_size_ = host_size;
-
- // Submit an update of desktop size to Javascript.
- instance_->GetScriptableObject()->SetDesktopSize(
- host_size.width(), host_size.height());
-}
-
-void PepperView::PaintFrame(media::VideoFrame* frame, const SkRegion& region) {
- DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
-
- SetHostSize(SkISize::Make(frame->width(), frame->height()));
-
- if (!backing_store_.get() || backing_store_->is_null()) {
- LOG(ERROR) << "Backing store is not available.";
- return;
- }
-
- base::Time start_time = base::Time::Now();
-
- // Copy updated regions to the backing store and then paint the regions.
- bool changes_made = false;
- for (SkRegion::Iterator i(region); !i.done(); i.next())
- changes_made |= PaintRect(frame, i.rect());
-
- if (changes_made)
- FlushGraphics(start_time);
-}
-
-bool PepperView::PaintRect(media::VideoFrame* frame, const SkIRect& r) {
- const uint8* frame_data = frame->data(media::VideoFrame::kRGBPlane);
- const int kFrameStride = frame->stride(media::VideoFrame::kRGBPlane);
- const int kBytesPerPixel = GetBytesPerPixel(media::VideoFrame::RGB32);
-
- pp::Size backing_store_size = backing_store_->size();
- SkIRect rect(r);
- if (!rect.intersect(SkIRect::MakeWH(backing_store_size.width(),
- backing_store_size.height()))) {
- return false;
- }
-
- const uint8* in =
- frame_data +
- kFrameStride * rect.fTop + // Y offset.
- kBytesPerPixel * rect.fLeft; // X offset.
- uint8* out =
- reinterpret_cast<uint8*>(backing_store_->data()) +
- backing_store_->stride() * rect.fTop + // Y offset.
- kBytesPerPixel * rect.fLeft; // X offset.
-
- // TODO(hclam): We really should eliminate this memory copy.
- for (int j = 0; j < rect.height(); ++j) {
- memcpy(out, in, rect.width() * kBytesPerPixel);
- in += kFrameStride;
- out += backing_store_->stride();
- }
-
- // Pepper Graphics 2D has a strange and badly documented API that the
- // point here is the offset from the source rect. Why?
- graphics2d_.PaintImageData(
- *backing_store_.get(),
- pp::Point(0, 0),
- pp::Rect(rect.fLeft, rect.fTop, rect.width(), rect.height()));
- return true;
-}
-
-void PepperView::BlankRect(pp::ImageData& image_data, const pp::Rect& rect) {
- const int kBytesPerPixel = GetBytesPerPixel(media::VideoFrame::RGB32);
- for (int y = rect.y(); y < rect.bottom(); y++) {
- uint8* to = reinterpret_cast<uint8*>(image_data.data()) +
- (y * image_data.stride()) + (rect.x() * kBytesPerPixel);
- memset(to, 0xff, rect.width() * kBytesPerPixel);
- }
-}
-
-void PepperView::FlushGraphics(base::Time paint_start) {
- scoped_ptr<base::Closure> task(
- new base::Closure(
- base::Bind(&PepperView::OnPaintDone, weak_factory_.GetWeakPtr(),
- paint_start)));
-
- // Flag needs to be set here in order to get a proper error code for Flush().
- // Otherwise Flush() will always return PP_OK_COMPLETIONPENDING and the error
- // would be hidden.
- //
- // Note that we can also handle this by providing an actual callback which
- // takes the result code. Right now everything goes to the task that doesn't
- // result value.
- pp::CompletionCallback pp_callback(&CompletionCallbackClosureAdapter,
- task.get(),
- PP_COMPLETIONCALLBACK_FLAG_OPTIONAL);
- int error = graphics2d_.Flush(pp_callback);
-
- // There is already a flush in progress so set this flag to true so that we
- // can flush again later.
- // |paint_start| is then discarded but this is fine because we're not aiming
- // for precise measurement of timing, otherwise we need to keep a list of
- // queued start time(s).
- if (error == PP_ERROR_INPROGRESS)
- flush_blocked_ = true;
- else
- flush_blocked_ = false;
-
- // If Flush() returns asynchronously then release the task.
- if (error == PP_OK_COMPLETIONPENDING)
- ignore_result(task.release());
-}
-
void PepperView::SetSolidFill(uint32 color) {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
- is_static_fill_ = true;
+ solid_fill_requested_ = true;
static_fill_color_ = color;
+ // The producer should not draw anything while the solid fill is enabled.
+ producer_disabled_ = true;
+ producer_->RequestReturnBuffers(base::Closure());
Paint();
}
void PepperView::UnsetSolidFill() {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
- is_static_fill_ = false;
+ solid_fill_requested_ = false;
+
+ // Now the producer may draw.
+ producer_disabled_ = false;
+ InitiateDrawing();
}
void PepperView::SetConnectionState(protocol::ConnectionToHost::State state,
@@ -262,77 +182,244 @@ void PepperView::SetConnectionState(protocol::ConnectionToHost::State state,
}
}
-bool PepperView::SetViewSize(const SkISize& view_size) {
- if (view_size_ == view_size)
- return false;
- view_size_ = view_size;
+void PepperView::SetView(const SkISize& view_size,
+ const SkIRect& clip_area) {
+ bool view_changed = false;
+
+ // Prevent accidental upscaling.
+ SkISize size = SkISize::Make(
+ std::min(view_size.width(), source_size_.width()),
+ std::min(view_size.height(), source_size_.height()));
- pp::Size pp_size = pp::Size(view_size.width(), view_size.height());
+ if (view_size_ != size) {
+ view_changed = true;
+ view_size_ = size;
- graphics2d_ = pp::Graphics2D(instance_, pp_size, true);
- if (!instance_->BindGraphics(graphics2d_)) {
- LOG(ERROR) << "Couldn't bind the device context.";
- return false;
+ pp::Size pp_size = pp::Size(view_size_.width(), view_size_.height());
+ graphics2d_ = pp::Graphics2D(instance_, pp_size, true);
+ bool result = instance_->BindGraphics(graphics2d_);
+
+ // There is no good way to handle this error currently.
+ DCHECK(result) << "Couldn't bind the device context.";
}
- if (view_size.isEmpty())
- return false;
-
- // Allocate the backing store to save the desktop image.
- if ((backing_store_.get() == NULL) ||
- (backing_store_->size() != pp_size)) {
- VLOG(1) << "Allocate backing store: "
- << view_size.width() << " x " << view_size.height();
- backing_store_.reset(
- new pp::ImageData(instance_, pp::ImageData::GetNativeImageDataFormat(),
- pp_size, false));
- DCHECK(backing_store_.get() && !backing_store_->is_null())
- << "Not enough memory for backing store.";
+ if (clip_area_ != clip_area) {
+ view_changed = true;
+
+ // YUV to RGB conversion may require even X and Y coordinates for
+ // the top left corner of the clipping area.
+ clip_area_ = AlignRect(clip_area);
+ clip_area_.intersect(SkIRect::MakeSize(view_size_));
+ }
+
+ if (view_changed) {
+ producer_->SetView(view_size_, clip_area_);
+ InitiateDrawing();
}
- return true;
}
-void PepperView::AllocateFrame(media::VideoFrame::Format format,
- const SkISize& size,
- scoped_refptr<media::VideoFrame>* frame_out,
- const base::Closure& done) {
+void PepperView::PaintBuffer(const SkISize& view_size,
+ const SkIRect& clip_area,
+ pp::ImageData* buffer,
+ const SkRegion& region) {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
- *frame_out = media::VideoFrame::CreateFrame(
- media::VideoFrame::RGB32, size.width(), size.height(),
- base::TimeDelta(), base::TimeDelta());
- (*frame_out)->AddRef();
- done.Run();
+ // Currently we cannot use the data in the buffer is scale factor has changed
+ // already.
+ // TODO(alexeypa): We could rescale and draw it to reduce the perception lag
Wez 2012/02/23 00:11:09 nit: Don't even need to re-scale; we could just dr
Wez 2012/02/23 00:11:09 typo: perception -> perceived typo: wating -> wait
alexeypa (please no reviews) 2012/02/23 17:10:33 Done.
alexeypa (please no reviews) 2012/02/23 17:10:33 Done.
+ // while we are wating for the preperly scaled data.
+ if (view_size_ != view_size) {
+ FreeBuffer(buffer);
+ InitiateDrawing();
+ } else {
+ FlushBuffer(clip_area, buffer, region);
+ }
}
-void PepperView::ReleaseFrame(media::VideoFrame* frame) {
+void PepperView::ReturnBuffer(pp::ImageData* buffer) {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
- if (frame)
- frame->Release();
+ // Free the buffer if there is nothing to paint.
+ if (producer_disabled_) {
+ FreeBuffer(buffer);
+ return;
+ }
+
+ // Reuse the buffer if it is large enough, otherwise drop it on the floor
+ // and allocate a new one.
+ if (buffer->size().width() >= clip_area_.width() &&
+ buffer->size().height() >= clip_area_.height()) {
+ producer_->AddBuffer(buffer);
+ } else {
+ FreeBuffer(buffer);
+ InitiateDrawing();
+ }
}
-void PepperView::OnPartialFrameOutput(media::VideoFrame* frame,
- SkRegion* region,
- const base::Closure& done) {
+void PepperView::SetSourceSize(const SkISize& source_size) {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
- // TODO(ajwong): Clean up this API to be async so we don't need to use a
- // member variable as a hack.
- PaintFrame(frame, *region);
- done.Run();
+ if (source_size_ == source_size)
+ return;
+
+ source_size_ = source_size;
+
+ // Notify JavaScript of the change in source size.
+ instance_->GetScriptableObject()->SetDesktopSize(
+ source_size.width(), source_size.height());
+}
+
+pp::ImageData* PepperView::AllocateBuffer() {
+ pp::ImageData* buffer = NULL;
+ if (buffers_.size() < kMaxPendingBuffersCount) {
+ pp::Size pp_size = pp::Size(clip_area_.width(), clip_area_.height());
+ buffer = new pp::ImageData(instance_,
+ PP_IMAGEDATAFORMAT_BGRA_PREMUL,
+ pp_size,
+ false);
+ if (buffer->is_null()) {
+ LOG(WARNING) << "Not enough memory for frame buffers.";
+ delete buffer;
+ buffer = NULL;
+ } else {
+ buffers_.push_back(buffer);
+ }
+ }
+
+ return buffer;
}
-void PepperView::OnPaintDone(base::Time paint_start) {
+void PepperView::FreeBuffer(pp::ImageData* buffer) {
+ DCHECK(std::find(buffers_.begin(), buffers_.end(), buffer) != buffers_.end());
+
+ buffers_.remove(buffer);
+ delete buffer;
+
+ // Now that we can allocate new buffers retry painting of the static
+ // background.
+ if (solid_fill_requested_)
+ Paint();
+}
+
+void PepperView::InitiateDrawing() {
+ // Do not schedule drawing if there is nothing to paint.
+ if (producer_disabled_)
+ return;
+
+ pp::ImageData* buffer = AllocateBuffer();
+ while (buffer) {
+ producer_->AddBuffer(buffer);
+ buffer = AllocateBuffer();
+ }
+}
+
+void PepperView::FlushBuffer(const SkIRect& clip_area,
+ pp::ImageData* buffer,
+ const SkRegion& region) {
+
+ // Defer drawing if the flush is already in progress.
+ if (flush_pending_) {
+ // |merge_buffer_| is guaranteed to be free here because we allocate only
+ // two buffers simultaneously. If more buffers are allowed this code should
+ // apply all pending changes to the screen.
+ DCHECK(merge_buffer_ == NULL);
+
+ merge_clip_area_ = clip_area;
+ merge_buffer_ = buffer;
+ merge_region_ = region;
+ return;
+ }
+
+ // Notify Pepper API about the updated areas and flush pixels to the screen.
+ base::Time start_time = base::Time::Now();
+
+ for (SkRegion::Iterator i(region); !i.done(); i.next()) {
+ SkIRect rect = i.rect();
+
+ // N.B. |clip_area_| may be different from |clip_area| meaning that
+ // the clipping area has changed since the time the image was drawn.
+ // The parts of the image that fit the new clipping area still need
+ // to be painted.
+ if (!rect.intersect(clip_area_))
+ continue;
+
+ // Specify the rectangle coordinates relative to the clipping area.
+ rect.offset(-clip_area.left(), -clip_area.top());
+
+ // Pepper Graphics 2D has a strange and badly documented API that the
+ // point here is the offset from the source rect. Why?
+ graphics2d_.PaintImageData(
+ *buffer,
+ pp::Point(clip_area.left(), clip_area.top()),
+ pp::Rect(rect.left(), rect.top(), rect.width(), rect.height()));
+ }
+
+ // Notify the producer that some parts of the region weren't painted because
+ // the clipping area has changed already.
+ if (clip_area != clip_area_) {
+ SkRegion not_painted = region;
+ not_painted.op(clip_area_, SkRegion::kDifference_Op);
+ if (!not_painted.isEmpty()) {
+ producer_->InvalidateRegion(not_painted);
+ }
+ }
+
+ // Flush the updated areas to the screen.
+ scoped_ptr<base::Closure> task(
+ new base::Closure(
+ base::Bind(&PepperView::OnFlushDone, weak_factory_.GetWeakPtr(),
+ start_time, buffer)));
+
+ // Flag needs to be set here in order to get a proper error code for Flush().
+ // Otherwise Flush() will always return PP_OK_COMPLETIONPENDING and the error
+ // would be hidden.
+ //
+ // Note that we can also handle this by providing an actual callback which
+ // takes the result code. Right now everything goes to the task that doesn't
+ // result value.
+ pp::CompletionCallback pp_callback(&CompletionCallbackClosureAdapter,
+ task.get(),
+ PP_COMPLETIONCALLBACK_FLAG_OPTIONAL);
+ int error = graphics2d_.Flush(pp_callback);
+
+ // If Flush() returns asynchronously then release the task.
+ flush_pending_ = (error == PP_OK_COMPLETIONPENDING);
+ if (flush_pending_) {
+ ignore_result(task.release());
+ } else {
+ instance_->GetStats()->video_paint_ms()->Record(
+ (base::Time::Now() - start_time).InMilliseconds());
+
+ ReturnBuffer(buffer);
+
+ // Resume painting for the buffer that was previoulsy postponed because of
+ // pending flush.
+ if (merge_buffer_ != NULL) {
+ buffer = merge_buffer_;
+ merge_buffer_ = NULL;
+ FlushBuffer(merge_clip_area_, buffer, merge_region_);
+ }
+ }
+}
+
+void PepperView::OnFlushDone(base::Time paint_start,
+ pp::ImageData* buffer) {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
+ DCHECK(flush_pending_);
+
instance_->GetStats()->video_paint_ms()->Record(
(base::Time::Now() - paint_start).InMilliseconds());
- // If the last flush failed because there was already another one in progress
- // then we perform the flush now.
- if (flush_blocked_)
- FlushGraphics(base::Time::Now());
- return;
+ flush_pending_ = false;
+ ReturnBuffer(buffer);
+
+ // Resume painting for the buffer that was previoulsy postponed because of
+ // pending flush.
+ if (merge_buffer_ != NULL) {
+ buffer = merge_buffer_;
+ merge_buffer_ = NULL;
+ FlushBuffer(merge_clip_area_, buffer, merge_region_);
+ }
}
} // namespace remoting

Powered by Google App Engine
This is Rietveld 408576698