blob: d8a776c8b31f26d08462bb48d3375f5507527c94 [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/client/plugin/pepper_view.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/graphics_2d.h"
#include "ppapi/cpp/image_data.h"
#include "ppapi/cpp/point.h"
#include "ppapi/cpp/rect.h"
#include "ppapi/cpp/size.h"
#include "remoting/base/util.h"
#include "remoting/client/chromoting_stats.h"
#include "remoting/client/client_context.h"
#include "remoting/client/plugin/chromoting_instance.h"
#include "remoting/client/plugin/pepper_util.h"
namespace remoting {
namespace {
ChromotingScriptableObject::ConnectionError ConvertConnectionError(
protocol::ConnectionToHost::Error error) {
switch (error) {
case protocol::ConnectionToHost::OK:
return ChromotingScriptableObject::ERROR_NONE;
case protocol::ConnectionToHost::HOST_IS_OFFLINE:
return ChromotingScriptableObject::ERROR_HOST_IS_OFFLINE;
case protocol::ConnectionToHost::SESSION_REJECTED:
return ChromotingScriptableObject::ERROR_SESSION_REJECTED;
case protocol::ConnectionToHost::INCOMPATIBLE_PROTOCOL:
return ChromotingScriptableObject::ERROR_INCOMPATIBLE_PROTOCOL;
case protocol::ConnectionToHost::NETWORK_FAILURE:
return ChromotingScriptableObject::ERROR_NETWORK_FAILURE;
}
DLOG(FATAL) << "Unknown error code" << error;
return ChromotingScriptableObject::ERROR_NONE;
}
} // namespace
PepperView::PepperView(ChromotingInstance* instance, ClientContext* context)
: instance_(instance),
context_(context),
flush_blocked_(false),
is_static_fill_(false),
static_fill_color_(0),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
}
PepperView::~PepperView() {
}
bool PepperView::Initialize() {
return true;
}
void PepperView::TearDown() {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
weak_factory_.InvalidateWeakPtrs();
}
void PepperView::Paint() {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
if (is_static_fill_) {
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_;
}
}
// 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());
} else {
// TODO(ajwong): We need to keep a backing store image of the host screen
// that has the data here which can be redrawn.
return;
}
}
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, RectVector* rects) {
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 (size_t i = 0; i < rects->size(); ++i)
changes_made |= PaintRect(frame, (*rects)[i]);
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;
static_fill_color_ = color;
Paint();
}
void PepperView::UnsetSolidFill() {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
is_static_fill_ = false;
}
void PepperView::SetConnectionState(protocol::ConnectionToHost::State state,
protocol::ConnectionToHost::Error error) {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
// TODO(hclam): Re-consider the way we communicate with Javascript.
ChromotingScriptableObject* scriptable_obj = instance_->GetScriptableObject();
switch (state) {
case protocol::ConnectionToHost::CONNECTING:
SetSolidFill(kCreatedColor);
scriptable_obj->SetConnectionStatus(
ChromotingScriptableObject::STATUS_CONNECTING,
ConvertConnectionError(error));
break;
case protocol::ConnectionToHost::CONNECTED:
UnsetSolidFill();
scriptable_obj->SetConnectionStatus(
ChromotingScriptableObject::STATUS_CONNECTED,
ConvertConnectionError(error));
break;
case protocol::ConnectionToHost::CLOSED:
SetSolidFill(kDisconnectedColor);
scriptable_obj->SetConnectionStatus(
ChromotingScriptableObject::STATUS_CLOSED,
ConvertConnectionError(error));
break;
case protocol::ConnectionToHost::FAILED:
SetSolidFill(kFailedColor);
scriptable_obj->SetConnectionStatus(
ChromotingScriptableObject::STATUS_FAILED,
ConvertConnectionError(error));
break;
}
}
bool PepperView::SetViewSize(const SkISize& view_size) {
if (view_size_ == view_size)
return false;
view_size_ = view_size;
pp::Size pp_size = pp::Size(view_size.width(), view_size.height());
graphics2d_ = pp::Graphics2D(instance_, pp_size, true);
if (!instance_->BindGraphics(graphics2d_)) {
LOG(ERROR) << "Couldn't bind the device context.";
return false;
}
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.";
}
return true;
}
void PepperView::AllocateFrame(media::VideoFrame::Format format,
const SkISize& size,
scoped_refptr<media::VideoFrame>* frame_out,
const base::Closure& done) {
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();
}
void PepperView::ReleaseFrame(media::VideoFrame* frame) {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
if (frame)
frame->Release();
}
void PepperView::OnPartialFrameOutput(media::VideoFrame* frame,
RectVector* rects,
const base::Closure& done) {
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, rects);
done.Run();
}
void PepperView::OnPaintDone(base::Time paint_start) {
DCHECK(context_->main_message_loop()->BelongsToCurrentThread());
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;
}
} // namespace remoting