// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/viz/common/gl_scaler.h" #include #include #include #include "build/build_config.h" #include "cc/test/pixel_test.h" #include "cc/test/pixel_test_utils.h" #include "components/viz/common/gl_scaler_test_util.h" #include "components/viz/common/gpu/context_provider.h" #include "gpu/GLES2/gl2chromium.h" #include "gpu/GLES2/gl2extchromium.h" #include "gpu/command_buffer/client/gles2_implementation.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkImageInfo.h" #include "third_party/skia/include/core/SkRect.h" #include "ui/gfx/color_transform.h" #if defined(OS_ANDROID) #include "base/android/build_info.h" #endif namespace viz { namespace { // Base size of test images to be operated upon. Both dimensions must be // divisible by 2, 3, or 4. constexpr gfx::Size kBaseSize = gfx::Size(16 * 4 * 3, 9 * 4 * 3); } // namespace class GLScalerShaderPixelTest : public cc::PixelTest, public testing::WithParamInterface>, public GLScalerTestUtil { public: using Axis = GLScaler::Axis; using Shader = GLScaler::Shader; using ShaderProgram = GLScaler::ShaderProgram; GLScalerShaderPixelTest() : scoped_trace_( __FILE__, __LINE__, (testing::Message() << "is_converting_rgb_to_yuv=" << is_converting_rgb_to_yuv() << ", is_swizzling_output=" << is_swizzling_output())) {} bool is_converting_rgb_to_yuv() const { return std::get<0>(GetParam()); } bool is_swizzling_output() const { return std::get<1>(GetParam()); } bool AreMultipleRenderingTargetsSupported() const { return scaler_->GetMaxDrawBuffersSupported() > 1; } // Returns a cached ShaderProgram, maybe configured to convert RGB→YUV and/or // swizzle the 1st and 3rd bytes in the output (depending on GetParams()). ShaderProgram* GetShaderProgram(Shader shader) { std::unique_ptr transform; if (is_converting_rgb_to_yuv()) { transform = gfx::ColorTransform::NewColorTransform( DefaultRGBColorSpace(), DefaultYUVColorSpace(), gfx::ColorTransform::Intent::INTENT_ABSOLUTE); } const GLenum swizzle[2] = { is_swizzling_output() ? GL_BGRA_EXT : GL_RGBA, is_swizzling_output() ? GL_BGRA_EXT : GL_RGBA, }; return scaler_->GetShaderProgram(shader, GL_UNSIGNED_BYTE, transform.get(), swizzle); } GLuint CreateTexture(const gfx::Size& size) { return texture_helper_->CreateTexture(size); } GLuint UploadTexture(const SkBitmap& bitmap) { return texture_helper_->UploadTexture(bitmap); } SkBitmap DownloadTexture(GLuint texture, const gfx::Size& size) { return texture_helper_->DownloadTexture(texture, size); } GLuint RenderToNewTexture(GLuint src_texture, const gfx::Size& size) { return RenderToNewTextures(src_texture, size, false).first; } // Using the current shader program, creates new texture(s) of the given // |size| and draws using |src_texture| as input. If |dual_outputs| is true, // two new textures are created and drawn-to simultaneously; otherwise, only // one is created and drawn-to. The caller does not take ownership of the new // texture(s). std::pair RenderToNewTextures(GLuint src_texture, const gfx::Size& size, bool dual_outputs) { auto dst_textures = std::make_pair( CreateTexture(size), dual_outputs ? CreateTexture(size) : 0u); GLuint framebuffer = 0; gl_->GenFramebuffers(1, &framebuffer); gl_->BindFramebuffer(GL_FRAMEBUFFER, framebuffer); gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_textures.first, 0); if (dual_outputs) { gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + 1, GL_TEXTURE_2D, dst_textures.second, 0); } gl_->BindTexture(GL_TEXTURE_2D, src_texture); gl_->Viewport(0, 0, size.width(), size.height()); const GLenum buffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT0 + 1}; gl_->DrawBuffersEXT(dual_outputs ? 2 : 1, buffers); // Assumption: The |vertex_attributes_buffer_| created in SetUp() is // currently bound to GL_ARRAY_BUFFER. gl_->DrawArrays(GL_TRIANGLE_STRIP, 0, 4); gl_->DeleteFramebuffers(1, &framebuffer); return dst_textures; } // Returns a texture that converts the input |texture| back to unswizzled RGB, // if necessary, depending on GetParam(). The caller does not take ownership // of the returned texture, which could be the same texture as the input // argument in some cases. GLuint ConvertBackToUnswizzledRGB(GLuint texture, const gfx::Size& size) { GLuint result = texture; if (is_swizzling_output()) { const GLenum swizzle[2] = {GL_BGRA_EXT, GL_BGRA_EXT}; scaler_ ->GetShaderProgram(Shader::BILINEAR, GL_UNSIGNED_BYTE, nullptr, swizzle) ->UseProgram(size, gfx::RectF(gfx::Rect(size)), size, Axis::HORIZONTAL, false); result = RenderToNewTexture(result, size); } if (is_converting_rgb_to_yuv()) { const auto transform = gfx::ColorTransform::NewColorTransform( DefaultYUVColorSpace(), DefaultRGBColorSpace(), gfx::ColorTransform::Intent::INTENT_ABSOLUTE); const GLenum swizzle[2] = {GL_RGBA, GL_RGBA}; scaler_ ->GetShaderProgram(Shader::BILINEAR, GL_UNSIGNED_BYTE, transform.get(), swizzle) ->UseProgram(size, gfx::RectF(gfx::Rect(size)), size, Axis::HORIZONTAL, false); result = RenderToNewTexture(result, size); } return result; } // A test case executed by RunSMPTEScalingTestCases(). struct SMPTEScalingTestCase { gfx::Rect src_rect; // Selects a subrect of the source. int scale_from; // Scale ratio denominator. int scale_to; // Scale ratio numerator. int fuzzy_bar_border; // Ignored pixels between color bars, when comparing. }; // Draws with the given shader for each of the provided |test_cases| and adds // gtest failure(s) if the output does not look like a part of the SMPTE test // image. void RunSMPTEScalingTestCases( Shader shader, const std::vector& test_cases) { const SkBitmap source = CreateSMPTETestImage(kBaseSize); const GLuint src_texture = UploadTexture(source); for (const auto& tc : test_cases) { for (int is_horizontal = 0; is_horizontal <= 1; ++is_horizontal) { gfx::Size dst_size = tc.src_rect.size(); Axis axis; if (is_horizontal) { CHECK_EQ((dst_size.width() * tc.scale_to) % tc.scale_from, 0); dst_size.set_width(dst_size.width() * tc.scale_to / tc.scale_from); axis = Axis::HORIZONTAL; } else { CHECK_EQ((dst_size.height() * tc.scale_to) % tc.scale_from, 0); dst_size.set_height(dst_size.height() * tc.scale_to / tc.scale_from); axis = Axis::VERTICAL; } SCOPED_TRACE(testing::Message() << "src_rect=" << tc.src_rect.ToString() << ", scale from→to=" << tc.scale_from << "→" << tc.scale_to << ", dst_size=" << dst_size.ToString()); GetShaderProgram(shader)->UseProgram(kBaseSize, gfx::RectF(tc.src_rect), dst_size, axis, false); const SkBitmap actual = DownloadTexture( ConvertBackToUnswizzledRGB( RenderToNewTexture(src_texture, dst_size), dst_size), dst_size); int max_color_diff = GetMaxAllowedColorDifference(); if (!LooksLikeSMPTETestImage(actual, kBaseSize, tc.src_rect, tc.fuzzy_bar_border, &max_color_diff)) { ADD_FAILURE() << "Scaled image does not look like the correct scaled " "subrect of the SMPTE test image (max diff measured=" << max_color_diff << "):\nActual: " << cc::GetPNGDataUrl(actual); } } } } // Adds test failures if an |actual| image does not match the |expected| // image. When not doing color space conversion, the images must match // exactly; otherwise, some minor differences are allowed. void ExpectAreTheSameImage(const SkBitmap& expected, const SkBitmap& actual) const { const int max_color_diff = GetMaxAllowedColorDifference(); if (!cc::FuzzyPixelComparator(false, 100.0f, 0.0f, max_color_diff, max_color_diff, 0) .Compare(expected, actual)) { ADD_FAILURE() << "Images are not similar enough (max_color_diff=" << max_color_diff << "):\nExpected: " << cc::GetPNGDataUrl(expected) << "\nActual: " << cc::GetPNGDataUrl(actual); } } // Draws with the given shader to downscale a "striped pattern" image by // |downscale_factor| in one dimension only, and adds gtest failure(s) if the // resulting image is not of the |expected_solid_color|. |cycle| specifies the // colors of the stripes, which should average to |expected_solid_color|. // // If the shader program is correct, it should be sampling the texture halfway // between each pair of stripes and then averaging the result. This means that // every N pixels in the source will be averaged to one pixel in the output, // creating a solid color fill as output. If the shader is sampling the // texture at the wrong points, the result will be tinted and/or contain // striping. void RunMultiplePassBilinearTest(Shader shader, int downscale_factor, const std::vector& cycle) { // Compute the expected solid fill color from the colors in |cycle|. uint32_t sum_red = 0; uint32_t sum_green = 0; uint32_t sum_blue = 0; uint32_t sum_alpha = 0; for (SkColor c : cycle) { sum_red += SkColorGetR(c); sum_green += SkColorGetG(c); sum_blue += SkColorGetB(c); sum_alpha += SkColorGetA(c); } const float count = cycle.size(); // Note: Taking the rounded average for each color channel. const SkColor expected_solid_color = SkColorSetARGB(sum_alpha / count + 0.5f, sum_red / count + 0.5f, sum_green / count + 0.5f, sum_blue / count + 0.5f); // Run the test for the vertical direction, and again for the horizontal // direction. const gfx::Rect src_rect = gfx::Rect(0, 0, 10 * downscale_factor, 10 * downscale_factor); for (int is_horizontal = 0; is_horizontal <= 1; ++is_horizontal) { gfx::Size dst_size = src_rect.size(); Axis axis; CyclicalPattern pattern; if (is_horizontal) { dst_size.set_width(dst_size.width() / downscale_factor); axis = Axis::HORIZONTAL; pattern = VERTICAL_STRIPES; } else { dst_size.set_height(dst_size.height() / downscale_factor); axis = Axis::VERTICAL; pattern = HORIZONTAL_STRIPES; } // Create the expected output image consisting of a solid fill color. SkBitmap expected = AllocateRGBABitmap(dst_size); expected.eraseColor(expected_solid_color); // Run the test for each of N possible rotations of the |cycle| of // stripes. for (size_t rotation = 0; rotation < cycle.size(); ++rotation) { SCOPED_TRACE(testing::Message() << "is_horizontal=" << !!is_horizontal << ", rotation=" << rotation << ", expected_solid_color=" << std::hex << expected_solid_color); const SkBitmap source = CreateCyclicalTestImage(src_rect.size(), pattern, cycle, rotation); const GLuint src_texture = UploadTexture(source); // Execute the program, and convert the shader program's drawn result // back to an unswizzled RGB form, and compare that with the expected // image. GetShaderProgram(shader)->UseProgram( src_rect.size(), gfx::RectF(src_rect), dst_size, axis, false); const SkBitmap actual = DownloadTexture( ConvertBackToUnswizzledRGB( RenderToNewTexture(src_texture, dst_size), dst_size), dst_size); ExpectAreTheSameImage(expected, actual); } } } protected: void SetUp() final { cc::PixelTest::SetUpGLWithoutRenderer(false); scaler_ = std::make_unique(context_provider()); gl_ = context_provider()->ContextGL(); CHECK(gl_); // Set up vertex attributes buffer and its data. gl_->GenBuffers(1, &vertex_attributes_buffer_); gl_->BindBuffer(GL_ARRAY_BUFFER, vertex_attributes_buffer_); gl_->BufferData(GL_ARRAY_BUFFER, sizeof(ShaderProgram::kVertexAttributes), ShaderProgram::kVertexAttributes, GL_STATIC_DRAW); texture_helper_ = std::make_unique(gl_); } void TearDown() final { texture_helper_.reset(); if (vertex_attributes_buffer_) { gl_->DeleteBuffers(1, &vertex_attributes_buffer_); vertex_attributes_buffer_ = 0; } gl_ = nullptr; scaler_.reset(); cc::PixelTest::TearDown(); } // Returns the maximum allowed absolute difference between any two color // values in the expected vs actual image comparisons, given the current test // parameters and known platform-specific inaccuracy. int GetMaxAllowedColorDifference() const { #if defined(OS_ANDROID) // Android seems to have texture sampling and/or readback accuracy issues // with these programs that are not at all seen on any of the desktop // platforms. Also, versions before Marshmallow seem to have a much larger // accuracy issues with a few of the programs. Thus, use higher thresholds, // assuming that the programs are correct if they can pass a much lower // threshold on other platforms. if (base::android::BuildInfo::GetInstance()->sdk_int() < base::android::SDK_VERSION_MARSHMALLOW) { return (is_converting_rgb_to_yuv() || is_swizzling_output()) ? 24 : 12; } return (is_converting_rgb_to_yuv() || is_swizzling_output()) ? 4 : 2; #else return (is_converting_rgb_to_yuv() || is_swizzling_output()) ? 2 : 0; #endif } testing::ScopedTrace scoped_trace_; std::unique_ptr scaler_; gpu::gles2::GLES2Interface* gl_ = nullptr; GLuint vertex_attributes_buffer_ = 0; std::unique_ptr texture_helper_; }; // As the BILINEAR shader is used by some of the test helpers, this test is // necessary to ensure the correctness of the tools used by all the other tests. TEST_P(GLScalerShaderPixelTest, ValidateTestHelpers) { // Create/validate a SMPTE color bar test image. const SkBitmap original = CreateSMPTETestImage(kBaseSize); int max_color_diff = GetMaxAllowedColorDifference(); ASSERT_TRUE(LooksLikeSMPTETestImage(original, kBaseSize, gfx::Rect(kBaseSize), 0, &max_color_diff)) << "max diff measured=" << max_color_diff; // Create and upload a test image that has had RGB→YUV conversion performed // and/or had its color channels swizzled, depending on the testing params. SkBitmap image = CreateSMPTETestImage(kBaseSize); if (is_converting_rgb_to_yuv()) { ConvertBitmapToYUV(&image); } if (is_swizzling_output()) { SwizzleBitmap(&image); } const GLuint uploaded_texture = UploadTexture(image); // Use the convert-back helper, which uses the BILINEAR shader to convert the // |uploaded_texture| back to an unswizzled RGB form. Then, download the // result and check whether it matches the original. const gfx::Size size(image.width(), image.height()); const GLuint converted_back_texture = ConvertBackToUnswizzledRGB(uploaded_texture, size); const SkBitmap actual = DownloadTexture(converted_back_texture, size); ExpectAreTheSameImage(original, actual); } // Tests the default, one-pass bilinear shader which can upscale or downscale by // up to 2X. TEST_P(GLScalerShaderPixelTest, Bilinear) { constexpr gfx::Rect whole = gfx::Rect(kBaseSize); constexpr gfx::Rect quadrant = gfx::Rect(kBaseSize.width() / 2, kBaseSize.height() / 2, kBaseSize.width() / 2, kBaseSize.height() / 2); const std::vector kTestCases = { // No scaling. {whole, 1, 1, 0}, // Downscale by half. {whole, 2, 1, 1}, // Upscale by 1.5. {whole, 2, 3, 1}, // No scaling; lower-right quadrant only. {quadrant, 1, 1, 0}, // Downscale by half; lower-right quadrant only. {quadrant, 2, 1, 1}, // Upscale by 1.5; lower-right quadrant only. {quadrant, 2, 3, 1}, }; RunSMPTEScalingTestCases(Shader::BILINEAR, kTestCases); } // Test the 2-tap bilinear shader, which downscales by 4X in one dimension. TEST_P(GLScalerShaderPixelTest, TwoTapBilinear) { RunMultiplePassBilinearTest(Shader::BILINEAR2, 4, {SkColorSetARGB(0xff, 0xff, 0x00, 0x00), SkColorSetARGB(0x7f, 0x00, 0x80, 0x00), SkColorSetARGB(0x7f, 0x00, 0x80, 0x00), SkColorSetARGB(0xff, 0x00, 0x00, 0xff)}); } // Test the 3-tap bilinear shader, which downscales by 6X in one dimension. TEST_P(GLScalerShaderPixelTest, ThreeTapBilinear) { RunMultiplePassBilinearTest(Shader::BILINEAR3, 6, {SkColorSetARGB(0xff, 0xff, 0x00, 0x00), SkColorSetARGB(0xbf, 0x00, 0x80, 0xff), SkColorSetARGB(0x7f, 0x00, 0x80, 0x00), SkColorSetARGB(0x7f, 0x00, 0x80, 0x00), SkColorSetARGB(0xbf, 0xff, 0x80, 0x00), SkColorSetARGB(0xff, 0x00, 0x00, 0xff)}); } // Test the 4-tap bilinear shader, which downscales by 8X in one dimension. TEST_P(GLScalerShaderPixelTest, FourTapBilinear) { RunMultiplePassBilinearTest(Shader::BILINEAR4, 8, {SkColorSetARGB(0xff, 0xff, 0x00, 0x00), SkColorSetARGB(0x7f, 0x00, 0x80, 0x00), SkColorSetARGB(0x7f, 0x00, 0x80, 0x00), SkColorSetARGB(0xff, 0x00, 0x00, 0xff), SkColorSetARGB(0xff, 0xff, 0xff, 0x00), SkColorSetARGB(0x7f, 0x00, 0x80, 0x80), SkColorSetARGB(0x7f, 0x00, 0x80, 0x80), SkColorSetARGB(0xff, 0xff, 0x00, 0xff)}); } // Test the 2-by-2-tap bilinear shader, which downscales by 4X in both // dimensions at the same time. TEST_P(GLScalerShaderPixelTest, TwoByTwoTapBilinear) { RunMultiplePassBilinearTest(Shader::BILINEAR2X2, 4, {SkColorSetARGB(0xff, 0xff, 0x00, 0x00), SkColorSetARGB(0x7f, 0x00, 0x80, 0x00), SkColorSetARGB(0x7f, 0x00, 0x80, 0x00), SkColorSetARGB(0xff, 0x00, 0x00, 0xff)}); } // Tests the bicubic upscaler for a variety of scaling factors between 1X and // 2X, and over the entire source texture versus just its lower-right quadrant. TEST_P(GLScalerShaderPixelTest, BicubicUpscale) { constexpr gfx::Rect whole = gfx::Rect(kBaseSize); constexpr gfx::Rect quadrant = gfx::Rect(kBaseSize.width() / 2, kBaseSize.height() / 2, kBaseSize.width() / 2, kBaseSize.height() / 2); const std::vector kTestCases = { // No scaling. {whole, 1, 1, 0}, // Upscale by 4/3. {whole, 3, 4, 3}, // Upscale by 3/2. {whole, 2, 3, 3}, // Upscale by 2X. {whole, 1, 2, 3}, // No scaling; lower-right quadrant only. {quadrant, 1, 1, 0}, // Upscale by 4/3. {quadrant, 3, 4, 3}, // Upscale by 3/2. {quadrant, 2, 3, 3}, // Upscale by 2X. {quadrant, 1, 2, 3}, }; RunSMPTEScalingTestCases(Shader::BICUBIC_UPSCALE, kTestCases); } // Tests the bicubic half-downscaler, both over an entire source texture and // over just its lower-right quadrant. TEST_P(GLScalerShaderPixelTest, BicubicDownscaleByHalf) { constexpr gfx::Rect whole = gfx::Rect(kBaseSize); constexpr gfx::Rect quadrant = gfx::Rect(kBaseSize.width() / 2, kBaseSize.height() / 2, kBaseSize.width() / 2, kBaseSize.height() / 2); const std::vector kTestCases = { // Downscale by half. {whole, 2, 1, 2}, // Downscale by half; lower-right quadrant only. {quadrant, 2, 1, 2}, }; RunSMPTEScalingTestCases(Shader::BICUBIC_HALF_1D, kTestCases); } // Tests the shaders that read a normal 4-channel interleaved texture and // produce a planar texture consisting of just one color channel, packed into // RGBA quads. TEST_P(GLScalerShaderPixelTest, Export_Planar) { const std::vector kCycle = {SkColorSetARGB(0xff, 0xff, 0x00, 0x00), SkColorSetARGB(0x80, 0x00, 0x80, 0x00), SkColorSetARGB(0x80, 0x00, 0x80, 0x00), SkColorSetARGB(0xff, 0x00, 0x00, 0xff)}; SkBitmap source = CreateCyclicalTestImage(kBaseSize, STAGGERED, kCycle, 0); const GLuint src_texture = UploadTexture(source); // For each channel, create an expected bitmap and compare it to the result // from drawing with the shader program. if (is_converting_rgb_to_yuv()) { ConvertBitmapToYUV(&source); } for (int channel = 0; channel <= 3; ++channel) { SkBitmap expected = CreatePackedPlanarBitmap(source, channel); if (is_swizzling_output()) { SwizzleBitmap(&expected); } const Shader shader = static_cast( static_cast(Shader::PLANAR_CHANNEL_0) + channel); const gfx::Size dst_size(kBaseSize.width() / 4, kBaseSize.height()); GetShaderProgram(shader)->UseProgram(kBaseSize, gfx::RectF(gfx::Rect(kBaseSize)), dst_size, Axis::HORIZONTAL, false); const SkBitmap actual = DownloadTexture(RenderToNewTexture(src_texture, dst_size), dst_size); ExpectAreTheSameImage(expected, actual); } } // Tests that the I422/NV61 formatter shader program produces a planar texture // and an interleaved half-width texture from a normal 4-channel interleaved // texture. See gl_shader.h for more specifics. TEST_P(GLScalerShaderPixelTest, Export_I422_NV61) { if (!AreMultipleRenderingTargetsSupported()) { LOG(WARNING) << "Skipping test due to lack of MRT support on this machine."; return; } // Use a vertical stripes source image/texture to test that the shader is // sampling the texture at the correct points and performing // downscale-blending in the second texture. const std::vector kCycle = {SkColorSetARGB(0xff, 0xff, 0x00, 0x00), SkColorSetARGB(0x80, 0x00, 0x80, 0x00), SkColorSetARGB(0x80, 0x00, 0x80, 0x00), SkColorSetARGB(0xff, 0x00, 0x00, 0xff)}; SkBitmap source = CreateCyclicalTestImage(kBaseSize, VERTICAL_STRIPES, kCycle, 0); const GLuint src_texture = UploadTexture(source); // Create the expected output images: The first (A) is simply the first color // channel of the source packed into a planar format. The second (BC) consists // of the second and third color channels downscaled by half and interleaved. // The following can be considered a reference implementation for what the // shader program is supposed to do. if (is_converting_rgb_to_yuv()) { ConvertBitmapToYUV(&source); } SkBitmap expected_a = CreatePackedPlanarBitmap(source, 0); if (is_swizzling_output()) { SwizzleBitmap(&expected_a); } const gfx::Size dst_size(expected_a.width(), expected_a.height()); SkBitmap expected_bc = AllocateRGBABitmap(dst_size); for (int y = 0; y < dst_size.height(); ++y) { const uint32_t* const src = source.getAddr32(0, y); uint32_t* const dst_bc = expected_bc.getAddr32(0, y); for (int x = 0; x < dst_size.width(); ++x) { // (src[0..3]) (dst_bc) // RGBA RGBA rgba rgba --> GBgb (e.g, two G's blended into one G) const uint32_t g01 = ((((src[x * 4 + 0] >> kGreenShift) & 0xff) + ((src[x * 4 + 1] >> kGreenShift) & 0xff)) / 2.f + 0.5f); const uint32_t b01 = ((((src[x * 4 + 0] >> kBlueShift) & 0xff) + ((src[x * 4 + 1] >> kBlueShift) & 0xff)) / 2.f + 0.5f); const uint32_t g23 = ((((src[x * 4 + 2] >> kGreenShift) & 0xff) + ((src[x * 4 + 3] >> kGreenShift) & 0xff)) / 2.f + 0.5f); const uint32_t b23 = ((((src[x * 4 + 2] >> kBlueShift) & 0xff) + ((src[x * 4 + 3] >> kBlueShift) & 0xff)) / 2.f + 0.5f); dst_bc[x] = ((g01 << kRedShift) | (b01 << kGreenShift) | (g23 << kBlueShift) | (b23 << kAlphaShift)); } } if (is_swizzling_output()) { SwizzleBitmap(&expected_bc); } // Execute the program, and compare the shader program's drawn result with the // expected images. GetShaderProgram(Shader::I422_NV61_MRT) ->UseProgram(kBaseSize, gfx::RectF(gfx::Rect(kBaseSize)), dst_size, Axis::HORIZONTAL, false); const auto textures = RenderToNewTextures(src_texture, dst_size, true); const SkBitmap actual_a = DownloadTexture(textures.first, dst_size); ExpectAreTheSameImage(expected_a, actual_a); const SkBitmap actual_bc = DownloadTexture(textures.second, dst_size); ExpectAreTheSameImage(expected_bc, actual_bc); } // Tests the pairwise-deinterleave shader program that produces two planar // textures from a single interleaved one. TEST_P(GLScalerShaderPixelTest, Export_PairwiseDeinterleave) { if (!AreMultipleRenderingTargetsSupported()) { LOG(WARNING) << "Skipping test due to lack of MRT support on this machine."; return; } // This shader does not provide color space conversion. It is just a // demultiplexer/repackager. if (is_converting_rgb_to_yuv()) { return; } // Create a source image/texture with a pattern suitable for ensuring the // shader is sampling the texture at the correct points. const std::vector kCycle = {SkColorSetARGB(0xff, 0xff, 0x00, 0x00), SkColorSetARGB(0xc0, 0x00, 0xc0, 0x00), SkColorSetARGB(0x80, 0x00, 0x00, 0x80), SkColorSetARGB(0xff, 0xff, 0xff, 0xff)}; const SkBitmap source = CreateCyclicalTestImage(kBaseSize, STAGGERED, kCycle, 0); const GLuint src_texture = UploadTexture(source); // Create the expected pair of planar images. const gfx::Size dst_size(kBaseSize.width() / 2, kBaseSize.height()); SkBitmap expected_a = AllocateRGBABitmap(dst_size); SkBitmap expected_b = AllocateRGBABitmap(dst_size); for (int y = 0; y < dst_size.height(); ++y) { const uint32_t* const src = source.getAddr32(0, y); uint32_t* const dst_a = expected_a.getAddr32(0, y); uint32_t* const dst_b = expected_b.getAddr32(0, y); for (int x = 0; x < dst_size.width(); ++x) { // (src) (dst_a) (dst_b) // ABAB abab --> { AAaa + BBbb } dst_a[x] = ((((src[x * 2 + 0] >> kRedShift) & 0xff) << kRedShift) | (((src[x * 2 + 0] >> kBlueShift) & 0xff) << kGreenShift) | (((src[x * 2 + 1] >> kRedShift) & 0xff) << kBlueShift) | (((src[x * 2 + 1] >> kBlueShift) & 0xff) << kAlphaShift)); dst_b[x] = ((((src[x * 2 + 0] >> kGreenShift) & 0xff) << kRedShift) | (((src[x * 2 + 0] >> kAlphaShift) & 0xff) << kGreenShift) | (((src[x * 2 + 1] >> kGreenShift) & 0xff) << kBlueShift) | (((src[x * 2 + 1] >> kAlphaShift) & 0xff) << kAlphaShift)); } } if (is_swizzling_output()) { SwizzleBitmap(&expected_a); SwizzleBitmap(&expected_b); } // Execute the program, and compare the shader program's drawn result with the // expected images. GetShaderProgram(Shader::DEINTERLEAVE_PAIRWISE_MRT) ->UseProgram(kBaseSize, gfx::RectF(gfx::Rect(kBaseSize)), dst_size, Axis::HORIZONTAL, false); const auto textures = RenderToNewTextures(src_texture, dst_size, true); const SkBitmap actual_a = DownloadTexture(textures.first, dst_size); ExpectAreTheSameImage(expected_a, actual_a); const SkBitmap actual_b = DownloadTexture(textures.second, dst_size); ExpectAreTheSameImage(expected_b, actual_b); } INSTANTIATE_TEST_CASE_P(, GLScalerShaderPixelTest, testing::Combine(testing::Bool(), testing::Bool())); } // namespace viz