summaryrefslogtreecommitdiffstats
path: root/content/renderer/pepper
diff options
context:
space:
mode:
authorvictorhsieh@chromium.org <victorhsieh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-12-05 16:02:40 +0000
committervictorhsieh@chromium.org <victorhsieh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-12-05 16:02:40 +0000
commitfd36e4ed7d4adb48df2b4b208d86467ea1d20f87 (patch)
tree366066b0b925594bd9c6c7941f2a51251f8f47c3 /content/renderer/pepper
parent427e48fd18a420b8eb322cbb249b12c0211eea13 (diff)
downloadchromium_src-fd36e4ed7d4adb48df2b4b208d86467ea1d20f87.zip
chromium_src-fd36e4ed7d4adb48df2b4b208d86467ea1d20f87.tar.gz
chromium_src-fd36e4ed7d4adb48df2b4b208d86467ea1d20f87.tar.bz2
Inline ppb_graphics_2d_impl to the host.
- Split original tests to content_browsertests and test_shell_tests - *_flush_callback_ are no longer needed TEST=browser_tests,content_browsertests,test_shell_tests,ppapi/examples/paint_manager BUG= Review URL: https://chromiumcodereview.appspot.com/11434049 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@171234 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/renderer/pepper')
-rw-r--r--content/renderer/pepper/pepper_graphics_2d_host.cc694
-rw-r--r--content/renderer/pepper/pepper_graphics_2d_host.h95
-rw-r--r--content/renderer/pepper/pepper_graphics_2d_host_unittest.cc92
3 files changed, 831 insertions, 50 deletions
diff --git a/content/renderer/pepper/pepper_graphics_2d_host.cc b/content/renderer/pepper/pepper_graphics_2d_host.cc
index 2c04d76..51b69d1 100644
--- a/content/renderer/pepper/pepper_graphics_2d_host.cc
+++ b/content/renderer/pepper/pepper_graphics_2d_host.cc
@@ -4,19 +4,165 @@
#include "content/renderer/pepper/pepper_graphics_2d_host.h"
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
#include "content/public/renderer/renderer_ppapi_host.h"
#include "ppapi/c/pp_bool.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/pp_rect.h"
+#include "ppapi/c/pp_resource.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/host_message_context.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/proxy/ppapi_messages.h"
-#include "ppapi/shared_impl/api_id.h"
#include "ppapi/thunk/enter.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/blit.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/skia_util.h"
+#include "webkit/plugins/ppapi/common.h"
+#include "webkit/plugins/ppapi/gfx_conversion.h"
#include "webkit/plugins/ppapi/ppapi_plugin_instance.h"
-#include "webkit/plugins/ppapi/ppb_graphics_2d_impl.h" // TODO: merge to here
+#include "webkit/plugins/ppapi/ppb_image_data_impl.h"
+#include "webkit/plugins/ppapi/resource_helper.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#endif
+
+using ppapi::thunk::EnterResourceNoLock;
+using ppapi::thunk::PPB_ImageData_API;
+using webkit::ppapi::ImageDataAutoMapper;
+using webkit::ppapi::PPB_ImageData_Impl;
namespace content {
+namespace {
+
+const int64 kOffscreenCallbackDelayMs = 1000 / 30; // 30 fps
+
+// Converts a rect inside an image of the given dimensions. The rect may be
+// NULL to indicate it should be the entire image. If the rect is outside of
+// the image, this will do nothing and return false.
+bool ValidateAndConvertRect(const PP_Rect* rect,
+ int image_width, int image_height,
+ gfx::Rect* dest) {
+ if (!rect) {
+ // Use the entire image area.
+ *dest = gfx::Rect(0, 0, image_width, image_height);
+ } else {
+ // Validate the passed-in area.
+ if (rect->point.x < 0 || rect->point.y < 0 ||
+ rect->size.width <= 0 || rect->size.height <= 0)
+ return false;
+
+ // Check the max bounds, being careful of overflow.
+ if (static_cast<int64>(rect->point.x) +
+ static_cast<int64>(rect->size.width) >
+ static_cast<int64>(image_width))
+ return false;
+ if (static_cast<int64>(rect->point.y) +
+ static_cast<int64>(rect->size.height) >
+ static_cast<int64>(image_height))
+ return false;
+
+ *dest = gfx::Rect(rect->point.x, rect->point.y,
+ rect->size.width, rect->size.height);
+ }
+ return true;
+}
+
+// Converts BGRA <-> RGBA.
+void ConvertBetweenBGRAandRGBA(const uint32_t* input,
+ int pixel_length,
+ uint32_t* output) {
+ for (int i = 0; i < pixel_length; i++) {
+ const unsigned char* pixel_in =
+ reinterpret_cast<const unsigned char*>(&input[i]);
+ unsigned char* pixel_out = reinterpret_cast<unsigned char*>(&output[i]);
+ pixel_out[0] = pixel_in[2];
+ pixel_out[1] = pixel_in[1];
+ pixel_out[2] = pixel_in[0];
+ pixel_out[3] = pixel_in[3];
+ }
+}
+
+// Converts ImageData from PP_IMAGEDATAFORMAT_BGRA_PREMUL to
+// PP_IMAGEDATAFORMAT_RGBA_PREMUL, or reverse. It's assumed that the
+// destination image is always mapped (so will have non-NULL data).
+void ConvertImageData(PPB_ImageData_Impl* src_image, const SkIRect& src_rect,
+ PPB_ImageData_Impl* dest_image, const SkRect& dest_rect) {
+ ImageDataAutoMapper auto_mapper(src_image);
+
+ DCHECK(src_image->format() != dest_image->format());
+ DCHECK(PPB_ImageData_Impl::IsImageDataFormatSupported(src_image->format()));
+ DCHECK(PPB_ImageData_Impl::IsImageDataFormatSupported(dest_image->format()));
+
+ const SkBitmap* src_bitmap = src_image->GetMappedBitmap();
+ const SkBitmap* dest_bitmap = dest_image->GetMappedBitmap();
+ if (src_rect.width() == src_image->width() &&
+ dest_rect.width() == dest_image->width()) {
+ // Fast path if the full line needs to be converted.
+ ConvertBetweenBGRAandRGBA(
+ src_bitmap->getAddr32(static_cast<int>(src_rect.fLeft),
+ static_cast<int>(src_rect.fTop)),
+ src_rect.width() * src_rect.height(),
+ dest_bitmap->getAddr32(static_cast<int>(dest_rect.fLeft),
+ static_cast<int>(dest_rect.fTop)));
+ } else {
+ // Slow path where we convert line by line.
+ for (int y = 0; y < src_rect.height(); y++) {
+ ConvertBetweenBGRAandRGBA(
+ src_bitmap->getAddr32(static_cast<int>(src_rect.fLeft),
+ static_cast<int>(src_rect.fTop + y)),
+ src_rect.width(),
+ dest_bitmap->getAddr32(static_cast<int>(dest_rect.fLeft),
+ static_cast<int>(dest_rect.fTop + y)));
+ }
+ }
+}
+
+} // namespace
+
+struct PepperGraphics2DHost::QueuedOperation {
+ enum Type {
+ PAINT,
+ SCROLL,
+ REPLACE
+ };
+
+ QueuedOperation(Type t)
+ : type(t),
+ paint_x(0),
+ paint_y(0),
+ scroll_dx(0),
+ scroll_dy(0) {
+ }
+
+ Type type;
+
+ // Valid when type == PAINT.
+ scoped_refptr<PPB_ImageData_Impl> paint_image;
+ int paint_x, paint_y;
+ gfx::Rect paint_src_rect;
+
+ // Valid when type == SCROLL.
+ gfx::Rect scroll_clip_rect;
+ int scroll_dx, scroll_dy;
+
+ // Valid when type == REPLACE.
+ scoped_refptr<PPB_ImageData_Impl> replace_image;
+};
+
// static
PepperGraphics2DHost* PepperGraphics2DHost::Create(RendererPpapiHost* host,
PP_Instance instance,
@@ -25,8 +171,8 @@ PepperGraphics2DHost* PepperGraphics2DHost::Create(RendererPpapiHost* host,
PP_Bool is_always_opaque) {
PepperGraphics2DHost* resource_host =
new PepperGraphics2DHost(host, instance, resource);
- if (!resource_host->graphics_2d_->Init(size.width, size.height,
- PP_ToBool(is_always_opaque))) {
+ if (!resource_host->Init(size.width, size.height,
+ PP_ToBool(is_always_opaque))) {
delete resource_host;
return NULL;
}
@@ -37,23 +183,42 @@ PepperGraphics2DHost::PepperGraphics2DHost(RendererPpapiHost* host,
PP_Instance instance,
PP_Resource resource)
: ResourceHost(host->GetPpapiHost(), instance, resource),
- graphics_2d_(new webkit::ppapi::PPB_Graphics2D_Impl(instance)),
+ renderer_ppapi_host_(host),
+ bound_instance_(NULL),
+ need_flush_ack_(false),
+ offscreen_flush_pending_(false),
+ is_always_opaque_(false),
+ scale_(1.0f),
+ weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
is_running_in_process_(host->IsRunningInProcess()) {
}
PepperGraphics2DHost::~PepperGraphics2DHost() {
// Unbind from the instance when destoryed.
- PP_Instance instance = graphics_2d_->pp_instance();
- ppapi::thunk::EnterInstanceNoLock enter(instance);
+ ppapi::thunk::EnterInstanceNoLock enter(pp_instance());
if (enter.succeeded())
- enter.functions()->BindGraphics(instance, 0);
+ enter.functions()->BindGraphics(pp_instance(), 0);
+}
+
+bool PepperGraphics2DHost::Init(int width, int height, bool is_always_opaque) {
+ // The underlying PPB_ImageData_Impl will validate the dimensions.
+ image_data_ = new PPB_ImageData_Impl(pp_instance(),
+ PPB_ImageData_Impl::PLATFORM);
+ if (!image_data_->Init(PPB_ImageData_Impl::GetNativeImageDataFormat(),
+ width, height, true) ||
+ !image_data_->Map()) {
+ image_data_ = NULL;
+ return false;
+ }
+ is_always_opaque_ = is_always_opaque;
+ scale_ = 1.0f;
+ return true;
}
int32_t PepperGraphics2DHost::OnResourceMessageReceived(
const IPC::Message& msg,
ppapi::host::HostMessageContext* context) {
IPC_BEGIN_MESSAGE_MAP(PepperGraphics2DHost, msg)
-#if !defined(OS_NACL)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(
PpapiHostMsg_Graphics2D_PaintImageData,
OnHostMsgPaintImageData)
@@ -72,71 +237,275 @@ int32_t PepperGraphics2DHost::OnResourceMessageReceived(
PPAPI_DISPATCH_HOST_RESOURCE_CALL(
PpapiHostMsg_Graphics2D_ReadImageData,
OnHostMsgReadImageData)
-#endif
IPC_END_MESSAGE_MAP()
return PP_ERROR_FAILED;
}
bool PepperGraphics2DHost::ReadImageData(PP_Resource image,
const PP_Point* top_left) {
- return graphics_2d_->ReadImageData(image, top_left);
+ // Get and validate the image object to paint into.
+ EnterResourceNoLock<PPB_ImageData_API> enter(image, true);
+ if (enter.failed())
+ return false;
+ PPB_ImageData_Impl* image_resource =
+ static_cast<PPB_ImageData_Impl*>(enter.object());
+ if (!PPB_ImageData_Impl::IsImageDataFormatSupported(
+ image_resource->format()))
+ return false; // Must be in the right format.
+
+ // Validate the bitmap position.
+ int x = top_left->x;
+ if (x < 0 ||
+ static_cast<int64>(x) + static_cast<int64>(image_resource->width()) >
+ image_data_->width())
+ return false;
+ int y = top_left->y;
+ if (y < 0 ||
+ static_cast<int64>(y) + static_cast<int64>(image_resource->height()) >
+ image_data_->height())
+ return false;
+
+ ImageDataAutoMapper auto_mapper(image_resource);
+ if (!auto_mapper.is_valid())
+ return false;
+
+ SkIRect src_irect = { x, y,
+ x + image_resource->width(),
+ y + image_resource->height() };
+ SkRect dest_rect = { SkIntToScalar(0),
+ SkIntToScalar(0),
+ SkIntToScalar(image_resource->width()),
+ SkIntToScalar(image_resource->height()) };
+
+ if (image_resource->format() != image_data_->format()) {
+ // Convert the image data if the format does not match.
+ ConvertImageData(image_data_, src_irect, image_resource, dest_rect);
+ } else {
+ SkCanvas* dest_canvas = image_resource->GetCanvas();
+
+ // We want to replace the contents of the bitmap rather than blend.
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ dest_canvas->drawBitmapRect(*image_data_->GetMappedBitmap(),
+ &src_irect, dest_rect, &paint);
+ }
+ return true;
}
bool PepperGraphics2DHost::BindToInstance(
webkit::ppapi::PluginInstance* new_instance) {
- if (new_instance &&
- new_instance->pp_instance() != graphics_2d_->pp_instance())
+ if (new_instance && new_instance->pp_instance() != pp_instance())
return false; // Can't bind other instance's contexts.
- return graphics_2d_->BindToInstance(new_instance);
+ if (bound_instance_ == new_instance)
+ return true; // Rebinding the same device, nothing to do.
+ if (bound_instance_ && new_instance)
+ return false; // Can't change a bound device.
+
+ if (!new_instance) {
+ // When the device is detached, we'll not get any more paint callbacks so
+ // we need to clear the list, but we still want to issue any pending
+ // callbacks to the plugin.
+ if (need_flush_ack_)
+ ScheduleOffscreenFlushAck();
+ } else {
+ // Devices being replaced, redraw the plugin.
+ new_instance->InvalidateRect(gfx::Rect());
+ }
+
+ bound_instance_ = new_instance;
+ return true;
}
+// The |backing_bitmap| must be clipped to the |plugin_rect| to avoid painting
+// outside the plugin area. This can happen if the plugin has been resized since
+// PaintImageData verified the image is within the plugin size.
void PepperGraphics2DHost::Paint(WebKit::WebCanvas* canvas,
const gfx::Rect& plugin_rect,
const gfx::Rect& paint_rect) {
- graphics_2d_->Paint(canvas, plugin_rect, paint_rect);
+ TRACE_EVENT0("pepper", "PepperGraphics2DHost::Paint");
+ ImageDataAutoMapper auto_mapper(image_data_);
+ const SkBitmap& backing_bitmap = *image_data_->GetMappedBitmap();
+
+#if defined(OS_MACOSX) && !defined(USE_SKIA)
+ SkAutoLockPixels lock(backing_bitmap);
+
+ base::mac::ScopedCFTypeRef<CGDataProviderRef> data_provider(
+ CGDataProviderCreateWithData(
+ NULL, backing_bitmap.getAddr32(0, 0),
+ backing_bitmap.rowBytes() * backing_bitmap.height(), NULL));
+ base::mac::ScopedCFTypeRef<CGImageRef> image(
+ CGImageCreate(
+ backing_bitmap.width(), backing_bitmap.height(),
+ 8, 32, backing_bitmap.rowBytes(),
+ base::mac::GetSystemColorSpace(),
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
+ data_provider, NULL, false, kCGRenderingIntentDefault));
+
+ // Flip the transform
+ gfx::ScopedCGContextSaveGState save_gstate(canvas)
+ float window_height = static_cast<float>(CGBitmapContextGetHeight(canvas));
+ CGContextTranslateCTM(canvas, 0, window_height);
+ CGContextScaleCTM(canvas, 1.0, -1.0);
+
+ // To avoid painting outside the plugin boundaries and clip instead of
+ // scaling, CGContextDrawImage() must draw the full image using |bitmap_rect|
+ // but the context must be clipped to the plugin using |bounds|.
+
+ CGRect bitmap_rect;
+ bitmap_rect.origin.x = plugin_rect.origin().x();
+ bitmap_rect.origin.y = window_height - plugin_rect.origin().y() -
+ backing_bitmap.height();
+ bitmap_rect.size.width = backing_bitmap.width();
+ bitmap_rect.size.height = backing_bitmap.height();
+
+ CGRect bounds;
+ bounds.origin.x = plugin_rect.origin().x();
+ bounds.origin.y = window_height - plugin_rect.origin().y() -
+ plugin_rect.height();
+ bounds.size.width = plugin_rect.width();
+ bounds.size.height = plugin_rect.height();
+ // TODO(yzshen): We should take |paint_rect| into consideration as well.
+ CGContextClipToRect(canvas, bounds);
+
+ // TODO(jhorwich) Figure out if this code is even active anymore, and if so
+ // how to properly handle scaling.
+ DCHECK_EQ(1.0f, scale_);
+
+ // TODO(brettw) bug 56673: do a direct memcpy instead of going through CG
+ // if the is_always_opaque_ flag is set. Must ensure bitmap is still clipped.
+
+ CGContextDrawImage(canvas, bitmap_rect, image);
+#else
+ gfx::Rect invalidate_rect = plugin_rect;
+ invalidate_rect.Intersect(paint_rect);
+ SkRect sk_invalidate_rect = gfx::RectToSkRect(invalidate_rect);
+ SkAutoCanvasRestore auto_restore(canvas, true);
+ canvas->clipRect(sk_invalidate_rect);
+ gfx::Size pixel_image_size(image_data_->width(), image_data_->height());
+ gfx::Size image_size = gfx::ToFlooredSize(
+ gfx::ScaleSize(pixel_image_size, scale_));
+
+ webkit::ppapi::PluginInstance* plugin_instance =
+ renderer_ppapi_host_->GetPluginInstance(pp_instance());
+ if (!plugin_instance)
+ return;
+ if (plugin_instance->IsFullPagePlugin()) {
+ // When we're resizing a window with a full-frame plugin, the plugin may
+ // not yet have bound a new device, which will leave parts of the
+ // background exposed if the window is getting larger. We want this to
+ // show white (typically less jarring) rather than black or uninitialized.
+ // We don't do this for non-full-frame plugins since we specifically want
+ // the page background to show through.
+ SkAutoCanvasRestore auto_restore(canvas, true);
+ SkRect image_data_rect =
+ gfx::RectToSkRect(gfx::Rect(plugin_rect.origin(), image_size));
+ canvas->clipRect(image_data_rect, SkRegion::kDifference_Op);
+
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ paint.setColor(SK_ColorWHITE);
+ canvas->drawRect(sk_invalidate_rect, paint);
+ }
+
+ SkBitmap image;
+ // Copy to device independent bitmap when target canvas doesn't support
+ // platform paint.
+ if (!skia::SupportsPlatformPaint(canvas))
+ backing_bitmap.copyTo(&image, SkBitmap::kARGB_8888_Config);
+ else
+ image = backing_bitmap;
+
+ SkPaint paint;
+ if (is_always_opaque_) {
+ // When we know the device is opaque, we can disable blending for slightly
+ // more optimized painting.
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ }
+
+ SkPoint origin;
+ origin.set(SkIntToScalar(plugin_rect.x()), SkIntToScalar(plugin_rect.y()));
+
+ SkPoint pixel_origin = origin;
+ if (scale_ != 1.0f && scale_ > 0.0f) {
+ float inverse_scale = 1.0f / scale_;
+ pixel_origin.scale(inverse_scale);
+ canvas->scale(scale_, scale_);
+ }
+ canvas->drawBitmap(image, pixel_origin.x(), pixel_origin.y(), &paint);
+#endif
}
void PepperGraphics2DHost::ViewWillInitiatePaint() {
- graphics_2d_->ViewWillInitiatePaint();
}
void PepperGraphics2DHost::ViewInitiatedPaint() {
- graphics_2d_->ViewInitiatedPaint();
}
void PepperGraphics2DHost::ViewFlushedPaint() {
- graphics_2d_->ViewFlushedPaint();
+ TRACE_EVENT0("pepper", "PepperGraphics2DHost::ViewFlushedPaint");
+ if (need_flush_ack_) {
+ SendFlushAck();
+ need_flush_ack_ = false;
+ }
}
void PepperGraphics2DHost::SetScale(float scale) {
- graphics_2d_->scale_ = scale;
+ scale_ = scale;
}
float PepperGraphics2DHost::GetScale() const {
- return graphics_2d_->scale_;
+ return scale_;
}
bool PepperGraphics2DHost::IsAlwaysOpaque() const {
- return graphics_2d_->is_always_opaque_;
+ return is_always_opaque_;
}
webkit::ppapi::PPB_ImageData_Impl* PepperGraphics2DHost::ImageData() {
- return graphics_2d_->image_data_.get();
+ return image_data_.get();
}
bool PepperGraphics2DHost::IsGraphics2DHost() const {
return true;
}
-#if !defined(OS_NACL)
int32_t PepperGraphics2DHost::OnHostMsgPaintImageData(
ppapi::host::HostMessageContext* context,
const ppapi::HostResource& image_data,
const PP_Point& top_left,
bool src_rect_specified,
const PP_Rect& src_rect) {
- graphics_2d_->PaintImageData(image_data.host_resource(), &top_left,
- src_rect_specified ? &src_rect : NULL);
+ EnterResourceNoLock<PPB_ImageData_API> enter(image_data.host_resource(),
+ true);
+ if (enter.failed())
+ return PP_ERROR_BADRESOURCE;
+ PPB_ImageData_Impl* image_resource =
+ static_cast<PPB_ImageData_Impl*>(enter.object());
+
+ QueuedOperation operation(QueuedOperation::PAINT);
+ operation.paint_image = image_resource;
+ if (!ValidateAndConvertRect(src_rect_specified ? &src_rect : NULL,
+ image_resource->width(),
+ image_resource->height(),
+ &operation.paint_src_rect))
+ return PP_ERROR_BADARGUMENT;
+
+ // Validate the bitmap position using the previously-validated rect, there
+ // should be no painted area outside of the image.
+ int64 x64 = static_cast<int64>(top_left.x);
+ int64 y64 = static_cast<int64>(top_left.y);
+ if (x64 + static_cast<int64>(operation.paint_src_rect.x()) < 0 ||
+ x64 + static_cast<int64>(operation.paint_src_rect.right()) >
+ image_data_->width())
+ return PP_ERROR_BADARGUMENT;
+ if (y64 + static_cast<int64>(operation.paint_src_rect.y()) < 0 ||
+ y64 + static_cast<int64>(operation.paint_src_rect.bottom()) >
+ image_data_->height())
+ return PP_ERROR_BADARGUMENT;
+ operation.paint_x = top_left.x;
+ operation.paint_y = top_left.y;
+
+ queued_operations_.push_back(operation);
return PP_OK;
}
@@ -145,31 +514,65 @@ int32_t PepperGraphics2DHost::OnHostMsgScroll(
bool clip_specified,
const PP_Rect& clip,
const PP_Point& amount) {
- graphics_2d_->Scroll(clip_specified ? &clip : NULL, &amount);
+ QueuedOperation operation(QueuedOperation::SCROLL);
+ if (!ValidateAndConvertRect(clip_specified ? &clip : NULL,
+ image_data_->width(),
+ image_data_->height(),
+ &operation.scroll_clip_rect))
+ return PP_ERROR_BADARGUMENT;
+
+ // If we're being asked to scroll by more than the clip rect size, just
+ // ignore this scroll command and say it worked.
+ int32 dx = amount.x;
+ int32 dy = amount.y;
+ if (dx <= -image_data_->width() || dx >= image_data_->width() ||
+ dy <= -image_data_->height() || dy >= image_data_->height())
+ return PP_ERROR_BADARGUMENT;
+
+ operation.scroll_dx = dx;
+ operation.scroll_dy = dy;
+
+ queued_operations_.push_back(operation);
return PP_OK;
}
int32_t PepperGraphics2DHost::OnHostMsgReplaceContents(
ppapi::host::HostMessageContext* context,
const ppapi::HostResource& image_data) {
- graphics_2d_->ReplaceContents(image_data.host_resource());
+ EnterResourceNoLock<PPB_ImageData_API> enter(image_data.host_resource(),
+ true);
+ if (enter.failed())
+ return PP_ERROR_BADRESOURCE;
+ PPB_ImageData_Impl* image_resource =
+ static_cast<PPB_ImageData_Impl*>(enter.object());
+
+ if (!PPB_ImageData_Impl::IsImageDataFormatSupported(
+ image_resource->format()))
+ return PP_ERROR_BADARGUMENT;
+
+ if (image_resource->width() != image_data_->width() ||
+ image_resource->height() != image_data_->height())
+ return PP_ERROR_BADARGUMENT;
+
+ QueuedOperation operation(QueuedOperation::REPLACE);
+ operation.replace_image = image_resource;
+ queued_operations_.push_back(operation);
return PP_OK;
}
int32_t PepperGraphics2DHost::OnHostMsgFlush(
ppapi::host::HostMessageContext* context) {
+ // Don't allow more than one pending flush at a time.
+ if (HasPendingFlush())
+ return PP_ERROR_INPROGRESS;
+
PP_Resource old_image_data = 0;
flush_reply_context_ = context->MakeReplyMessageContext();
- // TODO: when merge PP_Graphics2D_Impl, we won't need this tracked callback.
- scoped_refptr<ppapi::TrackedCallback> callback(new ppapi::TrackedCallback(
- graphics_2d_.get(),
- PP_MakeCompletionCallback(&PepperGraphics2DHost::SendFlushACKToPlugin,
- AsWeakPtr())));
if (is_running_in_process_)
- return graphics_2d_->Flush(callback, NULL);
+ return Flush(NULL);
// Reuse image data when running out of process.
- int32_t result = graphics_2d_->Flush(callback, &old_image_data);
+ int32_t result = Flush(&old_image_data);
if (old_image_data) {
// If the Graphics2D has an old image data it's not using any more, send
@@ -188,7 +591,11 @@ int32_t PepperGraphics2DHost::OnHostMsgFlush(
int32_t PepperGraphics2DHost::OnHostMsgSetScale(
ppapi::host::HostMessageContext* context,
float scale) {
- return graphics_2d_->SetScale(scale) ? PP_OK : PP_ERROR_BADARGUMENT;
+ if (scale > 0.0f) {
+ scale_ = scale;
+ return PP_OK;
+ }
+ return PP_ERROR_BADARGUMENT;
}
int32_t PepperGraphics2DHost::OnHostMsgReadImageData(
@@ -199,15 +606,216 @@ int32_t PepperGraphics2DHost::OnHostMsgReadImageData(
return ReadImageData(image, &top_left) ? PP_OK : PP_ERROR_FAILED;
}
-// static
-void PepperGraphics2DHost::SendFlushACKToPlugin(void* data,
- int32_t pp_error) {
- if (!data || pp_error != PP_OK)
+int32_t PepperGraphics2DHost::Flush(PP_Resource* old_image_data) {
+ bool done_replace_contents = false;
+ bool no_update_visible = true;
+ bool is_plugin_visible = true;
+ for (size_t i = 0; i < queued_operations_.size(); i++) {
+ QueuedOperation& operation = queued_operations_[i];
+ gfx::Rect op_rect;
+ switch (operation.type) {
+ case QueuedOperation::PAINT:
+ ExecutePaintImageData(operation.paint_image,
+ operation.paint_x, operation.paint_y,
+ operation.paint_src_rect,
+ &op_rect);
+ break;
+ case QueuedOperation::SCROLL:
+ ExecuteScroll(operation.scroll_clip_rect,
+ operation.scroll_dx, operation.scroll_dy,
+ &op_rect);
+ break;
+ case QueuedOperation::REPLACE:
+ // Since the out parameter |old_image_data| takes ownership of the
+ // reference, if there are more than one ReplaceContents calls queued
+ // the first |old_image_data| will get overwritten and leaked. So we
+ // only supply this for the first call.
+ ExecuteReplaceContents(operation.replace_image, &op_rect,
+ done_replace_contents ? NULL : old_image_data);
+ done_replace_contents = true;
+ break;
+ }
+
+ // For correctness with accelerated compositing, we must issue an invalidate
+ // on the full op_rect even if it is partially or completely off-screen.
+ // However, if we issue an invalidate for a clipped-out region, WebKit will
+ // do nothing and we won't get any ViewWillInitiatePaint/ViewFlushedPaint
+ // calls, leaving our callback stranded. So we still need to check whether
+ // the repainted area is visible to determine how to deal with the callback.
+ if (bound_instance_ && !op_rect.IsEmpty()) {
+ gfx::Point scroll_delta(operation.scroll_dx, operation.scroll_dy);
+ if (!ConvertToLogicalPixels(scale_,
+ &op_rect,
+ operation.type == QueuedOperation::SCROLL ?
+ &scroll_delta : NULL)) {
+ // Conversion requires falling back to InvalidateRect.
+ operation.type = QueuedOperation::PAINT;
+ }
+
+ gfx::Rect clip = webkit::ppapi::PP_ToGfxRect(
+ bound_instance_->view_data().clip_rect);
+ is_plugin_visible = !clip.IsEmpty();
+
+ // Set |no_update_visible| to false if the change overlaps the visible
+ // area.
+ gfx::Rect visible_changed_rect = gfx::IntersectRects(clip, op_rect);
+ if (!visible_changed_rect.IsEmpty())
+ no_update_visible = false;
+
+ // Notify the plugin of the entire change (op_rect), even if it is
+ // partially or completely off-screen.
+ if (operation.type == QueuedOperation::SCROLL) {
+ bound_instance_->ScrollRect(scroll_delta.x(), scroll_delta.y(),
+ op_rect);
+ } else {
+ bound_instance_->InvalidateRect(op_rect);
+ }
+ }
+ }
+ queued_operations_.clear();
+
+ if (!bound_instance_) {
+ // As promised in the API, we always schedule callback when unbound.
+ ScheduleOffscreenFlushAck();
+ } else if (no_update_visible && is_plugin_visible &&
+ bound_instance_->view_data().is_page_visible) {
+ // There's nothing visible to invalidate so just schedule the callback to
+ // execute in the next round of the message loop.
+ ScheduleOffscreenFlushAck();
+ } else {
+ need_flush_ack_ = true;
+ }
+
+ return PP_OK_COMPLETIONPENDING;
+}
+
+void PepperGraphics2DHost::ExecutePaintImageData(PPB_ImageData_Impl* image,
+ int x, int y,
+ const gfx::Rect& src_rect,
+ gfx::Rect* invalidated_rect) {
+ // Ensure the source image is mapped to read from it.
+ ImageDataAutoMapper auto_mapper(image);
+ if (!auto_mapper.is_valid())
return;
- PepperGraphics2DHost* self = (PepperGraphics2DHost*) data;
- self->host()->SendReply(self->flush_reply_context_,
- PpapiPluginMsg_Graphics2D_FlushAck());
+
+ // Portion within the source image to cut out.
+ SkIRect src_irect = { src_rect.x(), src_rect.y(),
+ src_rect.right(), src_rect.bottom() };
+
+ // Location within the backing store to copy to.
+ *invalidated_rect = src_rect;
+ invalidated_rect->Offset(x, y);
+ SkRect dest_rect = { SkIntToScalar(invalidated_rect->x()),
+ SkIntToScalar(invalidated_rect->y()),
+ SkIntToScalar(invalidated_rect->right()),
+ SkIntToScalar(invalidated_rect->bottom()) };
+
+ if (image->format() != image_data_->format()) {
+ // Convert the image data if the format does not match.
+ ConvertImageData(image, src_irect, image_data_, dest_rect);
+ } else {
+ // We're guaranteed to have a mapped canvas since we mapped it in Init().
+ SkCanvas* backing_canvas = image_data_->GetCanvas();
+
+ // We want to replace the contents of the bitmap rather than blend.
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ backing_canvas->drawBitmapRect(*image->GetMappedBitmap(),
+ &src_irect, dest_rect, &paint);
+ }
+}
+
+void PepperGraphics2DHost::ExecuteScroll(const gfx::Rect& clip,
+ int dx, int dy,
+ gfx::Rect* invalidated_rect) {
+ gfx::ScrollCanvas(image_data_->GetCanvas(),
+ clip, gfx::Vector2d(dx, dy));
+ *invalidated_rect = clip;
+}
+
+void PepperGraphics2DHost::ExecuteReplaceContents(PPB_ImageData_Impl* image,
+ gfx::Rect* invalidated_rect,
+ PP_Resource* old_image_data) {
+ if (image->format() != image_data_->format()) {
+ DCHECK(image->width() == image_data_->width() &&
+ image->height() == image_data_->height());
+ // Convert the image data if the format does not match.
+ SkIRect src_irect = { 0, 0, image->width(), image->height() };
+ SkRect dest_rect = { SkIntToScalar(0),
+ SkIntToScalar(0),
+ SkIntToScalar(image_data_->width()),
+ SkIntToScalar(image_data_->height()) };
+ ConvertImageData(image, src_irect, image_data_, dest_rect);
+ } else {
+ // The passed-in image may not be mapped in our process, and we need to
+ // guarantee that the current backing store is always mapped.
+ if (!image->Map())
+ return;
+
+ if (old_image_data)
+ *old_image_data = image_data_->GetReference();
+ image_data_ = image;
+ }
+ *invalidated_rect = gfx::Rect(0, 0,
+ image_data_->width(), image_data_->height());
+}
+
+void PepperGraphics2DHost::SendFlushAck() {
+ host()->SendReply(flush_reply_context_,
+ PpapiPluginMsg_Graphics2D_FlushAck());
+}
+
+void PepperGraphics2DHost::SendOffscreenFlushAck() {
+ DCHECK(offscreen_flush_pending_);
+
+ // We must clear this flag before issuing the callback. It will be
+ // common for the plugin to issue another invalidate in response to a flush
+ // callback, and we don't want to think that a callback is already pending.
+ offscreen_flush_pending_ = false;
+ SendFlushAck();
+}
+
+void PepperGraphics2DHost::ScheduleOffscreenFlushAck() {
+ offscreen_flush_pending_ = true;
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&PepperGraphics2DHost::SendOffscreenFlushAck,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kOffscreenCallbackDelayMs));
+}
+
+bool PepperGraphics2DHost::HasPendingFlush() const {
+ return need_flush_ack_ || offscreen_flush_pending_;
+}
+
+// static
+bool PepperGraphics2DHost::ConvertToLogicalPixels(float scale,
+ gfx::Rect* op_rect,
+ gfx::Point* delta) {
+ if (scale == 1.0f || scale <= 0.0f)
+ return true;
+
+ gfx::Rect original_rect = *op_rect;
+ // Take the enclosing rectangle after scaling so a rectangle scaled down then
+ // scaled back up by the inverse scale would fully contain the entire area
+ // affected by the original rectangle.
+ *op_rect = gfx::ToEnclosingRect(gfx::ScaleRect(*op_rect, scale));
+ if (delta) {
+ gfx::Point original_delta = *delta;
+ float inverse_scale = 1.0f / scale;
+ *delta = gfx::ToFlooredPoint(gfx::ScalePoint(*delta, scale));
+
+ gfx::Rect inverse_scaled_rect =
+ gfx::ToEnclosingRect(gfx::ScaleRect(*op_rect, inverse_scale));
+ if (original_rect != inverse_scaled_rect)
+ return false;
+ gfx::Point inverse_scaled_point =
+ gfx::ToFlooredPoint(gfx::ScalePoint(*delta, inverse_scale));
+ if (original_delta != inverse_scaled_point)
+ return false;
+ }
+
+ return true;
}
-#endif // !defined(OS_NACL)
} // namespace content
diff --git a/content/renderer/pepper/pepper_graphics_2d_host.h b/content/renderer/pepper/pepper_graphics_2d_host.h
index 3496b22..1970f8d 100644
--- a/content/renderer/pepper/pepper_graphics_2d_host.h
+++ b/content/renderer/pepper/pepper_graphics_2d_host.h
@@ -5,21 +5,31 @@
#ifndef CONTENT_RENDERER_PEPPER_PEPPER_GRAPHICS_2D_HOST_H_
#define CONTENT_RENDERER_PEPPER_PEPPER_GRAPHICS_2D_HOST_H_
+#include <vector>
+
+#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/memory/weak_ptr.h"
#include "content/common/content_export.h"
#include "ppapi/c/ppb_graphics_2d.h"
#include "ppapi/host/host_message_context.h"
#include "ppapi/host/resource_host.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebCanvas.h"
#include "webkit/plugins/ppapi/plugin_delegate.h"
-#include "webkit/plugins/ppapi/ppb_graphics_2d_impl.h"
+
+namespace gfx {
+class Point;
+class Rect;
+}
namespace webkit {
namespace ppapi {
class PPB_ImageData_Impl;
class PluginInstance;
-}
-}
+} // namespace ppapi
+} // namespace webkit
+
+using webkit::ppapi::PPB_ImageData_Impl;
namespace content {
@@ -38,6 +48,7 @@ class CONTENT_EXPORT PepperGraphics2DHost
virtual ~PepperGraphics2DHost();
+ // ppapi::host::ResourceHost override.
virtual int32_t OnResourceMessageReceived(
const IPC::Message& msg,
ppapi::host::HostMessageContext* context) OVERRIDE;
@@ -56,7 +67,7 @@ class CONTENT_EXPORT PepperGraphics2DHost
virtual void SetScale(float scale) OVERRIDE;
virtual float GetScale() const OVERRIDE;
virtual bool IsAlwaysOpaque() const OVERRIDE;
- virtual webkit::ppapi::PPB_ImageData_Impl* ImageData() OVERRIDE;
+ virtual PPB_ImageData_Impl* ImageData() OVERRIDE;
virtual bool IsGraphics2DHost() const OVERRIDE;
private:
@@ -64,6 +75,8 @@ class CONTENT_EXPORT PepperGraphics2DHost
PP_Instance instance,
PP_Resource resource);
+ bool Init(int width, int height, bool is_always_opaque);
+
int32_t OnHostMsgPaintImageData(ppapi::host::HostMessageContext* context,
const ppapi::HostResource& image_data,
const PP_Point& top_left,
@@ -82,15 +95,83 @@ class CONTENT_EXPORT PepperGraphics2DHost
PP_Resource image,
const PP_Point& top_left);
- static void SendFlushACKToPlugin(void* data, int32_t pp_error);
+ // If |old_image_data| is not NULL, a previous used ImageData object will be
+ // reused. This is used by ReplaceContents.
+ int32_t Flush(PP_Resource* old_image_data);
+
+ // Called internally to execute the different queued commands. The
+ // parameters to these functions will have already been validated. The last
+ // rect argument will be filled by each function with the area affected by
+ // the update that requires invalidation. If there were no pixels changed,
+ // this rect can be untouched.
+ void ExecutePaintImageData(PPB_ImageData_Impl* image,
+ int x, int y,
+ const gfx::Rect& src_rect,
+ gfx::Rect* invalidated_rect);
+ void ExecuteScroll(const gfx::Rect& clip, int dx, int dy,
+ gfx::Rect* invalidated_rect);
+ void ExecuteReplaceContents(PPB_ImageData_Impl* image,
+ gfx::Rect* invalidated_rect,
+ PP_Resource* old_image_data);
+
+ void SendFlushAck();
+
+ // Function scheduled to execute by ScheduleOffscreenFlushAck that actually
+ // issues the offscreen callbacks.
+ void SendOffscreenFlushAck();
+
+ // Schedules the offscreen flush ACK at a future time.
+ void ScheduleOffscreenFlushAck();
+
+ // Returns true if there is any type of flush callback pending.
+ bool HasPendingFlush() const;
+
+ // Scale |op_rect| to logical pixels, taking care to include partially-
+ // covered logical pixels (aka DIPs). Also scale optional |delta| to logical
+ // pixels as well for scrolling cases. Returns false for scrolling cases where
+ // scaling either |op_rect| or |delta| would require scrolling to fall back to
+ // invalidation due to rounding errors, true otherwise.
+ static bool ConvertToLogicalPixels(float scale,
+ gfx::Rect* op_rect,
+ gfx::Point* delta);
+
+
+ RendererPpapiHost* renderer_ppapi_host_;
+
+ scoped_refptr<PPB_ImageData_Impl> image_data_;
+
+ // Non-owning pointer to the plugin instance this context is currently bound
+ // to, if any. If the context is currently unbound, this will be NULL.
+ webkit::ppapi::PluginInstance* bound_instance_;
+
+ // Keeps track of all drawing commands queued before a Flush call.
+ struct QueuedOperation;
+ typedef std::vector<QueuedOperation> OperationQueue;
+ OperationQueue queued_operations_;
+
+ // True if we need to send an ACK to plugin.
+ bool need_flush_ack_;
+
+ // When doing offscreen flushes, we issue a task that issues the callback
+ // later. This is set when one of those tasks is pending so that we can
+ // enforce the "only one pending flush at a time" constraint in the API.
+ bool offscreen_flush_pending_;
+
+ // Set to true if the plugin declares that this device will always be opaque.
+ // This allows us to do more optimized painting in some cases.
+ bool is_always_opaque_;
+
+ // Set to the scale between what the plugin considers to be one pixel and one
+ // DIP
+ float scale_;
- // TODO: merge this delegation into this host class.
- scoped_refptr<webkit::ppapi::PPB_Graphics2D_Impl> graphics_2d_;
+ base::WeakPtrFactory<PepperGraphics2DHost> weak_ptr_factory_;
ppapi::host::ReplyMessageContext flush_reply_context_;
bool is_running_in_process_;
+ friend class PepperGraphics2DHostTest;
DISALLOW_COPY_AND_ASSIGN(PepperGraphics2DHost);
};
diff --git a/content/renderer/pepper/pepper_graphics_2d_host_unittest.cc b/content/renderer/pepper/pepper_graphics_2d_host_unittest.cc
new file mode 100644
index 0000000..78a8fe0
--- /dev/null
+++ b/content/renderer/pepper/pepper_graphics_2d_host_unittest.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 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 "content/renderer/pepper/pepper_graphics_2d_host.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+
+namespace content {
+
+class PepperGraphics2DHostTest : public testing::Test {
+ public:
+ static bool ConvertToLogicalPixels(float scale,
+ gfx::Rect* op_rect,
+ gfx::Point* delta) {
+ return PepperGraphics2DHost::ConvertToLogicalPixels(scale, op_rect, delta);
+ }
+};
+
+TEST_F(PepperGraphics2DHostTest, ConvertToLogicalPixels) {
+ static const struct {
+ int x1;
+ int y1;
+ int w1;
+ int h1;
+ int x2;
+ int y2;
+ int w2;
+ int h2;
+ int dx1;
+ int dy1;
+ int dx2;
+ int dy2;
+ float scale;
+ bool result;
+ } tests[] = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0, true },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.0, true },
+ { 0, 0, 4, 4, 0, 0, 2, 2, 0, 0, 0, 0, 0.5, true },
+ { 1, 1, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0.5, false },
+ { 53, 75, 100, 100, 53, 75, 100, 100, 0, 0, 0, 0, 1.0, true },
+ { 53, 75, 100, 100, 106, 150, 200, 200, 0, 0, 0, 0, 2.0, true },
+ { 53, 75, 100, 100, 26, 37, 51, 51, 0, 0, 0, 0, 0.5, false },
+ { 53, 74, 100, 100, 26, 37, 51, 50, 0, 0, 0, 0, 0.5, false },
+ { -1, -1, 100, 100, -1, -1, 51, 51, 0, 0, 0, 0, 0.5, false },
+ { -2, -2, 100, 100, -1, -1, 50, 50, 4, -4, 2, -2, 0.5, true },
+ { -101, -100, 50, 50, -51, -50, 26, 25, 0, 0, 0, 0, 0.5, false },
+ { 10, 10, 20, 20, 5, 5, 10, 10, 0, 0, 0, 0, 0.5, true },
+ // Cannot scroll due to partial coverage on sides
+ { 11, 10, 20, 20, 5, 5, 11, 10, 0, 0, 0, 0, 0.5, false },
+ // Can scroll since backing store is actually smaller/scaling up
+ { 11, 20, 100, 100, 22, 40, 200, 200, 7, 3, 14, 6, 2.0, true },
+ // Can scroll due to delta and bounds being aligned
+ { 10, 10, 20, 20, 5, 5, 10, 10, 6, 4, 3, 2, 0.5, true },
+ // Cannot scroll due to dx
+ { 10, 10, 20, 20, 5, 5, 10, 10, 5, 4, 2, 2, 0.5, false },
+ // Cannot scroll due to dy
+ { 10, 10, 20, 20, 5, 5, 10, 10, 6, 3, 3, 1, 0.5, false },
+ // Cannot scroll due to top
+ { 10, 11, 20, 20, 5, 5, 10, 11, 6, 4, 3, 2, 0.5, false },
+ // Cannot scroll due to left
+ { 7, 10, 20, 20, 3, 5, 11, 10, 6, 4, 3, 2, 0.5, false },
+ // Cannot scroll due to width
+ { 10, 10, 21, 20, 5, 5, 11, 10, 6, 4, 3, 2, 0.5, false },
+ // Cannot scroll due to height
+ { 10, 10, 20, 51, 5, 5, 10, 26, 6, 4, 3, 2, 0.5, false },
+ // Check negative scroll deltas
+ { 10, 10, 20, 20, 5, 5, 10, 10, -6, -4, -3, -2, 0.5, true },
+ { 10, 10, 20, 20, 5, 5, 10, 10, -6, -3, -3, -1, 0.5, false },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ gfx::Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ gfx::Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ gfx::Rect orig = r1;
+ gfx::Point delta(tests[i].dx1, tests[i].dy1);
+ bool res = ConvertToLogicalPixels(tests[i].scale, &r1, &delta);
+ EXPECT_EQ(r2.ToString(), r1.ToString());
+ EXPECT_EQ(res, tests[i].result);
+ if (res) {
+ EXPECT_EQ(delta, gfx::Point(tests[i].dx2, tests[i].dy2));
+ }
+ // Reverse the scale and ensure all the original pixels are still inside
+ // the result.
+ ConvertToLogicalPixels(1.0f / tests[i].scale, &r1, NULL);
+ EXPECT_TRUE(r1.Contains(orig));
+ }
+}
+
+} // namespace content