Commit 8bb8b562 authored by Yuri Wiitala's avatar Yuri Wiitala Committed by Commit Bot

Add GLI420Converter: Uses GLScaler to produce I420 video images.

This is the replacement for GLHelper::I420Converter. It uses the new
GLScaler to efficiently scale and re-format RGBA textures into I420
image planes. In a soon-upcoming CL, GLRendererCopier will be switched
over to use this.

Includes unit and pixel testing.

Bug: 870036, 810131
Change-Id: I465eb933e7eadeb205676211432cced361a3739b
Reviewed-on: https://chromium-review.googlesource.com/c/1330830Reviewed-by: default avatarRia Jiang <riajiang@chromium.org>
Commit-Queue: Yuri Wiitala <miu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#608182}
parent 17771fad
......@@ -89,6 +89,8 @@ viz_component("common") {
"gl_helper.h",
"gl_helper_scaling.cc",
"gl_helper_scaling.h",
"gl_i420_converter.cc",
"gl_i420_converter.h",
"gl_scaler.cc",
"gl_scaler.h",
"gpu/context_cache_controller.cc",
......@@ -237,6 +239,8 @@ viz_source_set("unit_tests") {
"frame_sinks/copy_output_util_unittest.cc",
"frame_sinks/delay_based_time_source_unittest.cc",
"gl_helper_unittest.cc",
"gl_i420_converter_pixeltest.cc",
"gl_i420_converter_unittest.cc",
"gl_scaler_overscan_pixeltest.cc",
"gl_scaler_pixeltest.cc",
"gl_scaler_shader_pixeltest.cc",
......
// 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_i420_converter.h"
#include <utility>
#include "components/viz/common/gpu/context_provider.h"
namespace viz {
GLI420Converter::GLI420Converter(
scoped_refptr<ContextProvider> context_provider)
: GLI420Converter(std::move(context_provider), true) {}
GLI420Converter::GLI420Converter(
scoped_refptr<ContextProvider> context_provider,
bool allow_mrt_path)
: context_provider_(std::move(context_provider)),
step1_(context_provider_),
step2_(context_provider_) {
context_provider_->AddObserver(this);
if (!allow_mrt_path || step1_.GetMaxDrawBuffersSupported() < 2) {
step3_ = std::make_unique<GLScaler>(context_provider_);
step4_ = std::make_unique<GLScaler>(context_provider_);
}
}
GLI420Converter::~GLI420Converter() {
OnContextLost(); // Free context-related resources.
}
bool GLI420Converter::Configure(const Parameters& params) {
Parameters step1_params = params;
if (!step1_params.output_color_space.IsValid()) {
step1_params.output_color_space = gfx::ColorSpace::CreateREC709();
}
// Configure the "step 1" scaler.
if (is_using_mrt_path()) {
step1_params.export_format = Parameters::ExportFormat::NV61;
DCHECK_EQ(step1_params.swizzle[0], params.swizzle[0]);
step1_params.swizzle[1] = GL_RGBA; // Don't swizzle 2nd rendering target.
} else {
step1_params.export_format = Parameters::ExportFormat::INTERLEAVED_QUADS;
step1_params.swizzle[0] = GL_RGBA; // Will swizzle in steps 2-4.
}
if (!step1_.Configure(step1_params)) {
return false;
}
// Configure the "step 2" scaler (and steps 3 and 4 for the non-MRT path) that
// further transform the output from the "step 1" scaler to produce the final
// outputs.
Parameters step2_params;
step2_params.scale_to = gfx::Vector2d(1, 1);
step2_params.source_color_space = step1_params.output_color_space;
step2_params.output_color_space = step1_params.output_color_space;
// Use FAST quality, a single bilinear pass, because there will either be no
// scaling or exactly 50% scaling.
step2_params.quality = Parameters::Quality::FAST;
step2_params.swizzle[0] = params.swizzle[0];
if (is_using_mrt_path()) {
// NV61 provides half-width and full-height U/V. I420 U/V planes are
// half-width and half-height. So, scale Y by 50%.
step2_params.scale_from = gfx::Vector2d(1, 2);
step2_params.export_format =
Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE;
step2_params.swizzle[1] = step2_params.swizzle[0];
if (!step2_.Configure(step2_params)) {
return false;
}
} else {
// Extract a full-size Y plane from the interleaved YUVA from step 1.
step2_params.scale_from = gfx::Vector2d(1, 1);
step2_params.export_format = Parameters::ExportFormat::CHANNEL_0;
if (!step2_.Configure(step2_params)) {
return false;
}
// Extract half-size U/V planes from the interleaved YUVA from step 1.
step2_params.scale_from = gfx::Vector2d(2, 2);
step2_params.export_format = Parameters::ExportFormat::CHANNEL_1;
if (!step3_->Configure(step2_params)) {
return false;
}
step2_params.export_format = Parameters::ExportFormat::CHANNEL_2;
if (!step4_->Configure(step2_params)) {
return false;
}
}
params_ = params;
return true;
}
bool GLI420Converter::Convert(GLuint src_texture,
const gfx::Size& src_texture_size,
const gfx::Vector2d& src_offset,
const gfx::Rect& output_rect,
const GLuint yuv_textures[3]) {
DCHECK_EQ(output_rect.x() % 8, 0);
DCHECK_EQ(output_rect.width() % 8, 0);
DCHECK_EQ(output_rect.y() % 2, 0);
DCHECK_EQ(output_rect.height() % 2, 0);
if (!context_provider_) {
return false;
}
if (is_using_mrt_path()) {
const gfx::Rect luma_output_rect(output_rect.x() / 4, output_rect.y(),
output_rect.width() / 4,
output_rect.height());
EnsureIntermediateTextureDefined(luma_output_rect.size());
const gfx::Rect chroma_output_rect(
gfx::Size(luma_output_rect.width() / 2, luma_output_rect.height() / 2));
return (step1_.ScaleToMultipleOutputs(
src_texture, src_texture_size, src_offset, yuv_textures[0],
intermediate_texture_, luma_output_rect) &&
step2_.ScaleToMultipleOutputs(intermediate_texture_,
intermediate_texture_size_,
gfx::Vector2d(), yuv_textures[1],
yuv_textures[2], chroma_output_rect));
}
// Non-MRT path:
EnsureIntermediateTextureDefined(output_rect.size());
const gfx::Rect luma_output_rect(0, 0, output_rect.width() / 4,
output_rect.height());
const gfx::Rect chroma_output_rect(0, 0, luma_output_rect.width() / 2,
luma_output_rect.height() / 2);
return (step1_.Scale(src_texture, src_texture_size, src_offset,
intermediate_texture_, output_rect) &&
step2_.Scale(intermediate_texture_, intermediate_texture_size_,
gfx::Vector2d(), yuv_textures[0], luma_output_rect) &&
step3_->Scale(intermediate_texture_, intermediate_texture_size_,
gfx::Vector2d(), yuv_textures[1], chroma_output_rect) &&
step4_->Scale(intermediate_texture_, intermediate_texture_size_,
gfx::Vector2d(), yuv_textures[2], chroma_output_rect));
}
// static
gfx::Rect GLI420Converter::ToAlignedRect(const gfx::Rect& rect) {
// Origin coordinates: FLOOR(...)
const int aligned_x =
((rect.x() < 0) ? ((rect.x() - 7) / 8) : (rect.x() / 8)) * 8;
const int aligned_y =
((rect.y() < 0) ? ((rect.y() - 1) / 2) : (rect.y() / 2)) * 2;
// Span coordinates: CEIL(...)
const int aligned_right =
((rect.right() < 0) ? (rect.right() / 8) : ((rect.right() + 7) / 8)) * 8;
const int aligned_bottom =
((rect.bottom() < 0) ? (rect.bottom() / 2) : ((rect.bottom() + 1) / 2)) *
2;
return gfx::Rect(aligned_x, aligned_y, aligned_right - aligned_x,
aligned_bottom - aligned_y);
}
// static
bool GLI420Converter::ParametersAreEquivalent(const Parameters& a,
const Parameters& b) {
const auto Resolve = [](Parameters params) {
// Per header comments, if an invalid output_color_space is specified, use
// REC709.
if (!params.output_color_space.IsValid()) {
params.output_color_space = gfx::ColorSpace::CreateREC709();
}
// Both of these fields are overwritten, in Configure(), whether the MRT
// path is going to be used or not. So, for the purposes of "equivalence,"
// just set these like the MRT path would.
params.export_format = Parameters::ExportFormat::NV61;
params.swizzle[1] = GL_RGBA;
return params;
};
return GLScaler::ParametersAreEquivalent(Resolve(a), Resolve(b));
}
void GLI420Converter::EnsureIntermediateTextureDefined(
const gfx::Size& required) {
if (intermediate_texture_size_ == required) {
return;
}
auto* const gl = context_provider_->ContextGL();
if (intermediate_texture_ == 0) {
gl->GenTextures(1, &intermediate_texture_);
}
gl->BindTexture(GL_TEXTURE_2D, intermediate_texture_);
gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, required.width(), required.height(),
0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
intermediate_texture_size_ = required;
}
void GLI420Converter::OnContextLost() {
if (intermediate_texture_ != 0) {
if (auto* gl = context_provider_->ContextGL()) {
gl->DeleteTextures(1, &intermediate_texture_);
}
intermediate_texture_ = 0;
intermediate_texture_size_ = gfx::Size();
}
if (context_provider_) {
context_provider_->RemoveObserver(this);
context_provider_ = nullptr;
}
}
} // namespace viz
// 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.
#ifndef COMPONENTS_VIZ_COMMON_GL_I420_CONVERTER_H_
#define COMPONENTS_VIZ_COMMON_GL_I420_CONVERTER_H_
#include <memory>
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "components/viz/common/gl_scaler.h"
#include "components/viz/common/gpu/context_lost_observer.h"
#include "components/viz/common/viz_common_export.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/size.h"
namespace gfx {
class Rect;
class Vector2d;
} // namespace gfx
namespace viz {
class ContextProvider;
// A convenience wrapper around GLScaler that also reformats the scaler's output
// from interleaved RGBA to I420 planes. The I420 format consists of three
// planes of image data: the Y (luma) plane at full size, plus U and V (chroma)
// planes at half-width and half-height. There are two possible modes of
// operation (auto-detected at runtime):
//
// The faster, multiple rendering target (MRT) path: If the platform supports
// MRTs (most of the GPUs in use today), scaling and conversion is a two step
// process:
//
// Step 1: Produce NV61 format output, a luma plane and a UV-interleaved
// image. The luma plane is the same as the desired I420 luma plane. Note,
// that the UV image is of half-width but not yet half-height.
//
// (interleaved quads)
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA
// |
// | (luma plane) (chroma, interleaved)
// | YYYY YYYY UVUV UVUV
// +---> { YYYY YYYY + UVUV UVUV }
// YYYY YYYY UVUV UVUV
// YYYY YYYY UVUV UVUV
//
// Step 2: Derives the two I420 chroma planes from the UV-interleaved image
// from Step 1. This step separates the U and V pixels into separate planes,
// and also scales the height by half. This produces the desired I420 chroma
// planes.
//
// (chroma, interleaved) (two chroma planes)
// UVUV UVUV
// UVUV UVUV --> { UUUU + VVVV }
// UVUV UVUV UUUU VVVV
// UVUV UVUV
//
// The non-MRT path: For platforms that can only render to a single target at a
// time. This first scales the source to its final size and color-converts,
// transforming an RGBA input into a YUVA output. Then, it scans the YUVA image
// three times to generate each of the Y+U+V planes.
//
// Texture packing: OpenGLES2 treats all of the input and output textures as
// RGBA format. See comments for the Convert() method, which explains how the
// planar image data is packed into GL_RGBA textures, how the output textures
// should be sized, and why there are alignment requirements when specifying the
// output rect.
class VIZ_COMMON_EXPORT GLI420Converter : public ContextLostObserver {
public:
// GLI420Converter uses the exact same parameters as GLScaler.
using Parameters = GLScaler::Parameters;
explicit GLI420Converter(scoped_refptr<ContextProvider> context_provider);
~GLI420Converter() final;
// Returns true if the GL context provides the necessary support for enabling
// precise color management (see Parameters::enable_precise_color_management).
bool SupportsPreciseColorManagement() const {
return step1_.SupportsPreciseColorManagement();
}
// [Re]Configure the converter with the given |new_params|. Returns true on
// success, or false on failure. If |new_params| does not specify an
// |output_color_space|, it will be default to REC709.
bool Configure(const Parameters& new_params) WARN_UNUSED_RESULT;
// Returns the currently-configured and resolved Parameters. Results are
// undefined if Configure() has never been called successfully.
const Parameters& params() const { return params_; }
// Scales a portion of |src_texture|, then format-converts it to three I420
// planes, placing the results into |yuv_textures| at offset (0, 0). Returns
// true to indicate success, or false if this GLI420Converter is not valid.
//
// |src_texture_size| is the full, allocated size of the |src_texture|. This
// is required for computing texture coordinate transforms (and only because
// the OpenGL ES 2.0 API lacks the ability to query this info).
//
// |src_offset| is the offset in the source texture corresponding to point
// (0,0) in the source/output coordinate spaces. This prevents the need for
// extra texture copies just to re-position the source coordinate system.
//
// |aligned_output_rect| selects the region to draw (in the scaled, not the
// source, coordinate space). This is used to save work in cases where only a
// portion needs to be re-scaled. Because of the way the planar image data is
// packed in the output textures, the output rect's coordinates must be
// aligned (see ToAlignedRect() below).
//
// The |yuv_textures| are packed with planar data, meaning that each RGBA quad
// contains four pixel values: R is pixel 0, G is pixel 1, and so on. This
// makes it trivial to read-back the textures from a pixel buffer as a
// sequence of unsigned bytes. Thus, the output texture for the Y plane should
// be defined as GL_RGBA and be at least 1/4 the width of that specified in
// |aligned_output_rect|. Similarly, the output textures for the U and V
// planes should be defined as GL_RGBA and have at least 1/8 the width and 1/2
// the height of |aligned_output_rect|.
//
// WARNING: The output will always be placed at (0, 0) in the output textures,
// and not at |aligned_output_rect.origin()|.
//
// Note that the |src_texture| will have the min/mag filter set to GL_LINEAR
// and wrap_s/t set to CLAMP_TO_EDGE in this call.
bool Convert(GLuint src_texture,
const gfx::Size& src_texture_size,
const gfx::Vector2d& src_offset,
const gfx::Rect& aligned_output_rect,
const GLuint yuv_textures[3]);
// Returns the smallest Rect that encloses |rect| and lays on aligned
// boundaries, as required by the |aligned_output_rect| argument passed to
// Convert(). The horizontal coordinates will always be a multiple of 8, and
// the vertical coordinates a multiple of 2.
static gfx::Rect ToAlignedRect(const gfx::Rect& rect);
// Returns true if configuring a GLI420Converter with either |a| or |b| will
// produce identical behaviors and results.
static bool ParametersAreEquivalent(const Parameters& a, const Parameters& b);
private:
friend class GLI420ConverterPixelTest;
GLI420Converter(scoped_refptr<ContextProvider> context_provider,
bool allow_mrt_path);
bool is_using_mrt_path() const { return !step3_; }
// Creates or re-defines the intermediate texture, to ensure a texture of the
// given |required| size is defined.
void EnsureIntermediateTextureDefined(const gfx::Size& required);
// ContextLostObserver implementation.
void OnContextLost() final;
// The provider of the GL context. This is non-null while the GL context is
// valid and GLI420Converter is observing for context loss.
scoped_refptr<ContextProvider> context_provider_;
// Scales the source content and produces either:
// * MRT path: NV61-format output in two textures.
// * Non-MRT path: YUVA interleaved output in one texture.
GLScaler step1_;
// Holds the results from executing the first-stage |scaler_|, and is read by
// the other scalers:
// * MRT path: This holds the UV-interleaved data (2nd rendering target).
// * Non-MRT path: The scaled YUVA interleaved data.
GLuint intermediate_texture_ = 0;
gfx::Size intermediate_texture_size_;
// Step 2 operation using the |intermediate_texture_| as input:
// * MRT path: Separates-out the U and V planes (and scales height by half).
// * Non-MRT path: Extracts the luma plane.
GLScaler step2_;
// Steps 3 and 4 are used by the non-MRT path only, to extract the two chroma
// planes from |intermediate_texture_|.
std::unique_ptr<GLScaler> step3_;
std::unique_ptr<GLScaler> step4_;
// The Parameters that were provided to the last successful Configure() call.
Parameters params_;
DISALLOW_COPY_AND_ASSIGN(GLI420Converter);
};
} // namespace viz
#endif // COMPONENTS_VIZ_COMMON_GL_I420_CONVERTER_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_i420_converter.h"
#include "cc/test/pixel_test.h"
#include "cc/test/pixel_test_utils.h"
#include "components/viz/common/gl_scaler_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
namespace viz {
class GLI420ConverterPixelTest : public cc::PixelTest,
public GLScalerTestUtil,
public testing::WithParamInterface<bool> {
public:
bool allow_mrt_path() const { return GetParam(); }
GLI420Converter* converter() const { return converter_.get(); }
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);
}
protected:
void SetUp() final {
cc::PixelTest::SetUpGLWithoutRenderer(false);
converter_.reset(new GLI420Converter(context_provider(), allow_mrt_path()));
texture_helper_ = std::make_unique<GLScalerTestTextureHelper>(
context_provider()->ContextGL());
}
void TearDown() final {
texture_helper_.reset();
converter_.reset();
cc::PixelTest::TearDown();
}
private:
std::unique_ptr<GLI420Converter> converter_;
std::unique_ptr<GLScalerTestTextureHelper> texture_helper_;
};
// Note: This test is pretty much the same as
// GLScalerPixelTest.Example_ScaleAndExportForScreenVideoCapture. The goal of
// this pixel test is to just confirm that everything internal to
// GLI420Converter has been plumbed-through correctly.
TEST_P(GLI420ConverterPixelTest, ScaleAndConvert) {
// These parameters have been chosen based on: 1) overriding defaults, to
// confirm Parameters plumbing; and 2) typical operation on most platforms
// (e.g., flipped source textures, the need to swizzle outputs, etc.).
GLI420Converter::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.enable_precise_color_management =
converter()->SupportsPreciseColorManagement();
params.quality = GLScaler::Parameters::Quality::GOOD;
params.is_flipped_source = true;
params.flip_output = true;
params.swizzle[0] = GL_BGRA_EXT; // Swizzle for readback.
ASSERT_TRUE(converter()->Configure(params));
constexpr gfx::Size kSourceSize = gfx::Size(2160, 1440);
const GLuint src_texture = UploadTexture(
CreateVerticallyFlippedBitmap(CreateSMPTETestImage(kSourceSize)));
constexpr gfx::Rect kOutputRect = gfx::Rect(0, 0, 1280, 720);
ASSERT_EQ(kOutputRect, GLI420Converter::ToAlignedRect(kOutputRect));
SkBitmap expected = CreateSMPTETestImage(kOutputRect.size());
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 (for the Y
// plane). Then, the other two planes are half the size of the Y plane in both
// dimensions.
const gfx::Size y_plane_size(kOutputRect.width() / 4, kOutputRect.height());
const gfx::Size chroma_plane_size(y_plane_size.width() / 2,
y_plane_size.height() / 2);
const GLuint yuv_textures[3] = {CreateTexture(y_plane_size),
CreateTexture(chroma_plane_size),
CreateTexture(chroma_plane_size)};
ASSERT_TRUE(converter()->Convert(src_texture, kSourceSize, gfx::Vector2d(),
kOutputRect, yuv_textures));
// Download the textures, and unpack them into an interleaved YUV bitmap, for
// comparison against the |expected| rendition.
SkBitmap actual = AllocateRGBABitmap(kOutputRect.size());
actual.eraseColor(SkColorSetARGB(0xff, 0x00, 0x80, 0x80));
SkBitmap y_plane = DownloadTexture(yuv_textures[0], y_plane_size);
SwizzleBitmap(&y_plane);
UnpackPlanarBitmap(y_plane, 0, &actual);
SkBitmap u_plane = DownloadTexture(yuv_textures[1], chroma_plane_size);
SwizzleBitmap(&u_plane);
UnpackPlanarBitmap(u_plane, 1, &actual);
SkBitmap v_plane = DownloadTexture(yuv_textures[2], chroma_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);
}
// Run the tests twice, once disallowing use of the MRT path, and once allowing
// its use (auto-detecting whether the current platform supports it).
INSTANTIATE_TEST_CASE_P(, GLI420ConverterPixelTest, testing::Bool());
} // namespace viz
// 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_i420_converter.h"
#include <string>
#include "base/strings/stringprintf.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/gfx/geometry/rect.h"
namespace viz {
namespace {
// Syntactic convenience: It's clearer to express the tests in terms of
// left+top+right+bottom coordinates, rather than gfx::Rect's x+y+width+height
// scheme.
SkIRect ToAlignedRect(const SkIRect& rect) {
const gfx::Rect& result = GLI420Converter::ToAlignedRect(
gfx::Rect(rect.fLeft, rect.fTop, rect.fRight - rect.fLeft,
rect.fBottom - rect.fTop));
return SkIRect{result.x(), result.y(), result.right(), result.bottom()};
}
// Logging convenience.
std::string ToString(const SkIRect& rect) {
return base::StringPrintf("%d,%d~%d%d", rect.fLeft, rect.fTop, rect.fRight,
rect.fBottom);
}
TEST(GLI420ConverterTest, AlignsOutputRects) {
struct {
SkIRect expected;
SkIRect input;
} kTestCases[] = {
{SkIRect{0, 0, 0, 0}, SkIRect{0, 0, 0, 0}},
{SkIRect{-16, 0, 16, 4}, SkIRect{-9, 0, 16, 4}},
{SkIRect{-8, 0, 16, 4}, SkIRect{-8, 0, 16, 4}},
{SkIRect{-8, 0, 16, 4}, SkIRect{-7, 0, 16, 4}},
{SkIRect{-8, 0, 16, 4}, SkIRect{-1, 0, 16, 4}},
{SkIRect{0, 0, 16, 4}, SkIRect{0, 0, 16, 4}},
{SkIRect{0, 0, 16, 4}, SkIRect{1, 0, 16, 4}},
{SkIRect{0, 0, 16, 4}, SkIRect{7, 0, 16, 4}},
{SkIRect{8, 0, 16, 4}, SkIRect{8, 0, 16, 4}},
{SkIRect{8, 0, 16, 4}, SkIRect{9, 0, 16, 4}},
{SkIRect{0, -4, 16, 4}, SkIRect{0, -3, 16, 4}},
{SkIRect{0, -2, 16, 4}, SkIRect{0, -2, 16, 4}},
{SkIRect{0, -2, 16, 4}, SkIRect{0, -1, 16, 4}},
{SkIRect{0, 0, 16, 4}, SkIRect{0, 0, 16, 4}},
{SkIRect{0, 0, 16, 4}, SkIRect{0, 1, 16, 4}},
{SkIRect{0, 2, 16, 4}, SkIRect{0, 2, 16, 4}},
{SkIRect{0, 2, 16, 4}, SkIRect{0, 3, 16, 4}},
{SkIRect{0, 0, 8, 2}, SkIRect{0, 0, 1, 2}},
{SkIRect{0, 0, 8, 2}, SkIRect{0, 0, 7, 2}},
{SkIRect{0, 0, 8, 2}, SkIRect{0, 0, 8, 2}},
{SkIRect{0, 0, 16, 2}, SkIRect{0, 0, 9, 2}},
{SkIRect{0, 0, 8, 2}, SkIRect{0, 0, 8, 1}},
{SkIRect{0, 0, 8, 2}, SkIRect{0, 0, 8, 2}},
{SkIRect{0, 0, 8, 4}, SkIRect{0, 0, 8, 3}},
{SkIRect{0, 0, 8, 4}, SkIRect{0, 0, 8, 4}},
};
for (const auto& test_case : kTestCases) {
EXPECT_EQ(test_case.expected, ToAlignedRect(test_case.input))
<< "ToAlignedRect(" << ToString(test_case.input) << ") should be "
<< ToString(test_case.expected);
}
}
} // namespace
} // namespace viz
......@@ -344,6 +344,36 @@ bool GLScaler::ParametersHasSameScaleRatio(const GLScaler::Parameters& params,
to.y());
}
// static
bool GLScaler::ParametersAreEquivalent(const Parameters& a,
const Parameters& b) {
if (!ParametersHasSameScaleRatio(a, b.scale_from, b.scale_to) ||
a.enable_precise_color_management != b.enable_precise_color_management ||
a.quality != b.quality || a.is_flipped_source != b.is_flipped_source ||
a.flip_output != b.flip_output || a.export_format != b.export_format ||
a.swizzle[0] != b.swizzle[0] || a.swizzle[1] != b.swizzle[1]) {
return false;
}
const gfx::ColorSpace source_color_space_a =
a.source_color_space.IsValid() ? a.source_color_space
: gfx::ColorSpace::CreateSRGB();
const gfx::ColorSpace source_color_space_b =
b.source_color_space.IsValid() ? b.source_color_space
: gfx::ColorSpace::CreateSRGB();
if (source_color_space_a != source_color_space_b) {
return false;
}
const gfx::ColorSpace output_color_space_a = a.output_color_space.IsValid()
? a.output_color_space
: source_color_space_a;
const gfx::ColorSpace output_color_space_b = b.output_color_space.IsValid()
? b.output_color_space
: source_color_space_b;
return output_color_space_a == output_color_space_b;
}
void GLScaler::OnContextLost() {
// The destruction order here is important due to data dependencies.
chain_.reset();
......@@ -685,6 +715,7 @@ bool GLScaler::AreAllGLExtensionsPresent(
}
GLScaler::Parameters::Parameters() = default;
GLScaler::Parameters::Parameters(const Parameters& other) = default;
GLScaler::Parameters::~Parameters() = default;
// static
......
......@@ -187,6 +187,7 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
};
Parameters();
Parameters(const Parameters& other);
~Parameters();
};
......@@ -208,8 +209,9 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
// Returns the currently-configured and resolved Parameters. Note that these
// Parameters might not be exactly the same as those that were passed to
// Configure() because some properties (e.g., color spaces) are auto-resolved.
// Results are undefined if Configure() has never been called successfully.
// Configure() because some properties (e.g., color spaces) are auto-resolved;
// however, ParametersAreEquivalent() will still return true. Results are
// undefined if Configure() has never been called successfully.
const Parameters& params() const { return params_; }
// Scales a portion of |src_texture| and draws the result into |dest_texture|
......@@ -258,6 +260,10 @@ class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
const gfx::Vector2d& from,
const gfx::Vector2d& to);
// Returns true if configuring a GLScaler with either |a| or |b| will produce
// identical behaviors and results.
static bool ParametersAreEquivalent(const Parameters& a, const Parameters& b);
private:
friend class GLScalerOverscanPixelTest;
friend class GLScalerShaderPixelTest;
......
......@@ -158,6 +158,7 @@ TEST_F(GLScalerTest, Configure_ResolvesUnspecifiedColorSpaces) {
const auto srgb = gfx::ColorSpace::CreateSRGB();
EXPECT_EQ(srgb, scaler.params().source_color_space);
EXPECT_EQ(srgb, scaler.params().output_color_space);
EXPECT_TRUE(GLScaler::ParametersAreEquivalent(params, scaler.params()));
// Source space set to XYZD50 with no output space specified: Both should
// resolve to XYZD50.
......@@ -166,6 +167,7 @@ TEST_F(GLScalerTest, Configure_ResolvesUnspecifiedColorSpaces) {
EXPECT_TRUE(scaler.Configure(params));
EXPECT_EQ(xyzd50, scaler.params().source_color_space);
EXPECT_EQ(xyzd50, scaler.params().output_color_space);
EXPECT_TRUE(GLScaler::ParametersAreEquivalent(params, scaler.params()));
// Source space set to XYZD50 with output space set to P3D65: Nothing should
// change.
......@@ -174,6 +176,7 @@ TEST_F(GLScalerTest, Configure_ResolvesUnspecifiedColorSpaces) {
EXPECT_TRUE(scaler.Configure(params));
EXPECT_EQ(xyzd50, scaler.params().source_color_space);
EXPECT_EQ(p3d65, scaler.params().output_color_space);
EXPECT_TRUE(GLScaler::ParametersAreEquivalent(params, scaler.params()));
}
TEST_F(GLScalerTest, Configure_RequiresValidSwizzles) {
......
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