[go: nahoru, domu]

blob: c626a63b8df06784e31a00891e1c2534b48edf63 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/paint/image_transfer_cache_entry.h"
#include <algorithm>
#include <array>
#include <type_traits>
#include <utility>
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "cc/paint/paint_op_reader.h"
#include "cc/paint/paint_op_writer.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/core/SkYUVAInfo.h"
#include "third_party/skia/include/gpu/GpuTypes.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "third_party/skia/include/gpu/GrYUVABackendTextures.h"
#include "third_party/skia/include/gpu/ganesh/SkImageGanesh.h"
#include "third_party/skia/include/gpu/graphite/Image.h"
#include "third_party/skia/include/gpu/graphite/Recorder.h"
#include "ui/gfx/color_conversion_sk_filter_cache.h"
#include "ui/gfx/hdr_metadata.h"
#include "ui/gfx/mojom/hdr_metadata.mojom.h"
namespace cc {
namespace {
struct Context {
const std::vector<sk_sp<SkImage>> sk_planes_;
};
void ReleaseContext(SkImages::ReleaseContext context) {
auto* texture_context = static_cast<Context*>(context);
delete texture_context;
}
bool IsYUVAInfoValid(SkYUVAInfo::PlaneConfig plane_config,
SkYUVAInfo::Subsampling subsampling,
SkYUVColorSpace yuv_color_space) {
if (plane_config == SkYUVAInfo::PlaneConfig::kUnknown) {
return subsampling == SkYUVAInfo::Subsampling::kUnknown &&
yuv_color_space == kIdentity_SkYUVColorSpace;
}
return subsampling != SkYUVAInfo::Subsampling::kUnknown;
}
int NumPixmapsForYUVConfig(SkYUVAInfo::PlaneConfig plane_config) {
return std::max(SkYUVAInfo::NumPlanes(plane_config), 1);
}
// Creates a SkImage backed by the YUV textures corresponding to |plane_images|.
// The layout is specified by |plane_images_format|). The backend textures are
// first extracted out of the |plane_images| (and work is flushed on each one).
// Note that we assume that the image is opaque (no alpha plane). Then, a
// SkImage is created out of those textures using the
// SkImages::TextureFromYUVATextures() API. Finally, |image_color_space| is the
// color space of the resulting image after applying |yuv_color_space|
// (converting from YUV to RGB). This is assumed to be sRGB if nullptr.
//
// On success, the resulting SkImage is
// returned. On failure, nullptr is returned (e.g., if one of the backend
// textures is invalid or a Skia error occurs).
sk_sp<SkImage> MakeYUVImageFromUploadedPlanes(
GrDirectContext* gr_context,
skgpu::graphite::Recorder* graphite_recorder,
const std::vector<sk_sp<SkImage>>& plane_images,
const SkYUVAInfo& yuva_info,
sk_sp<SkColorSpace> image_color_space) {
// 1) Extract the textures.
DCHECK_NE(SkYUVAInfo::PlaneConfig::kUnknown, yuva_info.planeConfig());
DCHECK_NE(SkYUVAInfo::Subsampling::kUnknown, yuva_info.subsampling());
DCHECK_EQ(static_cast<size_t>(SkYUVAInfo::NumPlanes(yuva_info.planeConfig())),
plane_images.size());
DCHECK_LE(plane_images.size(),
base::checked_cast<size_t>(SkYUVAInfo::kMaxPlanes));
if (graphite_recorder) {
sk_sp<SkImage> image = SkImages::TextureFromYUVAImages(
graphite_recorder, yuva_info, plane_images, image_color_space);
if (!image) {
DLOG(ERROR) << "Could not create YUV image";
return nullptr;
}
return image;
}
std::array<GrBackendTexture, SkYUVAInfo::kMaxPlanes> plane_backend_textures;
for (size_t plane = 0u; plane < plane_images.size(); plane++) {
if (!SkImages::GetBackendTextureFromImage(
plane_images[plane], &plane_backend_textures[plane],
true /* flushPendingGrContextIO */)) {
DLOG(ERROR) << "Invalid backend texture found";
return nullptr;
}
}
// 2) Create the YUV image.
GrYUVABackendTextures yuva_backend_textures(
yuva_info, plane_backend_textures.data(), kTopLeft_GrSurfaceOrigin);
Context* ctx = new Context{plane_images};
sk_sp<SkImage> image = SkImages::TextureFromYUVATextures(
gr_context, yuva_backend_textures, std::move(image_color_space),
ReleaseContext, ctx);
if (!image) {
DLOG(ERROR) << "Could not create YUV image";
return nullptr;
}
return image;
}
base::CheckedNumeric<uint32_t> SafeSizeForPixmap(const SkPixmap& pixmap) {
base::CheckedNumeric<uint32_t> safe_size;
safe_size += PaintOpWriter::SerializedSize(pixmap.colorType());
safe_size += PaintOpWriter::SerializedSize(pixmap.width());
safe_size += PaintOpWriter::SerializedSize(pixmap.height());
safe_size += PaintOpWriter::SerializedSize(pixmap.rowBytes());
safe_size += 16u; // The max of GetAlignmentForColorType().
safe_size += PaintOpWriter::SerializedSizeOfBytes(pixmap.computeByteSize());
return safe_size;
}
base::CheckedNumeric<uint32_t> SafeSizeForImage(
const ClientImageTransferCacheEntry::Image& image) {
base::CheckedNumeric<uint32_t> safe_size;
safe_size += PaintOpWriter::SerializedSize(image.yuv_plane_config);
safe_size += PaintOpWriter::SerializedSize(image.yuv_subsampling);
safe_size += PaintOpWriter::SerializedSize(image.yuv_color_space);
safe_size += PaintOpWriter::SerializedSize(image.color_space.get());
const int num_pixmaps = NumPixmapsForYUVConfig(image.yuv_plane_config);
for (int i = 0; i < num_pixmaps; ++i) {
safe_size += SafeSizeForPixmap(*image.pixmaps.at(i));
}
return safe_size;
}
size_t GetAlignmentForColorType(SkColorType color_type) {
size_t bpp = SkColorTypeBytesPerPixel(color_type);
if (bpp <= 4)
return 4;
if (bpp <= 16)
return 16;
NOTREACHED();
return 0;
}
bool WritePixmap(PaintOpWriter& writer, const SkPixmap& pixmap) {
if (pixmap.width() == 0 || pixmap.height() == 0) {
DLOG(ERROR) << "Cannot write empty pixmap";
return false;
}
DCHECK_GT(pixmap.width(), 0);
DCHECK_GT(pixmap.height(), 0);
DCHECK_GT(pixmap.rowBytes(), 0u);
writer.Write(pixmap.colorType());
writer.Write(pixmap.width());
writer.Write(pixmap.height());
size_t data_size = pixmap.computeByteSize();
if (data_size == SIZE_MAX) {
DLOG(ERROR) << "Size overflow writing pixmap";
return false;
}
writer.WriteSize(pixmap.rowBytes());
writer.WriteSize(data_size);
// The memory for the pixmap must be aligned to a byte boundary, or mipmap
// generation can fail.
// https://crbug.com/863659, https://crbug.com/1300188
writer.AlignMemory(GetAlignmentForColorType(pixmap.colorType()));
writer.WriteData(data_size, pixmap.addr());
return true;
}
bool ReadPixmap(PaintOpReader& reader, SkPixmap& pixmap) {
if (!reader.valid())
return false;
SkColorType color_type = kUnknown_SkColorType;
reader.Read(&color_type);
const size_t alignment = GetAlignmentForColorType(color_type);
if (color_type == kUnknown_SkColorType ||
color_type == kRGB_101010x_SkColorType ||
color_type > kLastEnum_SkColorType) {
DLOG(ERROR) << "Invalid color type";
return false;
}
int width = 0;
reader.Read(&width);
int height = 0;
reader.Read(&height);
if (width == 0 || height == 0) {
DLOG(ERROR) << "Empty width or height";
return false;
}
auto image_info =
SkImageInfo::Make(width, height, color_type, kPremul_SkAlphaType);
size_t row_bytes = 0;
reader.ReadSize(&row_bytes);
if (row_bytes < image_info.minRowBytes()) {
DLOG(ERROR) << "Row bytes " << row_bytes << " less than minimum "
<< image_info.minRowBytes();
return false;
}
size_t data_size = 0;
reader.ReadSize(&data_size);
if (image_info.computeByteSize(row_bytes) > data_size) {
DLOG(ERROR) << "Data size too small";
return false;
}
reader.AlignMemory(alignment);
const volatile void* data = reader.ExtractReadableMemory(data_size);
if (!reader.valid()) {
DLOG(ERROR) << "Failed to read pixels";
return false;
}
if (reinterpret_cast<uintptr_t>(data) % alignment) {
DLOG(ERROR) << "Pixel pointer not aligned";
return false;
}
// Const-cast away the "volatile" on |pixel_data|. We specifically understand
// that a malicious caller may change our pixels under us, and are OK with
// this as the worst case scenario is visual corruption.
pixmap = SkPixmap(image_info, const_cast<const void*>(data), row_bytes);
return true;
}
bool WriteImage(PaintOpWriter& writer,
const ClientImageTransferCacheEntry::Image& image) {
DCHECK(IsYUVAInfoValid(image.yuv_plane_config, image.yuv_subsampling,
image.yuv_color_space));
writer.Write(image.color_space);
writer.Write(image.yuv_plane_config);
writer.Write(image.yuv_subsampling);
writer.Write(image.yuv_color_space);
const int num_pixmaps = NumPixmapsForYUVConfig(image.yuv_plane_config);
for (int i = 0; i < num_pixmaps; ++i) {
if (!WritePixmap(writer, *image.pixmaps.at(i))) {
return false;
}
}
return true;
}
sk_sp<SkImage> ReadImage(
PaintOpReader& reader,
GrDirectContext* gr_context,
skgpu::graphite::Recorder* graphite_recorder,
bool mip_mapped_for_upload,
std::optional<SkYUVAInfo>* out_yuva_info = nullptr,
std::vector<sk_sp<SkImage>>* out_yuva_plane_images = nullptr) {
int max_size;
if (gr_context) {
max_size = gr_context->maxTextureSize();
} else if (graphite_recorder) {
// TODO(b/279234024): Retrieve correct max texture size for graphite.
max_size = 8192;
} else {
// Allow a nullptr context for testing using the software renderer.
max_size = 0;
}
sk_sp<SkColorSpace> color_space;
reader.Read(&color_space);
SkYUVAInfo::PlaneConfig plane_config = SkYUVAInfo::PlaneConfig::kUnknown;
reader.Read(&plane_config);
if (plane_config < SkYUVAInfo::PlaneConfig::kUnknown ||
plane_config > SkYUVAInfo::PlaneConfig::kLast) {
DLOG(ERROR) << "Invalid plane config";
return nullptr;
}
SkYUVAInfo::Subsampling subsampling = SkYUVAInfo::Subsampling::kUnknown;
reader.Read(&subsampling);
if (subsampling < SkYUVAInfo::Subsampling::kUnknown ||
subsampling > SkYUVAInfo::Subsampling::kLast) {
DLOG(ERROR) << "Invalid subsampling";
return nullptr;
}
SkYUVColorSpace yuv_color_space = kIdentity_SkYUVColorSpace;
reader.Read(&yuv_color_space);
if (yuv_color_space < kJPEG_Full_SkYUVColorSpace ||
yuv_color_space > kLastEnum_SkYUVColorSpace) {
DLOG(ERROR) << "Invalid YUV color space";
return nullptr;
}
if (!IsYUVAInfoValid(plane_config, subsampling, yuv_color_space)) {
DLOG(ERROR) << "Invalid YUV configuration";
return nullptr;
}
SkPixmap pixmaps[SkYUVAInfo::kMaxPlanes];
bool fits_on_gpu = true;
const int num_pixmaps = NumPixmapsForYUVConfig(plane_config);
for (int i = 0; i < num_pixmaps; ++i) {
if (!ReadPixmap(reader, pixmaps[i])) {
DLOG(ERROR) << "Failed to read pixmap";
return nullptr;
}
fits_on_gpu &=
pixmaps[i].width() <= max_size && pixmaps[i].height() <= max_size;
// This is likely unnecessary for YUVA images (the pixmaps of the individual
// planes should be ignored), but is left here to avoid behavior changes
// during refactoring.
pixmaps[i].setColorSpace(color_space);
}
if (plane_config == SkYUVAInfo::PlaneConfig::kUnknown) {
// Point `image_` directly to the data in the transfer cache.
auto image = SkImages::RasterFromPixmap(pixmaps[0], nullptr, nullptr);
if (!image) {
DLOG(ERROR) << "Failed to create image from pixmap";
return nullptr;
}
// Upload to the GPU if the image will fit.
if (fits_on_gpu) {
if (gr_context) {
image = SkImages::TextureFromImage(gr_context, image,
mip_mapped_for_upload
? skgpu::Mipmapped::kYes
: skgpu::Mipmapped::kNo,
skgpu::Budgeted::kNo);
} else {
CHECK(graphite_recorder);
SkImage::RequiredProperties props{.fMipmapped = mip_mapped_for_upload};
image = SkImages::TextureFromImage(graphite_recorder, image, props);
}
if (!image) {
DLOG(ERROR) << "Failed to upload pixmap to texture image.";
return nullptr;
}
DCHECK(image->isTextureBacked());
}
if (out_yuva_info) {
*out_yuva_info = std::nullopt;
}
if (out_yuva_plane_images) {
out_yuva_plane_images->clear();
}
return image;
} else {
if (!fits_on_gpu) {
DLOG(ERROR) << "YUVA images must fit in the GPU texture size limit";
return nullptr;
}
// Upload the planes to the GPU.
std::vector<sk_sp<SkImage>> plane_images;
for (int i = 0; i < num_pixmaps; i++) {
sk_sp<SkImage> plane =
SkImages::RasterFromPixmap(pixmaps[i], nullptr, nullptr);
if (!plane) {
DLOG(ERROR) << "Failed to create image from plane pixmap";
return nullptr;
}
if (gr_context) {
plane = SkImages::TextureFromImage(gr_context, plane,
mip_mapped_for_upload
? skgpu::Mipmapped::kYes
: skgpu::Mipmapped::kNo,
skgpu::Budgeted::kNo);
// Uploading pixels is a heavy operation that might take long and lead
// to yields to higher priority scheduler sequences. To ensure upload is
// done, perform flush for Ganesh in DDL mode (no-op if image is null).
SkImages::GetBackendTextureFromImage(plane, /*outTexture=*/nullptr,
/*flushPendingGrContextIO=*/true);
} else {
CHECK(graphite_recorder);
SkImage::RequiredProperties props{.fMipmapped = mip_mapped_for_upload};
// Graphite is like Ganesh in DDL mode but Graphite has lower CPU
// overhead with modern APIs leading to lesser scheduling concerns.
// Also, eventually we want to move tile raster off the GPU main thread.
// Based on these reasons, its okay to not flush for Graphite here.
// TODO(crbug.com/1463790): Revisit flushes for Graphite here if yield
// to scheduler is needed.
plane = SkImages::TextureFromImage(graphite_recorder, plane, props);
}
if (!plane) {
DLOG(ERROR) << "Failed to upload plane pixmap to texture image";
return nullptr;
}
CHECK(plane->isTextureBacked());
plane_images.push_back(std::move(plane));
}
SkYUVAInfo yuva_info(plane_images[0]->dimensions(), plane_config,
subsampling, yuv_color_space);
// Build the YUV image from its planes.
auto image = MakeYUVImageFromUploadedPlanes(
gr_context, graphite_recorder, plane_images, yuva_info, color_space);
if (!image) {
DLOG(ERROR) << "Failed to make YUV image from planes.";
return nullptr;
}
if (out_yuva_info) {
*out_yuva_info = yuva_info;
}
if (out_yuva_plane_images) {
*out_yuva_plane_images = std::move(plane_images);
}
return image;
}
}
} // namespace
size_t NumberOfPlanesForYUVDecodeFormat(YUVDecodeFormat format) {
switch (format) {
case YUVDecodeFormat::kYUVA4:
return 4u;
case YUVDecodeFormat::kYUV3:
case YUVDecodeFormat::kYVU3:
return 3u;
case YUVDecodeFormat::kYUV2:
return 2u;
case YUVDecodeFormat::kUnknown:
return 0u;
}
}
////////////////////////////////////////////////////////////////////////////////
// ClientImageTransferCacheEntry::Image
ClientImageTransferCacheEntry::Image::Image() {}
ClientImageTransferCacheEntry::Image::Image(const Image&) = default;
ClientImageTransferCacheEntry::Image&
ClientImageTransferCacheEntry::Image::operator=(const Image&) = default;
ClientImageTransferCacheEntry::Image::Image(const SkPixmap* pixmap)
: color_space(pixmap->colorSpace()) {
DCHECK(pixmap);
pixmaps[0] = pixmap;
}
ClientImageTransferCacheEntry::Image::Image(const SkPixmap yuva_pixmaps[],
const SkYUVAInfo& yuva_info,
const SkColorSpace* color_space)
: yuv_plane_config(yuva_info.planeConfig()),
yuv_subsampling(yuva_info.subsampling()),
yuv_color_space(yuva_info.yuvColorSpace()),
color_space(color_space) {
// The size of the first plane must equal the size specified in the
// SkYUVAInfo.
DCHECK(yuva_info.dimensions() == yuva_pixmaps[0].dimensions());
// We fail to serialize some parameters.
DCHECK_EQ(yuva_info.origin(), kTopLeft_SkEncodedOrigin);
DCHECK_EQ(yuva_info.sitingX(), SkYUVAInfo::Siting::kCentered);
DCHECK_EQ(yuva_info.sitingY(), SkYUVAInfo::Siting::kCentered);
DCHECK(IsYUVAInfoValid(yuv_plane_config, yuv_subsampling, yuv_color_space));
for (int i = 0; i < SkYUVAInfo::NumPlanes(yuv_plane_config); ++i) {
pixmaps[i] = &yuva_pixmaps[i];
}
}
////////////////////////////////////////////////////////////////////////////////
// ClientImageTransferCacheEntry
ClientImageTransferCacheEntry::ClientImageTransferCacheEntry(
const Image& image,
bool needs_mips,
const std::optional<gfx::HDRMetadata>& hdr_metadata,
sk_sp<SkColorSpace> target_color_space)
: needs_mips_(needs_mips),
target_color_space_(target_color_space),
id_(GetNextId()),
image_(image),
hdr_metadata_(hdr_metadata) {
ComputeSize();
}
ClientImageTransferCacheEntry::ClientImageTransferCacheEntry(
const Image& image,
const Image& gainmap_image,
const SkGainmapInfo& gainmap_info,
bool needs_mips)
: needs_mips_(needs_mips),
id_(GetNextId()),
image_(image),
gainmap_image_(gainmap_image),
gainmap_info_(gainmap_info) {
ComputeSize();
}
ClientImageTransferCacheEntry::~ClientImageTransferCacheEntry() = default;
// static
base::AtomicSequenceNumber ClientImageTransferCacheEntry::s_next_id_;
uint32_t ClientImageTransferCacheEntry::SerializedSize() const {
return size_;
}
uint32_t ClientImageTransferCacheEntry::Id() const {
return id_;
}
bool ClientImageTransferCacheEntry::Serialize(base::span<uint8_t> data) const {
DCHECK_GE(data.size(), SerializedSize());
// We don't need to populate the SerializeOptions here since the writer is
// only used for serializing primitives.
PaintOp::SerializeOptions options;
PaintOpWriter writer(data.data(), data.size(), options);
DCHECK_EQ(gainmap_image_.has_value(), gainmap_info_.has_value());
bool has_gainmap = gainmap_image_.has_value();
writer.Write(has_gainmap);
writer.Write(needs_mips_);
writer.Write(hdr_metadata_.has_value());
if (hdr_metadata_.has_value()) {
writer.Write(hdr_metadata_.value());
}
writer.Write(target_color_space_.get());
WriteImage(writer, image_);
if (has_gainmap) {
WriteImage(writer, gainmap_image_.value());
writer.Write(gainmap_info_.value());
}
// Size can't be 0 after serialization unless the writer has become invalid.
if (writer.size() == 0u)
return false;
return true;
}
void ClientImageTransferCacheEntry::ComputeSize() {
base::CheckedNumeric<uint32_t> safe_size;
safe_size += PaintOpWriter::SerializedSize<bool>(); // has_gainmap
safe_size += PaintOpWriter::SerializedSize<bool>(); // needs_mips
safe_size += PaintOpWriter::SerializedSize<bool>(); // has_hdr_metadata
if (hdr_metadata_.has_value()) {
safe_size += PaintOpWriter::SerializedSize(hdr_metadata_.value());
}
safe_size += PaintOpWriter::SerializedSize(target_color_space_.get());
safe_size += SafeSizeForImage(image_);
if (gainmap_image_) {
DCHECK(gainmap_info_);
safe_size += SafeSizeForImage(gainmap_image_.value());
if (gainmap_info_.has_value()) {
safe_size += PaintOpWriter::SerializedSize(gainmap_info_.value());
}
}
size_ = safe_size.ValueOrDefault(0);
}
////////////////////////////////////////////////////////////////////////////////
// ServiceImageTransferCacheEntry
ServiceImageTransferCacheEntry::ServiceImageTransferCacheEntry() = default;
ServiceImageTransferCacheEntry::~ServiceImageTransferCacheEntry() = default;
ServiceImageTransferCacheEntry::ServiceImageTransferCacheEntry(
ServiceImageTransferCacheEntry&& other) = default;
ServiceImageTransferCacheEntry& ServiceImageTransferCacheEntry::operator=(
ServiceImageTransferCacheEntry&& other) = default;
bool ServiceImageTransferCacheEntry::BuildFromHardwareDecodedImage(
GrDirectContext* gr_context,
std::vector<sk_sp<SkImage>> plane_images,
SkYUVAInfo::PlaneConfig plane_config,
SkYUVAInfo::Subsampling subsampling,
SkYUVColorSpace yuv_color_space,
size_t buffer_byte_size,
bool needs_mips) {
// Only supported on Ganesh for now since this code path is only used on CrOS.
CHECK(gr_context);
gr_context_ = gr_context;
size_ = buffer_byte_size;
// 1) Generate mipmap chains if requested.
if (needs_mips) {
DCHECK(plane_sizes_.empty());
base::CheckedNumeric<size_t> safe_total_size(0u);
for (size_t plane = 0; plane < plane_images.size(); plane++) {
plane_images[plane] = SkImages::TextureFromImage(
gr_context_, plane_images[plane], skgpu::Mipmapped::kYes,
skgpu::Budgeted::kNo);
if (!plane_images[plane]) {
DLOG(ERROR) << "Could not generate mipmap chain for plane " << plane;
return false;
}
plane_sizes_.push_back(plane_images[plane]->textureSize());
safe_total_size += plane_sizes_.back();
}
if (!safe_total_size.AssignIfValid(&size_)) {
DLOG(ERROR) << "Could not calculate the total image size";
return false;
}
}
plane_images_ = std::move(plane_images);
if (static_cast<size_t>(SkYUVAInfo::NumPlanes(plane_config)) !=
plane_images_.size()) {
DLOG(ERROR) << "Expected " << SkYUVAInfo::NumPlanes(plane_config)
<< " planes, got " << plane_images_.size();
return false;
}
yuva_info_ = SkYUVAInfo(plane_images_[0]->dimensions(), plane_config,
subsampling, yuv_color_space);
// 2) Create a SkImage backed by |plane_images|.
// TODO(andrescj): support embedded color profiles for hardware decodes and
// pass the color space to MakeYUVImageFromUploadedPlanes.
image_ = MakeYUVImageFromUploadedPlanes(
gr_context_, /*graphite_recorder=*/nullptr, plane_images_,
yuva_info_.value(), SkColorSpace::MakeSRGB());
if (!image_) {
return false;
}
DCHECK(image_->isTextureBacked());
return true;
}
size_t ServiceImageTransferCacheEntry::CachedSize() const {
return size_;
}
sk_sp<SkImage> ServiceImageTransferCacheEntry::GetImageWithToneMapApplied(
float hdr_headroom,
bool needs_mips) const {
sk_sp<SkImage> image;
// Apply tone mapping.
// TODO(https://crbug.com/1286088): Pass a shared cache as a parameter.
gfx::ColorConversionSkFilterCache cache;
if (has_gainmap_) {
image = cache.ApplyGainmap(
image_, gainmap_image_, gainmap_info_, hdr_headroom,
image_->isTextureBacked() ? gr_context_ : nullptr,
image_->isTextureBacked() ? graphite_recorder_ : nullptr);
} else if (use_tone_curve_) {
// Images are always rendered as SDR-relative and the output color space
// is SDR itself,
image = cache.ApplyToneCurve(
image_, tone_curve_hdr_metadata_,
gfx::ColorSpace::kDefaultSDRWhiteLevel, hdr_headroom,
image_->isTextureBacked() ? gr_context_ : nullptr,
image_->isTextureBacked() ? graphite_recorder_ : nullptr);
}
if (!image) {
DLOG(ERROR) << "Image tone mapping failed";
return nullptr;
}
// Create mipmaps if requested.
if (image->isTextureBacked() && needs_mips && !image->hasMipmaps()) {
if (gr_context_) {
image = SkImages::TextureFromImage(
gr_context_, image, skgpu::Mipmapped::kYes, skgpu::Budgeted::kNo);
} else {
CHECK(graphite_recorder_);
SkImage::RequiredProperties props{.fMipmapped = true};
image = SkImages::TextureFromImage(graphite_recorder_, image_, props);
}
if (!image) {
DLOG(ERROR) << "Failed to generate mipmaps after tone mapping";
return nullptr;
}
}
return image;
}
bool ServiceImageTransferCacheEntry::Deserialize(
GrDirectContext* gr_context,
skgpu::graphite::Recorder* graphite_recorder,
base::span<const uint8_t> data) {
gr_context_ = gr_context;
graphite_recorder_ = graphite_recorder;
// We don't need to populate the DeSerializeOptions here since the reader is
// only used for de-serializing primitives.
std::vector<uint8_t> scratch_buffer;
PaintOp::DeserializeOptions options(nullptr, nullptr, nullptr,
&scratch_buffer, false, nullptr);
PaintOpReader reader(data.data(), data.size(), options);
// Parameters common to RGBA and YUVA images.
reader.Read(&has_gainmap_);
bool needs_mips = false;
reader.Read(&needs_mips);
bool has_hdr_metadata = false;
reader.Read(&has_hdr_metadata);
if (has_hdr_metadata) {
gfx::HDRMetadata hdr_metadata_value;
reader.Read(&hdr_metadata_value);
tone_curve_hdr_metadata_ = hdr_metadata_value;
}
sk_sp<SkColorSpace> target_color_space;
reader.Read(&target_color_space);
const bool mip_mapped_for_upload = needs_mips && !target_color_space;
// Deserialize the image.
image_ = ReadImage(reader, gr_context, graphite_recorder,
mip_mapped_for_upload, &yuva_info_, &plane_images_);
if (!image_) {
DLOG(ERROR) << "Failed to deserialize image.";
return false;
}
// If the image doesn't fit in the GPU texture limits, then it is still on
// the CPU, pointing at memory in the transfer buffer.
sk_sp<SkImage> image_referencing_transfer_buffer;
if (!image_->isTextureBacked()) {
image_referencing_transfer_buffer = image_;
}
for (const auto& plane_image : plane_images_) {
plane_sizes_.push_back(plane_image->textureSize());
}
// Read the gainmap image, if one was specified to exist.
sk_sp<SkImage> gainmap_image_referencing_transfer_buffer;
if (has_gainmap_) {
gainmap_image_ =
ReadImage(reader, gr_context, graphite_recorder, mip_mapped_for_upload);
if (!gainmap_image_) {
DLOG(ERROR) << "Failed to deserialize gainmap image.";
return false;
}
if (!gainmap_image_->isTextureBacked()) {
gainmap_image_referencing_transfer_buffer = gainmap_image_;
}
reader.Read(&gainmap_info_);
}
// Save the tone curve parameters, if they are to be used.
use_tone_curve_ =
!has_gainmap_ && gfx::ColorConversionSkFilterCache::UseToneCurve(image_);
// Perform color conversion (if no tone mapping is needed).
if (target_color_space && !NeedsToneMapApplied()) {
if (graphite_recorder_) {
SkImage::RequiredProperties props{.fMipmapped = needs_mips};
image_ =
image_->makeColorSpace(graphite_recorder_, target_color_space, props);
} else {
// TODO(crbug.com/1443068): It's possible for both `gr_context` and
// `graphite_recorder` to be nullptr if `image_` is not texture backed.
// Need to handle this case (currently just goes through gr_context path
// with nullptr context).
image_ = image_->makeColorSpace(gr_context_, target_color_space);
if (needs_mips && gr_context_ && image_ && image_->isTextureBacked()) {
image_ = SkImages::TextureFromImage(
gr_context, image_, skgpu::Mipmapped::kYes, skgpu::Budgeted::kNo);
}
}
if (!image_) {
DLOG(ERROR) << "Failed image color conversion.";
return false;
}
// Color conversion converts to RGBA. Remove all YUV state.
yuva_info_ = std::nullopt;
plane_images_.clear();
plane_sizes_.clear();
// Ensure mipmaps were created if requested.
if (image_->isTextureBacked()) {
DCHECK_EQ(needs_mips, image_->hasMipmaps());
}
}
// If `image_` or `gainmap_image_` is still directly referencing the transfer
// buffer's memory, make a copy of it (because the memory will go away after
// this this call).
auto copy_from_transfer_buffer =
[](sk_sp<SkImage>& image,
sk_sp<SkImage> image_referencing_transfer_buffer) {
if (!image || image != image_referencing_transfer_buffer) {
return true;
}
SkPixmap pixmap;
if (!image->peekPixels(&pixmap)) {
NOTREACHED()
<< "Image should be referencing transfer buffer SkPixmap";
}
image = SkImages::RasterFromPixmapCopy(pixmap);
if (!image) {
DLOG(ERROR) << "Failed to create raster copy";
return false;
}
return true;
};
if (!copy_from_transfer_buffer(image_, image_referencing_transfer_buffer)) {
return false;
}
if (!copy_from_transfer_buffer(gainmap_image_,
gainmap_image_referencing_transfer_buffer)) {
return false;
}
size_ = image_->textureSize();
if (gainmap_image_) {
size_ += gainmap_image_->textureSize();
}
return true;
}
const sk_sp<SkImage>& ServiceImageTransferCacheEntry::GetPlaneImage(
size_t index) const {
DCHECK_GE(index, 0u);
DCHECK_LT(index, plane_images_.size());
DCHECK(plane_images_.at(index));
return plane_images_.at(index);
}
void ServiceImageTransferCacheEntry::EnsureMips() {
if (!image_ || !image_->isTextureBacked()) {
return;
}
// Don't generate mipmaps for images that will not be used directly.
if (NeedsToneMapApplied()) {
return;
}
if (image_->hasMipmaps()) {
return;
}
if (is_yuv()) {
DCHECK(image_);
DCHECK(yuva_info_.has_value());
DCHECK_NE(SkYUVAInfo::PlaneConfig::kUnknown, yuva_info_->planeConfig());
DCHECK_EQ(
static_cast<size_t>(SkYUVAInfo::NumPlanes(yuva_info_->planeConfig())),
plane_images_.size());
// We first do all the work with local variables. Then, if everything
// succeeds, we update the object's state. That way, we don't leave it in an
// inconsistent state if one step of mip generation fails.
std::vector<sk_sp<SkImage>> mipped_planes;
std::vector<size_t> mipped_plane_sizes;
for (size_t plane = 0; plane < plane_images_.size(); plane++) {
CHECK(plane_images_.at(plane));
sk_sp<SkImage> mipped_plane;
if (gr_context_) {
mipped_plane = SkImages::TextureFromImage(
gr_context_, plane_images_.at(plane), skgpu::Mipmapped::kYes,
skgpu::Budgeted::kNo);
} else {
CHECK(graphite_recorder_);
SkImage::RequiredProperties props{.fMipmapped = true};
mipped_plane = SkImages::TextureFromImage(
graphite_recorder_, plane_images_.at(plane), props);
}
if (!mipped_plane) {
DLOG(ERROR) << "Failed to mipmap plane.";
return;
}
mipped_planes.push_back(std::move(mipped_plane));
mipped_plane_sizes.push_back(mipped_planes.back()->textureSize());
}
sk_sp<SkImage> mipped_image = MakeYUVImageFromUploadedPlanes(
gr_context_, graphite_recorder_, mipped_planes, yuva_info_.value(),
image_->refColorSpace() /* image_color_space */);
if (!mipped_image) {
DLOG(ERROR) << "Failed to create YUV image from mipmapped planes.";
return;
}
// Note that we cannot update |size_| because the transfer cache keeps track
// of a total size that is not updated after EnsureMips(). The original size
// is used when the image is deleted from the cache.
plane_images_ = std::move(mipped_planes);
plane_sizes_ = std::move(mipped_plane_sizes);
image_ = std::move(mipped_image);
} else {
sk_sp<SkImage> mipped_image;
if (gr_context_) {
mipped_image = SkImages::TextureFromImage(
gr_context_, image_, skgpu::Mipmapped::kYes, skgpu::Budgeted::kNo);
} else {
CHECK(graphite_recorder_);
SkImage::RequiredProperties props{.fMipmapped = true};
mipped_image =
SkImages::TextureFromImage(graphite_recorder_, image_, props);
}
if (!mipped_image) {
DLOG(ERROR) << "Failed to mipmapped image";
return;
}
image_ = std::move(mipped_image);
}
}
bool ServiceImageTransferCacheEntry::has_mips() const {
return (image_ && image_->isTextureBacked()) ? image_->hasMipmaps() : true;
}
bool ServiceImageTransferCacheEntry::fits_on_gpu() const {
return image_ && image_->isTextureBacked();
}
} // namespace cc