bool SurfaceContext::internalWritePixels()

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;
}