1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/viz/common/gl_scaler.h"
6
7 #include <algorithm>
8 #include <array>
9 #include <sstream>
10 #include <string>
11
12 #include "base/logging.h"
13 #include "components/viz/common/gpu/context_provider.h"
14 #include "gpu/GLES2/gl2chromium.h"
15 #include "gpu/GLES2/gl2extchromium.h"
16 #include "ui/gfx/color_transform.h"
17 #include "ui/gfx/geometry/rect_conversions.h"
18
19 namespace viz {
20
21 namespace {
22
23 // The code in GLScaler that computes the ScalerStages is greatly simplified by
24 // being able to access the X and Y components by index (instead of
25 // Vector2d::x() or Vector2d::y()). Thus, define a helper class to represent the
26 // relative size as a 2-element std::array and convert to/from Vector2d.
27 struct RelativeSize : public std::array<int, 2> {
28 using std::array<int, 2>::operator[];
29
RelativeSizeviz::__anon72f6ed3a0111::RelativeSize30 RelativeSize(int width, int height) : std::array<int, 2>{{width, height}} {}
RelativeSizeviz::__anon72f6ed3a0111::RelativeSize31 explicit RelativeSize(const gfx::Vector2d& v)
32 : std::array<int, 2>{{v.x(), v.y()}} {}
33
AsVector2dviz::__anon72f6ed3a0111::RelativeSize34 gfx::Vector2d AsVector2d() const {
35 return gfx::Vector2d((*this)[0], (*this)[1]);
36 }
37 };
38
operator <<(std::ostream & out,const RelativeSize & size)39 std::ostream& operator<<(std::ostream& out, const RelativeSize& size) {
40 return (out << size[0] << 'x' << size[1]);
41 }
42
43 } // namespace
44
GLScaler(ContextProvider * context_provider)45 GLScaler::GLScaler(ContextProvider* context_provider)
46 : context_provider_(context_provider) {
47 if (context_provider_) {
48 DCHECK(context_provider_->ContextGL());
49 context_provider_->AddObserver(this);
50 }
51 }
52
~GLScaler()53 GLScaler::~GLScaler() {
54 OnContextLost(); // Ensures destruction in dependency order.
55 }
56
SupportsPreciseColorManagement() const57 bool GLScaler::SupportsPreciseColorManagement() const {
58 if (!context_provider_) {
59 return false;
60 }
61 if (!supports_half_floats_.has_value()) {
62 supports_half_floats_ = AreAllGLExtensionsPresent(
63 context_provider_->ContextGL(),
64 {"GL_EXT_color_buffer_half_float", "GL_OES_texture_half_float_linear"});
65 }
66 return supports_half_floats_.value();
67 }
68
GetMaxDrawBuffersSupported() const69 int GLScaler::GetMaxDrawBuffersSupported() const {
70 if (!context_provider_) {
71 return 0;
72 }
73
74 if (max_draw_buffers_ < 0) {
75 // Query the GL context for the multiple draw buffers extension and, if
76 // present, the actual platform-supported maximum.
77 GLES2Interface* const gl = context_provider_->ContextGL();
78 DCHECK(gl);
79 if (AreAllGLExtensionsPresent(gl, {"GL_EXT_draw_buffers"})) {
80 gl->GetIntegerv(GL_MAX_DRAW_BUFFERS_EXT, &max_draw_buffers_);
81 }
82
83 if (max_draw_buffers_ < 1) {
84 max_draw_buffers_ = 1;
85 }
86 }
87
88 return max_draw_buffers_;
89 }
90
Configure(const Parameters & new_params)91 bool GLScaler::Configure(const Parameters& new_params) {
92 chain_.reset();
93 shader_programs_.clear();
94
95 if (!context_provider_) {
96 return false;
97 }
98 GLES2Interface* const gl = context_provider_->ContextGL();
99 DCHECK(gl);
100
101 params_ = new_params;
102
103 // Ensure the client has provided valid scaling vectors.
104 if (params_.scale_from.x() == 0 || params_.scale_from.y() == 0 ||
105 params_.scale_to.x() == 0 || params_.scale_to.y() == 0) {
106 // The caller computed invalid scale_from and/or scale_to values.
107 DVLOG(1) << __func__ << ": Invalid scaling vectors: scale_from="
108 << params_.scale_from.ToString()
109 << ", scale_to=" << params_.scale_to.ToString();
110 return false;
111 }
112
113 // Resolve the color spaces according to the rules described in the header
114 // file.
115 if (!params_.source_color_space.IsValid()) {
116 params_.source_color_space = gfx::ColorSpace::CreateSRGB();
117 }
118 if (!params_.output_color_space.IsValid()) {
119 params_.output_color_space = params_.source_color_space;
120 }
121
122 // Check that 16-bit half floats are supported if precise color management is
123 // being requested.
124 if (params_.enable_precise_color_management) {
125 if (!SupportsPreciseColorManagement()) {
126 DVLOG(1) << __func__
127 << ": GL context does not support the half-floats "
128 "required for precise color management.";
129 return false;
130 }
131 }
132
133 // Check that MRT support is available if certain export formats were
134 // specified in the Parameters.
135 if (params_.export_format == Parameters::ExportFormat::NV61 ||
136 params_.export_format ==
137 Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE) {
138 if (GetMaxDrawBuffersSupported() < 2) {
139 DVLOG(1) << __func__ << ": GL context does not support 2+ draw buffers.";
140 return false;
141 }
142 }
143
144 // Color space transformation is meaningless when using the deinterleaver
145 // because it only deals with two color channels. This also means precise
146 // color management must be disabled.
147 if (params_.export_format ==
148 Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE &&
149 (params_.source_color_space != params_.output_color_space ||
150 params_.enable_precise_color_management)) {
151 NOTIMPLEMENTED();
152 return false;
153 }
154
155 // Check that one of the two implemented output swizzles has been specified.
156 for (GLenum s : params_.swizzle) {
157 if (s != GL_RGBA && s != GL_BGRA_EXT) {
158 NOTIMPLEMENTED();
159 return false;
160 }
161 }
162
163 // Create the chain of ScalerStages. If the quality setting is FAST or there
164 // is no scaling to be done, just create a single stage.
165 std::unique_ptr<ScalerStage> chain;
166 if (params_.quality == Parameters::Quality::FAST ||
167 params_.scale_from == params_.scale_to) {
168 chain = std::make_unique<ScalerStage>(gl, Shader::BILINEAR, HORIZONTAL,
169 params_.scale_from, params_.scale_to);
170 } else if (params_.quality == Parameters::Quality::GOOD) {
171 chain = CreateAGoodScalingChain(gl, params_.scale_from, params_.scale_to);
172 } else if (params_.quality == Parameters::Quality::BEST) {
173 chain = CreateTheBestScalingChain(gl, params_.scale_from, params_.scale_to);
174 } else {
175 NOTREACHED();
176 }
177 chain = MaybeAppendExportStage(gl, std::move(chain), params_.export_format);
178
179 // Determine the color space and the data type of the pixels in the
180 // intermediate textures, depending on whether precise color management is
181 // enabled. Note that nothing special need be done here if no scaling will be
182 // performed.
183 GLenum intermediate_texture_type;
184 if (params_.enable_precise_color_management &&
185 params_.scale_from != params_.scale_to) {
186 // Ensure the scaling color space is using a linear transfer function.
187 constexpr auto kLinearFunction = std::make_tuple(1, 0, 1, 0, 0, 0, 1);
188 skcms_TransferFunction fn;
189 if (params_.source_color_space.GetTransferFunction(&fn) &&
190 std::make_tuple(fn.a, fn.b, fn.c, fn.d, fn.e, fn.f, fn.g) ==
191 kLinearFunction) {
192 scaling_color_space_ = params_.source_color_space;
193 } else {
194 // Use the source color space, but with a linear transfer function.
195 skcms_Matrix3x3 to_XYZD50;
196 params_.source_color_space.GetPrimaryMatrix(&to_XYZD50);
197 std::tie(fn.a, fn.b, fn.c, fn.d, fn.e, fn.f, fn.g) = kLinearFunction;
198 scaling_color_space_ = gfx::ColorSpace::CreateCustom(to_XYZD50, fn);
199 }
200 intermediate_texture_type = GL_HALF_FLOAT_OES;
201 } else {
202 scaling_color_space_ = params_.source_color_space;
203 intermediate_texture_type = GL_UNSIGNED_BYTE;
204 }
205
206 // Set the shader program on the final stage. Include color space
207 // transformation and swizzling, if necessary.
208 std::unique_ptr<gfx::ColorTransform> transform;
209 if (scaling_color_space_ != params_.output_color_space) {
210 transform = gfx::ColorTransform::NewColorTransform(
211 scaling_color_space_, params_.output_color_space,
212 gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
213 }
214 ScalerStage* const final_stage = chain.get();
215 final_stage->set_shader_program(
216 GetShaderProgram(final_stage->shader(), intermediate_texture_type,
217 transform.get(), params_.swizzle));
218
219 // Set the shader program on all prior stages. These stages are all operating
220 // in the same color space, |scaling_color_space_|.
221 static const GLenum kNoSwizzle[2] = {GL_RGBA, GL_RGBA};
222 ScalerStage* input_stage = final_stage;
223 while (input_stage->input_stage()) {
224 input_stage = input_stage->input_stage();
225 input_stage->set_shader_program(GetShaderProgram(
226 input_stage->shader(), intermediate_texture_type, nullptr, kNoSwizzle));
227 }
228 // From this point, |input_stage| points to the first ScalerStage (i.e., the
229 // one that will be reading from the source).
230
231 // If necessary, prepend an extra "import stage" that color-converts the input
232 // before any scaling occurs. It's important not to merge color space
233 // conversion of the source with any other steps because the texture sampler
234 // must not linearly interpolate until after the colors have been mapped to a
235 // linear color space.
236 if (params_.source_color_space != scaling_color_space_) {
237 input_stage->set_input_stage(std::make_unique<ScalerStage>(
238 gl, Shader::BILINEAR, HORIZONTAL, input_stage->scale_from(),
239 input_stage->scale_from()));
240 input_stage = input_stage->input_stage();
241 transform = gfx::ColorTransform::NewColorTransform(
242 params_.source_color_space, scaling_color_space_,
243 gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
244 input_stage->set_shader_program(
245 GetShaderProgram(input_stage->shader(), intermediate_texture_type,
246 transform.get(), kNoSwizzle));
247 }
248
249 // If the source content is Y-flipped, the input scaler stage will perform
250 // math to account for this. It also will flip the content during scaling so
251 // that all following stages may assume the content is not flipped. Then, the
252 // final stage must ensure the final output is correctly flipped-back (or not)
253 // based on what the first stage did PLUS what is being requested by the
254 // client code.
255 if (params_.is_flipped_source) {
256 input_stage->set_is_flipped_source(true);
257 input_stage->set_flip_output(true);
258 }
259 if (input_stage->flip_output() != params_.flip_output) {
260 final_stage->set_flip_output(!final_stage->flip_output());
261 }
262
263 chain_ = std::move(chain);
264 VLOG(2) << __func__ << " built this: " << *this;
265 return true;
266 }
267
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)268 bool GLScaler::ScaleToMultipleOutputs(GLuint src_texture,
269 const gfx::Size& src_texture_size,
270 const gfx::Vector2d& src_offset,
271 GLuint dest_texture_0,
272 GLuint dest_texture_1,
273 const gfx::Rect& output_rect) {
274 if (!chain_) {
275 return false;
276 }
277
278 // Bind the vertex attributes used to sweep the entire source area when
279 // executing the shader programs.
280 GLES2Interface* const gl = context_provider_->ContextGL();
281 DCHECK(gl);
282 if (vertex_attributes_buffer_) {
283 gl->BindBuffer(GL_ARRAY_BUFFER, vertex_attributes_buffer_);
284 } else {
285 gl->GenBuffers(1, &vertex_attributes_buffer_);
286 gl->BindBuffer(GL_ARRAY_BUFFER, vertex_attributes_buffer_);
287 gl->BufferData(GL_ARRAY_BUFFER, sizeof(ShaderProgram::kVertexAttributes),
288 ShaderProgram::kVertexAttributes, GL_STATIC_DRAW);
289 }
290
291 // Disable GL clipping/blending features that interfere with assumptions made
292 // by the implementation. Only those known to possibly be enabled elsewhere in
293 // Chromium code are disabled here, while the remainder are sanity-DCHECK'ed.
294 gl->Disable(GL_SCISSOR_TEST);
295 gl->Disable(GL_STENCIL_TEST);
296 gl->Disable(GL_BLEND);
297 DCHECK_NE(gl->IsEnabled(GL_CULL_FACE), GL_TRUE);
298 DCHECK_NE(gl->IsEnabled(GL_DEPTH_TEST), GL_TRUE);
299 DCHECK_NE(gl->IsEnabled(GL_POLYGON_OFFSET_FILL), GL_TRUE);
300 DCHECK_NE(gl->IsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE), GL_TRUE);
301 DCHECK_NE(gl->IsEnabled(GL_SAMPLE_COVERAGE), GL_TRUE);
302 DCHECK_NE(gl->IsEnabled(GL_SCISSOR_TEST), GL_TRUE);
303 DCHECK_NE(gl->IsEnabled(GL_STENCIL_TEST), GL_TRUE);
304
305 chain_->ScaleToMultipleOutputs(src_texture, src_texture_size, src_offset,
306 dest_texture_0, dest_texture_1, output_rect);
307
308 gl->BindBuffer(GL_ARRAY_BUFFER, 0);
309 return true;
310 }
311
312 // static
ParametersHasSameScaleRatio(const GLScaler::Parameters & params,const gfx::Vector2d & from,const gfx::Vector2d & to)313 bool GLScaler::ParametersHasSameScaleRatio(const GLScaler::Parameters& params,
314 const gfx::Vector2d& from,
315 const gfx::Vector2d& to) {
316 // Returns true iff a_num/a_denom == b_num/b_denom.
317 const auto AreRatiosEqual = [](int32_t a_num, int32_t a_denom, int32_t b_num,
318 int32_t b_denom) -> bool {
319 // The math (for each dimension):
320 // If: a_num/a_denom == b_num/b_denom
321 // Then: a_num*b_denom == b_num*a_denom
322 //
323 // ...and cast to int64_t to guarantee no overflow from the multiplications.
324 return (static_cast<int64_t>(a_num) * b_denom) ==
325 (static_cast<int64_t>(b_num) * a_denom);
326 };
327
328 return AreRatiosEqual(params.scale_from.x(), params.scale_to.x(), from.x(),
329 to.x()) &&
330 AreRatiosEqual(params.scale_from.y(), params.scale_to.y(), from.y(),
331 to.y());
332 }
333
334 // static
ParametersAreEquivalent(const Parameters & a,const Parameters & b)335 bool GLScaler::ParametersAreEquivalent(const Parameters& a,
336 const Parameters& b) {
337 if (!ParametersHasSameScaleRatio(a, b.scale_from, b.scale_to) ||
338 a.enable_precise_color_management != b.enable_precise_color_management ||
339 a.quality != b.quality || a.is_flipped_source != b.is_flipped_source ||
340 a.flip_output != b.flip_output || a.export_format != b.export_format ||
341 a.swizzle[0] != b.swizzle[0] || a.swizzle[1] != b.swizzle[1]) {
342 return false;
343 }
344
345 const gfx::ColorSpace source_color_space_a =
346 a.source_color_space.IsValid() ? a.source_color_space
347 : gfx::ColorSpace::CreateSRGB();
348 const gfx::ColorSpace source_color_space_b =
349 b.source_color_space.IsValid() ? b.source_color_space
350 : gfx::ColorSpace::CreateSRGB();
351 if (source_color_space_a != source_color_space_b) {
352 return false;
353 }
354
355 const gfx::ColorSpace output_color_space_a = a.output_color_space.IsValid()
356 ? a.output_color_space
357 : source_color_space_a;
358 const gfx::ColorSpace output_color_space_b = b.output_color_space.IsValid()
359 ? b.output_color_space
360 : source_color_space_b;
361 return output_color_space_a == output_color_space_b;
362 }
363
OnContextLost()364 void GLScaler::OnContextLost() {
365 // The destruction order here is important due to data dependencies.
366 chain_.reset();
367 shader_programs_.clear();
368 if (vertex_attributes_buffer_) {
369 if (auto* gl = context_provider_->ContextGL()) {
370 gl->DeleteBuffers(1, &vertex_attributes_buffer_);
371 }
372 vertex_attributes_buffer_ = 0;
373 }
374 if (context_provider_) {
375 context_provider_->RemoveObserver(this);
376 context_provider_ = nullptr;
377 }
378 }
379
GetShaderProgram(Shader shader,GLenum texture_type,const gfx::ColorTransform * color_transform,const GLenum swizzle[2])380 GLScaler::ShaderProgram* GLScaler::GetShaderProgram(
381 Shader shader,
382 GLenum texture_type,
383 const gfx::ColorTransform* color_transform,
384 const GLenum swizzle[2]) {
385 const ShaderCacheKey key{
386 shader,
387 texture_type,
388 color_transform ? color_transform->GetSrcColorSpace() : gfx::ColorSpace(),
389 color_transform ? color_transform->GetDstColorSpace() : gfx::ColorSpace(),
390 swizzle[0],
391 swizzle[1]};
392 auto it = shader_programs_.find(key);
393 if (it == shader_programs_.end()) {
394 GLES2Interface* const gl = context_provider_->ContextGL();
395 DCHECK(gl);
396 it = shader_programs_
397 .emplace(std::piecewise_construct, std::forward_as_tuple(key),
398 std::forward_as_tuple(gl, shader, texture_type,
399 color_transform, swizzle))
400 .first;
401 }
402 return &it->second;
403 }
404
405 // static
CreateAGoodScalingChain(gpu::gles2::GLES2Interface * gl,const gfx::Vector2d & scale_from,const gfx::Vector2d & scale_to)406 std::unique_ptr<GLScaler::ScalerStage> GLScaler::CreateAGoodScalingChain(
407 gpu::gles2::GLES2Interface* gl,
408 const gfx::Vector2d& scale_from,
409 const gfx::Vector2d& scale_to) {
410 DCHECK(scale_from.x() != 0 && scale_from.y() != 0)
411 << "Bad scale_from: " << scale_from.ToString();
412 DCHECK(scale_to.x() != 0 && scale_to.y() != 0)
413 << "Bad scale_to: " << scale_to.ToString();
414 DCHECK(scale_from != scale_to);
415
416 // The GOOD quality chain performs one bilinear upscale followed by N bilinear
417 // halvings, and does this is both directions. Exception: No upscale is needed
418 // when |scale_from| is a power of two multiple of |scale_to|.
419 //
420 // Since all shaders use bilinear filtering, the heuristics below attempt to
421 // greedily merge steps wherever possible to minimize GPU memory usage and
422 // processing time. This also means that it will be extremely rare for the
423 // stage doing the initial upscale to actually require a larger output texture
424 // than the source texture (a downscale will be merged into the same stage).
425
426 // Determine the initial upscaled-to size, as the minimum number of doublings
427 // to make |scale_to| greater than |scale_from|.
428 const RelativeSize from(scale_from);
429 const RelativeSize to(scale_to);
430 RelativeSize upscale_to = to;
431 for (Axis x_or_y : std::array<Axis, 2>{HORIZONTAL, VERTICAL}) {
432 while (upscale_to[x_or_y] < from[x_or_y]) {
433 upscale_to[x_or_y] *= 2;
434 }
435 }
436
437 // Create the stages in order from first-to-last, taking the greediest path
438 // each time. Something like an A* algorithm would be better for discovering
439 // an optimal sequence of operations, and would allow using the BILINEAR3
440 // shader as well, but the run-time performance to compute the stages would be
441 // too prohibitive.
442 std::unique_ptr<ScalerStage> chain;
443 struct CandidateOp {
444 Shader shader;
445 Axis primary_axis;
446 RelativeSize output_size;
447 };
448 std::vector<CandidateOp> candidates;
449 for (RelativeSize cur = from; cur != to;
450 cur = RelativeSize(chain->scale_to())) {
451 candidates.clear();
452
453 // Determine whether it's possible to do exactly 2 bilinear passes in both
454 // directions.
455 RelativeSize output_size_2x2 = {0, 0};
456 for (Axis x_or_y : std::array<Axis, 2>{VERTICAL, HORIZONTAL}) {
457 if (cur[x_or_y] == from[x_or_y]) {
458 // For the first stage, the 2 bilinear passes must be the initial
459 // upscale followed by one downscale. If there is no initial upscale,
460 // then the 2 passes must both be downscales.
461 if (upscale_to[x_or_y] != from[x_or_y] &&
462 upscale_to[x_or_y] / 2 >= to[x_or_y]) {
463 output_size_2x2[x_or_y] = upscale_to[x_or_y] / 2;
464 } else if (upscale_to[x_or_y] == from[x_or_y] &&
465 upscale_to[x_or_y] / 4 >= to[x_or_y]) {
466 output_size_2x2[x_or_y] = cur[x_or_y] / 4;
467 }
468 } else {
469 // For all later stages, the 2 bilinear passes must be 2 halvings.
470 if (cur[x_or_y] / 4 >= to[x_or_y]) {
471 output_size_2x2[x_or_y] = cur[x_or_y] / 4;
472 }
473 }
474 }
475 if (output_size_2x2[HORIZONTAL] != 0 && output_size_2x2[VERTICAL] != 0) {
476 candidates.push_back(
477 CandidateOp{Shader::BILINEAR2X2, HORIZONTAL, output_size_2x2});
478 }
479
480 // Determine the valid set of Ops that do 1 to 3 bilinear passes in one
481 // direction and 0 or 1 pass in the other direction.
482 for (Axis x_or_y : std::array<Axis, 2>{VERTICAL, HORIZONTAL}) {
483 // The first bilinear pass in x_or_y must be an upscale or a halving.
484 Shader shader = Shader::BILINEAR;
485 RelativeSize output_size = cur;
486 if (cur[x_or_y] == from[x_or_y] && upscale_to[x_or_y] != from[x_or_y]) {
487 output_size[x_or_y] = upscale_to[x_or_y];
488 } else if (cur[x_or_y] / 2 >= to[x_or_y]) {
489 output_size[x_or_y] /= 2;
490 } else {
491 DCHECK_EQ(cur[x_or_y], to[x_or_y]);
492 continue;
493 }
494
495 // Determine whether 1 or 2 additional passes can be made in the same
496 // direction.
497 if (output_size[x_or_y] / 4 >= to[x_or_y]) {
498 shader = Shader::BILINEAR4; // 2 more passes == 3 total.
499 output_size[x_or_y] /= 4;
500 } else if (output_size[x_or_y] / 2 >= to[x_or_y]) {
501 shader = Shader::BILINEAR2; // 1 more pass == 2 total.
502 output_size[x_or_y] /= 2;
503 } else {
504 DCHECK_EQ(output_size[x_or_y], to[x_or_y]);
505 }
506
507 // Determine whether 0 or 1 bilinear passes can be made in the other
508 // direction at the same time.
509 const Axis y_or_x = TheOtherAxis(x_or_y);
510 if (cur[y_or_x] == from[y_or_x] && upscale_to[y_or_x] != from[y_or_x]) {
511 output_size[y_or_x] = upscale_to[y_or_x];
512 } else if (cur[y_or_x] / 2 >= to[y_or_x]) {
513 output_size[y_or_x] /= 2;
514 } else {
515 DCHECK_EQ(cur[y_or_x], to[y_or_x]);
516 }
517
518 candidates.push_back(CandidateOp{shader, x_or_y, output_size});
519 }
520
521 // From the candidates, pick the one that produces the fewest number of
522 // output pixels, and append a new ScalerStage. There are opportunities to
523 // improve the "cost function" here (e.g., pixels in the Y direction
524 // probably cost more to process than pixels in the X direction), but that
525 // would require more research.
526 const auto best_candidate = std::min_element(
527 candidates.begin(), candidates.end(),
528 [](const CandidateOp& a, const CandidateOp& b) {
529 static_assert(sizeof(a.output_size[0]) <= sizeof(int32_t),
530 "Overflow issue in the math here.");
531 const int64_t cost_of_a =
532 int64_t{a.output_size[HORIZONTAL]} * a.output_size[VERTICAL];
533 const int64_t cost_of_b =
534 int64_t{b.output_size[HORIZONTAL]} * b.output_size[VERTICAL];
535 return cost_of_a < cost_of_b;
536 });
537 DCHECK(best_candidate != candidates.end());
538 DCHECK(cur != best_candidate->output_size)
539 << "Best candidate's output size (" << best_candidate->output_size
540 << ") should not equal the input size.";
541 auto next_stage = std::make_unique<ScalerStage>(
542 gl, best_candidate->shader, best_candidate->primary_axis,
543 cur.AsVector2d(), best_candidate->output_size.AsVector2d());
544 next_stage->set_input_stage(std::move(chain));
545 chain = std::move(next_stage);
546 }
547
548 return chain;
549 }
550
551 // static
CreateTheBestScalingChain(gpu::gles2::GLES2Interface * gl,const gfx::Vector2d & scale_from,const gfx::Vector2d & scale_to)552 std::unique_ptr<GLScaler::ScalerStage> GLScaler::CreateTheBestScalingChain(
553 gpu::gles2::GLES2Interface* gl,
554 const gfx::Vector2d& scale_from,
555 const gfx::Vector2d& scale_to) {
556 // The BEST quality chain performs one bicubic upscale followed by N bicubic
557 // halvings, and does this is both directions. Exception: No upscale is needed
558 // when |scale_from| is a power of two multiple of |scale_to|.
559
560 // Determine the initial upscaled-to size, as the minimum number of doublings
561 // to make |scale_to| greater than |scale_from|.
562 const RelativeSize from(scale_from);
563 const RelativeSize to(scale_to);
564 RelativeSize upscale_to = to;
565 for (Axis x_or_y : std::array<Axis, 2>{HORIZONTAL, VERTICAL}) {
566 while (upscale_to[x_or_y] < from[x_or_y]) {
567 upscale_to[x_or_y] *= 2;
568 }
569 }
570
571 // Create the stages in order from first-to-last.
572 RelativeSize cur = from;
573 std::unique_ptr<ScalerStage> chain;
574 for (Axis x_or_y : std::array<Axis, 2>{VERTICAL, HORIZONTAL}) {
575 if (upscale_to[x_or_y] != from[x_or_y]) {
576 RelativeSize next = cur;
577 next[x_or_y] = upscale_to[x_or_y];
578 auto upscale_stage =
579 std::make_unique<ScalerStage>(gl, Shader::BICUBIC_UPSCALE, x_or_y,
580 cur.AsVector2d(), next.AsVector2d());
581 upscale_stage->set_input_stage(std::move(chain));
582 chain = std::move(upscale_stage);
583 cur = next;
584 }
585 while (cur[x_or_y] > to[x_or_y]) {
586 RelativeSize next = cur;
587 next[x_or_y] /= 2;
588 auto next_stage =
589 std::make_unique<ScalerStage>(gl, Shader::BICUBIC_HALF_1D, x_or_y,
590 cur.AsVector2d(), next.AsVector2d());
591 next_stage->set_input_stage(std::move(chain));
592 chain = std::move(next_stage);
593 cur = next;
594 }
595 }
596 DCHECK_EQ(cur, to);
597
598 return chain;
599 }
600
601 // static
MaybeAppendExportStage(gpu::gles2::GLES2Interface * gl,std::unique_ptr<GLScaler::ScalerStage> chain,GLScaler::Parameters::ExportFormat export_format)602 std::unique_ptr<GLScaler::ScalerStage> GLScaler::MaybeAppendExportStage(
603 gpu::gles2::GLES2Interface* gl,
604 std::unique_ptr<GLScaler::ScalerStage> chain,
605 GLScaler::Parameters::ExportFormat export_format) {
606 DCHECK(chain);
607
608 if (export_format == Parameters::ExportFormat::INTERLEAVED_QUADS) {
609 return chain; // No format change.
610 }
611
612 // If the final stage uses the BILINEAR shader that is not upscaling, the
613 // export stage can replace it with no change in the results. Otherwise, a
614 // separate export stage will be appended.
615 gfx::Vector2d scale_from = chain->scale_from();
616 const gfx::Vector2d scale_to = chain->scale_to();
617 if (chain->shader() == Shader::BILINEAR && scale_from.x() >= scale_to.x() &&
618 scale_from.y() >= scale_to.y()) {
619 chain = chain->take_input_stage();
620 } else {
621 scale_from = scale_to;
622 }
623
624 Shader shader = Shader::BILINEAR;
625 scale_from.set_x(scale_from.x() * 4);
626 switch (export_format) {
627 case Parameters::ExportFormat::INTERLEAVED_QUADS:
628 NOTREACHED();
629 break;
630 case Parameters::ExportFormat::CHANNEL_0:
631 shader = Shader::PLANAR_CHANNEL_0;
632 break;
633 case Parameters::ExportFormat::CHANNEL_1:
634 shader = Shader::PLANAR_CHANNEL_1;
635 break;
636 case Parameters::ExportFormat::CHANNEL_2:
637 shader = Shader::PLANAR_CHANNEL_2;
638 break;
639 case Parameters::ExportFormat::CHANNEL_3:
640 shader = Shader::PLANAR_CHANNEL_3;
641 break;
642 case Parameters::ExportFormat::NV61:
643 shader = Shader::I422_NV61_MRT;
644 break;
645 case Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE:
646 shader = Shader::DEINTERLEAVE_PAIRWISE_MRT;
647 // Horizontal scale is only 0.5X, not 0.25X like all the others.
648 scale_from.set_x(scale_from.x() / 2);
649 break;
650 }
651
652 auto export_stage = std::make_unique<ScalerStage>(gl, shader, HORIZONTAL,
653 scale_from, scale_to);
654 export_stage->set_input_stage(std::move(chain));
655 return export_stage;
656 }
657
658 // static
TheOtherAxis(GLScaler::Axis x_or_y)659 GLScaler::Axis GLScaler::TheOtherAxis(GLScaler::Axis x_or_y) {
660 return x_or_y == HORIZONTAL ? VERTICAL : HORIZONTAL;
661 }
662
663 // static
GetShaderName(GLScaler::Shader shader)664 const char* GLScaler::GetShaderName(GLScaler::Shader shader) {
665 switch (shader) {
666 #define CASE_RETURN_SHADER_STR(x) \
667 case Shader::x: \
668 return #x
669 CASE_RETURN_SHADER_STR(BILINEAR);
670 CASE_RETURN_SHADER_STR(BILINEAR2);
671 CASE_RETURN_SHADER_STR(BILINEAR3);
672 CASE_RETURN_SHADER_STR(BILINEAR4);
673 CASE_RETURN_SHADER_STR(BILINEAR2X2);
674 CASE_RETURN_SHADER_STR(BICUBIC_UPSCALE);
675 CASE_RETURN_SHADER_STR(BICUBIC_HALF_1D);
676 CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_0);
677 CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_1);
678 CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_2);
679 CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_3);
680 CASE_RETURN_SHADER_STR(I422_NV61_MRT);
681 CASE_RETURN_SHADER_STR(DEINTERLEAVE_PAIRWISE_MRT);
682 #undef CASE_RETURN_SHADER_STR
683 }
684 }
685
686 // static
AreAllGLExtensionsPresent(gpu::gles2::GLES2Interface * gl,const std::vector<std::string> & names)687 bool GLScaler::AreAllGLExtensionsPresent(
688 gpu::gles2::GLES2Interface* gl,
689 const std::vector<std::string>& names) {
690 DCHECK(gl);
691 if (const auto* extensions = gl->GetString(GL_EXTENSIONS)) {
692 const std::string extensions_string =
693 " " + std::string(reinterpret_cast<const char*>(extensions)) + " ";
694 for (const std::string& name : names) {
695 if (extensions_string.find(" " + name + " ") == std::string::npos) {
696 return false;
697 }
698 }
699 return true;
700 }
701 return false;
702 }
703
704 GLScaler::Parameters::Parameters() = default;
705 GLScaler::Parameters::Parameters(const Parameters& other) = default;
706 GLScaler::Parameters::~Parameters() = default;
707
708 // static
709 const GLfloat GLScaler::ShaderProgram::kVertexAttributes[16] = {
710 -1.0f, -1.0f, 0.0f, 0.0f, // vertex 0
711 1.0f, -1.0f, 1.0f, 0.0f, // vertex 1
712 -1.0f, 1.0f, 0.0f, 1.0f, // vertex 2
713 1.0f, 1.0f, 1.0f, 1.0f, // vertex 3
714 };
715
ShaderProgram(gpu::gles2::GLES2Interface * gl,GLScaler::Shader shader,GLenum texture_type,const gfx::ColorTransform * color_transform,const GLenum swizzle[2])716 GLScaler::ShaderProgram::ShaderProgram(
717 gpu::gles2::GLES2Interface* gl,
718 GLScaler::Shader shader,
719 GLenum texture_type,
720 const gfx::ColorTransform* color_transform,
721 const GLenum swizzle[2])
722 : gl_(gl),
723 shader_(shader),
724 texture_type_(texture_type),
725 program_(gl_->CreateProgram()) {
726 DCHECK(program_);
727
728 std::basic_ostringstream<GLchar> vertex_header;
729 std::basic_ostringstream<GLchar> fragment_directives;
730 std::basic_ostringstream<GLchar> fragment_header;
731 std::basic_ostringstream<GLchar> shared_variables;
732 std::basic_ostringstream<GLchar> vertex_main;
733 std::basic_ostringstream<GLchar> fragment_main;
734
735 vertex_header
736 << ("precision highp float;\n"
737 "attribute vec2 a_position;\n"
738 "attribute vec2 a_texcoord;\n"
739 "uniform vec4 src_rect;\n");
740
741 fragment_header << "precision mediump float;\n";
742 switch (texture_type_) {
743 case GL_FLOAT:
744 fragment_header << "precision highp sampler2D;\n";
745 break;
746 case GL_HALF_FLOAT_OES:
747 fragment_header << "precision mediump sampler2D;\n";
748 break;
749 default:
750 fragment_header << "precision lowp sampler2D;\n";
751 break;
752 }
753 fragment_header << "uniform sampler2D s_texture;\n";
754
755 if (color_transform && shader_ != Shader::PLANAR_CHANNEL_3) {
756 const std::string& source = color_transform->GetShaderSource();
757 // Assumption: gfx::ColorTransform::GetShaderSource() should provide a
758 // function named DoColorConversion() that takes a vec3 argument and returns
759 // a vec3.
760 DCHECK_NE(source.find("DoColorConversion"), std::string::npos);
761 fragment_header << source;
762 }
763
764 vertex_main
765 << (" gl_Position = vec4(a_position, 0.0, 1.0);\n"
766 " vec2 texcoord = src_rect.xy + a_texcoord * src_rect.zw;\n");
767
768 switch (shader_) {
769 case Shader::BILINEAR:
770 shared_variables << "varying highp vec2 v_texcoord;\n";
771 vertex_main << " v_texcoord = texcoord;\n";
772 fragment_main << " vec4 sample = texture2D(s_texture, v_texcoord);\n";
773 if (color_transform) {
774 fragment_main << " sample.rgb = DoColorConversion(sample.rgb);\n";
775 }
776 if (swizzle[0] == GL_BGRA_EXT) {
777 fragment_main << " sample.rb = sample.br;\n";
778 }
779 fragment_main << " gl_FragColor = sample;\n";
780 break;
781
782 case Shader::BILINEAR2:
783 // This is equivialent to two passes of the BILINEAR shader above. It can
784 // be used to scale an image down 1.0x-2.0x in either dimension, or
785 // exactly 4x.
786 shared_variables << "varying highp vec4 v_texcoords;\n";
787 vertex_header << "uniform vec2 scaling_vector;\n";
788 vertex_main
789 << (" vec2 step = scaling_vector / 4.0;\n"
790 " v_texcoords.xy = texcoord + step;\n"
791 " v_texcoords.zw = texcoord - step;\n");
792 fragment_main
793 << (" vec4 blended = (texture2D(s_texture, v_texcoords.xy) +\n"
794 " texture2D(s_texture, v_texcoords.zw)) /\n"
795 " 2.0;\n");
796 if (color_transform) {
797 fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
798 }
799 if (swizzle[0] == GL_BGRA_EXT) {
800 fragment_main << " blended.rb = blended.br;\n";
801 }
802 fragment_main << " gl_FragColor = blended;\n";
803 break;
804
805 case Shader::BILINEAR3:
806 // This is kind of like doing 1.5 passes of the BILINEAR shader. It can be
807 // used to scale an image down 1.5x-3.0x, or exactly 6x.
808 shared_variables
809 << ("varying highp vec4 v_texcoords0;\n"
810 "varying highp vec2 v_texcoords1;\n");
811 vertex_header << "uniform vec2 scaling_vector;\n";
812 vertex_main
813 << (" vec2 step = scaling_vector / 3.0;\n"
814 " v_texcoords0.xy = texcoord + step;\n"
815 " v_texcoords0.zw = texcoord;\n"
816 " v_texcoords1 = texcoord - step;\n");
817 fragment_main
818 << (" vec4 blended = (texture2D(s_texture, v_texcoords0.xy) +\n"
819 " texture2D(s_texture, v_texcoords0.zw) +\n"
820 " texture2D(s_texture, v_texcoords1)) / 3.0;\n");
821 if (color_transform) {
822 fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
823 }
824 if (swizzle[0] == GL_BGRA_EXT) {
825 fragment_main << " blended.rb = blended.br;\n";
826 }
827 fragment_main << " gl_FragColor = blended;\n";
828 break;
829
830 case Shader::BILINEAR4:
831 // This is equivialent to three passes of the BILINEAR shader above. It
832 // can be used to scale an image down 2.0x-4.0x or exactly 8x.
833 shared_variables << "varying highp vec4 v_texcoords[2];\n";
834 vertex_header << "uniform vec2 scaling_vector;\n";
835 vertex_main
836 << (" vec2 step = scaling_vector / 8.0;\n"
837 " v_texcoords[0].xy = texcoord - step * 3.0;\n"
838 " v_texcoords[0].zw = texcoord - step;\n"
839 " v_texcoords[1].xy = texcoord + step;\n"
840 " v_texcoords[1].zw = texcoord + step * 3.0;\n");
841 fragment_main
842 << (" vec4 blended = (\n"
843 " texture2D(s_texture, v_texcoords[0].xy) +\n"
844 " texture2D(s_texture, v_texcoords[0].zw) +\n"
845 " texture2D(s_texture, v_texcoords[1].xy) +\n"
846 " texture2D(s_texture, v_texcoords[1].zw)) / 4.0;\n");
847 if (color_transform) {
848 fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
849 }
850 if (swizzle[0] == GL_BGRA_EXT) {
851 fragment_main << " blended.rb = blended.br;\n";
852 }
853 fragment_main << " gl_FragColor = blended;\n";
854 break;
855
856 case Shader::BILINEAR2X2:
857 // This is equivialent to four passes of the BILINEAR shader above, two in
858 // each dimension. It can be used to scale an image down 1.0x-2.0x in both
859 // X and Y directions. Or, it could be used to scale an image down by
860 // exactly 4x in both dimensions.
861 shared_variables << "varying highp vec4 v_texcoords[2];\n";
862 vertex_header << "uniform vec2 scaling_vector;\n";
863 vertex_main
864 << (" vec2 step = scaling_vector / 4.0;\n"
865 " v_texcoords[0].xy = texcoord + vec2(step.x, step.y);\n"
866 " v_texcoords[0].zw = texcoord + vec2(step.x, -step.y);\n"
867 " v_texcoords[1].xy = texcoord + vec2(-step.x, step.y);\n"
868 " v_texcoords[1].zw = texcoord + vec2(-step.x, -step.y);\n");
869 fragment_main
870 << (" vec4 blended = (\n"
871 " texture2D(s_texture, v_texcoords[0].xy) +\n"
872 " texture2D(s_texture, v_texcoords[0].zw) +\n"
873 " texture2D(s_texture, v_texcoords[1].xy) +\n"
874 " texture2D(s_texture, v_texcoords[1].zw)) / 4.0;\n");
875 if (color_transform) {
876 fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
877 }
878 if (swizzle[0] == GL_BGRA_EXT) {
879 fragment_main << " blended.rb = blended.br;\n";
880 }
881 fragment_main << " gl_FragColor = blended;\n";
882 break;
883
884 case Shader::BICUBIC_UPSCALE:
885 // When scaling up, 4 texture reads are necessary. However, some
886 // instructions can be saved because the parameter passed to the bicubic
887 // function will be in a known range. Also, when sampling the bicubic
888 // function like this, the sum is always exactly one, so normalization can
889 // be skipped as well.
890 shared_variables << "varying highp vec2 v_texcoord;\n";
891 vertex_main << " v_texcoord = texcoord;\n";
892 fragment_header
893 << ("uniform highp vec2 src_pixelsize;\n"
894 "uniform highp vec2 scaling_vector;\n"
895 "const float a = -0.5;\n"
896 // This function is equivialent to calling the bicubic
897 // function with x-1, x, 1-x and 2-x (assuming
898 // 0 <= x < 1). The following is the Catmull-Rom spline.
899 // See: http://wikipedia.org/wiki/Cubic_Hermite_spline
900 "vec4 filt4(float x) {\n"
901 " return vec4(x * x * x, x * x, x, 1) *\n"
902 " mat4( a, -2.0 * a, a, 0.0,\n"
903 " a + 2.0, -a - 3.0, 0.0, 1.0,\n"
904 " -a - 2.0, 3.0 + 2.0 * a, -a, 0.0,\n"
905 " -a, a, 0.0, 0.0);\n"
906 "}\n"
907 "mat4 pixels_x(highp vec2 pos, highp vec2 step) {\n"
908 " return mat4(texture2D(s_texture, pos - step),\n"
909 " texture2D(s_texture, pos),\n"
910 " texture2D(s_texture, pos + step),\n"
911 " texture2D(s_texture, pos + step * 2.0));\n"
912 "}\n");
913 fragment_main
914 << (" highp vec2 pixel_pos = v_texcoord * src_pixelsize - \n"
915 " scaling_vector / 2.0;\n"
916 " highp float frac = fract(dot(pixel_pos, scaling_vector));\n"
917 " highp vec2 base = \n"
918 " (floor(pixel_pos) + vec2(0.5)) / src_pixelsize;\n"
919 " highp vec2 step = scaling_vector / src_pixelsize;\n"
920 " vec4 blended = pixels_x(base, step) * filt4(frac);\n");
921 if (color_transform) {
922 fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
923 }
924 if (swizzle[0] == GL_BGRA_EXT) {
925 fragment_main << " blended.rb = blended.br;\n";
926 }
927 fragment_main << " gl_FragColor = blended;\n";
928 break;
929
930 case Shader::BICUBIC_HALF_1D:
931 // This scales down an image by exactly half in one dimension. The
932 // bilinear lookup reduces the number of texture reads from 8 to 4.
933 shared_variables << "varying highp vec4 v_texcoords[2];\n";
934 vertex_header
935 << ("uniform vec2 scaling_vector;\n"
936 "const float CenterDist = 99.0 / 140.0;\n"
937 "const float LobeDist = 11.0 / 4.0;\n");
938 vertex_main
939 << (" vec2 step = scaling_vector / 2.0;\n"
940 " v_texcoords[0].xy = texcoord - LobeDist * step;\n"
941 " v_texcoords[0].zw = texcoord - CenterDist * step;\n"
942 " v_texcoords[1].xy = texcoord + CenterDist * step;\n"
943 " v_texcoords[1].zw = texcoord + LobeDist * step;\n");
944 fragment_header
945 << ("const float CenterWeight = 35.0 / 64.0;\n"
946 "const float LobeWeight = -3.0 / 64.0;\n");
947 fragment_main
948 << (" vec4 blended = \n"
949 // Lobe pixels
950 " (texture2D(s_texture, v_texcoords[0].xy) +\n"
951 " texture2D(s_texture, v_texcoords[1].zw)) *\n"
952 " LobeWeight +\n"
953 // Center pixels
954 " (texture2D(s_texture, v_texcoords[0].zw) +\n"
955 " texture2D(s_texture, v_texcoords[1].xy)) *\n"
956 " CenterWeight;\n");
957 if (color_transform) {
958 fragment_main << " blended.rgb = DoColorConversion(blended.rgb);\n";
959 }
960 if (swizzle[0] == GL_BGRA_EXT) {
961 fragment_main << " blended.rb = blended.br;\n";
962 }
963 fragment_main << " gl_FragColor = blended;\n";
964 break;
965
966 case Shader::PLANAR_CHANNEL_0:
967 case Shader::PLANAR_CHANNEL_1:
968 case Shader::PLANAR_CHANNEL_2:
969 case Shader::PLANAR_CHANNEL_3: {
970 // Select one color channel, and pack 4 pixels into one output quad. See
971 // header file for diagram.
972 shared_variables << "varying highp vec4 v_texcoords[2];\n";
973 vertex_header << "uniform vec2 scaling_vector;\n";
974 vertex_main
975 << (" vec2 step = scaling_vector / 4.0;\n"
976 " v_texcoords[0].xy = texcoord - step * 1.5;\n"
977 " v_texcoords[0].zw = texcoord - step * 0.5;\n"
978 " v_texcoords[1].xy = texcoord + step * 0.5;\n"
979 " v_texcoords[1].zw = texcoord + step * 1.5;\n");
980 std::basic_string<GLchar> convert_open;
981 std::basic_string<GLchar> convert_close;
982 if (color_transform && shader_ != Shader::PLANAR_CHANNEL_3) {
983 convert_open = "DoColorConversion(";
984 convert_close = ".rgb)";
985 }
986 const char selector = "rgba"[static_cast<int>(shader_) -
987 static_cast<int>(Shader::PLANAR_CHANNEL_0)];
988 fragment_main << " vec4 packed_quad = vec4(\n"
989 << " " << convert_open
990 << "texture2D(s_texture, v_texcoords[0].xy)"
991 << convert_close << '.' << selector << ",\n"
992 << " " << convert_open
993 << "texture2D(s_texture, v_texcoords[0].zw)"
994 << convert_close << '.' << selector << ",\n"
995 << " " << convert_open
996 << "texture2D(s_texture, v_texcoords[1].xy)"
997 << convert_close << '.' << selector << ",\n"
998 << " " << convert_open
999 << "texture2D(s_texture, v_texcoords[1].zw)"
1000 << convert_close << '.' << selector << ");\n";
1001 if (swizzle[0] == GL_BGRA_EXT) {
1002 fragment_main << " packed_quad.rb = packed_quad.br;\n";
1003 }
1004 fragment_main << " gl_FragColor = packed_quad;\n";
1005 break;
1006 }
1007
1008 case Shader::I422_NV61_MRT:
1009 // I422 sampling, delivered via two output textures (NV61 format). See
1010 // header file for diagram.
1011 shared_variables << "varying highp vec4 v_texcoords[2];\n";
1012 vertex_header << "uniform vec2 scaling_vector;\n";
1013 vertex_main
1014 << (" vec2 step = scaling_vector / 4.0;\n"
1015 " v_texcoords[0].xy = texcoord - step * 1.5;\n"
1016 " v_texcoords[0].zw = texcoord - step * 0.5;\n"
1017 " v_texcoords[1].xy = texcoord + step * 0.5;\n"
1018 " v_texcoords[1].zw = texcoord + step * 1.5;\n");
1019 fragment_directives << "#extension GL_EXT_draw_buffers : enable\n";
1020 fragment_main
1021 << (" vec3 pixel0 = texture2D(s_texture, v_texcoords[0].xy).rgb;\n"
1022 " vec3 pixel1 = texture2D(s_texture, v_texcoords[0].zw).rgb;\n"
1023 " vec3 pixel01 = (pixel0 + pixel1) / 2.0;\n"
1024 " vec3 pixel2 = texture2D(s_texture, v_texcoords[1].xy).rgb;\n"
1025 " vec3 pixel3 = texture2D(s_texture, v_texcoords[1].zw).rgb;\n"
1026 " vec3 pixel23 = (pixel2 + pixel3) / 2.0;\n");
1027 if (color_transform) {
1028 fragment_main
1029 << (" pixel0 = DoColorConversion(pixel0);\n"
1030 " pixel1 = DoColorConversion(pixel1);\n"
1031 " pixel01 = DoColorConversion(pixel01);\n"
1032 " pixel2 = DoColorConversion(pixel2);\n"
1033 " pixel3 = DoColorConversion(pixel3);\n"
1034 " pixel23 = DoColorConversion(pixel23);\n");
1035 }
1036 if (swizzle[0] == GL_BGRA_EXT) {
1037 fragment_main
1038 << (" gl_FragData[0] = \n"
1039 " vec4(pixel2.r, pixel1.r, pixel0.r, pixel3.r);\n");
1040 } else {
1041 fragment_main
1042 << (" gl_FragData[0] = \n"
1043 " vec4(pixel0.r, pixel1.r, pixel2.r, pixel3.r);\n");
1044 }
1045 if (swizzle[1] == GL_BGRA_EXT) {
1046 fragment_main
1047 << (" gl_FragData[1] = \n"
1048 " vec4(pixel23.g, pixel01.b, pixel01.g, pixel23.b);\n");
1049 } else {
1050 fragment_main
1051 << (" gl_FragData[1] = \n"
1052 " vec4(pixel01.g, pixel01.b, pixel23.g, pixel23.b);\n");
1053 }
1054 break;
1055
1056 case Shader::DEINTERLEAVE_PAIRWISE_MRT:
1057 // Sample two pixels and unswizzle them. There's no need to do vertical
1058 // scaling with math, since the bilinear interpolation in the sampler
1059 // takes care of that.
1060 shared_variables << "varying highp vec4 v_texcoords;\n";
1061 vertex_header << "uniform vec2 scaling_vector;\n";
1062 vertex_main
1063 << (" vec2 step = scaling_vector / 2.0;\n"
1064 " v_texcoords.xy = texcoord - step * 0.5;\n"
1065 " v_texcoords.zw = texcoord + step * 0.5;\n");
1066 fragment_directives << "#extension GL_EXT_draw_buffers : enable\n";
1067 DCHECK(!color_transform);
1068 fragment_main
1069 << (" vec4 lo_uvuv = texture2D(s_texture, v_texcoords.xy);\n"
1070 " vec4 hi_uvuv = texture2D(s_texture, v_texcoords.zw);\n"
1071 " vec4 uuuu = vec4(lo_uvuv.rb, hi_uvuv.rb);\n"
1072 " vec4 vvvv = vec4(lo_uvuv.ga, hi_uvuv.ga);\n");
1073 if (swizzle[0] == GL_BGRA_EXT) {
1074 fragment_main << " uuuu.rb = uuuu.br;\n";
1075 }
1076 fragment_main << " gl_FragData[0] = uuuu;\n";
1077 if (swizzle[1] == GL_BGRA_EXT) {
1078 fragment_main << " vvvv.rb = vvvv.br;\n";
1079 }
1080 fragment_main << " gl_FragData[1] = vvvv;\n";
1081 break;
1082 }
1083
1084 // Helper function to compile the shader source and log the GLSL compiler's
1085 // results.
1086 const auto CompileShaderFromSource =
1087 [](GLES2Interface* gl, const std::basic_string<GLchar>& source,
1088 GLenum type) -> GLuint {
1089 VLOG(2) << __func__ << ": Compiling shader " << type
1090 << " with source:" << std::endl
1091 << source;
1092 const GLuint shader = gl->CreateShader(type);
1093 const GLchar* source_data = source.data();
1094 const GLint length = base::checked_cast<GLint>(source.size());
1095 gl->ShaderSource(shader, 1, &source_data, &length);
1096 gl->CompileShader(shader);
1097 GLint compile_status = GL_FALSE;
1098 gl->GetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
1099
1100 // Fetch logs and forward them to the system logger. If compilation failed,
1101 // clean-up and return 0 for error.
1102 if (compile_status != GL_TRUE || VLOG_IS_ON(2)) {
1103 GLint log_length = 0;
1104 gl->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
1105 std::string log;
1106 if (log_length > 0) {
1107 std::unique_ptr<GLchar[]> tmp(new GLchar[log_length]);
1108 GLsizei returned_log_length = 0;
1109 gl->GetShaderInfoLog(shader, log_length, &returned_log_length,
1110 tmp.get());
1111 log.assign(tmp.get(), returned_log_length);
1112 }
1113 if (log.empty()) {
1114 log = "<<NO LOG>>";
1115 }
1116 if (compile_status != GL_TRUE) {
1117 LOG(ERROR) << __func__ << ": Compilation of shader " << type
1118 << " failed:" << std::endl
1119 << log;
1120 gl->DeleteShader(shader);
1121 return 0;
1122 }
1123 VLOG(2) << __func__ << ": Compilation of shader " << type
1124 << " succeeded:" << std::endl
1125 << log;
1126 }
1127 return shader;
1128 };
1129
1130 // Compile the vertex shader and attach it to the program.
1131 const std::string shared_variables_str = shared_variables.str();
1132 const GLuint vertex_shader =
1133 CompileShaderFromSource(gl_,
1134 vertex_header.str() + shared_variables_str +
1135 "void main() {\n" + vertex_main.str() + "}\n",
1136 GL_VERTEX_SHADER);
1137 if (vertex_shader == 0) {
1138 return;
1139 }
1140 gl_->AttachShader(program_, vertex_shader);
1141 gl_->DeleteShader(vertex_shader);
1142
1143 // Compile the fragment shader and attach to |program_|.
1144 const GLuint fragment_shader = CompileShaderFromSource(
1145 gl_,
1146 fragment_directives.str() + fragment_header.str() + shared_variables_str +
1147 "void main() {\n" + fragment_main.str() + "}\n",
1148 GL_FRAGMENT_SHADER);
1149 if (fragment_shader == 0) {
1150 return;
1151 }
1152 gl_->AttachShader(program_, fragment_shader);
1153 gl_->DeleteShader(fragment_shader);
1154
1155 // Link the program.
1156 gl_->LinkProgram(program_);
1157 GLint link_status = GL_FALSE;
1158 gl_->GetProgramiv(program_, GL_LINK_STATUS, &link_status);
1159 if (link_status != GL_TRUE) {
1160 LOG(ERROR) << "Failed to link shader program.";
1161 return;
1162 }
1163
1164 #define DCHECK_RESOLVED_LOCATION(member) \
1165 DCHECK(member != -1 || gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR) \
1166 << "Failed to get " #member " in program, or GPU process crashed."
1167
1168 // Resolve the locations of the global variables.
1169 position_location_ = gl_->GetAttribLocation(program_, "a_position");
1170 DCHECK_RESOLVED_LOCATION(position_location_);
1171 texcoord_location_ = gl_->GetAttribLocation(program_, "a_texcoord");
1172 DCHECK_RESOLVED_LOCATION(texcoord_location_);
1173 texture_location_ = gl_->GetUniformLocation(program_, "s_texture");
1174 DCHECK_RESOLVED_LOCATION(texture_location_);
1175 src_rect_location_ = gl_->GetUniformLocation(program_, "src_rect");
1176 DCHECK_RESOLVED_LOCATION(src_rect_location_);
1177 switch (shader_) {
1178 case Shader::BILINEAR:
1179 break;
1180
1181 case Shader::BILINEAR2:
1182 case Shader::BILINEAR3:
1183 case Shader::BILINEAR4:
1184 case Shader::BILINEAR2X2:
1185 case Shader::BICUBIC_HALF_1D:
1186 case Shader::PLANAR_CHANNEL_0:
1187 case Shader::PLANAR_CHANNEL_1:
1188 case Shader::PLANAR_CHANNEL_2:
1189 case Shader::PLANAR_CHANNEL_3:
1190 case Shader::I422_NV61_MRT:
1191 case Shader::DEINTERLEAVE_PAIRWISE_MRT:
1192 scaling_vector_location_ =
1193 gl_->GetUniformLocation(program_, "scaling_vector");
1194 DCHECK_RESOLVED_LOCATION(scaling_vector_location_);
1195 break;
1196
1197 case Shader::BICUBIC_UPSCALE:
1198 src_pixelsize_location_ =
1199 gl_->GetUniformLocation(program_, "src_pixelsize");
1200 DCHECK_RESOLVED_LOCATION(src_pixelsize_location_);
1201 scaling_vector_location_ =
1202 gl_->GetUniformLocation(program_, "scaling_vector");
1203 DCHECK_RESOLVED_LOCATION(scaling_vector_location_);
1204 break;
1205 }
1206
1207 #undef DCHECK_RESOLVED_LOCATION
1208 }
1209
~ShaderProgram()1210 GLScaler::ShaderProgram::~ShaderProgram() {
1211 gl_->DeleteProgram(program_);
1212 }
1213
UseProgram(const gfx::Size & src_texture_size,const gfx::RectF & src_rect,const gfx::Size & dst_size,GLScaler::Axis primary_axis,bool flip_y)1214 void GLScaler::ShaderProgram::UseProgram(const gfx::Size& src_texture_size,
1215 const gfx::RectF& src_rect,
1216 const gfx::Size& dst_size,
1217 GLScaler::Axis primary_axis,
1218 bool flip_y) {
1219 gl_->UseProgram(program_);
1220
1221 // OpenGL defines the last parameter to VertexAttribPointer as type
1222 // "const GLvoid*" even though it is actually an offset into the buffer
1223 // object's data store and not a pointer to the client's address space.
1224 const void* offsets[2] = {nullptr,
1225 reinterpret_cast<const void*>(2 * sizeof(GLfloat))};
1226
1227 gl_->VertexAttribPointer(position_location_, 2, GL_FLOAT, GL_FALSE,
1228 4 * sizeof(GLfloat), offsets[0]);
1229 gl_->EnableVertexAttribArray(position_location_);
1230
1231 gl_->VertexAttribPointer(texcoord_location_, 2, GL_FLOAT, GL_FALSE,
1232 4 * sizeof(GLfloat), offsets[1]);
1233 gl_->EnableVertexAttribArray(texcoord_location_);
1234
1235 // Always sample from the first texture unit.
1236 gl_->Uniform1i(texture_location_, 0);
1237
1238 // Convert |src_rect| from pixel coordinates to texture coordinates. The
1239 // source texture coordinates are in the range [0.0,1.0] for each dimension,
1240 // but the sampling rect may slightly "spill" outside that range (e.g., for
1241 // scaler overscan).
1242 GLfloat src_rect_texcoord[4] = {
1243 src_rect.x() / src_texture_size.width(),
1244 src_rect.y() / src_texture_size.height(),
1245 src_rect.width() / src_texture_size.width(),
1246 src_rect.height() / src_texture_size.height(),
1247 };
1248 if (flip_y) {
1249 src_rect_texcoord[1] += src_rect_texcoord[3];
1250 src_rect_texcoord[3] *= -1.0f;
1251 }
1252 gl_->Uniform4fv(src_rect_location_, 1, src_rect_texcoord);
1253
1254 // Set shader-specific uniform inputs. The |scaling_vector| is the ratio of
1255 // the number of source pixels sampled per dest pixels output. It is used by
1256 // the shader programs to locate distinct texels from the source texture, and
1257 // sample them at the appropriate offset to produce each output texel.
1258 switch (shader_) {
1259 case Shader::BILINEAR:
1260 break;
1261
1262 case Shader::BILINEAR2:
1263 case Shader::BILINEAR3:
1264 case Shader::BILINEAR4:
1265 case Shader::BICUBIC_HALF_1D:
1266 case Shader::PLANAR_CHANNEL_0:
1267 case Shader::PLANAR_CHANNEL_1:
1268 case Shader::PLANAR_CHANNEL_2:
1269 case Shader::PLANAR_CHANNEL_3:
1270 case Shader::I422_NV61_MRT:
1271 case Shader::DEINTERLEAVE_PAIRWISE_MRT:
1272 switch (primary_axis) {
1273 case HORIZONTAL:
1274 gl_->Uniform2f(scaling_vector_location_,
1275 src_rect_texcoord[2] / dst_size.width(), 0.0);
1276 break;
1277 case VERTICAL:
1278 gl_->Uniform2f(scaling_vector_location_, 0.0,
1279 src_rect_texcoord[3] / dst_size.height());
1280 break;
1281 }
1282 break;
1283
1284 case Shader::BILINEAR2X2:
1285 gl_->Uniform2f(scaling_vector_location_,
1286 src_rect_texcoord[2] / dst_size.width(),
1287 src_rect_texcoord[3] / dst_size.height());
1288 break;
1289
1290 case Shader::BICUBIC_UPSCALE:
1291 gl_->Uniform2f(src_pixelsize_location_, src_texture_size.width(),
1292 src_texture_size.height());
1293 // For this shader program, the |scaling_vector| has an alternate meaning:
1294 // It is only being used to select whether bicubic sampling is stepped in
1295 // the X or the Y direction.
1296 gl_->Uniform2f(scaling_vector_location_,
1297 primary_axis == HORIZONTAL ? 1.0 : 0.0,
1298 primary_axis == VERTICAL ? 1.0 : 0.0);
1299 break;
1300 }
1301 }
1302
ScalerStage(gpu::gles2::GLES2Interface * gl,GLScaler::Shader shader,GLScaler::Axis primary_axis,const gfx::Vector2d & scale_from,const gfx::Vector2d & scale_to)1303 GLScaler::ScalerStage::ScalerStage(gpu::gles2::GLES2Interface* gl,
1304 GLScaler::Shader shader,
1305 GLScaler::Axis primary_axis,
1306 const gfx::Vector2d& scale_from,
1307 const gfx::Vector2d& scale_to)
1308 : gl_(gl),
1309 shader_(shader),
1310 primary_axis_(primary_axis),
1311 scale_from_(scale_from),
1312 scale_to_(scale_to) {
1313 DCHECK(gl_);
1314 }
1315
~ScalerStage()1316 GLScaler::ScalerStage::~ScalerStage() {
1317 if (dest_framebuffer_) {
1318 gl_->DeleteFramebuffers(1, &dest_framebuffer_);
1319 }
1320 if (intermediate_texture_) {
1321 gl_->DeleteTextures(1, &intermediate_texture_);
1322 }
1323 }
1324
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)1325 void GLScaler::ScalerStage::ScaleToMultipleOutputs(
1326 GLuint src_texture,
1327 gfx::Size src_texture_size,
1328 const gfx::Vector2d& src_offset,
1329 GLuint dest_texture_0,
1330 GLuint dest_texture_1,
1331 const gfx::Rect& output_rect) {
1332 if (output_rect.IsEmpty())
1333 return; // No work to do.
1334
1335 // Make a recursive call to the "input" ScalerStage to produce an intermediate
1336 // texture for this stage to source from. Adjust src_* variables to use the
1337 // intermediate texture as input.
1338 //
1339 // If there is no input stage, simply modify |src_rect| to account for the
1340 // overall |src_offset| and Y-flip.
1341 gfx::RectF src_rect = ToSourceRect(output_rect);
1342 if (input_stage_) {
1343 const gfx::Rect input_rect = ToInputRect(src_rect);
1344 EnsureIntermediateTextureDefined(input_rect.size());
1345 input_stage_->ScaleToMultipleOutputs(src_texture, src_texture_size,
1346 src_offset, intermediate_texture_, 0,
1347 input_rect);
1348 src_texture = intermediate_texture_;
1349 src_texture_size = intermediate_texture_size_;
1350 DCHECK(!is_flipped_source_);
1351 src_rect -= input_rect.OffsetFromOrigin();
1352 } else {
1353 if (is_flipped_source_) {
1354 src_rect.set_x(src_rect.x() + src_offset.x());
1355 src_rect.set_y(src_texture_size.height() - src_rect.bottom() -
1356 src_offset.y());
1357 } else {
1358 src_rect += src_offset;
1359 }
1360 }
1361
1362 // Attach the output texture(s) to the framebuffer.
1363 if (!dest_framebuffer_) {
1364 gl_->GenFramebuffers(1, &dest_framebuffer_);
1365 }
1366 gl_->BindFramebuffer(GL_FRAMEBUFFER, dest_framebuffer_);
1367 gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
1368 dest_texture_0, 0);
1369 if (dest_texture_1 > 0) {
1370 gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + 1,
1371 GL_TEXTURE_2D, dest_texture_1, 0);
1372 }
1373
1374 // Bind to the source texture and set the texture sampler to use bilinear
1375 // filtering and clamp-to-edge, as required by all shader programs.
1376 //
1377 // It would be better to stash the existing parameter values, and restore them
1378 // back later. However, glGetTexParameteriv() currently requires a blocking
1379 // call to the GPU service, which is extremely costly performance-wise.
1380 gl_->ActiveTexture(GL_TEXTURE0);
1381 gl_->BindTexture(GL_TEXTURE_2D, src_texture);
1382 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1383 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1384 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1385 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1386
1387 // Prepare the shader program for drawing.
1388 DCHECK(program_);
1389 program_->UseProgram(src_texture_size, src_rect, output_rect.size(),
1390 primary_axis_, flip_output_);
1391
1392 // Execute the draw.
1393 gl_->Viewport(0, 0, output_rect.width(), output_rect.height());
1394 const GLenum buffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT0 + 1};
1395 if (dest_texture_1 > 0) {
1396 gl_->DrawBuffersEXT(2, buffers);
1397 }
1398 gl_->DrawArrays(GL_TRIANGLE_STRIP, 0, 4);
1399 if (dest_texture_1 > 0) {
1400 // Set the draw buffers back, to not disrupt external operations.
1401 gl_->DrawBuffersEXT(1, buffers);
1402 }
1403
1404 gl_->BindTexture(GL_TEXTURE_2D, 0);
1405 gl_->BindFramebuffer(GL_FRAMEBUFFER, 0);
1406 }
1407
ToSourceRect(const gfx::Rect & output_rect) const1408 gfx::RectF GLScaler::ScalerStage::ToSourceRect(
1409 const gfx::Rect& output_rect) const {
1410 return gfx::ScaleRect(gfx::RectF(output_rect),
1411 float{scale_from_.x()} / scale_to_.x(),
1412 float{scale_from_.y()} / scale_to_.y());
1413 }
1414
ToInputRect(gfx::RectF source_rect) const1415 gfx::Rect GLScaler::ScalerStage::ToInputRect(gfx::RectF source_rect) const {
1416 int overscan_x = 0;
1417 int overscan_y = 0;
1418 switch (shader_) {
1419 case Shader::BILINEAR:
1420 case Shader::BILINEAR2:
1421 case Shader::BILINEAR3:
1422 case Shader::BILINEAR4: {
1423 // These shaders sample 1 or more points along the primary axis, and only
1424 // 1 point in the other direction, in order to produce each output pixel.
1425 // The amount of overscan is always 0 or 1 pixel along the primary axis,
1426 // and this can be determined by looking at the upper-left-most source
1427 // texture sampling point: If this point is to the left of the middle of
1428 // the upper-left-most source pixel, the texture sampler will also read
1429 // the pixel to the left of that (for linear interpolation). Similar
1430 // behavior can occur towards the right, upwards, and downwards at the
1431 // source boundaries.
1432 int threshold;
1433 switch (shader_) {
1434 default:
1435 threshold = 1;
1436 break;
1437 case Shader::BILINEAR2:
1438 threshold = 2;
1439 break;
1440 case Shader::BILINEAR3:
1441 threshold = 3;
1442 break;
1443 case Shader::BILINEAR4:
1444 threshold = 4;
1445 break;
1446 }
1447 switch (primary_axis_) {
1448 case HORIZONTAL:
1449 if (scale_from_.x() < threshold * scale_to_.x()) {
1450 overscan_x = 1;
1451 }
1452 if (scale_from_.y() < scale_to_.y()) {
1453 overscan_y = 1;
1454 }
1455 break;
1456 case VERTICAL:
1457 if (scale_from_.x() < scale_to_.x()) {
1458 overscan_x = 1;
1459 }
1460 if (scale_from_.y() < threshold * scale_to_.y()) {
1461 overscan_y = 1;
1462 }
1463 break;
1464 }
1465 break;
1466 }
1467
1468 case Shader::BILINEAR2X2:
1469 // This shader samples 2 points along both axes, and the overscan is 0 or
1470 // 1 pixel in both directions (same explanation as for the other BILINEAR
1471 // shaders).
1472 if (scale_from_.x() < 2 * scale_to_.x()) {
1473 overscan_x = 1;
1474 }
1475 if (scale_from_.y() < 2 * scale_to_.y()) {
1476 overscan_y = 1;
1477 }
1478 break;
1479
1480 case Shader::BICUBIC_UPSCALE:
1481 // For each output pixel, this shader always reads 2 pixels about the
1482 // source position in one dimension, and has no overscan in the other
1483 // dimension.
1484 if (scale_from_.x() < scale_to_.x()) {
1485 DCHECK_EQ(HORIZONTAL, primary_axis_);
1486 overscan_x = 2;
1487 } else if (scale_from_.y() < scale_to_.y()) {
1488 DCHECK_EQ(VERTICAL, primary_axis_);
1489 overscan_y = 2;
1490 } else if (scale_from_ == scale_to_) {
1491 // Special case: When not scaling, the math in the shader will resolve
1492 // to just outputting the value for a single source pixel. The shader
1493 // will sample surrounding pixels, but then apply a zero weight to them
1494 // during convolution. Thus, there is effectively no overscan.
1495 NOTREACHED(); // This is a crazy-expensive way to do a 1:1 copy!
1496 } else {
1497 NOTREACHED(); // Downscaling is meaningless.
1498 }
1499 break;
1500
1501 case Shader::BICUBIC_HALF_1D: {
1502 // For each output pixel, this shader always reads 4 pixels about the
1503 // source position in one dimension, and has no overscan in the other
1504 // dimension. However, since the source position always has a distance
1505 // >= 1 inside the "logical" bounds, there can never be more than 3 pixels
1506 // of overscan.
1507 if (scale_from_.x() == 2 * scale_to_.x()) {
1508 DCHECK_EQ(HORIZONTAL, primary_axis_);
1509 overscan_x = 3;
1510 } else if (scale_from_.y() == 2 * scale_to_.y()) {
1511 DCHECK_EQ(VERTICAL, primary_axis_);
1512 overscan_y = 3;
1513 } else {
1514 // Anything but a half-downscale in one dimension is meaningless.
1515 NOTREACHED();
1516 }
1517 break;
1518 }
1519
1520 case Shader::PLANAR_CHANNEL_0:
1521 case Shader::PLANAR_CHANNEL_1:
1522 case Shader::PLANAR_CHANNEL_2:
1523 case Shader::PLANAR_CHANNEL_3:
1524 case Shader::I422_NV61_MRT:
1525 // All of these sample 4x1 source pixels to produce each output "pixel."
1526 // There is no overscan. They can also be combined with a bilinear
1527 // downscale, but not an upscale.
1528 DCHECK_GE(scale_from_.x(), 4 * scale_to_.x());
1529 DCHECK_EQ(HORIZONTAL, primary_axis_);
1530 break;
1531
1532 case Shader::DEINTERLEAVE_PAIRWISE_MRT:
1533 // This shader samples 2x1 source pixels to produce each output "pixel."
1534 // There is no overscan. It can also be combined with a bilinear
1535 // downscale, but not an upscale.
1536 DCHECK_GE(scale_from_.x(), 2 * scale_to_.x());
1537 DCHECK_EQ(HORIZONTAL, primary_axis_);
1538 break;
1539 }
1540
1541 source_rect.Inset(-overscan_x, -overscan_y);
1542 return gfx::ToEnclosingRect(source_rect);
1543 }
1544
EnsureIntermediateTextureDefined(const gfx::Size & size)1545 void GLScaler::ScalerStage::EnsureIntermediateTextureDefined(
1546 const gfx::Size& size) {
1547 // Reallocate a new texture, if needed.
1548 if (!intermediate_texture_) {
1549 gl_->GenTextures(1, &intermediate_texture_);
1550 }
1551 if (intermediate_texture_size_ != size) {
1552 gl_->BindTexture(GL_TEXTURE_2D, intermediate_texture_);
1553 // Note: Not setting the filter or wrap parameters on the texture here
1554 // because that will be done in ScaleToMultipleOutputs() anyway.
1555 gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0,
1556 GL_RGBA, program_->texture_type(), nullptr);
1557 intermediate_texture_size_ = size;
1558 }
1559 }
1560
operator <<(std::ostream & out,const GLScaler & scaler)1561 std::ostream& operator<<(std::ostream& out, const GLScaler& scaler) {
1562 if (!scaler.chain_) {
1563 return (out << "[GLScaler NOT configured]");
1564 }
1565
1566 out << "Output";
1567 const GLScaler::ScalerStage* const final_stage = scaler.chain_.get();
1568 for (auto* stage = final_stage; stage; stage = stage->input_stage()) {
1569 out << u8" ← {" << GLScaler::GetShaderName(stage->shader());
1570 if (stage->shader_program()) {
1571 switch (stage->shader_program()->texture_type()) {
1572 case GL_FLOAT:
1573 out << "/highp";
1574 break;
1575 case GL_HALF_FLOAT_OES:
1576 out << "/mediump";
1577 break;
1578 default:
1579 out << "/lowp";
1580 break;
1581 }
1582 }
1583 if (stage->flip_output()) {
1584 out << "+flip_y";
1585 }
1586 if (stage->scale_from() == stage->scale_to()) {
1587 out << " copy";
1588 } else {
1589 out << ' ' << stage->scale_from().ToString() << " to "
1590 << stage->scale_to().ToString();
1591 }
1592 if (!stage->input_stage() &&
1593 scaler.params_.source_color_space != scaler.scaling_color_space_) {
1594 out << ", with color x-form "
1595 << scaler.params_.source_color_space.ToString() << " to "
1596 << scaler.scaling_color_space_.ToString();
1597 }
1598 if (stage == final_stage) {
1599 if (scaler.params_.output_color_space != scaler.scaling_color_space_) {
1600 out << ", with color x-form to "
1601 << scaler.params_.output_color_space.ToString();
1602 }
1603 for (int i = 0; i < 2; ++i) {
1604 if (scaler.params_.swizzle[i] != GL_RGBA) {
1605 out << ", with swizzle(" << i << ')';
1606 }
1607 }
1608 }
1609 out << '}';
1610 }
1611 out << u8" ← Source";
1612 return out;
1613 }
1614
1615 } // namespace viz
1616