1// Copyright 2013 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 "cc/test/layer_tree_pixel_test.h"
6
7#include "base/path_service.h"
8#include "cc/layers/solid_color_layer.h"
9#include "cc/layers/texture_layer.h"
10#include "cc/output/copy_output_request.h"
11#include "cc/output/copy_output_result.h"
12#include "cc/resources/texture_mailbox.h"
13#include "cc/test/paths.h"
14#include "cc/test/pixel_comparator.h"
15#include "cc/test/pixel_test_output_surface.h"
16#include "cc/test/pixel_test_software_output_device.h"
17#include "cc/test/pixel_test_utils.h"
18#include "cc/trees/layer_tree_impl.h"
19#include "ui/gl/gl_implementation.h"
20#include "webkit/common/gpu/context_provider_in_process.h"
21#include "webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.h"
22
23namespace cc {
24
25LayerTreePixelTest::LayerTreePixelTest()
26    : pixel_comparator_(new ExactPixelComparator(true)),
27      test_type_(GL_WITH_DEFAULT),
28      pending_texture_mailbox_callbacks_(0) {}
29
30LayerTreePixelTest::~LayerTreePixelTest() {}
31
32scoped_ptr<OutputSurface> LayerTreePixelTest::CreateOutputSurface(
33    bool fallback) {
34  gfx::Vector2d viewport_offset(20, 10);
35  gfx::Size surface_expansion_size(40, 60);
36  scoped_ptr<PixelTestOutputSurface> output_surface;
37
38  switch (test_type_) {
39    case SOFTWARE_WITH_DEFAULT:
40    case SOFTWARE_WITH_BITMAP: {
41      scoped_ptr<PixelTestSoftwareOutputDevice> software_output_device(
42          new PixelTestSoftwareOutputDevice);
43      software_output_device->set_surface_expansion_size(
44          surface_expansion_size);
45      output_surface = make_scoped_ptr(
46          new PixelTestOutputSurface(
47              software_output_device.PassAs<SoftwareOutputDevice>()));
48      break;
49    }
50
51    case GL_WITH_DEFAULT:
52    case GL_WITH_BITMAP: {
53      CHECK(gfx::InitializeGLBindings(gfx::kGLImplementationOSMesaGL));
54
55      using WebKit::WebGraphicsContext3D;
56      using webkit::gpu::WebGraphicsContext3DInProcessCommandBufferImpl;
57      scoped_ptr<WebGraphicsContext3D> context(
58          WebGraphicsContext3DInProcessCommandBufferImpl::
59              CreateOffscreenContext(WebGraphicsContext3D::Attributes()));
60      output_surface.reset(new PixelTestOutputSurface(context.Pass()));
61      break;
62    }
63  }
64
65  output_surface->set_viewport_offset(viewport_offset);
66  output_surface->set_surface_expansion_size(surface_expansion_size);
67  return output_surface.PassAs<OutputSurface>();
68}
69
70scoped_refptr<cc::ContextProvider>
71LayerTreePixelTest::OffscreenContextProviderForMainThread() {
72  scoped_refptr<webkit::gpu::ContextProviderInProcess> provider =
73      webkit::gpu::ContextProviderInProcess::CreateOffscreen();
74  CHECK(provider->BindToCurrentThread());
75  return provider;
76}
77
78scoped_refptr<cc::ContextProvider>
79LayerTreePixelTest::OffscreenContextProviderForCompositorThread() {
80  scoped_refptr<webkit::gpu::ContextProviderInProcess> provider =
81      webkit::gpu::ContextProviderInProcess::CreateOffscreen();
82  CHECK(provider.get());
83  return provider;
84}
85
86scoped_ptr<CopyOutputRequest> LayerTreePixelTest::CreateCopyOutputRequest() {
87  return CopyOutputRequest::CreateBitmapRequest(
88      base::Bind(&LayerTreePixelTest::ReadbackResult, base::Unretained(this)));
89}
90
91void LayerTreePixelTest::ReadbackResult(scoped_ptr<CopyOutputResult> result) {
92  ASSERT_TRUE(result->HasBitmap());
93  result_bitmap_ = result->TakeBitmap().Pass();
94  EndTest();
95}
96
97void LayerTreePixelTest::BeginTest() {
98  Layer* target = readback_target_ ? readback_target_
99                                   : layer_tree_host()->root_layer();
100  target->RequestCopyOfOutput(CreateCopyOutputRequest().Pass());
101  PostSetNeedsCommitToMainThread();
102}
103
104void LayerTreePixelTest::AfterTest() {
105  base::FilePath test_data_dir;
106  EXPECT_TRUE(PathService::Get(cc::DIR_TEST_DATA, &test_data_dir));
107  base::FilePath ref_file_path = test_data_dir.Append(ref_file_);
108
109  // To rebaseline:
110  // EXPECT_TRUE(WritePNGFile(*result_bitmap_, ref_file_path, true));
111
112  EXPECT_TRUE(MatchesPNGFile(*result_bitmap_,
113                             ref_file_path,
114                             *pixel_comparator_));
115}
116
117scoped_refptr<SolidColorLayer> LayerTreePixelTest::CreateSolidColorLayer(
118    gfx::Rect rect, SkColor color) {
119  scoped_refptr<SolidColorLayer> layer = SolidColorLayer::Create();
120  layer->SetIsDrawable(true);
121  layer->SetAnchorPoint(gfx::PointF());
122  layer->SetBounds(rect.size());
123  layer->SetPosition(rect.origin());
124  layer->SetBackgroundColor(color);
125  return layer;
126}
127
128void LayerTreePixelTest::EndTest() {
129  // Drop TextureMailboxes on the main thread so that they can be cleaned up and
130  // the pending callbacks will fire.
131  for (size_t i = 0; i < texture_layers_.size(); ++i)
132    texture_layers_[i]->SetTextureMailbox(TextureMailbox());
133
134  TryEndTest();
135}
136
137void LayerTreePixelTest::TryEndTest() {
138  if (!result_bitmap_)
139    return;
140  if (pending_texture_mailbox_callbacks_)
141    return;
142  LayerTreeTest::EndTest();
143}
144
145scoped_refptr<SolidColorLayer> LayerTreePixelTest::
146    CreateSolidColorLayerWithBorder(
147        gfx::Rect rect, SkColor color, int border_width, SkColor border_color) {
148  scoped_refptr<SolidColorLayer> layer = CreateSolidColorLayer(rect, color);
149  scoped_refptr<SolidColorLayer> border_top = CreateSolidColorLayer(
150      gfx::Rect(0, 0, rect.width(), border_width), border_color);
151  scoped_refptr<SolidColorLayer> border_left = CreateSolidColorLayer(
152      gfx::Rect(0,
153                border_width,
154                border_width,
155                rect.height() - border_width * 2),
156      border_color);
157  scoped_refptr<SolidColorLayer> border_right = CreateSolidColorLayer(
158      gfx::Rect(rect.width() - border_width,
159                border_width,
160                border_width,
161                rect.height() - border_width * 2),
162      border_color);
163  scoped_refptr<SolidColorLayer> border_bottom = CreateSolidColorLayer(
164      gfx::Rect(0, rect.height() - border_width, rect.width(), border_width),
165      border_color);
166  layer->AddChild(border_top);
167  layer->AddChild(border_left);
168  layer->AddChild(border_right);
169  layer->AddChild(border_bottom);
170  return layer;
171}
172
173scoped_refptr<TextureLayer> LayerTreePixelTest::CreateTextureLayer(
174    gfx::Rect rect, const SkBitmap& bitmap) {
175  scoped_refptr<TextureLayer> layer = TextureLayer::CreateForMailbox(NULL);
176  layer->SetIsDrawable(true);
177  layer->SetAnchorPoint(gfx::PointF());
178  layer->SetBounds(rect.size());
179  layer->SetPosition(rect.origin());
180  layer->SetTextureMailbox(CopyBitmapToTextureMailboxAsTexture(bitmap));
181
182  texture_layers_.push_back(layer);
183  pending_texture_mailbox_callbacks_++;
184  return layer;
185}
186
187void LayerTreePixelTest::RunPixelTest(
188    PixelTestType test_type,
189    scoped_refptr<Layer> content_root,
190    base::FilePath file_name) {
191  test_type_ = test_type;
192  content_root_ = content_root;
193  readback_target_ = NULL;
194  ref_file_ = file_name;
195  RunTest(true, false, true);
196}
197
198void LayerTreePixelTest::RunPixelTestWithReadbackTarget(
199    PixelTestType test_type,
200    scoped_refptr<Layer> content_root,
201    Layer* target,
202    base::FilePath file_name) {
203  test_type_ = test_type;
204  content_root_ = content_root;
205  readback_target_ = target;
206  ref_file_ = file_name;
207  RunTest(true, false, true);
208}
209
210void LayerTreePixelTest::SetupTree() {
211  scoped_refptr<Layer> root = Layer::Create();
212  root->SetBounds(content_root_->bounds());
213  root->AddChild(content_root_);
214  layer_tree_host()->SetRootLayer(root);
215  LayerTreeTest::SetupTree();
216}
217
218scoped_ptr<SkBitmap> LayerTreePixelTest::CopyTextureMailboxToBitmap(
219    gfx::Size size,
220    const TextureMailbox& texture_mailbox) {
221  DCHECK(texture_mailbox.IsTexture());
222  if (!texture_mailbox.IsTexture())
223    return scoped_ptr<SkBitmap>();
224
225  using webkit::gpu::WebGraphicsContext3DInProcessCommandBufferImpl;
226  scoped_ptr<WebKit::WebGraphicsContext3D> context3d(
227      WebGraphicsContext3DInProcessCommandBufferImpl::CreateOffscreenContext(
228          WebKit::WebGraphicsContext3D::Attributes()));
229
230  EXPECT_TRUE(context3d->makeContextCurrent());
231
232  if (texture_mailbox.sync_point())
233    context3d->waitSyncPoint(texture_mailbox.sync_point());
234
235  unsigned texture_id = context3d->createTexture();
236  context3d->bindTexture(GL_TEXTURE_2D, texture_id);
237  context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
238  context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
239  context3d->texParameteri(
240      GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
241  context3d->texParameteri(
242      GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
243  context3d->consumeTextureCHROMIUM(texture_mailbox.target(),
244                                    texture_mailbox.data());
245  context3d->bindTexture(GL_TEXTURE_2D, 0);
246
247  unsigned fbo = context3d->createFramebuffer();
248  context3d->bindFramebuffer(GL_FRAMEBUFFER, fbo);
249  context3d->framebufferTexture2D(GL_FRAMEBUFFER,
250                                  GL_COLOR_ATTACHMENT0,
251                                  GL_TEXTURE_2D,
252                                  texture_id,
253                                  0);
254  EXPECT_EQ(static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE),
255            context3d->checkFramebufferStatus(GL_FRAMEBUFFER));
256
257  scoped_ptr<uint8[]> pixels(new uint8[size.GetArea() * 4]);
258  context3d->readPixels(0,
259                        0,
260                        size.width(),
261                        size.height(),
262                        GL_RGBA,
263                        GL_UNSIGNED_BYTE,
264                        pixels.get());
265
266  context3d->deleteFramebuffer(fbo);
267  context3d->deleteTexture(texture_id);
268
269  scoped_ptr<SkBitmap> bitmap(new SkBitmap);
270  bitmap->setConfig(SkBitmap::kARGB_8888_Config,
271                    size.width(),
272                    size.height());
273  bitmap->allocPixels();
274
275  scoped_ptr<SkAutoLockPixels> lock(new SkAutoLockPixels(*bitmap));
276  uint8* out_pixels = static_cast<uint8*>(bitmap->getPixels());
277
278  size_t row_bytes = size.width() * 4;
279  size_t total_bytes = size.height() * row_bytes;
280  for (size_t dest_y = 0; dest_y < total_bytes; dest_y += row_bytes) {
281    // Flip Y axis.
282    size_t src_y = total_bytes - dest_y - row_bytes;
283    // Swizzle OpenGL -> Skia byte order.
284    for (size_t x = 0; x < row_bytes; x += 4) {
285      out_pixels[dest_y + x + SK_R32_SHIFT/8] = pixels.get()[src_y + x + 0];
286      out_pixels[dest_y + x + SK_G32_SHIFT/8] = pixels.get()[src_y + x + 1];
287      out_pixels[dest_y + x + SK_B32_SHIFT/8] = pixels.get()[src_y + x + 2];
288      out_pixels[dest_y + x + SK_A32_SHIFT/8] = pixels.get()[src_y + x + 3];
289    }
290  }
291
292  return bitmap.Pass();
293}
294
295void LayerTreePixelTest::ReleaseTextureMailbox(
296    scoped_ptr<WebKit::WebGraphicsContext3D> context3d,
297    uint32 texture,
298    uint32 sync_point,
299    bool lost_resource) {
300  if (sync_point)
301    context3d->waitSyncPoint(sync_point);
302  context3d->deleteTexture(texture);
303  pending_texture_mailbox_callbacks_--;
304  TryEndTest();
305}
306
307TextureMailbox LayerTreePixelTest::CopyBitmapToTextureMailboxAsTexture(
308    const SkBitmap& bitmap) {
309  DCHECK_GT(bitmap.width(), 0);
310  DCHECK_GT(bitmap.height(), 0);
311
312  CHECK(gfx::InitializeGLBindings(gfx::kGLImplementationOSMesaGL));
313
314  using webkit::gpu::WebGraphicsContext3DInProcessCommandBufferImpl;
315  scoped_ptr<WebKit::WebGraphicsContext3D> context3d(
316      WebGraphicsContext3DInProcessCommandBufferImpl::CreateOffscreenContext(
317          WebKit::WebGraphicsContext3D::Attributes()));
318
319  EXPECT_TRUE(context3d->makeContextCurrent());
320
321  unsigned texture_id = context3d->createTexture();
322  context3d->bindTexture(GL_TEXTURE_2D, texture_id);
323  context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
324  context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
325  context3d->texParameteri(
326      GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
327  context3d->texParameteri(
328      GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
329
330  DCHECK_EQ(SkBitmap::kARGB_8888_Config, bitmap.getConfig());
331
332  {
333    SkAutoLockPixels lock(bitmap);
334
335    size_t row_bytes = bitmap.width() * 4;
336    size_t total_bytes = bitmap.height() * row_bytes;
337
338    scoped_ptr<uint8[]> gl_pixels(new uint8[total_bytes]);
339    uint8* bitmap_pixels = static_cast<uint8*>(bitmap.getPixels());
340
341    for (size_t y = 0; y < total_bytes; y += row_bytes) {
342      // Flip Y axis.
343      size_t src_y = total_bytes - y - row_bytes;
344      // Swizzle Skia -> OpenGL byte order.
345      for (size_t x = 0; x < row_bytes; x += 4) {
346        gl_pixels.get()[y + x + 0] = bitmap_pixels[src_y + x + SK_R32_SHIFT/8];
347        gl_pixels.get()[y + x + 1] = bitmap_pixels[src_y + x + SK_G32_SHIFT/8];
348        gl_pixels.get()[y + x + 2] = bitmap_pixels[src_y + x + SK_B32_SHIFT/8];
349        gl_pixels.get()[y + x + 3] = bitmap_pixels[src_y + x + SK_A32_SHIFT/8];
350      }
351    }
352
353    context3d->texImage2D(GL_TEXTURE_2D,
354                          0,
355                          GL_RGBA,
356                          bitmap.width(),
357                          bitmap.height(),
358                          0,
359                          GL_RGBA,
360                          GL_UNSIGNED_BYTE,
361                          gl_pixels.get());
362  }
363
364  gpu::Mailbox mailbox;
365  context3d->genMailboxCHROMIUM(mailbox.name);
366  context3d->produceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
367  context3d->bindTexture(GL_TEXTURE_2D, 0);
368  uint32 sync_point = context3d->insertSyncPoint();
369
370  return TextureMailbox(
371      mailbox,
372      base::Bind(&LayerTreePixelTest::ReleaseTextureMailbox,
373                 base::Unretained(this),
374                 base::Passed(&context3d),
375                 texture_id),
376      sync_point);
377}
378
379}  // namespace cc
380