blob: 2910ed82564e77e93ca0cdd8981b55de7638025a [file] [log] [blame]
// Copyright 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 "components/viz/common/frame_sinks/copy_output_result.h"
#include <utility>
#include "base/check_op.h"
#include "base/notreached.h"
#include "third_party/libyuv/include/libyuv.h"
#include "third_party/skia/include/core/SkPixelRef.h"
#include "ui/gfx/color_space.h"
namespace viz {
CopyOutputResult::CopyOutputResult(Format format,
const gfx::Rect& rect,
bool needs_lock_for_bitmap)
: format_(format),
rect_(rect),
needs_lock_for_bitmap_(needs_lock_for_bitmap) {
DCHECK(format_ == Format::RGBA_BITMAP || format_ == Format::RGBA_TEXTURE ||
format_ == Format::I420_PLANES);
}
CopyOutputResult::~CopyOutputResult() = default;
bool CopyOutputResult::IsEmpty() const {
if (rect_.IsEmpty())
return true;
switch (format_) {
case Format::RGBA_BITMAP:
case Format::I420_PLANES:
return false;
case Format::RGBA_TEXTURE:
if (const TextureResult* result = GetTextureResult())
return result->mailbox.IsZero();
else
return true;
}
NOTREACHED();
return true;
}
bool CopyOutputResult::LockSkBitmap() const {
return true;
}
void CopyOutputResult::UnlockSkBitmap() const {}
const SkBitmap& CopyOutputResult::AsSkBitmap() const {
DCHECK(!cached_bitmap_.readyToDraw() || cached_bitmap_.colorSpace());
return cached_bitmap_;
}
CopyOutputResult::ScopedSkBitmap CopyOutputResult::ScopedAccessSkBitmap()
const {
return ScopedSkBitmap(this);
}
const CopyOutputResult::TextureResult* CopyOutputResult::GetTextureResult()
const {
return nullptr;
}
ReleaseCallback CopyOutputResult::TakeTextureOwnership() {
return ReleaseCallback();
}
bool CopyOutputResult::ReadI420Planes(uint8_t* y_out,
int y_out_stride,
uint8_t* u_out,
int u_out_stride,
uint8_t* v_out,
int v_out_stride) const {
auto scoped_sk_bitmap = ScopedAccessSkBitmap();
const SkBitmap& bitmap = scoped_sk_bitmap.bitmap();
if (!bitmap.readyToDraw())
return false;
const uint8_t* pixels = static_cast<uint8_t*>(bitmap.getPixels());
// The conversion below ignores color space completely, and it's not even
// sRGB→Rec.709. Unfortunately, hand-optimized routines are not available, and
// a perfect conversion using gfx::ColorTransform would execute way too
// slowly. See SoftwareRenderer for related comments on its lack of color
// space management (due to performance concerns).
if (bitmap.colorType() == kBGRA_8888_SkColorType) {
return 0 == libyuv::ARGBToI420(pixels, bitmap.rowBytes(), y_out,
y_out_stride, u_out, u_out_stride, v_out,
v_out_stride, bitmap.width(),
bitmap.height());
} else if (bitmap.colorType() == kRGBA_8888_SkColorType) {
return 0 == libyuv::ABGRToI420(pixels, bitmap.rowBytes(), y_out,
y_out_stride, u_out, u_out_stride, v_out,
v_out_stride, bitmap.width(),
bitmap.height());
}
// Other SkBitmap color types could be supported, but are currently never
// being used.
NOTIMPLEMENTED();
return false;
}
bool CopyOutputResult::ReadRGBAPlane(uint8_t* dest, int stride) const {
auto scoped_sk_bitmap = ScopedAccessSkBitmap();
const SkBitmap& bitmap = scoped_sk_bitmap.bitmap();
if (!bitmap.readyToDraw())
return false;
DCHECK(bitmap.colorSpace());
SkImageInfo image_info =
SkImageInfo::MakeN32(bitmap.width(), bitmap.height(), kPremul_SkAlphaType,
bitmap.refColorSpace());
bitmap.readPixels(image_info, dest, stride, 0, 0);
return true;
}
gfx::ColorSpace CopyOutputResult::GetRGBAColorSpace() const {
auto scoped_sk_bitmap = ScopedAccessSkBitmap();
const SkBitmap& bitmap = scoped_sk_bitmap.bitmap();
if (!bitmap.readyToDraw())
return gfx::ColorSpace();
DCHECK(bitmap.colorSpace());
return gfx::ColorSpace(*(bitmap.colorSpace()));
}
CopyOutputSkBitmapResult::CopyOutputSkBitmapResult(const gfx::Rect& rect,
SkBitmap bitmap)
: CopyOutputSkBitmapResult(Format::RGBA_BITMAP, rect, std::move(bitmap)) {}
CopyOutputSkBitmapResult::CopyOutputSkBitmapResult(
CopyOutputResult::Format format,
const gfx::Rect& rect,
SkBitmap bitmap)
: CopyOutputResult(format, rect, false) {
DCHECK(format == Format::RGBA_BITMAP || format == Format::I420_PLANES);
if (!rect.IsEmpty()) {
DCHECK(!bitmap.pixelRef() || bitmap.pixelRef()->unique());
DCHECK(!bitmap.readyToDraw() || bitmap.colorSpace());
// Hold a reference to the |bitmap|'s pixels, for AsSkBitmap().
*(cached_bitmap()) = std::move(bitmap);
}
}
const SkBitmap& CopyOutputSkBitmapResult::AsSkBitmap() const {
SkBitmap* const bitmap = cached_bitmap();
if (rect().IsEmpty())
return *bitmap; // Return "null" bitmap for empty result.
const SkImageInfo image_info = SkImageInfo::MakeN32Premul(
rect().width(), rect().height(), bitmap->refColorSpace());
if (bitmap->info() == image_info && bitmap->readyToDraw())
return *bitmap; // Return bitmap in expected format.
// The bitmap is not in the "native optimized" format. Convert it once for
// this and all future calls of this method.
SkBitmap replacement;
replacement.allocPixels(image_info);
replacement.eraseColor(SK_ColorBLACK);
SkPixmap src_pixmap;
if (bitmap->peekPixels(&src_pixmap)) {
// Note: writePixels() can fail, but then the replacement bitmap will be
// left with part/all solid black due to the eraseColor() call above.
replacement.writePixels(src_pixmap);
}
*bitmap = replacement;
return *bitmap;
}
CopyOutputSkBitmapResult::~CopyOutputSkBitmapResult() = default;
CopyOutputTextureResult::CopyOutputTextureResult(
const gfx::Rect& rect,
const gpu::Mailbox& mailbox,
const gpu::SyncToken& sync_token,
const gfx::ColorSpace& color_space,
ReleaseCallback release_callback)
: CopyOutputResult(Format::RGBA_TEXTURE, rect, false),
texture_result_(mailbox, sync_token, color_space),
release_callback_(std::move(release_callback)) {
DCHECK_EQ(rect.IsEmpty(), mailbox.IsZero());
DCHECK_EQ(!release_callback_, mailbox.IsZero());
DCHECK(texture_result_.mailbox.IsZero() ||
texture_result_.color_space.IsValid());
}
CopyOutputTextureResult::~CopyOutputTextureResult() {
if (release_callback_)
std::move(release_callback_).Run(gpu::SyncToken(), false);
}
const CopyOutputResult::TextureResult*
CopyOutputTextureResult::GetTextureResult() const {
return &texture_result_;
}
ReleaseCallback CopyOutputTextureResult::TakeTextureOwnership() {
texture_result_.mailbox = gpu::Mailbox();
texture_result_.sync_token = gpu::SyncToken();
texture_result_.color_space = gfx::ColorSpace();
return std::move(release_callback_);
}
CopyOutputResult::ScopedSkBitmap::ScopedSkBitmap() = default;
CopyOutputResult::ScopedSkBitmap::ScopedSkBitmap(
const CopyOutputResult* result) {
DCHECK(result);
if (!result->needs_lock_for_bitmap_ || result->LockSkBitmap())
result_ = result;
}
CopyOutputResult::ScopedSkBitmap::ScopedSkBitmap(ScopedSkBitmap&& other) {
*this = std::move(other);
}
CopyOutputResult::ScopedSkBitmap::~ScopedSkBitmap() {
reset();
}
CopyOutputResult::ScopedSkBitmap& CopyOutputResult::ScopedSkBitmap::operator=(
ScopedSkBitmap&& other) {
DCHECK_CALLED_ON_VALID_THREAD(other.thread_checker_);
reset();
std::swap(result_, other.result_);
return *this;
}
void CopyOutputResult::ScopedSkBitmap::reset() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!result_)
return;
if (result_->needs_lock_for_bitmap_) {
#if DCHECK_IS_ON()
// We are going to unlock the content of the bitmap, so we need to make
// sure there is no other ref of the bitmap content.
auto* ref = bitmap().pixelRef();
DCHECK(!ref || ref->unique());
#endif
result_->UnlockSkBitmap();
result_ = nullptr;
}
}
SkBitmap CopyOutputResult::ScopedSkBitmap::GetOutScopedBitmap() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!result_)
return SkBitmap();
const auto& bitmap = result_->AsSkBitmap();
// If result_->needs_lock_for_bitmap_ is false, then bitmap can be used out of
// the scope.
if (!result_->needs_lock_for_bitmap_)
return bitmap;
// Make a new SkBitmap and copy content to this new SkBitmap.
SkBitmap bitmap_copy;
if (bitmap.readyToDraw()) {
bitmap_copy.allocPixels(bitmap.info());
bitmap.readPixels(bitmap_copy.pixmap(), 0, 0);
}
return bitmap_copy;
}
} // namespace viz