test_graphics_2d.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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 "ppapi/tests/test_graphics_2d.h"
6
7#include <stdlib.h>
8#include <string.h>
9
10#include <set>
11
12#include "ppapi/c/dev/ppb_testing_dev.h"
13#include "ppapi/c/pp_errors.h"
14#include "ppapi/c/ppb_graphics_2d.h"
15#include "ppapi/cpp/completion_callback.h"
16#include "ppapi/cpp/dev/graphics_2d_dev.h"
17#include "ppapi/cpp/dev/graphics_2d_dev.h"
18#include "ppapi/cpp/graphics_2d.h"
19#include "ppapi/cpp/graphics_3d.h"
20#include "ppapi/cpp/image_data.h"
21#include "ppapi/cpp/instance.h"
22#include "ppapi/cpp/module.h"
23#include "ppapi/cpp/rect.h"
24#include "ppapi/tests/test_utils.h"
25#include "ppapi/tests/testing_instance.h"
26
27REGISTER_TEST_CASE(Graphics2D);
28
29namespace {
30
31// A NOP flush callback for use in various tests.
32void FlushCallbackNOP(void* data, int32_t result) {
33}
34
35void FlushCallbackQuitMessageLoop(void* data, int32_t result) {
36  static_cast<TestGraphics2D*>(data)->QuitMessageLoop();
37}
38
39bool CanFlushContext(pp::Instance* instance, pp::Graphics2D* context) {
40  TestCompletionCallback callback(instance->pp_instance());
41  callback.WaitForResult(context->Flush(callback.GetCallback()));
42  return (callback.result() == PP_OK);
43}
44
45bool CanFlushContextC(pp::Instance* instance, PP_Resource graphics_2d,
46                      const PPB_Graphics2D_1_1* graphics_2d_if) {
47  TestCompletionCallback callback(instance->pp_instance());
48  callback.WaitForResult(graphics_2d_if->Flush(
49      graphics_2d, callback.GetCallback().pp_completion_callback()));
50  return (callback.result() == PP_OK);
51}
52
53}  // namespace
54
55TestGraphics2D::TestGraphics2D(TestingInstance* instance)
56  : TestCase(instance),
57    is_view_changed_(false),
58    post_quit_on_view_changed_(false) {
59}
60
61bool TestGraphics2D::Init() {
62  graphics_2d_interface_ = static_cast<const PPB_Graphics2D*>(
63      pp::Module::Get()->GetBrowserInterface(PPB_GRAPHICS_2D_INTERFACE_1_1));
64  image_data_interface_ = static_cast<const PPB_ImageData*>(
65      pp::Module::Get()->GetBrowserInterface(PPB_IMAGEDATA_INTERFACE_1_0));
66  return graphics_2d_interface_ && image_data_interface_ &&
67         CheckTestingInterface();
68}
69
70void TestGraphics2D::RunTests(const std::string& filter) {
71  RUN_TEST(InvalidResource, filter);
72  RUN_TEST(InvalidSize, filter);
73  RUN_TEST(Humongous, filter);
74  RUN_TEST(InitToZero, filter);
75  RUN_TEST(Describe, filter);
76  RUN_TEST(Scale, filter);
77  RUN_TEST_FORCEASYNC_AND_NOT(Paint, filter);
78  RUN_TEST_FORCEASYNC_AND_NOT(Scroll, filter);
79  RUN_TEST_FORCEASYNC_AND_NOT(Replace, filter);
80  RUN_TEST_FORCEASYNC_AND_NOT(Flush, filter);
81  RUN_TEST_FORCEASYNC_AND_NOT(FlushOffscreenUpdate, filter);
82  RUN_TEST(Dev, filter);
83  RUN_TEST(ReplaceContentsCaching, filter);
84  RUN_TEST(BindNull, filter);
85}
86
87void TestGraphics2D::QuitMessageLoop() {
88  testing_interface_->QuitMessageLoop(instance_->pp_instance());
89}
90
91bool TestGraphics2D::ReadImageData(const pp::Graphics2D& dc,
92                                   pp::ImageData* image,
93                                   const pp::Point& top_left) const {
94  return PP_ToBool(testing_interface_->ReadImageData(
95      dc.pp_resource(),
96      image->pp_resource(),
97      &top_left.pp_point()));
98}
99
100bool TestGraphics2D::IsDCUniformColor(const pp::Graphics2D& dc,
101                                      uint32_t color) const {
102  pp::ImageData readback(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
103                         dc.size(), false);
104  if (readback.is_null())
105    return false;
106  if (!ReadImageData(dc, &readback, pp::Point(0, 0)))
107    return false;
108  return IsSquareInImage(readback, 0, pp::Rect(dc.size()), color);
109}
110
111std::string TestGraphics2D::FlushAndWaitForDone(pp::Graphics2D* context) {
112  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
113  callback.WaitForResult(context->Flush(callback.GetCallback()));
114  CHECK_CALLBACK_BEHAVIOR(callback);
115  ASSERT_EQ(PP_OK, callback.result());
116  PASS();
117}
118
119void TestGraphics2D::FillRectInImage(pp::ImageData* image,
120                                     const pp::Rect& rect,
121                                     uint32_t color) const {
122  for (int y = rect.y(); y < rect.bottom(); y++) {
123    uint32_t* row = image->GetAddr32(pp::Point(rect.x(), y));
124    for (int pixel = 0; pixel < rect.width(); pixel++)
125      row[pixel] = color;
126  }
127}
128
129void TestGraphics2D::FillImageWithGradient(pp::ImageData* image) const {
130  for (int y = 0; y < image->size().height(); y++) {
131    uint32_t red = ((y * 256) / image->size().height()) & 0xFF;
132    for (int x = 0; x < image->size().width(); x++) {
133      uint32_t green = ((x * 256) / image->size().width()) & 0xFF;
134      uint32_t blue = ((red + green) / 2) & 0xFF;
135      uint32_t* pixel = image->GetAddr32(pp::Point(x, y));
136      *pixel = (blue << 24) | (green << 16) | (red << 8);
137    }
138  }
139}
140
141bool TestGraphics2D::CompareImages(const pp::ImageData& image1,
142                                   const pp::ImageData& image2) {
143  return CompareImageRect(
144      image1, pp::Rect(0, 0, image1.size().width(), image1.size().height()),
145      image2, pp::Rect(0, 0, image2.size().width(), image2.size().height()));
146}
147
148bool TestGraphics2D::CompareImageRect(const pp::ImageData& image1,
149                                      const pp::Rect& rc1,
150                                      const pp::ImageData& image2,
151                                      const pp::Rect& rc2) const {
152  if (rc1.width() != rc2.width() || rc1.height() != rc2.height())
153    return false;
154
155  for (int y = 0; y < rc1.height(); y++) {
156    for (int x = 0; x < rc1.width(); x++) {
157      if (*(image1.GetAddr32(pp::Point(rc1.x() + x, rc1.y() + y))) !=
158          *(image2.GetAddr32(pp::Point(rc2.x() + x, rc2.y() + y))))
159        return false;
160    }
161  }
162  return true;
163}
164
165bool TestGraphics2D::IsSquareInImage(const pp::ImageData& image_data,
166                                     uint32_t background_color,
167                                     const pp::Rect& square,
168                                     uint32_t square_color) const {
169  for (int y = 0; y < image_data.size().height(); y++) {
170    for (int x = 0; x < image_data.size().width(); x++) {
171      uint32_t pixel = *image_data.GetAddr32(pp::Point(x, y));
172      uint32_t desired_color;
173      if (square.Contains(x, y))
174        desired_color = square_color;
175      else
176        desired_color = background_color;
177      if (pixel != desired_color)
178        return false;
179    }
180  }
181  return true;
182}
183
184bool TestGraphics2D::IsSquareInDC(const pp::Graphics2D& dc,
185                                  uint32_t background_color,
186                                  const pp::Rect& square,
187                                  uint32_t square_color) const {
188  pp::ImageData readback(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
189                         dc.size(), false);
190  if (readback.is_null())
191    return false;
192  if (!ReadImageData(dc, &readback, pp::Point(0, 0)))
193    return false;
194  return IsSquareInImage(readback, background_color, square, square_color);
195}
196
197
198PP_Resource TestGraphics2D::ReplaceContentsAndReturnID(
199    pp::Graphics2D* dc,
200    const pp::Size& size) {
201  pp::ImageData image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, true);
202
203  PP_Resource id = image.pp_resource();
204
205  dc->ReplaceContents(&image);
206  std::string result = FlushAndWaitForDone(dc);
207  if (!result.empty())
208    return 0;
209
210  return id;
211}
212
213// Test all the functions with an invalid handle. Most of these just check for
214// a crash since the browser don't return a value.
215std::string TestGraphics2D::TestInvalidResource() {
216  pp::Graphics2D null_context;
217  pp::ImageData image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
218                      pp::Size(16, 16), true);
219
220  // Describe.
221  PP_Size size;
222  PP_Bool opaque;
223  graphics_2d_interface_->Describe(image.pp_resource(), &size, &opaque);
224  graphics_2d_interface_->Describe(null_context.pp_resource(),
225                                   &size, &opaque);
226
227  // PaintImageData.
228  PP_Point zero_zero;
229  zero_zero.x = 0;
230  zero_zero.y = 0;
231  graphics_2d_interface_->PaintImageData(image.pp_resource(),
232                                         image.pp_resource(),
233                                         &zero_zero, NULL);
234  graphics_2d_interface_->PaintImageData(null_context.pp_resource(),
235                                         image.pp_resource(),
236                                         &zero_zero, NULL);
237
238  // Scroll.
239  PP_Point zero_ten;
240  zero_ten.x = 0;
241  zero_ten.y = 10;
242  graphics_2d_interface_->Scroll(image.pp_resource(), NULL, &zero_ten);
243  graphics_2d_interface_->Scroll(null_context.pp_resource(),
244                                 NULL, &zero_ten);
245
246  // ReplaceContents.
247  graphics_2d_interface_->ReplaceContents(image.pp_resource(),
248                                          image.pp_resource());
249  graphics_2d_interface_->ReplaceContents(null_context.pp_resource(),
250                                          image.pp_resource());
251
252  // Flush.
253  TestCompletionCallback cb(instance_->pp_instance(), PP_OPTIONAL);
254  cb.WaitForResult(
255      graphics_2d_interface_->Flush(image.pp_resource(),
256                                    cb.GetCallback().pp_completion_callback()));
257  ASSERT_EQ(PP_ERROR_BADRESOURCE, cb.result());
258  cb.WaitForResult(
259      graphics_2d_interface_->Flush(null_context.pp_resource(),
260                                    cb.GetCallback().pp_completion_callback()));
261  ASSERT_EQ(PP_ERROR_BADRESOURCE, cb.result());
262
263  // ReadImageData.
264  ASSERT_FALSE(testing_interface_->ReadImageData(image.pp_resource(),
265                                                 image.pp_resource(),
266                                                 &zero_zero));
267  ASSERT_FALSE(testing_interface_->ReadImageData(null_context.pp_resource(),
268                                                 image.pp_resource(),
269                                                 &zero_zero));
270
271  PASS();
272}
273
274std::string TestGraphics2D::TestInvalidSize() {
275  pp::Graphics2D a(instance_, pp::Size(16, 0), false);
276  ASSERT_FALSE(CanFlushContext(instance_, &a));
277
278  pp::Graphics2D b(instance_, pp::Size(0, 16), false);
279  ASSERT_FALSE(CanFlushContext(instance_, &b));
280
281  // Need to use the C API since pp::Size prevents negative sizes.
282  PP_Size size;
283  size.width = 16;
284  size.height = -16;
285  PP_Resource graphics = graphics_2d_interface_->Create(
286      instance_->pp_instance(), &size, PP_FALSE);
287  ASSERT_FALSE(CanFlushContextC(instance_, graphics, graphics_2d_interface_));
288  pp::Module::Get()->core()->ReleaseResource(graphics);
289
290  size.width = -16;
291  size.height = 16;
292  graphics = graphics_2d_interface_->Create(
293      instance_->pp_instance(), &size, PP_FALSE);
294  ASSERT_FALSE(CanFlushContextC(instance_, graphics, graphics_2d_interface_));
295  pp::Module::Get()->core()->ReleaseResource(graphics);
296
297  // Overflow to negative size
298  size.width = std::numeric_limits<int32_t>::max();
299  size.height = std::numeric_limits<int32_t>::max();
300  graphics = graphics_2d_interface_->Create(
301      instance_->pp_instance(), &size, PP_FALSE);
302  ASSERT_FALSE(CanFlushContextC(instance_, graphics, graphics_2d_interface_));
303  pp::Module::Get()->core()->ReleaseResource(graphics);
304
305  PASS();
306}
307
308std::string TestGraphics2D::TestHumongous() {
309  pp::Graphics2D a(instance_, pp::Size(100000, 100000), false);
310  ASSERT_FALSE(CanFlushContext(instance_, &a));
311  PASS();
312}
313
314std::string TestGraphics2D::TestInitToZero() {
315  const int w = 15, h = 17;
316  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
317  ASSERT_FALSE(dc.is_null());
318
319  // Make an image with nonzero data in it (so we can test that zeros were
320  // actually read versus ReadImageData being a NOP).
321  pp::ImageData image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
322                      pp::Size(w, h), true);
323  ASSERT_FALSE(image.is_null());
324  ASSERT_FALSE(image.size().IsEmpty());
325  memset(image.data(), 0xFF, image.stride() * image.size().height() * 4);
326
327  // Read out the initial data from the device & check.
328  ASSERT_TRUE(ReadImageData(dc, &image, pp::Point(0, 0)));
329  ASSERT_TRUE(IsSquareInImage(image, 0, pp::Rect(0, 0, w, h), 0));
330
331  PASS();
332}
333
334std::string TestGraphics2D::TestDescribe() {
335  const int w = 15, h = 17;
336  const bool always_opaque = (::rand() % 2 == 1);
337  pp::Graphics2D dc(instance_, pp::Size(w, h), always_opaque);
338  ASSERT_FALSE(dc.is_null());
339
340  PP_Size size;
341  size.width = -1;
342  size.height = -1;
343  PP_Bool is_always_opaque = PP_FALSE;
344  ASSERT_TRUE(graphics_2d_interface_->Describe(dc.pp_resource(), &size,
345                                               &is_always_opaque));
346  ASSERT_EQ(w, size.width);
347  ASSERT_EQ(h, size.height);
348  ASSERT_EQ(PP_FromBool(always_opaque), is_always_opaque);
349
350  PASS();
351}
352
353std::string TestGraphics2D::TestScale() {
354  // Tests GetScale/SetScale
355  const int w = 20, h = 16;
356  const float scale = 1.0f/2.0f;
357  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
358  ASSERT_FALSE(dc.is_null());
359  ASSERT_EQ(1.0,  dc.GetScale());
360  ASSERT_TRUE(dc.SetScale(scale));
361  ASSERT_EQ(scale, dc.GetScale());
362  // Try setting a few invalid scale factors. Ensure that we catch these errors
363  // and don't change the actual scale
364  ASSERT_FALSE(dc.SetScale(-1.0f));
365  ASSERT_FALSE(dc.SetScale(0.0f));
366  ASSERT_EQ(scale, dc.GetScale());
367
368  // Verify that the context has the specified number of pixels, despite the
369  // non-identity scale
370  PP_Size size;
371  size.width = -1;
372  size.height = -1;
373  PP_Bool is_always_opaque = PP_FALSE;
374  ASSERT_TRUE(graphics_2d_interface_->Describe(dc.pp_resource(), &size,
375                                               &is_always_opaque));
376  ASSERT_EQ(w, size.width);
377  ASSERT_EQ(h, size.height);
378  ASSERT_EQ(PP_FALSE, is_always_opaque);
379
380  PASS();
381}
382
383std::string TestGraphics2D::TestPaint() {
384  const int w = 15, h = 17;
385  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
386  ASSERT_FALSE(dc.is_null());
387
388  // Make sure the device background is 0.
389  ASSERT_TRUE(IsDCUniformColor(dc, 0));
390
391  // Fill the backing store with white.
392  const uint32_t background_color = 0xFFFFFFFF;
393  pp::ImageData background(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
394                           pp::Size(w, h), false);
395  FillRectInImage(&background, pp::Rect(0, 0, w, h), background_color);
396  dc.PaintImageData(background, pp::Point(0, 0));
397  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
398
399  // Make an image to paint with that's opaque white and enqueue a paint.
400  const int fill_w = 2, fill_h = 3;
401  pp::ImageData fill(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
402                     pp::Size(fill_w, fill_h), true);
403  ASSERT_FALSE(fill.is_null());
404  FillRectInImage(&fill, pp::Rect(fill.size()), background_color);
405  const int paint_x = 4, paint_y = 5;
406  dc.PaintImageData(fill, pp::Point(paint_x, paint_y));
407
408  // Validate that nothing has been actually painted.
409  ASSERT_TRUE(IsDCUniformColor(dc, background_color));
410
411  // The paint hasn't been flushed so we can still change the bitmap. Fill with
412  // 50% blue. This will also verify that the backing store is replaced
413  // with the contents rather than blended.
414  const uint32_t fill_color = 0x80000080;
415  FillRectInImage(&fill, pp::Rect(fill.size()), fill_color);
416  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
417
418  ASSERT_TRUE(IsSquareInDC(dc, background_color,
419                           pp::Rect(paint_x, paint_y, fill_w, fill_h),
420                           fill_color));
421
422  // Reset the DC to blank white & paint our image slightly off the buffer.
423  // This should succeed. We also try painting the same thing where the
424  // dirty rect falls outeside of the device, which should fail.
425  dc.PaintImageData(background, pp::Point(0, 0));
426  const int second_paint_x = -1, second_paint_y = -2;
427  dc.PaintImageData(fill, pp::Point(second_paint_x, second_paint_y));
428  dc.PaintImageData(fill, pp::Point(second_paint_x, second_paint_y),
429                    pp::Rect(-second_paint_x, -second_paint_y, 1, 1));
430  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
431
432  // Now we should have a little bit of the image peeking out the top left.
433  ASSERT_TRUE(IsSquareInDC(dc, background_color, pp::Rect(0, 0, 1, 1),
434                           fill_color));
435
436  // Now repaint that top left pixel by doing a subset of the source image.
437  pp::ImageData subset(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
438                       pp::Size(w, h), false);
439  uint32_t subset_color = 0x80808080;
440  const int subset_x = 2, subset_y = 1;
441  *subset.GetAddr32(pp::Point(subset_x, subset_y)) = subset_color;
442  dc.PaintImageData(subset, pp::Point(-subset_x, -subset_y),
443                    pp::Rect(subset_x, subset_y, 1, 1));
444  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
445  ASSERT_TRUE(IsSquareInDC(dc, background_color, pp::Rect(0, 0, 1, 1),
446                           subset_color));
447
448  PASS();
449}
450
451std::string TestGraphics2D::TestScroll() {
452  const int w = 115, h = 117;
453  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
454  ASSERT_FALSE(dc.is_null());
455  ASSERT_TRUE(instance_->BindGraphics(dc));
456
457  // Make sure the device background is 0.
458  ASSERT_TRUE(IsDCUniformColor(dc, 0));
459
460  const int image_width = 15, image_height = 23;
461  pp::ImageData test_image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
462                           pp::Size(image_width, image_height), false);
463  FillImageWithGradient(&test_image);
464  pp::ImageData no_image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
465                         pp::Size(image_width, image_height), false);
466  FillRectInImage(&no_image, pp::Rect(0, 0, image_width, image_height), 0);
467  pp::ImageData readback_image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
468                               pp::Size(image_width, image_height), false);
469  pp::ImageData readback_scroll(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
470                                pp::Size(image_width, image_height), false);
471
472  ASSERT_EQ(pp::Size(image_width, image_height), test_image.size());
473
474  int image_x = 51, image_y = 72;
475  dc.PaintImageData(test_image, pp::Point(image_x, image_y));
476  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
477
478  // Test Case 1. Incorrect usage when scrolling image to a free space.
479  // The clip area is *not* the area to shift around within the graphics device
480  // by specified amount. It's the area to which the scroll is limited. So if
481  // the clip area is the size of the image and the amount points to free space,
482  // the scroll won't result in additional images.
483  int dx = -40, dy = -48;
484  int scroll_x = image_x + dx, scroll_y = image_y + dy;
485  pp::Rect clip(image_x, image_y, image_width, image_height);
486  dc.Scroll(clip, pp::Point(dx, dy));
487  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
488  ASSERT_TRUE(
489      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
490  ASSERT_TRUE(CompareImages(no_image, readback_scroll));
491
492  // Test Case 2.
493  // The amount is intended to place the image in the free space outside
494  // of the original, but the clip area extends beyond the graphics device area.
495  // This scroll is invalid and will be a noop.
496  scroll_x = 11, scroll_y = 24;
497  clip = pp::Rect(0, 0, w, h + 1);
498  dc.Scroll(clip, pp::Point(scroll_x - image_x, scroll_y - image_y));
499  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
500  ASSERT_TRUE(
501      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
502  ASSERT_TRUE(CompareImages(no_image, readback_scroll));
503
504  // Test Case 3.
505  // The amount is intended to place the image in the free space outside
506  // of the original, but the clip area does not cover the image,
507  // so there is nothing to scroll.
508  scroll_x = 11, scroll_y = 24;
509  clip = pp::Rect(0, 0, image_x, image_y);
510  dc.Scroll(clip, pp::Point(scroll_x - image_x, scroll_y - image_y));
511  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
512  ASSERT_TRUE(
513      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
514  ASSERT_TRUE(CompareImages(no_image, readback_scroll));
515
516  // Test Case 4.
517  // Same as TC3, but the clip covers part of the image.
518  // This part will be scrolled to the intended origin.
519  int part_w = image_width / 2, part_h = image_height / 2;
520  clip = pp::Rect(0, 0, image_x + part_w, image_y + part_h);
521  dc.Scroll(clip, pp::Point(scroll_x - image_x, scroll_y - image_y));
522  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
523  ASSERT_TRUE(
524      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
525  ASSERT_FALSE(CompareImages(test_image, readback_scroll));
526  pp::Rect part_rect(part_w, part_h);
527  ASSERT_TRUE(
528      CompareImageRect(test_image, part_rect, readback_scroll, part_rect));
529
530  // Test Case 5
531  // Same as TC3, but the clip area covers the entire image.
532  // It will be scrolled to the intended origin.
533  clip = pp::Rect(0, 0, image_x + image_width, image_y + image_height);
534  dc.Scroll(clip, pp::Point(scroll_x - image_x, scroll_y - image_y));
535  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
536  ASSERT_TRUE(
537      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
538  ASSERT_TRUE(CompareImages(test_image, readback_scroll));
539
540  // Note that the undefined area left by the scroll does not actually get
541  // cleared, so the original image is still there. This is not guaranteed and
542  // is not something for users to rely on, but we can test for this here, so
543  // we know when the underlying behavior changes.
544  ASSERT_TRUE(ReadImageData(dc, &readback_image, pp::Point(image_x, image_y)));
545  ASSERT_TRUE(CompareImages(test_image, readback_image));
546
547  // Test Case 6.
548  // Scroll image to an overlapping space. The clip area is limited
549  // to the image, so this will just modify its area.
550  dx = 6;
551  dy = 9;
552  scroll_x = image_x + dx;
553  scroll_y = image_y + dy;
554  clip = pp::Rect(image_x, image_y, image_width, image_height);
555  dc.Scroll(clip, pp::Point(dx, dy));
556  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
557  ASSERT_TRUE(ReadImageData(dc, &readback_image, pp::Point(image_x, image_y)));
558  ASSERT_FALSE(CompareImages(test_image, readback_image));
559  pp::Rect scroll_rect(image_width - dx, image_height - dy);
560  ASSERT_TRUE(
561      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
562  ASSERT_TRUE(
563      CompareImageRect(test_image, scroll_rect, readback_scroll, scroll_rect));
564
565  PASS();
566}
567
568std::string TestGraphics2D::TestReplace() {
569  const int w = 15, h = 17;
570  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
571  ASSERT_FALSE(dc.is_null());
572
573  // Replacing with a different size image should fail.
574  pp::ImageData weird_size(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
575                           pp::Size(w - 1, h), true);
576  ASSERT_FALSE(weird_size.is_null());
577  dc.ReplaceContents(&weird_size);
578
579  // Fill the background with blue but don't flush yet.
580  const int32_t background_color = 0xFF0000FF;
581  pp::ImageData background(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
582                           pp::Size(w, h), true);
583  ASSERT_FALSE(background.is_null());
584  FillRectInImage(&background, pp::Rect(0, 0, w, h), background_color);
585  dc.PaintImageData(background, pp::Point(0, 0));
586
587  // Replace with a green background but don't flush yet.
588  const int32_t swapped_color = 0x00FF00FF;
589  pp::ImageData swapped(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
590                        pp::Size(w, h), true);
591  ASSERT_FALSE(swapped.is_null());
592  FillRectInImage(&swapped, pp::Rect(0, 0, w, h), swapped_color);
593  dc.ReplaceContents(&swapped);
594
595  // The background should be unchanged since we didn't flush yet.
596  ASSERT_TRUE(IsDCUniformColor(dc, 0));
597
598  // Test the C++ wrapper. The size of the swapped image should be reset.
599  ASSERT_TRUE(!swapped.pp_resource() && !swapped.size().width() &&
600              !swapped.size().height() && !swapped.data());
601
602  // Painting with the swapped image should fail.
603  dc.PaintImageData(swapped, pp::Point(0, 0));
604
605  // Flush and make sure the result is correct.
606  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
607
608  // The background should be green from the swapped image.
609  ASSERT_TRUE(IsDCUniformColor(dc, swapped_color));
610
611  PASS();
612}
613
614std::string TestGraphics2D::TestFlush() {
615  // Tests that synchronous flushes (NULL callback) fail on the main thread
616  // (which is the current one).
617  const int w = 15, h = 17;
618  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
619  ASSERT_FALSE(dc.is_null());
620
621  // Fill the background with blue but don't flush yet.
622  pp::ImageData background(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
623                           pp::Size(w, h), true);
624  ASSERT_FALSE(background.is_null());
625  dc.PaintImageData(background, pp::Point(0, 0));
626
627  int32_t rv = dc.Flush(pp::BlockUntilComplete());
628  ASSERT_EQ(PP_ERROR_BLOCKS_MAIN_THREAD, rv);
629
630  // Test flushing with no operations still issues a callback.
631  // (This may also hang if the browser never issues the callback).
632  pp::Graphics2D dc_nopaints(instance_, pp::Size(w, h), false);
633  ASSERT_FALSE(dc.is_null());
634  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc_nopaints));
635
636  TestCompletionCallback callback_1(instance_->pp_instance(), callback_type());
637
638  // Test that multiple flushes fail if we don't get a callback in between.
639  rv = dc_nopaints.Flush(callback_1.GetCallback());
640  if (rv == PP_OK_COMPLETIONPENDING) {
641    // If the first flush completes asynchronously, then a second should fail.
642    TestCompletionCallback callback_2(instance_->pp_instance(),
643                                      callback_type());
644    callback_2.WaitForResult(dc_nopaints.Flush(callback_2.GetCallback()));
645    CHECK_CALLBACK_BEHAVIOR(callback_2);
646    ASSERT_EQ(PP_ERROR_INPROGRESS, callback_2.result());
647  }
648  callback_1.WaitForResult(rv);
649  ASSERT_EQ(PP_OK, callback_1.result());
650
651  PASS();
652}
653
654void TestGraphics2D::DidChangeView(const pp::View& view) {
655  if (post_quit_on_view_changed_) {
656    post_quit_on_view_changed_ = false;
657    is_view_changed_ = true;
658    testing_interface_->QuitMessageLoop(instance_->pp_instance());
659  }
660}
661
662void TestGraphics2D::ResetViewChangedState() {
663  is_view_changed_ = false;
664}
665
666bool TestGraphics2D::WaitUntilViewChanged() {
667  // Run a nested message loop. It will exit either on ViewChanged or if the
668  // timeout happens.
669
670  // If view changed before we have chance to run message loop, return directly.
671  if (is_view_changed_)
672    return true;
673
674  post_quit_on_view_changed_ = true;
675  testing_interface_->RunMessageLoop(instance_->pp_instance());
676  post_quit_on_view_changed_ = false;
677
678  return is_view_changed_;
679}
680
681std::string TestGraphics2D::TestFlushOffscreenUpdate() {
682  // Tests that callback of offscreen updates should be delayed.
683  const PP_Time kFlushDelaySec = 1. / 30;  // 30 fps
684  const int w = 80, h = 80;
685  pp::Graphics2D dc(instance_, pp::Size(w, h), true);
686  ASSERT_FALSE(dc.is_null());
687  ASSERT_TRUE(instance_->BindGraphics(dc));
688
689  // Squeeze from top until bottom half of plugin is out of screen.
690  ResetViewChangedState();
691  instance_->EvalScript(
692      "var big = document.createElement('div');"
693      "var offset = "
694      "    window.innerHeight - plugin.offsetTop - plugin.offsetHeight / 2;"
695      "big.setAttribute('id', 'big-div');"
696      "big.setAttribute('style', 'height: ' + offset + '; width: 100%;');"
697      "document.body.insertBefore(big, document.body.firstChild);");
698  ASSERT_TRUE(WaitUntilViewChanged());
699
700  // Allocate a red image chunk
701  pp::ImageData chunk(instance_, PP_IMAGEDATAFORMAT_RGBA_PREMUL,
702                      pp::Size(w/8, h/8), true);
703  ASSERT_FALSE(chunk.is_null());
704  const uint32_t kRed = 0xff0000ff;
705  FillRectInImage(&chunk, pp::Rect(chunk.size()), kRed);
706
707  // Paint a invisable chunk, expecting Flush to invoke callback slowly.
708  dc.PaintImageData(chunk, pp::Point(0, h*0.75));
709
710  PP_Time begin = pp::Module::Get()->core()->GetTime();
711  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
712  PP_Time actual_time_elapsed = pp::Module::Get()->core()->GetTime() - begin;
713  // Expect actual_time_elapsed >= kFlushDelaySec, but loose a bit to avoid
714  // precision issue.
715  ASSERT_GE(actual_time_elapsed, kFlushDelaySec * 0.9);
716
717  // Remove the padding on the top since test cases here isn't independent.
718  instance_->EvalScript(
719      "var big = document.getElementById('big-div');"
720      "big.parentNode.removeChild(big);");
721  ResetViewChangedState();
722  ASSERT_TRUE(WaitUntilViewChanged());
723
724  PASS();
725}
726
727std::string TestGraphics2D::TestDev() {
728  // Tests GetScale/SetScale via the Graphics2D_Dev C++ wrapper
729  const int w = 20, h = 16;
730  const float scale = 1.0f/2.0f;
731  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
732  ASSERT_FALSE(dc.is_null());
733  pp::Graphics2D_Dev dc_dev(dc);
734  ASSERT_EQ(1.0f, dc_dev.GetScale());
735  ASSERT_TRUE(dc_dev.SetScale(scale));
736  ASSERT_EQ(scale, dc_dev.GetScale());
737  // Try setting a few invalid scale factors. Ensure that we catch these errors
738  // and don't change the actual scale
739  ASSERT_FALSE(dc_dev.SetScale(-1.0f));
740  ASSERT_FALSE(dc_dev.SetScale(0.0f));
741  ASSERT_EQ(scale, dc_dev.GetScale());
742
743  // Verify that the context has the specified number of pixels, despite the
744  // non-identity scale
745  PP_Size size;
746  size.width = -1;
747  size.height = -1;
748  PP_Bool is_always_opaque = PP_FALSE;
749  ASSERT_TRUE(graphics_2d_interface_->Describe(dc_dev.pp_resource(), &size,
750                                               &is_always_opaque));
751  ASSERT_EQ(w, size.width);
752  ASSERT_EQ(h, size.height);
753  ASSERT_EQ(PP_FALSE, is_always_opaque);
754
755  PASS();
756}
757
758// This test makes sure that the out-of-process image data caching works as
759// expected. Doing ReplaceContents quickly should re-use the image data from
760// older ones.
761std::string TestGraphics2D::TestReplaceContentsCaching() {
762  // The cache is only active when running in the proxy, so skip it otherwise.
763  if (!testing_interface_->IsOutOfProcess())
764    PASS();
765
766  // Here we test resource IDs as a way to determine if the resource is being
767  // cached and re-used. This is non-optimal since it's entirely possible
768  // (and maybe better) for the proxy to return new resource IDs for the
769  // re-used objects. Howevever, our current implementation does this so it is
770  // an easy thing to check for.
771  //
772  // You could check for the shared memory pointers getting re-used, but the
773  // OS is very likely to use the same memory location for a newly-mapped image
774  // if one was deleted, meaning that it could pass even if the cache is broken.
775  // This would then require that we add some address-re-use-preventing code
776  // which would be tricky.
777  std::set<PP_Resource> resources;
778
779  pp::Size size(16, 16);
780  pp::Graphics2D dc(instance_, size, false);
781
782  // Do two replace contentses, adding the old resource IDs to our map.
783  PP_Resource imageres = ReplaceContentsAndReturnID(&dc, size);
784  ASSERT_TRUE(imageres);
785  resources.insert(imageres);
786  imageres = ReplaceContentsAndReturnID(&dc, size);
787  ASSERT_TRUE(imageres);
788  resources.insert(imageres);
789
790  // Now doing more replace contents should re-use older IDs if the cache is
791  // working.
792  imageres = ReplaceContentsAndReturnID(&dc, size);
793  ASSERT_TRUE(resources.find(imageres) != resources.end());
794  imageres = ReplaceContentsAndReturnID(&dc, size);
795  ASSERT_TRUE(resources.find(imageres) != resources.end());
796
797  PASS();
798}
799
800std::string TestGraphics2D::TestBindNull() {
801  // Binding a null resource is not an error, it should clear all bound
802  // resources. We can't easily test what resource is bound, but we can test
803  // that this doesn't throw an error.
804  ASSERT_TRUE(instance_->BindGraphics(pp::Graphics2D()));
805  ASSERT_TRUE(instance_->BindGraphics(pp::Graphics3D()));
806
807  const int w = 115, h = 117;
808  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
809  ASSERT_FALSE(dc.is_null());
810  ASSERT_TRUE(instance_->BindGraphics(dc));
811
812  ASSERT_TRUE(instance_->BindGraphics(pp::Graphics2D()));
813  ASSERT_TRUE(instance_->BindGraphics(pp::Graphics3D()));
814
815  PASS();
816}
817
818