// 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 #include #include #include #include #include #include #include #include "base/compiler_specific.h" #include "base/macros.h" #include "base/memory/scoped_refptr.h" #include "base/optional.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(const Parameters& other); ~Parameters(); }; explicit GLScaler(scoped_refptr 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; // 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| // 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); // 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; friend VIZ_COMMON_EXPORT std::ostream& operator<<(std::ostream&, const GLScaler&); using GLES2Interface = gpu::gles2::GLES2Interface; enum Axis { HORIZONTAL = 0, VERTICAL = 1 }; // 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, GLenum texture_type, const gfx::ColorTransform* color_transform, const GLenum swizzle[2]); ~ShaderProgram(); Shader shader() const { return shader_; } GLenum texture_type() const { return texture_type_; } // 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 GLenum texture_type_; // 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 stage) { input_stage_ = std::move(stage); } std::unique_ptr take_input_stage() { return std::move(input_stage_); } ShaderProgram* shader_program() const { return program_; } 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 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, GLenum texture_type, const gfx::ColorTransform* color_transform, const GLenum swizzle[2]); // Create a scaling chain using the bilinear shaders. static std::unique_ptr CreateAGoodScalingChain( gpu::gles2::GLES2Interface* gl, const gfx::Vector2d& scale_from, const gfx::Vector2d& scale_to); // Create a scaling chain using the bicubic shaders. static std::unique_ptr CreateTheBestScalingChain( gpu::gles2::GLES2Interface* gl, const gfx::Vector2d& scale_from, const gfx::Vector2d& scale_to); // Modifies |chain| by appending an export stage, to rearrange the image data // according to the requested |export_format|. In some cases, this will delete // the final stage in |chain| before appending the export stage. static std::unique_ptr MaybeAppendExportStage( gpu::gles2::GLES2Interface* gl, std::unique_ptr chain, Parameters::ExportFormat export_format); // Returns the other of the two axes. static Axis TheOtherAxis(Axis axis); // Returns the name of the |shader| in string form, for logging purposes. static const char* GetShaderName(Shader shader); // Returns true if the given |gl| context mentions all of |names| in its // extensions string. static bool AreAllGLExtensionsPresent(gpu::gles2::GLES2Interface* gl, const std::vector& names); // 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 context_provider_; // Set by Configure() to the resolved set of Parameters. Parameters params_; // If set to true, half-float textures are supported. This is lazy-initialized // by SupportsPreciseColorManagement(). mutable base::Optional supports_half_floats_; // 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; std::map 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 chain_; // The color space in which the scaling stages operate. gfx::ColorSpace scaling_color_space_; DISALLOW_COPY_AND_ASSIGN(GLScaler); }; // For logging. VIZ_COMMON_EXPORT std::ostream& operator<<(std::ostream& out, const GLScaler& scaler); } // namespace viz #endif // COMPONENTS_VIZ_COMMON_GL_SCALER_H_