in src/gpu/ganesh/SurfaceContext.cpp [733:1034]
void SurfaceContext::asyncRescaleAndReadPixelsYUV420(GrDirectContext* dContext,
SkYUVColorSpace yuvColorSpace,
bool readAlpha,
sk_sp<SkColorSpace> dstColorSpace,
const SkIRect& srcRect,
SkISize dstSize,
RescaleGamma rescaleGamma,
RescaleMode rescaleMode,
ReadPixelsCallback callback,
ReadPixelsContext callbackContext) {
using AsyncReadResult = skgpu::TAsyncReadResult<GrGpuBuffer, GrDirectContext::DirectContextID,
PixelTransferResult>;
SkASSERT(srcRect.fLeft >= 0 && srcRect.fRight <= this->width());
SkASSERT(srcRect.fTop >= 0 && srcRect.fBottom <= this->height());
SkASSERT(!dstSize.isZero());
SkASSERT((dstSize.width() % 2 == 0) && (dstSize.height() % 2 == 0));
if (!dContext) {
callback(callbackContext, nullptr);
return;
}
auto rt = this->asRenderTargetProxy();
if (rt && rt->wrapsVkSecondaryCB()) {
callback(callbackContext, nullptr);
return;
}
if (rt && rt->framebufferOnly()) {
callback(callbackContext, nullptr);
return;
}
if (this->asSurfaceProxy()->isProtected() == GrProtected::kYes) {
callback(callbackContext, nullptr);
return;
}
int x = srcRect.fLeft;
int y = srcRect.fTop;
bool needsRescale = srcRect.size() != dstSize ||
!SkColorSpace::Equals(this->colorInfo().colorSpace(), dstColorSpace.get());
GrSurfaceProxyView srcView = this->readSurfaceView();
if (needsRescale) {
auto info = SkImageInfo::Make(dstSize,
kRGBA_8888_SkColorType,
this->colorInfo().alphaType(),
dstColorSpace);
// TODO: Incorporate the YUV conversion into last pass of rescaling.
auto tempFC = this->rescale(info,
kTopLeft_GrSurfaceOrigin,
srcRect,
rescaleGamma,
rescaleMode);
if (!tempFC) {
callback(callbackContext, nullptr);
return;
}
SkASSERT(SkColorSpace::Equals(tempFC->colorInfo().colorSpace(), info.colorSpace()));
SkASSERT(tempFC->origin() == kTopLeft_GrSurfaceOrigin);
x = y = 0;
srcView = tempFC->readSurfaceView();
} else if (!srcView.asTextureProxy()) {
srcView = GrSurfaceProxyView::Copy(
fContext,
std::move(srcView),
skgpu::Mipmapped::kNo,
srcRect,
SkBackingFit::kApprox,
skgpu::Budgeted::kYes,
/*label=*/"SurfaceContext_AsyncRescaleAndReadPixelsYUV420");
if (!srcView) {
// If we can't get a texture copy of the contents then give up.
callback(callbackContext, nullptr);
return;
}
SkASSERT(srcView.asTextureProxy());
x = y = 0;
}
auto yaInfo = SkImageInfo::MakeA8(dstSize);
auto yFC = dContext->priv().makeSFCWithFallback(yaInfo, SkBackingFit::kApprox,
/* sampleCount= */ 1,
skgpu::Mipmapped::kNo, skgpu::Protected::kNo);
std::unique_ptr<SurfaceFillContext> aFC;
if (readAlpha) {
aFC = dContext->priv().makeSFCWithFallback(yaInfo, SkBackingFit::kApprox,
/* sampleCount= */ 1,
skgpu::Mipmapped::kNo, skgpu::Protected::kNo);
}
auto uvInfo = yaInfo.makeWH(yaInfo.width()/2, yaInfo.height()/2);
auto uFC = dContext->priv().makeSFCWithFallback(uvInfo, SkBackingFit::kApprox,
/* sampleCount= */ 1,
skgpu::Mipmapped::kNo, skgpu::Protected::kNo);
auto vFC = dContext->priv().makeSFCWithFallback(uvInfo, SkBackingFit::kApprox,
/* sampleCount= */ 1,
skgpu::Mipmapped::kNo, skgpu::Protected::kNo);
if (!yFC || !uFC || !vFC || (readAlpha && !aFC)) {
callback(callbackContext, nullptr);
return;
}
float baseM[20];
SkColorMatrix_RGB2YUV(yuvColorSpace, baseM);
// TODO: Use one transfer buffer for all three planes to reduce map/unmap cost?
auto texMatrix = SkMatrix::Translate(x, y);
auto [readCT, offsetAlignment] =
this->caps()->supportedReadPixelsColorType(yFC->colorInfo().colorType(),
yFC->asSurfaceProxy()->backendFormat(),
GrColorType::kAlpha_8);
if (readCT == GrColorType::kUnknown) {
callback(callbackContext, nullptr);
return;
}
bool doSynchronousRead = !this->caps()->transferFromSurfaceToBufferSupport() ||
!offsetAlignment;
PixelTransferResult yTransfer, aTransfer, uTransfer, vTransfer;
// This matrix generates (r,g,b,a) = (0, 0, 0, y)
float yM[20];
std::fill_n(yM, 15, 0.f);
std::copy_n(baseM + 0, 5, yM + 15);
auto yFP = GrTextureEffect::Make(srcView, this->colorInfo().alphaType(), texMatrix);
yFP = GrFragmentProcessor::ColorMatrix(std::move(yFP),
yM,
/*unpremulInput=*/false,
/*clampRGBOutput=*/true,
/*premulOutput=*/false);
yFC->fillWithFP(std::move(yFP));
if (!doSynchronousRead) {
yTransfer = yFC->transferPixels(GrColorType::kAlpha_8,
SkIRect::MakeSize(yFC->dimensions()));
if (!yTransfer.fTransferBuffer) {
callback(callbackContext, nullptr);
return;
}
}
if (readAlpha) {
auto aFP = GrTextureEffect::Make(srcView, this->colorInfo().alphaType(), texMatrix);
SkASSERT(baseM[15] == 0 &&
baseM[16] == 0 &&
baseM[17] == 0 &&
baseM[18] == 1 &&
baseM[19] == 0);
aFC->fillWithFP(std::move(aFP));
if (!doSynchronousRead) {
aTransfer = aFC->transferPixels(GrColorType::kAlpha_8,
SkIRect::MakeSize(aFC->dimensions()));
if (!aTransfer.fTransferBuffer) {
callback(callbackContext, nullptr);
return;
}
}
}
texMatrix.preScale(2.f, 2.f);
// This matrix generates (r,g,b,a) = (0, 0, 0, u)
float uM[20];
std::fill_n(uM, 15, 0.f);
std::copy_n(baseM + 5, 5, uM + 15);
auto uFP = GrTextureEffect::Make(srcView,
this->colorInfo().alphaType(),
texMatrix,
GrSamplerState::Filter::kLinear);
uFP = GrFragmentProcessor::ColorMatrix(std::move(uFP),
uM,
/*unpremulInput=*/false,
/*clampRGBOutput=*/true,
/*premulOutput=*/false);
uFC->fillWithFP(std::move(uFP));
if (!doSynchronousRead) {
uTransfer = uFC->transferPixels(GrColorType::kAlpha_8,
SkIRect::MakeSize(uFC->dimensions()));
if (!uTransfer.fTransferBuffer) {
callback(callbackContext, nullptr);
return;
}
}
// This matrix generates (r,g,b,a) = (0, 0, 0, v)
float vM[20];
std::fill_n(vM, 15, 0.f);
std::copy_n(baseM + 10, 5, vM + 15);
auto vFP = GrTextureEffect::Make(std::move(srcView),
this->colorInfo().alphaType(),
texMatrix,
GrSamplerState::Filter::kLinear);
vFP = GrFragmentProcessor::ColorMatrix(std::move(vFP),
vM,
/*unpremulInput=*/false,
/*clampRGBOutput=*/true,
/*premulOutput=*/false);
vFC->fillWithFP(std::move(vFP));
if (!doSynchronousRead) {
vTransfer = vFC->transferPixels(GrColorType::kAlpha_8,
SkIRect::MakeSize(vFC->dimensions()));
if (!vTransfer.fTransferBuffer) {
callback(callbackContext, nullptr);
return;
}
}
if (doSynchronousRead) {
GrPixmap yPmp = GrPixmap::Allocate(yaInfo);
GrPixmap uPmp = GrPixmap::Allocate(uvInfo);
GrPixmap vPmp = GrPixmap::Allocate(uvInfo);
GrPixmap aPmp;
if (readAlpha) {
aPmp = GrPixmap::Allocate(yaInfo);
}
if (!yFC->readPixels(dContext, yPmp, {0, 0}) ||
!uFC->readPixels(dContext, uPmp, {0, 0}) ||
!vFC->readPixels(dContext, vPmp, {0, 0}) ||
(readAlpha && !aFC->readPixels(dContext, aPmp, {0, 0}))) {
callback(callbackContext, nullptr);
return;
}
auto result = std::make_unique<AsyncReadResult>(dContext->directContextID());
result->addCpuPlane(yPmp.pixelStorage(), yPmp.rowBytes());
result->addCpuPlane(uPmp.pixelStorage(), uPmp.rowBytes());
result->addCpuPlane(vPmp.pixelStorage(), vPmp.rowBytes());
if (readAlpha) {
result->addCpuPlane(aPmp.pixelStorage(), aPmp.rowBytes());
}
callback(callbackContext, std::move(result));
return;
}
struct FinishContext {
ReadPixelsCallback* fClientCallback;
ReadPixelsContext fClientContext;
GrClientMappedBufferManager* fMappedBufferManager;
SkISize fSize;
PixelTransferResult fYTransfer;
PixelTransferResult fUTransfer;
PixelTransferResult fVTransfer;
PixelTransferResult fATransfer;
};
// Assumption is that the caller would like to flush. We could take a parameter or require an
// explicit flush from the caller. We'd have to have a way to defer attaching the finish
// callback to GrGpu until after the next flush that flushes our op list, though.
auto* finishContext = new FinishContext{callback,
callbackContext,
dContext->priv().clientMappedBufferManager(),
dstSize,
std::move(yTransfer),
std::move(uTransfer),
std::move(vTransfer),
std::move(aTransfer)};
auto finishCallback = [](GrGpuFinishedContext c) {
const auto* context = reinterpret_cast<const FinishContext*>(c);
auto manager = context->fMappedBufferManager;
auto result = std::make_unique<AsyncReadResult>(manager->ownerID());
if (!result->addTransferResult(context->fYTransfer,
context->fSize,
context->fYTransfer.fRowBytes,
manager)) {
(*context->fClientCallback)(context->fClientContext, nullptr);
delete context;
return;
}
SkISize uvSize = {context->fSize.width() / 2, context->fSize.height() / 2};
if (!result->addTransferResult(context->fUTransfer,
uvSize,
context->fUTransfer.fRowBytes,
manager)) {
(*context->fClientCallback)(context->fClientContext, nullptr);
delete context;
return;
}
if (!result->addTransferResult(context->fVTransfer,
uvSize,
context->fVTransfer.fRowBytes,
manager)) {
(*context->fClientCallback)(context->fClientContext, nullptr);
delete context;
return;
}
if (context->fATransfer.fTransferBuffer &&
!result->addTransferResult(context->fATransfer,
context->fSize,
context->fATransfer.fRowBytes,
manager)) {
(*context->fClientCallback)(context->fClientContext, nullptr);
delete context;
return;
}
(*context->fClientCallback)(context->fClientContext, std::move(result));
delete context;
};
GrFlushInfo flushInfo;
flushInfo.fFinishedContext = finishContext;
flushInfo.fFinishedProc = finishCallback;
dContext->priv().flushSurface(
this->asSurfaceProxy(), SkSurfaces::BackendSurfaceAccess::kNoAccess, flushInfo);
}