Index: remoting/host/video_frame_capturer_mac.mm |
diff --git a/remoting/host/video_frame_capturer_mac.mm b/remoting/host/video_frame_capturer_mac.mm |
index 8fec9bd255fd87cacd8388d8b6d19dc4aa8e1f50..8749181464d5f65604ebc7a350a7785f71ee084a 100644 |
--- a/remoting/host/video_frame_capturer_mac.mm |
+++ b/remoting/host/video_frame_capturer_mac.mm |
@@ -28,6 +28,7 @@ namespace remoting { |
namespace { |
+// skia/ext/skia_utils_mac.h only defines CGRectToSkRect(). |
SkIRect CGRectToSkIRect(const CGRect& rect) { |
SkIRect sk_rect = { |
SkScalarRound(rect.origin.x), |
@@ -54,18 +55,18 @@ class VideoFrameBuffer { |
// If the buffer is marked as needing to be updated (for example after the |
// screen mode changes) and is the wrong size, then release the old buffer |
// and create a new one. |
- void Update() { |
+ void Update(const SkISize& size) { |
if (needs_update_) { |
needs_update_ = false; |
- CGDirectDisplayID mainDevice = CGMainDisplayID(); |
- int width = CGDisplayPixelsWide(mainDevice); |
- int height = CGDisplayPixelsHigh(mainDevice); |
- if (width != size_.width() || height != size_.height()) { |
- size_.set(width, height); |
- bytes_per_row_ = width * sizeof(uint32_t); |
- size_t buffer_size = width * height * sizeof(uint32_t); |
+ size_t buffer_size = size.width() * size.height() * sizeof(uint32_t); |
+ if (size != size_) { |
+ size_ = size; |
+ bytes_per_row_ = size.width() * sizeof(uint32_t); |
ptr_.reset(new uint8[buffer_size]); |
} |
+ memset(ptr(), 0, buffer_size); |
+ |
+ // TODO(wez): Move the ugly DPI code into a helper. |
NSScreen* screen = [NSScreen mainScreen]; |
NSDictionary* attr = [screen deviceDescription]; |
NSSize resolution = [[attr objectForKey: NSDeviceResolution] sizeValue]; |
@@ -144,6 +145,10 @@ class VideoFrameCapturerMac : public VideoFrameCapturer { |
ScopedPixelBufferObject pixel_buffer_object_; |
VideoFrameBuffer buffers_[kNumBuffers]; |
+ // Current display configuration. |
+ std::vector<CGDirectDisplayID> display_ids_; |
+ SkIRect desktop_bounds_; |
+ |
// A thread-safe list of invalid rectangles, and the size of the most |
// recently captured screen. |
VideoFrameCapturerHelper helper_; |
@@ -291,7 +296,8 @@ void VideoFrameCapturerMac::CaptureInvalidRegion( |
SkRegion region; |
helper_.SwapInvalidRegion(®ion); |
VideoFrameBuffer& current_buffer = buffers_[current_buffer_]; |
- current_buffer.Update(); |
+ current_buffer.Update(SkISize::Make(desktop_bounds_.width(), |
+ desktop_bounds_.height())); |
bool flip = false; // GL capturers need flipping. |
if (base::mac::IsOSLionOrLater()) { |
@@ -504,35 +510,48 @@ void VideoFrameCapturerMac::GlBlitSlow(const VideoFrameBuffer& buffer) { |
void VideoFrameCapturerMac::CgBlitPreLion(const VideoFrameBuffer& buffer, |
const SkRegion& region) { |
const int buffer_height = buffer.size().height(); |
- const int buffer_width = buffer.size().width(); |
- |
- // Clip to the size of our current screen. |
- SkIRect clip_rect = SkIRect::MakeWH(buffer_width, buffer_height); |
+ // Copy the entire contents of the previous capture buffer, to capture over. |
+ // TODO(wez): Get rid of this as per crbug.com/145064, or implement |
+ // crbug.com/92354. |
if (last_buffer_) |
memcpy(buffer.ptr(), last_buffer_, buffer.bytes_per_row() * buffer_height); |
last_buffer_ = buffer.ptr(); |
- CGDirectDisplayID main_display = CGMainDisplayID(); |
+ |
+ for (unsigned int d = 0; d < display_ids_.size(); ++d) { |
#pragma clang diagnostic push |
#pragma clang diagnostic ignored "-Wdeprecated-declarations" |
- uint8* display_base_address = |
- reinterpret_cast<uint8*>(CGDisplayBaseAddress(main_display)); |
- CHECK(display_base_address); |
- int src_bytes_per_row = CGDisplayBytesPerRow(main_display); |
- int src_bytes_per_pixel = CGDisplayBitsPerPixel(main_display) / 8; |
+ uint8* display_base_address = |
+ reinterpret_cast<uint8*>(CGDisplayBaseAddress(display_ids_[d])); |
+ CHECK(display_base_address); |
+ int src_bytes_per_row = CGDisplayBytesPerRow(display_ids_[d]); |
+ int src_bytes_per_pixel = CGDisplayBitsPerPixel(display_ids_[d]) / 8; |
#pragma clang diagnostic pop |
- // TODO(hclam): We can reduce the amount of copying here by subtracting |
- // |capturer_helper_|s region from |last_invalid_region_|. |
- // http://crbug.com/92354 |
- for(SkRegion::Iterator i(region); !i.done(); i.next()) { |
- SkIRect copy_rect = i.rect(); |
- if (copy_rect.intersect(clip_rect)) { |
+ // Determine the position of the display in the buffer. |
+ SkIRect display_bounds = CGRectToSkIRect(CGDisplayBounds(display_ids_[d])); |
+ display_bounds.offset(-desktop_bounds_.left(), -desktop_bounds_.top()); |
+ |
+ // Determine which parts of the blit region, if any, lay within the monitor. |
+ SkRegion copy_region; |
+ if (!copy_region.op(region, display_bounds, SkRegion::kIntersect_Op)) |
+ continue; |
+ |
+ // Translate the region to be copied into display-relative coordinates. |
+ copy_region.translate(-display_bounds.left(), -display_bounds.top()); |
+ |
+ // Calculate where in the output buffer the display's origin is. |
+ uint8* out_ptr = buffer.ptr() + |
+ (display_bounds.left() * src_bytes_per_pixel) + |
+ (display_bounds.top() * buffer.bytes_per_row()); |
+ |
+ // Copy the dirty region from the display buffer into our desktop buffer. |
+ for(SkRegion::Iterator i(copy_region); !i.done(); i.next()) { |
CopyRect(display_base_address, |
src_bytes_per_row, |
- buffer.ptr(), |
+ out_ptr, |
buffer.bytes_per_row(), |
src_bytes_per_pixel, |
- copy_rect); |
+ i.rect()); |
} |
} |
} |
@@ -540,38 +559,57 @@ void VideoFrameCapturerMac::CgBlitPreLion(const VideoFrameBuffer& buffer, |
void VideoFrameCapturerMac::CgBlitPostLion(const VideoFrameBuffer& buffer, |
const SkRegion& region) { |
const int buffer_height = buffer.size().height(); |
- const int buffer_width = buffer.size().width(); |
- |
- // Clip to the size of our current screen. |
- SkIRect clip_rect = SkIRect::MakeWH(buffer_width, buffer_height); |
+ // Copy the entire contents of the previous capture buffer, to capture over. |
+ // TODO(wez): Get rid of this as per crbug.com/145064, or implement |
+ // crbug.com/92354. |
if (last_buffer_) |
memcpy(buffer.ptr(), last_buffer_, buffer.bytes_per_row() * buffer_height); |
last_buffer_ = buffer.ptr(); |
- CGDirectDisplayID display = CGMainDisplayID(); |
- base::mac::ScopedCFTypeRef<CGImageRef> image( |
- CGDisplayCreateImage(display)); |
- if (image.get() == NULL) |
- return; |
- CGDataProviderRef provider = CGImageGetDataProvider(image); |
- base::mac::ScopedCFTypeRef<CFDataRef> data(CGDataProviderCopyData(provider)); |
- if (data.get() == NULL) |
- return; |
- const uint8* display_base_address = CFDataGetBytePtr(data); |
- int src_bytes_per_row = CGImageGetBytesPerRow(image); |
- int src_bytes_per_pixel = CGImageGetBitsPerPixel(image) / 8; |
- // TODO(hclam): We can reduce the amount of copying here by subtracting |
- // |capturer_helper_|s region from |last_invalid_region_|. |
- // http://crbug.com/92354 |
- for(SkRegion::Iterator i(region); !i.done(); i.next()) { |
- SkIRect copy_rect = i.rect(); |
- if (copy_rect.intersect(clip_rect)) { |
+ |
+ for (unsigned int d = 0; d < display_ids_.size(); ++d) { |
+ // Determine the position of the display in the buffer. |
+ SkIRect display_bounds = CGRectToSkIRect(CGDisplayBounds(display_ids_[d])); |
+ display_bounds.offset(-desktop_bounds_.left(), -desktop_bounds_.top()); |
+ |
+ // Determine which parts of the blit region, if any, lay within the monitor. |
+ SkRegion copy_region; |
+ if (!copy_region.op(region, display_bounds, SkRegion::kIntersect_Op)) |
+ continue; |
+ |
+ // Translate the region to be copied into display-relative coordinates. |
+ copy_region.translate(-display_bounds.left(), -display_bounds.top()); |
+ |
+ // Create an image containing a snapshot of the display. |
+ base::mac::ScopedCFTypeRef<CGImageRef> image( |
+ CGDisplayCreateImage(display_ids_[d])); |
+ if (image.get() == NULL) |
+ continue; |
+ |
+ // Request access to the raw pixel data via the image's DataProvider. |
+ CGDataProviderRef provider = CGImageGetDataProvider(image); |
+ base::mac::ScopedCFTypeRef<CFDataRef> data( |
+ CGDataProviderCopyData(provider)); |
+ if (data.get() == NULL) |
+ continue; |
+ |
+ const uint8* display_base_address = CFDataGetBytePtr(data); |
+ int src_bytes_per_row = CGImageGetBytesPerRow(image); |
+ int src_bytes_per_pixel = CGImageGetBitsPerPixel(image) / 8; |
+ |
+ // Calculate where in the output buffer the display's origin is. |
+ uint8* out_ptr = buffer.ptr() + |
+ (display_bounds.left() * src_bytes_per_pixel) + |
+ (display_bounds.top() * buffer.bytes_per_row()); |
+ |
+ // Copy the dirty region from the display buffer into our desktop buffer. |
+ for(SkRegion::Iterator i(copy_region); !i.done(); i.next()) { |
CopyRect(display_base_address, |
src_bytes_per_row, |
- buffer.ptr(), |
+ out_ptr, |
buffer.bytes_per_row(), |
src_bytes_per_pixel, |
- copy_rect); |
+ i.rect()); |
} |
} |
} |
@@ -581,14 +619,33 @@ const SkISize& VideoFrameCapturerMac::size_most_recent() const { |
} |
void VideoFrameCapturerMac::ScreenConfigurationChanged() { |
+ // Release existing buffers, which will be of the wrong size. |
ReleaseBuffers(); |
- helper_.ClearInvalidRegion(); |
last_buffer_ = NULL; |
- CGDirectDisplayID mainDevice = CGMainDisplayID(); |
- int width = CGDisplayPixelsWide(mainDevice); |
- int height = CGDisplayPixelsHigh(mainDevice); |
- helper_.InvalidateScreen(SkISize::Make(width, height)); |
+ // Clear the dirty region, in case the display is down-sizing. |
+ helper_.ClearInvalidRegion(); |
+ |
+ // Fetch the list if active displays and calculate their bounds. |
+ CGDisplayCount display_count; |
+ CGError error = CGGetActiveDisplayList(0, NULL, &display_count); |
+ CHECK_EQ(error, CGDisplayNoErr); |
+ |
+ display_ids_.resize(display_count); |
+ error = CGGetActiveDisplayList(display_count, &display_ids_[0], |
+ &display_count); |
+ CHECK_EQ(error, CGDisplayNoErr); |
+ CHECK_EQ(display_count, display_ids_.size()); |
+ |
+ desktop_bounds_ = SkIRect::MakeEmpty(); |
+ for (unsigned int d = 0; d < display_count; ++d) { |
+ CGRect display_bounds = CGDisplayBounds(display_ids_[d]); |
+ desktop_bounds_.join(CGRectToSkIRect(display_bounds)); |
+ } |
+ |
+ // Re-mark the entire desktop as dirty. |
+ helper_.InvalidateScreen(SkISize::Make(desktop_bounds_.width(), |
+ desktop_bounds_.height())); |
if (base::mac::IsOSLionOrLater()) { |
LOG(INFO) << "Using CgBlitPostLion."; |
@@ -596,8 +653,14 @@ void VideoFrameCapturerMac::ScreenConfigurationChanged() { |
return; |
} |
+ if (display_ids_.size() > 1) { |
+ LOG(INFO) << "Using CgBlitPreLion (Multi-monitor)."; |
+ return; |
+ } |
+ |
+ CGDirectDisplayID mainDevice = CGMainDisplayID(); |
if (!CGDisplayUsesOpenGLAcceleration(mainDevice)) { |
- LOG(INFO) << "Using CgBlitPreLion."; |
+ LOG(INFO) << "Using CgBlitPreLion (OpenGL unavailable)."; |
return; |
} |
@@ -629,7 +692,8 @@ void VideoFrameCapturerMac::ScreenConfigurationChanged() { |
#pragma clang diagnostic pop |
CGLSetCurrentContext(cgl_context_); |
- size_t buffer_size = width * height * sizeof(uint32_t); |
+ size_t buffer_size = desktop_bounds_.width() * desktop_bounds_.height() * |
+ sizeof(uint32_t); |
pixel_buffer_object_.Init(cgl_context_, buffer_size); |
} |
@@ -638,6 +702,7 @@ void VideoFrameCapturerMac::ScreenRefresh(CGRectCount count, |
SkIRect skirect_array[count]; |
for (CGRectCount i = 0; i < count; ++i) { |
skirect_array[i] = CGRectToSkIRect(rect_array[i]); |
+ skirect_array[i].offset(-desktop_bounds_.left(), -desktop_bounds_.top()); |
} |
SkRegion region; |
region.setRects(skirect_array, count); |
@@ -647,14 +712,15 @@ void VideoFrameCapturerMac::ScreenRefresh(CGRectCount count, |
void VideoFrameCapturerMac::ScreenUpdateMove(CGScreenUpdateMoveDelta delta, |
size_t count, |
const CGRect* rect_array) { |
- SkIRect skirect_new_array[count]; |
+ SkIRect skirect_array[count]; |
for (CGRectCount i = 0; i < count; ++i) { |
CGRect rect = rect_array[i]; |
rect = CGRectOffset(rect, delta.dX, delta.dY); |
- skirect_new_array[i] = CGRectToSkIRect(rect); |
+ skirect_array[i] = CGRectToSkIRect(rect); |
+ skirect_array[i].offset(-desktop_bounds_.left(), -desktop_bounds_.top()); |
} |
SkRegion region; |
- region.setRects(skirect_new_array, count); |
+ region.setRects(skirect_array, count); |
InvalidateRegion(region); |
} |