summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormiu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-21 23:07:54 +0000
committermiu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-21 23:07:54 +0000
commitea2e54108c072d2f7cb9b56c5bd450702a688523 (patch)
treee58f8eb26139e529ba163a55d50c2df555f3b5d8
parent8559d45869f6a6c1c49e5cd83b83f5067bc14a53 (diff)
downloadchromium_src-ea2e54108c072d2f7cb9b56c5bd450702a688523.zip
chromium_src-ea2e54108c072d2f7cb9b56c5bd450702a688523.tar.gz
chromium_src-ea2e54108c072d2f7cb9b56c5bd450702a688523.tar.bz2
Implement GPU-accelerated conversion to YV12 for Mac platform.
This is heavily based on the D3D9 Windows implementation found in ui/surface/accelerated_surface_transformer_win.*. See: https://codereview.chromium.org/12090109/ Initial testing demonstrates that this improves capture/copy time by 30-50%. It also frees up significant CPU time for other tasks, such as VP8 video encoding for screencasting. compositing_iosurface_mac clean-ups (multiple minor bugs were discovered and fixed as a result): 1. Moved the existing GLSL shader programs for compositing_iosurface_mac into a new "shader program factory/cache" implementation (along with all the new GLSL shader programs needed for YV12 conversion). 2. Merged much of the "forked" CopyTo code in compositing_iosurface_mac together so that only the parts specialized for the asynchronous versus synchronous path are separate. 3. Untangled some of the complex web of callbacks and callback-wrapping surrounding the CopyTo() and CopyToVideoFrame() methods. BUG=174545 TEST=New content_unittests for new code. Plethora of manual testing by using the tab capture API. Review URL: https://codereview.chromium.org/12914002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@189717 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--content/browser/renderer_host/compositing_iosurface_mac.h95
-rw-r--r--content/browser/renderer_host/compositing_iosurface_mac.mm752
-rw-r--r--content/browser/renderer_host/compositing_iosurface_shader_programs_mac.cc407
-rw-r--r--content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h62
-rw-r--r--content/browser/renderer_host/compositing_iosurface_transformer_mac.cc320
-rw-r--r--content/browser/renderer_host/compositing_iosurface_transformer_mac.h93
-rw-r--r--content/browser/renderer_host/compositing_iosurface_transformer_mac_unittest.cc533
-rw-r--r--content/browser/renderer_host/render_widget_host_view_mac.mm8
-rw-r--r--content/content_browser.gypi6
-rw-r--r--content/content_tests.gypi5
10 files changed, 1827 insertions, 454 deletions
diff --git a/content/browser/renderer_host/compositing_iosurface_mac.h b/content/browser/renderer_host/compositing_iosurface_mac.h
index 19a04a6..7333d73 100644
--- a/content/browser/renderer_host/compositing_iosurface_mac.h
+++ b/content/browser/renderer_host/compositing_iosurface_mac.h
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef CONTENT_BROWSER_RENDERER_HOST_ACCELERATED_COMPOSITING_VIEW_MAC_H
-#define CONTENT_BROWSER_RENDERER_HOST_ACCELERATED_COMPOSITING_VIEW_MAC_H
+#ifndef CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_MAC_H_
#import <Cocoa/Cocoa.h>
#import <QuartzCore/CVDisplayLink.h>
@@ -12,17 +12,18 @@
#include "base/callback.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/memory/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
#include "base/synchronization/lock.h"
#include "base/time.h"
#include "base/timer.h"
#include "media/base/video_frame.h"
-#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/size.h"
class IOSurfaceSupport;
+class SkBitmap;
namespace gfx {
class Rect;
@@ -30,6 +31,8 @@ class Rect;
namespace content {
+class CompositingIOSurfaceShaderPrograms;
+class CompositingIOSurfaceTransformer;
class RenderWidgetHostViewFrameSubscriber;
// This class manages an OpenGL context and IOSurface for the accelerated
@@ -76,7 +79,6 @@ class CompositingIOSurfaceMac {
void CopyTo(const gfx::Rect& src_pixel_subrect,
float src_scale_factor,
const gfx::Size& dst_pixel_size,
- const SkBitmap& out,
const base::Callback<void(bool, const SkBitmap&)>& callback);
// Transfer the contents of the surface to an already-allocated YV12
@@ -115,6 +117,9 @@ class CompositingIOSurfaceMac {
uint32* interval_numerator,
uint32* interval_denominator);
+ // Returns true if asynchronous readback is supported on this system.
+ bool IsAsynchronousReadbackSupported();
+
private:
friend CVReturn DisplayLinkCallback(CVDisplayLinkRef,
const CVTimeStamp*,
@@ -177,42 +182,44 @@ class CompositingIOSurfaceMac {
SurfaceVertex verts_[4];
};
- // Keeps track of states and buffers for asynchronous readback of IOSurface.
+ // Keeps track of states and buffers for readback of IOSurface.
struct CopyContext {
CopyContext();
~CopyContext();
void Reset() {
started = false;
- cycles_elapsed = 0;
- frame_buffer = 0;
- frame_buffer_texture = 0;
- pixel_buffer = 0;
- use_fence = false;
+ num_outputs = 0;
+ memset(output_textures, 0u, sizeof(output_textures));
+ memset(frame_buffers, 0u, sizeof(frame_buffers));
+ memset(pixel_buffers, 0u, sizeof(pixel_buffers));
fence = 0;
+ cycles_elapsed = 0;
map_buffer_callback.Reset();
+ done_callback.Reset();
}
bool started;
+ int num_outputs;
+ GLuint output_textures[3];
+ // Note: For YUV, the |output_texture_sizes| widths are in terms of 4-byte
+ // quads, not pixels.
+ gfx::Size output_texture_sizes[3];
+ GLuint frame_buffers[3];
+ GLuint pixel_buffers[3];
+ GLuint fence; // When non-zero, doing an asynchronous copy.
int cycles_elapsed;
- GLuint frame_buffer;
- GLuint frame_buffer_texture;
- GLuint pixel_buffer;
- bool use_fence;
- GLuint fence;
- gfx::Rect src_rect;
- gfx::Size dest_size;
- base::Callback<base::Closure(void*)> map_buffer_callback;
+ base::Callback<bool(const void*, int)> map_buffer_callback;
+ base::Callback<void(bool)> done_callback;
};
- CompositingIOSurfaceMac(IOSurfaceSupport* io_surface_support,
- NSOpenGLContext* glContext,
- CGLContextObj cglContext,
- GLuint shader_program_blit_rgb,
- GLint blit_rgb_sampler_location,
- GLuint shader_program_white,
- bool is_vsync_disabled,
- CVDisplayLinkRef display_link);
+ CompositingIOSurfaceMac(
+ IOSurfaceSupport* io_surface_support,
+ NSOpenGLContext* glContext,
+ CGLContextObj cglContext,
+ scoped_ptr<CompositingIOSurfaceShaderPrograms> shader_program_cache,
+ bool is_vsync_disabled,
+ CVDisplayLinkRef display_link);
bool IsVendorIntel();
@@ -239,24 +246,30 @@ class CompositingIOSurfaceMac {
// Copy current frame to |target| video frame. This method must be called
// within a CGL context. Returns a callback that should be called outside
// of the CGL context.
- base::Closure CopyToVideoFrameInternal(
+ base::Closure CopyToVideoFrameWithinContext(
const gfx::Rect& src_subrect,
float src_scale_factor,
const scoped_refptr<media::VideoFrame>& target,
const base::Callback<void(bool)>& callback);
- // Two implementations of CopyTo() in synchronous and asynchronous mode.
- // These may copy regions smaller than the requested |src_pixel_subrect| if
- // the iosurface is smaller than |src_pixel_subrect|.
- bool SynchronousCopyTo(const gfx::Rect& src_pixel_subrect,
- float src_scale_factor,
- const gfx::Size& dst_pixel_size,
- const SkBitmap& out);
- bool AsynchronousCopyTo(
+ // Common GPU-readback copy path. Only one of |bitmap_output| or
+ // |video_frame_output| may be specified: Either ARGB is written to
+ // |bitmap_output| or letter-boxed YV12 is written to |video_frame_output|.
+ base::Closure CopyToSelectedOutputWithinContext(
const gfx::Rect& src_pixel_subrect,
float src_scale_factor,
- const gfx::Size& dst_pixel_size,
- const base::Callback<base::Closure(void*)>& map_buffer_callback);
+ const gfx::Rect& dst_pixel_rect,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output,
+ const base::Callback<void(bool)>& done_callback);
+ void AsynchronousReadbackForCopy(
+ const gfx::Rect& dst_pixel_rect,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output);
+ bool SynchronousReadbackForCopy(
+ const gfx::Rect& dst_pixel_rect,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output);
void FinishCopy();
void CleanupResourcesForCopy();
@@ -290,10 +303,8 @@ class CompositingIOSurfaceMac {
// Timer for finishing a copy operation.
base::RepeatingTimer<CompositingIOSurfaceMac> copy_timer_;
- // Shader parameters.
- GLuint shader_program_blit_rgb_;
- GLint blit_rgb_sampler_location_;
- GLuint shader_program_white_;
+ scoped_ptr<CompositingIOSurfaceShaderPrograms> shader_program_cache_;
+ scoped_ptr<CompositingIOSurfaceTransformer> transformer_;
SurfaceQuad quad_;
@@ -324,4 +335,4 @@ class CompositingIOSurfaceMac {
} // namespace content
-#endif // CONTENT_BROWSER_RENDERER_HOST_ACCELERATED_COMPOSITING_VIEW_MAC_H
+#endif // CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_MAC_H_
diff --git a/content/browser/renderer_host/compositing_iosurface_mac.mm b/content/browser/renderer_host/compositing_iosurface_mac.mm
index 488bbc7..ed8dd52 100644
--- a/content/browser/renderer_host/compositing_iosurface_mac.mm
+++ b/content/browser/renderer_host/compositing_iosurface_mac.mm
@@ -8,15 +8,21 @@
#include <OpenGL/OpenGL.h>
#include <vector>
+#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
+#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/message_loop.h"
#include "base/threading/platform_thread.h"
+#include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
+#include "content/browser/renderer_host/compositing_iosurface_transformer_mac.h"
#include "content/common/content_constants_internal.h"
#include "content/port/browser/render_widget_host_view_frame_subscriber.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "media/base/video_util.h"
+#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
#include "ui/gl/gl_context.h"
@@ -34,82 +40,11 @@
} while (0)
#endif
-#define SHADER_STRING_GLSL(shader) #shader
-
namespace content {
namespace {
-const char* g_vertex_shader_blit_rgb = SHADER_STRING_GLSL(
- varying vec2 texture_coordinate;
- void main() {
- gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
- texture_coordinate = vec2(gl_MultiTexCoord0);
- });
-
-const char* g_fragment_shader_blit_rgb = SHADER_STRING_GLSL(
- varying vec2 texture_coordinate;
- uniform sampler2DRect texture;
- void main() {
- gl_FragColor = vec4(texture2DRect(texture, texture_coordinate).rgb, 1.0);
- });
-
-const char* g_vertex_shader_white = SHADER_STRING_GLSL(
- void main() {
- gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
- });
-
-const char* g_fragment_shader_white = SHADER_STRING_GLSL(
- void main() {
- gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
- });
-
-// Create and compile shader, return its ID or 0 on error.
-GLuint CompileShaderGLSL(GLenum type, const char* shader_str) {
- GLuint shader = glCreateShader(type);
- glShaderSource(shader, 1, &shader_str, NULL);
- glCompileShader(shader); CHECK_GL_ERROR();
- GLint error;
- glGetShaderiv(shader, GL_COMPILE_STATUS, &error);
- if (error != GL_TRUE) {
- glDeleteShader(shader);
- return 0;
- }
- return shader;
-}
-
-// Compile the given vertex and shader source strings into a GLSL program.
-GLuint CreateProgramGLSL(const char* vertex_shader_str,
- const char* fragment_shader_str) {
- GLuint vertex_shader =
- CompileShaderGLSL(GL_VERTEX_SHADER, vertex_shader_str);
- if (!vertex_shader)
- return 0;
-
- GLuint fragment_shader =
- CompileShaderGLSL(GL_FRAGMENT_SHADER, fragment_shader_str);
- if (!fragment_shader) {
- glDeleteShader(vertex_shader);
- return 0;
- }
-
- GLuint program = glCreateProgram(); CHECK_GL_ERROR();
- glAttachShader(program, vertex_shader);
- glAttachShader(program, fragment_shader);
- glLinkProgram(program); CHECK_GL_ERROR();
-
- // Flag shaders for deletion so that they will be deleted when the program
- // is deleted. That way we don't have to retain these IDs.
- glDeleteShader(vertex_shader);
- glDeleteShader(fragment_shader);
-
- GLint error;
- glGetProgramiv(program, GL_LINK_STATUS, &error);
- if (error != GL_TRUE) {
- glDeleteProgram(program);
- return 0;
- }
- return program;
-}
+// Everything uses only the GL_TEXTURE0 texture unit.
+const int kTextureUnit = 0;
bool HasAppleFenceExtension() {
static bool initialized_has_fence = false;
@@ -137,35 +72,77 @@ bool HasPixelBufferObjectExtension() {
return has_pbo;
}
-// Called during an async GPU readback with a pointer to the pixel buffer. In
-// the snapshot path, we just memcpy the data into our output bitmap. The
-// async copy completion callback is returned to be invoked after cleanup.
-base::Closure MapBufferMemcpy(
- const SkBitmap& out,
+// Helper function to reverse the argument order. Also takes ownership of
+// |bitmap_output| for the life of the binding.
+void ReverseArgumentOrder(
const base::Callback<void(bool, const SkBitmap&)>& callback,
- size_t num_bytes, void* buf) {
+ scoped_ptr<SkBitmap> bitmap_output, bool success) {
+ callback.Run(success, *bitmap_output);
+}
+
+// Called during an async GPU readback with a pointer to the pixel buffer. In
+// the snapshot path, we just memcpy the data into our output bitmap since the
+// width, height, and stride should all be equal.
+bool MapBufferToSkBitmap(const SkBitmap* output, const void* buf, int ignored) {
+ TRACE_EVENT0("browser", "MapBufferToSkBitmap()");
+
if (buf) {
- SkAutoLockPixels bitmap_lock(out);
- memcpy(out.getPixels(), buf, num_bytes);
+ SkAutoLockPixels output_lock(*output);
+ memcpy(output->getPixels(), buf, output->getSize());
}
- return base::Bind(callback, buf != NULL, out);
+ return buf != NULL;
}
-// Called during an async GPU readback with a pointer to the pixel buffer. In
-// the video path, we letterbox and convert to YUV into the target frame. The
-// async copy completion callback is returned to be invoked after cleanup.
-base::Closure MapBufferToVideoFrame(
+// Copies tightly-packed scanlines from |buf| to |region_in_frame| in the given
+// |target| VideoFrame's |plane|. Assumption: |buf|'s width is
+// |region_in_frame.width()| and its stride is always in 4-byte alignment.
+//
+// TODO(miu): Refactor by moving this function into media/video_util.
+// http://crbug.com/219779
+bool MapBufferToVideoFrame(
const scoped_refptr<media::VideoFrame>& target,
- const gfx::Rect region_in_frame,
- const base::Callback<void(bool)>& callback,
- void* buf) {
+ const gfx::Rect& region_in_frame,
+ const void* buf,
+ int plane) {
+ COMPILE_ASSERT(media::VideoFrame::kYPlane == 0, VideoFrame_kYPlane_mismatch);
+ COMPILE_ASSERT(media::VideoFrame::kUPlane == 1, VideoFrame_kUPlane_mismatch);
+ COMPILE_ASSERT(media::VideoFrame::kVPlane == 2, VideoFrame_kVPlane_mismatch);
+
+ TRACE_EVENT1("browser", "MapBufferToVideoFrame()", "plane", plane);
+
+ // Apply black-out in the regions surrounding the view area (for
+ // letterboxing/pillarboxing). Only do this once, since this is performed on
+ // all planes in the VideoFrame here.
+ if (plane == 0)
+ media::LetterboxYUV(target, region_in_frame);
+
if (buf) {
- media::CopyRGBToVideoFrame(reinterpret_cast<const uint8*>(buf),
- region_in_frame.width() * 4,
- region_in_frame,
- target.get());
+ int packed_width = region_in_frame.width();
+ int packed_height = region_in_frame.height();
+ // For planes 1 and 2, the width and height are 1/2 size (rounded up).
+ if (plane > 0) {
+ packed_width = (packed_width + 1) / 2;
+ packed_height = (packed_height + 1) / 2;
+ }
+ const uint8* src = reinterpret_cast<const uint8*>(buf);
+ const int src_stride = (packed_width % 4 == 0 ?
+ packed_width :
+ (packed_width + 4 - (packed_width % 4)));
+ const uint8* const src_end = src + packed_height * src_stride;
+
+ // Calculate starting offset and stride into the destination buffer.
+ const int dst_stride = target->stride(plane);
+ uint8* dst = target->data(plane);
+ if (plane == 0)
+ dst += (region_in_frame.y() * dst_stride) + region_in_frame.x();
+ else
+ dst += (region_in_frame.y() / 2 * dst_stride) + (region_in_frame.x() / 2);
+
+ // Copy each row, accounting for strides in the source and destination.
+ for (; src < src_end; src += src_stride, dst += dst_stride)
+ memcpy(dst, src, packed_width);
}
- return base::Bind(callback, buf != NULL);
+ return buf != NULL;
}
} // namespace
@@ -241,19 +218,17 @@ CompositingIOSurfaceMac* CompositingIOSurfaceMac::Create(SurfaceOrder order) {
GLint swapInterval = is_vsync_disabled ? 0 : 1;
[glContext setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
- // Build shaders.
+ // Prepare the shader program cache. Precompile only the shader programs
+ // needed to draw the IO Surface.
CGLSetCurrentContext(cglContext);
- GLuint shader_program_blit_rgb =
- CreateProgramGLSL(g_vertex_shader_blit_rgb, g_fragment_shader_blit_rgb);
- GLuint shader_program_white =
- CreateProgramGLSL(g_vertex_shader_white, g_fragment_shader_white);
- GLint blit_rgb_sampler_location =
- glGetUniformLocation(shader_program_blit_rgb, "texture");
+ scoped_ptr<CompositingIOSurfaceShaderPrograms> shader_program_cache(
+ new CompositingIOSurfaceShaderPrograms());
+ const bool prepared = (shader_program_cache->UseBlitProgram(kTextureUnit) &&
+ shader_program_cache->UseSolidWhiteProgram());
+ glUseProgram(0u);
CGLSetCurrentContext(0);
-
- if (!shader_program_blit_rgb || !shader_program_white ||
- blit_rgb_sampler_location == -1) {
- LOG(ERROR) << "IOSurface shader build error";
+ if (!prepared) {
+ LOG(ERROR) << "IOSurface failed to compile/link required shader programs.";
return NULL;
}
@@ -266,9 +241,7 @@ CompositingIOSurfaceMac* CompositingIOSurfaceMac::Create(SurfaceOrder order) {
return new CompositingIOSurfaceMac(io_surface_support, glContext.release(),
cglContext,
- shader_program_blit_rgb,
- blit_rgb_sampler_location,
- shader_program_white,
+ shader_program_cache.Pass(),
is_vsync_disabled,
display_link);
}
@@ -277,9 +250,7 @@ CompositingIOSurfaceMac::CompositingIOSurfaceMac(
IOSurfaceSupport* io_surface_support,
NSOpenGLContext* glContext,
CGLContextObj cglContext,
- GLuint shader_program_blit_rgb,
- GLint blit_rgb_sampler_location,
- GLuint shader_program_white,
+ scoped_ptr<CompositingIOSurfaceShaderPrograms> shader_program_cache,
bool is_vsync_disabled,
CVDisplayLinkRef display_link)
: io_surface_support_(io_surface_support),
@@ -287,9 +258,7 @@ CompositingIOSurfaceMac::CompositingIOSurfaceMac(
cglContext_(cglContext),
io_surface_handle_(0),
texture_(0),
- shader_program_blit_rgb_(shader_program_blit_rgb),
- blit_rgb_sampler_location_(blit_rgb_sampler_location),
- shader_program_white_(shader_program_white),
+ shader_program_cache_(shader_program_cache.Pass()),
is_vsync_disabled_(is_vsync_disabled),
display_link_(display_link),
display_link_stop_timer_(FROM_HERE, base::TimeDelta::FromSeconds(1),
@@ -335,12 +304,13 @@ CompositingIOSurfaceMac::~CompositingIOSurfaceMac() {
// Make sure we still run the callback if we are being destroyed with an
// active copy_timer_ that has not yet fired.
if (copy_context_.started)
- copy_context_.map_buffer_callback.Run(NULL).Run();
+ copy_context_.done_callback.Run(false);
CVDisplayLinkRelease(display_link_);
CGLSetCurrentContext(cglContext_);
CleanupResourcesForCopy();
UnrefIOSurfaceWithContextCurrent();
+ shader_program_cache_->Reset();
CGLSetCurrentContext(0);
}
@@ -397,11 +367,8 @@ void CompositingIOSurfaceMac::DrawIOSurface(
glDisable(GL_BLEND);
if (has_io_surface) {
- glUseProgram(shader_program_blit_rgb_);
-
- int texture_unit = 0;
- glUniform1i(blit_rgb_sampler_location_, texture_unit);
- glActiveTexture(GL_TEXTURE0 + texture_unit);
+ shader_program_cache_->UseBlitProgram(kTextureUnit);
+ glActiveTexture(GL_TEXTURE0 + kTextureUnit);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
DrawQuad(quad_);
@@ -411,7 +378,7 @@ void CompositingIOSurfaceMac::DrawIOSurface(
// Fill the resize gutters with white.
if (window_size.width() > io_surface_size_.width() ||
window_size.height() > io_surface_size_.height()) {
- glUseProgram(shader_program_white_);
+ shader_program_cache_->UseSolidWhiteProgram();
SurfaceQuad filler_quad;
if (window_size.width() > io_surface_size_.width()) {
// Draw right-side gutter down to the bottom of the window.
@@ -471,10 +438,8 @@ void CompositingIOSurfaceMac::DrawIOSurface(
if (frame_subscriber) {
scoped_refptr<media::VideoFrame> frame;
RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
- bool should_copy = frame_subscriber->ShouldCaptureFrame(&frame, &callback);
-
- if (should_copy) {
- copy_done_callback = CopyToVideoFrameInternal(
+ if (frame_subscriber->ShouldCaptureFrame(&frame, &callback)) {
+ copy_done_callback = CopyToVideoFrameWithinContext(
gfx::Rect(io_surface_size_), scale_factor, frame,
base::Bind(callback, base::Time::Now()));
}
@@ -500,67 +465,49 @@ void CompositingIOSurfaceMac::CopyTo(
const gfx::Rect& src_pixel_subrect,
float src_scale_factor,
const gfx::Size& dst_pixel_size,
- const SkBitmap& out,
const base::Callback<void(bool, const SkBitmap&)>& callback) {
- CGLSetCurrentContext(cglContext_);
-
- // Using PBO crashes on Intel drivers but not on newer Mountain Lion
- // systems. See bug http://crbug.com/152225.
- const bool async_copy = HasPixelBufferObjectExtension() &&
- (base::mac::IsOSMountainLionOrLater() || !IsVendorIntel());
-
- bool ret = false;
- if (async_copy) {
- ret = AsynchronousCopyTo(
- src_pixel_subrect, src_scale_factor, dst_pixel_size,
- base::Bind(
- &MapBufferMemcpy, out, callback, dst_pixel_size.GetArea() * 4));
- } else {
- ret = SynchronousCopyTo(
- src_pixel_subrect, src_scale_factor, dst_pixel_size, out);
+ scoped_ptr<SkBitmap> output(new SkBitmap());
+ output->setConfig(SkBitmap::kARGB_8888_Config,
+ dst_pixel_size.width(), dst_pixel_size.height());
+ if (!output->allocPixels()) {
+ DLOG(ERROR) << "Failed to allocate SkBitmap pixels!";
+ callback.Run(false, *output);
+ return;
}
- CGLSetCurrentContext(0);
-
- if (!ret)
- VLOG(1) << "Failed to copy IOSurface, asynchronous mode: " << async_copy;
+ DCHECK_EQ(output->rowBytesAsPixels(), dst_pixel_size.width())
+ << "Stride is required to be equal to width for GPU readback.";
+ output->setIsOpaque(true);
- if (async_copy) {
- if (!ret)
- callback.Run(false, SkBitmap());
- } else {
- callback.Run(ret, out);
- }
+ CGLSetCurrentContext(cglContext_);
+ const base::Closure copy_done_callback = CopyToSelectedOutputWithinContext(
+ src_pixel_subrect, src_scale_factor, gfx::Rect(dst_pixel_size),
+ output.get(), NULL,
+ base::Bind(&ReverseArgumentOrder, callback, base::Passed(&output)));
+ CGLSetCurrentContext(0);
+ if (!copy_done_callback.is_null())
+ copy_done_callback.Run();
}
void CompositingIOSurfaceMac::CopyToVideoFrame(
- const gfx::Rect& requested_src_subrect,
+ const gfx::Rect& src_pixel_subrect,
float src_scale_factor,
const scoped_refptr<media::VideoFrame>& target,
const base::Callback<void(bool)>& callback) {
CGLSetCurrentContext(cglContext_);
- base::Closure done_callback = CopyToVideoFrameInternal(requested_src_subrect,
- src_scale_factor,
- target, callback);
+ const base::Closure copy_done_callback = CopyToVideoFrameWithinContext(
+ src_pixel_subrect, src_scale_factor, target, callback);
CGLSetCurrentContext(0);
-
- if (done_callback.is_null())
- return;
- done_callback.Run();
+ if (!copy_done_callback.is_null())
+ copy_done_callback.Run();
}
-base::Closure CompositingIOSurfaceMac::CopyToVideoFrameInternal(
- const gfx::Rect& requested_src_subrect,
+base::Closure CompositingIOSurfaceMac::CopyToVideoFrameWithinContext(
+ const gfx::Rect& src_pixel_subrect,
float src_scale_factor,
const scoped_refptr<media::VideoFrame>& target,
const base::Callback<void(bool)>& callback) {
- // Using PBO crashes on Intel drivers but not on newer Mountain Lion
- // systems. See bug http://crbug.com/152225.
- const bool async_copy = HasPixelBufferObjectExtension() &&
- (base::mac::IsOSMountainLionOrLater() || !IsVendorIntel());
-
- gfx::Rect region_in_frame =
- media::ComputeLetterboxRegion(gfx::Rect(target->coded_size()),
- requested_src_subrect.size());
+ gfx::Rect region_in_frame = media::ComputeLetterboxRegion(
+ gfx::Rect(target->coded_size()), src_pixel_subrect.size());
// Make coordinates and sizes even because we letterbox in YUV space right
// now (see CopyRGBToVideoFrame). They need to be even for the UV samples to
// line up correctly.
@@ -568,48 +515,13 @@ base::Closure CompositingIOSurfaceMac::CopyToVideoFrameInternal(
region_in_frame.y() & ~1,
region_in_frame.width() & ~1,
region_in_frame.height() & ~1);
+ DCHECK(!region_in_frame.IsEmpty());
+ DCHECK_LE(region_in_frame.right(), target->coded_size().width());
+ DCHECK_LE(region_in_frame.bottom(), target->coded_size().height());
- bool ret = false;
- if (async_copy) {
- ret = AsynchronousCopyTo(
- requested_src_subrect, src_scale_factor, region_in_frame.size(),
- base::Bind(&MapBufferToVideoFrame, target, region_in_frame, callback));
- } else {
- SkBitmap out;
- out.setConfig(SkBitmap::kARGB_8888_Config,
- region_in_frame.width(),
- region_in_frame.height(),
- region_in_frame.width() * 4);
- if (out.allocPixels() && SynchronousCopyTo(requested_src_subrect,
- src_scale_factor,
- region_in_frame.size(),
- out)) {
- SkAutoLockPixels bitmap_lock(out);
- media::CopyRGBToVideoFrame(
- reinterpret_cast<const uint8*>(out.getPixels()),
- region_in_frame.width() * 4,
- region_in_frame,
- target.get());
- ret = true;
- }
- }
-
- if (!ret) {
- VLOG(1) << "Failed to copy IOSurface to video frame, asynchronous mode: "
- << async_copy;
- }
-
- if (async_copy) {
- // Asynchronous copy failed, return a callback to be executed now.
- if (!ret)
- return base::Bind(callback, false);
-
- // Asynchronous copy started, return a null closure.
- return base::Closure();
- } else {
- // Synchronous copy, return a callback to be executed now.
- return base::Bind(callback, ret);
- }
+ return CopyToSelectedOutputWithinContext(
+ src_pixel_subrect, src_scale_factor, region_in_frame, NULL, target,
+ callback);
}
bool CompositingIOSurfaceMac::MapIOSurfaceToTexture(
@@ -783,257 +695,291 @@ void CompositingIOSurfaceMac::StopDisplayLink() {
CVDisplayLinkStop(display_link_);
}
-bool CompositingIOSurfaceMac::SynchronousCopyTo(
- const gfx::Rect& src_pixel_subrect,
- float src_scale_factor,
- const gfx::Size& dst_pixel_size,
- const SkBitmap& out) {
- if (!MapIOSurfaceToTexture(io_surface_handle_))
- return false;
-
- TRACE_EVENT0("browser", "CompositingIOSurfaceMac::SynchronousCopyTo()");
-
- const GLenum kDestTextureTarget = GL_TEXTURE_2D;
- const GLenum kSrcTextureTarget = GL_TEXTURE_RECTANGLE_ARB;
-
- GLuint dst_texture = 0;
- glGenTextures(1, &dst_texture); CHECK_GL_ERROR();
- glBindTexture(kDestTextureTarget, dst_texture); CHECK_GL_ERROR();
- glTexParameterf(kDestTextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameterf(kDestTextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
- GLuint dst_framebuffer = 0;
- glGenFramebuffersEXT(1, &dst_framebuffer); CHECK_GL_ERROR();
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, dst_framebuffer); CHECK_GL_ERROR();
-
- glTexImage2D(kDestTextureTarget,
- 0,
- GL_RGBA,
- dst_pixel_size.width(),
- dst_pixel_size.height(),
- 0,
- GL_BGRA,
- GL_UNSIGNED_INT_8_8_8_8_REV,
- NULL); CHECK_GL_ERROR();
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
- GL_COLOR_ATTACHMENT0_EXT,
- kDestTextureTarget,
- dst_texture,
- 0); CHECK_GL_ERROR();
- glBindTexture(kDestTextureTarget, 0); CHECK_GL_ERROR();
-
- glViewport(0, 0, dst_pixel_size.width(), dst_pixel_size.height());
-
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glOrtho(0, dst_pixel_size.width(), 0, dst_pixel_size.height(), -1, 1);
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
-
- glDisable(GL_DEPTH_TEST);
- glDisable(GL_BLEND);
-
- glUseProgram(shader_program_blit_rgb_);
-
- int texture_unit = 0;
- glUniform1i(blit_rgb_sampler_location_, texture_unit);
- glActiveTexture(GL_TEXTURE0 + texture_unit);
- glBindTexture(kSrcTextureTarget, texture_);
- glTexParameterf(kSrcTextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameterf(kSrcTextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
- const gfx::Rect composited_src_pixel_subrect =
- IntersectWithIOSurface(src_pixel_subrect, src_scale_factor);
-
- SurfaceQuad quad;
- quad.set_rect(0.0f, 0.0f, dst_pixel_size.width(), dst_pixel_size.height());
- quad.set_texcoord_rect(composited_src_pixel_subrect.x(),
- composited_src_pixel_subrect.y(),
- composited_src_pixel_subrect.right(),
- composited_src_pixel_subrect.bottom());
- DrawQuad(quad);
-
- glBindTexture(kSrcTextureTarget, 0); CHECK_GL_ERROR();
- glUseProgram(0);
-
- CGLFlushDrawable(cglContext_);
-
- glReadPixels(0, 0, dst_pixel_size.width(), dst_pixel_size.height(),
- GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, out.getPixels());
-
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); CHECK_GL_ERROR();
-
- glDeleteFramebuffersEXT(1, &dst_framebuffer);
- glDeleteTextures(1, &dst_texture);
- return true;
+bool CompositingIOSurfaceMac::IsAsynchronousReadbackSupported() {
+ // Using PBO crashes on Intel drivers but not on newer Mountain Lion
+ // systems. See bug http://crbug.com/152225.
+ return (HasAppleFenceExtension() &&
+ HasPixelBufferObjectExtension() &&
+ (base::mac::IsOSMountainLionOrLater() || !IsVendorIntel()));
}
-bool CompositingIOSurfaceMac::AsynchronousCopyTo(
- const gfx::Rect& src_pixel_subrect,
- float src_scale_factor,
- const gfx::Size& dst_pixel_size,
- const base::Callback<base::Closure(void*)>& map_buffer_callback) {
- if (copy_context_.started)
- return false;
-
- if (!MapIOSurfaceToTexture(io_surface_handle_))
- return false;
-
- TRACE_EVENT0("browser", "CompositingIOSurfaceMac::AsynchronousCopyTo()");
+base::Closure CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext(
+ const gfx::Rect& src_pixel_subrect,
+ float src_scale_factor,
+ const gfx::Rect& dst_pixel_rect,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output,
+ const base::Callback<void(bool)>& done_callback) {
+ DCHECK_NE(bitmap_output != NULL, video_frame_output != NULL);
+ DCHECK(!done_callback.is_null());
+ if (copy_context_.started)
+ return base::Bind(done_callback, false);
copy_context_.started = true;
- copy_context_.src_rect = IntersectWithIOSurface(src_pixel_subrect,
- src_scale_factor);
- copy_context_.dest_size = dst_pixel_size;
- copy_context_.map_buffer_callback = map_buffer_callback;
- const bool use_fence = HasAppleFenceExtension();
- if (use_fence) {
+ // If the platform supports fences, readback will occur asynchronously.
+ if (IsAsynchronousReadbackSupported()) {
glGenFencesAPPLE(1, &copy_context_.fence); CHECK_GL_ERROR();
- copy_context_.use_fence = true;
copy_context_.cycles_elapsed = 0;
+ } else {
+ copy_context_.fence = 0;
}
- // Create an offscreen framebuffer.
- // This is used to render and scale a subrect of IOSurface.
- const GLenum kDestTextureTarget = GL_TEXTURE_2D;
- const GLenum kSrcTextureTarget = GL_TEXTURE_RECTANGLE_ARB;
- const int dest_width = copy_context_.dest_size.width();
- const int dest_height = copy_context_.dest_size.height();
+ TRACE_EVENT2(
+ "browser", "CompositingIOSurfaceMac::CopyToSelectedOutputWithinContext()",
+ "output", bitmap_output ? "SkBitmap (ARGB)" : "VideoFrame (YV12)",
+ "async_readback", !!copy_context_.fence);
- glGenTextures(1, &copy_context_.frame_buffer_texture); CHECK_GL_ERROR();
- glBindTexture(kDestTextureTarget, copy_context_.frame_buffer_texture);
- CHECK_GL_ERROR();
- glTexParameterf(kDestTextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameterf(kDestTextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ // Set up source texture, bound to the GL_TEXTURE_RECTANGLE_ARB target.
+ if (!MapIOSurfaceToTexture(io_surface_handle_))
+ return base::Bind(done_callback, false);
+
+ // Create the transformer_ on first use.
+ if (!transformer_) {
+ transformer_.reset(new CompositingIOSurfaceTransformer(
+ GL_TEXTURE_RECTANGLE_ARB, kTextureUnit, true,
+ shader_program_cache_.get()));
+ }
+
+ // Send transform commands to the GPU.
+ const gfx::Rect src_rect = IntersectWithIOSurface(src_pixel_subrect,
+ src_scale_factor);
+ if (bitmap_output) {
+ if (transformer_->ResizeBilinear(texture_, src_rect, dst_pixel_rect.size(),
+ &copy_context_.output_textures[0])) {
+ copy_context_.num_outputs = 1;
+ copy_context_.output_texture_sizes[0] = dst_pixel_rect.size();
+ } else {
+ copy_context_.num_outputs = 0;
+ }
+ } else {
+ if (transformer_->TransformRGBToYV12(
+ texture_, src_rect, dst_pixel_rect.size(),
+ &copy_context_.output_textures[0],
+ &copy_context_.output_textures[1],
+ &copy_context_.output_textures[2],
+ &copy_context_.output_texture_sizes[0],
+ &copy_context_.output_texture_sizes[1])) {
+ copy_context_.num_outputs = 3;
+ copy_context_.output_texture_sizes[2] =
+ copy_context_.output_texture_sizes[1];
+ } else {
+ copy_context_.num_outputs = 0;
+ }
+ }
+ if (!copy_context_.num_outputs)
+ return base::Bind(done_callback, false);
- glGenFramebuffersEXT(1, &copy_context_.frame_buffer); CHECK_GL_ERROR();
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, copy_context_.frame_buffer);
- CHECK_GL_ERROR();
+ // In the asynchronous case, issue commands to the GPU and return a null
+ // closure here. In the synchronous case, perform a blocking readback and
+ // return a callback to be run outside the CGL context to indicate success.
+ if (copy_context_.fence) {
+ copy_context_.done_callback = done_callback;
+ AsynchronousReadbackForCopy(
+ dst_pixel_rect, bitmap_output, video_frame_output);
+ return base::Closure();
+ } else {
+ const bool success = SynchronousReadbackForCopy(
+ dst_pixel_rect, bitmap_output, video_frame_output);
+ CleanupResourcesForCopy();
+ return base::Bind(done_callback, success);
+ }
+}
- glTexImage2D(kDestTextureTarget,
- 0,
- GL_RGBA,
- dest_width,
- dest_height,
- 0,
- GL_BGRA,
- GL_UNSIGNED_INT_8_8_8_8_REV,
- NULL); CHECK_GL_ERROR();
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
- GL_COLOR_ATTACHMENT0_EXT,
- kDestTextureTarget,
- copy_context_.frame_buffer_texture,
- 0); CHECK_GL_ERROR();
- glBindTexture(kDestTextureTarget, 0); CHECK_GL_ERROR();
-
- glViewport(0, 0, dest_width, dest_height); CHECK_GL_ERROR();
- glMatrixMode(GL_PROJECTION); CHECK_GL_ERROR();
- glLoadIdentity(); CHECK_GL_ERROR();
- glOrtho(0, dest_width, 0, dest_height, -1, 1); CHECK_GL_ERROR();
- glMatrixMode(GL_MODELVIEW); CHECK_GL_ERROR();
- glLoadIdentity(); CHECK_GL_ERROR();
-
- glDisable(GL_DEPTH_TEST); CHECK_GL_ERROR();
- glDisable(GL_BLEND); CHECK_GL_ERROR();
-
- glUseProgram(shader_program_blit_rgb_); CHECK_GL_ERROR();
-
- const int kTextureUnit = 0;
- glUniform1i(blit_rgb_sampler_location_, kTextureUnit); CHECK_GL_ERROR();
- glActiveTexture(GL_TEXTURE0 + kTextureUnit); CHECK_GL_ERROR();
- glBindTexture(kSrcTextureTarget, texture_); CHECK_GL_ERROR();
- glTexParameterf(kSrcTextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+void CompositingIOSurfaceMac::AsynchronousReadbackForCopy(
+ const gfx::Rect& dst_pixel_rect,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output) {
+ // Copy the textures to a PBO.
+ glGenFramebuffersEXT(copy_context_.num_outputs, copy_context_.frame_buffers);
CHECK_GL_ERROR();
- glTexParameterf(kSrcTextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glGenBuffersARB(copy_context_.num_outputs, copy_context_.pixel_buffers);
CHECK_GL_ERROR();
-
- SurfaceQuad quad;
- quad.set_rect(0.0f, 0.0f, dest_width, dest_height); CHECK_GL_ERROR();
- quad.set_texcoord_rect(
- copy_context_.src_rect.x(), copy_context_.src_rect.y(),
- copy_context_.src_rect.right(), copy_context_.src_rect.bottom());
- DrawQuad(quad);
-
- glBindTexture(kSrcTextureTarget, 0); CHECK_GL_ERROR();
- glUseProgram(0); CHECK_GL_ERROR();
-
- // Copy the offscreen framebuffer to a PBO.
- glGenBuffersARB(1, &copy_context_.pixel_buffer); CHECK_GL_ERROR();
- glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context_.pixel_buffer);
- CHECK_GL_ERROR();
- glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB,
- dest_width * dest_height * 4,
- NULL, GL_STREAM_READ_ARB); CHECK_GL_ERROR();
- glReadPixels(0, 0, dest_width, dest_height, GL_BGRA,
- GL_UNSIGNED_INT_8_8_8_8_REV, 0); CHECK_GL_ERROR();
+ for (int i = 0; i < copy_context_.num_outputs; ++i) {
+ TRACE_EVENT1(
+ "browser", "CompositingIOSurfaceMac::AsynchronousReadbackForCopy()",
+ "plane", i);
+
+ // Attach the output texture to the FBO.
+ glBindFramebufferEXT(
+ GL_READ_FRAMEBUFFER_EXT, copy_context_.frame_buffers[i]);
+ glFramebufferTexture2DEXT(
+ GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_RECTANGLE_ARB, copy_context_.output_textures[i], 0);
+ DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+
+ // Create a PBO and issue an asynchronous read-back.
+ glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context_.pixel_buffers[i]);
+ CHECK_GL_ERROR();
+ glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB,
+ copy_context_.output_texture_sizes[i].GetArea() * 4,
+ NULL, GL_STREAM_READ_ARB); CHECK_GL_ERROR();
+ glReadPixels(0, 0,
+ copy_context_.output_texture_sizes[i].width(),
+ copy_context_.output_texture_sizes[i].height(),
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); CHECK_GL_ERROR();
+ }
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_GL_ERROR();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); CHECK_GL_ERROR();
- if (use_fence) {
- glSetFenceAPPLE(copy_context_.fence); CHECK_GL_ERROR();
- }
+ glSetFenceAPPLE(copy_context_.fence); CHECK_GL_ERROR();
glFlush(); CHECK_GL_ERROR();
+ copy_context_.map_buffer_callback = bitmap_output ?
+ base::Bind(&MapBufferToSkBitmap, bitmap_output) :
+ base::Bind(&MapBufferToVideoFrame, video_frame_output, dst_pixel_rect);
+
// 20ms is an estimate assuming most hardware can complete asynchronous
// readback within this time limit. The timer will keep running until
// operation is completed.
- const int kIntervalMilliseconds = 20;
+ static const int kIntervalMilliseconds = 20;
copy_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kIntervalMilliseconds),
this, &CompositingIOSurfaceMac::FinishCopy);
- return true;
}
void CompositingIOSurfaceMac::FinishCopy() {
CHECK(copy_context_.started);
- TRACE_EVENT0("browser", "CompositingIOSurfaceMac::FinishCopy()");
CGLSetCurrentContext(cglContext_);
- if (copy_context_.use_fence) {
- bool copy_completed = glTestFenceAPPLE(copy_context_.fence);
+ if (copy_context_.fence) {
+ const bool copy_completed = glTestFenceAPPLE(copy_context_.fence);
CHECK_GL_ERROR();
- // Allow 1s for the operation to complete.
- const int kRetryCycles = 50;
+ // Allow up to 1s for the operation to complete.
+ const int kMaxRetryCycles = 50;
- if (!copy_completed && copy_context_.cycles_elapsed < kRetryCycles) {
+ if (!copy_completed && copy_context_.cycles_elapsed < kMaxRetryCycles) {
++copy_context_.cycles_elapsed;
CGLSetCurrentContext(0);
return;
}
}
copy_timer_.Stop();
- glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context_.pixel_buffer);
- CHECK_GL_ERROR();
- void* buf = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB);
- CHECK_GL_ERROR();
+ bool success = true;
+ for (int i = 0; success && i < copy_context_.num_outputs; ++i) {
+ TRACE_EVENT1(
+ "browser", "CompositingIOSurfaceMac::FinishCopy()",
+ "plane", i);
- base::Closure finish_callback = copy_context_.map_buffer_callback.Run(buf);
- if (buf) {
+ glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, copy_context_.pixel_buffers[i]);
+ CHECK_GL_ERROR();
+
+ void* buf = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB);
+ CHECK_GL_ERROR();
+ success &= copy_context_.map_buffer_callback.Run(buf, i);
glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB); CHECK_GL_ERROR();
}
+
glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); CHECK_GL_ERROR();
+ // Grab the done_callback here, before CleanupResourcesForCopy() clears it.
+ const base::Callback<void(bool)> done_callback = copy_context_.done_callback;
+
CleanupResourcesForCopy();
CGLSetCurrentContext(0);
- finish_callback.Run();
+ DCHECK(!done_callback.is_null());
+ done_callback.Run(success);
+}
+
+bool CompositingIOSurfaceMac::SynchronousReadbackForCopy(
+ const gfx::Rect& dst_pixel_rect,
+ const SkBitmap* bitmap_output,
+ const scoped_refptr<media::VideoFrame>& video_frame_output) {
+ bool success = true;
+ glGenFramebuffersEXT(copy_context_.num_outputs, copy_context_.frame_buffers);
+ CHECK_GL_ERROR();
+ for (int i = 0; i < copy_context_.num_outputs; ++i) {
+ TRACE_EVENT1(
+ "browser", "CompositingIOSurfaceMac::SynchronousReadbackForCopy()",
+ "plane", i);
+
+ // Attach the output texture to the FBO.
+ glBindFramebufferEXT(
+ GL_READ_FRAMEBUFFER_EXT, copy_context_.frame_buffers[i]);
+ glFramebufferTexture2DEXT(
+ GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_RECTANGLE_ARB, copy_context_.output_textures[i], 0);
+ DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+
+ // Blocking read-back of pixels from textures.
+ void* buf;
+ // When data must be transferred into a VideoFrame one scanline at a time,
+ // it is necessary to allocate a separate buffer for glReadPixels() that can
+ // be populated one-shot.
+ //
+ // TODO(miu): Don't keep allocating/deleting this buffer for every frame.
+ // Keep it cached, allocated on first use.
+ scoped_ptr<uint32[]> temp_readback_buffer;
+ if (bitmap_output) {
+ // The entire SkBitmap is populated, never a region within. So, read the
+ // texture directly into the bitmap's pixel memory.
+ buf = bitmap_output->getPixels();
+ } else {
+ // Optimization: If the VideoFrame is letterboxed (not pillarboxed), and
+ // its stride is equal to the stride of the data being read back, then
+ // readback directly into the VideoFrame's buffer to save a round of
+ // memcpy'ing.
+ //
+ // TODO(miu): Move these calculations into VideoFrame (need a CalcOffset()
+ // method). http://crbug.com/219779
+ const int src_stride = copy_context_.output_texture_sizes[i].width() * 4;
+ const int dst_stride = video_frame_output->stride(i);
+ if (src_stride == dst_stride && dst_pixel_rect.x() == 0) {
+ const int y_offset = dst_pixel_rect.y() / (i == 0 ? 1 : 2);
+ buf = video_frame_output->data(i) + y_offset * dst_stride;
+ } else {
+ // Create and readback into a temporary buffer because the data must be
+ // transferred to VideoFrame's pixel memory one scanline at a time.
+ temp_readback_buffer.reset(
+ new uint32[copy_context_.output_texture_sizes[i].GetArea()]);
+ buf = temp_readback_buffer.get();
+ }
+ }
+ glReadPixels(0, 0,
+ copy_context_.output_texture_sizes[i].width(),
+ copy_context_.output_texture_sizes[i].height(),
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+ buf); CHECK_GL_ERROR();
+ if (video_frame_output) {
+ if (!temp_readback_buffer) {
+ // Apply letterbox black-out around view region.
+ media::LetterboxYUV(video_frame_output, dst_pixel_rect);
+ } else {
+ // Copy from temporary buffer and fully render the VideoFrame.
+ success &= MapBufferToVideoFrame(video_frame_output, dst_pixel_rect,
+ temp_readback_buffer.get(), i);
+ }
+ }
+ }
+
+ glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); CHECK_GL_ERROR();
+
+ return success;
}
void CompositingIOSurfaceMac::CleanupResourcesForCopy() {
if (!copy_context_.started)
return;
- glDeleteFramebuffersEXT(1, &copy_context_.frame_buffer); CHECK_GL_ERROR();
- glDeleteTextures(1, &copy_context_.frame_buffer_texture); CHECK_GL_ERROR();
- glDeleteBuffers(1, &copy_context_.pixel_buffer); CHECK_GL_ERROR();
- if (copy_context_.use_fence) {
+ glDeleteFramebuffersEXT(copy_context_.num_outputs,
+ copy_context_.frame_buffers); CHECK_GL_ERROR();
+ glDeleteTextures(copy_context_.num_outputs, copy_context_.output_textures);
+ CHECK_GL_ERROR();
+
+ // For an asynchronous read-back, there are more objects to delete:
+ if (copy_context_.fence) {
+ glDeleteBuffers(copy_context_.num_outputs, copy_context_.pixel_buffers);
+ CHECK_GL_ERROR();
glDeleteFencesAPPLE(1, &copy_context_.fence); CHECK_GL_ERROR();
}
+
copy_context_.Reset();
}
diff --git a/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.cc b/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.cc
new file mode 100644
index 0000000..40575df
--- /dev/null
+++ b/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.cc
@@ -0,0 +1,407 @@
+// 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_shader_programs_mac.h"
+
+#include <string>
+#include <OpenGL/gl.h>
+
+#include "base/basictypes.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace content {
+
+namespace {
+
+// Convenience macro allowing GLSL programs to be specified inline, and to be
+// automatically converted into string form by the C preprocessor. As required
+// by the spec, add the version directive to the beginning of each program to
+// activate the expected syntax and built-in features. GLSL version 1.2 is the
+// latest version supported by MacOS 10.6.
+#define GLSL_PROGRAM_AS_STRING(shader_code) "#version 120\n" #shader_code
+
+
+// Only the bare-bones calculations here for speed.
+const char kvsBlit[] = GLSL_PROGRAM_AS_STRING(
+ varying vec2 texture_coord;
+ void main() {
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+ texture_coord = gl_MultiTexCoord0.xy;
+ }
+);
+
+// Just samples the texture.
+const char kfsBlit[] = GLSL_PROGRAM_AS_STRING(
+ uniform sampler2DRect texture_;
+ varying vec2 texture_coord;
+ void main() {
+ gl_FragColor = vec4(texture2DRect(texture_, texture_coord).rgb, 1.0);
+ }
+);
+
+
+// Only calculates position.
+const char kvsSolidWhite[] = GLSL_PROGRAM_AS_STRING(
+ void main() {
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+ }
+);
+
+// Always white.
+const char kfsSolidWhite[] = GLSL_PROGRAM_AS_STRING(
+ void main() {
+ gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
+ }
+);
+
+
+///////////////////////////////////////////////////////////////////////
+// RGB24 to YV12 in two passes; writing two 8888 targets each pass.
+//
+// YV12 is full-resolution luma and half-resolution blue/red chroma.
+//
+// (original)
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// XRGB XRGB XRGB XRGB XRGB XRGB XRGB XRGB
+// |
+// | (y plane) (temporary)
+// | YYYY YYYY UUVV UUVV
+// +--> { YYYY YYYY + UUVV UUVV }
+// YYYY YYYY UUVV UUVV
+// First YYYY YYYY UUVV UUVV
+// pass YYYY YYYY UUVV UUVV
+// YYYY YYYY UUVV UUVV
+// |
+// | (u plane) (v plane)
+// Second | UUUU VVVV
+// pass +--> { UUUU + VVVV }
+// UUUU VVVV
+//
+///////////////////////////////////////////////////////////////////////
+
+// Phase one of RGB24->YV12 conversion: vsFetch4Pixels/fsConvertRGBtoY8UV44
+//
+// Writes four source pixels at a time to a full-size Y plane and a half-width
+// interleaved UV plane. After execution, the Y plane is complete but the UV
+// planes still need to be de-interleaved and vertically scaled.
+const char kRGBtoYV12_vsFetch4Pixels[] = GLSL_PROGRAM_AS_STRING(
+ uniform float texel_scale_x_;
+ varying vec2 texture_coord0;
+ varying vec2 texture_coord1;
+ varying vec2 texture_coord2;
+ varying vec2 texture_coord3;
+ void main() {
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+
+ vec2 texcoord_base = gl_MultiTexCoord0.xy;
+ vec2 one_texel_x = vec2(texel_scale_x_, 0.0);
+ texture_coord0 = texcoord_base - 1.5 * one_texel_x;
+ texture_coord1 = texcoord_base - 0.5 * one_texel_x;
+ texture_coord2 = texcoord_base + 0.5 * one_texel_x;
+ texture_coord3 = texcoord_base + 1.5 * one_texel_x;
+ }
+);
+
+const char kRGBtoYV12_fsConvertRGBtoY8UV44[] = GLSL_PROGRAM_AS_STRING(
+ const vec3 rgb_to_y = vec3(0.257, 0.504, 0.098);
+ const vec3 rgb_to_u = vec3(-0.148, -0.291, 0.439);
+ const vec3 rgb_to_v = vec3(0.439, -0.368, -0.071);
+ const float y_bias = 0.0625;
+ const float uv_bias = 0.5;
+ uniform sampler2DRect texture_;
+ varying vec2 texture_coord0;
+ varying vec2 texture_coord1;
+ varying vec2 texture_coord2;
+ varying vec2 texture_coord3;
+ void main() {
+ // Load the four texture samples.
+ vec3 pixel0 = texture2DRect(texture_, texture_coord0).rgb;
+ vec3 pixel1 = texture2DRect(texture_, texture_coord1).rgb;
+ vec3 pixel2 = texture2DRect(texture_, texture_coord2).rgb;
+ vec3 pixel3 = texture2DRect(texture_, texture_coord3).rgb;
+
+ // RGB -> Y conversion (x4).
+ vec4 yyyy = vec4(dot(pixel0, rgb_to_y),
+ dot(pixel1, rgb_to_y),
+ dot(pixel2, rgb_to_y),
+ dot(pixel3, rgb_to_y)) + y_bias;
+
+ // Average adjacent texture samples while converting RGB->UV. This is the
+ // same as color converting then averaging, but slightly less math. These
+ // values will be in the range [-0.439f, +0.439f] and still need to have
+ // the bias term applied.
+ vec3 blended_pixel0 = pixel0 + pixel1;
+ vec3 blended_pixel1 = pixel2 + pixel3;
+ vec2 uu = vec2(dot(blended_pixel0, rgb_to_u),
+ dot(blended_pixel1, rgb_to_u)) / 2.0;
+ vec2 vv = vec2(dot(blended_pixel0, rgb_to_v),
+ dot(blended_pixel1, rgb_to_v)) / 2.0;
+
+ // Note: Packaging the result to account for BGRA byte ordering.
+ gl_FragData[0] = yyyy.bgra;
+ gl_FragData[1] = vec4(uu, vv) + uv_bias;
+ }
+);
+
+// Phase two of RGB24->YV12 conversion: vsFetch2Pixels/fsConvertUV44toU2V2
+//
+// Deals with UV only. Input is two UUVV quads. The pixels have already been
+// scaled horizontally prior to this point, and vertical scaling will now happen
+// via bilinear interpolation during texture sampling. Output is two color
+// planes U and V, packed four pixels to a "RGBA" quad.
+const char kRGBtoYV12_vsFetch2Pixels[] = GLSL_PROGRAM_AS_STRING(
+ varying vec2 texture_coord0;
+ varying vec2 texture_coord1;
+ void main() {
+ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+
+ vec2 texcoord_base = gl_MultiTexCoord0.xy;
+ texture_coord0 = texcoord_base - vec2(0.5, 0.0);
+ texture_coord1 = texcoord_base + vec2(0.5, 0.0);
+ }
+);
+
+const char kRGBtoYV12_fsConvertUV44toU2V2[] = GLSL_PROGRAM_AS_STRING(
+ uniform sampler2DRect texture_;
+ varying vec2 texture_coord0;
+ varying vec2 texture_coord1;
+ void main() {
+ // We're just sampling two pixels and unswizzling them. There's no need
+ // to do vertical scaling with math, since bilinear interpolation in the
+ // sampler takes care of that.
+ vec4 lo_uuvv = texture2DRect(texture_, texture_coord0);
+ vec4 hi_uuvv = texture2DRect(texture_, texture_coord1);
+ // Note: Packaging the result to account for BGRA byte ordering.
+ gl_FragData[0] = vec4(lo_uuvv.rg, hi_uuvv.rg).bgra;
+ gl_FragData[1] = vec4(lo_uuvv.ba, hi_uuvv.ba).bgra;
+ }
+);
+
+
+enum ShaderProgram {
+ SHADER_PROGRAM_BLIT = 0,
+ SHADER_PROGRAM_SOLID_WHITE,
+ SHADER_PROGRAM_RGB_TO_YV12__1_OF_2,
+ SHADER_PROGRAM_RGB_TO_YV12__2_OF_2,
+ NUM_SHADER_PROGRAMS
+};
+
+// The code snippets that together make up an entire vertex shader program.
+const char* kVertexShaderSourceCodeMap[] = {
+ // SHADER_PROGRAM_BLIT
+ kvsBlit,
+ // SHADER_PROGRAM_SOLID_WHITE
+ kvsSolidWhite,
+
+ // SHADER_PROGRAM_RGB_TO_YV12__1_OF_2
+ kRGBtoYV12_vsFetch4Pixels,
+ // SHADER_PROGRAM_RGB_TO_YV12__2_OF_2
+ kRGBtoYV12_vsFetch2Pixels,
+};
+
+// The code snippets that together make up an entire fragment shader program.
+const char* kFragmentShaderSourceCodeMap[] = {
+ // SHADER_PROGRAM_BLIT
+ kfsBlit,
+ // SHADER_PROGRAM_SOLID_WHITE
+ kfsSolidWhite,
+
+ // SHADER_PROGRAM_RGB_TO_YV12__1_OF_2
+ kRGBtoYV12_fsConvertRGBtoY8UV44,
+ // SHADER_PROGRAM_RGB_TO_YV12__2_OF_2
+ kRGBtoYV12_fsConvertUV44toU2V2,
+};
+
+GLuint CompileShaderGLSL(ShaderProgram shader_program, GLenum shader_type) {
+ TRACE_EVENT2("gpu", "CompileShaderGLSL",
+ "program", shader_program,
+ "type", shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment");
+
+ DCHECK_GE(shader_program, 0);
+ DCHECK_LT(shader_program, NUM_SHADER_PROGRAMS);
+
+ const GLuint shader = glCreateShader(shader_type);
+ DCHECK_NE(shader, 0u);
+
+ // Select and compile the shader program source code.
+ glShaderSource(shader,
+ 1,
+ (shader_type == GL_VERTEX_SHADER ? kVertexShaderSourceCodeMap :
+ kFragmentShaderSourceCodeMap) + shader_program,
+ NULL);
+ glCompileShader(shader);
+
+ // Check for successful compilation. On error in debug builds, pull the info
+ // log and emit the compiler messages.
+ GLint error;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &error);
+ if (error != GL_TRUE) {
+#ifndef NDEBUG
+ static const int kMaxInfoLogLength = 8192;
+ scoped_ptr<char[]> buffer(new char[kMaxInfoLogLength]);
+ GLsizei length_returned = 0;
+ glGetShaderInfoLog(shader, kMaxInfoLogLength - 1, &length_returned,
+ buffer.get());
+ buffer[kMaxInfoLogLength - 1] = '\0';
+ DLOG(ERROR) << "Failed to compile "
+ << (shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment")
+ << " shader for program " << shader_program << ":\n"
+ << buffer.get()
+ << (length_returned >= kMaxInfoLogLength ?
+ "\n*** TRUNCATED! ***" : "");
+#endif
+ glDeleteShader(shader);
+ return 0;
+ }
+
+ // Success!
+ return shader;
+}
+
+GLuint CompileAndLinkProgram(ShaderProgram which) {
+ TRACE_EVENT1("gpu", "CompileAndLinkProgram", "program", which);
+
+ // Compile and link a new shader program.
+ const GLuint vertex_shader = CompileShaderGLSL(which, GL_VERTEX_SHADER);
+ const GLuint fragment_shader = CompileShaderGLSL(which, GL_FRAGMENT_SHADER);
+ const GLuint program = glCreateProgram();
+ DCHECK_NE(program, 0u);
+ glAttachShader(program, vertex_shader);
+ glAttachShader(program, fragment_shader);
+ glLinkProgram(program);
+
+ // Flag shaders for deletion so that they will be deleted automatically when
+ // the program is later deleted.
+ glDeleteShader(vertex_shader);
+ glDeleteShader(fragment_shader);
+
+ // Check that the program successfully linked.
+ GLint error = GL_FALSE;
+ glGetProgramiv(program, GL_LINK_STATUS, &error);
+ if (error != GL_TRUE) {
+ glDeleteProgram(program);
+ return 0;
+ }
+ return program;
+}
+
+} // namespace
+
+
+CompositingIOSurfaceShaderPrograms::CompositingIOSurfaceShaderPrograms() {
+ COMPILE_ASSERT(kNumShaderPrograms == NUM_SHADER_PROGRAMS,
+ header_constant_disagrees_with_enum);
+ COMPILE_ASSERT(arraysize(kVertexShaderSourceCodeMap) == NUM_SHADER_PROGRAMS,
+ vertex_shader_source_code_map_incorrect_size);
+ COMPILE_ASSERT(arraysize(kFragmentShaderSourceCodeMap) == NUM_SHADER_PROGRAMS,
+ fragment_shader_source_code_map_incorrect_size);
+
+ memset(shader_programs_, 0, sizeof(shader_programs_));
+ for (size_t i = 0; i < arraysize(texture_var_locations_); ++i)
+ texture_var_locations_[i] = -1;
+ for (size_t i = 0; i < arraysize(texel_scale_x_var_locations_); ++i)
+ texel_scale_x_var_locations_[i] = -1;
+}
+
+CompositingIOSurfaceShaderPrograms::~CompositingIOSurfaceShaderPrograms() {
+#ifndef NDEBUG
+ for (size_t i = 0; i < arraysize(shader_programs_); ++i)
+ DCHECK_EQ(shader_programs_[i], 0u) << "Failed to call Reset().";
+#endif
+}
+
+void CompositingIOSurfaceShaderPrograms::Reset() {
+ for (size_t i = 0; i < arraysize(shader_programs_); ++i) {
+ if (shader_programs_[i] != 0u) {
+ glDeleteProgram(shader_programs_[i]);
+ DCHECK(glGetError() == GL_NO_ERROR)
+ << "when calling glDeleteProgram(shader_programs_[" << i << "])";
+ shader_programs_[i] = 0u;
+ }
+ }
+ for (size_t i = 0; i < arraysize(texture_var_locations_); ++i)
+ texture_var_locations_[i] = -1;
+ for (size_t i = 0; i < arraysize(texel_scale_x_var_locations_); ++i)
+ texel_scale_x_var_locations_[i] = -1;
+}
+
+bool CompositingIOSurfaceShaderPrograms::UseBlitProgram(
+ int texture_unit_offset) {
+ const GLuint program = GetShaderProgram(SHADER_PROGRAM_BLIT);
+ if (program == 0u)
+ return false;
+ glUseProgram(program);
+ BindUniformTextureVariable(SHADER_PROGRAM_BLIT, texture_unit_offset);
+ return true;
+}
+
+bool CompositingIOSurfaceShaderPrograms::UseSolidWhiteProgram() {
+ const GLuint program = GetShaderProgram(SHADER_PROGRAM_SOLID_WHITE);
+ if (program == 0u)
+ return false;
+ glUseProgram(program);
+ return true;
+}
+
+bool CompositingIOSurfaceShaderPrograms::UseRGBToYV12Program(
+ int pass_number, int texture_unit_offset, float texel_scale_x) {
+ const int which = SHADER_PROGRAM_RGB_TO_YV12__1_OF_2 + pass_number - 1;
+ DCHECK_GE(which, SHADER_PROGRAM_RGB_TO_YV12__1_OF_2);
+ DCHECK_LE(which, SHADER_PROGRAM_RGB_TO_YV12__2_OF_2);
+
+ const GLuint program = GetShaderProgram(which);
+ if (program == 0u)
+ return false;
+ glUseProgram(program);
+ BindUniformTextureVariable(which, texture_unit_offset);
+ if (which == SHADER_PROGRAM_RGB_TO_YV12__1_OF_2) {
+ BindUniformTexelScaleXVariable(which, texel_scale_x);
+ } else {
+ // The second pass doesn't have a texel_scale_x uniform variable since it's
+ // never supposed to be doing any scaling (i.e., outside of the usual
+ // 2x2-->1x1 that's already built into the process).
+ DCHECK_EQ(texel_scale_x, 1.0f);
+ }
+ return true;
+}
+
+GLuint CompositingIOSurfaceShaderPrograms::GetShaderProgram(int which) {
+ if (shader_programs_[which] == 0u) {
+ shader_programs_[which] =
+ CompileAndLinkProgram(static_cast<ShaderProgram>(which));
+ DCHECK_NE(shader_programs_[which], 0u)
+ << "Failed to create ShaderProgram " << which;
+ }
+ return shader_programs_[which];
+}
+
+void CompositingIOSurfaceShaderPrograms::BindUniformTextureVariable(
+ int which, int texture_unit_offset) {
+ if (texture_var_locations_[which] == -1) {
+ texture_var_locations_[which] =
+ glGetUniformLocation(GetShaderProgram(which), "texture_");
+ DCHECK_NE(texture_var_locations_[which], -1)
+ << "Failed to find location of uniform variable: texture_";
+ }
+ glUniform1i(texture_var_locations_[which], texture_unit_offset);
+}
+
+void CompositingIOSurfaceShaderPrograms::BindUniformTexelScaleXVariable(
+ int which, float texel_scale_x) {
+ if (texel_scale_x_var_locations_[which] == -1) {
+ texel_scale_x_var_locations_[which] =
+ glGetUniformLocation(GetShaderProgram(which), "texel_scale_x_");
+ DCHECK_NE(texel_scale_x_var_locations_[which], -1)
+ << "Failed to find location of uniform variable: texel_scale_x_";
+ }
+ glUniform1f(texel_scale_x_var_locations_[which], texel_scale_x);
+}
+
+} // namespace content
diff --git a/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h b/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h
new file mode 100644
index 0000000..ce6bfa9
--- /dev/null
+++ b/content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h
@@ -0,0 +1,62 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_SHADER_PROGRAMS_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_SHADER_PROGRAMS_MAC_H_
+
+#include <OpenGL/gl.h>
+
+#include "base/basictypes.h"
+
+namespace content {
+
+// Provides caching of the compile-and-link step for shader programs at runtime
+// since, once compiled and linked, the programs can be shared. Callers invoke
+// one of the UseXXX() methods within a GL context to glUseProgram() the program
+// and have its uniform variables bound with the given parameters.
+class CompositingIOSurfaceShaderPrograms {
+ public:
+ CompositingIOSurfaceShaderPrograms();
+ ~CompositingIOSurfaceShaderPrograms();
+
+ // Reset the cache, deleting any references to currently-cached shader
+ // programs. This must be called within an active OpenGL context just before
+ // destruction.
+ void Reset();
+
+ // Begin using the "blit" program, which is set up to sample the texture at
+ // GL_TEXTURE_0 + |texture_unit_offset|. Returns false on error.
+ bool UseBlitProgram(int texture_unit_offset);
+
+ // Begin using the program that just draws solid white very efficiently.
+ // Returns false on error.
+ bool UseSolidWhiteProgram();
+
+ // Begin using one of the two RGB-to-YV12 color conversion programs, as
+ // specified by |pass_number| 1 or 2. The programs will sample the texture at
+ // GL_TEXTURE0 + |texture_unit_offset|, and account for scaling in the X
+ // direction by |texel_scale_x|. Returns false on error.
+ bool UseRGBToYV12Program(
+ int pass_number, int texture_unit_offset, float texel_scale_x);
+
+ private:
+ enum { kNumShaderPrograms = 4 };
+
+ // Helper methods to cache uniform variable locations.
+ GLuint GetShaderProgram(int which);
+ void BindUniformTextureVariable(int which, int texture_unit_offset);
+ void BindUniformTexelScaleXVariable(int which, float texel_scale_x);
+
+ // Cached values for previously-compiled/linked shader programs, and the
+ // locations of their uniform variables.
+ GLuint shader_programs_[kNumShaderPrograms];
+ GLint texture_var_locations_[kNumShaderPrograms];
+ GLint texel_scale_x_var_locations_[kNumShaderPrograms];
+
+ DISALLOW_COPY_AND_ASSIGN(CompositingIOSurfaceShaderPrograms);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_SHADER_PROGRAMS_MAC_H_
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 0000000..be9d264
--- /dev/null
+++ b/content/browser/renderer_host/compositing_iosurface_transformer_mac.cc
@@ -0,0 +1,320 @@
+// 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 {
+
+// Simple auto-delete scoping support for an owned Framebuffer object.
+class ScopedFramebuffer {
+ public:
+ ScopedFramebuffer() {
+ glGenFramebuffersEXT(1, &name_);
+ }
+
+ ~ScopedFramebuffer() {
+ if (name_ != 0u)
+ glDeleteFramebuffersEXT(1, &name_);
+ }
+
+ bool is_valid() const { return name_ != 0u; }
+ GLuint name() const { return name_; }
+
+ private:
+ GLuint name_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedFramebuffer);
+};
+
+// Simple auto-delete scoping support for an owned texture object.
+class ScopedTexture {
+ public:
+ ScopedTexture() : name_(0u) {}
+ ScopedTexture(GLenum target, const gfx::Size& size);
+
+ ~ScopedTexture() {
+ if (name_ != 0u)
+ glDeleteTextures(1, &name_);
+ }
+
+ bool is_valid() const { return name_ != 0u; }
+ GLuint name() const { return name_; }
+
+ void Reset(GLuint texture) {
+ if (name_ != 0u)
+ glDeleteTextures(1, &name_);
+ name_ = texture;
+ }
+
+ GLuint Release() {
+ GLuint ret = name_;
+ name_ = 0u;
+ return ret;
+ }
+
+ private:
+ GLuint name_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedTexture);
+};
+
+ScopedTexture::ScopedTexture(GLenum target, const gfx::Size& size) {
+ glGenTextures(1, &name_);
+ glBindTexture(target, name_);
+ glTexImage2D(target, 0, GL_RGBA, size.width(), size.height(), 0, GL_BGRA,
+ GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
+ DCHECK(glGetError() == GL_NO_ERROR);
+ glBindTexture(target, 0u);
+}
+
+// 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, GLint texture_unit, bool src_texture_needs_y_flip,
+ CompositingIOSurfaceShaderPrograms* shader_program_cache)
+ : texture_target_(texture_target),
+ texture_unit_(texture_unit),
+ src_texture_needs_y_flip_(src_texture_needs_y_flip),
+ shader_program_cache_(shader_program_cache) {
+ DCHECK(texture_target_ == GL_TEXTURE_RECTANGLE_ARB)
+ << "Fragment shaders currently only support RECTANGLE textures.";
+ DCHECK(shader_program_cache_);
+
+ // 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() {
+}
+
+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 + texture_unit_);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_BLEND);
+
+ ScopedTexture dst_texture(texture_target_, dst_size);
+ if (!dst_texture.is_valid())
+ return false;
+
+ ScopedFramebuffer temp_frame_buffer;
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, temp_frame_buffer.name());
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ texture_target_, dst_texture.name(), 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(texture_unit_);
+ 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_, 0u);
+
+ *texture = dst_texture.Release();
+ 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 + texture_unit_);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_BLEND);
+
+ // Allocate output textures for each plane, and the temporary one for the UUVV
+ // 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);
+ ScopedTexture temp_texture_y(texture_target_, *packed_y_size);
+ if (!temp_texture_y.is_valid())
+ return false;
+ ScopedTexture temp_texture_u(texture_target_, *packed_uv_size);
+ if (!temp_texture_u.is_valid())
+ return false;
+ ScopedTexture temp_texture_v(texture_target_, *packed_uv_size);
+ if (!temp_texture_v.is_valid())
+ return false;
+
+ // Create a temporary texture for the UUVV that becomes an input into pass #2.
+ ScopedTexture temp_texture_uuvv(texture_target_, *packed_y_size);
+ if (!temp_texture_uuvv.is_valid())
+ return false;
+
+ // Create a temporary FBO for writing to the textures off-screen.
+ ScopedFramebuffer temp_frame_buffer;
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, temp_frame_buffer.name());
+
+ /////////////////////////////////////////
+ // Pass 1: RGB --(scaled)--> YYYY + UUVV
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ texture_target_, temp_texture_y.name(), 0);
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
+ texture_target_, temp_texture_uuvv.name(), 0);
+ DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+ static const GLenum kAttachments[] =
+ { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
+ glDrawBuffers(2, kAttachments);
+
+ // 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, texture_unit_,
+ 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_, temp_texture_u.name(), 0);
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
+ texture_target_, temp_texture_v.name(), 0);
+ DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+
+ // Read from texture_uuvv. The second pass uses bilinear minification to
+ // achieve vertical scaling, so enable it always.
+ glBindTexture(texture_target_, temp_texture_uuvv.name());
+ 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, texture_unit_, 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);
+ glBindTexture(texture_target_, 0);
+
+ // Before leaving, put back to drawing to a single rendering output.
+ glDrawBuffers(1, kAttachments);
+
+ *texture_y = temp_texture_y.Release();
+ *texture_u = temp_texture_u.Release();
+ *texture_v = temp_texture_v.Release();
+ return true;
+}
+
+} // namespace content
diff --git a/content/browser/renderer_host/compositing_iosurface_transformer_mac.h b/content/browser/renderer_host/compositing_iosurface_transformer_mac.h
new file mode 100644
index 0000000..c38c0e0
--- /dev/null
+++ b/content/browser/renderer_host/compositing_iosurface_transformer_mac.h
@@ -0,0 +1,93 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_TRANSFORMER_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_TRANSFORMER_MAC_H_
+
+#include <OpenGL/gl.h>
+
+#include "base/basictypes.h"
+#include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
+
+namespace gfx {
+class Rect;
+class Size;
+} // namespace gfx
+
+namespace content {
+
+// Provides useful image filtering operations that are implemented efficiently
+// using OpenGL shader programs.
+//
+// Note: All methods assume to be called within an active OpenGL context.
+class CompositingIOSurfaceTransformer {
+ public:
+ // Construct a transformer that always uses the given parameters for texture
+ // bindings. |texture_target| is one of the valid enums to use with
+ // glBindTexture(). |texture_unit| selects the texture unit to be used when
+ // running the shader programs (i.e., specify 0 for GL_TEXTURE0).
+ // |src_texture_needs_y_flip| is true when the |src_texture| argument to any
+ // of the methods below uses upside-down Y coordinates.
+ // |shader_program_cache| is not owned by this instance.
+ CompositingIOSurfaceTransformer(
+ GLenum texture_target, int texture_unit, bool src_texture_needs_y_flip,
+ CompositingIOSurfaceShaderPrograms* shader_program_cache);
+
+ ~CompositingIOSurfaceTransformer();
+
+ // Resize using bilinear interpolation. Returns false on error. Otherwise,
+ // the |texture| argument will point to the result and the caller is
+ // responsible for calling glDeleteTexture(*texture).
+ //
+ // If the src and dst sizes are identical, this becomes a simple copy into a
+ // new texture.
+ //
+ // Note: This implementation is faulty in that minifications by more than 2X
+ // will undergo aliasing.
+ bool ResizeBilinear(GLuint src_texture, const gfx::Rect& src_subrect,
+ const gfx::Size& dst_size, GLuint* texture);
+
+ // Color format conversion from RGB to planar YV12 (also known as YUV420).
+ //
+ // YV12 is effectively a twelve bit per pixel format consisting of a full-
+ // size y (luminance) plane and half-width, half-height u and v (blue and
+ // red chrominance) planes. This method will allocate three off-screen
+ // textures, one for each plane, and return them via the output arguments
+ // |texture_y|, |texture_u|, and |texture_v|. While the textures are in
+ // GL_RGBA format, they should be interpreted as the appropriate single-byte,
+ // planar format after reading the pixel data. The output arguments
+ // |packed_y_size| and |packed_uv_size| follow from these special semantics:
+ // They represent the size of their corresponding texture, if it was to be
+ // treated like RGBA pixel data. That means their widths are in terms of
+ // "quads," where one quad contains 4 Y (or U or V) pixels.
+ //
+ // If |src_subrect|'s size does not match |dst_size|, the source will be
+ // bilinearly interpolated during conversion.
+ //
+ // Returns true if successful, and the caller is responsible for deleting the
+ // output textures.
+ bool 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);
+
+ private:
+ // Target to bind all input and output textures to (which defines the type of
+ // textures being created and read). Generally, this is
+ // GL_TEXTURE_RECTANGLE_ARB.
+ const GLenum texture_target_;
+ const int texture_unit_;
+ const bool src_texture_needs_y_flip_;
+ CompositingIOSurfaceShaderPrograms* const shader_program_cache_;
+
+ // Auto-detected and set once in the constructor.
+ bool system_supports_multiple_draw_buffers_;
+
+ DISALLOW_COPY_AND_ASSIGN(CompositingIOSurfaceTransformer);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_COMPOSITING_IOSURFACE_TRANSFORMER_MAC_H_
diff --git a/content/browser/renderer_host/compositing_iosurface_transformer_mac_unittest.cc b/content/browser/renderer_host/compositing_iosurface_transformer_mac_unittest.cc
new file mode 100644
index 0000000..110d79a
--- /dev/null
+++ b/content/browser/renderer_host/compositing_iosurface_transformer_mac_unittest.cc
@@ -0,0 +1,533 @@
+// 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 <OpenGL/CGLCurrent.h>
+#include <OpenGL/CGLRenderers.h>
+#include <OpenGL/CGLTypes.h>
+#include <OpenGL/OpenGL.h>
+#include <OpenGL/gl.h>
+#include <OpenGL/glu.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <sstream>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/browser/renderer_host/compositing_iosurface_shader_programs_mac.h"
+#include "media/base/yuv_convert.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "ui/gfx/rect.h"
+
+namespace content {
+
+#define EXPECT_NO_GL_ERROR(stmt) \
+ do { \
+ stmt; \
+ const GLenum error_code = glGetError(); \
+ EXPECT_TRUE(GL_NO_ERROR == error_code) \
+ << "for error code " << error_code \
+ << ": " << gluErrorString(error_code); \
+ } while(0)
+
+namespace {
+
+const GLenum kGLTextureTarget = GL_TEXTURE_RECTANGLE_ARB;
+
+enum RendererRestriction {
+ RESTRICTION_NONE,
+ RESTRICTION_SOFTWARE_ONLY,
+ RESTRICTION_HARDWARE_ONLY
+};
+
+bool InitializeGLContext(CGLContextObj* context,
+ RendererRestriction restriction) {
+ std::vector<CGLPixelFormatAttribute> attribs;
+ // Select off-screen renderers only.
+ attribs.push_back(kCGLPFAOffScreen);
+ // By default, the library will prefer hardware-accelerated renderers, but
+ // falls back on the software ones if necessary. However, there are use cases
+ // where we want to force a restriction (e.g., benchmarking performance).
+ if (restriction == RESTRICTION_SOFTWARE_ONLY) {
+ attribs.push_back(kCGLPFARendererID);
+ attribs.push_back(static_cast<CGLPixelFormatAttribute>(
+ kCGLRendererGenericFloatID));
+ } else if (restriction == RESTRICTION_HARDWARE_ONLY) {
+ attribs.push_back(kCGLPFAAccelerated);
+ }
+ attribs.push_back(static_cast<CGLPixelFormatAttribute>(0));
+
+ CGLPixelFormatObj format;
+ GLint num_pixel_formats = 0;
+ bool success = true;
+ if (CGLChoosePixelFormat(&attribs.front(), &format, &num_pixel_formats) !=
+ kCGLNoError) {
+ LOG(ERROR) << "Error choosing pixel format.";
+ success = false;
+ }
+ if (success && num_pixel_formats <= 0) {
+ LOG(ERROR) << "num_pixel_formats <= 0; actual value is "
+ << num_pixel_formats;
+ success = false;
+ }
+ if (success && CGLCreateContext(format, NULL, context) != kCGLNoError) {
+ LOG(ERROR) << "Error creating context.";
+ success = false;
+ }
+ CGLDestroyPixelFormat(format);
+ return success;
+}
+
+// Returns a decent test pattern for testing all of: 1) orientation, 2) scaling,
+// 3) color space conversion (e.g., 4 pixels --> one U or V pixel), and 4)
+// texture alignment/processing. Example 32x32 bitmap:
+//
+// GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
+// GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
+// GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
+// GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
+// GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
+// GGGGGGGGGGGGGGGGRRBBRRBBRRBBRRBB
+// GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
+// GGGGGGGGGGGGGGGGYYCCYYCCYYCCYYCC
+// RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
+// RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
+// YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
+// YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
+// RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
+// RRBBRRBBRRBBRRBBRRBBRRBBRRBBRRBB
+// YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
+// YYCCYYCCYYCCYYCCYYCCYYCCYYCCYYCC
+//
+// Key: G = Gray, R = Red, B = Blue, Y = Yellow, C = Cyan
+SkBitmap GenerateTestPatternBitmap(const gfx::Size& size) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
+ CHECK(bitmap.allocPixels());
+ SkAutoLockPixels lock_bitmap(bitmap);
+ bitmap.eraseColor(SK_ColorGRAY);
+ for (int y = 0; y < size.height(); ++y) {
+ uint32_t* p = bitmap.getAddr32(0, y);
+ for (int x = 0; x < size.width(); ++x, ++p) {
+ if ((x < (size.width() / 2)) && (y < (size.height() / 2)))
+ continue; // Leave upper-left quadrant gray.
+ *p = SkColorSetARGB(255,
+ x % 4 < 2 ? 255 : 0,
+ y % 4 < 2 ? 255 : 0,
+ x % 4 < 2 ? 0 : 255);
+ }
+ }
+ return bitmap;
+}
+
+// Creates a new texture consisting of the given |bitmap|.
+GLuint CreateTextureWithImage(const SkBitmap& bitmap) {
+ GLuint texture;
+ EXPECT_NO_GL_ERROR(glGenTextures(1, &texture));
+ EXPECT_NO_GL_ERROR(glBindTexture(kGLTextureTarget, texture));
+ {
+ SkAutoLockPixels lock_bitmap(bitmap);
+ EXPECT_NO_GL_ERROR(glTexImage2D(
+ kGLTextureTarget, 0, GL_RGBA, bitmap.width(), bitmap.height(), 0,
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, bitmap.getPixels()));
+ }
+ glBindTexture(kGLTextureTarget, 0);
+ return texture;
+}
+
+// Read back a texture from the GPU, returning the image data as an SkBitmap.
+SkBitmap ReadBackTexture(GLuint texture, const gfx::Size& size) {
+ SkBitmap result;
+ result.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
+ CHECK(result.allocPixels());
+
+ GLuint frame_buffer;
+ EXPECT_NO_GL_ERROR(glGenFramebuffersEXT(1, &frame_buffer));
+ EXPECT_NO_GL_ERROR(
+ glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, frame_buffer));
+ EXPECT_NO_GL_ERROR(glFramebufferTexture2DEXT(
+ GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, kGLTextureTarget,
+ texture, 0));
+ DCHECK(glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT) ==
+ GL_FRAMEBUFFER_COMPLETE_EXT);
+
+ {
+ SkAutoLockPixels lock_result(result);
+ EXPECT_NO_GL_ERROR(glReadPixels(
+ 0, 0, size.width(), size.height(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+ result.getPixels()));
+ }
+
+ EXPECT_NO_GL_ERROR(glDeleteFramebuffersEXT(1, &frame_buffer));
+
+ return result;
+}
+
+// Returns the |src_rect| region of |src| scaled to |to_size| by drawing on a
+// Skia canvas, and using bilinear filtering (just like a GPU would).
+SkBitmap ScaleBitmapWithSkia(const SkBitmap& src,
+ const gfx::Rect& src_rect,
+ const gfx::Size& to_size) {
+ SkBitmap cropped_src;
+ if (src_rect == gfx::Rect(0, 0, src.width(), src.height())) {
+ cropped_src = src;
+ } else {
+ CHECK(src.extractSubset(
+ &cropped_src,
+ SkIRect::MakeXYWH(src_rect.x(), src_rect.y(),
+ src_rect.width(), src_rect.height())));
+ }
+
+ SkBitmap result;
+ result.setConfig(cropped_src.config(), to_size.width(), to_size.height());
+ CHECK(result.allocPixels());
+
+ SkCanvas canvas(result);
+ canvas.scale(static_cast<double>(result.width()) / cropped_src.width(),
+ static_cast<double>(result.height()) / cropped_src.height());
+ SkPaint paint;
+ paint.setFilterBitmap(true); // Use bilinear filtering.
+ canvas.drawBitmap(cropped_src, 0, 0, &paint);
+
+ return result;
+}
+
+// The maximum value by which a pixel value may deviate from the expected value
+// before considering it "significantly different." This is meant to account
+// for the slight differences in filtering techniques used between the various
+// GPUs and software implementations.
+const int kDifferenceThreshold = 16;
+
+// Returns the number of pixels significantly different between |expected| and
+// |actual|.
+int ImageDifference(const SkBitmap& expected, const SkBitmap& actual) {
+ SkAutoLockPixels lock_expected(expected);
+ SkAutoLockPixels lock_actual(actual);
+
+ // Sanity-check assumed image properties.
+ DCHECK_EQ(expected.width(), actual.width());
+ DCHECK_EQ(expected.height(), actual.height());
+ DCHECK_EQ(SkBitmap::kARGB_8888_Config, expected.config());
+ DCHECK_EQ(SkBitmap::kARGB_8888_Config, actual.config());
+
+ // Compare both images.
+ int num_pixels_different = 0;
+ for (int y = 0; y < expected.height(); ++y) {
+ const uint32_t* p = expected.getAddr32(0, y);
+ const uint32_t* q = actual.getAddr32(0, y);
+ for (int x = 0; x < expected.width(); ++x, ++p, ++q) {
+ if (abs(static_cast<int>(SkColorGetR(*p)) -
+ static_cast<int>(SkColorGetR(*q))) > kDifferenceThreshold ||
+ abs(static_cast<int>(SkColorGetG(*p)) -
+ static_cast<int>(SkColorGetG(*q))) > kDifferenceThreshold ||
+ abs(static_cast<int>(SkColorGetB(*p)) -
+ static_cast<int>(SkColorGetB(*q))) > kDifferenceThreshold) {
+ ++num_pixels_different;
+ }
+ }
+ }
+
+ return num_pixels_different;
+}
+
+// Returns the number of pixels significantly different between |expected| and
+// |actual|. It is understood that |actual| contains 4-byte quads, and so we
+// may need to be ignoring a mod-4 number of pixels at the end of each of its
+// rows.
+int ImagePlaneDifference(const uint8* expected, const SkBitmap& actual,
+ const gfx::Size& dst_size) {
+ SkAutoLockPixels actual_lock(actual);
+
+ int num_pixels_different = 0;
+ for (int y = 0; y < dst_size.height(); ++y) {
+ const uint8* p = expected + y * dst_size.width();
+ const uint8* const p_end = p + dst_size.width();
+ const uint8* q =
+ reinterpret_cast<uint8*>(actual.getPixels()) + y * actual.rowBytes();
+ for (; p < p_end; ++p, ++q) {
+ if (abs(static_cast<int>(*p) - static_cast<int>(*q)) >
+ kDifferenceThreshold) {
+ ++num_pixels_different;
+ }
+ }
+ }
+
+ return num_pixels_different;
+}
+
+} // namespace
+
+// Note: All tests fixtures operate within an off-screen OpenGL context.
+class CompositingIOSurfaceTransformerTest : public testing::Test {
+ public:
+ CompositingIOSurfaceTransformerTest() {
+ // TODO(miu): Try to use RESTRICTION_NONE to speed up the execution time of
+ // unit tests, once it's established that the trybots and buildbots behave
+ // well when using the GPU.
+ CHECK(InitializeGLContext(&context_, RESTRICTION_SOFTWARE_ONLY));
+ CGLSetCurrentContext(context_);
+ shader_program_cache_.reset(new CompositingIOSurfaceShaderPrograms());
+ transformer_.reset(new CompositingIOSurfaceTransformer(
+ kGLTextureTarget, 0, false, shader_program_cache_.get()));
+ }
+
+ virtual ~CompositingIOSurfaceTransformerTest() {
+ shader_program_cache_->Reset();
+ CGLSetCurrentContext(NULL);
+ CGLDestroyContext(context_);
+ }
+
+ protected:
+ void RunResizeTest(const SkBitmap& src_bitmap, const gfx::Rect& src_rect,
+ const gfx::Size& dst_size) {
+ SCOPED_TRACE(::testing::Message()
+ << "src_rect=" << src_rect.x() << ',' << src_rect.y()
+ << ")x[" << src_rect.width() << 'x' << src_rect.height()
+ << "]; dst_size=[" << dst_size.width() << 'x'
+ << dst_size.height() << ']');
+
+ const GLuint original_texture = CreateTextureWithImage(src_bitmap);
+ EXPECT_NE(0u, original_texture);
+
+ // Do the scale operation on the GPU.
+ GLuint scaled_texture = 0u;
+ EXPECT_TRUE(transformer_->ResizeBilinear(
+ original_texture, src_rect, dst_size, &scaled_texture));
+ EXPECT_NE(0u, scaled_texture);
+ CGLFlushDrawable(context_); // Account for some buggy driver impls.
+ const SkBitmap result_bitmap = ReadBackTexture(scaled_texture, dst_size);
+
+ // Delete the textures.
+ EXPECT_NO_GL_ERROR(glDeleteTextures(1, &original_texture));
+ EXPECT_NO_GL_ERROR(glDeleteTextures(1, &scaled_texture));
+
+ // Compare the image read back to the version produced by a known-working
+ // software implementation. Allow up to 2 lines of mismatch due to how
+ // implementations disagree on resolving the processing of edges.
+ const SkBitmap expected_bitmap =
+ ScaleBitmapWithSkia(src_bitmap, src_rect, dst_size);
+ EXPECT_GE(std::max(expected_bitmap.width(), expected_bitmap.height()) * 2,
+ ImageDifference(expected_bitmap, result_bitmap));
+ }
+
+ void RunTransformRGBToYV12Test(
+ const SkBitmap& src_bitmap, const gfx::Rect& src_rect,
+ const gfx::Size& dst_size) {
+ SCOPED_TRACE(::testing::Message()
+ << "src_rect=" << src_rect.x() << ',' << src_rect.y()
+ << ")x[" << src_rect.width() << 'x' << src_rect.height()
+ << "]; dst_size=[" << dst_size.width() << 'x'
+ << dst_size.height() << ']');
+
+ const GLuint original_texture = CreateTextureWithImage(src_bitmap);
+ EXPECT_NE(0u, original_texture);
+
+ // Perform the RGB to YV12 conversion.
+ GLuint texture_y = 0u;
+ GLuint texture_u = 0u;
+ GLuint texture_v = 0u;
+ gfx::Size packed_y_size;
+ gfx::Size packed_uv_size;
+ EXPECT_TRUE(transformer_->TransformRGBToYV12(
+ original_texture, src_rect, dst_size,
+ &texture_y, &texture_u, &texture_v, &packed_y_size, &packed_uv_size));
+ EXPECT_NE(0u, texture_y);
+ EXPECT_NE(0u, texture_u);
+ EXPECT_NE(0u, texture_v);
+ EXPECT_FALSE(packed_y_size.IsEmpty());
+ EXPECT_FALSE(packed_uv_size.IsEmpty());
+
+ // Read-back the texture for each plane.
+ CGLFlushDrawable(context_); // Account for some buggy driver impls.
+ const SkBitmap result_y_bitmap = ReadBackTexture(texture_y, packed_y_size);
+ const SkBitmap result_u_bitmap = ReadBackTexture(texture_u, packed_uv_size);
+ const SkBitmap result_v_bitmap = ReadBackTexture(texture_v, packed_uv_size);
+
+ // Delete the textures.
+ EXPECT_NO_GL_ERROR(glDeleteTextures(1, &original_texture));
+ EXPECT_NO_GL_ERROR(glDeleteTextures(1, &texture_y));
+ EXPECT_NO_GL_ERROR(glDeleteTextures(1, &texture_u));
+ EXPECT_NO_GL_ERROR(glDeleteTextures(1, &texture_v));
+
+ // Compare the Y, U, and V planes read-back to the version produced by a
+ // known-working software implementation. Allow up to 2 lines of mismatch
+ // due to how implementations disagree on resolving the processing of edges.
+ const SkBitmap expected_bitmap =
+ ScaleBitmapWithSkia(src_bitmap, src_rect, dst_size);
+ const gfx::Size dst_uv_size(
+ (dst_size.width() + 1) / 2, (dst_size.height() + 1) / 2);
+ scoped_ptr<uint8[]> expected_y_plane(
+ new uint8[dst_size.width() * dst_size.height()]);
+ scoped_ptr<uint8[]> expected_u_plane(
+ new uint8[dst_uv_size.width() * dst_uv_size.height()]);
+ scoped_ptr<uint8[]> expected_v_plane(
+ new uint8[dst_uv_size.width() * dst_uv_size.height()]);
+ {
+ SkAutoLockPixels src_bitmap_lock(expected_bitmap);
+ media::ConvertRGB32ToYUV(
+ reinterpret_cast<const uint8*>(expected_bitmap.getPixels()),
+ expected_y_plane.get(), expected_u_plane.get(),
+ expected_v_plane.get(),
+ expected_bitmap.width(), expected_bitmap.height(),
+ expected_bitmap.rowBytes(),
+ dst_size.width(), (dst_size.width() + 1) / 2);
+ }
+ EXPECT_GE(
+ std::max(expected_bitmap.width(), expected_bitmap.height()) * 2,
+ ImagePlaneDifference(expected_y_plane.get(), result_y_bitmap, dst_size))
+ << " for RGB --> Y Plane";
+ EXPECT_GE(
+ std::max(expected_bitmap.width(), expected_bitmap.height()),
+ ImagePlaneDifference(expected_u_plane.get(), result_u_bitmap,
+ dst_uv_size))
+ << " for RGB --> U Plane";
+ EXPECT_GE(
+ std::max(expected_bitmap.width(), expected_bitmap.height()),
+ ImagePlaneDifference(expected_v_plane.get(), result_v_bitmap,
+ dst_uv_size))
+ << " for RGB --> V Plane";
+ }
+
+ CompositingIOSurfaceShaderPrograms* shader_program_cache() const {
+ return shader_program_cache_.get();
+ }
+
+ private:
+ CGLContextObj context_;
+ scoped_ptr<CompositingIOSurfaceShaderPrograms> shader_program_cache_;
+ scoped_ptr<CompositingIOSurfaceTransformer> transformer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompositingIOSurfaceTransformerTest);
+};
+
+// TODO(miu): Enable ALL the tests after the initial change lands. These were
+// started out DISABLED since unit tests executing on GPUs have been known to
+// sometimes cause messy flakiness problems.
+TEST_F(CompositingIOSurfaceTransformerTest,
+ DISABLED_ShaderProgramsCompileAndLink) {
+ // Attempt to use each program, binding its required uniform variables.
+ EXPECT_NO_GL_ERROR(shader_program_cache()->UseBlitProgram(0));
+ EXPECT_NO_GL_ERROR(shader_program_cache()->UseSolidWhiteProgram());
+ EXPECT_NO_GL_ERROR(shader_program_cache()->UseRGBToYV12Program(1, 0, 1.0f));
+ EXPECT_NO_GL_ERROR(shader_program_cache()->UseRGBToYV12Program(2, 0, 1.0f));
+
+ EXPECT_NO_GL_ERROR(glUseProgram(0));
+}
+
+namespace {
+
+const struct TestParameters {
+ int src_width;
+ int src_height;
+ int scaled_width;
+ int scaled_height;
+} kTestParameters[] = {
+ // Test 1:1 copies, but exposing varying pixel packing configurations.
+ { 64, 64, 64, 64 },
+ { 63, 63, 63, 63 },
+ { 62, 62, 62, 62 },
+ { 61, 61, 61, 61 },
+ { 60, 60, 60, 60 },
+ { 59, 59, 59, 59 },
+ { 58, 58, 58, 58 },
+ { 57, 57, 57, 57 },
+ { 56, 56, 56, 56 },
+
+ // Even-size, one or both dimensions upscaled.
+ { 32, 32, 64, 32 }, { 32, 32, 32, 64 }, { 32, 32, 64, 64 },
+ // Even-size, one or both dimensions downscaled by 2X.
+ { 32, 32, 16, 32 }, { 32, 32, 32, 16 }, { 32, 32, 16, 16 },
+ // Even-size, one or both dimensions downscaled by 1 pixel.
+ { 32, 32, 31, 32 }, { 32, 32, 32, 31 }, { 32, 32, 31, 31 },
+ // Even-size, one or both dimensions downscaled by 2 pixels.
+ { 32, 32, 30, 32 }, { 32, 32, 32, 30 }, { 32, 32, 30, 30 },
+ // Even-size, one or both dimensions downscaled by 3 pixels.
+ { 32, 32, 29, 32 }, { 32, 32, 32, 29 }, { 32, 32, 29, 29 },
+
+ // Odd-size, one or both dimensions upscaled.
+ { 33, 33, 66, 33 }, { 33, 33, 33, 66 }, { 33, 33, 66, 66 },
+ // Odd-size, one or both dimensions downscaled by 2X.
+ { 33, 33, 16, 33 }, { 33, 33, 33, 16 }, { 33, 33, 16, 16 },
+ // Odd-size, one or both dimensions downscaled by 1 pixel.
+ { 33, 33, 32, 33 }, { 33, 33, 33, 32 }, { 33, 33, 32, 32 },
+ // Odd-size, one or both dimensions downscaled by 2 pixels.
+ { 33, 33, 31, 33 }, { 33, 33, 33, 31 }, { 33, 33, 31, 31 },
+ // Odd-size, one or both dimensions downscaled by 3 pixels.
+ { 33, 33, 30, 33 }, { 33, 33, 33, 30 }, { 33, 33, 30, 30 },
+};
+
+} // namespace
+
+TEST_F(CompositingIOSurfaceTransformerTest, DISABLED_ResizesTexturesCorrectly) {
+ for (size_t i = 0; i < arraysize(kTestParameters); ++i) {
+ SCOPED_TRACE(::testing::Message() << "kTestParameters[" << i << ']');
+
+ const TestParameters& params = kTestParameters[i];
+ const gfx::Size src_size(params.src_width, params.src_height);
+ const gfx::Size dst_size(params.scaled_width, params.scaled_height);
+ const SkBitmap src_bitmap = GenerateTestPatternBitmap(src_size);
+
+ // Full texture resize test.
+ RunResizeTest(src_bitmap, gfx::Rect(src_size), dst_size);
+ // Subrect resize test: missing top row in source.
+ RunResizeTest(src_bitmap,
+ gfx::Rect(0, 1, params.src_width, params.src_height - 1),
+ dst_size);
+ // Subrect resize test: missing left column in source.
+ RunResizeTest(src_bitmap,
+ gfx::Rect(1, 0, params.src_width - 1, params.src_height),
+ dst_size);
+ // Subrect resize test: missing top+bottom rows, and left column in source.
+ RunResizeTest(src_bitmap,
+ gfx::Rect(1, 1, params.src_width - 1, params.src_height - 2),
+ dst_size);
+ // Subrect resize test: missing top row, and left+right columns in source.
+ RunResizeTest(src_bitmap,
+ gfx::Rect(1, 1, params.src_width - 2, params.src_height - 1),
+ dst_size);
+ }
+}
+
+TEST_F(CompositingIOSurfaceTransformerTest, DISABLED_TransformsRGBToYV12) {
+ for (size_t i = 0; i < arraysize(kTestParameters); ++i) {
+ SCOPED_TRACE(::testing::Message() << "kTestParameters[" << i << ']');
+
+ const TestParameters& params = kTestParameters[i];
+ const gfx::Size src_size(params.src_width, params.src_height);
+ const gfx::Size dst_size(params.scaled_width, params.scaled_height);
+ const SkBitmap src_bitmap = GenerateTestPatternBitmap(src_size);
+
+ // Full texture resize test.
+ RunTransformRGBToYV12Test(src_bitmap, gfx::Rect(src_size), dst_size);
+ // Subrect resize test: missing top row in source.
+ RunTransformRGBToYV12Test(
+ src_bitmap, gfx::Rect(0, 1, params.src_width, params.src_height - 1),
+ dst_size);
+ // Subrect resize test: missing left column in source.
+ RunTransformRGBToYV12Test(
+ src_bitmap, gfx::Rect(1, 0, params.src_width - 1, params.src_height),
+ dst_size);
+ // Subrect resize test: missing top+bottom rows, and left column in
+ // source.
+ RunTransformRGBToYV12Test(
+ src_bitmap,
+ gfx::Rect(1, 1, params.src_width - 1, params.src_height - 2),
+ dst_size);
+ // Subrect resize test: missing top row, and left+right columns in source.
+ RunTransformRGBToYV12Test(
+ src_bitmap,
+ gfx::Rect(1, 1, params.src_width - 2, params.src_height - 1),
+ dst_size);
+ }
+}
+
+} // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index e643071..e13bbdf 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -926,19 +926,11 @@ void RenderWidgetHostViewMac::CopyFromCompositingSurface(
gfx::Size dst_pixel_size = gfx::ToFlooredSize(
gfx::ScaleSize(dst_size, scale));
- SkBitmap output;
- output.setConfig(SkBitmap::kARGB_8888_Config,
- dst_pixel_size.width(), dst_pixel_size.height());
- if (!output.allocPixels())
- return;
- output.setIsOpaque(true);
-
scoped_callback_runner.Release();
compositing_iosurface_->CopyTo(GetScaledOpenGLPixelRect(src_subrect),
ScaleFactor(cocoa_view_),
dst_pixel_size,
- output,
callback);
}
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index 1fd17b8..8b9236c 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -638,10 +638,14 @@
'browser/renderer_host/clipboard_message_filter.cc',
'browser/renderer_host/clipboard_message_filter.h',
'browser/renderer_host/clipboard_message_filter_mac.mm',
- 'browser/renderer_host/compositor_impl_android.h',
'browser/renderer_host/compositor_impl_android.cc',
+ 'browser/renderer_host/compositor_impl_android.h',
'browser/renderer_host/compositing_iosurface_mac.h',
'browser/renderer_host/compositing_iosurface_mac.mm',
+ 'browser/renderer_host/compositing_iosurface_shader_programs_mac.cc',
+ 'browser/renderer_host/compositing_iosurface_shader_programs_mac.h',
+ 'browser/renderer_host/compositing_iosurface_transformer_mac.cc',
+ 'browser/renderer_host/compositing_iosurface_transformer_mac.h',
'browser/renderer_host/database_message_filter.cc',
'browser/renderer_host/database_message_filter.h',
'browser/renderer_host/dip_util.cc',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 883c199..be81322 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -89,6 +89,10 @@
'browser/geolocation/mock_location_arbitrator.h',
'browser/geolocation/mock_location_provider.cc',
'browser/geolocation/mock_location_provider.h',
+ 'browser/renderer_host/compositing_iosurface_shader_programs_mac.cc',
+ 'browser/renderer_host/compositing_iosurface_shader_programs_mac.h',
+ 'browser/renderer_host/compositing_iosurface_transformer_mac.cc',
+ 'browser/renderer_host/compositing_iosurface_transformer_mac.h',
'browser/renderer_host/media/mock_media_observer.cc',
'browser/renderer_host/media/mock_media_observer.h',
'browser/renderer_host/test_backing_store.cc',
@@ -300,6 +304,7 @@
'browser/media/webrtc_internals_unittest.cc',
'browser/notification_service_impl_unittest.cc',
'browser/plugin_loader_posix_unittest.cc',
+ 'browser/renderer_host/compositing_iosurface_transformer_mac_unittest.cc',
'browser/renderer_host/gtk_key_bindings_handler_unittest.cc',
'browser/renderer_host/media/audio_input_device_manager_unittest.cc',
'browser/renderer_host/media/audio_mirroring_manager_unittest.cc',