Commit 35735c3d authored by Yuri Wiitala's avatar Yuri Wiitala Committed by Commit Bot

Introduce new GLScaler (API only) to replace GLHelper+GLHelperScaler.

Introduces the "outer shell" API to for GLScaler, with upcoming patches
coming soon to fill-in the rest of the implementation. This is a major
refactoring of GLHelper+GLHelperScaler to: 1) fix dangerous memory
management issues, 2) provide a cleaner interface for client-side code,
3) unblock performance and color management efforts.

Bug: 870036,758057
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel
Change-Id: Ib8f0ed9b988bfe914bd8983940b089262543e8c7
Reviewed-on: https://chromium-review.googlesource.com/1194783
Commit-Queue: Yuri Wiitala <miu@chromium.org>
Reviewed-by: default avatarXiangjun Zhang <xjz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589674}
parent c4cb73ec
...@@ -64,6 +64,8 @@ viz_component("common") { ...@@ -64,6 +64,8 @@ viz_component("common") {
"gl_helper.h", "gl_helper.h",
"gl_helper_scaling.cc", "gl_helper_scaling.cc",
"gl_helper_scaling.h", "gl_helper_scaling.h",
"gl_scaler.cc",
"gl_scaler.h",
"gpu/context_cache_controller.cc", "gpu/context_cache_controller.cc",
"gpu/context_cache_controller.h", "gpu/context_cache_controller.h",
"gpu/context_lost_observer.h", "gpu/context_lost_observer.h",
...@@ -213,6 +215,7 @@ viz_source_set("unit_tests") { ...@@ -213,6 +215,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_unittest.cc",
"gpu/context_cache_controller_unittest.cc", "gpu/context_cache_controller_unittest.cc",
"quads/draw_quad_unittest.cc", "quads/draw_quad_unittest.cc",
"quads/render_pass_unittest.cc", "quads/render_pass_unittest.cc",
......
...@@ -14,7 +14,7 @@ specific_include_rules = { ...@@ -14,7 +14,7 @@ specific_include_rules = {
"+third_party/skia", "+third_party/skia",
], ],
# DEPS for GLHelper and friends which are in the root common/ directory. # DEPS for GLHelper and friends which are in the root common/ directory.
"(yuv_readback|gl_helper).*\.(cc|h)": [ "(yuv_readback|gl_helper|gl_scaler).*\.(cc|h)": [
"+gpu/GLES2", "+gpu/GLES2",
"+gpu/command_buffer/client", "+gpu/command_buffer/client",
"+gpu/command_buffer/common", "+gpu/command_buffer/common",
......
// 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 <utility>
#include "base/logging.h"
#include "components/viz/common/gpu/context_provider.h"
#include "gpu/GLES2/gl2chromium.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/common/capabilities.h"
#include "ui/gfx/geometry/vector2d_f.h"
namespace viz {
GLScaler::GLScaler(scoped_refptr<ContextProvider> context_provider)
: context_provider_(std::move(context_provider)) {
if (context_provider_) {
DCHECK(context_provider_->ContextGL());
context_provider_->AddObserver(this);
}
}
GLScaler::~GLScaler() {
OnContextLost(); // Ensures destruction in dependency order.
}
bool GLScaler::SupportsPreciseColorManagement() const {
if (!context_provider_) {
return false;
}
const gpu::Capabilities& caps = context_provider_->ContextCapabilities();
return caps.texture_half_float_linear && caps.color_buffer_half_float_rgba;
}
int GLScaler::GetMaxDrawBuffersSupported() const {
if (!context_provider_) {
return 0;
}
if (max_draw_buffers_ < 0) {
// Query the GL context for the multiple draw buffers extension and, if
// present, the actual platform-supported maximum.
GLES2Interface* const gl = context_provider_->ContextGL();
DCHECK(gl);
if (const auto* extensions = gl->GetString(GL_EXTENSIONS)) {
const std::string extensions_string =
" " + std::string(reinterpret_cast<const char*>(extensions)) + " ";
if (extensions_string.find(" GL_EXT_draw_buffers ") !=
std::string::npos) {
gl->GetIntegerv(GL_MAX_DRAW_BUFFERS_EXT, &max_draw_buffers_);
}
}
if (max_draw_buffers_ < 1) {
max_draw_buffers_ = 1;
}
}
return max_draw_buffers_;
}
bool GLScaler::Configure(const Parameters& new_params) {
if (!context_provider_) {
return false;
}
GLES2Interface* const gl = context_provider_->ContextGL();
DCHECK(gl);
params_ = new_params;
// Ensure the client has provided valid scaling vectors.
if (params_.scale_from.x() == 0 || params_.scale_from.y() == 0 ||
params_.scale_to.x() == 0 || params_.scale_to.y() == 0) {
// The caller computed invalid scale_from and/or scale_to values.
DVLOG(1) << __func__ << ": Invalid scaling vectors: scale_from="
<< params_.scale_from.ToString()
<< ", scale_to=" << params_.scale_to.ToString();
return false;
}
// Resolve the color spaces according to the rules described in the header
// file.
if (!params_.source_color_space.IsValid()) {
params_.source_color_space = gfx::ColorSpace::CreateSRGB();
}
if (!params_.output_color_space.IsValid()) {
params_.output_color_space = params_.source_color_space;
}
// Check that 16-bit half floats are supported if precise color management is
// being requested.
if (params_.enable_precise_color_management) {
if (!SupportsPreciseColorManagement()) {
DVLOG(1) << __func__
<< ": GL context does not support the half-floats "
"required for precise color management.";
return false;
}
}
// Check that MRT support is available if certain export formats were
// specified in the Parameters.
if (params_.export_format == Parameters::ExportFormat::NV61 ||
params_.export_format ==
Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE) {
if (GetMaxDrawBuffersSupported() < 2) {
DVLOG(1) << __func__ << ": GL context does not support 2+ draw buffers.";
return false;
}
}
// Check that one of the two implemented output swizzles has been specified.
for (GLenum s : params_.swizzle) {
if (s != GL_RGBA && s != GL_BGRA_EXT) {
NOTIMPLEMENTED();
return false;
}
}
return true;
}
bool GLScaler::ScaleToMultipleOutputs(GLuint src_texture,
const gfx::Size& src_texture_size,
const gfx::Vector2dF& src_offset,
GLuint dest_texture_0,
GLuint dest_texture_1,
const gfx::Rect& output_rect) {
NOTIMPLEMENTED();
return false;
}
bool GLScaler::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 {
NOTIMPLEMENTED();
return false;
}
// static
bool GLScaler::ParametersHasSameScaleRatio(const GLScaler::Parameters& params,
const gfx::Vector2d& from,
const gfx::Vector2d& to) {
// Returns true iff a_num/a_denom == b_num/b_denom.
const auto AreRatiosEqual = [](int32_t a_num, int32_t a_denom, int32_t b_num,
int32_t b_denom) -> bool {
// The math (for each dimension):
// If: a_num/a_denom == b_num/b_denom
// Then: a_num*b_denom == b_num*a_denom
//
// ...and cast to int64_t to guarantee no overflow from the multiplications.
return (static_cast<int64_t>(a_num) * b_denom) ==
(static_cast<int64_t>(b_num) * a_denom);
};
return AreRatiosEqual(params.scale_from.x(), params.scale_to.x(), from.x(),
to.x()) &&
AreRatiosEqual(params.scale_from.y(), params.scale_to.y(), from.y(),
to.y());
}
void GLScaler::OnContextLost() {
if (context_provider_) {
context_provider_->RemoveObserver(this);
context_provider_ = nullptr;
}
}
GLScaler::Parameters::Parameters() = default;
GLScaler::Parameters::~Parameters() = default;
} // namespace viz
This diff is collapsed.
// 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 "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 "gpu/command_buffer/common/capabilities.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::ByRef;
using ::testing::Eq;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::SaveArg;
using ::testing::Sequence;
namespace viz {
namespace {
class MockContextProvider : public ContextProvider {
public:
MockContextProvider() {
ON_CALL(*this, ContextGL())
.WillByDefault(
Return(reinterpret_cast<gpu::gles2::GLES2Interface*>(0xdeadbeef)));
ON_CALL(*this, ContextCapabilities()).WillByDefault(ReturnRef(caps_));
}
MOCK_METHOD1(AddObserver, void(ContextLostObserver* obs));
MOCK_METHOD1(RemoveObserver, void(ContextLostObserver* obs));
MOCK_CONST_METHOD0(ContextCapabilities, const gpu::Capabilities&());
MOCK_METHOD0(ContextGL, gpu::gles2::GLES2Interface*());
// Stubbed-out, because the tests just stack-allocate this object.
void AddRef() const final {}
void Release() const final {}
private:
gpu::Capabilities caps_;
// Other ContextProvider methods; but stubbed-out because they are never
// called.
gpu::ContextResult BindToCurrentThread() final {
NOTREACHED();
return gpu::ContextResult::kSuccess;
}
base::Lock* GetLock() final {
NOTREACHED();
return nullptr;
}
ContextCacheController* CacheController() final {
NOTREACHED();
return nullptr;
}
gpu::ContextSupport* ContextSupport() final {
NOTREACHED();
return nullptr;
}
class GrContext* GrContext() final {
NOTREACHED();
return nullptr;
}
const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const final {
NOTREACHED();
return *reinterpret_cast<gpu::GpuFeatureInfo*>(0xdeadbeef);
}
};
class GLScalerTest : public testing::Test {};
TEST_F(GLScalerTest, AddAndRemovesSelfAsContextLossObserver) {
NiceMock<MockContextProvider> provider;
ContextLostObserver* registered_observer = nullptr;
Sequence s;
EXPECT_CALL(provider, AddObserver(NotNull()))
.InSequence(s)
.WillOnce(SaveArg<0>(&registered_observer));
EXPECT_CALL(provider, RemoveObserver(Eq(ByRef(registered_observer))))
.InSequence(s);
GLScaler scaler(base::WrapRefCounted(&provider));
}
TEST_F(GLScalerTest, CleansUpWhenContextIsLost) {
NiceMock<MockContextProvider> provider;
ContextLostObserver* registered_observer = nullptr;
Sequence s;
EXPECT_CALL(provider, AddObserver(NotNull()))
.InSequence(s)
.WillOnce(SaveArg<0>(&registered_observer));
EXPECT_CALL(provider, RemoveObserver(Eq(ByRef(registered_observer))))
.InSequence(s);
GLScaler scaler(base::WrapRefCounted(&provider));
static_cast<ContextLostObserver&>(scaler).OnContextLost();
// Verify RemoveObserver() was called before |scaler| goes out-of-scope.
Mock::VerifyAndClearExpectations(&provider);
}
TEST_F(GLScalerTest, Configure_RequiresValidScalingVectors) {
NiceMock<MockContextProvider> provider;
GLScaler scaler(base::WrapRefCounted(&provider));
GLScaler::Parameters params;
EXPECT_TRUE(scaler.Configure(params));
for (int i = 0; i < 4; ++i) {
params.scale_from = gfx::Vector2d(i == 0 ? 0 : 1, i == 1 ? 0 : 1);
params.scale_to = gfx::Vector2d(i == 2 ? 0 : 1, i == 3 ? 0 : 1);
EXPECT_FALSE(scaler.Configure(params));
}
}
TEST_F(GLScalerTest, Configure_ResolvesUnspecifiedColorSpaces) {
NiceMock<MockContextProvider> provider;
GLScaler scaler(base::WrapRefCounted(&provider));
// Neither source nor output space specified: Both should resolve to sRGB.
GLScaler::Parameters params;
EXPECT_TRUE(scaler.Configure(params));
const auto srgb = gfx::ColorSpace::CreateSRGB();
EXPECT_EQ(srgb, scaler.params().source_color_space);
EXPECT_EQ(srgb, scaler.params().output_color_space);
// Source space set to XYZD50 with no output space specified: Both should
// resolve to XYZD50.
const auto xyzd50 = gfx::ColorSpace::CreateXYZD50();
params.source_color_space = xyzd50;
EXPECT_TRUE(scaler.Configure(params));
EXPECT_EQ(xyzd50, scaler.params().source_color_space);
EXPECT_EQ(xyzd50, scaler.params().output_color_space);
// Source space set to XYZD50 with output space set to P3D65: Nothing should
// change.
const auto p3d65 = gfx::ColorSpace::CreateDisplayP3D65();
params.output_color_space = p3d65;
EXPECT_TRUE(scaler.Configure(params));
EXPECT_EQ(xyzd50, scaler.params().source_color_space);
EXPECT_EQ(p3d65, scaler.params().output_color_space);
}
TEST_F(GLScalerTest, Configure_RequiresValidSwizzles) {
NiceMock<MockContextProvider> provider;
GLScaler scaler(base::WrapRefCounted(&provider));
GLScaler::Parameters params;
// Test that all valid combinations work.
for (int i = 0; i < 4; ++i) {
params.swizzle[0] = (i % 2 == 0) ? GL_RGBA : GL_BGRA_EXT;
params.swizzle[1] = (i / 2 == 0) ? GL_RGBA : GL_BGRA_EXT;
EXPECT_TRUE(scaler.Configure(params)) << "i=" << i;
}
// Test that invalid combinations don't work.
for (int i = 1; i < 4; ++i) {
params.swizzle[0] = (i % 2 == 0) ? GL_RGBA : GL_RGB;
params.swizzle[1] = (i / 2 == 0) ? GL_RGBA : GL_RGB;
EXPECT_FALSE(scaler.Configure(params)) << "i=" << i;
}
}
TEST_F(GLScalerTest, DetectsEquivalentScaleRatios) {
GLScaler::Parameters params;
EXPECT_TRUE(GLScaler::ParametersHasSameScaleRatio(params, gfx::Vector2d(1, 1),
gfx::Vector2d(1, 1)));
EXPECT_TRUE(GLScaler::ParametersHasSameScaleRatio(
params, gfx::Vector2d(15, 15), gfx::Vector2d(15, 15)));
params.scale_from = gfx::Vector2d(2, 1);
EXPECT_TRUE(GLScaler::ParametersHasSameScaleRatio(params, gfx::Vector2d(2, 1),
gfx::Vector2d(1, 1)));
EXPECT_TRUE(GLScaler::ParametersHasSameScaleRatio(
params, gfx::Vector2d(30, 15), gfx::Vector2d(15, 15)));
params.scale_from = gfx::Vector2d(1, 2);
EXPECT_TRUE(GLScaler::ParametersHasSameScaleRatio(params, gfx::Vector2d(1, 2),
gfx::Vector2d(1, 1)));
EXPECT_TRUE(GLScaler::ParametersHasSameScaleRatio(
params, gfx::Vector2d(15, 30), gfx::Vector2d(15, 15)));
params.scale_from = gfx::Vector2d(2, 1);
EXPECT_FALSE(GLScaler::ParametersHasSameScaleRatio(
params, gfx::Vector2d(1, 2), gfx::Vector2d(1, 1)));
EXPECT_FALSE(GLScaler::ParametersHasSameScaleRatio(
params, gfx::Vector2d(15, 30), gfx::Vector2d(15, 15)));
params.scale_from = gfx::Vector2d(1, 2);
EXPECT_FALSE(GLScaler::ParametersHasSameScaleRatio(
params, gfx::Vector2d(2, 1), gfx::Vector2d(1, 1)));
EXPECT_FALSE(GLScaler::ParametersHasSameScaleRatio(
params, gfx::Vector2d(30, 15), gfx::Vector2d(15, 15)));
}
} // namespace
} // 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