blob: befcb2cd479968b3e0aa306de45e3e00b492932f [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/common/gl_scaler.h"
#include <algorithm>
#include <array>
#include <sstream>
#include <string>
#include "base/logging.h"
#include "components/viz/common/gpu/context_provider.h"
#include "gpu/GLES2/gl2chromium.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "ui/gfx/color_transform.h"
#include "ui/gfx/geometry/rect_conversions.h"
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)
: 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;
}
if (!supports_half_floats_.has_value()) {
supports_half_floats_ = AreAllGLExtensionsPresent(
context_provider_->ContextGL(),
{"GL_EXT_color_buffer_half_float", "GL_OES_texture_half_float_linear"});
}
return supports_half_floats_.value();
}
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 (AreAllGLExtensionsPresent(gl, {"GL_EXT_draw_buffers"})) {
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) {
chain_.reset();
shader_programs_.clear();
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;
}
}
// Color space transformation is meaningless when using the deinterleaver
// because it only deals with two color channels. This also means precise
// color management must be disabled.
if (params_.export_format ==
Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE &&
(params_.source_color_space != params_.output_color_space ||
params_.enable_precise_color_management)) {
NOTIMPLEMENTED();
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;
}
}
// 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);
// Determine the color space and the data type of the pixels in the
// intermediate textures, depending on whether precise color management is
// enabled. Note that nothing special need be done here if no scaling will be
// performed.
GLenum intermediate_texture_type;
if (params_.enable_precise_color_management &&
params_.scale_from != params_.scale_to) {
// Ensure the scaling color space is using a linear transfer function.
constexpr auto kLinearFunction = std::make_tuple(1, 0, 1, 0, 0, 0, 1);
SkColorSpaceTransferFn fn;
if (params_.source_color_space.GetTransferFunction(&fn) &&
std::make_tuple(fn.fA, fn.fB, fn.fC, fn.fD, fn.fE, fn.fF, fn.fG) ==
kLinearFunction) {
scaling_color_space_ = params_.source_color_space;
} else {
// Use the source color space, but with a linear transfer function.
SkMatrix44 to_XYZD50;
params_.source_color_space.GetPrimaryMatrix(&to_XYZD50);
std::tie(fn.fA, fn.fB, fn.fC, fn.fD, fn.fE, fn.fF, fn.fG) =
kLinearFunction;
scaling_color_space_ = gfx::ColorSpace::CreateCustom(to_XYZD50, fn);
}
intermediate_texture_type = GL_HALF_FLOAT_OES;
} else {
scaling_color_space_ = params_.source_color_space;
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 necessary, prepend an extra "import stage" that color-converts the input
// before any scaling occurs. It's important not to merge color space
// conversion of the source with any other steps because the texture sampler
// must not linearly interpolate until after the colors have been mapped to a
// linear color space.
if (params_.source_color_space != scaling_color_space_) {
input_stage->set_input_stage(std::make_unique<ScalerStage>(
gl, Shader::BILINEAR, HORIZONTAL, input_stage->scale_from(),
input_stage->scale_from()));
input_stage = input_stage->input_stage();
transform = gfx::ColorTransform::NewColorTransform(
params_.source_color_space, scaling_color_space_,
gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
if (!transform->CanGetShaderSource()) {
NOTIMPLEMENTED() << "color transform from "
<< params_.source_color_space.ToString() << " to "
<< scaling_color_space_.ToString();
return false;
}
input_stage->set_shader_program(
GetShaderProgram(input_stage->shader(), intermediate_texture_type,
transform.get(), kNoSwizzle));
}
// 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;
}
bool GLScaler::ScaleToMultipleOutputs(GLuint src_texture,
const gfx::Size& src_texture_size,
const gfx::Vector2d& src_offset,
GLuint dest_texture_0,
GLuint dest_texture_1,
const gfx::Rect& output_rect) {
if (!chain_) {
return false;
}
// Bind the vertex attributes used to sweep the entire source area when
// executing the shader programs.
GLES2Interface* const gl = context_provider_->ContextGL();
DCHECK(gl);
if (vertex_attributes_buffer_) {
gl->BindBuffer(GL_ARRAY_BUFFER, vertex_attributes_buffer_);
} 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
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());
}
// 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();
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_) {
context_provider_->RemoveObserver(this);
context_provider_ = nullptr;
}
}
GLScaler::ShaderProgram* GLScaler::GetShaderProgram(
Shader shader,
GLenum texture_type,
const gfx::ColorTransform* color_transform,
const GLenum swizzle[2]) {
const ShaderCacheKey key{
shader,
texture_type,
color_transform ? color_transform->GetSrcColorSpace() : gfx::ColorSpace(),
color_transform ? color_transform->GetDstColorSpace() : gfx::ColorSpace(),
swizzle[0],
swizzle[1]};
auto it = shader_programs_.find(key);
if (it == shader_programs_.end()) {
GLES2Interface* const gl = context_provider_->ContextGL();
DCHECK(gl);
it = shader_programs_
.emplace(std::piecewise_construct, std::forward_as_tuple(key),
std::forward_as_tuple(gl, shader, texture_type,
color_transform, swizzle))
.first;
}
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
bool GLScaler::AreAllGLExtensionsPresent(
gpu::gles2::GLES2Interface* gl,
const std::vector<std::string>& names) {
DCHECK(gl);
if (const auto* extensions = gl->GetString(GL_EXTENSIONS)) {
const std::string extensions_string =
" " + std::string(reinterpret_cast<const char*>(extensions)) + " ";
for (const std::string& name : names) {
if (extensions_string.find(" " + name + " ") == std::string::npos) {
return false;
}
}
return true;
}
return false;
}
GLScaler::Parameters::Parameters() = default;
GLScaler::Parameters::Parameters(const Parameters& other) = default;
GLScaler::Parameters::~Parameters() = default;
// static
const GLfloat GLScaler::ShaderProgram::kVertexAttributes[16] = {
-1.0f, -1.0f, 0.0f, 0.0f, // vertex 0
1.0f, -1.0f, 1.0f, 0.0f, // vertex 1
-1.0f, 1.0f, 0.0f, 1.0f, // vertex 2
1.0f, 1.0f, 1.0f, 1.0f, // vertex 3
};
GLScaler::ShaderProgram::ShaderProgram(
gpu::gles2::GLES2Interface* gl,
GLScaler::Shader shader,
GLenum texture_type,
const gfx::ColorTransform* color_transform,
const GLenum swizzle[2])
: gl_(gl),
shader_(shader),
texture_type_(texture_type),
program_(gl_->CreateProgram()) {
DCHECK(program_);
std::basic_ostringstream<GLchar> vertex_header;
std::basic_ostringstream<GLchar> fragment_directives;
std::basic_ostringstream<GLchar> fragment_header;
std::basic_ostringstream<GLchar> shared_variables;
std::basic_ostringstream<GLchar> vertex_main;
std::basic_ostringstream<GLchar> fragment_main;
vertex_header
<< ("precision highp float;\n"
"attribute vec2 a_position;\n"
"attribute vec2 a_texcoord;\n"
"uniform vec4 src_rect;\n");
fragment_header << "precision mediump float;\n";
switch (texture_type_) {
case GL_FLOAT:
fragment_header << "precision highp sampler2D;\n";
break;
case GL_HALF_FLOAT_OES:
fragment_header << "precision mediump sampler2D;\n";
break;
default:
fragment_header << "precision lowp sampler2D;\n";
break;
}
fragment_header << "uniform sampler2D s_texture;\n";
if (color_transform && shader_ != Shader::PLANAR_CHANNEL_3) {
const std::string& source = color_transform->GetShaderSource();
// Assumption: gfx::ColorTransform::GetShaderSource() should provide a
// function named DoColorConversion() that takes a vec3 argument and returns
// a vec3.
DCHECK_NE(source.find("DoColorConversion"), std::string::npos);
fragment_header << source;
}
vertex_main
<< (" gl_Position = vec4(a_position, 0.0, 1.0);\n"
" vec2 texcoord = src_rect.xy + a_texcoord * src_rect.zw;\n");
switch (shader_) {
case Shader::BILINEAR:
shared_variables << "varying highp vec2 v_texcoord;\n";
vertex_main << " v_texcoord = texcoord;\n";
fragment_main << " vec4 sample = texture2D(s_texture, v_texcoord);\n";
if (color_transform) {
fragment_main << " sample.rgb = DoColorConversion(sample.rgb);\n";
}
if (swizzle[0] == GL_BGRA_EXT) {
fragment_main << " sample.rb = sample.br;\n";
}
fragment_main << " gl_FragColor = sample;\n";
break;
case Shader::BILINEAR2:
// This is equivialent to two passes of the BILINEAR shader above. It can
// be used to scale an image down 1.0x-2.0x in either dimension, or
// exactly 4x.
shared_variables << "varying highp vec4 v_texcoords;\n";
vertex_header << "uniform vec2 scaling_vector;\n";
vertex_main
<< (" vec2 step = scaling_vector / 4.0;\n"
" v_texcoords.xy = texcoord + step;\n"
" v_texcoords.zw = texcoord - step;\n");
fragment_main
<< (" vec4 blended = (texture2D(s_texture, v_texcoords.xy) +\n"
" texture2D(s_texture, v_texcoords.zw)) /\n"
" 2.0;\n");
if (color_transform) {
fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
}
if (swizzle[0] == GL_BGRA_EXT) {
fragment_main << " blended.rb = blended.br;\n";
}
fragment_main << " gl_FragColor = blended;\n";
break;
case Shader::BILINEAR3:
// This is kind of like doing 1.5 passes of the BILINEAR shader. It can be
// used to scale an image down 1.5x-3.0x, or exactly 6x.
shared_variables
<< ("varying highp vec4 v_texcoords0;\n"
"varying highp vec2 v_texcoords1;\n");
vertex_header << "uniform vec2 scaling_vector;\n";
vertex_main
<< (" vec2 step = scaling_vector / 3.0;\n"
" v_texcoords0.xy = texcoord + step;\n"
" v_texcoords0.zw = texcoord;\n"
" v_texcoords1 = texcoord - step;\n");
fragment_main
<< (" vec4 blended = (texture2D(s_texture, v_texcoords0.xy) +\n"
" texture2D(s_texture, v_texcoords0.zw) +\n"
" texture2D(s_texture, v_texcoords1)) / 3.0;\n");
if (color_transform) {
fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
}
if (swizzle[0] == GL_BGRA_EXT) {
fragment_main << " blended.rb = blended.br;\n";
}
fragment_main << " gl_FragColor = blended;\n";
break;
case Shader::BILINEAR4:
// This is equivialent to three passes of the BILINEAR shader above. It
// can be used to scale an image down 2.0x-4.0x or exactly 8x.
shared_variables << "varying highp vec4 v_texcoords[2];\n";
vertex_header << "uniform vec2 scaling_vector;\n";
vertex_main
<< (" vec2 step = scaling_vector / 8.0;\n"
" v_texcoords[0].xy = texcoord - step * 3.0;\n"
" v_texcoords[0].zw = texcoord - step;\n"
" v_texcoords[1].xy = texcoord + step;\n"
" v_texcoords[1].zw = texcoord + step * 3.0;\n");
fragment_main
<< (" vec4 blended = (\n"
" texture2D(s_texture, v_texcoords[0].xy) +\n"
" texture2D(s_texture, v_texcoords[0].zw) +\n"
" texture2D(s_texture, v_texcoords[1].xy) +\n"
" texture2D(s_texture, v_texcoords[1].zw)) / 4.0;\n");
if (color_transform) {
fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
}
if (swizzle[0] == GL_BGRA_EXT) {
fragment_main << " blended.rb = blended.br;\n";
}
fragment_main << " gl_FragColor = blended;\n";
break;
case Shader::BILINEAR2X2:
// This is equivialent to four passes of the BILINEAR shader above, two in
// each dimension. It can be used to scale an image down 1.0x-2.0x in both
// X and Y directions. Or, it could be used to scale an image down by
// exactly 4x in both dimensions.
shared_variables << "varying highp vec4 v_texcoords[2];\n";
vertex_header << "uniform vec2 scaling_vector;\n";
vertex_main
<< (" vec2 step = scaling_vector / 4.0;\n"
" v_texcoords[0].xy = texcoord + vec2(step.x, step.y);\n"
" v_texcoords[0].zw = texcoord + vec2(step.x, -step.y);\n"
" v_texcoords[1].xy = texcoord + vec2(-step.x, step.y);\n"
" v_texcoords[1].zw = texcoord + vec2(-step.x, -step.y);\n");
fragment_main
<< (" vec4 blended = (\n"
" texture2D(s_texture, v_texcoords[0].xy) +\n"
" texture2D(s_texture, v_texcoords[0].zw) +\n"
" texture2D(s_texture, v_texcoords[1].xy) +\n"
" texture2D(s_texture, v_texcoords[1].zw)) / 4.0;\n");
if (color_transform) {
fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
}
if (swizzle[0] == GL_BGRA_EXT) {
fragment_main << " blended.rb = blended.br;\n";
}
fragment_main << " gl_FragColor = blended;\n";
break;
case Shader::BICUBIC_UPSCALE:
// When scaling up, 4 texture reads are necessary. However, some
// instructions can be saved because the parameter passed to the bicubic
// function will be in a known range. Also, when sampling the bicubic
// function like this, the sum is always exactly one, so normalization can
// be skipped as well.
shared_variables << "varying highp vec2 v_texcoord;\n";
vertex_main << " v_texcoord = texcoord;\n";
fragment_header
<< ("uniform highp vec2 src_pixelsize;\n"
"uniform highp vec2 scaling_vector;\n"
"const float a = -0.5;\n"
// This function is equivialent to calling the bicubic
// function with x-1, x, 1-x and 2-x (assuming
// 0 <= x < 1). The following is the Catmull-Rom spline.
// See: http://wikipedia.org/wiki/Cubic_Hermite_spline
"vec4 filt4(float x) {\n"
" return vec4(x * x * x, x * x, x, 1) *\n"
" mat4( a, -2.0 * a, a, 0.0,\n"
" a + 2.0, -a - 3.0, 0.0, 1.0,\n"
" -a - 2.0, 3.0 + 2.0 * a, -a, 0.0,\n"
" -a, a, 0.0, 0.0);\n"
"}\n"
"mat4 pixels_x(highp vec2 pos, highp vec2 step) {\n"
" return mat4(texture2D(s_texture, pos - step),\n"
" texture2D(s_texture, pos),\n"
" texture2D(s_texture, pos + step),\n"
" texture2D(s_texture, pos + step * 2.0));\n"
"}\n");
fragment_main
<< (" highp vec2 pixel_pos = v_texcoord * src_pixelsize - \n"
" scaling_vector / 2.0;\n"
" highp float frac = fract(dot(pixel_pos, scaling_vector));\n"
" highp vec2 base = \n"
" (floor(pixel_pos) + vec2(0.5)) / src_pixelsize;\n"
" highp vec2 step = scaling_vector / src_pixelsize;\n"
" vec4 blended = pixels_x(base, step) * filt4(frac);\n");
if (color_transform) {
fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
}
if (swizzle[0] == GL_BGRA_EXT) {
fragment_main << " blended.rb = blended.br;\n";
}
fragment_main << " gl_FragColor = blended;\n";
break;
case Shader::BICUBIC_HALF_1D:
// This scales down an image by exactly half in one dimension. The
// bilinear lookup reduces the number of texture reads from 8 to 4.
shared_variables << "varying highp vec4 v_texcoords[2];\n";
vertex_header
<< ("uniform vec2 scaling_vector;\n"
"const float CenterDist = 99.0 / 140.0;\n"
"const float LobeDist = 11.0 / 4.0;\n");
vertex_main
<< (" vec2 step = scaling_vector / 2.0;\n"
" v_texcoords[0].xy = texcoord - LobeDist * step;\n"
" v_texcoords[0].zw = texcoord - CenterDist * step;\n"
" v_texcoords[1].xy = texcoord + CenterDist * step;\n"
" v_texcoords[1].zw = texcoord + LobeDist * step;\n");
fragment_header
<< ("const float CenterWeight = 35.0 / 64.0;\n"
"const float LobeWeight = -3.0 / 64.0;\n");
fragment_main
<< (" vec4 blended = \n"
// Lobe pixels
" (texture2D(s_texture, v_texcoords[0].xy) +\n"
" texture2D(s_texture, v_texcoords[1].zw)) *\n"
" LobeWeight +\n"
// Center pixels
" (texture2D(s_texture, v_texcoords[0].zw) +\n"
" texture2D(s_texture, v_texcoords[1].xy)) *\n"
" CenterWeight;\n");
if (color_transform) {
fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
}
if (swizzle[0] == GL_BGRA_EXT) {
fragment_main << " blended.rb = blended.br;\n";
}
fragment_main << " gl_FragColor = blended;\n";
break;
case Shader::PLANAR_CHANNEL_0:
case Shader::PLANAR_CHANNEL_1:
case Shader::PLANAR_CHANNEL_2:
case Shader::PLANAR_CHANNEL_3: {
// Select one color channel, and pack 4 pixels into one output quad. See
// header file for diagram.
shared_variables << "varying highp vec4 v_texcoords[2];\n";
vertex_header << "uniform vec2 scaling_vector;\n";
vertex_main
<< (" vec2 step = scaling_vector / 4.0;\n"
" v_texcoords[0].xy = texcoord - step * 1.5;\n"
" v_texcoords[0].zw = texcoord - step * 0.5;\n"
" v_texcoords[1].xy = texcoord + step * 0.5;\n"
" v_texcoords[1].zw = texcoord + step * 1.5;\n");
std::basic_string<GLchar> convert_open;
std::basic_string<GLchar> convert_close;
if (color_transform && shader_ != Shader::PLANAR_CHANNEL_3) {
convert_open = "DoColorConversion(";
convert_close = ".rgb)";
}
const char selector = "rgba"[static_cast<int>(shader_) -
static_cast<int>(Shader::PLANAR_CHANNEL_0)];
fragment_main << " vec4 packed_quad = vec4(\n"
<< " " << convert_open
<< "texture2D(s_texture, v_texcoords[0].xy)"
<< convert_close << '.' << selector << ",\n"
<< " " << convert_open
<< "texture2D(s_texture, v_texcoords[0].zw)"
<< convert_close << '.' << selector << ",\n"
<< " " << convert_open
<< "texture2D(s_texture, v_texcoords[1].xy)"
<< convert_close << '.' << selector << ",\n"
<< " " << convert_open
<< "texture2D(s_texture, v_texcoords[1].zw)"
<< convert_close << '.' << selector << ");\n";
if (swizzle[0] == GL_BGRA_EXT) {
fragment_main << " packed_quad.rb = packed_quad.br;\n";
}
fragment_main << " gl_FragColor = packed_quad;\n";
break;
}
case Shader::I422_NV61_MRT:
// I422 sampling, delivered via two output textures (NV61 format). See
// header file for diagram.
shared_variables << "varying highp vec4 v_texcoords[2];\n";
vertex_header << "uniform vec2 scaling_vector;\n";
vertex_main
<< (" vec2 step = scaling_vector / 4.0;\n"
" v_texcoords[0].xy = texcoord - step * 1.5;\n"
" v_texcoords[0].zw = texcoord - step * 0.5;\n"
" v_texcoords[1].xy = texcoord + step * 0.5;\n"
" v_texcoords[1].zw = texcoord + step * 1.5;\n");
fragment_directives << "#extension GL_EXT_draw_buffers : enable\n";
fragment_main
<< (" vec3 pixel0 = texture2D(s_texture, v_texcoords[0].xy).rgb;\n"
" vec3 pixel1 = texture2D(s_texture, v_texcoords[0].zw).rgb;\n"
" vec3 pixel01 = (pixel0 + pixel1) / 2.0;\n"
" vec3 pixel2 = texture2D(s_texture, v_texcoords[1].xy).rgb;\n"
" vec3 pixel3 = texture2D(s_texture, v_texcoords[1].zw).rgb;\n"
" vec3 pixel23 = (pixel2 + pixel3) / 2.0;\n");
if (color_transform) {
fragment_main
<< (" pixel0 = DoColorConversion(pixel0);\n"
" pixel1 = DoColorConversion(pixel1);\n"
" pixel01 = DoColorConversion(pixel01);\n"
" pixel2 = DoColorConversion(pixel2);\n"
" pixel3 = DoColorConversion(pixel3);\n"
" pixel23 = DoColorConversion(pixel23);\n");
}
if (swizzle[0] == GL_BGRA_EXT) {
fragment_main
<< (" gl_FragData[0] = \n"
" vec4(pixel2.r, pixel1.r, pixel0.r, pixel3.r);\n");
} else {
fragment_main
<< (" gl_FragData[0] = \n"
" vec4(pixel0.r, pixel1.r, pixel2.r, pixel3.r);\n");
}
if (swizzle[1] == GL_BGRA_EXT) {
fragment_main
<< (" gl_FragData[1] = \n"
" vec4(pixel23.g, pixel01.b, pixel01.g, pixel23.b);\n");
} else {
fragment_main
<< (" gl_FragData[1] = \n"
" vec4(pixel01.g, pixel01.b, pixel23.g, pixel23.b);\n");
}
break;
case Shader::DEINTERLEAVE_PAIRWISE_MRT:
// Sample two pixels and unswizzle them. There's no need to do vertical
// scaling with math, since the bilinear interpolation in the sampler
// takes care of that.
shared_variables << "varying highp vec4 v_texcoords;\n";
vertex_header << "uniform vec2 scaling_vector;\n";
vertex_main
<< (" vec2 step = scaling_vector / 2.0;\n"
" v_texcoords.xy = texcoord - step * 0.5;\n"
" v_texcoords.zw = texcoord + step * 0.5;\n");
fragment_directives << "#extension GL_EXT_draw_buffers : enable\n";
DCHECK(!color_transform);
fragment_main
<< (" vec4 lo_uvuv = texture2D(s_texture, v_texcoords.xy);\n"
" vec4 hi_uvuv = texture2D(s_texture, v_texcoords.zw);\n"
" vec4 uuuu = vec4(lo_uvuv.rb, hi_uvuv.rb);\n"
" vec4 vvvv = vec4(lo_uvuv.ga, hi_uvuv.ga);\n");
if (swizzle[0] == GL_BGRA_EXT) {
fragment_main << " uuuu.rb = uuuu.br;\n";
}
fragment_main << " gl_FragData[0] = uuuu;\n";
if (swizzle[1] == GL_BGRA_EXT) {
fragment_main << " vvvv.rb = vvvv.br;\n";
}
fragment_main << " gl_FragData[1] = vvvv;\n";
break;
}
// Helper function to compile the shader source and log the GLSL compiler's
// results.
const auto CompileShaderFromSource =
[](GLES2Interface* gl, const std::basic_string<GLchar>& source,
GLenum type) -> GLuint {
VLOG(2) << __func__ << ": Compiling shader " << type
<< " with source:" << std::endl
<< source;
const GLuint shader = gl->CreateShader(type);
const GLchar* source_data = source.data();
const GLint length = base::checked_cast<GLint>(source.size());
gl->ShaderSource(shader, 1, &source_data, &length);
gl->CompileShader(shader);
GLint compile_status = GL_FALSE;
gl->GetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
// Fetch logs and forward them to the system logger. If compilation failed,
// clean-up and return 0 for error.
if (compile_status != GL_TRUE || VLOG_IS_ON(2)) {
GLint log_length = 0;
gl->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
std::string log;
if (log_length > 0) {
std::unique_ptr<GLchar[]> tmp(new GLchar[log_length]);
GLsizei returned_log_length = 0;
gl->GetShaderInfoLog(shader, log_length, &returned_log_length,
tmp.get());
log.assign(tmp.get(), returned_log_length);
}
if (log.empty()) {
log = "<<NO LOG>>";
}
if (compile_status != GL_TRUE) {
LOG(ERROR) << __func__ << ": Compilation of shader " << type
<< " failed:" << std::endl
<< log;
gl->DeleteShader(shader);
return 0;
}
VLOG(2) << __func__ << ": Compilation of shader " << type
<< " succeeded:" << std::endl
<< log;
}
return shader;
};
// Compile the vertex shader and attach it to the program.
const std::string shared_variables_str = shared_variables.str();
const GLuint vertex_shader =
CompileShaderFromSource(gl_,
vertex_header.str() + shared_variables_str +
"void main() {\n" + vertex_main.str() + "}\n",
GL_VERTEX_SHADER);
if (vertex_shader == 0) {
return;
}
gl_->AttachShader(program_, vertex_shader);
gl_->DeleteShader(vertex_shader);
// Compile the fragment shader and attach to |program_|.
const GLuint fragment_shader = CompileShaderFromSource(
gl_,
fragment_directives.str() + fragment_header.str() + shared_variables_str +
"void main() {\n" + fragment_main.str() + "}\n",
GL_FRAGMENT_SHADER);
if (fragment_shader == 0) {
return;
}
gl_->AttachShader(program_, fragment_shader);
gl_->DeleteShader(fragment_shader);
// Link the program.
gl_->LinkProgram(program_);
GLint link_status = GL_FALSE;
gl_->GetProgramiv(program_, GL_LINK_STATUS, &link_status);
if (link_status != GL_TRUE) {
LOG(ERROR) << "Failed to link shader program.";
return;
}
#define DCHECK_RESOLVED_LOCATION(member) \
DCHECK(member != -1 || gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR) \
<< "Failed to get " #member " in program, or GPU process crashed."
// Resolve the locations of the global variables.
position_location_ = gl_->GetAttribLocation(program_, "a_position");
DCHECK_RESOLVED_LOCATION(position_location_);
texcoord_location_ = gl_->GetAttribLocation(program_, "a_texcoord");
DCHECK_RESOLVED_LOCATION(texcoord_location_);
texture_location_ = gl_->GetUniformLocation(program_, "s_texture");
DCHECK_RESOLVED_LOCATION(texture_location_);
src_rect_location_ = gl_->GetUniformLocation(program_, "src_rect");
DCHECK_RESOLVED_LOCATION(src_rect_location_);
switch (shader_) {
case Shader::BILINEAR:
break;
case Shader::BILINEAR2:
case Shader::BILINEAR3:
case Shader::BILINEAR4:
case Shader::BILINEAR2X2:
case Shader::BICUBIC_HALF_1D:
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:
case Shader::DEINTERLEAVE_PAIRWISE_MRT:
scaling_vector_location_ =
gl_->GetUniformLocation(program_, "scaling_vector");
DCHECK_RESOLVED_LOCATION(scaling_vector_location_);
break;
case Shader::BICUBIC_UPSCALE:
src_pixelsize_location_ =
gl_->GetUniformLocation(program_, "src_pixelsize");
DCHECK_RESOLVED_LOCATION(src_pixelsize_location_);
scaling_vector_location_ =
gl_->GetUniformLocation(program_, "scaling_vector");
DCHECK_RESOLVED_LOCATION(scaling_vector_location_);
break;
}
#undef DCHECK_RESOLVED_LOCATION
}
GLScaler::ShaderProgram::~ShaderProgram() {
gl_->DeleteProgram(program_);
}
void GLScaler::ShaderProgram::UseProgram(const gfx::Size& src_texture_size,
const gfx::RectF& src_rect,
const gfx::Size& dst_size,
GLScaler::Axis primary_axis,
bool flip_y) {
gl_->UseProgram(program_);
// OpenGL defines the last parameter to VertexAttribPointer as type
// "const GLvoid*" even though it is actually an offset into the buffer
// object's data store and not a pointer to the client's address space.
const void* offsets[2] = {nullptr,
reinterpret_cast<const void*>(2 * sizeof(GLfloat))};
gl_->VertexAttribPointer(position_location_, 2, GL_FLOAT, GL_FALSE,
4 * sizeof(GLfloat), offsets[0]);
gl_->EnableVertexAttribArray(position_location_);
gl_->VertexAttribPointer(texcoord_location_, 2, GL_FLOAT, GL_FALSE,
4 * sizeof(GLfloat), offsets[1]);
gl_->EnableVertexAttribArray(texcoord_location_);
// Always sample from the first texture unit.
gl_->Uniform1i(texture_location_, 0);
// Convert |src_rect| from pixel coordinates to texture coordinates. The
// source texture coordinates are in the range [0.0,1.0] for each dimension,
// but the sampling rect may slightly "spill" outside that range (e.g., for
// scaler overscan).
GLfloat src_rect_texcoord[4] = {
src_rect.x() / src_texture_size.width(),
src_rect.y() / src_texture_size.height(),
src_rect.width() / src_texture_size.width(),
src_rect.height() / src_texture_size.height(),
};
if (flip_y) {
src_rect_texcoord[1] += src_rect_texcoord[3];
src_rect_texcoord[3] *= -1.0f;
}
gl_->Uniform4fv(src_rect_location_, 1, src_rect_texcoord);
// Set shader-specific uniform inputs. The |scaling_vector| is the ratio of
// the number of source pixels sampled per dest pixels output. It is used by
// the shader programs to locate distinct texels from the source texture, and
// sample them at the appropriate offset to produce each output texel.
switch (shader_) {
case Shader::BILINEAR:
break;
case Shader::BILINEAR2:
case Shader::BILINEAR3:
case Shader::BILINEAR4:
case Shader::BICUBIC_HALF_1D:
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:
case Shader::DEINTERLEAVE_PAIRWISE_MRT:
switch (primary_axis) {
case HORIZONTAL:
gl_->Uniform2f(scaling_vector_location_,
src_rect_texcoord[2] / dst_size.width(), 0.0);
break;
case VERTICAL:
gl_->Uniform2f(scaling_vector_location_, 0.0,
src_rect_texcoord[3] / dst_size.height());
break;
}
break;
case Shader::BILINEAR2X2:
gl_->Uniform2f(scaling_vector_location_,
src_rect_texcoord[2] / dst_size.width(),
src_rect_texcoord[3] / dst_size.height());
break;
case Shader::BICUBIC_UPSCALE:
gl_->Uniform2f(src_pixelsize_location_, src_texture_size.width(),
src_texture_size.height());
// For this shader program, the |scaling_vector| has an alternate meaning:
// It is only being used to select whether bicubic sampling is stepped in
// the X or the Y direction.
gl_->Uniform2f(scaling_vector_location_,
primary_axis == HORIZONTAL ? 1.0 : 0.0,
primary_axis == VERTICAL ? 1.0 : 0.0);
break;
}
}
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.
// 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 there is no input stage, simply modify |src_rect| to account for the
// overall |src_offset| and Y-flip.
gfx::RectF src_rect = ToSourceRect(output_rect);
if (input_stage_) {
const gfx::Rect input_rect = ToInputRect(src_rect);
EnsureIntermediateTextureDefined(input_rect.size());
input_stage_->ScaleToMultipleOutputs(src_texture, src_texture_size,
src_offset, intermediate_texture_, 0,
input_rect);
src_texture = intermediate_texture_;
src_texture_size = intermediate_texture_size_;
DCHECK(!is_flipped_source_);
src_rect -= input_rect.OffsetFromOrigin();
} else {
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;
}
}
// 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 4x1 source pixels to produce each output "pixel."
// There is no overscan. They can also be combined with a bilinear
// downscale, but not an upscale.
DCHECK_GE(scale_from_.x(), 4 * scale_to_.x());
DCHECK_EQ(HORIZONTAL, primary_axis_);
break;
case Shader::DEINTERLEAVE_PAIRWISE_MRT:
// This shader samples 2x1 source pixels to produce each output "pixel."
// There is no overscan. It can also be combined with a bilinear
// downscale, but not an upscale.
DCHECK_GE(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, GL_RGBA, size.width(), size.height(), 0,
GL_RGBA, program_->texture_type(), nullptr);
intermediate_texture_size_ = size;
}
}
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->input_stage() &&
scaler.params_.source_color_space != scaler.scaling_color_space_) {
out << ", with color x-form "
<< scaler.params_.source_color_space.ToString() << " to "
<< scaler.scaling_color_space_.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