summaryrefslogtreecommitdiff
path: root/chromium/components/viz/common/gl_scaler.h
blob: dd1dd7b3e9722cc662f7e1d3ac55048f7aadd370 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
// 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 <ostream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#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<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;
  // 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<ScalerStage> stage) {
      input_stage_ = std::move(stage);
    }
    std::unique_ptr<ScalerStage> 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<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,
                                  GLenum texture_type,
                                  const gfx::ColorTransform* color_transform,
                                  const GLenum swizzle[2]);

  // Create a scaling chain using the bilinear shaders.
  static std::unique_ptr<ScalerStage> 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<ScalerStage> 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<ScalerStage> MaybeAppendExportStage(
      gpu::gles2::GLES2Interface* gl,
      std::unique_ptr<ScalerStage> 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<std::string>& 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<ContextProvider> 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<bool> 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<Shader, GLenum, 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_;

  // 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_