in src/gpu/ganesh/SurfaceContext.cpp [364:587]
bool SurfaceContext::internalWritePixels(GrDirectContext* dContext,
const GrCPixmap src[],
int numLevels,
SkIPoint pt) {
GR_CREATE_TRACE_MARKER_CONTEXT("SurfaceContext", "internalWritePixels", fContext);
SkASSERT(numLevels >= 1);
SkASSERT(src);
// We can either write to a subset or write MIP levels, but not both.
SkASSERT((src[0].dimensions() == this->dimensions() && pt.isZero()) || numLevels == 1);
SkASSERT(numLevels == 1 || (this->asTextureProxy() &&
this->asTextureProxy()->mipmapped() == skgpu::Mipmapped::kYes));
// Our public caller should have clipped to the bounds of the surface already.
SkASSERT(SkIRect::MakeSize(this->dimensions()).contains(
SkIRect::MakePtSize(pt, src[0].dimensions())));
if (!dContext) {
return false;
}
if (this->asSurfaceProxy()->readOnly()) {
return false;
}
if (src[0].colorType() == GrColorType::kUnknown) {
return false;
}
if (!alpha_types_compatible(src[0].alphaType(), this->colorInfo().alphaType())) {
return false;
}
GrSurfaceProxy* dstProxy = this->asSurfaceProxy();
if (dstProxy->framebufferOnly()) {
return false;
}
if (!dstProxy->instantiate(dContext->priv().resourceProvider())) {
return false;
}
GrSurface* dstSurface = dstProxy->peekSurface();
SkColorSpaceXformSteps::Flags flags =
SkColorSpaceXformSteps{src[0].colorInfo(), this->colorInfo()}.fFlags;
bool unpremul = flags.unpremul,
needColorConversion = flags.linearize || flags.gamut_transform || flags.encode,
premul = flags.premul;
const GrCaps* caps = dContext->priv().caps();
auto rgbaDefaultFormat = caps->getDefaultBackendFormat(GrColorType::kRGBA_8888,
GrRenderable::kNo);
GrColorType dstColorType = this->colorInfo().colorType();
// For canvas2D putImageData performance we have a special code path for unpremul RGBA_8888 srcs
// that are premultiplied on the GPU. This is kept as narrow as possible for now.
bool canvas2DFastPath = !caps->avoidWritePixelsFastPath() && premul && !needColorConversion &&
(src[0].colorType() == GrColorType::kRGBA_8888 ||
src[0].colorType() == GrColorType::kBGRA_8888) &&
this->asFillContext() &&
(dstColorType == GrColorType::kRGBA_8888 ||
dstColorType == GrColorType::kBGRA_8888) &&
rgbaDefaultFormat.isValid() &&
dContext->priv().validPMUPMConversionExists();
// Since the validPMUPMConversionExists function actually submits work to the gpu to do its
// tests, it is possible that during that call we have abanoned the context. Thus we do an
// abanoned check here to make sure we are still valid.
RETURN_FALSE_IF_ABANDONED
// Drawing code path doesn't support writing to levels and doesn't support inserting layout
// transitions.
if ((!caps->surfaceSupportsWritePixels(dstSurface) || canvas2DFastPath) && numLevels == 1) {
GrColorInfo tempColorInfo;
GrBackendFormat format;
skgpu::Swizzle tempReadSwizzle;
if (canvas2DFastPath) {
tempColorInfo = {GrColorType::kRGBA_8888,
kUnpremul_SkAlphaType,
this->colorInfo().refColorSpace()};
format = rgbaDefaultFormat;
} else {
tempColorInfo = this->colorInfo();
format = dstProxy->backendFormat().makeTexture2D();
if (!format.isValid()) {
return false;
}
tempReadSwizzle = this->readSwizzle();
}
// It is more efficient for us to write pixels into a top left origin so we prefer that.
// However, if the final proxy isn't a render target then we must use a copy to move the
// data into it which requires the origins to match. If the final proxy is a render target
// we can use a draw instead which doesn't have this origin restriction. Thus for render
// targets we will use top left and otherwise we will make the origins match.
GrSurfaceOrigin tempOrigin =
this->asFillContext() ? kTopLeft_GrSurfaceOrigin : this->origin();
auto tempProxy = dContext->priv().proxyProvider()->createProxy(
format,
src[0].dimensions(),
GrRenderable::kNo,
1,
skgpu::Mipmapped::kNo,
SkBackingFit::kApprox,
skgpu::Budgeted::kYes,
GrProtected::kNo,
/*label=*/"SurfaceContext_InternalWritePixels");
if (!tempProxy) {
return false;
}
GrSurfaceProxyView tempView(tempProxy, tempOrigin, tempReadSwizzle);
SurfaceContext tempCtx(dContext, tempView, tempColorInfo);
// In the fast path we always write the srcData to the temp context as though it were RGBA.
// When the data is really BGRA the write will cause the R and B channels to be swapped in
// the intermediate surface which gets corrected by a swizzle effect when drawing to the
// dst.
GrCPixmap origSrcBase = src[0];
GrCPixmap srcBase = origSrcBase;
if (canvas2DFastPath) {
srcBase = GrCPixmap(origSrcBase.info().makeColorType(GrColorType::kRGBA_8888),
origSrcBase.addr(),
origSrcBase.rowBytes());
}
if (!tempCtx.writePixels(dContext, srcBase, {0, 0})) {
return false;
}
if (this->asFillContext()) {
std::unique_ptr<GrFragmentProcessor> fp;
if (canvas2DFastPath) {
fp = dContext->priv().createUPMToPMEffect(
GrTextureEffect::Make(std::move(tempView), tempColorInfo.alphaType()));
// Important: check the original src color type here!
if (origSrcBase.colorType() == GrColorType::kBGRA_8888) {
fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), skgpu::Swizzle::BGRA());
}
} else {
fp = GrTextureEffect::Make(std::move(tempView), tempColorInfo.alphaType());
}
if (!fp) {
return false;
}
this->asFillContext()->fillRectToRectWithFP(
SkIRect::MakeSize(srcBase.dimensions()),
SkIRect::MakePtSize(pt, srcBase.dimensions()),
std::move(fp));
} else {
SkIRect srcRect = SkIRect::MakeSize(srcBase.dimensions());
SkIPoint dstPoint = SkIPoint::Make(pt.fX, pt.fY);
if (!this->copy(std::move(tempProxy), srcRect, dstPoint)) {
return false;
}
}
return true;
}
GrColorType srcColorType = src[0].colorType();
auto [allowedColorType, _] =
caps->supportedWritePixelsColorType(this->colorInfo().colorType(),
dstProxy->backendFormat(),
srcColorType);
bool flip = this->origin() == kBottomLeft_GrSurfaceOrigin;
bool convertAll = premul ||
unpremul ||
needColorConversion ||
flip ||
(srcColorType != allowedColorType);
bool mustBeTight = !caps->writePixelsRowBytesSupport();
size_t tmpSize = 0;
if (mustBeTight || convertAll) {
for (int i = 0; i < numLevels; ++i) {
if (convertAll || (mustBeTight && src[i].rowBytes() != src[i].info().minRowBytes())) {
tmpSize += src[i].info().makeColorType(allowedColorType).minRowBytes()*
src[i].height();
}
}
}
auto tmpData = tmpSize ? SkData::MakeUninitialized(tmpSize) : nullptr;
void* tmp = tmpSize ? tmpData->writable_data() : nullptr;
AutoSTArray<15, GrMipLevel> srcLevels(numLevels);
bool ownAllStorage = true;
for (int i = 0; i < numLevels; ++i) {
if (convertAll || (mustBeTight && src[i].rowBytes() != src[i].info().minRowBytes())) {
GrImageInfo tmpInfo(allowedColorType,
this->colorInfo().alphaType(),
this->colorInfo().refColorSpace(),
src[i].dimensions());
auto tmpRB = tmpInfo.minRowBytes();
GrPixmap tmpPM(tmpInfo, tmp, tmpRB);
SkAssertResult(GrConvertPixels(tmpPM, src[i], flip));
srcLevels[i] = {tmpPM.addr(), tmpPM.rowBytes(), tmpData};
tmp = SkTAddOffset<void>(tmp, tmpRB*tmpPM.height());
} else {
srcLevels[i] = {src[i].addr(), src[i].rowBytes(), src[i].pixelStorage()};
ownAllStorage &= src[i].ownsPixels();
}
}
pt.fY = flip ? dstSurface->height() - pt.fY - src[0].height() : pt.fY;
if (!dContext->priv().drawingManager()->newWritePixelsTask(
sk_ref_sp(dstProxy),
SkIRect::MakePtSize(pt, src[0].dimensions()),
allowedColorType,
this->colorInfo().colorType(),
srcLevels.begin(),
numLevels)) {
return false;
}
if (numLevels > 1) {
dstProxy->asTextureProxy()->markMipmapsClean();
}
if (!ownAllStorage) {
// If any pixmap doesn't own its pixels then we must flush so that the pixels are pushed to
// the GPU before we return.
dContext->priv().flushSurface(dstProxy);
}
return true;
}