blob: a6f78693a12678c3c84bf6a233c7a00a0ca291fb [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/display_embedder/image_context_impl.h"
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "components/viz/common/resources/resource_format_utils.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/mailbox_manager.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_utils.h"
#include "gpu/command_buffer/service/skia_utils.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkPromiseImageTexture.h"
#include "third_party/skia/include/gpu/GrContextThreadSafeProxy.h"
#include "third_party/skia/include/gpu/graphite/Recorder.h"
namespace {
SkColor4f GetFallbackColorForPlane(viz::SharedImageFormat format,
int plane_index) {
DCHECK(format.IsValidPlaneIndex(plane_index));
// For DCHECKed builds return: 1) kRed for single planar formats; 2) kWhite
// for all multiplanar format planes that translates to Purple/Magenta color
// in RGB space. For non-DCHECKed builds return: 1) kWhite for single planar
// formats; 2) kWhite for Y, A planes and kGray for U, V, UV planes that
// translates to White color in RGB colorspace.
#if DCHECK_IS_ON()
return format.is_single_plane() ? SkColors::kRed : SkColors::kWhite;
#else
if (format.is_single_plane())
return SkColors::kWhite;
switch (format.plane_config()) {
case viz::SharedImageFormat::PlaneConfig::kY_V_U:
return plane_index == 0 ? SkColors::kWhite : SkColors::kGray;
case viz::SharedImageFormat::PlaneConfig::kY_UV:
return plane_index == 0 ? SkColors::kWhite : SkColors::kGray;
case viz::SharedImageFormat::PlaneConfig::kY_UV_A:
return plane_index == 1 ? SkColors::kGray : SkColors::kWhite;
}
#endif
}
} // namespace
namespace viz {
ImageContextImpl::ImageContextImpl(
const gpu::MailboxHolder& mailbox_holder,
const gfx::Size& size,
SharedImageFormat format,
bool maybe_concurrent_reads,
const absl::optional<gpu::VulkanYCbCrInfo>& ycbcr_info,
sk_sp<SkColorSpace> color_space,
bool is_for_render_pass,
bool raw_draw_if_possible)
: ImageContext(mailbox_holder, size, format, ycbcr_info, color_space),
maybe_concurrent_reads_(maybe_concurrent_reads),
is_for_render_pass_(is_for_render_pass),
raw_draw_if_possible_(raw_draw_if_possible) {}
ImageContextImpl::~ImageContextImpl() {
DeleteFallbackTextures();
}
void ImageContextImpl::OnContextLost() {
if (texture_passthrough_) {
texture_passthrough_->MarkContextLost();
texture_passthrough_.reset();
}
if (representation_) {
representation_->OnContextLost();
representation_scoped_read_access_.reset();
representation_.reset();
}
if (fallback_context_state_) {
fallback_context_state_ = nullptr;
fallback_textures_.clear();
graphite_fallback_textures_.clear();
}
}
void ImageContextImpl::SetPromiseImageTextures(
std::vector<sk_sp<SkPromiseImageTexture>> promise_image_textures) {
owned_promise_image_textures_ = std::move(promise_image_textures);
promise_image_textures_.clear();
for (auto& owned_texture : owned_promise_image_textures_) {
promise_image_textures_.push_back(owned_texture.get());
}
}
void ImageContextImpl::DeleteFallbackTextures() {
if (fallback_context_state_) {
if (fallback_context_state_->gr_context()) {
CHECK(graphite_fallback_textures_.empty());
for (auto& fallback_texture : fallback_textures_) {
gpu::DeleteGrBackendTexture(fallback_context_state_, &fallback_texture);
}
} else {
CHECK(fallback_context_state_->gpu_main_graphite_recorder());
CHECK(fallback_textures_.empty());
for (auto& fallback_texture : graphite_fallback_textures_) {
fallback_context_state_->gpu_main_graphite_recorder()
->deleteBackendTexture(fallback_texture);
}
}
}
fallback_context_state_ = nullptr;
fallback_textures_.clear();
graphite_fallback_textures_.clear();
}
void ImageContextImpl::CreateFallbackImage(
gpu::SharedContextState* context_state) {
// Render pass backings are managed by the renderer, so we expect them to be
// available if we try to reference one. If this trips, it likely indicates a
// bug in viz.
LOG_IF(DFATAL, is_for_render_pass_)
<< "Expected to fulfill promise texture for render pass backing.";
const int num_planes = format().NumberOfPlanes();
if (context_state->graphite_context()) {
const auto& tex_infos = texture_infos();
if (tex_infos.size() != static_cast<size_t>(num_planes) ||
base::ranges::any_of(tex_infos, [](const auto& tex_info) {
return !tex_info.isValid();
})) {
DLOG(ERROR) << "Invalid Graphite texture infos for format: "
<< format().ToString();
return;
}
for (int plane_index = 0; plane_index < num_planes; plane_index++) {
SkISize sk_size =
gfx::SizeToSkISize(format().GetPlaneSize(plane_index, size()));
graphite_fallback_textures_.push_back(
context_state->gpu_main_graphite_recorder()->createBackendTexture(
sk_size, tex_infos[plane_index]));
SkColorType color_type =
ToClosestSkColorType(/*gpu_compositing=*/true, format(), plane_index);
auto sk_surface = SkSurface::MakeGraphiteFromBackendTexture(
context_state->gpu_main_graphite_recorder(),
graphite_fallback_textures_[plane_index], color_type, color_space(),
/*props=*/nullptr);
if (!sk_surface) {
DLOG(ERROR) << "Failed to create fallback graphite backend texture";
DeleteFallbackTextures();
return;
}
sk_surface->getCanvas()->clear(
GetFallbackColorForPlane(format(), plane_index));
sk_surface->flush();
}
graphite_textures_ = graphite_fallback_textures_;
return;
}
// We can't allocate a fallback texture as the original texture was externally
// allocated. Skia will skip drawing a null SkPromiseImageTexture, do nothing
// and leave it null.
const auto& formats = backend_formats();
if (formats.empty() || formats[0].textureType() == GrTextureType::kExternal)
return;
DCHECK(!fallback_context_state_);
fallback_context_state_ = context_state;
std::vector<sk_sp<SkPromiseImageTexture>> promise_textures;
for (int plane_index = 0; plane_index < num_planes; plane_index++) {
DCHECK_NE(formats[plane_index].textureType(), GrTextureType::kExternal);
auto fallback_texture =
fallback_context_state_->gr_context()->createBackendTexture(
size().width(), size().height(), formats[plane_index],
GetFallbackColorForPlane(format(), plane_index), GrMipMapped::kNo,
GrRenderable::kYes);
if (!fallback_texture.isValid()) {
DeleteFallbackTextures();
DLOG(ERROR) << "Could not create backend texture.";
return;
}
auto promise_texture = SkPromiseImageTexture::Make(fallback_texture);
promise_textures.push_back(std::move(promise_texture));
fallback_textures_.push_back(fallback_texture);
}
SetPromiseImageTextures(promise_textures);
}
void ImageContextImpl::BeginAccessIfNecessary(
gpu::SharedContextState* context_state,
gpu::SharedImageRepresentationFactory* representation_factory,
gpu::MailboxManager* mailbox_manager,
std::vector<GrBackendSemaphore>* begin_semaphores,
std::vector<GrBackendSemaphore>* end_semaphores) {
if (representation_raster_scoped_access_)
return;
// Prepare for accessing shared image.
if (mailbox_holder().mailbox.IsSharedImage()) {
if (!BeginAccessIfNecessaryForSharedImage(
context_state, representation_factory, begin_semaphores,
end_semaphores)) {
CreateFallbackImage(context_state);
}
return;
}
// Prepare for accessing legacy mailbox.
// The promise images have been fulfilled once, so we don't need to do
// anything.
if (!promise_image_textures_.empty()) {
return;
}
if (!graphite_textures_.empty()) {
return;
}
if (!context_state->GrContextIsGL()) {
// Probably this texture is created with wrong interface (GLES2Interface).
DLOG(ERROR) << "Failed to fulfill the promise texture whose backend is not "
"compatible with vulkan or graphite.";
CreateFallbackImage(context_state);
return;
}
auto* texture_base =
mailbox_manager->ConsumeTexture(mailbox_holder().mailbox);
if (!texture_base) {
DLOG(ERROR) << "Failed to fulfill the promise texture.";
CreateFallbackImage(context_state);
return;
}
if (texture_base->GetType() == gpu::TextureBase::Type::kValidated) {
// Verify that the client-provided size matches the size of the GPU
// resource, using a fallback texture otherwise.
// NOTE: This verification is possible only for the validating decoder, as
// the size of the GL texture isn't tracked in the passthrough decoder.
auto* texture = gpu::gles2::Texture::CheckedCast(texture_base);
GLsizei width, height;
texture->GetLevelSize(texture_base->target(), 0 /* level */, &width,
&height, nullptr /* depth */);
gfx::Size texture_size(width, height);
if (texture_size != size()) {
DLOG(ERROR) << "Failed to fulfill the promise texture - texture "
"size does not match TransferableResource size: "
<< texture_size.ToString() << " vs " << size().ToString();
CreateFallbackImage(context_state);
return;
}
}
// Legacy mailboxes support only single planar formats.
DCHECK(format().is_single_plane());
bool angle_rgbx_internal_format =
context_state->feature_info()->feature_flags().angle_rgbx_internal_format;
GrBackendTexture backend_texture;
GLenum gl_storage_internal_format =
gpu::TextureStorageFormat(format(), angle_rgbx_internal_format);
gpu::GetGrBackendTexture(
context_state->feature_info(), texture_base->target(), size(),
texture_base->service_id(), gl_storage_internal_format,
context_state->gr_context()->threadSafeProxy(), &backend_texture);
if (!backend_texture.isValid()) {
DLOG(ERROR) << "Failed to fulfill the promise texture.";
CreateFallbackImage(context_state);
return;
}
SetPromiseImageTextures({SkPromiseImageTexture::Make(backend_texture)});
// Hold onto a reference to legacy GL textures while still in use, see
// https://crbug.com/1118166 for why this is necessary.
if (texture_base->GetType() == gpu::TextureBase::Type::kPassthrough) {
texture_passthrough_ =
gpu::gles2::TexturePassthrough::CheckedCast(texture_base);
}
// TODO(crbug.com/1118166): The case above handles textures with the
// passthrough command decoder, verify if something is required for the
// validating command decoder as well.
}
bool ImageContextImpl::BeginRasterAccess(
gpu::SharedImageRepresentationFactory* representation_factory) {
if (paint_op_buffer()) {
DCHECK(raster_representation_);
DCHECK(representation_raster_scoped_access_);
return true;
}
auto raster =
raw_draw_if_possible_
? representation_factory->ProduceRaster(mailbox_holder().mailbox)
: nullptr;
if (!raster)
return false;
auto scoped_access = raster->BeginScopedReadAccess();
if (!scoped_access)
return false;
set_paint_op_buffer(scoped_access->paint_op_buffer());
set_clear_color(scoped_access->clear_color());
raster_representation_ = std::move(raster);
representation_raster_scoped_access_ = std::move(scoped_access);
return true;
}
bool ImageContextImpl::BeginAccessIfNecessaryForSharedImage(
gpu::SharedContextState* context_state,
gpu::SharedImageRepresentationFactory* representation_factory,
std::vector<GrBackendSemaphore>* begin_semaphores,
std::vector<GrBackendSemaphore>* end_semaphores) {
// Skip the context if it has been processed.
if (representation_scoped_read_access_) {
CHECK(owned_promise_image_textures_.empty());
CHECK(!context_state->gr_context() || !promise_image_textures_.empty());
CHECK(!context_state->graphite_context() || !graphite_textures_.empty());
return true;
}
// promise_image_textures_ are not empty here, it means we are using a
// fallback image.
if (!promise_image_textures_.empty()) {
CHECK_EQ(promise_image_textures_.size(),
owned_promise_image_textures_.size());
return true;
}
if (!graphite_textures_.empty()) {
CHECK_EQ(graphite_textures_.size(), graphite_fallback_textures_.size());
return true;
}
if (!representation_) {
auto representation = representation_factory->ProduceSkia(
mailbox_holder().mailbox, context_state);
if (!representation) {
DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
"mailbox not found in SharedImageManager.";
return false;
}
if (!(representation->usage() & gpu::SHARED_IMAGE_USAGE_DISPLAY_READ)) {
DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
"was not created with DISPLAY_READ usage.";
return false;
}
if (representation->size() != size()) {
DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
"size does not match TransferableResource size: "
<< representation->size().ToString() << " vs "
<< size().ToString();
return false;
}
representation_ = std::move(representation);
}
representation_scoped_read_access_ =
representation_->BeginScopedReadAccess(begin_semaphores, end_semaphores);
if (!representation_scoped_read_access_) {
representation_ = nullptr;
DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
"begin read access failed..";
return false;
}
// Only one promise texture for external sampler case.
int num_planes =
format().PrefersExternalSampler() ? 1 : format().NumberOfPlanes();
if (context_state->graphite_context()) {
for (int plane_index = 0; plane_index < num_planes; plane_index++) {
graphite_textures_.push_back(
representation_scoped_read_access_->graphite_texture(plane_index));
}
} else {
CHECK(context_state->gr_context());
for (int plane_index = 0; plane_index < num_planes; plane_index++) {
promise_image_textures_.push_back(
representation_scoped_read_access_->promise_image_texture(
plane_index));
}
}
return true;
}
void ImageContextImpl::EndAccessIfNecessary() {
if (paint_op_buffer()) {
DCHECK(!representation_scoped_read_access_);
return;
}
if (!representation_scoped_read_access_)
return;
// Avoid unnecessary read access churn for representations that
// support multiple readers.
if (representation_->SupportsMultipleConcurrentReadAccess() &&
!is_for_render_pass_) {
return;
}
representation_scoped_read_access_.reset();
promise_image_textures_.clear();
graphite_textures_.clear();
}
} // namespace viz