blob: 779445adcc4945e0376bdb65a9e9e8c508a513b2 [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_I420_CONVERTER_H_
#define COMPONENTS_VIZ_COMMON_GL_I420_CONVERTER_H_
#include <memory>
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "components/viz/common/gl_scaler.h"
#include "components/viz/common/gpu/context_lost_observer.h"
#include "components/viz/common/viz_common_export.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/size.h"
namespace gfx {
class Rect;
class Vector2d;
} // namespace gfx
namespace viz {
class ContextProvider;
// A convenience wrapper around GLScaler that also reformats the scaler's output
// from interleaved RGBA to I420 planes. The I420 format consists of three
// planes of image data: the Y (luma) plane at full size, plus U and V (chroma)
// planes at half-width and half-height. There are two possible modes of
// operation (auto-detected at runtime):
//
// The faster, multiple rendering target (MRT) path: If the platform supports
// MRTs (most of the GPUs in use today), scaling and conversion is a two step
// process:
//
// Step 1: Produce NV61 format output, a luma plane and a UV-interleaved
// image. The luma plane is the same as the desired I420 luma plane. Note,
// that the UV image is of half-width but not yet half-height.
//
// (interleaved quads)
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA
// RGBA RGBA RGBA RGBA RGBA RGBA RGBA RGBA
// |
// | (luma plane) (chroma, interleaved)
// | YYYY YYYY UVUV UVUV
// +---> { YYYY YYYY + UVUV UVUV }
// YYYY YYYY UVUV UVUV
// YYYY YYYY UVUV UVUV
//
// Step 2: Derives the two I420 chroma planes from the UV-interleaved image
// from Step 1. This step separates the U and V pixels into separate planes,
// and also scales the height by half. This produces the desired I420 chroma
// planes.
//
// (chroma, interleaved) (two chroma planes)
// UVUV UVUV
// UVUV UVUV --> { UUUU + VVVV }
// UVUV UVUV UUUU VVVV
// UVUV UVUV
//
// The non-MRT path: For platforms that can only render to a single target at a
// time. This first scales the source to its final size and color-converts,
// transforming an RGBA input into a YUVA output. Then, it scans the YUVA image
// three times to generate each of the Y+U+V planes.
//
// Texture packing: OpenGLES2 treats all of the input and output textures as
// RGBA format. See comments for the Convert() method, which explains how the
// planar image data is packed into GL_RGBA textures, how the output textures
// should be sized, and why there are alignment requirements when specifying the
// output rect.
class VIZ_COMMON_EXPORT GLI420Converter : public ContextLostObserver {
public:
// GLI420Converter uses the exact same parameters as GLScaler.
using Parameters = GLScaler::Parameters;
explicit GLI420Converter(scoped_refptr<ContextProvider> context_provider);
~GLI420Converter() final;
// Returns true if the GL context provides the necessary support for enabling
// precise color management (see Parameters::enable_precise_color_management).
bool SupportsPreciseColorManagement() const {
return step1_.SupportsPreciseColorManagement();
}
// [Re]Configure the converter with the given |new_params|. Returns true on
// success, or false on failure. If |new_params| does not specify an
// |output_color_space|, it will be default to REC709.
bool Configure(const Parameters& new_params) WARN_UNUSED_RESULT;
// Returns the currently-configured and resolved Parameters. Results are
// undefined if Configure() has never been called successfully.
const Parameters& params() const { return params_; }
// Scales a portion of |src_texture|, then format-converts it to three I420
// planes, placing the results into |yuv_textures| at offset (0, 0). Returns
// true to indicate success, or false if this GLI420Converter is not valid.
//
// |src_texture_size| is the full, allocated size of the |src_texture|. This
// is required for computing texture coordinate transforms (and only because
// the OpenGL ES 2.0 API lacks the ability to query this info).
//
// |src_offset| is the offset in the source texture corresponding to point
// (0,0) in the source/output coordinate spaces. This prevents the need for
// extra texture copies just to re-position the source coordinate system.
//
// |aligned_output_rect| selects the region to draw (in the scaled, not the
// source, coordinate space). This is used to save work in cases where only a
// portion needs to be re-scaled. Because of the way the planar image data is
// packed in the output textures, the output rect's coordinates must be
// aligned (see ToAlignedRect() below).
//
// The |yuv_textures| are packed with planar data, meaning that each RGBA quad
// contains four pixel values: R is pixel 0, G is pixel 1, and so on. This
// makes it trivial to read-back the textures from a pixel buffer as a
// sequence of unsigned bytes. Thus, the output texture for the Y plane should
// be defined as GL_RGBA and be at least 1/4 the width of that specified in
// |aligned_output_rect|. Similarly, the output textures for the U and V
// planes should be defined as GL_RGBA and have at least 1/8 the width and 1/2
// the height of |aligned_output_rect|.
//
// WARNING: The output will always be placed at (0, 0) in the output textures,
// and not at |aligned_output_rect.origin()|.
//
// Note that the |src_texture| will have the min/mag filter set to GL_LINEAR
// and wrap_s/t set to CLAMP_TO_EDGE in this call.
bool Convert(GLuint src_texture,
const gfx::Size& src_texture_size,
const gfx::Vector2d& src_offset,
const gfx::Rect& aligned_output_rect,
const GLuint yuv_textures[3]);
// Returns the smallest Rect that encloses |rect| and lays on aligned
// boundaries, as required by the |aligned_output_rect| argument passed to
// Convert(). The horizontal coordinates will always be a multiple of 8, and
// the vertical coordinates a multiple of 2.
static gfx::Rect ToAlignedRect(const gfx::Rect& rect);
// Returns true if configuring a GLI420Converter with either |a| or |b| will
// produce identical behaviors and results.
static bool ParametersAreEquivalent(const Parameters& a, const Parameters& b);
private:
friend class GLI420ConverterPixelTest;
GLI420Converter(scoped_refptr<ContextProvider> context_provider,
bool allow_mrt_path);
bool is_using_mrt_path() const { return !step3_; }
// Creates or re-defines the intermediate texture, to ensure a texture of the
// given |required| size is defined.
void EnsureIntermediateTextureDefined(const gfx::Size& required);
// ContextLostObserver implementation.
void OnContextLost() final;
// The provider of the GL context. This is non-null while the GL context is
// valid and GLI420Converter is observing for context loss.
scoped_refptr<ContextProvider> context_provider_;
// Scales the source content and produces either:
// * MRT path: NV61-format output in two textures.
// * Non-MRT path: YUVA interleaved output in one texture.
GLScaler step1_;
// Holds the results from executing the first-stage |scaler_|, and is read by
// the other scalers:
// * MRT path: This holds the UV-interleaved data (2nd rendering target).
// * Non-MRT path: The scaled YUVA interleaved data.
GLuint intermediate_texture_ = 0;
gfx::Size intermediate_texture_size_;
// Step 2 operation using the |intermediate_texture_| as input:
// * MRT path: Separates-out the U and V planes (and scales height by half).
// * Non-MRT path: Extracts the luma plane.
GLScaler step2_;
// Steps 3 and 4 are used by the non-MRT path only, to extract the two chroma
// planes from |intermediate_texture_|.
std::unique_ptr<GLScaler> step3_;
std::unique_ptr<GLScaler> step4_;
// The Parameters that were provided to the last successful Configure() call.
Parameters params_;
DISALLOW_COPY_AND_ASSIGN(GLI420Converter);
};
} // namespace viz
#endif // COMPONENTS_VIZ_COMMON_GL_I420_CONVERTER_H_