1// Copyright (c) 2013 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 "content/browser/renderer_host/compositing_iosurface_transformer_mac.h"
6
7#include <algorithm>
8
9#include "base/basictypes.h"
10#include "base/debug/trace_event.h"
11#include "base/logging.h"
12#include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
13#include "ui/gfx/rect.h"
14#include "ui/gfx/size.h"
15
16namespace content {
17
18namespace {
19
20const GLenum kColorAttachments[] = {
21  GL_COLOR_ATTACHMENT0_EXT,
22  GL_COLOR_ATTACHMENT1_EXT
23};
24
25// Set viewport and model/projection matrices for drawing to a framebuffer of
26// size dst_size, with coordinates starting at (0, 0).
27void SetTransformationsForOffScreenRendering(const gfx::Size& dst_size) {
28  glViewport(0, 0, dst_size.width(), dst_size.height());
29  glMatrixMode(GL_PROJECTION);
30  glLoadIdentity();
31  glOrtho(0, dst_size.width(), 0, dst_size.height(), -1, 1);
32  glMatrixMode(GL_MODELVIEW);
33  glLoadIdentity();
34}
35
36// Configure texture sampling parameters.
37void SetTextureParameters(GLenum target, GLint min_mag_filter, GLint wrap) {
38  glTexParameteri(target, GL_TEXTURE_MIN_FILTER, min_mag_filter);
39  glTexParameteri(target, GL_TEXTURE_MAG_FILTER, min_mag_filter);
40  glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap);
41  glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap);
42}
43
44// Draw the currently-bound texture.  The src region is applied to the entire
45// destination framebuffer of the given size.  Specify |flip_y| is the src
46// texture is upside-down relative to the destination.
47//
48// Assumption: The orthographic projection is set up as
49// (0,0)x(dst_width,dst_height).
50void DrawQuad(float src_x, float src_y, float src_width, float src_height,
51              bool flip_y, float dst_width, float dst_height) {
52  glEnableClientState(GL_VERTEX_ARRAY);
53  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
54
55  float vertices[4][2] = {
56    { 0.0f, dst_height },
57    { 0.0f, 0.0f },
58    { dst_width, 0.0f },
59    { dst_width, dst_height }
60  };
61  glVertexPointer(arraysize(vertices[0]), GL_FLOAT, sizeof(vertices[0]),
62                  vertices);
63
64  float tex_coords[4][2] = {
65    { src_x, src_y + src_height },
66    { src_x, src_y },
67    { src_x + src_width, src_y },
68    { src_x + src_width, src_y + src_height }
69  };
70  if (flip_y) {
71    std::swap(tex_coords[0][1], tex_coords[1][1]);
72    std::swap(tex_coords[2][1], tex_coords[3][1]);
73  }
74  glTexCoordPointer(arraysize(tex_coords[0]), GL_FLOAT, sizeof(tex_coords[0]),
75                    tex_coords);
76
77  COMPILE_ASSERT(arraysize(vertices) == arraysize(tex_coords),
78                 same_number_of_points_in_both);
79  glDrawArrays(GL_QUADS, 0, arraysize(vertices));
80
81  glDisableClientState(GL_VERTEX_ARRAY);
82  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
83}
84
85}  // namespace
86
87CompositingIOSurfaceTransformer::CompositingIOSurfaceTransformer(
88    GLenum texture_target, bool src_texture_needs_y_flip,
89    CompositingIOSurfaceShaderPrograms* shader_program_cache)
90    : texture_target_(texture_target),
91      src_texture_needs_y_flip_(src_texture_needs_y_flip),
92      shader_program_cache_(shader_program_cache),
93      frame_buffer_(0) {
94  DCHECK(texture_target_ == GL_TEXTURE_RECTANGLE_ARB)
95      << "Fragment shaders currently only support RECTANGLE textures.";
96  DCHECK(shader_program_cache_);
97
98  memset(textures_, 0, sizeof(textures_));
99
100  // The RGB-to-YV12 transform requires that the driver/hardware supports
101  // multiple draw buffers.
102  GLint max_draw_buffers = 1;
103  glGetIntegerv(GL_MAX_DRAW_BUFFERS, &max_draw_buffers);
104  system_supports_multiple_draw_buffers_ = (max_draw_buffers >= 2);
105}
106
107CompositingIOSurfaceTransformer::~CompositingIOSurfaceTransformer() {
108  for (int i = 0; i < NUM_CACHED_TEXTURES; ++i)
109    DCHECK_EQ(textures_[i], 0u) << "Failed to call ReleaseCachedGLObjects().";
110  DCHECK_EQ(frame_buffer_, 0u) << "Failed to call ReleaseCachedGLObjects().";
111}
112
113void CompositingIOSurfaceTransformer::ReleaseCachedGLObjects() {
114  for (int i = 0; i < NUM_CACHED_TEXTURES; ++i) {
115    if (textures_[i]) {
116      glDeleteTextures(1, &textures_[i]);
117      textures_[i] = 0;
118      texture_sizes_[i] = gfx::Size();
119    }
120  }
121  if (frame_buffer_) {
122    glDeleteFramebuffersEXT(1, &frame_buffer_);
123    frame_buffer_ = 0;
124  }
125}
126
127bool CompositingIOSurfaceTransformer::ResizeBilinear(
128    GLuint src_texture, const gfx::Rect& src_subrect, const gfx::Size& dst_size,
129    GLuint* texture) {
130  if (src_subrect.IsEmpty() || dst_size.IsEmpty())
131    return false;
132
133  glActiveTexture(GL_TEXTURE0);
134  glDisable(GL_DEPTH_TEST);
135  glDisable(GL_BLEND);
136
137  PrepareTexture(RGBA_OUTPUT, dst_size);
138  PrepareFramebuffer();
139  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame_buffer_);
140  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
141                            texture_target_, textures_[RGBA_OUTPUT], 0);
142  DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
143             GL_FRAMEBUFFER_COMPLETE_EXT);
144
145  glBindTexture(texture_target_, src_texture);
146  SetTextureParameters(
147      texture_target_, src_subrect.size() == dst_size ? GL_NEAREST : GL_LINEAR,
148      GL_CLAMP_TO_EDGE);
149
150  const bool prepared = shader_program_cache_->UseBlitProgram();
151  DCHECK(prepared);
152  SetTransformationsForOffScreenRendering(dst_size);
153  DrawQuad(src_subrect.x(), src_subrect.y(),
154           src_subrect.width(), src_subrect.height(),
155           src_texture_needs_y_flip_,
156           dst_size.width(), dst_size.height());
157  glUseProgram(0);
158
159  glBindTexture(texture_target_, 0);
160  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
161
162  *texture = textures_[RGBA_OUTPUT];
163  return true;
164}
165
166bool CompositingIOSurfaceTransformer::TransformRGBToYV12(
167    GLuint src_texture,
168    const gfx::Rect& src_subrect,
169    const gfx::Size& dst_size,
170    GLuint* texture_y,
171    GLuint* texture_u,
172    GLuint* texture_v,
173    gfx::Size* packed_y_size,
174    gfx::Size* packed_uv_size) {
175  if (!system_supports_multiple_draw_buffers_)
176    return false;
177  if (src_subrect.IsEmpty() || dst_size.IsEmpty())
178    return false;
179
180  TRACE_EVENT0("gpu", "TransformRGBToYV12");
181
182  glActiveTexture(GL_TEXTURE0);
183  glDisable(GL_DEPTH_TEST);
184  glDisable(GL_BLEND);
185
186  // Resize output textures for each plane, and for the intermediate UUVV one
187  // that becomes an input into pass #2.  |packed_y_size| is the size of the Y
188  // output texture, where its width is 1/4 the number of Y pixels because 4 Y
189  // pixels are packed into a single quad.  |packed_uv_size| is half the size of
190  // Y in both dimensions, rounded up.
191  *packed_y_size = gfx::Size((dst_size.width() + 3) / 4, dst_size.height());
192  *packed_uv_size = gfx::Size((packed_y_size->width() + 1) / 2,
193                              (packed_y_size->height() + 1) / 2);
194  PrepareTexture(Y_PLANE_OUTPUT, *packed_y_size);
195  PrepareTexture(UUVV_INTERMEDIATE, *packed_y_size);
196  PrepareTexture(U_PLANE_OUTPUT, *packed_uv_size);
197  PrepareTexture(V_PLANE_OUTPUT, *packed_uv_size);
198
199  /////////////////////////////////////////
200  // Pass 1: RGB --(scaled)--> YYYY + UUVV
201  PrepareFramebuffer();
202  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame_buffer_);
203  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
204                            texture_target_, textures_[Y_PLANE_OUTPUT], 0);
205  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
206                            texture_target_, textures_[UUVV_INTERMEDIATE], 0);
207  DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
208             GL_FRAMEBUFFER_COMPLETE_EXT);
209  glDrawBuffers(2, kColorAttachments);
210
211  // Read from |src_texture|.  Enable bilinear filtering only if scaling is
212  // required.  The filtering will take place entirely in the first pass.
213  glBindTexture(texture_target_, src_texture);
214  SetTextureParameters(
215      texture_target_, src_subrect.size() == dst_size ? GL_NEAREST : GL_LINEAR,
216      GL_CLAMP_TO_EDGE);
217
218  // Use the first-pass shader program and draw the scene.
219  const bool prepared_pass_1 = shader_program_cache_->UseRGBToYV12Program(
220      1,
221      static_cast<float>(src_subrect.width()) / dst_size.width());
222  DCHECK(prepared_pass_1);
223  SetTransformationsForOffScreenRendering(*packed_y_size);
224  DrawQuad(src_subrect.x(), src_subrect.y(),
225           ((packed_y_size->width() * 4.0f) / dst_size.width()) *
226               src_subrect.width(),
227           src_subrect.height(),
228           src_texture_needs_y_flip_,
229           packed_y_size->width(), packed_y_size->height());
230
231  /////////////////////////////////////////
232  // Pass 2: UUVV -> UUUU + VVVV
233  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
234                            texture_target_, textures_[U_PLANE_OUTPUT], 0);
235  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
236                            texture_target_, textures_[V_PLANE_OUTPUT], 0);
237  DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
238             GL_FRAMEBUFFER_COMPLETE_EXT);
239
240  // Read from the intermediate UUVV texture.  The second pass uses bilinear
241  // minification to achieve vertical scaling, so enable it always.
242  glBindTexture(texture_target_, textures_[UUVV_INTERMEDIATE]);
243  SetTextureParameters(texture_target_, GL_LINEAR, GL_CLAMP_TO_EDGE);
244
245  // Use the second-pass shader program and draw the scene.
246  const bool prepared_pass_2 =
247      shader_program_cache_->UseRGBToYV12Program(2, 1.0f);
248  DCHECK(prepared_pass_2);
249  SetTransformationsForOffScreenRendering(*packed_uv_size);
250  DrawQuad(0.0f, 0.0f,
251           packed_uv_size->width() * 2.0f,
252           packed_uv_size->height() * 2.0f,
253           false,
254           packed_uv_size->width(), packed_uv_size->height());
255  glUseProgram(0);
256
257  // Before leaving, put back to drawing to a single rendering output.
258  glDrawBuffers(1, kColorAttachments);
259
260  glBindTexture(texture_target_, 0);
261  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
262
263  *texture_y = textures_[Y_PLANE_OUTPUT];
264  *texture_u = textures_[U_PLANE_OUTPUT];
265  *texture_v = textures_[V_PLANE_OUTPUT];
266  return true;
267}
268
269void CompositingIOSurfaceTransformer::PrepareTexture(
270    CachedTexture which, const gfx::Size& size) {
271  DCHECK_GE(which, 0);
272  DCHECK_LT(which, NUM_CACHED_TEXTURES);
273  DCHECK(!size.IsEmpty());
274
275  if (!textures_[which]) {
276    glGenTextures(1, &textures_[which]);
277    DCHECK_NE(textures_[which], 0u);
278    texture_sizes_[which] = gfx::Size();
279  }
280
281  // Re-allocate the texture if its size has changed since last use.
282  if (texture_sizes_[which] != size) {
283    TRACE_EVENT2("gpu", "Resize Texture",
284                 "which", which,
285                 "new_size", size.ToString());
286    glBindTexture(texture_target_, textures_[which]);
287    glTexImage2D(texture_target_, 0, GL_RGBA, size.width(), size.height(), 0,
288                 GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
289    texture_sizes_[which] = size;
290  }
291}
292
293void CompositingIOSurfaceTransformer::PrepareFramebuffer() {
294  if (!frame_buffer_) {
295    glGenFramebuffersEXT(1, &frame_buffer_);
296    DCHECK_NE(frame_buffer_, 0u);
297  }
298}
299
300}  // namespace content
301