diff options
author | miu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-21 23:07:54 +0000 |
---|---|---|
committer | miu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-21 23:07:54 +0000 |
commit | ea2e54108c072d2f7cb9b56c5bd450702a688523 (patch) | |
tree | e58f8eb26139e529ba163a55d50c2df555f3b5d8 | |
parent | 8559d45869f6a6c1c49e5cd83b83f5067bc14a53 (diff) | |
download | chromium_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
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, ©_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, ©_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(), + ©_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(), + ©_context_.output_textures[0], + ©_context_.output_textures[1], + ©_context_.output_textures[2], + ©_context_.output_texture_sizes[0], + ©_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, ©_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, ©_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, ©_context_.frame_buffer); CHECK_GL_ERROR(); - glDeleteTextures(1, ©_context_.frame_buffer_texture); CHECK_GL_ERROR(); - glDeleteBuffers(1, ©_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, ©_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', |