| Index: content/browser/renderer_host/compositing_iosurface_transformer_mac.cc
|
| diff --git a/content/browser/renderer_host/compositing_iosurface_transformer_mac.cc b/content/browser/renderer_host/compositing_iosurface_transformer_mac.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2e0401ff476afacb2556bd5ba7571ae730469857
|
| --- /dev/null
|
| +++ b/content/browser/renderer_host/compositing_iosurface_transformer_mac.cc
|
| @@ -0,0 +1,300 @@
|
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "content/browser/renderer_host/compositing_iosurface_transformer_mac.h"
|
| +
|
| +#include <algorithm>
|
| +
|
| +#include "base/basictypes.h"
|
| +#include "base/debug/trace_event.h"
|
| +#include "base/logging.h"
|
| +#include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
|
| +#include "ui/gfx/rect.h"
|
| +#include "ui/gfx/size.h"
|
| +
|
| +namespace content {
|
| +
|
| +namespace {
|
| +
|
| +const GLenum kColorAttachments[] = {
|
| + GL_COLOR_ATTACHMENT0_EXT,
|
| + GL_COLOR_ATTACHMENT1_EXT
|
| +};
|
| +
|
| +// Set viewport and model/projection matrices for drawing to a framebuffer of
|
| +// size dst_size, with coordinates starting at (0, 0).
|
| +void SetTransformationsForOffScreenRendering(const gfx::Size& dst_size) {
|
| + glViewport(0, 0, dst_size.width(), dst_size.height());
|
| + glMatrixMode(GL_PROJECTION);
|
| + glLoadIdentity();
|
| + glOrtho(0, dst_size.width(), 0, dst_size.height(), -1, 1);
|
| + glMatrixMode(GL_MODELVIEW);
|
| + glLoadIdentity();
|
| +}
|
| +
|
| +// Configure texture sampling parameters.
|
| +void SetTextureParameters(GLenum target, GLint min_mag_filter, GLint wrap) {
|
| + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, min_mag_filter);
|
| + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, min_mag_filter);
|
| + glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap);
|
| + glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap);
|
| +}
|
| +
|
| +// Draw the currently-bound texture. The src region is applied to the entire
|
| +// destination framebuffer of the given size. Specify |flip_y| is the src
|
| +// texture is upside-down relative to the destination.
|
| +//
|
| +// Assumption: The orthographic projection is set up as
|
| +// (0,0)x(dst_width,dst_height).
|
| +void DrawQuad(float src_x, float src_y, float src_width, float src_height,
|
| + bool flip_y, float dst_width, float dst_height) {
|
| + glEnableClientState(GL_VERTEX_ARRAY);
|
| + glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
| +
|
| + float vertices[4][2] = {
|
| + { 0.0f, dst_height },
|
| + { 0.0f, 0.0f },
|
| + { dst_width, 0.0f },
|
| + { dst_width, dst_height }
|
| + };
|
| + glVertexPointer(arraysize(vertices[0]), GL_FLOAT, sizeof(vertices[0]),
|
| + vertices);
|
| +
|
| + float tex_coords[4][2] = {
|
| + { src_x, src_y + src_height },
|
| + { src_x, src_y },
|
| + { src_x + src_width, src_y },
|
| + { src_x + src_width, src_y + src_height }
|
| + };
|
| + if (flip_y) {
|
| + std::swap(tex_coords[0][1], tex_coords[1][1]);
|
| + std::swap(tex_coords[2][1], tex_coords[3][1]);
|
| + }
|
| + glTexCoordPointer(arraysize(tex_coords[0]), GL_FLOAT, sizeof(tex_coords[0]),
|
| + tex_coords);
|
| +
|
| + COMPILE_ASSERT(arraysize(vertices) == arraysize(tex_coords),
|
| + same_number_of_points_in_both);
|
| + glDrawArrays(GL_QUADS, 0, arraysize(vertices));
|
| +
|
| + glDisableClientState(GL_VERTEX_ARRAY);
|
| + glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +CompositingIOSurfaceTransformer::CompositingIOSurfaceTransformer(
|
| + GLenum texture_target, bool src_texture_needs_y_flip,
|
| + CompositingIOSurfaceShaderPrograms* shader_program_cache)
|
| + : texture_target_(texture_target),
|
| + src_texture_needs_y_flip_(src_texture_needs_y_flip),
|
| + shader_program_cache_(shader_program_cache),
|
| + frame_buffer_(0) {
|
| + DCHECK(texture_target_ == GL_TEXTURE_RECTANGLE_ARB)
|
| + << "Fragment shaders currently only support RECTANGLE textures.";
|
| + DCHECK(shader_program_cache_);
|
| +
|
| + memset(textures_, 0, sizeof(textures_));
|
| +
|
| + // The RGB-to-YV12 transform requires that the driver/hardware supports
|
| + // multiple draw buffers.
|
| + GLint max_draw_buffers = 1;
|
| + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &max_draw_buffers);
|
| + system_supports_multiple_draw_buffers_ = (max_draw_buffers >= 2);
|
| +}
|
| +
|
| +CompositingIOSurfaceTransformer::~CompositingIOSurfaceTransformer() {
|
| + for (int i = 0; i < NUM_CACHED_TEXTURES; ++i)
|
| + DCHECK_EQ(textures_[i], 0u) << "Failed to call ReleaseCachedGLObjects().";
|
| + DCHECK_EQ(frame_buffer_, 0u) << "Failed to call ReleaseCachedGLObjects().";
|
| +}
|
| +
|
| +void CompositingIOSurfaceTransformer::ReleaseCachedGLObjects() {
|
| + for (int i = 0; i < NUM_CACHED_TEXTURES; ++i) {
|
| + if (textures_[i]) {
|
| + glDeleteTextures(1, &textures_[i]);
|
| + textures_[i] = 0;
|
| + texture_sizes_[i] = gfx::Size();
|
| + }
|
| + }
|
| + if (frame_buffer_) {
|
| + glDeleteFramebuffersEXT(1, &frame_buffer_);
|
| + frame_buffer_ = 0;
|
| + }
|
| +}
|
| +
|
| +bool CompositingIOSurfaceTransformer::ResizeBilinear(
|
| + GLuint src_texture, const gfx::Rect& src_subrect, const gfx::Size& dst_size,
|
| + GLuint* texture) {
|
| + if (src_subrect.IsEmpty() || dst_size.IsEmpty())
|
| + return false;
|
| +
|
| + glActiveTexture(GL_TEXTURE0);
|
| + glDisable(GL_DEPTH_TEST);
|
| + glDisable(GL_BLEND);
|
| +
|
| + PrepareTexture(RGBA_OUTPUT, dst_size);
|
| + PrepareFramebuffer();
|
| + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame_buffer_);
|
| + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
|
| + texture_target_, textures_[RGBA_OUTPUT], 0);
|
| + DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
|
| + GL_FRAMEBUFFER_COMPLETE_EXT);
|
| +
|
| + glBindTexture(texture_target_, src_texture);
|
| + SetTextureParameters(
|
| + texture_target_, src_subrect.size() == dst_size ? GL_NEAREST : GL_LINEAR,
|
| + GL_CLAMP_TO_EDGE);
|
| +
|
| + const bool prepared = shader_program_cache_->UseBlitProgram();
|
| + DCHECK(prepared);
|
| + SetTransformationsForOffScreenRendering(dst_size);
|
| + DrawQuad(src_subrect.x(), src_subrect.y(),
|
| + src_subrect.width(), src_subrect.height(),
|
| + src_texture_needs_y_flip_,
|
| + dst_size.width(), dst_size.height());
|
| + glUseProgram(0);
|
| +
|
| + glBindTexture(texture_target_, 0);
|
| + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
|
| +
|
| + *texture = textures_[RGBA_OUTPUT];
|
| + return true;
|
| +}
|
| +
|
| +bool CompositingIOSurfaceTransformer::TransformRGBToYV12(
|
| + GLuint src_texture,
|
| + const gfx::Rect& src_subrect,
|
| + const gfx::Size& dst_size,
|
| + GLuint* texture_y,
|
| + GLuint* texture_u,
|
| + GLuint* texture_v,
|
| + gfx::Size* packed_y_size,
|
| + gfx::Size* packed_uv_size) {
|
| + if (!system_supports_multiple_draw_buffers_)
|
| + return false;
|
| + if (src_subrect.IsEmpty() || dst_size.IsEmpty())
|
| + return false;
|
| +
|
| + TRACE_EVENT0("gpu", "TransformRGBToYV12");
|
| +
|
| + glActiveTexture(GL_TEXTURE0);
|
| + glDisable(GL_DEPTH_TEST);
|
| + glDisable(GL_BLEND);
|
| +
|
| + // Resize output textures for each plane, and for the intermediate UUVV one
|
| + // that becomes an input into pass #2. |packed_y_size| is the size of the Y
|
| + // output texture, where its width is 1/4 the number of Y pixels because 4 Y
|
| + // pixels are packed into a single quad. |packed_uv_size| is half the size of
|
| + // Y in both dimensions, rounded up.
|
| + *packed_y_size = gfx::Size((dst_size.width() + 3) / 4, dst_size.height());
|
| + *packed_uv_size = gfx::Size((packed_y_size->width() + 1) / 2,
|
| + (packed_y_size->height() + 1) / 2);
|
| + PrepareTexture(Y_PLANE_OUTPUT, *packed_y_size);
|
| + PrepareTexture(UUVV_INTERMEDIATE, *packed_y_size);
|
| + PrepareTexture(U_PLANE_OUTPUT, *packed_uv_size);
|
| + PrepareTexture(V_PLANE_OUTPUT, *packed_uv_size);
|
| +
|
| + /////////////////////////////////////////
|
| + // Pass 1: RGB --(scaled)--> YYYY + UUVV
|
| + PrepareFramebuffer();
|
| + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame_buffer_);
|
| + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
|
| + texture_target_, textures_[Y_PLANE_OUTPUT], 0);
|
| + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
|
| + texture_target_, textures_[UUVV_INTERMEDIATE], 0);
|
| + DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
|
| + GL_FRAMEBUFFER_COMPLETE_EXT);
|
| + glDrawBuffers(2, kColorAttachments);
|
| +
|
| + // Read from |src_texture|. Enable bilinear filtering only if scaling is
|
| + // required. The filtering will take place entirely in the first pass.
|
| + glBindTexture(texture_target_, src_texture);
|
| + SetTextureParameters(
|
| + texture_target_, src_subrect.size() == dst_size ? GL_NEAREST : GL_LINEAR,
|
| + GL_CLAMP_TO_EDGE);
|
| +
|
| + // Use the first-pass shader program and draw the scene.
|
| + const bool prepared_pass_1 = shader_program_cache_->UseRGBToYV12Program(
|
| + 1,
|
| + static_cast<float>(src_subrect.width()) / dst_size.width());
|
| + DCHECK(prepared_pass_1);
|
| + SetTransformationsForOffScreenRendering(*packed_y_size);
|
| + DrawQuad(src_subrect.x(), src_subrect.y(),
|
| + ((packed_y_size->width() * 4.0f) / dst_size.width()) *
|
| + src_subrect.width(),
|
| + src_subrect.height(),
|
| + src_texture_needs_y_flip_,
|
| + packed_y_size->width(), packed_y_size->height());
|
| +
|
| + /////////////////////////////////////////
|
| + // Pass 2: UUVV -> UUUU + VVVV
|
| + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
|
| + texture_target_, textures_[U_PLANE_OUTPUT], 0);
|
| + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
|
| + texture_target_, textures_[V_PLANE_OUTPUT], 0);
|
| + DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
|
| + GL_FRAMEBUFFER_COMPLETE_EXT);
|
| +
|
| + // Read from the intermediate UUVV texture. The second pass uses bilinear
|
| + // minification to achieve vertical scaling, so enable it always.
|
| + glBindTexture(texture_target_, textures_[UUVV_INTERMEDIATE]);
|
| + SetTextureParameters(texture_target_, GL_LINEAR, GL_CLAMP_TO_EDGE);
|
| +
|
| + // Use the second-pass shader program and draw the scene.
|
| + const bool prepared_pass_2 =
|
| + shader_program_cache_->UseRGBToYV12Program(2, 1.0f);
|
| + DCHECK(prepared_pass_2);
|
| + SetTransformationsForOffScreenRendering(*packed_uv_size);
|
| + DrawQuad(0.0f, 0.0f,
|
| + packed_uv_size->width() * 2.0f,
|
| + packed_uv_size->height() * 2.0f,
|
| + false,
|
| + packed_uv_size->width(), packed_uv_size->height());
|
| + glUseProgram(0);
|
| +
|
| + // Before leaving, put back to drawing to a single rendering output.
|
| + glDrawBuffers(1, kColorAttachments);
|
| +
|
| + glBindTexture(texture_target_, 0);
|
| + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
|
| +
|
| + *texture_y = textures_[Y_PLANE_OUTPUT];
|
| + *texture_u = textures_[U_PLANE_OUTPUT];
|
| + *texture_v = textures_[V_PLANE_OUTPUT];
|
| + return true;
|
| +}
|
| +
|
| +void CompositingIOSurfaceTransformer::PrepareTexture(
|
| + CachedTexture which, const gfx::Size& size) {
|
| + DCHECK_GE(which, 0);
|
| + DCHECK_LT(which, NUM_CACHED_TEXTURES);
|
| + DCHECK(!size.IsEmpty());
|
| +
|
| + if (!textures_[which]) {
|
| + glGenTextures(1, &textures_[which]);
|
| + DCHECK_NE(textures_[which], 0u);
|
| + texture_sizes_[which] = gfx::Size();
|
| + }
|
| +
|
| + // Re-allocate the texture if its size has changed since last use.
|
| + if (texture_sizes_[which] != size) {
|
| + TRACE_EVENT2("gpu", "Resize Texture",
|
| + "which", which,
|
| + "new_size", size.ToString());
|
| + glBindTexture(texture_target_, textures_[which]);
|
| + glTexImage2D(texture_target_, 0, GL_RGBA, size.width(), size.height(), 0,
|
| + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
| + texture_sizes_[which] = size;
|
| + }
|
| +}
|
| +
|
| +void CompositingIOSurfaceTransformer::PrepareFramebuffer() {
|
| + if (!frame_buffer_) {
|
| + glGenFramebuffersEXT(1, &frame_buffer_);
|
| + DCHECK_NE(frame_buffer_, 0u);
|
| + }
|
| +}
|
| +
|
| +} // namespace content
|
|
|