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