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_i420_converter.h"
6 
7 #include <utility>
8 
9 #include "components/viz/common/gpu/context_provider.h"
10 
11 namespace viz {
12 
GLI420Converter(ContextProvider * context_provider)13 GLI420Converter::GLI420Converter(ContextProvider* context_provider)
14     : GLI420Converter(context_provider, true) {
15   DCHECK(context_provider_);
16 }
17 
GLI420Converter(ContextProvider * context_provider,bool allow_mrt_path)18 GLI420Converter::GLI420Converter(ContextProvider* context_provider,
19                                  bool allow_mrt_path)
20     : context_provider_(context_provider),
21       step1_(context_provider_),
22       step2_(context_provider_) {
23   DCHECK(context_provider_);
24   context_provider_->AddObserver(this);
25   if (!allow_mrt_path || step1_.GetMaxDrawBuffersSupported() < 2) {
26     step3_ = std::make_unique<GLScaler>(context_provider_);
27     step4_ = std::make_unique<GLScaler>(context_provider_);
28   }
29 }
30 
~GLI420Converter()31 GLI420Converter::~GLI420Converter() {
32   OnContextLost();  // Free context-related resources.
33 }
34 
Configure(const Parameters & params)35 bool GLI420Converter::Configure(const Parameters& params) {
36   Parameters step1_params = params;
37   if (!step1_params.output_color_space.IsValid()) {
38     step1_params.output_color_space = gfx::ColorSpace::CreateREC709();
39   }
40 
41   // Configure the "step 1" scaler.
42   if (is_using_mrt_path()) {
43     step1_params.export_format = Parameters::ExportFormat::NV61;
44     DCHECK_EQ(step1_params.swizzle[0], params.swizzle[0]);
45     step1_params.swizzle[1] = GL_RGBA;  // Don't swizzle 2nd rendering target.
46   } else {
47     step1_params.export_format = Parameters::ExportFormat::INTERLEAVED_QUADS;
48     step1_params.swizzle[0] = GL_RGBA;  // Will swizzle in steps 2-4.
49   }
50   if (!step1_.Configure(step1_params)) {
51     return false;
52   }
53 
54   // Configure the "step 2" scaler (and steps 3 and 4 for the non-MRT path) that
55   // further transform the output from the "step 1" scaler to produce the final
56   // outputs.
57   Parameters step2_params;
58   step2_params.scale_to = gfx::Vector2d(1, 1);
59   step2_params.source_color_space = step1_params.output_color_space;
60   step2_params.output_color_space = step1_params.output_color_space;
61   // Use FAST quality, a single bilinear pass, because there will either be no
62   // scaling or exactly 50% scaling.
63   step2_params.quality = Parameters::Quality::FAST;
64   step2_params.swizzle[0] = params.swizzle[0];
65   if (is_using_mrt_path()) {
66     // NV61 provides half-width and full-height U/V. I420 U/V planes are
67     // half-width and half-height. So, scale Y by 50%.
68     step2_params.scale_from = gfx::Vector2d(1, 2);
69     step2_params.export_format =
70         Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE;
71     step2_params.swizzle[1] = step2_params.swizzle[0];
72     if (!step2_.Configure(step2_params)) {
73       return false;
74     }
75   } else {
76     // Extract a full-size Y plane from the interleaved YUVA from step 1.
77     step2_params.scale_from = gfx::Vector2d(1, 1);
78     step2_params.export_format = Parameters::ExportFormat::CHANNEL_0;
79     if (!step2_.Configure(step2_params)) {
80       return false;
81     }
82     // Extract half-size U/V planes from the interleaved YUVA from step 1.
83     step2_params.scale_from = gfx::Vector2d(2, 2);
84     step2_params.export_format = Parameters::ExportFormat::CHANNEL_1;
85     if (!step3_->Configure(step2_params)) {
86       return false;
87     }
88     step2_params.export_format = Parameters::ExportFormat::CHANNEL_2;
89     if (!step4_->Configure(step2_params)) {
90       return false;
91     }
92   }
93 
94   params_ = params;
95   return true;
96 }
97 
Convert(GLuint src_texture,const gfx::Size & src_texture_size,const gfx::Vector2d & src_offset,const gfx::Rect & output_rect,const GLuint yuv_textures[3])98 bool GLI420Converter::Convert(GLuint src_texture,
99                               const gfx::Size& src_texture_size,
100                               const gfx::Vector2d& src_offset,
101                               const gfx::Rect& output_rect,
102                               const GLuint yuv_textures[3]) {
103   DCHECK_EQ(output_rect.x() % 8, 0);
104   DCHECK_EQ(output_rect.width() % 8, 0);
105   DCHECK_EQ(output_rect.y() % 2, 0);
106   DCHECK_EQ(output_rect.height() % 2, 0);
107 
108   if (!context_provider_) {
109     return false;
110   }
111 
112   if (is_using_mrt_path()) {
113     const gfx::Rect luma_output_rect(output_rect.x() / 4, output_rect.y(),
114                                      output_rect.width() / 4,
115                                      output_rect.height());
116     EnsureIntermediateTextureDefined(luma_output_rect.size());
117     const gfx::Rect chroma_output_rect(
118         gfx::Size(luma_output_rect.width() / 2, luma_output_rect.height() / 2));
119     return (step1_.ScaleToMultipleOutputs(
120                 src_texture, src_texture_size, src_offset, yuv_textures[0],
121                 intermediate_texture_, luma_output_rect) &&
122             step2_.ScaleToMultipleOutputs(intermediate_texture_,
123                                           intermediate_texture_size_,
124                                           gfx::Vector2d(), yuv_textures[1],
125                                           yuv_textures[2], chroma_output_rect));
126   }
127 
128   // Non-MRT path:
129   EnsureIntermediateTextureDefined(output_rect.size());
130   const gfx::Rect luma_output_rect(0, 0, output_rect.width() / 4,
131                                    output_rect.height());
132   const gfx::Rect chroma_output_rect(0, 0, luma_output_rect.width() / 2,
133                                      luma_output_rect.height() / 2);
134   return (step1_.Scale(src_texture, src_texture_size, src_offset,
135                        intermediate_texture_, output_rect) &&
136           step2_.Scale(intermediate_texture_, intermediate_texture_size_,
137                        gfx::Vector2d(), yuv_textures[0], luma_output_rect) &&
138           step3_->Scale(intermediate_texture_, intermediate_texture_size_,
139                         gfx::Vector2d(), yuv_textures[1], chroma_output_rect) &&
140           step4_->Scale(intermediate_texture_, intermediate_texture_size_,
141                         gfx::Vector2d(), yuv_textures[2], chroma_output_rect));
142 }
143 
144 // static
ToAlignedRect(const gfx::Rect & rect)145 gfx::Rect GLI420Converter::ToAlignedRect(const gfx::Rect& rect) {
146   // Origin coordinates: FLOOR(...)
147   const int aligned_x =
148       ((rect.x() < 0) ? ((rect.x() - 7) / 8) : (rect.x() / 8)) * 8;
149   const int aligned_y =
150       ((rect.y() < 0) ? ((rect.y() - 1) / 2) : (rect.y() / 2)) * 2;
151   // Span coordinates: CEIL(...)
152   const int aligned_right =
153       ((rect.right() < 0) ? (rect.right() / 8) : ((rect.right() + 7) / 8)) * 8;
154   const int aligned_bottom =
155       ((rect.bottom() < 0) ? (rect.bottom() / 2) : ((rect.bottom() + 1) / 2)) *
156       2;
157   return gfx::Rect(aligned_x, aligned_y, aligned_right - aligned_x,
158                    aligned_bottom - aligned_y);
159 }
160 
161 // static
ParametersAreEquivalent(const Parameters & a,const Parameters & b)162 bool GLI420Converter::ParametersAreEquivalent(const Parameters& a,
163                                               const Parameters& b) {
164   const auto Resolve = [](Parameters params) {
165     // Per header comments, if an invalid output_color_space is specified, use
166     // REC709.
167     if (!params.output_color_space.IsValid()) {
168       params.output_color_space = gfx::ColorSpace::CreateREC709();
169     }
170     // Both of these fields are overwritten, in Configure(), whether the MRT
171     // path is going to be used or not. So, for the purposes of "equivalence,"
172     // just set these like the MRT path would.
173     params.export_format = Parameters::ExportFormat::NV61;
174     params.swizzle[1] = GL_RGBA;
175     return params;
176   };
177   return GLScaler::ParametersAreEquivalent(Resolve(a), Resolve(b));
178 }
179 
EnsureIntermediateTextureDefined(const gfx::Size & required)180 void GLI420Converter::EnsureIntermediateTextureDefined(
181     const gfx::Size& required) {
182   if (intermediate_texture_size_ == required) {
183     return;
184   }
185   auto* const gl = context_provider_->ContextGL();
186   if (intermediate_texture_ == 0) {
187     gl->GenTextures(1, &intermediate_texture_);
188   }
189   gl->BindTexture(GL_TEXTURE_2D, intermediate_texture_);
190   gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, required.width(), required.height(),
191                  0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
192   intermediate_texture_size_ = required;
193 }
194 
OnContextLost()195 void GLI420Converter::OnContextLost() {
196   if (intermediate_texture_ != 0) {
197     if (auto* gl = context_provider_->ContextGL()) {
198       gl->DeleteTextures(1, &intermediate_texture_);
199     }
200     intermediate_texture_ = 0;
201     intermediate_texture_size_ = gfx::Size();
202   }
203   if (context_provider_) {
204     context_provider_->RemoveObserver(this);
205     context_provider_ = nullptr;
206   }
207 }
208 
209 }  // namespace viz
210