blob: 227094c427c25dc892ebacd026fef4f82c3f77aa [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "gpu/command_buffer/service/copy_shared_image_helper.h"
#include <array>
#include <memory>
#include <vector>
#include "base/check.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/service/graphite_shared_context.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_service_utils.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "skia/ext/rgba_to_yuva.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "third_party/libyuv/include/libyuv/planar_functions.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/ganesh/GrBackendSemaphore.h"
#include "third_party/skia/include/gpu/ganesh/GrDirectContext.h"
#include "third_party/skia/include/gpu/ganesh/GrTypes.h"
#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h"
#include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h"
#include "third_party/skia/include/gpu/ganesh/gl/GrGLTypes.h"
#include "third_party/skia/include/gpu/graphite/Context.h"
#include "third_party/skia/include/gpu/graphite/Image.h"
#include "third_party/skia/include/gpu/graphite/Recorder.h"
#include "ui/gfx/geometry/skia_conversions.h"
namespace gpu {
using GLError = CopySharedImageHelper::GLError;
namespace {
SkColorType GetCompatibleSurfaceColorType(GrGLenum format) {
switch (format) {
case GL_RGBA8:
return kRGBA_8888_SkColorType;
case GL_RGB565:
return kRGB_565_SkColorType;
case GL_RGBA16F:
return kRGBA_F16_SkColorType;
case GL_RGB8:
return kRGB_888x_SkColorType;
case GL_RGB10_A2:
return kRGBA_1010102_SkColorType;
case GL_RGBA4:
return kARGB_4444_SkColorType;
case GL_SRGB8_ALPHA8:
return kRGBA_8888_SkColorType;
default:
NOTREACHED() << "Unknown format: " << format;
}
}
GrGLenum GetSurfaceColorFormat(GrGLenum format, GrGLenum type) {
if (format == GL_RGBA) {
if (type == GL_UNSIGNED_BYTE) {
return GL_RGBA8;
}
if (type == GL_UNSIGNED_SHORT_4_4_4_4) {
return GL_RGBA4;
}
}
if (format == GL_RGB) {
if (type == GL_UNSIGNED_BYTE) {
return GL_RGB8;
}
if (type == GL_UNSIGNED_SHORT_5_6_5) {
return GL_RGB565;
}
}
return format;
}
// Returns an SkSurface wrapping `texture_id`. Assumes the presence of a Ganesh
// GL context to do the wrapping.
sk_sp<SkSurface> CreateSkSurfaceWrappingGLTexture(
SharedContextState* shared_context_state,
GLuint texture_id,
GLenum target,
GLuint internal_format,
GLenum type,
GLsizei width,
GLsizei height,
GrSurfaceOrigin dst_origin) {
CHECK_NE(texture_id, 0u);
CHECK(shared_context_state->GrContextIsGL());
GrGLTextureInfo texture_info;
texture_info.fID = texture_id;
texture_info.fTarget = target;
// Get the surface color format similar to that in VideoFrameYUVConverter.
texture_info.fFormat = GetSurfaceColorFormat(internal_format, type);
auto backend_texture = GrBackendTextures::MakeGL(
width, height, skgpu::Mipmapped::kNo, texture_info);
auto dest_color_space = SkColorSpace::MakeSRGB();
GrDirectContext* direct_context = shared_context_state->gr_context();
CHECK(direct_context);
return SkSurfaces::WrapBackendTexture(
direct_context, backend_texture, dst_origin,
/*sampleCnt=*/1, GetCompatibleSurfaceColorType(texture_info.fFormat),
dest_color_space, nullptr);
}
bool CopyPixelsToTexture(
GLint xoffset,
GLint yoffset,
GLint x,
GLint y,
GLsizei width,
GLsizei height,
gfx::Rect dest_cleared_rect,
const Mailbox& source_mailbox,
SkiaImageRepresentation* dest_shared_image,
SkiaImageRepresentation::ScopedWriteAccess* dest_scoped_access,
SharedImageRepresentationFactory* representation_factory,
SharedContextState* shared_context_state,
const std::vector<GrBackendSemaphore>& begin_semaphores,
std::vector<GrBackendSemaphore>& end_semaphores) {
auto source_shared_image =
representation_factory->ProduceMemory(source_mailbox);
if (!source_shared_image) {
return false;
}
if (source_shared_image->surface_origin() !=
dest_shared_image->surface_origin()) {
return false;
}
gfx::Size source_size = source_shared_image->size();
gfx::Rect source_rect(x, y, width, height);
if (!gfx::Rect(source_size).Contains(source_rect)) {
return false;
}
auto scoped_read_access = source_shared_image->BeginScopedReadAccess();
if (!scoped_read_access) {
return false;
}
SkPixmap pm = scoped_read_access->pixmap();
SkIRect skIRect = RectToSkIRect(source_rect);
SkPixmap subset;
if (!pm.extractSubset(&subset, skIRect)) {
return false;
}
if (!begin_semaphores.empty()) {
bool result = dest_scoped_access->surface()->wait(
begin_semaphores.size(), begin_semaphores.data(),
/*deleteSemaphoresAfterWait=*/false);
DCHECK(result);
}
dest_scoped_access->surface()->writePixels(subset, xoffset, yoffset);
shared_context_state->FlushWriteAccess(dest_scoped_access);
shared_context_state->SubmitIfNecessary(
std::move(end_semaphores),
dest_scoped_access->NeedGraphiteContextSubmit());
if (!dest_shared_image->IsCleared()) {
dest_shared_image->SetClearedRect(dest_cleared_rect);
}
return true;
}
struct ReadPixelsContext {
std::unique_ptr<const SkImage::AsyncReadResult> async_result;
bool finished = false;
};
void OnReadPixelsDone(
void* raw_ctx,
std::unique_ptr<const SkImage::AsyncReadResult> async_result) {
ReadPixelsContext* context = reinterpret_cast<ReadPixelsContext*>(raw_ctx);
context->async_result = std::move(async_result);
context->finished = true;
}
} // namespace
CopySharedImageHelper::CopySharedImageHelper(
SharedImageRepresentationFactory* representation_factory,
SharedContextState* shared_context_state)
: representation_factory_(representation_factory),
shared_context_state_(shared_context_state) {}
CopySharedImageHelper::~CopySharedImageHelper() = default;
CopySharedImageHelper::GLError::GLError(GLenum gl_error,
std::string function_name,
std::string msg)
: gl_error(gl_error),
function_name(std::move(function_name)),
msg(std::move(msg)) {}
base::expected<void, GLError> CopySharedImageHelper::CopySharedImage(
GLint xoffset,
GLint yoffset,
GLint x,
GLint y,
GLsizei src_width,
GLsizei src_height,
GLsizei dst_width,
GLsizei dst_height,
const volatile GLbyte* mailboxes) {
Mailbox source_mailbox = Mailbox::FromVolatile(
reinterpret_cast<const volatile Mailbox*>(mailboxes)[0]);
DLOG_IF(ERROR, !source_mailbox.Verify())
<< "CopySubTexture was passed an invalid mailbox";
Mailbox dest_mailbox = Mailbox::FromVolatile(
reinterpret_cast<const volatile Mailbox*>(mailboxes)[1]);
DLOG_IF(ERROR, !dest_mailbox.Verify())
<< "CopySubTexture was passed an invalid mailbox";
if (source_mailbox == dest_mailbox) {
return base::unexpected(
GLError(GL_INVALID_OPERATION, "glCopySubTexture",
"source and destination mailboxes are the same"));
}
auto dest_shared_image = representation_factory_->ProduceSkia(
dest_mailbox,
scoped_refptr<gpu::SharedContextState>(shared_context_state_));
if (!dest_shared_image) {
return base::unexpected(
GLError(GL_INVALID_VALUE, "glCopySubTexture", "unknown mailbox"));
}
auto dest_format = dest_shared_image->format();
// Destination shared image cannot prefer external sampler.
if (dest_format.PrefersExternalSampler()) {
return base::unexpected(
GLError(GL_INVALID_VALUE, "glCopySubTexture", "unexpected format"));
}
gfx::Size dest_size = dest_shared_image->size();
gfx::Rect dest_rect(xoffset, yoffset, dst_width, dst_height);
if (!gfx::Rect(dest_size).Contains(dest_rect)) {
return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
"destination texture bad dimensions."));
}
std::vector<GrBackendSemaphore> begin_semaphores;
std::vector<GrBackendSemaphore> end_semaphores;
// Allow uncleared access, as we manually handle clear tracking.
std::unique_ptr<SkiaImageRepresentation::ScopedWriteAccess>
dest_scoped_access = dest_shared_image->BeginScopedWriteAccess(
&begin_semaphores, &end_semaphores,
SharedImageRepresentation::AllowUnclearedAccess::kYes);
if (!dest_scoped_access) {
return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
"Dest shared image is not writable"));
}
bool need_graphite_submit = dest_scoped_access->NeedGraphiteContextSubmit();
// Flush dest surface and submit if necessary before exiting.
absl::Cleanup cleanup = [&]() {
shared_context_state_->FlushWriteAccess(dest_scoped_access.get());
shared_context_state_->SubmitIfNecessary(std::move(end_semaphores),
need_graphite_submit);
};
gfx::Rect new_cleared_rect;
gfx::Rect old_cleared_rect = dest_shared_image->ClearedRect();
if (!gles2::TextureManager::CombineAdjacentRects(old_cleared_rect, dest_rect,
&new_cleared_rect)) {
// No users of RasterDecoder leverage this functionality. Clearing uncleared
// regions could be added here if needed.
return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
"Cannot clear non-combineable rects."));
}
DCHECK(old_cleared_rect.IsEmpty() ||
new_cleared_rect.Contains(old_cleared_rect));
// Attempt to upload directly from CPU shared memory to destination texture.
// Only do this if no scaling is happening.
if (src_width == dst_width && src_height == dst_height &&
CopyPixelsToTexture(xoffset, yoffset, x, y, src_width, src_height,
new_cleared_rect, source_mailbox,
dest_shared_image.get(), dest_scoped_access.get(),
representation_factory_, shared_context_state_,
begin_semaphores, end_semaphores)) {
// Cancel cleanup as CopyPixelsToTexture already handles it.
std::move(cleanup).Cancel();
return base::ok();
}
// Fall back to GPU->GPU copy if src image is not CPU-backed.
auto source_shared_image = representation_factory_->ProduceSkia(
source_mailbox,
scoped_refptr<gpu::SharedContextState>(shared_context_state_));
// In some cases (e.g android video that is promoted to overlay) we can't
// create representation of the valid mailbox. To avoid problems with
// uncleared destination later, we do clear destination rect with black
// color.
if (!source_shared_image) {
auto* canvas = dest_scoped_access->surface()->getCanvas();
SkAutoCanvasRestore autoRestore(canvas, /*doSave=*/true);
canvas->clipRect(gfx::RectToSkRect(dest_rect));
canvas->clear(SkColors::kBlack);
if (!dest_shared_image->IsCleared()) {
dest_shared_image->SetClearedRect(new_cleared_rect);
}
// Note, that we still generate error for the client to indicate there was
// problem.
return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
"unknown source image mailbox."));
}
gfx::Size source_size = source_shared_image->size();
gfx::Rect source_rect(x, y, src_width, src_height);
if (!gfx::Rect(source_size).Contains(source_rect)) {
return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
"source texture bad dimensions."));
}
std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>
source_scoped_access = source_shared_image->BeginScopedReadAccess(
&begin_semaphores, &end_semaphores);
if (!begin_semaphores.empty()) {
GrDirectContext* direct_context = shared_context_state_->gr_context();
bool ret =
direct_context->wait(begin_semaphores.size(), begin_semaphores.data(),
/*deleteSemaphoresAfterWait=*/false);
DCHECK(ret);
}
if (!source_scoped_access) {
return base::unexpected(GLError(GL_INVALID_VALUE, "glCopySubTexture",
"Source shared image is not accessable"));
}
// Update submit is needed by `source_scoped_access`.
need_graphite_submit |= source_scoped_access->NeedGraphiteContextSubmit();
base::expected<void, GLError> result = base::ok();
auto source_image =
source_scoped_access->CreateSkImage(shared_context_state_);
if (!source_image) {
result = base::unexpected(
GLError(GL_INVALID_VALUE, "glCopySubTexture",
"Couldn't create SkImage from source shared image."));
} else {
if (dest_format.is_single_plane()) {
auto* canvas = dest_scoped_access->surface()->getCanvas();
// Reinterpret the source image as being in the destination color space,
// to disable color conversion.
auto source_image_reinterpreted = source_image;
if (canvas->imageInfo().colorSpace()) {
source_image_reinterpreted = source_image->reinterpretColorSpace(
canvas->imageInfo().refColorSpace());
}
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
canvas->drawImageRect(source_image_reinterpreted,
gfx::RectToSkRect(source_rect),
gfx::RectToSkRect(dest_rect), SkSamplingOptions(),
&paint, SkCanvas::kStrict_SrcRectConstraint);
} else {
std::array<SkSurface*, SkYUVAInfo::kMaxPlanes> yuva_sk_surfaces = {};
for (int plane_index = 0; plane_index < dest_format.NumberOfPlanes();
plane_index++) {
// Get surface per plane from destination scoped write access.
yuva_sk_surfaces[plane_index] =
dest_scoped_access->surface(plane_index);
}
// TODO(crbug.com/41380578): This should really default to rec709.
SkYUVColorSpace yuv_color_space = kRec601_SkYUVColorSpace;
dest_shared_image->color_space().ToSkYUVColorSpace(
dest_format.MultiplanarBitDepth(), &yuv_color_space);
SkYUVAInfo yuva_info(gfx::SizeToSkISize(dest_shared_image->size()),
ToSkYUVAPlaneConfig(dest_format),
ToSkYUVASubsampling(dest_format), yuv_color_space);
// Perform skia::BlitRGBAToYUVA for the multiplanar YUV format image.
// TODO(crbug.com/40270413): This will scale the image if the source image
// is smaller than the destination image. What we should actually do
// instead is just blit the destination rect and clear out the rest.
// However, doing that resulted in resulted in pixeltest failures due to
// images having pixel bleeding at their borders when this codepath is
// used by RenderableGMBVideoFramePool (see the bug for details). The
// current behavior of scaling the image matches the legacy
// (non-multiplanar SI) behavior in RenderableGMBVideoFramePool, so it is
// not a regression. Nonetheless, this behavior should
// ideally be changed to that described above for correctness.
if (dst_width != dest_size.width() || dst_height != dest_size.height()) {
dest_rect = gfx::Rect(dest_size);
}
skia::BlitRGBAToYUVA(source_image.get(), yuva_sk_surfaces, yuva_info,
gfx::RectToSkRect(dest_rect), false,
gfx::RectToSkRect(source_rect));
dest_shared_image->SetCleared();
}
if (!dest_shared_image->IsCleared()) {
dest_shared_image->SetClearedRect(new_cleared_rect);
}
}
// Cancel cleanup as the cleanup order is different here.
std::move(cleanup).Cancel();
shared_context_state_->FlushWriteAccess(dest_scoped_access.get());
source_scoped_access->ApplyBackendSurfaceEndState();
shared_context_state_->SubmitIfNecessary(std::move(end_semaphores),
need_graphite_submit);
return result;
}
base::expected<void, GLError> CopySharedImageHelper::CopySharedImageToGLTexture(
GLuint dest_texture_id,
GLenum target,
GLuint internal_format,
GLenum type,
GLint src_x,
GLint src_y,
GLsizei width,
GLsizei height,
GrSurfaceOrigin dst_origin,
const volatile GLbyte* src_mailbox) {
Mailbox source_mailbox = Mailbox::FromVolatile(
reinterpret_cast<const volatile Mailbox*>(src_mailbox)[0]);
DLOG_IF(ERROR, !source_mailbox.Verify())
<< "CopySharedImageToGLTexture was passed an invalid mailbox";
GrDirectContext* direct_context = shared_context_state_->gr_context();
CHECK(direct_context);
sk_sp<SkSurface> dest_surface = CreateSkSurfaceWrappingGLTexture(
shared_context_state_, dest_texture_id, target, internal_format, type,
width, height, dst_origin);
if (!dest_surface) {
return base::unexpected<GLError>(
GLError(GL_INVALID_VALUE, "glCopySharedImageToTexture",
"Cannot create destination surface"));
}
// `dest_rect` always starts from (0, 0).
SkRect dest_rect =
SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
auto source_shared_image = representation_factory_->ProduceSkia(
source_mailbox,
scoped_refptr<gpu::SharedContextState>(shared_context_state_));
// In some cases (e.g android video that is promoted to overlay) we can't
// create representation of the valid mailbox. To avoid problems with
// uncleared destination later, we do clear destination rect with black
// color.
if (!source_shared_image) {
auto* canvas = dest_surface->getCanvas();
SkAutoCanvasRestore autoRestore(canvas, /*doSave=*/true);
canvas->clipRect(dest_rect);
canvas->clear(SkColors::kBlack);
direct_context->flush(dest_surface.get());
shared_context_state_->SubmitIfNecessary(/*signal_semaphores=*/{},
/*need_graphite_submit=*/false);
// Note, that we still generate error for the client to indicate there was
// problem.
return base::unexpected<GLError>(GLError(GL_INVALID_VALUE,
"glCopySharedImageToTexture",
"unknown source image mailbox."));
}
gfx::Size source_size = source_shared_image->size();
gfx::Rect source_rect(src_x, src_y, width, height);
if (!gfx::Rect(source_size).Contains(source_rect)) {
return base::unexpected<GLError>(GLError(GL_INVALID_VALUE,
"glCopySharedImageToTexture",
"source texture bad dimensions."));
}
std::vector<GrBackendSemaphore> begin_semaphores;
std::vector<GrBackendSemaphore> end_semaphores;
std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>
source_scoped_access = source_shared_image->BeginScopedReadAccess(
&begin_semaphores, &end_semaphores);
if (!begin_semaphores.empty()) {
bool ret =
dest_surface->wait(begin_semaphores.size(), begin_semaphores.data(),
/*deleteSemaphoresAfterWait=*/false);
DCHECK(ret);
}
if (!source_scoped_access) {
// We still need to flush surface for begin semaphores above.
direct_context->flush(dest_surface.get());
shared_context_state_->SubmitIfNecessary(std::move(end_semaphores),
/*need_graphite_submit=*/false);
return base::unexpected<GLError>(
GLError(GL_INVALID_VALUE, "glCopySharedImageToTexture",
"Source shared image is not accessable"));
}
base::expected<void, GLError> result = base::ok();
auto source_image =
source_scoped_access->CreateSkImage(shared_context_state_);
if (!source_image) {
result = base::unexpected<GLError>(
GLError(GL_INVALID_VALUE, "glCopySharedImageToTexture",
"Couldn't create SkImage from source shared image."));
} else {
auto* canvas = dest_surface->getCanvas();
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
// Reinterpret the source image as being in the destination color space,
// to disable color conversion.
auto source_image_reinterpreted = source_image;
if (canvas->imageInfo().colorSpace()) {
source_image_reinterpreted = source_image->reinterpretColorSpace(
canvas->imageInfo().refColorSpace());
}
canvas->drawImageRect(
source_image_reinterpreted, gfx::RectToSkRect(source_rect), dest_rect,
SkSamplingOptions(), &paint, SkCanvas::kStrict_SrcRectConstraint);
}
direct_context->flush(dest_surface.get());
source_scoped_access->ApplyBackendSurfaceEndState();
shared_context_state_->SubmitIfNecessary(std::move(end_semaphores),
/*need_graphite_submit=*/false);
return result;
}
namespace {
// Graphite only supports asynchronous reads, and the asynchronous reads require
// that the src rect is contained within the image. This function automatically
// adjusts the parameters to match the permissiveness of Ganesh's
// SkImage::readPixels and makes it synchronous.
bool GraphiteImageReadPixels(GraphiteSharedContext* graphite_shared_context,
sk_sp<SkImage> sk_image,
GrSurfaceOrigin src_surface_origin,
int src_x,
int src_y,
const SkImageInfo& dst_info,
void* pixel_address,
size_t row_bytes) {
gfx::Rect src_rect(src_x, src_y, dst_info.width(), dst_info.height());
gfx::Rect src_image_bounds(sk_image->width(), sk_image->height());
// TODO(crbug.com/40942998): Once all src rects are required to be contained
// in the image, the !Contains branch can be removed.
if (!src_image_bounds.Contains(src_rect)) {
src_rect.Intersect(src_image_bounds);
if (src_rect.IsEmpty()) {
// NOTE: This is consistent with SkImage::readPixels on a Ganesh image,
// which permits src_rect to not be fully contained, but can't be disjoint
return false;
}
// Adjust the pixel address to account for any intersection, so that the
// available content remains aligned with the intended dst pixel data.
// When `src_rect` was originally contained in the src image bounds, this
// is equal to the original `pixel_address`.
uint8_t* subset_pixel_addr =
static_cast<uint8_t*>(pixel_address) +
(src_rect.y() - src_y) * row_bytes +
(src_rect.x() - src_x) * dst_info.bytesPerPixel();
SkImageInfo subset_dst_info =
dst_info.makeWH(src_rect.width(), src_rect.height());
// src_rect.Intersect(src_image_bounds) should ensure this call skips the
// !Contains branch and actually reads the pixels. Check here to prevent
// infinite recursion.
CHECK(src_image_bounds.Contains(src_rect));
return GraphiteImageReadPixels(graphite_shared_context, std::move(sk_image),
src_surface_origin, src_rect.x(),
src_rect.y(), subset_dst_info,
subset_pixel_addr, row_bytes);
}
// Now that `src_rect` meets the requirements of the asyncRead API, call the
// async function, then submit and block until it's completed.
CHECK(graphite_shared_context);
ReadPixelsContext context;
#if !defined(SK_GRAPHITE_READ_PIXELS_SUPPORTS_BOTTOM_LEFT)
// Make the `src_rect` relative to the bottom left origin if needed. This
// works around lack of support for bottom-left origin data during readback in
// Graphite. Graphite correctly handles bottom-left origins when rendering, so
// if asyncRescaleAndReadPixels() were to render `sk_image` for any reason,
// this adjustment won't work. But this call isn't scaling and is presumably
// for a shared image that has copy-src usage, so it shouldn't happen. This is
// also a no-op when the src rect is the entire image, regardless of origin.
if (src_surface_origin == kBottomLeft_GrSurfaceOrigin) {
src_y = src_image_bounds.height() - src_rect.y() - src_rect.height();
src_rect = gfx::Rect(
src_rect.x(), src_y, src_rect.width(), src_rect.height());
// This adjustment should not change the fact that src_rect is still valid
CHECK(src_image_bounds.Contains(src_rect));
}
#endif
// We don't need to insert a recording since asyncRescaleAndReadPixels is a
// context operation that inserts its own recording internally.
if (!graphite_shared_context->asyncRescaleAndReadPixelsAndSubmit(
sk_image.get(), dst_info, RectToSkIRect(src_rect),
SkImage::RescaleGamma::kSrc, SkImage::RescaleMode::kRepeatedLinear,
base::BindOnce(&OnReadPixelsDone), &context)) {
return false;
}
CHECK(context.finished);
if (!context.async_result) {
return false;
}
#if !defined(SK_GRAPHITE_READ_PIXELS_SUPPORTS_BOTTOM_LEFT)
// Use CopyPlane to flip as Graphite doesn't support bottom left origin
// images. Using a negative height causes CopyPlane to flip while copying.
// TODO(crbug.com/40269891): Remove this if Graphite performs the flip
// once it supports bottom left origin images.
const int height = src_surface_origin == kTopLeft_GrSurfaceOrigin
? dst_info.height()
: -dst_info.height();
#else
// Must copy the async results (often a GPU-mapped buffer) to the CPU
// pixel_address.
const int height = dst_info.height();
#endif
libyuv::CopyPlane(static_cast<const uint8_t*>(context.async_result->data(0)),
context.async_result->rowBytes(0),
static_cast<uint8_t*>(pixel_address), row_bytes,
dst_info.width() * dst_info.bytesPerPixel(), height);
return true;
}
} // anonymous namespace
base::expected<void, GLError> CopySharedImageHelper::ReadPixels(
GLint src_x,
GLint src_y,
GLint plane_index,
GLuint row_bytes,
SkImageInfo dst_info,
void* pixel_address,
std::unique_ptr<SkiaImageRepresentation> source_shared_image) {
std::vector<GrBackendSemaphore> begin_semaphores;
std::vector<GrBackendSemaphore> end_semaphores;
std::unique_ptr<SkiaImageRepresentation::ScopedReadAccess>
source_scoped_access = source_shared_image->BeginScopedReadAccess(
&begin_semaphores, &end_semaphores);
if (!source_scoped_access) {
return base::unexpected(GLError(GL_INVALID_VALUE, "glReadbackImagePixels",
"Source shared image is not accessible"));
}
auto* gr_context = shared_context_state_->gr_context();
if (!begin_semaphores.empty()) {
CHECK(gr_context);
bool wait_result =
gr_context->wait(begin_semaphores.size(), begin_semaphores.data(),
/*deleteSemaphoresAfterWait=*/false);
DCHECK(wait_result);
}
sk_sp<SkImage> sk_image;
if (source_shared_image->format().is_single_plane() ||
source_shared_image->format().PrefersExternalSampler()) {
// Create SkImage without plane index for single planar formats or legacy
// multiplanar formats with external sampler.
sk_image = source_scoped_access->CreateSkImage(shared_context_state_);
} else {
// Pass plane index for creating an SkImage for multiplanar formats.
sk_image = source_scoped_access->CreateSkImageForPlane(
plane_index, shared_context_state_);
}
if (!sk_image) {
source_scoped_access->ApplyBackendSurfaceEndState();
shared_context_state_->SubmitIfNecessary(
std::move(end_semaphores),
source_scoped_access->NeedGraphiteContextSubmit());
return base::unexpected(GLError(GL_INVALID_OPERATION,
"glReadbackImagePixels",
"Couldn't create SkImage for reading."));
}
// TODO(crbug.com/40942998): Add back src_rect validation once renderer passes
// a correct rect size.
bool success = false;
if (gr_context) {
success = sk_image->readPixels(gr_context, dst_info, pixel_address,
row_bytes, src_x, src_y);
source_scoped_access->ApplyBackendSurfaceEndState();
shared_context_state_->SubmitIfNecessary(
std::move(end_semaphores),
/*need_graphite_shared_context_submit==*/false);
} else {
auto* graphite_shared_context =
shared_context_state_->graphite_shared_context();
success =
GraphiteImageReadPixels(graphite_shared_context, std::move(sk_image),
source_shared_image->surface_origin(), src_x,
src_y, dst_info, pixel_address, row_bytes);
}
if (!success) {
return base::unexpected(GLError(GL_INVALID_OPERATION,
"glReadbackImagePixels",
"Failed to read pixels from SkImage"));
}
return base::ok();
}
base::expected<void, GLError> CopySharedImageHelper::WritePixelsYUV(
GLuint src_width,
GLuint src_height,
std::array<SkPixmap, SkYUVAInfo::kMaxPlanes> pixmaps,
std::vector<GrBackendSemaphore> end_semaphores,
std::unique_ptr<SkiaImageRepresentation> dest_shared_image,
std::unique_ptr<SkiaImageRepresentation::ScopedWriteAccess>
dest_scoped_access) {
// Order of destruction for function arguments is not specified, but the
// ScopedWriteAccess must be destroyed before representation; so perform a
// Cleanup before exiting.
absl::Cleanup cleanup = [&]() { dest_scoped_access.reset(); };
viz::SharedImageFormat dest_format = dest_shared_image->format();
auto* gr_context = shared_context_state_->gr_context();
const bool need_graphite_submit =
dest_scoped_access->NeedGraphiteContextSubmit();
for (int plane = 0; plane < dest_format.NumberOfPlanes(); plane++) {
bool written = false;
if (gr_context) {
written = gr_context->updateBackendTexture(
dest_scoped_access->promise_image_texture(plane)->backendTexture(),
&pixmaps[plane], /*numLevels=*/1, dest_shared_image->surface_origin(),
/*finishedProc=*/nullptr, /*finishedContext=*/nullptr);
} else {
CHECK(shared_context_state_->graphite_shared_context());
auto graphite_texture_ref =
dest_scoped_access->graphite_texture_holder(plane);
auto* graphite_texture_ptr = graphite_texture_ref.release();
using graphite_texture_ptr_type = decltype(graphite_texture_ptr);
auto release_proc = [](void* context, skgpu::CallbackResult) {
static_cast<graphite_texture_ptr_type>(context)->Release();
};
written = shared_context_state_->gpu_main_graphite_recorder()
->updateBackendTexture(
graphite_texture_ptr->texture(), &pixmaps[plane],
/*numLevels=*/1, release_proc, graphite_texture_ptr);
}
if (!written) {
dest_scoped_access->ApplyBackendSurfaceEndState();
shared_context_state_->SubmitIfNecessary(std::move(end_semaphores),
need_graphite_submit);
return base::unexpected(
GLError(GL_INVALID_OPERATION, "glWritePixelsYUV",
"Failed to upload pixels to dest shared image"));
}
}
shared_context_state_->FlushWriteAccess(dest_scoped_access.get());
shared_context_state_->SubmitIfNecessary(std::move(end_semaphores),
need_graphite_submit);
if (!dest_shared_image->IsCleared()) {
dest_shared_image->SetClearedRect(gfx::Rect(src_width, src_height));
}
return base::ok();
}
} // namespace gpu