diff options
author | victorhsieh@chromium.org <victorhsieh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-05 16:02:40 +0000 |
---|---|---|
committer | victorhsieh@chromium.org <victorhsieh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-05 16:02:40 +0000 |
commit | fd36e4ed7d4adb48df2b4b208d86467ea1d20f87 (patch) | |
tree | 366066b0b925594bd9c6c7941f2a51251f8f47c3 /content/renderer/pepper | |
parent | 427e48fd18a420b8eb322cbb249b12c0211eea13 (diff) | |
download | chromium_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.cc | 694 | ||||
-rw-r--r-- | content/renderer/pepper/pepper_graphics_2d_host.h | 95 | ||||
-rw-r--r-- | content/renderer/pepper/pepper_graphics_2d_host_unittest.cc | 92 |
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 |