Commit f26763fc authored by Yuri Wiitala's avatar Yuri Wiitala Committed by Commit Bot

GLScaler: Build ScalerStage chains and add pixel tests.

Adds the implementation that determines which shader programs to use,
and in what order, to execute scaling and format conversion. Also, adds
pixel tests to check all the various features provided by GLScaler (via
its Parameters).

Bug: 870036
Change-Id: I7cb1d64d6df79a3a6221c2ab6cdf4b504b26c5b1
Reviewed-on: https://chromium-review.googlesource.com/c/1298737
Commit-Queue: Yuri Wiitala <miu@chromium.org>
Reviewed-by: default avatarXiangjun Zhang <xjz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#603762}
parent fc94bd1e
...@@ -221,6 +221,7 @@ viz_source_set("unit_tests") { ...@@ -221,6 +221,7 @@ viz_source_set("unit_tests") {
"frame_sinks/delay_based_time_source_unittest.cc", "frame_sinks/delay_based_time_source_unittest.cc",
"gl_helper_unittest.cc", "gl_helper_unittest.cc",
"gl_scaler_overscan_pixeltest.cc", "gl_scaler_overscan_pixeltest.cc",
"gl_scaler_pixeltest.cc",
"gl_scaler_shader_pixeltest.cc", "gl_scaler_shader_pixeltest.cc",
"gl_scaler_test_util.cc", "gl_scaler_test_util.cc",
"gl_scaler_test_util.h", "gl_scaler_test_util.h",
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "components/viz/common/gl_scaler.h" #include "components/viz/common/gl_scaler.h"
#include <algorithm>
#include <array>
#include <sstream> #include <sstream>
#include <string> #include <string>
...@@ -16,6 +18,30 @@ ...@@ -16,6 +18,30 @@
namespace viz { namespace viz {
namespace {
// The code in GLScaler that computes the ScalerStages is greatly simplified by
// being able to access the X and Y components by index (instead of
// Vector2d::x() or Vector2d::y()). Thus, define a helper class to represent the
// relative size as a 2-element std::array and convert to/from Vector2d.
struct RelativeSize : public std::array<int, 2> {
using std::array<int, 2>::operator[];
RelativeSize(int width, int height) : std::array<int, 2>{{width, height}} {}
explicit RelativeSize(const gfx::Vector2d& v)
: std::array<int, 2>{{v.x(), v.y()}} {}
gfx::Vector2d AsVector2d() const {
return gfx::Vector2d((*this)[0], (*this)[1]);
}
};
std::ostream& operator<<(std::ostream& out, const RelativeSize& size) {
return (out << size[0] << 'x' << size[1]);
}
} // namespace
GLScaler::GLScaler(scoped_refptr<ContextProvider> context_provider) GLScaler::GLScaler(scoped_refptr<ContextProvider> context_provider)
: context_provider_(std::move(context_provider)) { : context_provider_(std::move(context_provider)) {
if (context_provider_) { if (context_provider_) {
...@@ -131,8 +157,74 @@ bool GLScaler::Configure(const Parameters& new_params) { ...@@ -131,8 +157,74 @@ bool GLScaler::Configure(const Parameters& new_params) {
} }
} }
// TODO(crbug.com/870036): Build ScalerStage chain (upcoming CL). // Create the chain of ScalerStages. If the quality setting is FAST or there
// is no scaling to be done, just create a single stage.
std::unique_ptr<ScalerStage> chain;
if (params_.quality == Parameters::Quality::FAST ||
params_.scale_from == params_.scale_to) {
chain = std::make_unique<ScalerStage>(gl, Shader::BILINEAR, HORIZONTAL,
params_.scale_from, params_.scale_to);
} else if (params_.quality == Parameters::Quality::GOOD) {
chain = CreateAGoodScalingChain(gl, params_.scale_from, params_.scale_to);
} else if (params_.quality == Parameters::Quality::BEST) {
chain = CreateTheBestScalingChain(gl, params_.scale_from, params_.scale_to);
} else {
NOTREACHED();
}
chain = MaybeAppendExportStage(gl, std::move(chain), params_.export_format);
// TODO(crbug.com/870036): Add support for color management (uses half-float
// textures).
scaling_color_space_ = params_.source_color_space;
const GLenum intermediate_texture_type = GL_UNSIGNED_BYTE;
// Set the shader program on the final stage. Include color space
// transformation and swizzling, if necessary.
std::unique_ptr<gfx::ColorTransform> transform;
if (scaling_color_space_ != params_.output_color_space) {
transform = gfx::ColorTransform::NewColorTransform(
scaling_color_space_, params_.output_color_space,
gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
if (!transform->CanGetShaderSource()) {
NOTIMPLEMENTED() << "color transform from "
<< scaling_color_space_.ToString() << " to "
<< params_.output_color_space.ToString();
return false;
}
}
ScalerStage* const final_stage = chain.get();
final_stage->set_shader_program(
GetShaderProgram(final_stage->shader(), intermediate_texture_type,
transform.get(), params_.swizzle));
// Set the shader program on all prior stages. These stages are all operating
// in the same color space, |scaling_color_space_|.
static const GLenum kNoSwizzle[2] = {GL_RGBA, GL_RGBA};
ScalerStage* input_stage = final_stage;
while (input_stage->input_stage()) {
input_stage = input_stage->input_stage();
input_stage->set_shader_program(GetShaderProgram(
input_stage->shader(), intermediate_texture_type, nullptr, kNoSwizzle));
}
// From this point, |input_stage| points to the first ScalerStage (i.e., the
// one that will be reading from the source).
// If the source content is Y-flipped, the input scaler stage will perform
// math to account for this. It also will flip the content during scaling so
// that all following stages may assume the content is not flipped. Then, the
// final stage must ensure the final output is correctly flipped-back (or not)
// based on what the first stage did PLUS what is being requested by the
// client code.
if (params_.is_flipped_source) {
input_stage->set_is_flipped_source(true);
input_stage->set_flip_output(true);
}
if (input_stage->flip_output() != params_.flip_output) {
final_stage->set_flip_output(!final_stage->flip_output());
}
chain_ = std::move(chain);
VLOG(2) << __func__ << " built this: " << *this;
return true; return true;
} }
...@@ -243,6 +335,287 @@ GLScaler::ShaderProgram* GLScaler::GetShaderProgram( ...@@ -243,6 +335,287 @@ GLScaler::ShaderProgram* GLScaler::GetShaderProgram(
return &it->second; return &it->second;
} }
// static
std::unique_ptr<GLScaler::ScalerStage> GLScaler::CreateAGoodScalingChain(
gpu::gles2::GLES2Interface* gl,
const gfx::Vector2d& scale_from,
const gfx::Vector2d& scale_to) {
DCHECK(scale_from.x() != 0 && scale_from.y() != 0)
<< "Bad scale_from: " << scale_from.ToString();
DCHECK(scale_to.x() != 0 && scale_to.y() != 0)
<< "Bad scale_to: " << scale_to.ToString();
DCHECK(scale_from != scale_to);
// The GOOD quality chain performs one bilinear upscale followed by N bilinear
// halvings, and does this is both directions. Exception: No upscale is needed
// when |scale_from| is a power of two multiple of |scale_to|.
//
// Since all shaders use bilinear filtering, the heuristics below attempt to
// greedily merge steps wherever possible to minimize GPU memory usage and
// processing time. This also means that it will be extremely rare for the
// stage doing the initial upscale to actually require a larger output texture
// than the source texture (a downscale will be merged into the same stage).
// Determine the initial upscaled-to size, as the minimum number of doublings
// to make |scale_to| greater than |scale_from|.
const RelativeSize from(scale_from);
const RelativeSize to(scale_to);
RelativeSize upscale_to = to;
for (Axis x_or_y : std::array<Axis, 2>{HORIZONTAL, VERTICAL}) {
while (upscale_to[x_or_y] < from[x_or_y]) {
upscale_to[x_or_y] *= 2;
}
}
// Create the stages in order from first-to-last, taking the greediest path
// each time. Something like an A* algorithm would be better for discovering
// an optimal sequence of operations, and would allow using the BILINEAR3
// shader as well, but the run-time performance to compute the stages would be
// too prohibitive.
std::unique_ptr<ScalerStage> chain;
struct CandidateOp {
Shader shader;
Axis primary_axis;
RelativeSize output_size;
};
std::vector<CandidateOp> candidates;
for (RelativeSize cur = from; cur != to;
cur = RelativeSize(chain->scale_to())) {
candidates.clear();
// Determine whether it's possible to do exactly 2 bilinear passes in both
// directions.
RelativeSize output_size_2x2 = {0, 0};
for (Axis x_or_y : std::array<Axis, 2>{VERTICAL, HORIZONTAL}) {
if (cur[x_or_y] == from[x_or_y]) {
// For the first stage, the 2 bilinear passes must be the initial
// upscale followed by one downscale. If there is no initial upscale,
// then the 2 passes must both be downscales.
if (upscale_to[x_or_y] != from[x_or_y] &&
upscale_to[x_or_y] / 2 >= to[x_or_y]) {
output_size_2x2[x_or_y] = upscale_to[x_or_y] / 2;
} else if (upscale_to[x_or_y] == from[x_or_y] &&
upscale_to[x_or_y] / 4 >= to[x_or_y]) {
output_size_2x2[x_or_y] = cur[x_or_y] / 4;
}
} else {
// For all later stages, the 2 bilinear passes must be 2 halvings.
if (cur[x_or_y] / 4 >= to[x_or_y]) {
output_size_2x2[x_or_y] = cur[x_or_y] / 4;
}
}
}
if (output_size_2x2[HORIZONTAL] != 0 && output_size_2x2[VERTICAL] != 0) {
candidates.push_back(
CandidateOp{Shader::BILINEAR2X2, HORIZONTAL, output_size_2x2});
}
// Determine the valid set of Ops that do 1 to 3 bilinear passes in one
// direction and 0 or 1 pass in the other direction.
for (Axis x_or_y : std::array<Axis, 2>{VERTICAL, HORIZONTAL}) {
// The first bilinear pass in x_or_y must be an upscale or a halving.
Shader shader = Shader::BILINEAR;
RelativeSize output_size = cur;
if (cur[x_or_y] == from[x_or_y] && upscale_to[x_or_y] != from[x_or_y]) {
output_size[x_or_y] = upscale_to[x_or_y];
} else if (cur[x_or_y] / 2 >= to[x_or_y]) {
output_size[x_or_y] /= 2;
} else {
DCHECK_EQ(cur[x_or_y], to[x_or_y]);
continue;
}
// Determine whether 1 or 2 additional passes can be made in the same
// direction.
if (output_size[x_or_y] / 4 >= to[x_or_y]) {
shader = Shader::BILINEAR4; // 2 more passes == 3 total.
output_size[x_or_y] /= 4;
} else if (output_size[x_or_y] / 2 >= to[x_or_y]) {
shader = Shader::BILINEAR2; // 1 more pass == 2 total.
output_size[x_or_y] /= 2;
} else {
DCHECK_EQ(output_size[x_or_y], to[x_or_y]);
}
// Determine whether 0 or 1 bilinear passes can be made in the other
// direction at the same time.
const Axis y_or_x = TheOtherAxis(x_or_y);
if (cur[y_or_x] == from[y_or_x] && upscale_to[y_or_x] != from[y_or_x]) {
output_size[y_or_x] = upscale_to[y_or_x];
} else if (cur[y_or_x] / 2 >= to[y_or_x]) {
output_size[y_or_x] /= 2;
} else {
DCHECK_EQ(cur[y_or_x], to[y_or_x]);
}
candidates.push_back(CandidateOp{shader, x_or_y, output_size});
}
// From the candidates, pick the one that produces the fewest number of
// output pixels, and append a new ScalerStage. There are opportunities to
// improve the "cost function" here (e.g., pixels in the Y direction
// probably cost more to process than pixels in the X direction), but that
// would require more research.
const auto best_candidate = std::min_element(
candidates.begin(), candidates.end(),
[](const CandidateOp& a, const CandidateOp& b) {
static_assert(sizeof(a.output_size[0]) <= sizeof(int32_t),
"Overflow issue in the math here.");
const int64_t cost_of_a =
int64_t{a.output_size[HORIZONTAL]} * a.output_size[VERTICAL];
const int64_t cost_of_b =
int64_t{b.output_size[HORIZONTAL]} * b.output_size[VERTICAL];
return cost_of_a < cost_of_b;
});
DCHECK(best_candidate != candidates.end());
DCHECK(cur != best_candidate->output_size)
<< "Best candidate's output size (" << best_candidate->output_size
<< ") should not equal the input size.";
auto next_stage = std::make_unique<ScalerStage>(
gl, best_candidate->shader, best_candidate->primary_axis,
cur.AsVector2d(), best_candidate->output_size.AsVector2d());
next_stage->set_input_stage(std::move(chain));
chain = std::move(next_stage);
}
return chain;
}
// static
std::unique_ptr<GLScaler::ScalerStage> GLScaler::CreateTheBestScalingChain(
gpu::gles2::GLES2Interface* gl,
const gfx::Vector2d& scale_from,
const gfx::Vector2d& scale_to) {
// The BEST quality chain performs one bicubic upscale followed by N bicubic
// halvings, and does this is both directions. Exception: No upscale is needed
// when |scale_from| is a power of two multiple of |scale_to|.
// Determine the initial upscaled-to size, as the minimum number of doublings
// to make |scale_to| greater than |scale_from|.
const RelativeSize from(scale_from);
const RelativeSize to(scale_to);
RelativeSize upscale_to = to;
for (Axis x_or_y : std::array<Axis, 2>{HORIZONTAL, VERTICAL}) {
while (upscale_to[x_or_y] < from[x_or_y]) {
upscale_to[x_or_y] *= 2;
}
}
// Create the stages in order from first-to-last.
RelativeSize cur = from;
std::unique_ptr<ScalerStage> chain;
for (Axis x_or_y : std::array<Axis, 2>{VERTICAL, HORIZONTAL}) {
if (upscale_to[x_or_y] != from[x_or_y]) {
RelativeSize next = cur;
next[x_or_y] = upscale_to[x_or_y];
auto upscale_stage =
std::make_unique<ScalerStage>(gl, Shader::BICUBIC_UPSCALE, x_or_y,
cur.AsVector2d(), next.AsVector2d());
upscale_stage->set_input_stage(std::move(chain));
chain = std::move(upscale_stage);
cur = next;
}
while (cur[x_or_y] > to[x_or_y]) {
RelativeSize next = cur;
next[x_or_y] /= 2;
auto next_stage =
std::make_unique<ScalerStage>(gl, Shader::BICUBIC_HALF_1D, x_or_y,
cur.AsVector2d(), next.AsVector2d());
next_stage->set_input_stage(std::move(chain));
chain = std::move(next_stage);
cur = next;
}
}
DCHECK_EQ(cur, to);
return chain;
}
// static
std::unique_ptr<GLScaler::ScalerStage> GLScaler::MaybeAppendExportStage(
gpu::gles2::GLES2Interface* gl,
std::unique_ptr<GLScaler::ScalerStage> chain,
GLScaler::Parameters::ExportFormat export_format) {
DCHECK(chain);
if (export_format == Parameters::ExportFormat::INTERLEAVED_QUADS) {
return chain; // No format change.
}
// If the final stage uses the BILINEAR shader that is not upscaling, the
// export stage can replace it with no change in the results. Otherwise, a
// separate export stage will be appended.
gfx::Vector2d scale_from = chain->scale_from();
const gfx::Vector2d scale_to = chain->scale_to();
if (chain->shader() == Shader::BILINEAR && scale_from.x() >= scale_to.x() &&
scale_from.y() >= scale_to.y()) {
chain = chain->take_input_stage();
} else {
scale_from = scale_to;
}
Shader shader = Shader::BILINEAR;
scale_from.set_x(scale_from.x() * 4);
switch (export_format) {
case Parameters::ExportFormat::INTERLEAVED_QUADS:
NOTREACHED();
break;
case Parameters::ExportFormat::CHANNEL_0:
shader = Shader::PLANAR_CHANNEL_0;
break;
case Parameters::ExportFormat::CHANNEL_1:
shader = Shader::PLANAR_CHANNEL_1;
break;
case Parameters::ExportFormat::CHANNEL_2:
shader = Shader::PLANAR_CHANNEL_2;
break;
case Parameters::ExportFormat::CHANNEL_3:
shader = Shader::PLANAR_CHANNEL_3;
break;
case Parameters::ExportFormat::NV61:
shader = Shader::I422_NV61_MRT;
break;
case Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE:
shader = Shader::DEINTERLEAVE_PAIRWISE_MRT;
// Horizontal scale is only 0.5X, not 0.25X like all the others.
scale_from.set_x(scale_from.x() / 2);
break;
}
auto export_stage = std::make_unique<ScalerStage>(gl, shader, HORIZONTAL,
scale_from, scale_to);
export_stage->set_input_stage(std::move(chain));
return export_stage;
}
// static
GLScaler::Axis GLScaler::TheOtherAxis(GLScaler::Axis x_or_y) {
return x_or_y == HORIZONTAL ? VERTICAL : HORIZONTAL;
}
// static
const char* GLScaler::GetShaderName(GLScaler::Shader shader) {
switch (shader) {
#define CASE_RETURN_SHADER_STR(x) \
case Shader::x: \
return #x
CASE_RETURN_SHADER_STR(BILINEAR);
CASE_RETURN_SHADER_STR(BILINEAR2);
CASE_RETURN_SHADER_STR(BILINEAR3);
CASE_RETURN_SHADER_STR(BILINEAR4);
CASE_RETURN_SHADER_STR(BILINEAR2X2);
CASE_RETURN_SHADER_STR(BICUBIC_UPSCALE);
CASE_RETURN_SHADER_STR(BICUBIC_HALF_1D);
CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_0);
CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_1);
CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_2);
CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_3);
CASE_RETURN_SHADER_STR(I422_NV61_MRT);
CASE_RETURN_SHADER_STR(DEINTERLEAVE_PAIRWISE_MRT);
#undef CASE_RETURN_SHADER_STR
}
}
// static // static
bool GLScaler::AreAllGLExtensionsPresent( bool GLScaler::AreAllGLExtensionsPresent(
gpu::gles2::GLES2Interface* gl, gpu::gles2::GLES2Interface* gl,
...@@ -1079,16 +1452,18 @@ gfx::Rect GLScaler::ScalerStage::ToInputRect(gfx::RectF source_rect) const { ...@@ -1079,16 +1452,18 @@ gfx::Rect GLScaler::ScalerStage::ToInputRect(gfx::RectF source_rect) const {
case Shader::PLANAR_CHANNEL_2: case Shader::PLANAR_CHANNEL_2:
case Shader::PLANAR_CHANNEL_3: case Shader::PLANAR_CHANNEL_3:
case Shader::I422_NV61_MRT: case Shader::I422_NV61_MRT:
// All of these sample exactly 4x1 source pixels to produce each output // All of these sample 4x1 source pixels to produce each output "pixel."
// "pixel." There is no overscan. // There is no overscan. They can also be combined with a bilinear
DCHECK_EQ(scale_from_.x(), 4 * scale_to_.x()); // downscale, but not an upscale.
DCHECK_GE(scale_from_.x(), 4 * scale_to_.x());
DCHECK_EQ(HORIZONTAL, primary_axis_); DCHECK_EQ(HORIZONTAL, primary_axis_);
break; break;
case Shader::DEINTERLEAVE_PAIRWISE_MRT: case Shader::DEINTERLEAVE_PAIRWISE_MRT:
// This shader samples exactly 2x1 source pixels to produce each output // This shader samples 2x1 source pixels to produce each output "pixel."
// "pixel." There is no overscan. // There is no overscan. It can also be combined with a bilinear
DCHECK_EQ(scale_from_.x(), 2 * scale_to_.x()); // downscale, but not an upscale.
DCHECK_GE(scale_from_.x(), 2 * scale_to_.x());
DCHECK_EQ(HORIZONTAL, primary_axis_); DCHECK_EQ(HORIZONTAL, primary_axis_);
break; break;
} }
...@@ -1113,4 +1488,52 @@ void GLScaler::ScalerStage::EnsureIntermediateTextureDefined( ...@@ -1113,4 +1488,52 @@ void GLScaler::ScalerStage::EnsureIntermediateTextureDefined(
} }
} }
std::ostream& operator<<(std::ostream& out, const GLScaler& scaler) {
if (!scaler.chain_) {
return (out << "[GLScaler NOT configured]");
}
out << "Output";
const GLScaler::ScalerStage* const final_stage = scaler.chain_.get();
for (auto* stage = final_stage; stage; stage = stage->input_stage()) {
out << u8" ← {" << GLScaler::GetShaderName(stage->shader());
if (stage->shader_program()) {
switch (stage->shader_program()->texture_type()) {
case GL_FLOAT:
out << "/highp";
break;
case GL_HALF_FLOAT_OES:
out << "/mediump";
break;
default:
out << "/lowp";
break;
}
}
if (stage->flip_output()) {
out << "+flip_y";
}
if (stage->scale_from() == stage->scale_to()) {
out << " copy";
} else {
out << ' ' << stage->scale_from().ToString() << " to "
<< stage->scale_to().ToString();
}
if (stage == final_stage) {
if (scaler.params_.output_color_space != scaler.scaling_color_space_) {
out << ", with color x-form to "
<< scaler.params_.output_color_space.ToString();
}
for (int i = 0; i < 2; ++i) {
if (scaler.params_.swizzle[i] != GL_RGBA) {
out << ", with swizzle(" << i << ')';
}
}
}
out << '}';
}
out << u8" ← Source";
return out;
}
} // namespace viz } // namespace viz
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <ostream>
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <utility> #include <utility>
...@@ -260,10 +261,12 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver { ...@@ -260,10 +261,12 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
private: private:
friend class GLScalerOverscanPixelTest; friend class GLScalerOverscanPixelTest;
friend class GLScalerShaderPixelTest; friend class GLScalerShaderPixelTest;
friend VIZ_COMMON_EXPORT std::ostream& operator<<(std::ostream&,
const GLScaler&);
using GLES2Interface = gpu::gles2::GLES2Interface; using GLES2Interface = gpu::gles2::GLES2Interface;
enum Axis { HORIZONTAL, VERTICAL }; enum Axis { HORIZONTAL = 0, VERTICAL = 1 };
// The shaders used by each stage in the scaling pipeline. // The shaders used by each stage in the scaling pipeline.
enum class Shader : int8_t { enum class Shader : int8_t {
...@@ -370,7 +373,11 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver { ...@@ -370,7 +373,11 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
void set_input_stage(std::unique_ptr<ScalerStage> stage) { void set_input_stage(std::unique_ptr<ScalerStage> stage) {
input_stage_ = std::move(stage); input_stage_ = std::move(stage);
} }
std::unique_ptr<ScalerStage> take_input_stage() {
return std::move(input_stage_);
}
ShaderProgram* shader_program() const { return program_; }
void set_shader_program(ShaderProgram* program) { program_ = program; } void set_shader_program(ShaderProgram* program) { program_ = program; }
bool is_flipped_source() const { return is_flipped_source_; } bool is_flipped_source() const { return is_flipped_source_; }
...@@ -428,6 +435,32 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver { ...@@ -428,6 +435,32 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
const gfx::ColorTransform* color_transform, const gfx::ColorTransform* color_transform,
const GLenum swizzle[2]); const GLenum swizzle[2]);
// Create a scaling chain using the bilinear shaders.
static std::unique_ptr<ScalerStage> CreateAGoodScalingChain(
gpu::gles2::GLES2Interface* gl,
const gfx::Vector2d& scale_from,
const gfx::Vector2d& scale_to);
// Create a scaling chain using the bicubic shaders.
static std::unique_ptr<ScalerStage> CreateTheBestScalingChain(
gpu::gles2::GLES2Interface* gl,
const gfx::Vector2d& scale_from,
const gfx::Vector2d& scale_to);
// Modifies |chain| by appending an export stage, to rearrange the image data
// according to the requested |export_format|. In some cases, this will delete
// the final stage in |chain| before appending the export stage.
static std::unique_ptr<ScalerStage> MaybeAppendExportStage(
gpu::gles2::GLES2Interface* gl,
std::unique_ptr<ScalerStage> chain,
Parameters::ExportFormat export_format);
// Returns the other of the two axes.
static Axis TheOtherAxis(Axis axis);
// Returns the name of the |shader| in string form, for logging purposes.
static const char* GetShaderName(Shader shader);
// Returns true if the given |gl| context mentions all of |names| in its // Returns true if the given |gl| context mentions all of |names| in its
// extensions string. // extensions string.
static bool AreAllGLExtensionsPresent(gpu::gles2::GLES2Interface* gl, static bool AreAllGLExtensionsPresent(gpu::gles2::GLES2Interface* gl,
...@@ -463,9 +496,16 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver { ...@@ -463,9 +496,16 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
// The chain of ScalerStages. // The chain of ScalerStages.
std::unique_ptr<ScalerStage> chain_; std::unique_ptr<ScalerStage> chain_;
// The color space in which the scaling stages operate.
gfx::ColorSpace scaling_color_space_;
DISALLOW_COPY_AND_ASSIGN(GLScaler); DISALLOW_COPY_AND_ASSIGN(GLScaler);
}; };
// For logging.
VIZ_COMMON_EXPORT std::ostream& operator<<(std::ostream& out,
const GLScaler& scaler);
} // namespace viz } // namespace viz
#endif // COMPONENTS_VIZ_COMMON_GL_SCALER_H_ #endif // COMPONENTS_VIZ_COMMON_GL_SCALER_H_
// 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 "base/strings/pattern.h"
#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 "ui/gfx/color_space.h"
#if defined(OS_ANDROID)
#include "base/android/build_info.h"
#endif
namespace viz {
#define EXPECT_STRING_MATCHES(expected, actual) \
if (!base::MatchPattern(actual, expected)) { \
ADD_FAILURE() << "\nActual: " << (actual) \
<< "\nExpected to match pattern: " << (expected); \
}
class GLScalerPixelTest : public cc::PixelTest, public GLScalerTestUtil {
public:
GLScaler* scaler() const { return scaler_.get(); }
std::string GetScalerString() const {
std::ostringstream oss;
oss << *scaler_;
return oss.str();
}
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);
}
// Test convenience to upload |src_bitmap| to the GPU, execute the scaling,
// then download the result from the GPU and return it as a SkBitmap.
SkBitmap Scale(const SkBitmap& src_bitmap,
const gfx::Vector2d& src_offset,
const gfx::Rect& output_rect) {
const GLuint src_texture = UploadTexture(src_bitmap);
const GLuint dest_texture = CreateTexture(output_rect.size());
if (!scaler()->Scale(src_texture,
gfx::Size(src_bitmap.width(), src_bitmap.height()),
src_offset, dest_texture, output_rect)) {
return SkBitmap();
}
return DownloadTexture(dest_texture, output_rect.size());
}
// Returns the amount of color error expected due to bugs in the current
// platform's bilinear texture sampler.
int GetBaselineColorDifference() const {
#if defined(OS_ANDROID)
// Android seems to have texture sampling problems that are not at all seen
// on any of the desktop platforms. Also, versions before Marshmallow seem
// to have a much larger accuracy issues.
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SDK_VERSION_MARSHMALLOW) {
return 12;
}
return 2;
#else
return 0;
#endif
}
protected:
void SetUp() final {
cc::PixelTest::SetUpGLWithoutRenderer(false);
scaler_ = std::make_unique<GLScaler>(context_provider());
gl_ = context_provider()->ContextGL();
CHECK(gl_);
texture_helper_ = std::make_unique<GLScalerTestTextureHelper>(gl_);
}
void TearDown() final {
texture_helper_.reset();
gl_ = nullptr;
scaler_.reset();
cc::PixelTest::TearDown();
}
private:
std::unique_ptr<GLScaler> scaler_;
gpu::gles2::GLES2Interface* gl_ = nullptr;
std::unique_ptr<GLScalerTestTextureHelper> texture_helper_;
};
// Tests that the default GLScaler::Parameters produces an unscaled copy.
TEST_F(GLScalerPixelTest, CopiesByDefault) {
ASSERT_TRUE(scaler()->Configure(GLScaler::Parameters()));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp copy} ← Source", GetScalerString());
const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(kSMPTEFullSize));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSMPTEFullSize, gfx::Rect(kSMPTEFullSize), 0, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests a FAST quality scaling of 2→1 in X and 3→2 in Y.
TEST_F(GLScalerPixelTest, ScalesAtFastQuality) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(2, 3);
params.scale_to = gfx::Vector2d(1, 2);
params.quality = GLScaler::Parameters::Quality::FAST;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp [2 3] to [1 2]} ← Source",
GetScalerString());
const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
static_assert(kSMPTEFullSize.width() % 2 == 0, "Fix kSMPTEFullSize.");
static_assert(kSMPTEFullSize.height() % 3 == 0, "Fix kSMPTEFullSize.");
const SkBitmap actual = Scale(source, gfx::Vector2d(),
gfx::Rect(0, 0, kSMPTEFullSize.width() / 2,
kSMPTEFullSize.height() * 2 / 3));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSMPTEFullSize, gfx::Rect(kSMPTEFullSize), 2, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests a GOOD quality scaling of 1280x720 → 1024x700.
TEST_F(GLScalerPixelTest, ScalesALittleAtGoodQuality) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(1280, 720);
params.scale_to = gfx::Vector2d(1024, 700);
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR2X2/lowp [1280 720] to [1024 700]} ← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(1280, 720);
const SkBitmap source = CreateSMPTETestImage(kSourceSize);
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 1024, 700));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSourceSize, gfx::Rect(kSourceSize), 2, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests a large, skewed reduction at GOOD quality: 3840x720 → 128x256.
TEST_F(GLScalerPixelTest, ScalesALotHorizontallyAtGoodQuality) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(3840, 720);
params.scale_to = gfx::Vector2d(128, 256);
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(
u8"Output "
u8"← {BILINEAR/lowp [256 256] to [128 256]} "
u8"← {BILINEAR4/lowp [2048 512] to [256 256]} "
u8"← {BILINEAR2X2/lowp [3840 720] to [2048 512]} "
u8"← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(3840, 720);
const SkBitmap source = CreateSMPTETestImage(kSourceSize);
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 128, 256));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSourceSize, gfx::Rect(kSourceSize), 2, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests a large, skewed reduction at GOOD quality: 640x2160 → 256x128.
TEST_F(GLScalerPixelTest, ScalesALotVerticallyAtGoodQuality) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(640, 2160);
params.scale_to = gfx::Vector2d(256, 128);
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(
u8"Output "
u8"← {BILINEAR/lowp [256 256] to [256 128]} "
u8"← {BILINEAR4/lowp [512 2048] to [256 256]} "
u8"← {BILINEAR2X2/lowp [640 2160] to [512 2048]} "
u8"← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(640, 2160);
const SkBitmap source = CreateSMPTETestImage(kSourceSize);
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 256, 128));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSourceSize, gfx::Rect(kSourceSize), 2, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests a BEST quality scaling of 1280x720 → 1024x700.
TEST_F(GLScalerPixelTest, ScalesAtBestQuality) {
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(1280, 720);
params.scale_to = gfx::Vector2d(1024, 700);
params.quality = GLScaler::Parameters::Quality::BEST;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(
u8"Output "
u8"← {BICUBIC_HALF_1D/lowp [2048 700] to [1024 700]} "
u8"← {BICUBIC_UPSCALE/lowp [1280 700] to [2048 700]} "
u8"← {BICUBIC_HALF_1D/lowp [1280 1400] to [1280 700]} "
u8"← {BICUBIC_UPSCALE/lowp [1280 720] to [1280 1400]} "
u8"← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(1280, 720);
const SkBitmap source = CreateSMPTETestImage(kSourceSize);
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 1024, 700));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(
actual, kSourceSize, gfx::Rect(kSourceSize), 4, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests that a source offset can be provided to sample the source starting at a
// different location.
TEST_F(GLScalerPixelTest, TranslatesWithSourceOffset) {
GLScaler::Parameters params;
params.is_flipped_source = false;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp copy} ← Source", GetScalerString());
const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
static_assert(kSMPTEFullSize.width() % 2 == 0, "Fix kSMPTEFullSize.");
static_assert(kSMPTEFullSize.height() % 4 == 0, "Fix kSMPTEFullSize.");
const gfx::Vector2d offset(kSMPTEFullSize.width() / 2,
kSMPTEFullSize.height() / 4);
const gfx::Rect src_rect(offset.x(), offset.y(),
kSMPTEFullSize.width() - offset.x(),
kSMPTEFullSize.height() - offset.y());
const gfx::Rect output_rect(0, 0, kSMPTEFullSize.width() - offset.x(),
kSMPTEFullSize.height() - offset.y());
const SkBitmap actual = Scale(source, offset, output_rect);
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(actual, kSMPTEFullSize, src_rect, 0,
&max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual: " << cc::GetPNGDataUrl(actual);
}
// Tests that the source offset works when the source content is vertically
// flipped.
TEST_F(GLScalerPixelTest, TranslatesVerticallyFlippedSourceWithSourceOffset) {
GLScaler::Parameters params;
params.is_flipped_source = true;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp copy} ← Source", GetScalerString());
const SkBitmap flipped_source =
CreateVerticallyFlippedBitmap(CreateSMPTETestImage(kSMPTEFullSize));
const gfx::Vector2d offset(kSMPTEFullSize.width() / 2,
kSMPTEFullSize.height() / 4);
const gfx::Rect src_rect(offset.x(), offset.y(),
kSMPTEFullSize.width() - offset.x(),
kSMPTEFullSize.height() - offset.y());
const gfx::Rect output_rect(0, 0, kSMPTEFullSize.width() - offset.x(),
kSMPTEFullSize.height() - offset.y());
const SkBitmap flipped_back_actual =
CreateVerticallyFlippedBitmap(Scale(flipped_source, offset, output_rect));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(flipped_back_actual, kSMPTEFullSize,
src_rect, 0, &max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual (flipped-back): " << cc::GetPNGDataUrl(flipped_back_actual);
}
// Tests that the output is vertically flipped, if requested in the parameters.
TEST_F(GLScalerPixelTest, VerticallyFlipsOutput) {
GLScaler::Parameters params;
params.is_flipped_source = false;
params.flip_output = true;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {BILINEAR/lowp+flip_y copy} ← Source",
GetScalerString());
const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
const SkBitmap flipped_back_actual = CreateVerticallyFlippedBitmap(
Scale(source, gfx::Vector2d(), gfx::Rect(kSMPTEFullSize)));
int max_color_diff = GetBaselineColorDifference();
EXPECT_TRUE(LooksLikeSMPTETestImage(flipped_back_actual, kSMPTEFullSize,
gfx::Rect(kSMPTEFullSize), 0,
&max_color_diff))
<< "max_color_diff measured was " << max_color_diff
<< "\nActual (flipped-back): " << cc::GetPNGDataUrl(flipped_back_actual);
}
// Tests that the single-channel export ScalerStage works by executing a red
// channel export.
TEST_F(GLScalerPixelTest, ExportsTheRedColorChannel) {
GLScaler::Parameters params;
params.is_flipped_source = false;
params.export_format = GLScaler::Parameters::ExportFormat::CHANNEL_0;
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(u8"Output ← {PLANAR_CHANNEL_0/lowp [4 1] to [1 1]} ← Source",
GetScalerString());
const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
const SkBitmap expected = CreatePackedPlanarBitmap(source, 0);
const gfx::Size output_size(expected.width(), expected.height());
const SkBitmap actual =
Scale(source, gfx::Vector2d(), gfx::Rect(output_size));
constexpr float kAvgAbsoluteErrorLimit = 1.f;
constexpr int kMaxAbsoluteErrorLimit = 2;
EXPECT_TRUE(cc::FuzzyPixelComparator(
false, 100.f, 0.f,
GetBaselineColorDifference() + kAvgAbsoluteErrorLimit,
GetBaselineColorDifference() + kMaxAbsoluteErrorLimit, 0)
.Compare(expected, actual))
<< "\nActual: " << cc::GetPNGDataUrl(actual)
<< "\Expected: " << cc::GetPNGDataUrl(expected);
}
// A test that also stands as an example for how to use the GLScaler to scale a
// screen-sized RGB source (2160x1440, 16:10 aspect ratio) to a typical video
// resolution (720p, 16:9). The end-goal is to produce three textures, which
// contain the three YUV planes in I420 format.
//
// This is a two step process: First, the source is scaled and color space
// converted, with the final result exported as NV61 format (a full size luma
// plane + a half-width interleaved UV image). Second, the interleaved UV image
// is scaled by half in the vertical direction and then separated into one U and
// one V plane.
TEST_F(GLScalerPixelTest, Example_ScaleAndExportForScreenVideoCapture) {
if (scaler()->GetMaxDrawBuffersSupported() < 2) {
LOG(WARNING) << "Skipping test due to lack of MRT support.";
return;
}
// Step 1: Produce a scaled NV61-format result.
GLScaler::Parameters params;
params.scale_from = gfx::Vector2d(2160, 1440);
params.scale_to = gfx::Vector2d(1280, 720);
params.source_color_space = DefaultRGBColorSpace();
params.output_color_space = DefaultYUVColorSpace();
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = true;
params.flip_output = true;
params.export_format = GLScaler::Parameters::ExportFormat::NV61;
params.swizzle[0] = GL_BGRA_EXT; // Swizzle for readback.
params.swizzle[1] = GL_RGBA; // Don't swizzle output for Step 2.
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_STRING_MATCHES(
u8"Output "
u8"← {I422_NV61_MRT/lowp [5120 720] to [1280 720], with color x-form "
u8"to *BT709*, with swizzle(0)} "
u8"← {BILINEAR2/lowp+flip_y [2160 1440] to [1280 720]} "
u8"← Source",
GetScalerString());
constexpr gfx::Size kSourceSize = gfx::Size(2160, 1440);
const GLuint src_texture = UploadTexture(
CreateVerticallyFlippedBitmap(CreateSMPTETestImage(kSourceSize)));
constexpr gfx::Size kOutputSize = gfx::Size(1280, 720);
SkBitmap expected = CreateSMPTETestImage(kOutputSize);
ConvertBitmapToYUV(&expected);
// While the output size is 1280x720, the packing of 4 pixels into one RGBA
// quad means that the texture width must be divided by 4, and that size
// passed in the output_rect argument in the call to ScaleToMultipleOutputs().
const gfx::Size y_plane_size(kOutputSize.width() / 4, kOutputSize.height());
const GLuint y_plane_texture = CreateTexture(y_plane_size);
const GLuint uv_interleaved_texture = CreateTexture(y_plane_size);
ASSERT_TRUE(scaler()->ScaleToMultipleOutputs(
src_texture, kSourceSize, gfx::Vector2d(), y_plane_texture,
uv_interleaved_texture, gfx::Rect(y_plane_size)));
// Step 2: Run the scaler again with the deinterleaver exporter, to produce
// the I420 U and V planes from the NV61 UV interleaved image.
params = GLScaler::Parameters(); // Reset params.
params.scale_from = gfx::Vector2d(1, 2);
params.scale_to = gfx::Vector2d(1, 1);
params.source_color_space = DefaultYUVColorSpace();
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = false; // Output was already flipped in Step 1.
params.export_format =
GLScaler::Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE;
params.swizzle[0] = GL_BGRA_EXT; // Swizzle for readback.
params.swizzle[1] = GL_BGRA_EXT; // Swizzle for readback.
ASSERT_TRUE(scaler()->Configure(params));
EXPECT_EQ(
u8"Output "
u8"← {DEINTERLEAVE_PAIRWISE_MRT/lowp [2 2] to [1 1], with swizzle(0), "
u8"with swizzle(1)} "
u8"← Source",
GetScalerString());
const gfx::Size uv_plane_size(y_plane_size.width() / 2,
y_plane_size.height() / 2);
const GLuint u_plane_texture = CreateTexture(uv_plane_size);
const GLuint v_plane_texture = CreateTexture(uv_plane_size);
ASSERT_TRUE(scaler()->ScaleToMultipleOutputs(
uv_interleaved_texture, y_plane_size, gfx::Vector2d(), u_plane_texture,
v_plane_texture, gfx::Rect(uv_plane_size)));
// Download the textures, and unpack them into an interleaved YUV bitmap, for
// comparison against the |expected| rendition.
SkBitmap actual = AllocateRGBABitmap(kOutputSize);
actual.eraseColor(SkColorSetARGB(0xff, 0x00, 0x80, 0x80));
SkBitmap y_plane = DownloadTexture(y_plane_texture, y_plane_size);
SwizzleBitmap(&y_plane);
UnpackPlanarBitmap(y_plane, 0, &actual);
SkBitmap u_plane = DownloadTexture(u_plane_texture, uv_plane_size);
SwizzleBitmap(&u_plane);
UnpackPlanarBitmap(u_plane, 1, &actual);
SkBitmap v_plane = DownloadTexture(v_plane_texture, uv_plane_size);
SwizzleBitmap(&v_plane);
UnpackPlanarBitmap(v_plane, 2, &actual);
// Provide generous error limits to account for the chroma subsampling in the
// |actual| result when compared to the perfect |expected| rendition.
constexpr float kAvgAbsoluteErrorLimit = 16.f;
constexpr int kMaxAbsoluteErrorLimit = 0x80;
EXPECT_TRUE(cc::FuzzyPixelComparator(false, 100.f, 0.f,
kAvgAbsoluteErrorLimit,
kMaxAbsoluteErrorLimit, 0)
.Compare(expected, actual))
<< "\nActual: " << cc::GetPNGDataUrl(actual)
<< "\nExpected: " << cc::GetPNGDataUrl(expected);
}
#undef EXPECT_STRING_MATCHES
} // namespace viz
...@@ -346,7 +346,7 @@ class GLScalerShaderPixelTest ...@@ -346,7 +346,7 @@ class GLScalerShaderPixelTest
// Android seems to have texture sampling and/or readback accuracy issues // 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 // 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 // platforms. Also, versions before Marshmallow seem to have a much larger
// accuracy issue with a few of the programs. Thus, use higher thresholds, // 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 // assuming that the programs are correct if they can pass a much lower
// threshold on other platforms. // threshold on other platforms.
if (base::android::BuildInfo::GetInstance()->sdk_int() < if (base::android::BuildInfo::GetInstance()->sdk_int() <
......
...@@ -256,6 +256,65 @@ SkBitmap GLScalerTestUtil::CreatePackedPlanarBitmap(const SkBitmap& source, ...@@ -256,6 +256,65 @@ SkBitmap GLScalerTestUtil::CreatePackedPlanarBitmap(const SkBitmap& source,
return result; return result;
} }
// static
void GLScalerTestUtil::UnpackPlanarBitmap(const SkBitmap& plane,
int channel,
SkBitmap* out) {
// The heuristic below auto-adapts to subsampled plane sizes. However, there
// are two cricital requirements: 1) |plane| cannot be empty; 2) |plane| must
// have a size that cleanly unpacks to |out|'s size.
CHECK_GT(plane.width(), 0);
CHECK_GT(plane.height(), 0);
const int col_sampling_ratio = out->width() / plane.width();
CHECK_EQ(out->width() % plane.width(), 0);
CHECK_GT(col_sampling_ratio, 0);
const int row_sampling_ratio = out->height() / plane.height();
CHECK_EQ(out->height() % plane.height(), 0);
CHECK_GT(row_sampling_ratio, 0);
const int ch_sampling_ratio = col_sampling_ratio / 4;
CHECK_GT(ch_sampling_ratio, 0);
// These determine which single byte in each of |out|'s uint32_t-valued pixels
// will be modified.
constexpr int kShiftForChannel[4] = {kRedShift, kGreenShift, kBlueShift,
kAlphaShift};
const int output_shift = kShiftForChannel[channel];
const uint32_t output_retain_mask = ~(UINT32_C(0xff) << output_shift);
// Iterate over the pixels of |out|, sampling each of the 4 components of each
// of |plane|'s pixels.
for (int y = 0; y < out->height(); ++y) {
const uint32_t* const src = plane.getAddr32(0, y / row_sampling_ratio);
uint32_t* const dst = out->getAddr32(0, y);
for (int x = 0; x < out->width(); ++x) {
// Zero-out the existing byte (e.g., if channel==1, then "RGBA" → "R0BA").
dst[x] &= output_retain_mask;
// From |src|, grab one of "XYZW". Then, copy it to the target byte in
// |dst| (e.g., if x_src_ch=3, then grab "W" from |src|, and |dst| changes
// from "R0BA" to "RWBA").
const int x_src = x / col_sampling_ratio;
const int x_src_ch = (x / ch_sampling_ratio) % 4;
dst[x] |= ((src[x_src] >> kShiftForChannel[x_src_ch]) & 0xff)
<< output_shift;
}
}
}
// static
SkBitmap GLScalerTestUtil::CreateVerticallyFlippedBitmap(
const SkBitmap& source) {
SkBitmap bitmap;
bitmap.allocPixels(source.info());
CHECK_EQ(bitmap.rowBytes(), source.rowBytes());
for (int y = 0; y < bitmap.height(); ++y) {
const int src_y = bitmap.height() - y - 1;
memcpy(bitmap.getAddr32(0, y), source.getAddr32(0, src_y),
bitmap.rowBytes());
}
return bitmap;
}
// The area and color of the bars in a 1920x1080 HD SMPTE color bars test image // The area and color of the bars in a 1920x1080 HD SMPTE color bars test image
// (https://commons.wikimedia.org/wiki/File:SMPTE_Color_Bars_16x9.svg). The gray // (https://commons.wikimedia.org/wiki/File:SMPTE_Color_Bars_16x9.svg). The gray
// linear gradient bar is defined as half solid 0-level black and half solid // linear gradient bar is defined as half solid 0-level black and half solid
......
...@@ -98,6 +98,17 @@ class GLScalerTestUtil { ...@@ -98,6 +98,17 @@ class GLScalerTestUtil {
// considered a reference implementation of that. // considered a reference implementation of that.
static SkBitmap CreatePackedPlanarBitmap(const SkBitmap& source, int channel); static SkBitmap CreatePackedPlanarBitmap(const SkBitmap& source, int channel);
// Performs the inverse operation to CreatePackedPlanarBitmap(). This takes
// all of the data in |plane| and uses it to populate a single color channel
// of all the pixels of |out|. The |plane| can be a full-size or half-size
// (subsampled) plane.
static void UnpackPlanarBitmap(const SkBitmap& plane,
int channel,
SkBitmap* out);
// Returns the |source| bitmap, but with its content vertically flipped.
static SkBitmap CreateVerticallyFlippedBitmap(const SkBitmap& source);
// The area and color of the bars in a 1920x1080 HD SMPTE color bars test // The area and color of the bars in a 1920x1080 HD SMPTE color bars test
// image (https://commons.wikimedia.org/wiki/File:SMPTE_Color_Bars_16x9.svg). // image (https://commons.wikimedia.org/wiki/File:SMPTE_Color_Bars_16x9.svg).
// The gray linear gradient bar is defined as half solid 0-level black and // The gray linear gradient bar is defined as half solid 0-level black and
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "components/viz/common/gl_scaler.h" #include "components/viz/common/gl_scaler.h"
#include "cc/test/pixel_test.h"
#include "components/viz/common/gl_scaler_test_util.h"
#include "components/viz/common/gpu/context_provider.h" #include "components/viz/common/gpu/context_provider.h"
#include "gpu/GLES2/gl2chromium.h" #include "gpu/GLES2/gl2chromium.h"
#include "gpu/GLES2/gl2extchromium.h" #include "gpu/GLES2/gl2extchromium.h"
...@@ -79,7 +81,12 @@ class MockContextProvider : public ContextProvider { ...@@ -79,7 +81,12 @@ class MockContextProvider : public ContextProvider {
} }
}; };
class GLScalerTest : public testing::Test {}; class GLScalerTest : public cc::PixelTest {
protected:
void SetUp() final { cc::PixelTest::SetUpGLWithoutRenderer(false); }
void TearDown() final { cc::PixelTest::TearDown(); }
};
TEST_F(GLScalerTest, AddAndRemovesSelfAsContextLossObserver) { TEST_F(GLScalerTest, AddAndRemovesSelfAsContextLossObserver) {
NiceMock<MockContextProvider> provider; NiceMock<MockContextProvider> provider;
...@@ -93,7 +100,7 @@ TEST_F(GLScalerTest, AddAndRemovesSelfAsContextLossObserver) { ...@@ -93,7 +100,7 @@ TEST_F(GLScalerTest, AddAndRemovesSelfAsContextLossObserver) {
GLScaler scaler(base::WrapRefCounted(&provider)); GLScaler scaler(base::WrapRefCounted(&provider));
} }
TEST_F(GLScalerTest, CleansUpWhenContextIsLost) { TEST_F(GLScalerTest, RemovesObserverWhenContextIsLost) {
NiceMock<MockContextProvider> provider; NiceMock<MockContextProvider> provider;
ContextLostObserver* registered_observer = nullptr; ContextLostObserver* registered_observer = nullptr;
Sequence s; Sequence s;
...@@ -108,9 +115,29 @@ TEST_F(GLScalerTest, CleansUpWhenContextIsLost) { ...@@ -108,9 +115,29 @@ TEST_F(GLScalerTest, CleansUpWhenContextIsLost) {
Mock::VerifyAndClearExpectations(&provider); Mock::VerifyAndClearExpectations(&provider);
} }
TEST_F(GLScalerTest, StopsScalingWhenContextIsLost) {
GLScaler scaler(context_provider());
// Configure the scaler with default parameters (1:1 scale ratio).
ASSERT_TRUE(scaler.Configure(GLScaler::Parameters()));
// Call Scale() and expect it to return true to indicate the operation
// succeeded.
GLScalerTestTextureHelper helper(context_provider()->ContextGL());
constexpr gfx::Size kSomeSize = gfx::Size(32, 32);
const GLuint src_texture = helper.CreateTexture(kSomeSize);
const GLuint dest_texture = helper.CreateTexture(kSomeSize);
EXPECT_TRUE(scaler.Scale(src_texture, kSomeSize, gfx::Vector2d(),
dest_texture, gfx::Rect(kSomeSize)));
// After the context is lost, another call to Scale() should return false.
static_cast<ContextLostObserver&>(scaler).OnContextLost();
EXPECT_FALSE(scaler.Scale(src_texture, kSomeSize, gfx::Vector2d(),
dest_texture, gfx::Rect(kSomeSize)));
}
TEST_F(GLScalerTest, Configure_RequiresValidScalingVectors) { TEST_F(GLScalerTest, Configure_RequiresValidScalingVectors) {
NiceMock<MockContextProvider> provider; GLScaler scaler(context_provider());
GLScaler scaler(base::WrapRefCounted(&provider));
GLScaler::Parameters params; GLScaler::Parameters params;
EXPECT_TRUE(scaler.Configure(params)); EXPECT_TRUE(scaler.Configure(params));
...@@ -123,8 +150,7 @@ TEST_F(GLScalerTest, Configure_RequiresValidScalingVectors) { ...@@ -123,8 +150,7 @@ TEST_F(GLScalerTest, Configure_RequiresValidScalingVectors) {
} }
TEST_F(GLScalerTest, Configure_ResolvesUnspecifiedColorSpaces) { TEST_F(GLScalerTest, Configure_ResolvesUnspecifiedColorSpaces) {
NiceMock<MockContextProvider> provider; GLScaler scaler(context_provider());
GLScaler scaler(base::WrapRefCounted(&provider));
// Neither source nor output space specified: Both should resolve to sRGB. // Neither source nor output space specified: Both should resolve to sRGB.
GLScaler::Parameters params; GLScaler::Parameters params;
...@@ -151,8 +177,7 @@ TEST_F(GLScalerTest, Configure_ResolvesUnspecifiedColorSpaces) { ...@@ -151,8 +177,7 @@ TEST_F(GLScalerTest, Configure_ResolvesUnspecifiedColorSpaces) {
} }
TEST_F(GLScalerTest, Configure_RequiresValidSwizzles) { TEST_F(GLScalerTest, Configure_RequiresValidSwizzles) {
NiceMock<MockContextProvider> provider; GLScaler scaler(context_provider());
GLScaler scaler(base::WrapRefCounted(&provider));
GLScaler::Parameters params; GLScaler::Parameters params;
// Test that all valid combinations work. // Test that all valid combinations work.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment