Chromium Code Reviews| Index: content/common/gpu/client/gl_helper_unittest.cc |
| diff --git a/content/common/gpu/client/gl_helper_unittest.cc b/content/common/gpu/client/gl_helper_unittest.cc |
| index e22c3f7966bc6eaa19b1c7af6078d4deb8d3f726..a124acce11d2777a3d689a9b890aa1a9f30bc2c4 100644 |
| --- a/content/common/gpu/client/gl_helper_unittest.cc |
| +++ b/content/common/gpu/client/gl_helper_unittest.cc |
| @@ -139,24 +139,40 @@ class GLHelperTest : public testing::Test { |
| } |
| } |
| - // Look up a single R/G/B/A value. |
| - // Clamp x/y. |
| + // Look up a single channel value. Works for 4-channel and single channel |
| + // bitmaps. Clamp x/y. |
| int Channel(SkBitmap* pixels, int x, int y, int c) { |
| - uint32* data = |
| - pixels->getAddr32(std::max(0, std::min(x, pixels->width() - 1)), |
| - std::max(0, std::min(y, pixels->height() - 1))); |
| - return (*data) >> (c * 8) & 0xff; |
| + if (pixels->bytesPerPixel() == 4) { |
| + uint32* data = |
| + pixels->getAddr32(std::max(0, std::min(x, pixels->width() - 1)), |
| + std::max(0, std::min(y, pixels->height() - 1))); |
| + return (*data) >> (c * 8) & 0xff; |
| + } else { |
| + DCHECK_EQ(pixels->bytesPerPixel(), 1); |
| + DCHECK_EQ(c, 0); |
| + return *pixels->getAddr8(std::max(0, std::min(x, pixels->width() - 1)), |
| + std::max(0, std::min(y, pixels->height() - 1))); |
| + } |
| } |
| - // Set a single R/G/B/A value. |
| + // Set a single channel value. Works for 4-channel and single channel |
| + // bitmaps. Clamp x/y. |
| void SetChannel(SkBitmap* pixels, int x, int y, int c, int v) { |
| DCHECK_GE(x, 0); |
| DCHECK_GE(y, 0); |
| DCHECK_LT(x, pixels->width()); |
| DCHECK_LT(y, pixels->height()); |
| - uint32* data = pixels->getAddr32(x, y); |
| - v = std::max(0, std::min(v, 255)); |
| - *data = (*data & ~(0xffu << (c * 8))) | (v << (c * 8)); |
| + if (pixels->bytesPerPixel() == 4) { |
| + uint32* data = pixels->getAddr32(x, y); |
| + v = std::max(0, std::min(v, 255)); |
| + *data = (*data & ~(0xffu << (c * 8))) | (v << (c * 8)); |
| + } else { |
| + DCHECK_EQ(pixels->bytesPerPixel(), 1); |
| + DCHECK_EQ(c, 0); |
| + uint8* data = pixels->getAddr8(x, y); |
| + v = std::max(0, std::min(v, 255)); |
| + *data = v; |
| + } |
| } |
| // Print all the R, G, B or A values from an SkBitmap in a |
| @@ -389,21 +405,32 @@ class GLHelperTest : public testing::Test { |
| std::string message) { |
| EXPECT_EQ(truth->width(), other->width()); |
| EXPECT_EQ(truth->height(), other->height()); |
| + EXPECT_EQ(truth->bytesPerPixel(), other->bytesPerPixel()); |
| + int bpp = truth->bytesPerPixel(); |
| for (int x = 0; x < truth->width(); x++) { |
| for (int y = 0; y < truth->height(); y++) { |
| - for (int c = 0; c < 4; c++) { |
| + for (int c = 0; c < bpp; c++) { |
| int a = Channel(truth, x, y, c); |
| int b = Channel(other, x, y, c); |
| EXPECT_NEAR(a, b, maxdiff) << " x=" << x << " y=" << y << " c=" << c |
| << " " << message; |
| if (std::abs(a - b) > maxdiff) { |
| LOG(ERROR) << "-------expected--------"; |
| - PrintChannel(truth, c); |
| + for (int i = 0; i < bpp; i++) { |
| + LOG(ERROR) << "Channel " << i << ":"; |
| + PrintChannel(truth, i); |
| + } |
| LOG(ERROR) << "-------actual--------"; |
| - PrintChannel(other, c); |
| + for (int i = 0; i < bpp; i++) { |
| + LOG(ERROR) << "Channel " << i << ":"; |
| + PrintChannel(other, i); |
| + } |
| if (source) { |
| - LOG(ERROR) << "-------before scaling--------"; |
| - PrintChannel(source, c); |
| + LOG(ERROR) << "-------original--------"; |
|
no sievers
2014/08/20 20:29:52
nit: indent
mfomitchev
2014/08/22 18:01:33
Done.
|
| + for (int i = 0; i < source->bytesPerPixel(); i++) { |
| + LOG(ERROR) << "Channel " << i << ":"; |
| + PrintChannel(source, i); |
| + } |
| } |
| LOG(ERROR) << "-----Scaler stages------"; |
| LOG(ERROR) << PrintStages(scaler_stages); |
| @@ -433,6 +460,34 @@ class GLHelperTest : public testing::Test { |
| ChannelAsFloat(pixels, base_x + 1, base_y + 1, c) * x * y); |
| } |
| + // Encodes an RGBA bitmap to grayscale. |
| + // Reference implementation for |
| + // GLHelper::CopyToTextureImpl::EncodeTextureAsGrayscale. |
| + void EncodeToGrayscaleSlow(SkBitmap* input, |
| + SkBitmap* output) { |
| + const float kRGBtoGrayscaleColorWeights[3] = {0.213f, 0.715f, 0.072f}; |
|
no sievers
2014/08/20 20:29:52
Should these constants be shared?
mfomitchev
2014/08/22 18:01:33
It wouldn't be terribly straightforward. This arra
no sievers
2014/08/22 18:44:13
If you expose them in gl_helper.h somehow, can the
mfomitchev
2014/08/25 19:21:53
Yes, I could construct the 3-element array in gl_h
|
| + CHECK_EQ(kAlpha_8_SkColorType, output->colorType()); |
| + CHECK_EQ(input->width(), output->width()); |
| + CHECK_EQ(input->height(), output->height()); |
| + CHECK_EQ(input->colorType(), kRGBA_8888_SkColorType); |
| + |
| + for (int dst_y = 0; dst_y < output->height(); dst_y++) { |
| + for (int dst_x = 0; dst_x < output->width(); dst_x++) { |
| + float c0 = ChannelAsFloat(input, dst_x, dst_y, 0); |
| + float c1 = ChannelAsFloat(input, dst_x, dst_y, 1); |
| + float c2 = ChannelAsFloat(input, dst_x, dst_y, 2); |
| + float value = c0 * kRGBtoGrayscaleColorWeights[0] + |
| + c1 * kRGBtoGrayscaleColorWeights[1] + |
| + c2 * kRGBtoGrayscaleColorWeights[2]; |
| + SetChannel(output, |
| + dst_x, |
| + dst_y, |
| + 0, |
| + static_cast<int>(value * 255.0f + 0.5f)); |
|
no sievers
2014/08/20 20:29:52
hmm why +0.5?
mfomitchev
2014/08/22 18:01:33
So that it rounds to the closest int rather than r
|
| + } |
| + } |
| + } |
| + |
| // Very slow bicubic / bilinear scaler for reference. |
| void ScaleSlow(SkBitmap* input, |
| SkBitmap* output, |
| @@ -515,18 +570,38 @@ class GLHelperTest : public testing::Test { |
| } |
| void FlipSKBitmap(SkBitmap* bitmap) { |
| + int bpp = bitmap->bytesPerPixel(); |
| + DCHECK(bpp == 4 || bpp == 1); |
| int top_line = 0; |
| int bottom_line = bitmap->height() - 1; |
| while (top_line < bottom_line) { |
| for (int x = 0; x < bitmap->width(); x++) { |
| - std::swap(*bitmap->getAddr32(x, top_line), |
| - *bitmap->getAddr32(x, bottom_line)); |
| + bpp == 4 ? |
| + std::swap(*bitmap->getAddr32(x, top_line), |
| + *bitmap->getAddr32(x, bottom_line)) |
| + : std::swap(*bitmap->getAddr8(x, top_line), |
| + *bitmap->getAddr8(x, bottom_line)); |
| } |
| top_line++; |
| bottom_line--; |
| } |
| } |
| + // Swaps red and blue channels in each pixel in a 32-bit bitmap. |
| + void SwizzleSKBitmap(SkBitmap* bitmap) { |
| + int bpp = bitmap->bytesPerPixel(); |
| + DCHECK(bpp == 4); |
| + for (int y = 0; y < bitmap->height(); y++) { |
| + for (int x = 0; x < bitmap->width(); x++) { |
| + // Swap channels 0 and 2 (red and blue) |
| + int c0 = Channel(bitmap, x, y, 0); |
| + int c2 = Channel(bitmap, x, y, 2); |
| + SetChannel(bitmap, x, y, 2, c0); |
| + SetChannel(bitmap, x, y, 0, c2); |
| + } |
| + } |
| + } |
| + |
| // gl_helper scales recursively, so we'll need to do that |
| // in the reference implementation too. |
| void ScaleSlowRecursive(SkBitmap* input, |
| @@ -575,56 +650,179 @@ class GLHelperTest : public testing::Test { |
| ScaleSlowRecursive(&tmp, output, quality); |
| } |
| - // Scaling test: Create a test image, scale it using GLHelperScaling |
| - // and a reference implementation and compare the results. |
| - void TestScale(int xsize, |
| - int ysize, |
| - int scaled_xsize, |
| - int scaled_ysize, |
| - int test_pattern, |
| - size_t quality, |
| - bool flip) { |
| - WebGLId src_texture = context_->createTexture(); |
| - WebGLId framebuffer = context_->createFramebuffer(); |
| - SkBitmap input_pixels; |
| - input_pixels.allocN32Pixels(xsize, ysize); |
| + // Creates an RGBA SkBitmap |
| + scoped_ptr<SkBitmap> CreateTestBitmap(int width, |
| + int height, |
| + int test_pattern) { |
| + scoped_ptr<SkBitmap> bitmap(new SkBitmap); |
| + bitmap->allocPixels(SkImageInfo::Make( |
| + width, height, kRGBA_8888_SkColorType, kPremul_SkAlphaType)); |
| - for (int x = 0; x < xsize; ++x) { |
| - for (int y = 0; y < ysize; ++y) { |
| + for (int x = 0; x < width; ++x) { |
| + for (int y = 0; y < height; ++y) { |
| switch (test_pattern) { |
| case 0: // Smooth test pattern |
| - SetChannel(&input_pixels, x, y, 0, x * 10); |
| - SetChannel(&input_pixels, x, y, 1, y * 10); |
| - SetChannel(&input_pixels, x, y, 2, (x + y) * 10); |
| - SetChannel(&input_pixels, x, y, 3, 255); |
| + SetChannel(bitmap.get(), x, y, 0, x * 10); |
| + SetChannel(bitmap.get(), x, y, 0, y == 0 ? x * 50 : x * 10); |
| + SetChannel(bitmap.get(), x, y, 1, y * 10); |
| + SetChannel(bitmap.get(), x, y, 2, (x + y) * 10); |
| + SetChannel(bitmap.get(), x, y, 3, 255); |
| break; |
| case 1: // Small blocks |
| - SetChannel(&input_pixels, x, y, 0, x & 1 ? 255 : 0); |
| - SetChannel(&input_pixels, x, y, 1, y & 1 ? 255 : 0); |
| - SetChannel(&input_pixels, x, y, 2, (x + y) & 1 ? 255 : 0); |
| - SetChannel(&input_pixels, x, y, 3, 255); |
| + SetChannel(bitmap.get(), x, y, 0, x & 1 ? 255 : 0); |
| + SetChannel(bitmap.get(), x, y, 1, y & 1 ? 255 : 0); |
| + SetChannel(bitmap.get(), x, y, 2, (x + y) & 1 ? 255 : 0); |
| + SetChannel(bitmap.get(), x, y, 3, 255); |
| break; |
| case 2: // Medium blocks |
| - SetChannel(&input_pixels, x, y, 0, 10 + x / 2 * 50); |
| - SetChannel(&input_pixels, x, y, 1, 10 + y / 3 * 50); |
| - SetChannel(&input_pixels, x, y, 2, (x + y) / 5 * 50 + 5); |
| - SetChannel(&input_pixels, x, y, 3, 255); |
| + SetChannel(bitmap.get(), x, y, 0, 10 + x / 2 * 50); |
| + SetChannel(bitmap.get(), x, y, 1, 10 + y / 3 * 50); |
| + SetChannel(bitmap.get(), x, y, 2, (x + y) / 5 * 50 + 5); |
| + SetChannel(bitmap.get(), x, y, 3, 255); |
| break; |
| } |
| } |
| } |
| + return bitmap.Pass(); |
| + } |
| + // Binds texture and framebuffer and loads the bitmap pixels into the texture. |
| + void BindTextureAndFrameBuffer(WebGLId texture, |
| + WebGLId framebuffer, |
| + SkBitmap* bitmap, |
| + int width, |
| + int height) { |
| context_->bindFramebuffer(GL_FRAMEBUFFER, framebuffer); |
| - context_->bindTexture(GL_TEXTURE_2D, src_texture); |
| + context_->bindTexture(GL_TEXTURE_2D, texture); |
| context_->texImage2D(GL_TEXTURE_2D, |
| 0, |
| GL_RGBA, |
| - xsize, |
| - ysize, |
| + width, |
| + height, |
| 0, |
| GL_RGBA, |
| GL_UNSIGNED_BYTE, |
| - input_pixels.getPixels()); |
| + bitmap->getPixels()); |
| + } |
| + |
| + // Create a test image, transform it using |
| + // GLHelper::CropScaleReadbackAndCleanTexture and a reference implementation |
| + // and compare the results. |
| + void TestCropScaleReadbackAndCleanTexture(int xsize, |
| + int ysize, |
| + int scaled_xsize, |
| + int scaled_ysize, |
| + int test_pattern, |
| + SkColorType out_color_type, |
| + bool swizzle, |
| + size_t quality) { |
|
no sievers
2014/08/20 20:29:52
nit: maybe less error-prone to just pass the Scale
mfomitchev
2014/08/22 18:01:33
Yeah, this is modeled after TestScale() below, but
no sievers
2014/08/22 18:44:13
I see, maybe just keep it as is then if it matches
|
| + DCHECK(out_color_type == kAlpha_8_SkColorType || |
| + out_color_type == kRGBA_8888_SkColorType || |
| + out_color_type == kBGRA_8888_SkColorType); |
| + WebGLId src_texture = context_->createTexture(); |
| + WebGLId framebuffer = context_->createFramebuffer(); |
| + scoped_ptr<SkBitmap> input_pixels = |
| + CreateTestBitmap(xsize, ysize, test_pattern).Pass(); |
| + BindTextureAndFrameBuffer( |
| + src_texture, framebuffer, input_pixels.get(), xsize, ysize); |
| + |
| + std::string message = base::StringPrintf( |
| + "input size: %dx%d " |
| + "output size: %dx%d " |
| + "pattern: %d , quality: %s, " |
| + "out_color_type: %d", |
| + xsize, |
| + ysize, |
| + scaled_xsize, |
| + scaled_ysize, |
| + test_pattern, |
| + kQualityNames[quality], |
| + out_color_type); |
| + |
| + // Transform the bitmap using GLHelper::CropScaleReadbackAndCleanTexture. |
| + SkBitmap output_pixels; |
| + output_pixels.allocPixels(SkImageInfo::Make( |
| + scaled_xsize, scaled_ysize, out_color_type, kPremul_SkAlphaType)); |
| + base::RunLoop run_loop; |
| + gfx::Size encoded_texture_size; |
| + helper_->CropScaleReadbackAndCleanTexture( |
| + src_texture, |
| + gfx::Size(xsize, ysize), |
| + gfx::Rect(xsize, ysize), |
| + gfx::Size(scaled_xsize, scaled_ysize), |
| + static_cast<unsigned char*>(output_pixels.getPixels()), |
| + out_color_type, |
| + base::Bind(&callcallback, run_loop.QuitClosure()), |
| + kQualities[quality]); |
| + run_loop.Run(); |
| + // CropScaleReadbackAndCleanTexture flips the pixels. Flip them back. |
| + FlipSKBitmap(&output_pixels); |
|
no sievers
2014/08/20 20:29:52
Why do we end up with an upside down image? That s
mfomitchev
2014/08/22 18:01:34
The old version of CropScaleReadbackAndCleanTextur
no sievers
2014/08/22 18:44:13
This is intriguing, as I believe we use this path
|
| + |
| + // Now transofrm the bitmap using the reference implementation. |
|
no sievers
2014/08/20 20:29:52
nit, typo: 'transform'
mfomitchev
2014/08/22 18:01:33
Done.
|
| + SkBitmap scaled_pixels; |
| + scaled_pixels.allocPixels(SkImageInfo::Make( |
| + scaled_xsize, |
| + scaled_ysize, |
| + kRGBA_8888_SkColorType, |
| + kPremul_SkAlphaType)); |
| + SkBitmap truth_pixels; |
| + // Step 1: Scale |
| + ScaleSlowRecursive(input_pixels.get(), &scaled_pixels, kQualities[quality]); |
| + // Step 2: Convert to the specified output format, encoding to grayscale if |
| + // needed. |
| + switch (out_color_type) { |
| + case kRGBA_8888_SkColorType: |
| + truth_pixels = scaled_pixels; |
| + break; |
| + case kBGRA_8888_SkColorType: |
|
no sievers
2014/08/20 20:29:52
nit: indent here and below
mfomitchev
2014/08/22 18:01:33
Done.
|
| + // Swizzle pixels of the scaled bitmap if the output needs to be BGRA |
|
no sievers
2014/08/20 20:29:52
or could Compare() just learn to handle the format
mfomitchev
2014/08/22 18:01:33
Problem is, scale tests here use Compare() to do a
no sievers
2014/08/22 18:44:13
hmm maybe put a TODO then?
mfomitchev
2014/08/25 19:21:53
Ok, I've just updated TestScale to consistently us
|
| + truth_pixels.installPixels( |
| + SkImageInfo::Make(scaled_xsize, |
| + scaled_ysize, |
| + out_color_type, |
| + kPremul_SkAlphaType), |
| + scaled_pixels.getPixels(), |
| + scaled_pixels.width() * 4); |
| + SwizzleSKBitmap(&truth_pixels); |
| + break; |
| + case kAlpha_8_SkColorType: |
| + truth_pixels.allocPixels(SkImageInfo::Make( |
| + scaled_xsize, scaled_ysize, out_color_type, kPremul_SkAlphaType)); |
| + EncodeToGrayscaleSlow(&scaled_pixels, &truth_pixels); |
| + break; |
| + default: |
| + NOTREACHED(); |
| + } |
| + |
| + // Now compare the results. |
| + SkAutoLockPixels lock_input(truth_pixels); |
| + const std::vector<GLHelperScaling::ScalerStage> dummy_stages; |
| + Compare(&truth_pixels, |
| + &output_pixels, |
| + 2, |
|
no sievers
2014/08/20 20:29:52
Just wondering, why 2? Maybe hubbe knows. I did no
mfomitchev
2014/08/22 18:01:33
It does fail on my machine for 16x16 -> 3x6 best-q
|
| + input_pixels.get(), |
| + dummy_stages, |
| + message + " comparing against transformed/scaled"); |
| + |
| + context_->deleteTexture(src_texture); |
| + context_->deleteFramebuffer(framebuffer); |
| + } |
| + |
| + // Scaling test: Create a test image, scale it using GLHelperScaling |
| + // and a reference implementation and compare the results. |
| + void TestScale(int xsize, |
| + int ysize, |
| + int scaled_xsize, |
| + int scaled_ysize, |
| + int test_pattern, |
| + size_t quality, |
| + bool flip) { |
| + WebGLId src_texture = context_->createTexture(); |
| + WebGLId framebuffer = context_->createFramebuffer(); |
| + scoped_ptr<SkBitmap> input_pixels = |
| + CreateTestBitmap(xsize, ysize, test_pattern).Pass(); |
| + BindTextureAndFrameBuffer( |
| + src_texture, framebuffer, input_pixels.get(), xsize, ysize); |
| std::string message = base::StringPrintf( |
| "input size: %dx%d " |
| @@ -670,7 +868,7 @@ class GLHelperTest : public testing::Test { |
| FlipSKBitmap(&output_pixels); |
| } |
| if (xsize == scaled_xsize && ysize == scaled_ysize) { |
| - Compare(&input_pixels, |
| + Compare(input_pixels.get(), |
| &output_pixels, |
| 2, |
| NULL, |
| @@ -680,11 +878,11 @@ class GLHelperTest : public testing::Test { |
| SkBitmap truth_pixels; |
| truth_pixels.allocN32Pixels(scaled_xsize, scaled_ysize); |
| - ScaleSlowRecursive(&input_pixels, &truth_pixels, kQualities[quality]); |
| + ScaleSlowRecursive(input_pixels.get(), &truth_pixels, kQualities[quality]); |
| Compare(&truth_pixels, |
| &output_pixels, |
| 2, |
| - &input_pixels, |
| + input_pixels.get(), |
| stages, |
| message + " comparing against scaled"); |
| @@ -1013,7 +1211,6 @@ class GLHelperTest : public testing::Test { |
| color_type); |
| } |
| } |
| - |
| // Test basic format readback. |
| bool TestTextureFormatReadback(const gfx::Size& src_size, |
| SkColorType color_type, |
| @@ -1625,6 +1822,39 @@ TEST_F(GLHelperPixelTest, ScaleTest) { |
| } |
| } |
| +// Per pixel tests, all sizes are small so that we can print |
| +// out the generated bitmaps. |
| +TEST_F(GLHelperPixelTest, CropScaleReadbackAndCleanTextureTest) { |
| + int sizes[] = {3, 6, 16}; |
| + SkColorType color_types[] = |
|
no sievers
2014/08/20 20:29:52
nit:
const int kSizes[] = ...
const SkColorType k
mfomitchev
2014/08/22 18:01:33
Done.
|
| + {kAlpha_8_SkColorType, kRGBA_8888_SkColorType, kBGRA_8888_SkColorType}; |
| + // Test BEST and FAST qualities, skip GOOD |
| + for (size_t q = 0; q < arraysize(kQualities); q += 2) { |
|
no sievers
2014/08/20 20:29:52
why q+=2 and not q++?
mfomitchev
2014/08/22 18:01:33
FAST uses a different code path in CropScaleReadba
no sievers
2014/08/22 18:44:13
Ok, maybe it's more straightforward to define your
mfomitchev
2014/08/25 19:21:53
The index is also used to fetch the name from kQua
|
| + for (int color_type = 0; color_type < 3; color_type++) { |
|
no sievers
2014/08/20 20:29:52
color_type < arraysize(kColorTypes)
mfomitchev
2014/08/22 18:01:33
Done.
|
| + for (int x = 0; x < 3; x++) { |
| + for (int y = 0; y < 3; y++) { |
| + for (int dst_x = 0; dst_x < 3; dst_x++) { |
| + for (int dst_y = 0; dst_y < 3; dst_y++) { |
| + for (int pattern = 0; pattern < 3; pattern++) { |
| + TestCropScaleReadbackAndCleanTexture(sizes[x], |
| + sizes[y], |
| + sizes[dst_x], |
| + sizes[dst_y], |
| + pattern, |
| + color_types[color_type], |
| + false, |
| + q); |
| + if (HasFailure()) |
| + return; |
| + } |
| + } |
| + } |
| + } |
| + } |
| + } |
| + } |
| +} |
| + |
| // Validate that all scaling generates valid pipelines. |
| TEST_F(GLHelperTest, ValidateScalerPipelines) { |
| int sizes[] = {7, 99, 128, 256, 512, 719, 720, 721, 1920, 2011, 3217, 4096}; |