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

GLScaler: Add scaler stages and overscan pixel tests.

Refactors and cleans-up code, transforming GLHelperScaling's internal
"ScalerImpl" class into a GLScaler::ScalerStage class. A ScalerStage
chains together with other ScalerStages to perform each step from the
source input to ultimate scaled output. (A future change will provide
the logic for deciding how to create/configure ScalerStages and chain
them together.)

A major change other than the refactoring: The math that calculates each
shader's "overscan" padding has been revisited and corrected. New math
is included in this change, along with pixel tests that confirm it is
correct (i.e., tests the math, but also runs the shader programs to
prove the numbers jive).

Bug: 870036
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel
Change-Id: I3dbbbb8f8cca45006895faed6d7501ceab5cc1cb
Reviewed-on: https://chromium-review.googlesource.com/c/1285328Reviewed-by: default avatarXiangjun Zhang <xjz@chromium.org>
Reviewed-by: default avatarYuri Wiitala <miu@chromium.org>
Commit-Queue: Yuri Wiitala <miu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601341}
parent 04843c10
...@@ -218,6 +218,7 @@ viz_source_set("unit_tests") { ...@@ -218,6 +218,7 @@ viz_source_set("unit_tests") {
"frame_sinks/copy_output_util_unittest.cc", "frame_sinks/copy_output_util_unittest.cc",
"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_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",
......
...@@ -14,9 +14,7 @@ ...@@ -14,9 +14,7 @@
#include "gpu/GLES2/gl2extchromium.h" #include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/common/capabilities.h" #include "gpu/command_buffer/common/capabilities.h"
#include "ui/gfx/color_transform.h" #include "ui/gfx/color_transform.h"
#include "ui/gfx/geometry/rect_f.h" #include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d_f.h"
namespace viz { namespace viz {
...@@ -68,6 +66,7 @@ int GLScaler::GetMaxDrawBuffersSupported() const { ...@@ -68,6 +66,7 @@ int GLScaler::GetMaxDrawBuffersSupported() const {
} }
bool GLScaler::Configure(const Parameters& new_params) { bool GLScaler::Configure(const Parameters& new_params) {
chain_.reset();
shader_programs_.clear(); shader_programs_.clear();
if (!context_provider_) { if (!context_provider_) {
...@@ -135,26 +134,53 @@ bool GLScaler::Configure(const Parameters& new_params) { ...@@ -135,26 +134,53 @@ bool GLScaler::Configure(const Parameters& new_params) {
} }
} }
// TODO(crbug.com/870036): Build ScalerStage chain (upcoming CL).
return true; return true;
} }
bool GLScaler::ScaleToMultipleOutputs(GLuint src_texture, bool GLScaler::ScaleToMultipleOutputs(GLuint src_texture,
const gfx::Size& src_texture_size, const gfx::Size& src_texture_size,
const gfx::Vector2dF& src_offset, const gfx::Vector2d& src_offset,
GLuint dest_texture_0, GLuint dest_texture_0,
GLuint dest_texture_1, GLuint dest_texture_1,
const gfx::Rect& output_rect) { const gfx::Rect& output_rect) {
NOTIMPLEMENTED(); if (!chain_) {
return false; return false;
} }
bool GLScaler::ComputeRegionOfInfluence(const gfx::Size& src_texture_size, // Bind the vertex attributes used to sweep the entire source area when
const gfx::Vector2dF& src_offset, // executing the shader programs.
const gfx::Rect& output_rect, GLES2Interface* const gl = context_provider_->ContextGL();
gfx::Rect* sampling_rect, DCHECK(gl);
gfx::Vector2dF* offset) const { if (vertex_attributes_buffer_) {
NOTIMPLEMENTED(); gl->BindBuffer(GL_ARRAY_BUFFER, vertex_attributes_buffer_);
return false; } else {
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);
}
// Disable GL clipping/blending features that interfere with assumptions made
// by the implementation. Only those known to possibly be enabled elsewhere in
// Chromium code are disabled here, while the remainder are sanity-DCHECK'ed.
gl->Disable(GL_SCISSOR_TEST);
gl->Disable(GL_STENCIL_TEST);
gl->Disable(GL_BLEND);
DCHECK_NE(gl->IsEnabled(GL_CULL_FACE), GL_TRUE);
DCHECK_NE(gl->IsEnabled(GL_DEPTH_TEST), GL_TRUE);
DCHECK_NE(gl->IsEnabled(GL_POLYGON_OFFSET_FILL), GL_TRUE);
DCHECK_NE(gl->IsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE), GL_TRUE);
DCHECK_NE(gl->IsEnabled(GL_SAMPLE_COVERAGE), GL_TRUE);
DCHECK_NE(gl->IsEnabled(GL_SCISSOR_TEST), GL_TRUE);
DCHECK_NE(gl->IsEnabled(GL_STENCIL_TEST), GL_TRUE);
chain_->ScaleToMultipleOutputs(src_texture, src_texture_size, src_offset,
dest_texture_0, dest_texture_1, output_rect);
gl->BindBuffer(GL_ARRAY_BUFFER, 0);
return true;
} }
// static // static
...@@ -181,7 +207,14 @@ bool GLScaler::ParametersHasSameScaleRatio(const GLScaler::Parameters& params, ...@@ -181,7 +207,14 @@ bool GLScaler::ParametersHasSameScaleRatio(const GLScaler::Parameters& params,
void GLScaler::OnContextLost() { void GLScaler::OnContextLost() {
// The destruction order here is important due to data dependencies. // The destruction order here is important due to data dependencies.
chain_.reset();
shader_programs_.clear(); shader_programs_.clear();
if (vertex_attributes_buffer_) {
if (auto* gl = context_provider_->ContextGL()) {
gl->DeleteBuffers(1, &vertex_attributes_buffer_);
}
vertex_attributes_buffer_ = 0;
}
if (context_provider_) { if (context_provider_) {
context_provider_->RemoveObserver(this); context_provider_->RemoveObserver(this);
context_provider_ = nullptr; context_provider_ = nullptr;
...@@ -402,7 +435,8 @@ GLScaler::ShaderProgram::ShaderProgram( ...@@ -402,7 +435,8 @@ GLScaler::ShaderProgram::ShaderProgram(
"const float a = -0.5;\n" "const float a = -0.5;\n"
// This function is equivialent to calling the bicubic // This function is equivialent to calling the bicubic
// function with x-1, x, 1-x and 2-x (assuming // function with x-1, x, 1-x and 2-x (assuming
// 0 <= x < 1) // 0 <= x < 1). The following is the Catmull-Rom spline.
// See: http://wikipedia.org/wiki/Cubic_Hermite_spline
"vec4 filt4(float x) {\n" "vec4 filt4(float x) {\n"
" return vec4(x * x * x, x * x, x, 1) *\n" " return vec4(x * x * x, x * x, x, 1) *\n"
" mat4( a, -2.0 * a, a, 0.0,\n" " mat4( a, -2.0 * a, a, 0.0,\n"
...@@ -806,4 +840,258 @@ void GLScaler::ShaderProgram::UseProgram(const gfx::Size& src_texture_size, ...@@ -806,4 +840,258 @@ void GLScaler::ShaderProgram::UseProgram(const gfx::Size& src_texture_size,
} }
} }
GLScaler::ScalerStage::ScalerStage(gpu::gles2::GLES2Interface* gl,
GLScaler::Shader shader,
GLScaler::Axis primary_axis,
const gfx::Vector2d& scale_from,
const gfx::Vector2d& scale_to)
: gl_(gl),
shader_(shader),
primary_axis_(primary_axis),
scale_from_(scale_from),
scale_to_(scale_to) {
DCHECK(gl_);
}
GLScaler::ScalerStage::~ScalerStage() {
if (dest_framebuffer_) {
gl_->DeleteFramebuffers(1, &dest_framebuffer_);
}
if (intermediate_texture_) {
gl_->DeleteTextures(1, &intermediate_texture_);
}
}
void GLScaler::ScalerStage::ScaleToMultipleOutputs(
GLuint src_texture,
gfx::Size src_texture_size,
const gfx::Vector2d& src_offset,
GLuint dest_texture_0,
GLuint dest_texture_1,
const gfx::Rect& output_rect) {
if (output_rect.IsEmpty())
return; // No work to do.
// Calculate the source region from the given |output_rect|, accounting for
// both the |src_offset| and whether the source's coordinate system is
// Y-flipped.
gfx::RectF src_rect = ToSourceRect(output_rect);
if (is_flipped_source_) {
src_rect.set_x(src_rect.x() + src_offset.x());
src_rect.set_y(src_texture_size.height() - src_rect.bottom() -
src_offset.y());
} else {
src_rect += src_offset;
}
// Make a recursive call to the "input" ScalerStage to produce an intermediate
// texture for this stage to source from. Adjust src_* variables to use the
// intermediate texture as input.
if (input_stage_) {
const gfx::Rect input_rect = ToInputRect(src_rect);
EnsureIntermediateTextureDefined(input_rect.size());
input_stage_->ScaleToMultipleOutputs(src_texture, src_texture_size,
gfx::Vector2d(0, 0),
intermediate_texture_, 0, input_rect);
src_texture = intermediate_texture_;
src_texture_size = intermediate_texture_size_;
src_rect -= input_rect.OffsetFromOrigin();
}
// Attach the output texture(s) to the framebuffer.
if (!dest_framebuffer_) {
gl_->GenFramebuffers(1, &dest_framebuffer_);
}
gl_->BindFramebuffer(GL_FRAMEBUFFER, dest_framebuffer_);
gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dest_texture_0, 0);
if (dest_texture_1 > 0) {
gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + 1,
GL_TEXTURE_2D, dest_texture_1, 0);
}
// Bind to the source texture and set the texture sampler to use bilinear
// filtering and clamp-to-edge, as required by all shader programs.
//
// It would be better to stash the existing parameter values, and restore them
// back later. However, glGetTexParameteriv() currently requires a blocking
// call to the GPU service, which is extremely costly performance-wise.
gl_->BindTexture(GL_TEXTURE_2D, src_texture);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Prepare the shader program for drawing.
DCHECK(program_);
program_->UseProgram(src_texture_size, src_rect, output_rect.size(),
primary_axis_, flip_output_);
// Execute the draw.
gl_->Viewport(0, 0, output_rect.width(), output_rect.height());
const GLenum buffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT0 + 1};
if (dest_texture_1 > 0) {
gl_->DrawBuffersEXT(2, buffers);
}
gl_->DrawArrays(GL_TRIANGLE_STRIP, 0, 4);
if (dest_texture_1 > 0) {
// Set the draw buffers back, to not disrupt external operations.
gl_->DrawBuffersEXT(1, buffers);
}
gl_->BindTexture(GL_TEXTURE_2D, 0);
gl_->BindFramebuffer(GL_FRAMEBUFFER, 0);
}
gfx::RectF GLScaler::ScalerStage::ToSourceRect(
const gfx::Rect& output_rect) const {
return gfx::ScaleRect(gfx::RectF(output_rect),
float{scale_from_.x()} / scale_to_.x(),
float{scale_from_.y()} / scale_to_.y());
}
gfx::Rect GLScaler::ScalerStage::ToInputRect(gfx::RectF source_rect) const {
int overscan_x = 0;
int overscan_y = 0;
switch (shader_) {
case Shader::BILINEAR:
case Shader::BILINEAR2:
case Shader::BILINEAR3:
case Shader::BILINEAR4: {
// These shaders sample 1 or more points along the primary axis, and only
// 1 point in the other direction, in order to produce each output pixel.
// The amount of overscan is always 0 or 1 pixel along the primary axis,
// and this can be determined by looking at the upper-left-most source
// texture sampling point: If this point is to the left of the middle of
// the upper-left-most source pixel, the texture sampler will also read
// the pixel to the left of that (for linear interpolation). Similar
// behavior can occur towards the right, upwards, and downwards at the
// source boundaries.
int threshold;
switch (shader_) {
default:
threshold = 1;
break;
case Shader::BILINEAR2:
threshold = 2;
break;
case Shader::BILINEAR3:
threshold = 3;
break;
case Shader::BILINEAR4:
threshold = 4;
break;
}
switch (primary_axis_) {
case HORIZONTAL:
if (scale_from_.x() < threshold * scale_to_.x()) {
overscan_x = 1;
}
if (scale_from_.y() < scale_to_.y()) {
overscan_y = 1;
}
break;
case VERTICAL:
if (scale_from_.x() < scale_to_.x()) {
overscan_x = 1;
}
if (scale_from_.y() < threshold * scale_to_.y()) {
overscan_y = 1;
}
break;
}
break;
}
case Shader::BILINEAR2X2:
// This shader samples 2 points along both axes, and the overscan is 0 or
// 1 pixel in both directions (same explanation as for the other BILINEAR
// shaders).
if (scale_from_.x() < 2 * scale_to_.x()) {
overscan_x = 1;
}
if (scale_from_.y() < 2 * scale_to_.y()) {
overscan_y = 1;
}
break;
case Shader::BICUBIC_UPSCALE:
// For each output pixel, this shader always reads 2 pixels about the
// source position in one dimension, and has no overscan in the other
// dimension.
if (scale_from_.x() < scale_to_.x()) {
DCHECK_EQ(HORIZONTAL, primary_axis_);
overscan_x = 2;
} else if (scale_from_.y() < scale_to_.y()) {
DCHECK_EQ(VERTICAL, primary_axis_);
overscan_y = 2;
} else if (scale_from_ == scale_to_) {
// Special case: When not scaling, the math in the shader will resolve
// to just outputting the value for a single source pixel. The shader
// will sample surrounding pixels, but then apply a zero weight to them
// during convolution. Thus, there is effectively no overscan.
NOTREACHED(); // This is a crazy-expensive way to do a 1:1 copy!
} else {
NOTREACHED(); // Downscaling is meaningless.
}
break;
case Shader::BICUBIC_HALF_1D: {
// For each output pixel, this shader always reads 4 pixels about the
// source position in one dimension, and has no overscan in the other
// dimension. However, since the source position always has a distance
// >= 1 inside the "logical" bounds, there can never be more than 3 pixels
// of overscan.
if (scale_from_.x() == 2 * scale_to_.x()) {
DCHECK_EQ(HORIZONTAL, primary_axis_);
overscan_x = 3;
} else if (scale_from_.y() == 2 * scale_to_.y()) {
DCHECK_EQ(VERTICAL, primary_axis_);
overscan_y = 3;
} else {
// Anything but a half-downscale in one dimension is meaningless.
NOTREACHED();
}
break;
}
case Shader::PLANAR_CHANNEL_0:
case Shader::PLANAR_CHANNEL_1:
case Shader::PLANAR_CHANNEL_2:
case Shader::PLANAR_CHANNEL_3:
case Shader::I422_NV61_MRT:
// All of these sample exactly 4x1 source pixels to produce each output
// "pixel." There is no overscan.
DCHECK_EQ(scale_from_.x(), 4 * scale_to_.x());
DCHECK_EQ(HORIZONTAL, primary_axis_);
break;
case Shader::DEINTERLEAVE_PAIRWISE_MRT:
// This shader samples exactly 2x1 source pixels to produce each output
// "pixel." There is no overscan.
DCHECK_EQ(scale_from_.x(), 2 * scale_to_.x());
DCHECK_EQ(HORIZONTAL, primary_axis_);
break;
}
source_rect.Inset(-overscan_x, -overscan_y);
return gfx::ToEnclosingRect(source_rect);
}
void GLScaler::ScalerStage::EnsureIntermediateTextureDefined(
const gfx::Size& size) {
// Reallocate a new texture, if needed.
if (!intermediate_texture_) {
gl_->GenTextures(1, &intermediate_texture_);
}
if (intermediate_texture_size_ != size) {
gl_->BindTexture(GL_TEXTURE_2D, intermediate_texture_);
// Note: Not setting the filter or wrap parameters on the texture here
// because that will be done in ScaleToMultipleOutputs() anyway.
gl_->TexImage2D(GL_TEXTURE_2D, 0, program_->texture_format(), size.width(),
size.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
intermediate_texture_size_ = size;
}
}
} // namespace viz } // namespace viz
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <tuple> #include <tuple>
#include <utility>
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -18,14 +19,13 @@ ...@@ -18,14 +19,13 @@
#include "components/viz/common/viz_common_export.h" #include "components/viz/common/viz_common_export.h"
#include "gpu/command_buffer/client/gles2_interface.h" #include "gpu/command_buffer/client/gles2_interface.h"
#include "ui/gfx/color_space.h" #include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/geometry/vector2d.h"
namespace gfx { namespace gfx {
class ColorTransform; class ColorTransform;
class Vector2dF;
class Rect;
class RectF;
class Size;
} // namespace gfx } // namespace gfx
namespace viz { namespace viz {
...@@ -232,7 +232,7 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver { ...@@ -232,7 +232,7 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
// and wrap_s/t set to CLAMP_TO_EDGE in this call. // and wrap_s/t set to CLAMP_TO_EDGE in this call.
bool Scale(GLuint src_texture, bool Scale(GLuint src_texture,
const gfx::Size& src_texture_size, const gfx::Size& src_texture_size,
const gfx::Vector2dF& src_offset, const gfx::Vector2d& src_offset,
GLuint dest_texture, GLuint dest_texture,
const gfx::Rect& output_rect) WARN_UNUSED_RESULT { const gfx::Rect& output_rect) WARN_UNUSED_RESULT {
return ScaleToMultipleOutputs(src_texture, src_texture_size, src_offset, return ScaleToMultipleOutputs(src_texture, src_texture_size, src_offset,
...@@ -243,28 +243,11 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver { ...@@ -243,28 +243,11 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
// (see Parameters::ExportFormat). // (see Parameters::ExportFormat).
bool ScaleToMultipleOutputs(GLuint src_texture, bool ScaleToMultipleOutputs(GLuint src_texture,
const gfx::Size& src_texture_size, const gfx::Size& src_texture_size,
const gfx::Vector2dF& src_offset, const gfx::Vector2d& src_offset,
GLuint dest_texture_0, GLuint dest_texture_0,
GLuint dest_texture_1, GLuint dest_texture_1,
const gfx::Rect& output_rect) WARN_UNUSED_RESULT; const gfx::Rect& output_rect) WARN_UNUSED_RESULT;
// Given the |src_texture_size|, |src_offset| and |output_rect| arguments that
// would be passed to Scale(), compute the region of pixels in the source
// texture that would be sampled to produce a scaled result. The result is
// stored in |sampling_rect|, along with the |offset| to the (0,0) point
// relative to |sampling_rect|'s origin. Returns true to indicate success, or
// false if this GLScaler is not valid.
//
// This is used by clients that need to know the minimal portion of a source
// buffer that must be copied without affecting Scale()'s results. This
// method also accounts for vertical flipping.
bool ComputeRegionOfInfluence(const gfx::Size& src_texture_size,
const gfx::Vector2dF& src_offset,
const gfx::Rect& output_rect,
gfx::Rect* sampling_rect,
gfx::Vector2dF* offset) const
WARN_UNUSED_RESULT;
// Returns true if from:to represent the same scale ratio as that specified in // Returns true if from:to represent the same scale ratio as that specified in
// |params|. // |params|.
static bool ParametersHasSameScaleRatio(const Parameters& params, static bool ParametersHasSameScaleRatio(const Parameters& params,
...@@ -272,6 +255,7 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver { ...@@ -272,6 +255,7 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
const gfx::Vector2d& to); const gfx::Vector2d& to);
private: private:
friend class GLScalerOverscanPixelTest;
friend class GLScalerShaderPixelTest; friend class GLScalerShaderPixelTest;
using GLES2Interface = gpu::gles2::GLES2Interface; using GLES2Interface = gpu::gles2::GLES2Interface;
...@@ -313,9 +297,11 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver { ...@@ -313,9 +297,11 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
// attribute buffer. |src_texture_size| is the size of the entire source // attribute buffer. |src_texture_size| is the size of the entire source
// texture, regardless of which region is to be sampled. |src_rect| is the // texture, regardless of which region is to be sampled. |src_rect| is the
// source region, not including overscan pixels past the edges. // source region, not including overscan pixels past the edges.
// |primary_axis| configures certain programs which scale in only one // |primary_axis| determines whether multiple texture samplings occur in one
// particular direction. |flip_y| causes the |src_rect| to be scanned // direction or the other (for some shaders). Note that this cannot
// upside-down, to produce a vertically-flipped result. // necessarily be determined by just comparing the src and dst sizes.
// |flip_y| causes the |src_rect| to be scanned upside-down, to produce a
// vertically-flipped result.
void UseProgram(const gfx::Size& src_texture_size, void UseProgram(const gfx::Size& src_texture_size,
const gfx::RectF& src_rect, const gfx::RectF& src_rect,
const gfx::Size& dst_size, const gfx::Size& dst_size,
...@@ -356,6 +342,80 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver { ...@@ -356,6 +342,80 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
DISALLOW_COPY_AND_ASSIGN(ShaderProgram); DISALLOW_COPY_AND_ASSIGN(ShaderProgram);
}; };
// One scaling stage in a chain of scaler pipeline stages. Each ScalerStage
// owns the previous ScalerStage in the chain: At execution time, a "working
// backwards" approach is used: The previous "input" stage renders an
// intermediate result that will be used as input for the current stage.
//
// Each ScalerStage caches textures and framebuffers to avoid reallocating
// them for each separate image scaling, which can be expensive on some
// platforms/drivers.
class VIZ_COMMON_EXPORT ScalerStage {
public:
ScalerStage(GLES2Interface* gl,
Shader shader,
Axis primary_axis,
const gfx::Vector2d& scale_from,
const gfx::Vector2d& scale_to);
~ScalerStage();
Shader shader() const { return shader_; }
const gfx::Vector2d& scale_from() const { return scale_from_; }
const gfx::Vector2d& scale_to() const { return scale_to_; }
ScalerStage* input_stage() const { return input_stage_.get(); }
void set_input_stage(std::unique_ptr<ScalerStage> stage) {
input_stage_ = std::move(stage);
}
void set_shader_program(ShaderProgram* program) { program_ = program; }
bool is_flipped_source() const { return is_flipped_source_; }
void set_is_flipped_source(bool flipped) { is_flipped_source_ = flipped; }
bool flip_output() const { return flip_output_; }
void set_flip_output(bool flip) { flip_output_ = flip; }
void ScaleToMultipleOutputs(GLuint src_texture,
gfx::Size src_texture_size,
const gfx::Vector2d& src_offset,
GLuint dest_texture_0,
GLuint dest_texture_1,
const gfx::Rect& output_rect);
private:
friend class GLScalerOverscanPixelTest;
// Returns the given |output_rect| mapped to the input stage's coordinate
// system.
gfx::RectF ToSourceRect(const gfx::Rect& output_rect) const;
// Returns the given |source_rect| padded to include the overscan pixels the
// shader program will access.
gfx::Rect ToInputRect(gfx::RectF source_rect) const;
// Generates the intermediate texture and/or re-defines it if its size has
// changed.
void EnsureIntermediateTextureDefined(const gfx::Size& size);
GLES2Interface* const gl_;
const Shader shader_;
const Axis primary_axis_;
const gfx::Vector2d scale_from_;
const gfx::Vector2d scale_to_;
std::unique_ptr<ScalerStage> input_stage_;
ShaderProgram* program_ = nullptr;
bool is_flipped_source_ = false;
bool flip_output_ = false;
GLuint intermediate_texture_ = 0;
gfx::Size intermediate_texture_size_;
GLuint dest_framebuffer_ = 0;
DISALLOW_COPY_AND_ASSIGN(ScalerStage);
};
// ContextLostObserver implementation. // ContextLostObserver implementation.
void OnContextLost() final; void OnContextLost() final;
...@@ -383,6 +443,14 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver { ...@@ -383,6 +443,14 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
tuple<Shader, GLint, gfx::ColorSpace, gfx::ColorSpace, GLenum, GLenum>; tuple<Shader, GLint, gfx::ColorSpace, gfx::ColorSpace, GLenum, GLenum>;
std::map<ShaderCacheKey, ShaderProgram> shader_programs_; std::map<ShaderCacheKey, ShaderProgram> shader_programs_;
// The GL_ARRAY_BUFFER that holds the vertices and the texture coordinates
// data for sweeping the source area when a ScalerStage draws a quad (to
// execute its shader program).
GLuint vertex_attributes_buffer_ = 0;
// The chain of ScalerStages.
std::unique_ptr<ScalerStage> chain_;
DISALLOW_COPY_AND_ASSIGN(GLScaler); DISALLOW_COPY_AND_ASSIGN(GLScaler);
}; };
......
// 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 "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/SkRect.h"
namespace viz {
class GLScalerOverscanPixelTest : public cc::PixelTest,
public GLScalerTestUtil {
public:
using Axis = GLScaler::Axis;
using ScalerStage = GLScaler::ScalerStage;
using Shader = GLScaler::Shader;
bool AreMultipleRenderingTargetsSupported() const {
return scaler_->GetMaxDrawBuffersSupported() > 1;
}
// Creates a ScalerStage chain consisting of a single stage having the given
// configuration.
void UseScaler(Shader shader,
Axis primary_axis,
const gfx::Vector2d& scale_from,
const gfx::Vector2d& scale_to) {
scaler_->chain_ = std::make_unique<ScalerStage>(gl_, shader, primary_axis,
scale_from, scale_to);
scaler_->chain_->set_shader_program(scaler_->GetShaderProgram(
shader, GL_RGBA, nullptr, GLScaler::Parameters().swizzle));
}
// Converts the given |source_rect| into a possibly-larger one that includes
// all of the pixels that would be sampled by the current scaler (i.e.,
// including overscan). This uses the math of the internal implementation to
// compute the values.
gfx::Rect ToInputRect(gfx::Rect source_rect) {
CHECK(scaler_ && scaler_->chain_);
return scaler_->chain_->ToInputRect(gfx::RectF(source_rect));
}
// Renders images using the current scaler to auto-detect its overscan. This
// does NOT use the internal implementation to compute the values, but instead
// discovers them experimentally. This is used to confirm that: a) the scaler
// behaves as ToInputRect() expects; and b) the math internal to ToInputRect()
// is correct.
//
// The general approach is to upload a source image containing a blue box in
// the center, surrounded by a red background. The size of the blue box is
// varied: It starts out at a size encompassing more than all of the pixels to
// be sampled by the scaler, and is gradually shrunk until the scaler's output
// begins to include red "bleed-in." At that point, the overscan amount is
// confirmed experimentally.
gfx::Vector2d DetectScalerOverscan(const gfx::Vector2d& scale_from,
const gfx::Vector2d& scale_to) {
// Assume a source size three times the "scale from" width and height. This
// allows for scaling the middle third of a source image, to test possible
// bleed-in on all sides of the output.
const gfx::Size src_size(scale_from.x() * 3, scale_from.y() * 3);
// The requested output rect is the center third of the image, in the
// destination coordinate space.
const gfx::Rect dst_rect(
src_size.width() / 3 * scale_to.x() / scale_from.x(),
src_size.height() / 3 * scale_to.y() / scale_from.y(),
src_size.width() / 3 * scale_to.x() / scale_from.x(),
src_size.height() / 3 * scale_to.y() / scale_from.y());
const GLuint dst_texture = texture_helper_->CreateTexture(dst_rect.size());
// This is our "basis for comparison" image. If scaled output images match
// this, then there is no bleed-in.
SkBitmap output_without_bleed_in;
{
const bool did_scale =
scaler_->Scale(texture_helper_->UploadTexture(CreateBlueBoxOnRedImage(
src_size, gfx::Rect(src_size))),
src_size, gfx::Vector2d(0, 0), dst_texture, dst_rect);
CHECK(did_scale);
output_without_bleed_in =
texture_helper_->DownloadTexture(dst_texture, dst_rect.size());
VLOG(2) << scale_from.ToString() << "→" << scale_to.ToString()
<< ": Output without bleed-in is "
<< cc::GetPNGDataUrl(output_without_bleed_in);
}
// Perform a linear search for the minimal overscan values that do not cause
// the red bleed-in in the scaled output image. There are actually two
// separate searches here, one horizontally and one vertically. Note that an
// overscan result of -1 indicates a failed search and/or a broken
// implementation.
gfx::Vector2d min_overscan(5, 5);
for (int is_horizontal = 1; is_horizontal >= 0; --is_horizontal) {
while (min_overscan.x() >= 0 && min_overscan.y() >= 0) {
// Decrease the overscan by one pixel (one dimension at a time).
const gfx::Vector2d overscan(
is_horizontal ? (min_overscan.x() - 1) : min_overscan.x(),
is_horizontal ? min_overscan.y() : (min_overscan.y() - 1));
// Create the source texture consisting of a centered blue box
// surrounded by red.
const gfx::Rect blue_rect(scale_from.x() - overscan.x(),
scale_from.y() - overscan.y(),
scale_from.x() + 2 * overscan.x(),
scale_from.y() + 2 * overscan.y());
const SkBitmap source_image =
CreateBlueBoxOnRedImage(src_size, blue_rect);
const bool did_scale = scaler_->Scale(
texture_helper_->UploadTexture(source_image), src_size,
gfx::Vector2d(0, 0), dst_texture, dst_rect);
CHECK(did_scale);
const SkBitmap output =
texture_helper_->DownloadTexture(dst_texture, dst_rect.size());
// Compare |output| with |output_without_bleed_in|. If they are
// different, then the blue rect became too small.
bool output_has_bleed_in = false;
for (int y = 0; y < output.height(); ++y) {
for (int x = 0; x < output.width(); ++x) {
if (output.getColor(x, y) !=
output_without_bleed_in.getColor(x, y)) {
output_has_bleed_in = true;
break;
}
}
}
VLOG(2) << scale_from.ToString() << "→" << scale_to.ToString()
<< ": Testing overscan=" << overscan.ToString() << std::endl
<< "\tSource image is " << cc::GetPNGDataUrl(source_image)
<< std::endl
<< "\tOutput image is " << cc::GetPNGDataUrl(output);
if (output_has_bleed_in) {
break; // Search complete: Red bleed-in detected.
}
min_overscan = overscan;
}
}
return min_overscan;
}
static SkBitmap CreateBlueBoxOnRedImage(const gfx::Size& size,
const gfx::Rect& blue_rect) {
SkBitmap result = AllocateRGBABitmap(size);
// Note: None of the color channel values should be close to 0 or 255. This
// is because the bicubic scaler will generate values that overshoot and
// clip, and this will mess-up detection of the number of overscan pixels.
result.eraseColor(SkColorSetRGB(0xc0, 0x40, 0x40));
result.erase(SkColorSetRGB(0x40, 0x40, 0xc0),
SkIRect{blue_rect.x(), blue_rect.y(), blue_rect.right(),
blue_rect.bottom()});
return result;
}
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();
}
std::unique_ptr<GLScaler> scaler_;
gpu::gles2::GLES2Interface* gl_ = nullptr;
std::unique_ptr<GLScalerTestTextureHelper> texture_helper_;
};
namespace {
constexpr gfx::Rect kTenByTenRect = gfx::Rect(10, 10, 10, 10);
} // namespace
TEST_F(GLScalerOverscanPixelTest, Bilinear) {
constexpr struct {
gfx::Vector2d scale_from;
gfx::Vector2d scale_to;
gfx::Vector2d expected_overscan;
} kTestCases[] = {
// No scaling.
{gfx::Vector2d(32, 20), gfx::Vector2d(32, 20), gfx::Vector2d(0, 0)},
// Scale by 0.5X.
{gfx::Vector2d(32, 20), gfx::Vector2d(16, 20), gfx::Vector2d(0, 0)},
{gfx::Vector2d(32, 20), gfx::Vector2d(32, 10), gfx::Vector2d(0, 0)},
{gfx::Vector2d(32, 20), gfx::Vector2d(16, 10), gfx::Vector2d(0, 0)},
// Scale by 0.75X.
{gfx::Vector2d(32, 20), gfx::Vector2d(24, 20), gfx::Vector2d(0, 0)},
{gfx::Vector2d(32, 20), gfx::Vector2d(32, 15), gfx::Vector2d(0, 0)},
{gfx::Vector2d(32, 20), gfx::Vector2d(24, 15), gfx::Vector2d(0, 0)},
// Scale by 1.5X.
{gfx::Vector2d(32, 20), gfx::Vector2d(48, 20), gfx::Vector2d(1, 0)},
{gfx::Vector2d(32, 20), gfx::Vector2d(32, 30), gfx::Vector2d(0, 1)},
{gfx::Vector2d(32, 20), gfx::Vector2d(48, 30), gfx::Vector2d(1, 1)},
// Scale by 4X.
{gfx::Vector2d(32, 20), gfx::Vector2d(128, 20), gfx::Vector2d(1, 0)},
{gfx::Vector2d(32, 20), gfx::Vector2d(32, 80), gfx::Vector2d(0, 1)},
{gfx::Vector2d(32, 20), gfx::Vector2d(128, 80), gfx::Vector2d(1, 1)},
};
for (const auto& tc : kTestCases) {
SCOPED_TRACE(testing::Message() << "scale_from=" << tc.scale_from.ToString()
<< ", scale_to=" << tc.scale_to.ToString());
// Test the effect on the pixels.
UseScaler(Shader::BILINEAR, Axis::HORIZONTAL, tc.scale_from, tc.scale_to);
EXPECT_EQ(tc.expected_overscan,
DetectScalerOverscan(tc.scale_from, tc.scale_to));
// Sanity-check that the internal math estimating the overscan is correct.
gfx::Rect expected_input_rect = kTenByTenRect;
expected_input_rect.Inset(-tc.expected_overscan.x(),
-tc.expected_overscan.y());
EXPECT_EQ(expected_input_rect, ToInputRect(kTenByTenRect));
}
}
TEST_F(GLScalerOverscanPixelTest, TwoTapBilinear) {
constexpr struct {
Axis primary_axis;
gfx::Vector2d scale_from;
gfx::Vector2d scale_to;
gfx::Vector2d expected_overscan;
} kTestCases[] = {
// Scale by 0.25X in one direction only.
{Axis::HORIZONTAL, gfx::Vector2d(64, 40), gfx::Vector2d(16, 40),
gfx::Vector2d(0, 0)},
{Axis::VERTICAL, gfx::Vector2d(64, 40), gfx::Vector2d(64, 10),
gfx::Vector2d(0, 0)},
// Scale by 0.25X in one direction, 0.5X in the other.
{Axis::HORIZONTAL, gfx::Vector2d(64, 40), gfx::Vector2d(16, 20),
gfx::Vector2d(0, 0)},
{Axis::VERTICAL, gfx::Vector2d(64, 40), gfx::Vector2d(32, 10),
gfx::Vector2d(0, 0)},
// Scale by 0.75X (1.5X * 0.5X).
{Axis::HORIZONTAL, gfx::Vector2d(64, 40), gfx::Vector2d(48, 40),
gfx::Vector2d(1, 0)},
{Axis::VERTICAL, gfx::Vector2d(64, 40), gfx::Vector2d(64, 30),
gfx::Vector2d(0, 1)},
};
for (const auto& tc : kTestCases) {
SCOPED_TRACE(testing::Message() << "scale_from=" << tc.scale_from.ToString()
<< ", scale_to=" << tc.scale_to.ToString());
// Test the effect on the pixels.
UseScaler(Shader::BILINEAR2, tc.primary_axis, tc.scale_from, tc.scale_to);
EXPECT_EQ(tc.expected_overscan,
DetectScalerOverscan(tc.scale_from, tc.scale_to));
// Sanity-check that the internal math estimating the overscan is correct.
gfx::Rect expected_input_rect = kTenByTenRect;
expected_input_rect.Inset(-tc.expected_overscan.x(),
-tc.expected_overscan.y());
EXPECT_EQ(expected_input_rect, ToInputRect(kTenByTenRect));
}
}
TEST_F(GLScalerOverscanPixelTest, ThreeTapBilinear) {
constexpr struct {
Axis primary_axis;
gfx::Vector2d scale_from;
gfx::Vector2d scale_to;
gfx::Vector2d expected_overscan;
} kTestCases[] = {
// Scale by 0.16...X in one direction only.
{Axis::HORIZONTAL, gfx::Vector2d(66, 40), gfx::Vector2d(11, 40),
gfx::Vector2d(0, 0)},
{Axis::VERTICAL, gfx::Vector2d(32, 60), gfx::Vector2d(32, 10),
gfx::Vector2d(0, 0)},
// Scale by 0.16...X in one direction, 0.5X in the other.
{Axis::HORIZONTAL, gfx::Vector2d(66, 40), gfx::Vector2d(11, 20),
gfx::Vector2d(0, 0)},
{Axis::VERTICAL, gfx::Vector2d(64, 60), gfx::Vector2d(32, 10),
gfx::Vector2d(0, 0)},
// Scale by 0.75X (3.0X * 0.5X * 0.5X).
{Axis::HORIZONTAL, gfx::Vector2d(64, 40), gfx::Vector2d(48, 40),
gfx::Vector2d(1, 0)},
{Axis::VERTICAL, gfx::Vector2d(64, 40), gfx::Vector2d(64, 30),
gfx::Vector2d(0, 1)},
};
for (const auto& tc : kTestCases) {
SCOPED_TRACE(testing::Message() << "scale_from=" << tc.scale_from.ToString()
<< ", scale_to=" << tc.scale_to.ToString());
// Test the effect on the pixels.
UseScaler(Shader::BILINEAR3, tc.primary_axis, tc.scale_from, tc.scale_to);
EXPECT_EQ(tc.expected_overscan,
DetectScalerOverscan(tc.scale_from, tc.scale_to));
// Sanity-check that the internal math estimating the overscan is correct.
gfx::Rect expected_input_rect = kTenByTenRect;
expected_input_rect.Inset(-tc.expected_overscan.x(),
-tc.expected_overscan.y());
EXPECT_EQ(expected_input_rect, ToInputRect(kTenByTenRect));
}
}
TEST_F(GLScalerOverscanPixelTest, FourTapBilinear) {
constexpr struct {
Axis primary_axis;
gfx::Vector2d scale_from;
gfx::Vector2d scale_to;
gfx::Vector2d expected_overscan;
} kTestCases[] = {
// Scale by 0.125X in one direction only.
{Axis::HORIZONTAL, gfx::Vector2d(64, 40), gfx::Vector2d(8, 40),
gfx::Vector2d(0, 0)},
{Axis::VERTICAL, gfx::Vector2d(64, 40), gfx::Vector2d(64, 5),
gfx::Vector2d(0, 0)},
// Scale by 0.125X in one direction, 0.5X in the other.
{Axis::HORIZONTAL, gfx::Vector2d(64, 40), gfx::Vector2d(8, 20),
gfx::Vector2d(0, 0)},
{Axis::VERTICAL, gfx::Vector2d(64, 40), gfx::Vector2d(32, 5),
gfx::Vector2d(0, 0)},
// Scale by 0.75X (6.0X * 0.5X * 0.5X * 0.5X).
{Axis::HORIZONTAL, gfx::Vector2d(64, 40), gfx::Vector2d(48, 40),
gfx::Vector2d(1, 0)},
{Axis::VERTICAL, gfx::Vector2d(64, 40), gfx::Vector2d(64, 30),
gfx::Vector2d(0, 1)},
};
for (const auto& tc : kTestCases) {
SCOPED_TRACE(testing::Message() << "scale_from=" << tc.scale_from.ToString()
<< ", scale_to=" << tc.scale_to.ToString());
// Test the effect on the pixels.
UseScaler(Shader::BILINEAR4, tc.primary_axis, tc.scale_from, tc.scale_to);
EXPECT_EQ(tc.expected_overscan,
DetectScalerOverscan(tc.scale_from, tc.scale_to));
// Sanity-check that the internal math estimating the overscan is correct.
gfx::Rect expected_input_rect = kTenByTenRect;
expected_input_rect.Inset(-tc.expected_overscan.x(),
-tc.expected_overscan.y());
EXPECT_EQ(expected_input_rect, ToInputRect(kTenByTenRect));
}
}
TEST_F(GLScalerOverscanPixelTest, TwoByTwoTapBilinear) {
constexpr struct {
gfx::Vector2d scale_from;
gfx::Vector2d scale_to;
gfx::Vector2d expected_overscan;
} kTestCases[] = {
// Scale by 0.25X in both directions.
{gfx::Vector2d(64, 40), gfx::Vector2d(16, 10), gfx::Vector2d(0, 0)},
// Scale by 0.75X (1.5X * 0.5X) in one direction, 0.25X in the other.
{gfx::Vector2d(64, 40), gfx::Vector2d(48, 10), gfx::Vector2d(1, 0)},
{gfx::Vector2d(64, 40), gfx::Vector2d(16, 30), gfx::Vector2d(0, 1)},
// Scale by 0.75X (1.5X * 0.5X) in both directions.
{gfx::Vector2d(64, 40), gfx::Vector2d(48, 30), gfx::Vector2d(1, 1)},
};
for (const auto& tc : kTestCases) {
SCOPED_TRACE(testing::Message() << "scale_from=" << tc.scale_from.ToString()
<< ", scale_to=" << tc.scale_to.ToString());
// Test the effect on the pixels.
UseScaler(Shader::BILINEAR2X2, Axis::HORIZONTAL, tc.scale_from,
tc.scale_to);
EXPECT_EQ(tc.expected_overscan,
DetectScalerOverscan(tc.scale_from, tc.scale_to));
// Sanity-check that the internal math estimating the overscan is correct.
gfx::Rect expected_input_rect = kTenByTenRect;
expected_input_rect.Inset(-tc.expected_overscan.x(),
-tc.expected_overscan.y());
EXPECT_EQ(expected_input_rect, ToInputRect(kTenByTenRect));
}
}
TEST_F(GLScalerOverscanPixelTest, BicubicUpscale) {
#if defined(OS_ANDROID)
// Unfortunately, on our current Android bots, there are some inaccuracies
// introduced by the platform that seem to throw-off the pixel testing of the
// bicubic sampler.
constexpr bool kSkipDetectionTest = true;
LOG(WARNING) << "Skipping overscan detection due to platform issues.";
#else
constexpr bool kSkipDetectionTest = false;
#endif
constexpr struct {
Axis primary_axis;
gfx::Vector2d scale_from;
gfx::Vector2d scale_to;
gfx::Vector2d expected_overscan;
} kTestCases[] = {
// Scale by 1.5X, 2X, and 3.3...X horizontally.
{Axis::HORIZONTAL, gfx::Vector2d(12, 10), gfx::Vector2d(18, 10),
gfx::Vector2d(2, 0)},
{Axis::HORIZONTAL, gfx::Vector2d(12, 10), gfx::Vector2d(24, 10),
gfx::Vector2d(2, 0)},
{Axis::HORIZONTAL, gfx::Vector2d(12, 10), gfx::Vector2d(40, 10),
gfx::Vector2d(2, 0)},
// Scale by 1.5X, 2X, and 3.3...X vertically.
{Axis::VERTICAL, gfx::Vector2d(12, 10), gfx::Vector2d(12, 15),
gfx::Vector2d(0, 2)},
{Axis::VERTICAL, gfx::Vector2d(12, 10), gfx::Vector2d(12, 20),
gfx::Vector2d(0, 2)},
{Axis::VERTICAL, gfx::Vector2d(12, 9), gfx::Vector2d(12, 30),
gfx::Vector2d(0, 2)},
};
for (const auto& tc : kTestCases) {
SCOPED_TRACE(testing::Message() << "scale_from=" << tc.scale_from.ToString()
<< ", scale_to=" << tc.scale_to.ToString());
// Test the effect on the pixels.
UseScaler(Shader::BICUBIC_UPSCALE, tc.primary_axis, tc.scale_from,
tc.scale_to);
if (!kSkipDetectionTest) {
EXPECT_EQ(tc.expected_overscan,
DetectScalerOverscan(tc.scale_from, tc.scale_to));
}
// Sanity-check that the internal math estimating the overscan is correct.
gfx::Rect expected_input_rect = kTenByTenRect;
expected_input_rect.Inset(-tc.expected_overscan.x(),
-tc.expected_overscan.y());
EXPECT_EQ(expected_input_rect, ToInputRect(kTenByTenRect));
}
}
TEST_F(GLScalerOverscanPixelTest, BicubicHalving) {
constexpr struct {
Axis primary_axis;
gfx::Vector2d scale_from;
gfx::Vector2d scale_to;
gfx::Vector2d expected_overscan;
} kTestCases[] = {
{Axis::HORIZONTAL, gfx::Vector2d(16, 16), gfx::Vector2d(8, 16),
gfx::Vector2d(3, 0)},
{Axis::VERTICAL, gfx::Vector2d(16, 16), gfx::Vector2d(16, 8),
gfx::Vector2d(0, 3)},
};
for (const auto& tc : kTestCases) {
SCOPED_TRACE(testing::Message() << "scale_from=" << tc.scale_from.ToString()
<< ", scale_to=" << tc.scale_to.ToString());
// Test the effect on the pixels.
UseScaler(Shader::BICUBIC_HALF_1D, tc.primary_axis, tc.scale_from,
tc.scale_to);
EXPECT_EQ(tc.expected_overscan,
DetectScalerOverscan(tc.scale_from, tc.scale_to));
// Sanity-check that the internal math estimating the overscan is correct.
gfx::Rect expected_input_rect = kTenByTenRect;
expected_input_rect.Inset(-tc.expected_overscan.x(),
-tc.expected_overscan.y());
EXPECT_EQ(expected_input_rect, ToInputRect(kTenByTenRect));
}
}
TEST_F(GLScalerOverscanPixelTest, Planerizers) {
if (!AreMultipleRenderingTargetsSupported()) {
LOG(WARNING) << "Skipping test due to lack of MRT support on this machine.";
return;
}
constexpr struct {
Shader shader;
Axis primary_axis;
gfx::Vector2d scale_from;
gfx::Vector2d scale_to;
} kTestCases[] = {
{Shader::PLANAR_CHANNEL_0, Axis::HORIZONTAL, gfx::Vector2d(16, 16),
gfx::Vector2d(4, 16)},
// Note: Other PLANAR_CHANNEL_N shaders don't need to be tested since they
// use the same code path.
{Shader::I422_NV61_MRT, Axis::HORIZONTAL, gfx::Vector2d(16, 16),
gfx::Vector2d(4, 16)},
{
Shader::DEINTERLEAVE_PAIRWISE_MRT, Axis::HORIZONTAL,
gfx::Vector2d(16, 16), gfx::Vector2d(8, 16),
},
};
for (const auto& tc : kTestCases) {
SCOPED_TRACE(testing::Message()
<< "shader=" << static_cast<int>(tc.shader)
<< ", scale_from=" << tc.scale_from.ToString()
<< ", scale_to=" << tc.scale_to.ToString());
// Test the effect on the pixels.
UseScaler(tc.shader, tc.primary_axis, tc.scale_from, tc.scale_to);
EXPECT_EQ(gfx::Vector2d(0, 0),
DetectScalerOverscan(tc.scale_from, tc.scale_to));
// Sanity-check that the internal math estimating the overscan is correct.
EXPECT_EQ(kTenByTenRect, ToInputRect(kTenByTenRect));
}
}
} // namespace viz
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