blob: 9ef13ec53e581e35204a0fd546d8052125d65235 [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.
#ifndef COMPONENTS_VIZ_COMMON_GL_SCALER_H_
#define COMPONENTS_VIZ_COMMON_GL_SCALER_H_
#include <stdint.h>
#include <map>
#include <memory>
#include <tuple>
#include <utility>
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "components/viz/common/gpu/context_lost_observer.h"
#include "components/viz/common/viz_common_export.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h"
namespace gfx {
class ColorTransform;
} // namespace gfx
namespace viz {
class ContextProvider;
// A high-performance texture scaler for use with an OpenGL ES 2.0 context. It
// can be configured to operate at different quality levels, manages/converts
// color spaces, and optionally re-arranges/formats data in output textures for
// use with more-efficient texture readback pipelines.
class VIZ_COMMON_EXPORT GLScaler : public ContextLostObserver {
public:
struct VIZ_COMMON_EXPORT Parameters {
// Relative scale from/to factors. Both of these must be non-zero.
gfx::Vector2d scale_from = gfx::Vector2d(1, 1);
gfx::Vector2d scale_to = gfx::Vector2d(1, 1);
// The color space of the source texture and the desired color space of the
// output texture. If |source_color_space| is not set (or invalid), sRGB is
// assumed. If |output_color_space| is not set (or invalid), the source
// color space is assumed.
gfx::ColorSpace source_color_space;
gfx::ColorSpace output_color_space;
// Enable color management heuristics, using higher precision texture and
// gamma-aware scaling?
//
// When disabled, the gamma of the source color space and other concerns are
// ignored and 8-bit precision is used.
//
// When enabled, scaling occurs in a linear color space with 16-bit floats.
// This produces excellent results for virtually all color spaces while
// typically requiring twice the memory and execution resources. The caller
// must ensure the GL context supports the use of GL_RGBA16F format
// textures.
//
// Relevant reading: http://www.ericbrasseur.org/gamma.html
bool enable_precise_color_management = false;
// Selects the trade-off between quality and speed.
enum class Quality : int8_t {
// Bilinear single pass. Fastest possible. Do not use this unless the GL
// implementation is so slow that the other quality options won't work.
FAST,
// Bilinear upscale + N * 50% bilinear downscales. This is still fast
// enough for general-purpose use, and image quality is nearly as good as
// BEST when downscaling.
GOOD,
// Bicubic upscale + N * 50% bicubic downscales. Produces very good
// quality scaled images, but it's 2-8x slower than the "GOOD" quality.
BEST,
} quality = Quality::GOOD;
// Is the source texture Y-flipped (i.e., the origin is the lower-left
// corner and not the upper-left corner)? Most GL textures are Y-flipped.
// This information is required so that the scaler can correctly compute the
// sampling region.
bool is_flipped_source = true;
// Should the output be vertically flipped? Usually, this is used when the
// source is not Y-flipped, but the destination texture needs to be. Or, it
// can be used to draw the final output upside-down to avoid having to copy
// the rows in reverse order after a glReadPixels().
bool flip_output = false;
// Optionally rearrange the image data for export. Generally, this is used
// to make later readback steps more efficient (e.g., using glReadPixels()
// will produce the raw bytes in their correct locations).
//
// Output textures are assumed to be using one of the 4-channel RGBA
// formats. While it may be more "proper" to use a single-component texture
// format for the planar-oriented image data, not all GL implementations
// support the use of those formats. However, all must support at least
// GL_RGBA. Therefore, each RGBA pixel is treated as a generic "vec4" (a
// quad of values).
//
// When using this feature, it is usually necessary to adjust the
// |output_rect| passed to Scale() or ScaleToMultipleOutputs(). See notes
// below.
enum class ExportFormat : int8_t {
// Do not rearrange the image data:
//
// (interleaved quads) (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
INTERLEAVED_QUADS,
// Select one color channel, packing each of 4 pixels' values into the 4
// elements of one output quad.
//
// For example, for CHANNEL_0:
//
// (interleaved quads) (channel 0)
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA RRRR RRRR
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA --> RRRR RRRR
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA RRRR RRRR
//
// Note: Because of this packing, the horizontal coordinates of the
// |output_rect| used with Scale() should be divided by 4.
CHANNEL_0,
CHANNEL_1,
CHANNEL_2,
CHANNEL_3,
// I422 sampling, delivered via two output textures (NV61 format): The
// first texture is produced the same as CHANNEL_0, while the second
// texture contains CHANNEL_1 and CHANNEL_2 at half-width interleaved and
// full-height. For example, if this is combined with RGB→YUV color space
// conversion:
//
// (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
// |
// | (luma plane) (chroma, interleaved)
// | YYYY YYYY UVUV UVUV
// +---> { YYYY YYYY + UVUV UVUV }
// YYYY YYYY UVUV UVUV
//
// Note: Because of this packing, the horizontal coordinates of the
// |output_rect| used with ScaleToMultipleOutputs() should be divided by
// 4.
// Note 2: This requires a GL context that supports multiple render
// targets with at least two draw buffers.
NV61,
// Deinterleave into two output textures.
//
// UVUV UVUV UUUU VVVV
// UVUV UVUV --> { UUUU + VVVV }
// UVUV UVUV UUUU VVVV
//
// Note: Because of this packing, the horizontal coordinates of the
// |output_rect| used with ScaleToMultipleOutputs() should be divided by
// 2.
// Note 2: This requires a GL context that supports multiple render
// targets with at least two draw buffers.
DEINTERLEAVE_PAIRWISE,
} export_format = ExportFormat::INTERLEAVED_QUADS;
// Optionally swizzle the ordering of the values in each output quad. If the
// output of the scaler is not going to be read back (e.g., used with
// glReadPixels()), simply leave these unchanged. Otherwise, changing this
// allows a read-back pipeline to use the native format of the platform to
// avoid having to perform extra "BGRA⇄RGBA swizzle" memcpy's. Usually, this
// should match the format to be used with glReadPixels(), and that should
// match the GL_IMPLEMENTATION_COLOR_READ_FORMAT.
GLenum swizzle[2] = {
GL_RGBA, // For |dest_texture_0|.
GL_RGBA, // For |dest_texture_1|.
};
Parameters();
~Parameters();
};
explicit GLScaler(scoped_refptr<ContextProvider> context_provider);
~GLScaler() 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;
// Returns the maximum number of simultaneous drawing buffers supported by the
// GL context. Certain Parameters can only be used when this is more than 1.
int GetMaxDrawBuffersSupported() const;
// [Re]Configure the scaler with the given |new_params|. Returns true on
// success, or false on failure.
bool Configure(const Parameters& new_params) WARN_UNUSED_RESULT;
// 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.
const Parameters& params() const { return params_; }
// Scales a portion of |src_texture| and draws the result into |dest_texture|
// at offset (0, 0). Returns true to indicate success, or false if this
// GLScaler 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.
//
// |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. The implementation will back-compute, internally, to
// determine the region of the |src_texture| to sample.
//
// WARNING: The output will always be placed at (0, 0) in the |dest_texture|,
// and not at |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 Scale(GLuint src_texture,
const gfx::Size& src_texture_size,
const gfx::Vector2d& src_offset,
GLuint dest_texture,
const gfx::Rect& output_rect) WARN_UNUSED_RESULT {
return ScaleToMultipleOutputs(src_texture, src_texture_size, src_offset,
dest_texture, 0, output_rect);
}
// Same as above, but for use cases where there are two output textures drawn
// (see Parameters::ExportFormat).
bool 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) WARN_UNUSED_RESULT;
// Returns true if from:to represent the same scale ratio as that specified in
// |params|.
static bool ParametersHasSameScaleRatio(const Parameters& params,
const gfx::Vector2d& from,
const gfx::Vector2d& to);
private:
friend class GLScalerOverscanPixelTest;
friend class GLScalerShaderPixelTest;
using GLES2Interface = gpu::gles2::GLES2Interface;
enum Axis { HORIZONTAL, VERTICAL };
// The shaders used by each stage in the scaling pipeline.
enum class Shader : int8_t {
BILINEAR,
BILINEAR2,
BILINEAR3,
BILINEAR4,
BILINEAR2X2,
BICUBIC_UPSCALE,
BICUBIC_HALF_1D,
PLANAR_CHANNEL_0,
PLANAR_CHANNEL_1,
PLANAR_CHANNEL_2,
PLANAR_CHANNEL_3,
I422_NV61_MRT,
DEINTERLEAVE_PAIRWISE_MRT,
};
// A cached, re-usable shader program that performs one step in the scaling
// pipeline.
class VIZ_COMMON_EXPORT ShaderProgram {
public:
ShaderProgram(GLES2Interface* gl,
Shader shader,
GLint texture_format,
const gfx::ColorTransform* color_transform,
const GLenum swizzle[2]);
~ShaderProgram();
Shader shader() const { return shader_; }
GLint texture_format() const { return texture_format_; }
// UseProgram must be called with GL_ARRAY_BUFFER bound to a vertex
// attribute buffer. |src_texture_size| is the size of the entire source
// texture, regardless of which region is to be sampled. |src_rect| is the
// source region, not including overscan pixels past the edges.
// |primary_axis| determines whether multiple texture samplings occur in one
// direction or the other (for some shaders). Note that this cannot
// necessarily be determined by just comparing the src and dst sizes.
// |flip_y| causes the |src_rect| to be scanned upside-down, to produce a
// vertically-flipped result.
void UseProgram(const gfx::Size& src_texture_size,
const gfx::RectF& src_rect,
const gfx::Size& dst_size,
Axis primary_axis,
bool flip_y);
// GL_ARRAY_BUFFER data that must be bound when drawing with a
// ShaderProgram. These are the vertex attributes that will sweep the entire
// source area when executing the program. They represent triangle strip
// coordinates: The first two columns are (x,y) values interpolated to
// produce the vertex coordinates in object space, while the latter two
// columns are (s,t) values interpolated to produce the texture coordinates
// that correspond to the vertex coordinates.
static const GLfloat kVertexAttributes[16];
private:
GLES2Interface* const gl_;
const Shader shader_;
const GLint texture_format_;
// A program for copying a source texture into a destination texture.
const GLuint program_;
// The location of the position in the program.
GLint position_location_ = -1;
// The location of the texture coordinate in the program.
GLint texcoord_location_ = -1;
// The location of the source texture in the program.
GLint texture_location_ = -1;
// The location of the texture coordinate of the source rectangle in the
// program.
GLint src_rect_location_ = -1;
// Location of size of source image in pixels.
GLint src_pixelsize_location_ = -1;
// Location of vector for scaling ratio between source and dest textures.
GLint scaling_vector_location_ = -1;
DISALLOW_COPY_AND_ASSIGN(ShaderProgram);
};
// One scaling stage in a chain of scaler pipeline stages. Each ScalerStage
// owns the previous ScalerStage in the chain: At execution time, a "working
// backwards" approach is used: The previous "input" stage renders an
// intermediate result that will be used as input for the current stage.
//
// Each ScalerStage caches textures and framebuffers to avoid reallocating
// them for each separate image scaling, which can be expensive on some
// platforms/drivers.
class VIZ_COMMON_EXPORT ScalerStage {
public:
ScalerStage(GLES2Interface* gl,
Shader shader,
Axis primary_axis,
const gfx::Vector2d& scale_from,
const gfx::Vector2d& scale_to);
~ScalerStage();
Shader shader() const { return shader_; }
const gfx::Vector2d& scale_from() const { return scale_from_; }
const gfx::Vector2d& scale_to() const { return scale_to_; }
ScalerStage* input_stage() const { return input_stage_.get(); }
void set_input_stage(std::unique_ptr<ScalerStage> stage) {
input_stage_ = std::move(stage);
}
void set_shader_program(ShaderProgram* program) { program_ = program; }
bool is_flipped_source() const { return is_flipped_source_; }
void set_is_flipped_source(bool flipped) { is_flipped_source_ = flipped; }
bool flip_output() const { return flip_output_; }
void set_flip_output(bool flip) { flip_output_ = flip; }
void ScaleToMultipleOutputs(GLuint src_texture,
gfx::Size src_texture_size,
const gfx::Vector2d& src_offset,
GLuint dest_texture_0,
GLuint dest_texture_1,
const gfx::Rect& output_rect);
private:
friend class GLScalerOverscanPixelTest;
// Returns the given |output_rect| mapped to the input stage's coordinate
// system.
gfx::RectF ToSourceRect(const gfx::Rect& output_rect) const;
// Returns the given |source_rect| padded to include the overscan pixels the
// shader program will access.
gfx::Rect ToInputRect(gfx::RectF source_rect) const;
// Generates the intermediate texture and/or re-defines it if its size has
// changed.
void EnsureIntermediateTextureDefined(const gfx::Size& size);
GLES2Interface* const gl_;
const Shader shader_;
const Axis primary_axis_;
const gfx::Vector2d scale_from_;
const gfx::Vector2d scale_to_;
std::unique_ptr<ScalerStage> input_stage_;
ShaderProgram* program_ = nullptr;
bool is_flipped_source_ = false;
bool flip_output_ = false;
GLuint intermediate_texture_ = 0;
gfx::Size intermediate_texture_size_;
GLuint dest_framebuffer_ = 0;
DISALLOW_COPY_AND_ASSIGN(ScalerStage);
};
// ContextLostObserver implementation.
void OnContextLost() final;
// Returns a cached ShaderProgram, creating one on-demand if necessary.
ShaderProgram* GetShaderProgram(Shader shader,
GLint texture_format,
const gfx::ColorTransform* color_transform,
const GLenum swizzle[2]);
// The provider of the GL context. This is non-null while the GL context is
// valid and GLScaler is observing for context loss.
scoped_refptr<ContextProvider> context_provider_;
// Set by Configure() to the resolved set of Parameters.
Parameters params_;
// The maximum number of simultaneous draw buffers, lazy initialized by
// GetMaxDrawBuffersSupported(). -1 means "not yet known."
mutable int max_draw_buffers_ = -1;
// Cache of ShaderPrograms. The cache key consists of fields that correspond
// to the arguments of GetShaderProgram(): the shader, the texture format, the
// source and output color spaces (color transform), and the two swizzles.
using ShaderCacheKey = std::
tuple<Shader, GLint, gfx::ColorSpace, gfx::ColorSpace, GLenum, GLenum>;
std::map<ShaderCacheKey, ShaderProgram> shader_programs_;
// The GL_ARRAY_BUFFER that holds the vertices and the texture coordinates
// data for sweeping the source area when a ScalerStage draws a quad (to
// execute its shader program).
GLuint vertex_attributes_buffer_ = 0;
// The chain of ScalerStages.
std::unique_ptr<ScalerStage> chain_;
DISALLOW_COPY_AND_ASSIGN(GLScaler);
};
} // namespace viz
#endif // COMPONENTS_VIZ_COMMON_GL_SCALER_H_