1 // Copyright (c) 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.
5 #include "content/browser/renderer_host/compositing_iosurface_transformer_mac.h"
7 #include <OpenGL/CGLCurrent.h>
8 #include <OpenGL/CGLRenderers.h>
9 #include <OpenGL/CGLTypes.h>
10 #include <OpenGL/OpenGL.h>
11 #include <OpenGL/gl.h>
12 #include <OpenGL/glu.h>
19 #include "base/logging.h"
20 #include "base/memory/scoped_ptr.h"
21 #include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
22 #include "media/base/yuv_convert.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "third_party/skia/include/core/SkBitmap.h"
25 #include "third_party/skia/include/core/SkCanvas.h"
26 #include "third_party/skia/include/core/SkColor.h"
27 #include "third_party/skia/include/core/SkRect.h"
28 #include "ui/gfx/rect.h"
32 #define EXPECT_NO_GL_ERROR(stmt) \
35 const GLenum error_code = glGetError(); \
36 EXPECT_TRUE(GL_NO_ERROR == error_code) \
37 << "for error code " << error_code \
38 << ": " << gluErrorString(error_code); \
43 const GLenum kGLTextureTarget
= GL_TEXTURE_RECTANGLE_ARB
;
45 enum RendererRestriction
{
47 RESTRICTION_SOFTWARE_ONLY
,
48 RESTRICTION_HARDWARE_ONLY
51 bool InitializeGLContext(CGLContextObj
* context
,
52 RendererRestriction restriction
) {
53 std::vector
<CGLPixelFormatAttribute
> attribs
;
54 // Select off-screen renderers only.
55 attribs
.push_back(kCGLPFAOffScreen
);
56 // By default, the library will prefer hardware-accelerated renderers, but
57 // falls back on the software ones if necessary. However, there are use cases
58 // where we want to force a restriction (e.g., benchmarking performance).
59 if (restriction
== RESTRICTION_SOFTWARE_ONLY
) {
60 attribs
.push_back(kCGLPFARendererID
);
61 attribs
.push_back(static_cast<CGLPixelFormatAttribute
>(
62 kCGLRendererGenericFloatID
));
63 } else if (restriction
== RESTRICTION_HARDWARE_ONLY
) {
64 attribs
.push_back(kCGLPFAAccelerated
);
66 attribs
.push_back(static_cast<CGLPixelFormatAttribute
>(0));
68 CGLPixelFormatObj format
;
69 GLint num_pixel_formats
= 0;
71 if (CGLChoosePixelFormat(&attribs
.front(), &format
, &num_pixel_formats
) !=
73 LOG(ERROR
) << "Error choosing pixel format.";
76 if (success
&& num_pixel_formats
<= 0) {
77 LOG(ERROR
) << "num_pixel_formats <= 0; actual value is "
81 if (success
&& CGLCreateContext(format
, NULL
, context
) != kCGLNoError
) {
82 LOG(ERROR
) << "Error creating context.";
85 CGLDestroyPixelFormat(format
);
89 // Returns a decent test pattern for testing all of: 1) orientation, 2) scaling,
90 // 3) color space conversion (e.g., 4 pixels --> one U or V pixel), and 4)
91 // texture alignment/processing. Example 32x32 bitmap:
93 // GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
94 // GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
95 // GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
96 // GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
97 // GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
98 // GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
99 // GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
100 // GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
101 // RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
102 // RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
103 // YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
104 // YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
105 // RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
106 // RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
107 // YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
108 // YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
110 // Key: G = Gray, R = Red, B = Blue, Y = Yellow, C = Cyan
111 SkBitmap
GenerateTestPatternBitmap(const gfx::Size
& size
) {
113 CHECK(bitmap
.allocN32Pixels(size
.width(), size
.height()));
114 bitmap
.eraseColor(SK_ColorGRAY
);
115 for (int y
= 0; y
< size
.height(); ++y
) {
116 uint32_t* p
= bitmap
.getAddr32(0, y
);
117 for (int x
= 0; x
< size
.width(); ++x
, ++p
) {
118 if ((x
< (size
.width() / 2)) && (y
< (size
.height() / 2)))
119 continue; // Leave upper-left quadrant gray.
120 *p
= SkColorSetARGB(255,
123 x
% 4 < 2 ? 0 : 255);
129 // Creates a new texture consisting of the given |bitmap|.
130 GLuint
CreateTextureWithImage(const SkBitmap
& bitmap
) {
132 EXPECT_NO_GL_ERROR(glGenTextures(1, &texture
));
133 EXPECT_NO_GL_ERROR(glBindTexture(kGLTextureTarget
, texture
));
135 SkAutoLockPixels
lock_bitmap(bitmap
);
136 EXPECT_NO_GL_ERROR(glTexImage2D(
137 kGLTextureTarget
, 0, GL_RGBA
, bitmap
.width(), bitmap
.height(), 0,
138 GL_BGRA
, GL_UNSIGNED_INT_8_8_8_8_REV
, bitmap
.getPixels()));
140 glBindTexture(kGLTextureTarget
, 0);
144 // Read back a texture from the GPU, returning the image data as an SkBitmap.
145 SkBitmap
ReadBackTexture(GLuint texture
, const gfx::Size
& size
, GLenum format
) {
147 CHECK(result
.allocN32Pixels(size
.width(), size
.height()));
150 EXPECT_NO_GL_ERROR(glGenFramebuffersEXT(1, &frame_buffer
));
152 glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT
, frame_buffer
));
153 EXPECT_NO_GL_ERROR(glFramebufferTexture2DEXT(
154 GL_READ_FRAMEBUFFER_EXT
, GL_COLOR_ATTACHMENT0_EXT
, kGLTextureTarget
,
156 DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT
) ==
157 GL_FRAMEBUFFER_COMPLETE_EXT
);
160 SkAutoLockPixels
lock_result(result
);
161 EXPECT_NO_GL_ERROR(glReadPixels(
162 0, 0, size
.width(), size
.height(), format
, GL_UNSIGNED_INT_8_8_8_8_REV
,
163 result
.getPixels()));
166 EXPECT_NO_GL_ERROR(glDeleteFramebuffersEXT(1, &frame_buffer
));
171 // Returns the |src_rect| region of |src| scaled to |to_size| by drawing on a
172 // Skia canvas, and using bilinear filtering (just like a GPU would).
173 SkBitmap
ScaleBitmapWithSkia(const SkBitmap
& src
,
174 const gfx::Rect
& src_rect
,
175 const gfx::Size
& to_size
) {
176 SkBitmap cropped_src
;
177 if (src_rect
== gfx::Rect(0, 0, src
.width(), src
.height())) {
180 CHECK(src
.extractSubset(
182 SkIRect::MakeXYWH(src_rect
.x(), src_rect
.y(),
183 src_rect
.width(), src_rect
.height())));
187 CHECK(result
.allocPixels(
188 cropped_src
.info().makeWH(to_size
.width(), to_size
.height())));
190 SkCanvas
canvas(result
);
191 canvas
.scale(static_cast<double>(result
.width()) / cropped_src
.width(),
192 static_cast<double>(result
.height()) / cropped_src
.height());
194 paint
.setFilterLevel(SkPaint::kLow_FilterLevel
); // Use bilinear filtering.
195 canvas
.drawBitmap(cropped_src
, 0, 0, &paint
);
200 // The maximum value by which a pixel value may deviate from the expected value
201 // before considering it "significantly different." This is meant to account
202 // for the slight differences in filtering techniques used between the various
203 // GPUs and software implementations.
204 const int kDifferenceThreshold
= 16;
206 // Returns the number of pixels significantly different between |expected| and
208 int ImageDifference(const SkBitmap
& expected
, const SkBitmap
& actual
) {
209 SkAutoLockPixels
lock_expected(expected
);
210 SkAutoLockPixels
lock_actual(actual
);
212 // Sanity-check assumed image properties.
213 DCHECK_EQ(expected
.width(), actual
.width());
214 DCHECK_EQ(expected
.height(), actual
.height());
215 DCHECK_EQ(kN32_SkColorType
, expected
.colorType());
216 DCHECK_EQ(kN32_SkColorType
, actual
.colorType());
218 // Compare both images.
219 int num_pixels_different
= 0;
220 for (int y
= 0; y
< expected
.height(); ++y
) {
221 const uint32_t* p
= expected
.getAddr32(0, y
);
222 const uint32_t* q
= actual
.getAddr32(0, y
);
223 for (int x
= 0; x
< expected
.width(); ++x
, ++p
, ++q
) {
224 if (abs(static_cast<int>(SkColorGetR(*p
)) -
225 static_cast<int>(SkColorGetR(*q
))) > kDifferenceThreshold
||
226 abs(static_cast<int>(SkColorGetG(*p
)) -
227 static_cast<int>(SkColorGetG(*q
))) > kDifferenceThreshold
||
228 abs(static_cast<int>(SkColorGetB(*p
)) -
229 static_cast<int>(SkColorGetB(*q
))) > kDifferenceThreshold
) {
230 ++num_pixels_different
;
235 return num_pixels_different
;
238 // Returns the number of pixels significantly different between |expected| and
239 // |actual|. It is understood that |actual| contains 4-byte quads, and so we
240 // may need to be ignoring a mod-4 number of pixels at the end of each of its
242 int ImagePlaneDifference(const uint8
* expected
, const SkBitmap
& actual
,
243 const gfx::Size
& dst_size
) {
244 SkAutoLockPixels
actual_lock(actual
);
246 int num_pixels_different
= 0;
247 for (int y
= 0; y
< dst_size
.height(); ++y
) {
248 const uint8
* p
= expected
+ y
* dst_size
.width();
249 const uint8
* const p_end
= p
+ dst_size
.width();
251 reinterpret_cast<uint8
*>(actual
.getPixels()) + y
* actual
.rowBytes();
252 for (; p
< p_end
; ++p
, ++q
) {
253 if (abs(static_cast<int>(*p
) - static_cast<int>(*q
)) >
254 kDifferenceThreshold
) {
255 ++num_pixels_different
;
260 return num_pixels_different
;
265 // Note: All tests fixtures operate within an off-screen OpenGL context.
266 class CompositingIOSurfaceTransformerTest
: public testing::Test
{
268 CompositingIOSurfaceTransformerTest() {
269 // TODO(miu): Try to use RESTRICTION_NONE to speed up the execution time of
270 // unit tests, once it's established that the trybots and buildbots behave
271 // well when using the GPU.
272 CHECK(InitializeGLContext(&context_
, RESTRICTION_SOFTWARE_ONLY
));
273 CGLSetCurrentContext(context_
);
274 shader_program_cache_
.reset(new CompositingIOSurfaceShaderPrograms());
275 transformer_
.reset(new CompositingIOSurfaceTransformer(
276 kGLTextureTarget
, false, shader_program_cache_
.get()));
279 virtual ~CompositingIOSurfaceTransformerTest() {
280 transformer_
->ReleaseCachedGLObjects();
281 shader_program_cache_
->Reset();
282 CGLSetCurrentContext(NULL
);
283 CGLDestroyContext(context_
);
287 void RunResizeTest(const SkBitmap
& src_bitmap
, const gfx::Rect
& src_rect
,
288 const gfx::Size
& dst_size
) {
289 SCOPED_TRACE(::testing::Message()
290 << "src_rect=(" << src_rect
.x() << ',' << src_rect
.y()
291 << ")x[" << src_rect
.width() << 'x' << src_rect
.height()
292 << "]; dst_size=[" << dst_size
.width() << 'x'
293 << dst_size
.height() << ']');
295 // Do the scale operation on the GPU.
296 const GLuint original_texture
= CreateTextureWithImage(src_bitmap
);
297 ASSERT_NE(0u, original_texture
);
298 GLuint scaled_texture
= 0u;
299 ASSERT_TRUE(transformer_
->ResizeBilinear(
300 original_texture
, src_rect
, dst_size
, &scaled_texture
));
301 EXPECT_NE(0u, scaled_texture
);
302 CGLFlushDrawable(context_
); // Account for some buggy driver impls.
303 const SkBitmap result_bitmap
=
304 ReadBackTexture(scaled_texture
, dst_size
, GL_BGRA
);
305 EXPECT_NO_GL_ERROR(glDeleteTextures(1, &original_texture
));
307 // Compare the image read back to the version produced by a known-working
308 // software implementation. Allow up to 2 lines of mismatch due to how
309 // implementations disagree on resolving the processing of edges.
310 const SkBitmap expected_bitmap
=
311 ScaleBitmapWithSkia(src_bitmap
, src_rect
, dst_size
);
312 EXPECT_GE(std::max(expected_bitmap
.width(), expected_bitmap
.height()) * 2,
313 ImageDifference(expected_bitmap
, result_bitmap
));
316 void RunTransformRGBToYV12Test(
317 const SkBitmap
& src_bitmap
, const gfx::Rect
& src_rect
,
318 const gfx::Size
& dst_size
) {
319 SCOPED_TRACE(::testing::Message()
320 << "src_rect=(" << src_rect
.x() << ',' << src_rect
.y()
321 << ")x[" << src_rect
.width() << 'x' << src_rect
.height()
322 << "]; dst_size=[" << dst_size
.width() << 'x'
323 << dst_size
.height() << ']');
325 // Perform the RGB to YV12 conversion.
326 const GLuint original_texture
= CreateTextureWithImage(src_bitmap
);
327 ASSERT_NE(0u, original_texture
);
328 GLuint texture_y
= 0u;
329 GLuint texture_u
= 0u;
330 GLuint texture_v
= 0u;
331 gfx::Size packed_y_size
;
332 gfx::Size packed_uv_size
;
333 ASSERT_TRUE(transformer_
->TransformRGBToYV12(
334 original_texture
, src_rect
, dst_size
,
335 &texture_y
, &texture_u
, &texture_v
, &packed_y_size
, &packed_uv_size
));
336 EXPECT_NE(0u, texture_y
);
337 EXPECT_NE(0u, texture_u
);
338 EXPECT_NE(0u, texture_v
);
339 EXPECT_FALSE(packed_y_size
.IsEmpty());
340 EXPECT_FALSE(packed_uv_size
.IsEmpty());
341 EXPECT_NO_GL_ERROR(glDeleteTextures(1, &original_texture
));
343 // Read-back the texture for each plane.
344 CGLFlushDrawable(context_
); // Account for some buggy driver impls.
345 const GLenum format
= shader_program_cache_
->rgb_to_yv12_output_format();
346 const SkBitmap result_y_bitmap
=
347 ReadBackTexture(texture_y
, packed_y_size
, format
);
348 const SkBitmap result_u_bitmap
=
349 ReadBackTexture(texture_u
, packed_uv_size
, format
);
350 const SkBitmap result_v_bitmap
=
351 ReadBackTexture(texture_v
, packed_uv_size
, format
);
353 // Compare the Y, U, and V planes read-back to the version produced by a
354 // known-working software implementation. Allow up to 2 lines of mismatch
355 // due to how implementations disagree on resolving the processing of edges.
356 const SkBitmap expected_bitmap
=
357 ScaleBitmapWithSkia(src_bitmap
, src_rect
, dst_size
);
358 const gfx::Size
dst_uv_size(
359 (dst_size
.width() + 1) / 2, (dst_size
.height() + 1) / 2);
360 scoped_ptr
<uint8
[]> expected_y_plane(
361 new uint8
[dst_size
.width() * dst_size
.height()]);
362 scoped_ptr
<uint8
[]> expected_u_plane(
363 new uint8
[dst_uv_size
.width() * dst_uv_size
.height()]);
364 scoped_ptr
<uint8
[]> expected_v_plane(
365 new uint8
[dst_uv_size
.width() * dst_uv_size
.height()]);
367 SkAutoLockPixels
src_bitmap_lock(expected_bitmap
);
368 media::ConvertRGB32ToYUV(
369 reinterpret_cast<const uint8
*>(expected_bitmap
.getPixels()),
370 expected_y_plane
.get(), expected_u_plane
.get(),
371 expected_v_plane
.get(),
372 expected_bitmap
.width(), expected_bitmap
.height(),
373 expected_bitmap
.rowBytes(),
374 dst_size
.width(), (dst_size
.width() + 1) / 2);
377 std::max(expected_bitmap
.width(), expected_bitmap
.height()) * 2,
378 ImagePlaneDifference(expected_y_plane
.get(), result_y_bitmap
, dst_size
))
379 << " for RGB --> Y Plane";
381 std::max(expected_bitmap
.width(), expected_bitmap
.height()),
382 ImagePlaneDifference(expected_u_plane
.get(), result_u_bitmap
,
384 << " for RGB --> U Plane";
386 std::max(expected_bitmap
.width(), expected_bitmap
.height()),
387 ImagePlaneDifference(expected_v_plane
.get(), result_v_bitmap
,
389 << " for RGB --> V Plane";
392 CompositingIOSurfaceShaderPrograms
* shader_program_cache() const {
393 return shader_program_cache_
.get();
397 CGLContextObj context_
;
398 scoped_ptr
<CompositingIOSurfaceShaderPrograms
> shader_program_cache_
;
399 scoped_ptr
<CompositingIOSurfaceTransformer
> transformer_
;
402 DISALLOW_COPY_AND_ASSIGN(CompositingIOSurfaceTransformerTest
);
405 TEST_F(CompositingIOSurfaceTransformerTest
, ShaderProgramsCompileAndLink
) {
406 // Attempt to use each program, binding its required uniform variables.
407 EXPECT_NO_GL_ERROR(shader_program_cache()->UseBlitProgram());
408 EXPECT_NO_GL_ERROR(shader_program_cache()->UseSolidWhiteProgram());
409 EXPECT_NO_GL_ERROR(shader_program_cache()->UseRGBToYV12Program(1, 1.0f
));
410 EXPECT_NO_GL_ERROR(shader_program_cache()->UseRGBToYV12Program(2, 1.0f
));
412 EXPECT_NO_GL_ERROR(glUseProgram(0));
417 const struct TestParameters
{
422 } kTestParameters
[] = {
423 // Test 1:1 copies, but exposing varying pixel packing configurations.
434 // Even-size, one or both dimensions upscaled.
435 { 32, 32, 64, 32 }, { 32, 32, 32, 64 }, { 32, 32, 64, 64 },
436 // Even-size, one or both dimensions downscaled by 2X.
437 { 32, 32, 16, 32 }, { 32, 32, 32, 16 }, { 32, 32, 16, 16 },
438 // Even-size, one or both dimensions downscaled by 1 pixel.
439 { 32, 32, 31, 32 }, { 32, 32, 32, 31 }, { 32, 32, 31, 31 },
440 // Even-size, one or both dimensions downscaled by 2 pixels.
441 { 32, 32, 30, 32 }, { 32, 32, 32, 30 }, { 32, 32, 30, 30 },
442 // Even-size, one or both dimensions downscaled by 3 pixels.
443 { 32, 32, 29, 32 }, { 32, 32, 32, 29 }, { 32, 32, 29, 29 },
445 // Odd-size, one or both dimensions upscaled.
446 { 33, 33, 66, 33 }, { 33, 33, 33, 66 }, { 33, 33, 66, 66 },
447 // Odd-size, one or both dimensions downscaled by 2X.
448 { 33, 33, 16, 33 }, { 33, 33, 33, 16 }, { 33, 33, 16, 16 },
449 // Odd-size, one or both dimensions downscaled by 1 pixel.
450 { 33, 33, 32, 33 }, { 33, 33, 33, 32 }, { 33, 33, 32, 32 },
451 // Odd-size, one or both dimensions downscaled by 2 pixels.
452 { 33, 33, 31, 33 }, { 33, 33, 33, 31 }, { 33, 33, 31, 31 },
453 // Odd-size, one or both dimensions downscaled by 3 pixels.
454 { 33, 33, 30, 33 }, { 33, 33, 33, 30 }, { 33, 33, 30, 30 },
459 TEST_F(CompositingIOSurfaceTransformerTest
, ResizesTexturesCorrectly
) {
460 for (size_t i
= 0; i
< arraysize(kTestParameters
); ++i
) {
461 SCOPED_TRACE(::testing::Message() << "kTestParameters[" << i
<< ']');
463 const TestParameters
& params
= kTestParameters
[i
];
464 const gfx::Size
src_size(params
.src_width
, params
.src_height
);
465 const gfx::Size
dst_size(params
.scaled_width
, params
.scaled_height
);
466 const SkBitmap src_bitmap
= GenerateTestPatternBitmap(src_size
);
468 // Full texture resize test.
469 RunResizeTest(src_bitmap
, gfx::Rect(src_size
), dst_size
);
470 // Subrect resize test: missing top row in source.
471 RunResizeTest(src_bitmap
,
472 gfx::Rect(0, 1, params
.src_width
, params
.src_height
- 1),
474 // Subrect resize test: missing left column in source.
475 RunResizeTest(src_bitmap
,
476 gfx::Rect(1, 0, params
.src_width
- 1, params
.src_height
),
478 // Subrect resize test: missing top+bottom rows, and left column in source.
479 RunResizeTest(src_bitmap
,
480 gfx::Rect(1, 1, params
.src_width
- 1, params
.src_height
- 2),
482 // Subrect resize test: missing top row, and left+right columns in source.
483 RunResizeTest(src_bitmap
,
484 gfx::Rect(1, 1, params
.src_width
- 2, params
.src_height
- 1),
489 TEST_F(CompositingIOSurfaceTransformerTest
, TransformsRGBToYV12
) {
490 static const GLenum kOutputFormats
[] = { GL_BGRA
, GL_RGBA
};
492 for (size_t i
= 0; i
< arraysize(kOutputFormats
); ++i
) {
493 SCOPED_TRACE(::testing::Message() << "kOutputFormats[" << i
<< ']');
495 shader_program_cache()->SetOutputFormatForTesting(kOutputFormats
[i
]);
497 for (size_t j
= 0; j
< arraysize(kTestParameters
); ++j
) {
498 SCOPED_TRACE(::testing::Message() << "kTestParameters[" << j
<< ']');
500 const TestParameters
& params
= kTestParameters
[j
];
501 const gfx::Size
src_size(params
.src_width
, params
.src_height
);
502 const gfx::Size
dst_size(params
.scaled_width
, params
.scaled_height
);
503 const SkBitmap src_bitmap
= GenerateTestPatternBitmap(src_size
);
505 // Full texture resize test.
506 RunTransformRGBToYV12Test(src_bitmap
, gfx::Rect(src_size
), dst_size
);
507 // Subrect resize test: missing top row in source.
508 RunTransformRGBToYV12Test(
509 src_bitmap
, gfx::Rect(0, 1, params
.src_width
, params
.src_height
- 1),
511 // Subrect resize test: missing left column in source.
512 RunTransformRGBToYV12Test(
513 src_bitmap
, gfx::Rect(1, 0, params
.src_width
- 1, params
.src_height
),
515 // Subrect resize test: missing top+bottom rows, and left column in
517 RunTransformRGBToYV12Test(
519 gfx::Rect(1, 1, params
.src_width
- 1, params
.src_height
- 2),
521 // Subrect resize test: missing top row, and left+right columns in source.
522 RunTransformRGBToYV12Test(
524 gfx::Rect(1, 1, params
.src_width
- 2, params
.src_height
- 1),
530 } // namespace content