blob: 9c9c393148019e653bd9beb3e915133657a4d467 [file] [log] [blame]
// Copyright 2014 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/compositor/software_output_device_mac.h"
#import <Cocoa/Cocoa.h>
#include <stddef.h>
#include <stdint.h>
#include "base/mac/foundation_util.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "ui/accelerated_widget_mac/accelerated_widget_mac.h"
#include "ui/compositor/compositor.h"
#include "ui/gfx/skia_util.h"
namespace content {
SoftwareOutputDeviceMac::SoftwareOutputDeviceMac(ui::Compositor* compositor)
: compositor_(compositor), scale_factor_(1), current_index_(0) {}
SoftwareOutputDeviceMac::~SoftwareOutputDeviceMac() {
}
void SoftwareOutputDeviceMac::Resize(const gfx::Size& pixel_size,
float scale_factor) {
if (pixel_size_ == pixel_size && scale_factor_ == scale_factor)
return;
pixel_size_ = pixel_size;
scale_factor_ = scale_factor;
DiscardBackbuffer();
}
void SoftwareOutputDeviceMac::CopyPreviousBufferDamage(
const SkRegion& new_damage_region) {
TRACE_EVENT0("browser", "CopyPreviousBufferDamage");
// The region to copy is the region drawn last frame, minus the region that
// is drawn this frame.
SkRegion copy_region = previous_buffer_damage_region_;
bool copy_region_nonempty =
copy_region.op(new_damage_region, SkRegion::kDifference_Op);
previous_buffer_damage_region_ = new_damage_region;
if (!copy_region_nonempty)
return;
IOSurfaceRef previous_io_surface = io_surfaces_[!current_index_].get();
{
TRACE_EVENT0("browser", "IOSurfaceLock for software copy");
IOReturn io_result = IOSurfaceLock(
previous_io_surface, kIOSurfaceLockReadOnly | kIOSurfaceLockAvoidSync,
nullptr);
if (io_result) {
DLOG(ERROR) << "Failed to lock previous IOSurface " << io_result;
return;
}
}
uint8_t* pixels =
static_cast<uint8_t*>(IOSurfaceGetBaseAddress(previous_io_surface));
size_t stride = IOSurfaceGetBytesPerRow(previous_io_surface);
size_t bytes_per_element = 4;
for (SkRegion::Iterator it(copy_region); !it.done(); it.next()) {
const SkIRect& rect = it.rect();
canvas_->writePixels(
SkImageInfo::MakeN32Premul(rect.width(), rect.height()),
pixels + bytes_per_element * rect.x() + stride * rect.y(), stride,
rect.x(), rect.y());
}
{
TRACE_EVENT0("browser", "IOSurfaceUnlock");
IOReturn io_result = IOSurfaceUnlock(
previous_io_surface, kIOSurfaceLockReadOnly | kIOSurfaceLockAvoidSync,
nullptr);
if (io_result)
DLOG(ERROR) << "Failed to unlock previous IOSurface " << io_result;
}
}
bool SoftwareOutputDeviceMac::EnsureBuffersExist() {
for (int i = 0; i < 2; ++i) {
if (!io_surfaces_[i]) {
TRACE_EVENT0("browser", "IOSurfaceCreate");
unsigned pixel_format = 'BGRA';
unsigned bytes_per_element = 4;
NSDictionary* options = @{
static_cast<id>(kIOSurfaceWidth) : @(pixel_size_.width()),
static_cast<id>(kIOSurfaceHeight) : @(pixel_size_.height()),
static_cast<id>(kIOSurfacePixelFormat) : @(pixel_format),
static_cast<id>(kIOSurfaceBytesPerElement) : @(bytes_per_element),
};
io_surfaces_[i].reset(IOSurfaceCreate(base::mac::NSToCFCast(options)));
}
if (!io_surfaces_[i]) {
DLOG(ERROR) << "Failed to allocate IOSurface";
return false;
}
}
return true;
}
SkCanvas* SoftwareOutputDeviceMac::BeginPaint(
const gfx::Rect& new_damage_rect) {
if (!EnsureBuffersExist())
return nullptr;
{
TRACE_EVENT0("browser", "IOSurfaceLock for software paint");
IOReturn io_result = IOSurfaceLock(io_surfaces_[current_index_],
kIOSurfaceLockAvoidSync, nullptr);
if (io_result) {
DLOG(ERROR) << "Failed to lock IOSurface " << io_result;
return nullptr;
}
}
SkPMColor* pixels = static_cast<SkPMColor*>(
IOSurfaceGetBaseAddress(io_surfaces_[current_index_]));
size_t stride = IOSurfaceGetBytesPerRow(io_surfaces_[current_index_]);
canvas_ = sk_sp<SkCanvas>(SkCanvas::NewRasterDirectN32(
pixel_size_.width(), pixel_size_.height(), pixels, stride));
CopyPreviousBufferDamage(SkRegion(gfx::RectToSkIRect(new_damage_rect)));
return canvas_.get();
}
void SoftwareOutputDeviceMac::EndPaint() {
SoftwareOutputDevice::EndPaint();
{
TRACE_EVENT0("browser", "IOSurfaceUnlock");
IOReturn io_result = IOSurfaceUnlock(io_surfaces_[current_index_],
kIOSurfaceLockAvoidSync, nullptr);
if (io_result)
DLOG(ERROR) << "Failed to unlock IOSurface " << io_result;
}
canvas_.reset();
base::TimeTicks vsync_timebase;
base::TimeDelta vsync_interval;
ui::AcceleratedWidgetMacGotFrame(
compositor_->widget(), 0, io_surfaces_[current_index_], pixel_size_,
scale_factor_, &vsync_timebase, &vsync_interval);
if (!update_vsync_callback_.is_null())
update_vsync_callback_.Run(vsync_timebase, vsync_interval);
current_index_ = !current_index_;
}
void SoftwareOutputDeviceMac::DiscardBackbuffer() {
for (int i = 0; i < 2; ++i)
io_surfaces_[i].reset();
}
void SoftwareOutputDeviceMac::EnsureBackbuffer() {}
gfx::VSyncProvider* SoftwareOutputDeviceMac::GetVSyncProvider() {
return this;
}
void SoftwareOutputDeviceMac::GetVSyncParameters(
const gfx::VSyncProvider::UpdateVSyncCallback& callback) {
update_vsync_callback_ = callback;
}
} // namespace content