pepper_graphics_2d_host.cc revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/renderer/pepper/pepper_graphics_2d_host.h"
6
7#include "base/bind.h"
8#include "base/debug/trace_event.h"
9#include "base/logging.h"
10#include "base/message_loop/message_loop.h"
11#include "cc/resources/texture_mailbox.h"
12#include "content/public/renderer/render_thread.h"
13#include "content/public/renderer/renderer_ppapi_host.h"
14#include "content/renderer/pepper/common.h"
15#include "content/renderer/pepper/gfx_conversion.h"
16#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
17#include "content/renderer/pepper/ppb_image_data_impl.h"
18#include "ppapi/c/pp_bool.h"
19#include "ppapi/c/pp_errors.h"
20#include "ppapi/c/pp_rect.h"
21#include "ppapi/c/pp_resource.h"
22#include "ppapi/host/dispatch_host_message.h"
23#include "ppapi/host/host_message_context.h"
24#include "ppapi/host/ppapi_host.h"
25#include "ppapi/proxy/ppapi_messages.h"
26#include "ppapi/shared_impl/ppb_view_shared.h"
27#include "ppapi/thunk/enter.h"
28#include "skia/ext/platform_canvas.h"
29#include "third_party/skia/include/core/SkBitmap.h"
30#include "ui/gfx/blit.h"
31#include "ui/gfx/point_conversions.h"
32#include "ui/gfx/rect.h"
33#include "ui/gfx/rect_conversions.h"
34#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
35#include "ui/gfx/size_conversions.h"
36#include "ui/gfx/skia_util.h"
37
38#if defined(OS_MACOSX)
39#include "base/mac/mac_util.h"
40#include "base/mac/scoped_cftyperef.h"
41#endif
42
43using ppapi::thunk::EnterResourceNoLock;
44using ppapi::thunk::PPB_ImageData_API;
45
46namespace content {
47
48namespace {
49
50const int64 kOffscreenCallbackDelayMs = 1000 / 30;  // 30 fps
51
52// Converts a rect inside an image of the given dimensions. The rect may be
53// NULL to indicate it should be the entire image. If the rect is outside of
54// the image, this will do nothing and return false.
55bool ValidateAndConvertRect(const PP_Rect* rect,
56                            int image_width, int image_height,
57                            gfx::Rect* dest) {
58  if (!rect) {
59    // Use the entire image area.
60    *dest = gfx::Rect(0, 0, image_width, image_height);
61  } else {
62    // Validate the passed-in area.
63    if (rect->point.x < 0 || rect->point.y < 0 ||
64        rect->size.width <= 0 || rect->size.height <= 0)
65      return false;
66
67    // Check the max bounds, being careful of overflow.
68    if (static_cast<int64>(rect->point.x) +
69        static_cast<int64>(rect->size.width) >
70        static_cast<int64>(image_width))
71      return false;
72    if (static_cast<int64>(rect->point.y) +
73        static_cast<int64>(rect->size.height) >
74        static_cast<int64>(image_height))
75      return false;
76
77    *dest = gfx::Rect(rect->point.x, rect->point.y,
78                      rect->size.width, rect->size.height);
79  }
80  return true;
81}
82
83// Converts BGRA <-> RGBA.
84void ConvertBetweenBGRAandRGBA(const uint32_t* input,
85                               int pixel_length,
86                               uint32_t* output) {
87  for (int i = 0; i < pixel_length; i++) {
88    const unsigned char* pixel_in =
89        reinterpret_cast<const unsigned char*>(&input[i]);
90    unsigned char* pixel_out = reinterpret_cast<unsigned char*>(&output[i]);
91    pixel_out[0] = pixel_in[2];
92    pixel_out[1] = pixel_in[1];
93    pixel_out[2] = pixel_in[0];
94    pixel_out[3] = pixel_in[3];
95  }
96}
97
98// Converts ImageData from PP_IMAGEDATAFORMAT_BGRA_PREMUL to
99// PP_IMAGEDATAFORMAT_RGBA_PREMUL, or reverse. It's assumed that the
100// destination image is always mapped (so will have non-NULL data).
101void ConvertImageData(PPB_ImageData_Impl* src_image, const SkIRect& src_rect,
102                      PPB_ImageData_Impl* dest_image, const SkRect& dest_rect) {
103  ImageDataAutoMapper auto_mapper(src_image);
104
105  DCHECK(src_image->format() != dest_image->format());
106  DCHECK(PPB_ImageData_Impl::IsImageDataFormatSupported(src_image->format()));
107  DCHECK(PPB_ImageData_Impl::IsImageDataFormatSupported(dest_image->format()));
108
109  const SkBitmap* src_bitmap = src_image->GetMappedBitmap();
110  const SkBitmap* dest_bitmap = dest_image->GetMappedBitmap();
111  if (src_rect.width() == src_image->width() &&
112      dest_rect.width() == dest_image->width()) {
113    // Fast path if the full line needs to be converted.
114    ConvertBetweenBGRAandRGBA(
115        src_bitmap->getAddr32(static_cast<int>(src_rect.fLeft),
116                              static_cast<int>(src_rect.fTop)),
117        src_rect.width() * src_rect.height(),
118        dest_bitmap->getAddr32(static_cast<int>(dest_rect.fLeft),
119                               static_cast<int>(dest_rect.fTop)));
120  } else {
121    // Slow path where we convert line by line.
122    for (int y = 0; y < src_rect.height(); y++) {
123      ConvertBetweenBGRAandRGBA(
124          src_bitmap->getAddr32(static_cast<int>(src_rect.fLeft),
125                                static_cast<int>(src_rect.fTop + y)),
126          src_rect.width(),
127          dest_bitmap->getAddr32(static_cast<int>(dest_rect.fLeft),
128                                 static_cast<int>(dest_rect.fTop + y)));
129    }
130  }
131}
132
133}  // namespace
134
135struct PepperGraphics2DHost::QueuedOperation {
136  enum Type {
137    PAINT,
138    SCROLL,
139    REPLACE,
140  };
141
142  QueuedOperation(Type t)
143      : type(t),
144        paint_x(0),
145        paint_y(0),
146        scroll_dx(0),
147        scroll_dy(0) {
148  }
149
150  Type type;
151
152  // Valid when type == PAINT.
153  scoped_refptr<PPB_ImageData_Impl> paint_image;
154  int paint_x, paint_y;
155  gfx::Rect paint_src_rect;
156
157  // Valid when type == SCROLL.
158  gfx::Rect scroll_clip_rect;
159  int scroll_dx, scroll_dy;
160
161  // Valid when type == REPLACE.
162  scoped_refptr<PPB_ImageData_Impl> replace_image;
163};
164
165// static
166PepperGraphics2DHost* PepperGraphics2DHost::Create(
167    RendererPpapiHost* host,
168    PP_Instance instance,
169    PP_Resource resource,
170    const PP_Size& size,
171    PP_Bool is_always_opaque,
172    scoped_refptr<PPB_ImageData_Impl> backing_store) {
173  PepperGraphics2DHost* resource_host =
174      new PepperGraphics2DHost(host, instance, resource);
175  if (!resource_host->Init(size.width, size.height,
176                           PP_ToBool(is_always_opaque),
177                           backing_store)) {
178    delete resource_host;
179    return NULL;
180  }
181  return resource_host;
182}
183
184PepperGraphics2DHost::PepperGraphics2DHost(RendererPpapiHost* host,
185                                           PP_Instance instance,
186                                           PP_Resource resource)
187    : ResourceHost(host->GetPpapiHost(), instance, resource),
188      renderer_ppapi_host_(host),
189      bound_instance_(NULL),
190      need_flush_ack_(false),
191      offscreen_flush_pending_(false),
192      is_always_opaque_(false),
193      scale_(1.0f),
194      is_running_in_process_(host->IsRunningInProcess()),
195      texture_mailbox_modified_(true) {}
196
197PepperGraphics2DHost::~PepperGraphics2DHost() {
198  // Unbind from the instance when destroyed if we're still bound.
199  if (bound_instance_)
200    bound_instance_->BindGraphics(bound_instance_->pp_instance(), 0);
201}
202
203bool PepperGraphics2DHost::Init(
204    int width,
205    int height,
206    bool is_always_opaque,
207    scoped_refptr<PPB_ImageData_Impl> backing_store) {
208  // The underlying PPB_ImageData_Impl will validate the dimensions.
209  image_data_ = backing_store;
210  if (!image_data_->Init(PPB_ImageData_Impl::GetNativeImageDataFormat(),
211                         width, height, true) ||
212      !image_data_->Map()) {
213    image_data_ = NULL;
214    return false;
215  }
216  is_always_opaque_ = is_always_opaque;
217  scale_ = 1.0f;
218  return true;
219}
220
221int32_t PepperGraphics2DHost::OnResourceMessageReceived(
222    const IPC::Message& msg,
223    ppapi::host::HostMessageContext* context) {
224  IPC_BEGIN_MESSAGE_MAP(PepperGraphics2DHost, msg)
225    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
226        PpapiHostMsg_Graphics2D_PaintImageData,
227        OnHostMsgPaintImageData)
228    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
229        PpapiHostMsg_Graphics2D_Scroll,
230        OnHostMsgScroll)
231    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
232        PpapiHostMsg_Graphics2D_ReplaceContents,
233        OnHostMsgReplaceContents)
234    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
235        PpapiHostMsg_Graphics2D_Flush,
236        OnHostMsgFlush)
237    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
238        PpapiHostMsg_Graphics2D_SetScale,
239        OnHostMsgSetScale)
240    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
241        PpapiHostMsg_Graphics2D_ReadImageData,
242        OnHostMsgReadImageData)
243  IPC_END_MESSAGE_MAP()
244  return PP_ERROR_FAILED;
245}
246
247bool PepperGraphics2DHost::IsGraphics2DHost() {
248  return true;
249}
250
251bool PepperGraphics2DHost::ReadImageData(PP_Resource image,
252                                         const PP_Point* top_left) {
253  // Get and validate the image object to paint into.
254  EnterResourceNoLock<PPB_ImageData_API> enter(image, true);
255  if (enter.failed())
256    return false;
257  PPB_ImageData_Impl* image_resource =
258      static_cast<PPB_ImageData_Impl*>(enter.object());
259  if (!PPB_ImageData_Impl::IsImageDataFormatSupported(
260          image_resource->format()))
261    return false;  // Must be in the right format.
262
263  // Validate the bitmap position.
264  int x = top_left->x;
265  if (x < 0 ||
266      static_cast<int64>(x) + static_cast<int64>(image_resource->width()) >
267      image_data_->width())
268    return false;
269  int y = top_left->y;
270  if (y < 0 ||
271      static_cast<int64>(y) + static_cast<int64>(image_resource->height()) >
272      image_data_->height())
273    return false;
274
275  ImageDataAutoMapper auto_mapper(image_resource);
276  if (!auto_mapper.is_valid())
277    return false;
278
279  SkIRect src_irect = { x, y,
280                        x + image_resource->width(),
281                        y + image_resource->height() };
282  SkRect dest_rect = { SkIntToScalar(0),
283                       SkIntToScalar(0),
284                       SkIntToScalar(image_resource->width()),
285                       SkIntToScalar(image_resource->height()) };
286
287  if (image_resource->format() != image_data_->format()) {
288    // Convert the image data if the format does not match.
289    ConvertImageData(image_data_.get(), src_irect, image_resource, dest_rect);
290  } else {
291    SkCanvas* dest_canvas = image_resource->GetCanvas();
292
293    // We want to replace the contents of the bitmap rather than blend.
294    SkPaint paint;
295    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
296    dest_canvas->drawBitmapRect(*image_data_->GetMappedBitmap(),
297                                &src_irect, dest_rect, &paint);
298  }
299  return true;
300}
301
302bool PepperGraphics2DHost::BindToInstance(
303    PepperPluginInstanceImpl* new_instance) {
304  if (new_instance && new_instance->pp_instance() != pp_instance())
305    return false;  // Can't bind other instance's contexts.
306  if (bound_instance_ == new_instance)
307    return true;  // Rebinding the same device, nothing to do.
308  if (bound_instance_ && new_instance)
309    return false;  // Can't change a bound device.
310
311  if (!new_instance) {
312    // When the device is detached, we'll not get any more paint callbacks so
313    // we need to clear the list, but we still want to issue any pending
314    // callbacks to the plugin.
315    if (need_flush_ack_)
316      ScheduleOffscreenFlushAck();
317  } else {
318    // Devices being replaced, redraw the plugin.
319    new_instance->InvalidateRect(gfx::Rect());
320  }
321
322  texture_mailbox_modified_ = true;
323
324  bound_instance_ = new_instance;
325  return true;
326}
327
328// The |backing_bitmap| must be clipped to the |plugin_rect| to avoid painting
329// outside the plugin area. This can happen if the plugin has been resized since
330// PaintImageData verified the image is within the plugin size.
331void PepperGraphics2DHost::Paint(blink::WebCanvas* canvas,
332                                 const gfx::Rect& plugin_rect,
333                                 const gfx::Rect& paint_rect) {
334  TRACE_EVENT0("pepper", "PepperGraphics2DHost::Paint");
335  ImageDataAutoMapper auto_mapper(image_data_.get());
336  const SkBitmap& backing_bitmap = *image_data_->GetMappedBitmap();
337
338  gfx::Rect invalidate_rect = plugin_rect;
339  invalidate_rect.Intersect(paint_rect);
340  SkRect sk_invalidate_rect = gfx::RectToSkRect(invalidate_rect);
341  SkAutoCanvasRestore auto_restore(canvas, true);
342  canvas->clipRect(sk_invalidate_rect);
343  gfx::Size pixel_image_size(image_data_->width(), image_data_->height());
344  gfx::Size image_size = gfx::ToFlooredSize(
345      gfx::ScaleSize(pixel_image_size, scale_));
346
347  PepperPluginInstance* plugin_instance =
348      renderer_ppapi_host_->GetPluginInstance(pp_instance());
349  if (!plugin_instance)
350    return;
351  if (plugin_instance->IsFullPagePlugin()) {
352    // When we're resizing a window with a full-frame plugin, the plugin may
353    // not yet have bound a new device, which will leave parts of the
354    // background exposed if the window is getting larger. We want this to
355    // show white (typically less jarring) rather than black or uninitialized.
356    // We don't do this for non-full-frame plugins since we specifically want
357    // the page background to show through.
358    SkAutoCanvasRestore auto_restore(canvas, true);
359    SkRect image_data_rect =
360        gfx::RectToSkRect(gfx::Rect(plugin_rect.origin(), image_size));
361    canvas->clipRect(image_data_rect, SkRegion::kDifference_Op);
362
363    SkPaint paint;
364    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
365    paint.setColor(SK_ColorWHITE);
366    canvas->drawRect(sk_invalidate_rect, paint);
367  }
368
369  SkBitmap image;
370  // Copy to device independent bitmap when target canvas doesn't support
371  // platform paint.
372  if (!skia::SupportsPlatformPaint(canvas))
373    backing_bitmap.copyTo(&image, kPMColor_SkColorType);
374  else
375    image = backing_bitmap;
376
377  SkPaint paint;
378  if (is_always_opaque_) {
379    // When we know the device is opaque, we can disable blending for slightly
380    // more optimized painting.
381    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
382  }
383
384  SkPoint origin;
385  origin.set(SkIntToScalar(plugin_rect.x()), SkIntToScalar(plugin_rect.y()));
386
387  SkPoint pixel_origin = origin;
388
389  if (scale_ != 1.0f && scale_ > 0.0f) {
390    canvas->scale(scale_, scale_);
391    pixel_origin.set(pixel_origin.x() * (1.0f / scale_),
392                     pixel_origin.y() * (1.0f / scale_));
393  }
394  canvas->drawBitmap(image, pixel_origin.x(), pixel_origin.y(), &paint);
395}
396
397void PepperGraphics2DHost::ViewInitiatedPaint() {
398}
399
400void PepperGraphics2DHost::ViewFlushedPaint() {
401  TRACE_EVENT0("pepper", "PepperGraphics2DHost::ViewFlushedPaint");
402  if (need_flush_ack_) {
403    SendFlushAck();
404    need_flush_ack_ = false;
405  }
406}
407
408void PepperGraphics2DHost::SetScale(float scale) {
409  scale_ = scale;
410}
411
412float PepperGraphics2DHost::GetScale() const {
413  return scale_;
414}
415
416bool PepperGraphics2DHost::IsAlwaysOpaque() const {
417  return is_always_opaque_;
418}
419
420PPB_ImageData_Impl* PepperGraphics2DHost::ImageData() {
421  return image_data_.get();
422}
423
424gfx::Size PepperGraphics2DHost::Size() const {
425  if (!image_data_)
426    return gfx::Size();
427  return gfx::Size(image_data_->width(), image_data_->height());
428}
429
430int32_t PepperGraphics2DHost::OnHostMsgPaintImageData(
431    ppapi::host::HostMessageContext* context,
432    const ppapi::HostResource& image_data,
433    const PP_Point& top_left,
434    bool src_rect_specified,
435    const PP_Rect& src_rect) {
436  EnterResourceNoLock<PPB_ImageData_API> enter(image_data.host_resource(),
437                                               true);
438  if (enter.failed())
439    return PP_ERROR_BADRESOURCE;
440  PPB_ImageData_Impl* image_resource =
441      static_cast<PPB_ImageData_Impl*>(enter.object());
442
443  QueuedOperation operation(QueuedOperation::PAINT);
444  operation.paint_image = image_resource;
445  if (!ValidateAndConvertRect(src_rect_specified ? &src_rect : NULL,
446                              image_resource->width(),
447                              image_resource->height(),
448                              &operation.paint_src_rect))
449    return PP_ERROR_BADARGUMENT;
450
451  // Validate the bitmap position using the previously-validated rect, there
452  // should be no painted area outside of the image.
453  int64 x64 = static_cast<int64>(top_left.x);
454  int64 y64 = static_cast<int64>(top_left.y);
455  if (x64 + static_cast<int64>(operation.paint_src_rect.x()) < 0 ||
456      x64 + static_cast<int64>(operation.paint_src_rect.right()) >
457      image_data_->width())
458    return PP_ERROR_BADARGUMENT;
459  if (y64 + static_cast<int64>(operation.paint_src_rect.y()) < 0 ||
460      y64 + static_cast<int64>(operation.paint_src_rect.bottom()) >
461      image_data_->height())
462    return PP_ERROR_BADARGUMENT;
463  operation.paint_x = top_left.x;
464  operation.paint_y = top_left.y;
465
466  queued_operations_.push_back(operation);
467  return PP_OK;
468}
469
470int32_t PepperGraphics2DHost::OnHostMsgScroll(
471    ppapi::host::HostMessageContext* context,
472    bool clip_specified,
473    const PP_Rect& clip,
474    const PP_Point& amount) {
475  QueuedOperation operation(QueuedOperation::SCROLL);
476  if (!ValidateAndConvertRect(clip_specified ? &clip : NULL,
477                              image_data_->width(),
478                              image_data_->height(),
479                              &operation.scroll_clip_rect))
480    return PP_ERROR_BADARGUMENT;
481
482  // If we're being asked to scroll by more than the clip rect size, just
483  // ignore this scroll command and say it worked.
484  int32 dx = amount.x;
485  int32 dy = amount.y;
486  if (dx <= -image_data_->width() || dx >= image_data_->width() ||
487      dy <= -image_data_->height() || dy >= image_data_->height())
488    return PP_ERROR_BADARGUMENT;
489
490  operation.scroll_dx = dx;
491  operation.scroll_dy = dy;
492
493  queued_operations_.push_back(operation);
494  return PP_OK;
495}
496
497int32_t PepperGraphics2DHost::OnHostMsgReplaceContents(
498    ppapi::host::HostMessageContext* context,
499    const ppapi::HostResource& image_data) {
500  EnterResourceNoLock<PPB_ImageData_API> enter(image_data.host_resource(),
501                                               true);
502  if (enter.failed())
503    return PP_ERROR_BADRESOURCE;
504  PPB_ImageData_Impl* image_resource =
505      static_cast<PPB_ImageData_Impl*>(enter.object());
506
507  if (!PPB_ImageData_Impl::IsImageDataFormatSupported(
508          image_resource->format()))
509    return PP_ERROR_BADARGUMENT;
510
511  if (image_resource->width() != image_data_->width() ||
512      image_resource->height() != image_data_->height())
513    return PP_ERROR_BADARGUMENT;
514
515  QueuedOperation operation(QueuedOperation::REPLACE);
516  operation.replace_image = image_resource;
517  queued_operations_.push_back(operation);
518  return PP_OK;
519}
520
521int32_t PepperGraphics2DHost::OnHostMsgFlush(
522    ppapi::host::HostMessageContext* context) {
523  // Don't allow more than one pending flush at a time.
524  if (HasPendingFlush())
525    return PP_ERROR_INPROGRESS;
526
527  PP_Resource old_image_data = 0;
528  flush_reply_context_ = context->MakeReplyMessageContext();
529  if (is_running_in_process_)
530    return Flush(NULL);
531
532  // Reuse image data when running out of process.
533  int32_t result = Flush(&old_image_data);
534
535  if (old_image_data) {
536    // If the Graphics2D has an old image data it's not using any more, send
537    // it back to the plugin for possible re-use. See ppb_image_data_proxy.cc
538    // for a description how this process works.
539    ppapi::HostResource old_image_data_host_resource;
540    old_image_data_host_resource.SetHostResource(pp_instance(),
541                                                 old_image_data);
542    host()->Send(new PpapiMsg_PPBImageData_NotifyUnusedImageData(
543        ppapi::API_ID_PPB_IMAGE_DATA, old_image_data_host_resource));
544  }
545
546  return result;
547}
548
549int32_t PepperGraphics2DHost::OnHostMsgSetScale(
550    ppapi::host::HostMessageContext* context,
551    float scale) {
552  if (scale > 0.0f) {
553    scale_ = scale;
554    return PP_OK;
555  }
556  return PP_ERROR_BADARGUMENT;
557}
558
559int32_t PepperGraphics2DHost::OnHostMsgReadImageData(
560    ppapi::host::HostMessageContext* context,
561    PP_Resource image,
562    const PP_Point& top_left) {
563  context->reply_msg = PpapiPluginMsg_Graphics2D_ReadImageDataAck();
564  return ReadImageData(image, &top_left) ? PP_OK : PP_ERROR_FAILED;
565}
566
567void ReleaseCallback(scoped_ptr<base::SharedMemory> memory,
568                     uint32 sync_point,
569                     bool lost_resource) {}
570
571bool PepperGraphics2DHost::PrepareTextureMailbox(
572    cc::TextureMailbox* mailbox,
573    scoped_ptr<cc::SingleReleaseCallback>* release_callback) {
574  if (!texture_mailbox_modified_)
575    return false;
576  // TODO(jbauman): Send image_data_ through mailbox to avoid copy.
577  gfx::Size pixel_image_size(image_data_->width(), image_data_->height());
578  int buffer_size = pixel_image_size.GetArea() * 4;
579  scoped_ptr<base::SharedMemory> memory =
580      RenderThread::Get()->HostAllocateSharedMemoryBuffer(buffer_size);
581  if (!memory || !memory->Map(buffer_size))
582    return false;
583  void* src = image_data_->Map();
584  memcpy(memory->memory(), src, buffer_size);
585  image_data_->Unmap();
586
587  *mailbox = cc::TextureMailbox(memory.get(), pixel_image_size);
588  *release_callback = cc::SingleReleaseCallback::Create(
589      base::Bind(&ReleaseCallback, base::Passed(&memory)));
590  texture_mailbox_modified_ = false;
591  return true;
592}
593
594void PepperGraphics2DHost::AttachedToNewLayer() {
595  texture_mailbox_modified_ = true;
596}
597
598int32_t PepperGraphics2DHost::Flush(PP_Resource* old_image_data) {
599  bool done_replace_contents = false;
600  bool no_update_visible = true;
601  bool is_plugin_visible = true;
602  for (size_t i = 0; i < queued_operations_.size(); i++) {
603    QueuedOperation& operation = queued_operations_[i];
604    gfx::Rect op_rect;
605    switch (operation.type) {
606      case QueuedOperation::PAINT:
607        ExecutePaintImageData(operation.paint_image.get(),
608                              operation.paint_x,
609                              operation.paint_y,
610                              operation.paint_src_rect,
611                              &op_rect);
612        break;
613      case QueuedOperation::SCROLL:
614        ExecuteScroll(operation.scroll_clip_rect,
615                      operation.scroll_dx, operation.scroll_dy,
616                      &op_rect);
617        break;
618      case QueuedOperation::REPLACE:
619        // Since the out parameter |old_image_data| takes ownership of the
620        // reference, if there are more than one ReplaceContents calls queued
621        // the first |old_image_data| will get overwritten and leaked. So we
622        // only supply this for the first call.
623        ExecuteReplaceContents(operation.replace_image.get(),
624                               &op_rect,
625                               done_replace_contents ? NULL : old_image_data);
626        done_replace_contents = true;
627        break;
628    }
629
630    // For correctness with accelerated compositing, we must issue an invalidate
631    // on the full op_rect even if it is partially or completely off-screen.
632    // However, if we issue an invalidate for a clipped-out region, WebKit will
633    // do nothing and we won't get any ViewFlushedPaint calls, leaving our
634    // callback stranded. So we still need to check whether the repainted area
635    // is visible to determine how to deal with the callback.
636    if (bound_instance_ && !op_rect.IsEmpty()) {
637      gfx::Point scroll_delta(operation.scroll_dx, operation.scroll_dy);
638      if (!ConvertToLogicalPixels(scale_,
639                                  &op_rect,
640                                  operation.type == QueuedOperation::SCROLL ?
641                                      &scroll_delta : NULL)) {
642        // Conversion requires falling back to InvalidateRect.
643        operation.type = QueuedOperation::PAINT;
644      }
645
646      gfx::Rect clip = PP_ToGfxRect(bound_instance_->view_data().clip_rect);
647      is_plugin_visible = !clip.IsEmpty();
648
649      // Set |no_update_visible| to false if the change overlaps the visible
650      // area.
651      if (!gfx::IntersectRects(clip, op_rect).IsEmpty()) {
652        no_update_visible = false;
653      }
654
655      // Notify the plugin of the entire change (op_rect), even if it is
656      // partially or completely off-screen.
657      if (operation.type == QueuedOperation::SCROLL) {
658        bound_instance_->ScrollRect(scroll_delta.x(), scroll_delta.y(),
659                                    op_rect);
660      } else {
661        if (!op_rect.IsEmpty())
662          bound_instance_->InvalidateRect(op_rect);
663      }
664      texture_mailbox_modified_ = true;
665    }
666  }
667  queued_operations_.clear();
668
669  if (!bound_instance_) {
670    // As promised in the API, we always schedule callback when unbound.
671    ScheduleOffscreenFlushAck();
672  } else if (no_update_visible && is_plugin_visible &&
673             bound_instance_->view_data().is_page_visible) {
674    // There's nothing visible to invalidate so just schedule the callback to
675    // execute in the next round of the message loop.
676    ScheduleOffscreenFlushAck();
677  } else {
678    need_flush_ack_ = true;
679  }
680
681  return PP_OK_COMPLETIONPENDING;
682}
683
684void PepperGraphics2DHost::ExecutePaintImageData(PPB_ImageData_Impl* image,
685                                                int x, int y,
686                                                const gfx::Rect& src_rect,
687                                                gfx::Rect* invalidated_rect) {
688  // Ensure the source image is mapped to read from it.
689  ImageDataAutoMapper auto_mapper(image);
690  if (!auto_mapper.is_valid())
691    return;
692
693  // Portion within the source image to cut out.
694  SkIRect src_irect = { src_rect.x(), src_rect.y(),
695                        src_rect.right(), src_rect.bottom() };
696
697  // Location within the backing store to copy to.
698  *invalidated_rect = src_rect;
699  invalidated_rect->Offset(x, y);
700  SkRect dest_rect = { SkIntToScalar(invalidated_rect->x()),
701                       SkIntToScalar(invalidated_rect->y()),
702                       SkIntToScalar(invalidated_rect->right()),
703                       SkIntToScalar(invalidated_rect->bottom()) };
704
705  if (image->format() != image_data_->format()) {
706    // Convert the image data if the format does not match.
707    ConvertImageData(image, src_irect, image_data_.get(), dest_rect);
708  } else {
709    // We're guaranteed to have a mapped canvas since we mapped it in Init().
710    SkCanvas* backing_canvas = image_data_->GetCanvas();
711
712    // We want to replace the contents of the bitmap rather than blend.
713    SkPaint paint;
714    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
715    backing_canvas->drawBitmapRect(*image->GetMappedBitmap(),
716                                   &src_irect, dest_rect, &paint);
717  }
718}
719
720void PepperGraphics2DHost::ExecuteScroll(const gfx::Rect& clip,
721                                        int dx, int dy,
722                                        gfx::Rect* invalidated_rect) {
723  gfx::ScrollCanvas(image_data_->GetCanvas(),
724                    clip, gfx::Vector2d(dx, dy));
725  *invalidated_rect = clip;
726}
727
728void PepperGraphics2DHost::ExecuteReplaceContents(PPB_ImageData_Impl* image,
729                                                 gfx::Rect* invalidated_rect,
730                                                 PP_Resource* old_image_data) {
731  if (image->format() != image_data_->format()) {
732    DCHECK(image->width() == image_data_->width() &&
733           image->height() == image_data_->height());
734    // Convert the image data if the format does not match.
735    SkIRect src_irect = { 0, 0, image->width(), image->height() };
736    SkRect dest_rect = { SkIntToScalar(0),
737                         SkIntToScalar(0),
738                         SkIntToScalar(image_data_->width()),
739                         SkIntToScalar(image_data_->height()) };
740    ConvertImageData(image, src_irect, image_data_.get(), dest_rect);
741  } else {
742    // The passed-in image may not be mapped in our process, and we need to
743    // guarantee that the current backing store is always mapped.
744    if (!image->Map())
745      return;
746
747    if (old_image_data)
748      *old_image_data = image_data_->GetReference();
749    image_data_ = image;
750  }
751  *invalidated_rect = gfx::Rect(0, 0,
752                                image_data_->width(), image_data_->height());
753}
754
755void PepperGraphics2DHost::SendFlushAck() {
756  host()->SendReply(flush_reply_context_,
757                    PpapiPluginMsg_Graphics2D_FlushAck());
758}
759
760void PepperGraphics2DHost::SendOffscreenFlushAck() {
761  DCHECK(offscreen_flush_pending_);
762
763  // We must clear this flag before issuing the callback. It will be
764  // common for the plugin to issue another invalidate in response to a flush
765  // callback, and we don't want to think that a callback is already pending.
766  offscreen_flush_pending_ = false;
767  SendFlushAck();
768}
769
770void PepperGraphics2DHost::ScheduleOffscreenFlushAck() {
771  offscreen_flush_pending_ = true;
772  base::MessageLoop::current()->PostDelayedTask(
773      FROM_HERE,
774      base::Bind(&PepperGraphics2DHost::SendOffscreenFlushAck,
775                 AsWeakPtr()),
776      base::TimeDelta::FromMilliseconds(kOffscreenCallbackDelayMs));
777}
778
779bool PepperGraphics2DHost::HasPendingFlush() const {
780  return need_flush_ack_ || offscreen_flush_pending_;
781}
782
783// static
784bool PepperGraphics2DHost::ConvertToLogicalPixels(float scale,
785                                                 gfx::Rect* op_rect,
786                                                 gfx::Point* delta) {
787  if (scale == 1.0f || scale <= 0.0f)
788    return true;
789
790  gfx::Rect original_rect = *op_rect;
791  // Take the enclosing rectangle after scaling so a rectangle scaled down then
792  // scaled back up by the inverse scale would fully contain the entire area
793  // affected by the original rectangle.
794  *op_rect = gfx::ToEnclosingRect(gfx::ScaleRect(*op_rect, scale));
795  if (delta) {
796    gfx::Point original_delta = *delta;
797    float inverse_scale = 1.0f / scale;
798    *delta = gfx::ToFlooredPoint(gfx::ScalePoint(*delta, scale));
799
800    gfx::Rect inverse_scaled_rect =
801        gfx::ToEnclosingRect(gfx::ScaleRect(*op_rect, inverse_scale));
802    if (original_rect != inverse_scaled_rect)
803      return false;
804    gfx::Point inverse_scaled_point =
805        gfx::ToFlooredPoint(gfx::ScalePoint(*delta, inverse_scale));
806    if (original_delta != inverse_scaled_point)
807      return false;
808  }
809
810  return true;
811}
812
813}  // namespace content
814