blob: 1d414d53cbab687e2a5ce08c17b1ab16c393e75a [file] [log] [blame]
// 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 <sstream>
#include <tuple>
#include <vector>
#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<std::tuple<bool, bool>>,
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<gfx::ColorTransform> 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<GLuint, GLuint> RenderToNewTextures(GLuint src_texture,
const gfx::Size& size,
bool dual_outputs) {
auto dst_textures = std::make_pair<GLuint, GLuint>(
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<SMPTEScalingTestCase>& 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<SkColor>& 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<GLScaler>(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<GLScalerTestTextureHelper>(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
}
bool IsAndroidMarshmallow() {
#if defined(OS_ANDROID)
return base::android::BuildInfo::GetInstance()->sdk_int() ==
base::android::SDK_VERSION_MARSHMALLOW;
#else
return false;
#endif
}
testing::ScopedTrace scoped_trace_;
std::unique_ptr<GLScaler> scaler_;
gpu::gles2::GLES2Interface* gl_ = nullptr;
GLuint vertex_attributes_buffer_ = 0;
std::unique_ptr<GLScalerTestTextureHelper> 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) {
// Disabled on Marshmallow. See crbug.com/933080
if (IsAndroidMarshmallow())
return;
// 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) {
// Disabled on Marshmallow. See crbug.com/933080
if (IsAndroidMarshmallow())
return;
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<SMPTEScalingTestCase> 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) {
// Disabled on Marshmallow. See crbug.com/933080
if (IsAndroidMarshmallow())
return;
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) {
// Disabled on Marshmallow. See crbug.com/933080
if (IsAndroidMarshmallow())
return;
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<SMPTEScalingTestCase> 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<SMPTEScalingTestCase> 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) {
// Disabled on Marshmallow. See crbug.com/933080
if (IsAndroidMarshmallow())
return;
const std::vector<SkColor> 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<Shader>(
static_cast<int>(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<SkColor> 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<SkColor> 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_SUITE_P(,
GLScalerShaderPixelTest,
testing::Combine(testing::Bool(), testing::Bool()));
} // namespace viz