Add SkSurface::asyncReadPixels()

Initial version. Current limitations: No Metal support, no color space
conversions, for each src color type only one dst color type is legal (
which may or may not be the src color type), no alpha type conversions.

Bug: skia:8962

Change-Id: I6f046a32342b8f5ffb1799d67d7ba15c250ef9bf
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/212981
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
This commit is contained in:
Brian Salomon 2019-05-10 14:24:50 -04:00 committed by Skia Commit-Bot
parent 6e407986cd
commit ab32f65653
20 changed files with 459 additions and 17 deletions

View File

@ -306,6 +306,8 @@ skia_gpu_sources = [
"$_src/gpu/ops/GrTessellatingPathRenderer.h",
"$_src/gpu/ops/GrTextureOp.cpp",
"$_src/gpu/ops/GrTextureOp.h",
"$_src/gpu/ops/GrTransferFromOp.cpp",
"$_src/gpu/ops/GrTransferFromOp.h",
"$_src/gpu/effects/GrCoverageSetOpXP.cpp",
"$_src/gpu/effects/GrCoverageSetOpXP.h",

View File

@ -660,6 +660,33 @@ public:
*/
bool readPixels(const SkBitmap& dst, int srcX, int srcY);
/** Makes pixel data available to caller, possibly asynchronously.
Currently asynchronous reads are only supported on the GPU backend and only when the
underlying 3D API supports transfer buffers and CPU/GPU synchronization primitives. In all
other cases this operates synchronously.
When the pixel data is ready the caller's ReadPixelsCallback is called with a pointer to
the data in the requested color type, alpha type, and color space. The data pointer is
only valid for the duration of the callback.
Upon failure the the callback is called with nullptr as the data pointer.
If the src rectangle is not contained by the bounds of the surface then failure occurs.
@param ct color type of the read data
@param at alpha type of the read data
@param cs color space of the read data
@param srcRect a subrectangle of the surface to read
@param callback function to call with result of the read.
@param context passed to callback.
*/
using ReadPixelsContext = void*;
using ReadPixelsCallback = void(ReadPixelsContext, const void* data, size_t rowBytes);
void asyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
const SkIRect& srcRect, ReadPixelsCallback callback,
ReadPixelsContext context);
/** Copies SkRect of pixels from the src SkPixmap to the SkSurface.
Source SkRect corners are (0, 0) and (src.width(), src.height()).

View File

@ -33,6 +33,9 @@ public:
// GrGpuRenderTargetCommandBuffer.
virtual void copy(GrSurface* src, GrSurfaceOrigin srcOrigin,
const SkIRect& srcRect, const SkIPoint& dstPoint) = 0;
// Initiates a transfer from the surface owned by the command buffer to the GrGpuBuffer.
virtual void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) = 0;
virtual void insertEventMarker(const char*) = 0;

View File

@ -5,6 +5,7 @@
* found in the LICENSE file.
*/
#include "src/gpu/GrRenderTargetContext.h"
#include "include/core/SkDrawable.h"
#include "include/gpu/GrBackendSemaphore.h"
#include "include/gpu/GrRenderTarget.h"
@ -31,7 +32,6 @@
#include "src/gpu/GrPathRenderer.h"
#include "src/gpu/GrQuad.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrRenderTargetContext.h"
#include "src/gpu/GrRenderTargetContextPriv.h"
#include "src/gpu/GrResourceProvider.h"
#include "src/gpu/GrShape.h"
@ -60,6 +60,7 @@
#include "src/gpu/ops/GrStencilPathOp.h"
#include "src/gpu/ops/GrStrokeRectOp.h"
#include "src/gpu/ops/GrTextureOp.h"
#include "src/gpu/ops/GrTransferFromOp.h"
#include "src/gpu/text/GrTextContext.h"
#include "src/gpu/text/GrTextTarget.h"
@ -1735,6 +1736,114 @@ void GrRenderTargetContext::drawDrawable(std::unique_ptr<SkDrawable::GpuDrawHand
this->getRTOpList()->addOp(std::move(op), *this->caps());
}
bool GrRenderTargetContext::asyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
const SkIRect& srcRect, ReadPixelsCallback callback,
ReadPixelsContext context) {
auto direct = fContext->priv().asDirectContext();
if (!direct) {
return false;
}
if (!this->caps()->transferBufferSupport()) {
return false;
}
if (fRenderTargetProxy->wrapsVkSecondaryCB()) {
return false;
}
// We currently don't know our own alpha type, we assume it's premul if we have an alpha channel
// and opaque otherwise.
if (!GrPixelConfigIsAlphaOnly(fRenderTargetProxy->config()) && at != kPremul_SkAlphaType) {
return false;
}
// TODO(bsalomon): Enhance support for reading to different color types.
auto dstCT = SkColorTypeToGrColorType(ct);
auto readCT = this->caps()->supportedReadPixelsColorType(fRenderTargetProxy->config(), dstCT);
if (readCT != dstCT) {
return false;
}
if (!this->caps()->transferFromOffsetAlignment(readCT)) {
return false;
}
// TODO(bsalomon): Support color space conversion.
if (!SkColorSpace::Equals(cs.get(), this->colorSpaceInfo().colorSpace())) {
return false;
}
// Insert a draw to a temporary surface if we need to do a y-flip (and in future for a color
// space conversion.)
if (this->origin() == kBottomLeft_GrSurfaceOrigin) {
sk_sp<GrTextureProxy> texProxy = sk_ref_sp(fRenderTargetProxy->asTextureProxy());
const auto& backendFormat = fRenderTargetProxy->backendFormat();
SkRect srcRectToDraw = SkRect::Make(srcRect);
// If the src is not texturable first try to make a copy to a texture.
if (!texProxy) {
GrSurfaceDesc desc;
desc.fWidth = srcRect.width();
desc.fHeight = srcRect.height();
desc.fConfig = fRenderTargetProxy->config();
auto sContext = direct->priv().makeDeferredSurfaceContext(
backendFormat, desc, this->origin(), GrMipMapped::kNo, SkBackingFit::kApprox,
SkBudgeted::kNo, this->colorSpaceInfo().refColorSpace());
if (!sContext) {
return false;
}
if (!sContext->copy(fRenderTargetProxy.get(), srcRect, {0, 0})) {
return false;
}
texProxy = sk_ref_sp(sContext->asTextureProxy());
SkASSERT(texProxy);
srcRectToDraw = SkRect::MakeWH(srcRect.width(), srcRect.height());
}
auto rtc = direct->priv().makeDeferredRenderTargetContext(
backendFormat, SkBackingFit::kApprox, srcRect.width(), srcRect.height(),
fRenderTargetProxy->config(), cs, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin);
if (!rtc) {
return false;
}
rtc->drawTexture(GrNoClip(), std::move(texProxy), GrSamplerState::Filter::kNearest,
SkBlendMode::kSrc, SK_PMColor4fWHITE, srcRectToDraw,
SkRect::MakeWH(srcRect.width(), srcRect.height()), GrAA::kNo,
GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(),
/* colorSpaceXform = */ nullptr);
return rtc->asyncReadPixels(ct, at, std::move(cs),
SkIRect::MakeWH(srcRect.width(), srcRect.height()), callback,
context);
}
size_t rowBytes = GrColorTypeBytesPerPixel(dstCT) * srcRect.width();
size_t size = rowBytes * srcRect.height();
auto buffer = direct->priv().resourceProvider()->createBuffer(
size, GrGpuBufferType::kXferGpuToCpu, GrAccessPattern::kStream_GrAccessPattern);
if (!buffer) {
return false;
}
this->getRTOpList()->addOp(GrTransferFromOp::Make(fContext, srcRect, dstCT, buffer, 0),
*this->caps());
struct FinishContext {
ReadPixelsCallback* fClientCallback;
ReadPixelsContext fClientContext;
sk_sp<GrGpuBuffer> fBuffer;
size_t fRowBytes;
};
// 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, context, buffer, rowBytes};
auto finishCallback = [](GrGpuFinishedContext c) {
auto context = reinterpret_cast<const FinishContext*>(c);
void* data = context->fBuffer->map();
(*context->fClientCallback)(context->fClientContext, data, data ? context->fRowBytes : 0);
if (data) {
context->fBuffer->unmap();
}
delete context;
};
GrFlushInfo flushInfo;
flushInfo.fFinishedContext = finishContext;
flushInfo.fFinishedProc = finishCallback;
this->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
return true;
}
GrSemaphoresSubmitted GrRenderTargetContext::flush(SkSurface::BackendSurfaceAccess access,
const GrFlushInfo& info) {
ASSERT_SINGLE_OWNER

View File

@ -402,6 +402,11 @@ public:
*/
void drawDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>, const SkRect& bounds);
using ReadPixelsCallback = SkSurface::ReadPixelsCallback;
using ReadPixelsContext = SkSurface::ReadPixelsContext;
bool asyncReadPixels(SkColorType, SkAlphaType, sk_sp<SkColorSpace>, const SkIRect& srcRect,
ReadPixelsCallback, ReadPixelsContext);
/**
* After this returns any pending surface IO will be issued to the backend 3D API and
* if the surface has MSAA it will be resolved.

View File

@ -26,6 +26,12 @@ public:
fGpu->copySurface(fTexture, fOrigin, src, srcOrigin, srcRect, dstPoint);
}
void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) override {
fGpu->transferPixelsFrom(fTexture, srcRect.fLeft, srcRect.fTop, srcRect.width(),
srcRect.height(), bufferColorType, transferBuffer, offset);
}
void insertEventMarker(const char* msg) override {
fGpu->insertEventMarker(msg);
}
@ -67,6 +73,12 @@ public:
fGpu->copySurface(fRenderTarget, fOrigin, src, srcOrigin, srcRect, dstPoint);
}
void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) override {
fGpu->transferPixelsFrom(fRenderTarget, srcRect.fLeft, srcRect.fTop, srcRect.width(),
srcRect.height(), bufferColorType, transferBuffer, offset);
}
void set(GrRenderTarget*, GrSurfaceOrigin,
const GrGpuRTCommandBuffer::LoadAndStoreInfo&,
const GrGpuRTCommandBuffer::StencilLoadAndStoreInfo&);

View File

@ -21,6 +21,8 @@ public:
void copy(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
const SkIPoint& dstPoint) override {}
void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) override {}
void insertEventMarker(const char*) override {}
private:
@ -42,6 +44,8 @@ public:
void end() override {}
void copy(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
const SkIPoint& dstPoint) override {}
void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) override {}
int numDraws() const { return fNumDraws; }

View File

@ -33,7 +33,11 @@ public:
const SkIPoint& dstPoint) override {
fGpu->copySurface(fTexture, fOrigin, src, srcOrigin, srcRect, dstPoint);
}
void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) override {
fGpu->transferPixelsFrom(fTexture, srcRect.fLeft, srcRect.fTop, srcRect.width(),
srcRect.height(), bufferColorType, transferBuffer, offset);
}
void insertEventMarker(const char* msg) override {}
private:
@ -62,7 +66,8 @@ public:
// TODO: this could be more efficient
state->doUpload(upload);
}
void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) override;
void copy(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
const SkIPoint& dstPoint) override;

View File

@ -96,6 +96,15 @@ void GrMtlGpuRTCommandBuffer::copy(GrSurface* src, GrSurfaceOrigin srcOrigin,
fGpu->copySurface(fRenderTarget, fOrigin, src, srcOrigin, srcRect, dstPoint);
}
void GrMtlGpuRTCommandBuffer::transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) {
// We cannot have an active encoder when we call transferFrom since it requires its own
// command encoder.
SkASSERT(nil == fActiveRenderCmdEncoder);
fGpu->transferPixelsFrom(fRenderTarget, srcRect.fLeft, srcRect.fTop, srcRect.width(),
srcRect.height(), bufferColorType, transferBuffer, offset);
}
GrMtlPipelineState* GrMtlGpuRTCommandBuffer::prepareDrawState(
const GrPrimitiveProcessor& primProc,
const GrPipeline& pipeline,

View File

@ -30,12 +30,12 @@ public:
#ifdef SK_DEBUG
SkString dumpInfo() const override {
SkString string;
string.append(INHERITED::dumpInfo());
string.printf("srcProxyID: %d,\n"
"srcRect: [ L: %d, T: %d, R: %d, B: %d ], dstPt: [ X: %d, Y: %d ]\n",
fSrc.get()->uniqueID().asUInt(),
fSrcRect.fLeft, fSrcRect.fTop, fSrcRect.fRight, fSrcRect.fBottom,
fDstPoint.fX, fDstPoint.fY);
string = INHERITED::dumpInfo();
string.appendf(
"srcProxyID: %d,\n"
"srcRect: [ L: %d, T: %d, R: %d, B: %d ], dstPt: [ X: %d, Y: %d ]\n",
fSrc.get()->uniqueID().asUInt(), fSrcRect.fLeft, fSrcRect.fTop, fSrcRect.fRight,
fSrcRect.fBottom, fDstPoint.fX, fDstPoint.fY);
return string;
}
#endif

View File

@ -0,0 +1,28 @@
/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/ops/GrTransferFromOp.h"
#include "include/private/GrRecordingContext.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrGpuCommandBuffer.h"
#include "src/gpu/GrMemoryPool.h"
#include "src/gpu/GrRecordingContextPriv.h"
std::unique_ptr<GrOp> GrTransferFromOp::Make(GrRecordingContext* context,
const SkIRect& srcRect,
GrColorType dstColorType,
sk_sp<GrGpuBuffer> dstBuffer,
size_t dstOffset) {
SkASSERT(context->priv().caps()->transferFromOffsetAlignment(dstColorType));
SkASSERT(dstOffset % context->priv().caps()->transferFromOffsetAlignment(dstColorType) == 0);
GrOpMemoryPool* pool = context->priv().opMemoryPool();
return pool->allocate<GrTransferFromOp>(srcRect, dstColorType, std::move(dstBuffer), dstOffset);
}
void GrTransferFromOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) {
state->commandBuffer()->transferFrom(fSrcRect, fDstColorType, fDstBuffer.get(), fDstOffset);
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrTransferFromOp_DEFINED
#define GrTransferFromOp_DEFINED
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/ops/GrOp.h"
/**
* Does a transfer from the surface context's surface to a transfer buffer. It is assumed
* that the caller has checked the GrCaps to ensure this transfer is legal.
*/
class GrTransferFromOp final : public GrOp {
public:
DEFINE_OP_CLASS_ID
static std::unique_ptr<GrOp> Make(GrRecordingContext*,
const SkIRect& srcRect,
GrColorType dstColorType,
sk_sp<GrGpuBuffer> dstBuffer,
size_t dstOffset);
const char* name() const override { return "TransferFromOp"; }
#ifdef SK_DEBUG
SkString dumpInfo() const override {
SkString string;
string = INHERITED::dumpInfo();
string.appendf(
"bufferID:: %d offset: %zu, color type: %d\n"
"srcRect: [ L: %d, T: %d, R: %d, B: %d ]\n",
fDstBuffer->uniqueID().asUInt(), fDstOffset, fDstColorType, fSrcRect.fLeft,
fSrcRect.fTop, fSrcRect.fRight, fSrcRect.fBottom);
return string;
}
#endif
private:
friend class GrOpMemoryPool; // for ctor
GrTransferFromOp(const SkIRect& srcRect,
GrColorType dstColorType,
sk_sp<GrGpuBuffer> dstBuffer,
size_t dstOffset)
: INHERITED(ClassID())
, fDstBuffer(std::move(dstBuffer))
, fDstOffset(dstOffset)
, fSrcRect(srcRect)
, fDstColorType(dstColorType) {
this->setBounds(SkRect::Make(srcRect), HasAABloat::kNo, IsZeroArea::kNo);
}
void onPrepare(GrOpFlushState*) override {}
void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
sk_sp<GrGpuBuffer> fDstBuffer;
size_t fDstOffset;
SkIRect fSrcRect;
GrColorType fDstColorType;
typedef GrOp INHERITED;
};
#endif

View File

@ -649,7 +649,6 @@ bool GrVkPrimaryCommandBuffer::finished(const GrVkGpu* gpu) {
VkResult err = GR_VK_CALL(gpu->vkInterface(), GetFenceStatus(gpu->device(), fSubmitFence));
switch (err) {
case VK_SUCCESS:
fFinishedProcs.reset();
return true;
case VK_NOT_READY:
@ -672,6 +671,7 @@ void GrVkPrimaryCommandBuffer::onReleaseResources(GrVkGpu* gpu) {
for (int i = 0; i < fSecondaryCommandBuffers.count(); ++i) {
fSecondaryCommandBuffers[i]->releaseResources(gpu);
}
fFinishedProcs.reset();
}
void GrVkPrimaryCommandBuffer::recycleSecondaryCommandBuffers() {

View File

@ -36,7 +36,6 @@ class InlineUpload : public GrVkPrimaryCommandBufferTask {
public:
InlineUpload(GrOpFlushState* state, const GrDeferredTextureUploadFn& upload)
: fFlushState(state), fUpload(upload) {}
~InlineUpload() override = default;
void execute(const Args& args) override { fFlushState->doUpload(fUpload); }
@ -54,7 +53,6 @@ public:
, fSrcRect(srcRect)
, fDstPoint(dstPoint)
, fShouldDiscardDst(shouldDiscardDst) {}
~Copy() override = default;
void execute(const Args& args) override {
args.fGpu->copySurface(args.fSurface, args.fOrigin, fSrc.get(), fSrcOrigin, fSrcRect,
@ -70,6 +68,28 @@ private:
bool fShouldDiscardDst;
};
class TransferFrom : public GrVkPrimaryCommandBufferTask {
public:
TransferFrom(const SkIRect& srcRect, GrColorType bufferColorType, GrGpuBuffer* transferBuffer,
size_t offset)
: fTransferBuffer(sk_ref_sp(transferBuffer))
, fOffset(offset)
, fSrcRect(srcRect)
, fBufferColorType(bufferColorType) {}
void execute(const Args& args) override {
args.fGpu->transferPixelsFrom(args.fSurface, fSrcRect.fLeft, fSrcRect.fTop,
fSrcRect.width(), fSrcRect.height(), fBufferColorType,
fTransferBuffer.get(), fOffset);
}
private:
sk_sp<GrGpuBuffer> fTransferBuffer;
size_t fOffset;
SkIRect fSrcRect;
GrColorType fBufferColorType;
};
} // anonymous namespace
/////////////////////////////////////////////////////////////////////////////
@ -79,6 +99,11 @@ void GrVkGpuTextureCommandBuffer::copy(GrSurface* src, GrSurfaceOrigin srcOrigin
fTasks.emplace<Copy>(src, srcOrigin, srcRect, dstPoint, false);
}
void GrVkGpuTextureCommandBuffer::transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) {
fTasks.emplace<TransferFrom>(srcRect, bufferColorType, transferBuffer, offset);
}
void GrVkGpuTextureCommandBuffer::insertEventMarker(const char* msg) {
// TODO: does Vulkan have a correlate?
}
@ -620,6 +645,16 @@ void GrVkGpuRTCommandBuffer::copy(GrSurface* src, GrSurfaceOrigin srcOrigin, con
}
}
void GrVkGpuRTCommandBuffer::transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) {
CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
if (!cbInfo.fIsEmpty) {
this->addAdditionalRenderPass();
}
fPreCommandBufferTasks.emplace<TransferFrom>(srcRect, bufferColorType, transferBuffer, offset);
++fCommandBufferInfos[fCurrentCmdInfo].fNumPreCmds;
}
////////////////////////////////////////////////////////////////////////////////
void GrVkGpuRTCommandBuffer::bindGeometry(const GrGpuBuffer* indexBuffer,

View File

@ -48,6 +48,8 @@ public:
void copy(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
const SkIPoint& dstPoint) override;
void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) override;
void insertEventMarker(const char*) override;
@ -81,6 +83,8 @@ public:
void copy(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
const SkIPoint& dstPoint) override;
void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
GrGpuBuffer* transferBuffer, size_t offset) override;
void executeDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>) override;

View File

@ -5,12 +5,13 @@
* found in the LICENSE file.
*/
#include <atomic>
#include "include/core/SkCanvas.h"
#include "include/core/SkFontLCDConfig.h"
#include "include/gpu/GrBackendSurface.h"
#include "src/core/SkAutoPixmapStorage.h"
#include "src/core/SkImagePriv.h"
#include "src/image/SkSurface_Base.h"
#include <atomic>
static SkPixelGeometry compute_default_geometry() {
SkFontLCDConfig::LCDOrder order = SkFontLCDConfig::GetSubpixelOrder();
@ -86,6 +87,19 @@ void SkSurface_Base::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPa
}
}
void SkSurface_Base::onAsyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
const SkIRect& rect, ReadPixelsCallback callback,
ReadPixelsContext context) {
auto info = SkImageInfo::Make(rect.width(), rect.height(), ct, at, std::move(cs));
SkAutoPixmapStorage pm;
pm.alloc(info);
if (this->readPixels(pm, rect.fLeft, rect.fTop)) {
callback(context, pm.addr(), pm.rowBytes());
} else {
callback(context, nullptr, 0);
}
}
bool SkSurface_Base::outstandingImageSnapshot() const {
return fCachedImage && !fCachedImage->unique();
}
@ -207,6 +221,18 @@ bool SkSurface::readPixels(const SkBitmap& bitmap, int srcX, int srcY) {
return bitmap.peekPixels(&pm) && this->readPixels(pm, srcX, srcY);
}
void SkSurface::asyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
const SkIRect& srcRect, ReadPixelsCallback callback,
ReadPixelsContext context) {
auto dstII = SkImageInfo::Make(srcRect.width(), srcRect.height(), ct, at, cs);
if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) ||
!SkImageInfoIsValid(dstII)) {
callback(context, nullptr, 0);
return;
}
asSB(this)->onAsyncReadPixels(ct, at, std::move(cs), srcRect, callback, context);
}
void SkSurface::writePixels(const SkPixmap& pmap, int x, int y) {
if (pmap.addr() == nullptr || pmap.width() <= 0 || pmap.height() <= 0) {
return;

View File

@ -45,6 +45,13 @@ public:
virtual void onWritePixels(const SkPixmap&, int x, int y) = 0;
/**
* Default implementation does a synchronous read and calls the callback.
*/
virtual void onAsyncReadPixels(SkColorType, SkAlphaType, sk_sp<SkColorSpace>,
const SkIRect& srcRect, ReadPixelsCallback callback,
ReadPixelsContext context);
/**
* Default implementation:
*

View File

@ -132,6 +132,19 @@ void SkSurface_Gpu::onWritePixels(const SkPixmap& src, int x, int y) {
fDevice->writePixels(src, x, y);
}
void SkSurface_Gpu::onAsyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
const SkIRect& srcRect, ReadPixelsCallback callback,
ReadPixelsContext context) {
auto* rtc = fDevice->accessRenderTargetContext();
if (!rtc->caps()->transferBufferSupport()) {
INHERITED::onAsyncReadPixels(ct, at, cs, srcRect, callback, context);
return;
}
if (!rtc->asyncReadPixels(ct, at, std::move(cs), srcRect, callback, context)) {
callback(context, nullptr, 0);
}
}
// Create a new render target and, if necessary, copy the contents of the old
// render target into it. Note that this flushes the SkGpuDevice but
// doesn't force an OpenGL flush.

View File

@ -30,6 +30,9 @@ public:
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override;
sk_sp<SkImage> onNewImageSnapshot(const SkIRect* subset) override;
void onWritePixels(const SkPixmap&, int x, int y) override;
void onAsyncReadPixels(SkColorType, SkAlphaType, sk_sp<SkColorSpace>, const SkIRect& rect,
ReadPixelsCallback, ReadPixelsContext) override;
void onCopyOnWrite(ContentChangeMode) override;
void onDiscard() override;
GrSemaphoresSubmitted onFlush(BackendSurfaceAccess access, const GrFlushInfo& info) override;

View File

@ -8,20 +8,19 @@
#include <initializer_list>
#include "include/core/SkCanvas.h"
#include "include/core/SkSurface.h"
#include "include/gpu/GrContext.h"
#include "include/private/SkColorData.h"
#include "include/private/SkHalf.h"
#include "include/private/SkImageInfoPriv.h"
#include "src/core/SkAutoPixmapStorage.h"
#include "src/core/SkMathPriv.h"
#include "tests/Test.h"
#include "include/gpu/GrContext.h"
#include "src/gpu/GrContextPriv.h"
#include "src/gpu/GrProxyProvider.h"
#include "src/gpu/SkGr.h"
#include "tests/Test.h"
#include "tools/gpu/GrContextFactory.h"
#include "tools/gpu/ProxyUtils.h"
static const int DEV_W = 100, DEV_H = 100;
static const SkIRect DEV_RECT = SkIRect::MakeWH(DEV_W, DEV_H);
static const SkRect DEV_RECT_S = SkRect::MakeWH(DEV_W * SK_Scalar1,
@ -688,3 +687,84 @@ DEF_TEST(ReadPixels_ValidConversion, reporter) {
}
}
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(AsyncReadPixels, reporter, ctxInfo) {
struct Context {
SkPixmap* fPixmap = nullptr;
bool fSuceeded = false;
bool fCalled = false;
};
auto callback = [](SkSurface::ReleaseContext context, const void* data, size_t rowBytes) {
auto* pm = static_cast<Context*>(context)->fPixmap;
static_cast<Context*>(context)->fCalled = true;
if ((static_cast<Context*>(context)->fSuceeded = SkToBool(data))) {
auto dst = static_cast<char*>(pm->writable_addr());
const auto* src = static_cast<const char*>(data);
for (int y = 0; y < pm->height(); ++y, src += rowBytes, dst += pm->rowBytes()) {
memcpy(dst, src, pm->width() * SkColorTypeBytesPerPixel(pm->colorType()));
}
}
};
for (auto origin : {kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin}) {
static constexpr int kW = 16;
static constexpr int kH = 16;
for (int c = 0; c <= kLastEnum_SkColorType; ++c) {
auto ct = static_cast<SkColorType>(c);
auto info = SkImageInfo::Make(kW, kH, ct, kPremul_SkAlphaType, nullptr);
auto surf = SkSurface::MakeRenderTarget(ctxInfo.grContext(), SkBudgeted::kNo, info, 1,
origin, nullptr);
if (!surf) {
continue;
}
float d = std::sqrt((float)surf->width() * surf->width() +
(float)surf->height() * surf->height());
for (int j = 0; j < surf->height(); ++j) {
for (int i = 0; i < surf->width(); ++i) {
float r = i / (float)surf->width();
float g = 1.f - i / (float)surf->height();
float b = std::sqrt((float)i * i + (float)j * j) / d;
SkPaint paint;
paint.setColor4f(SkColor4f{r, g, b, 1.f}, nullptr);
surf->getCanvas()->drawRect(SkRect::MakeXYWH(i, j, 1, 1), paint);
}
}
for (const auto& rect : {SkIRect::MakeWH(kW, kH), // full size
SkIRect::MakeLTRB(1, 2, kW - 3, kH - 4), // partial
SkIRect::MakeXYWH(1, 1, 0, 0), // zero size - fail
SkIRect::MakeWH(kW + 1, kH / 2)}) { // too big - fail
SkAutoPixmapStorage pixmap;
Context context;
context.fPixmap = &pixmap;
info = SkImageInfo::Make(rect.width(), rect.height(), ct, kPremul_SkAlphaType,
nullptr);
pixmap.alloc(info);
memset(pixmap.writable_addr(), 0xAB, pixmap.computeByteSize());
surf->asyncReadPixels(ct, kPremul_SkAlphaType, nullptr, rect, callback, &context);
while (!context.fCalled) {
ctxInfo.grContext()->checkAsyncWorkCompletion();
}
if (rect.isEmpty() || !SkIRect::MakeWH(kW, kH).contains(rect)) {
REPORTER_ASSERT(reporter, !context.fSuceeded);
}
if (context.fSuceeded) {
// We use a synchronous read as the source of truth.
SkAutoPixmapStorage ref;
ref.alloc(info);
memset(ref.writable_addr(), 0xCD, ref.computeByteSize());
if (!surf->readPixels(ref, rect.fLeft, rect.fTop)) {
continue;
}
const auto* a = static_cast<const char*>(pixmap.addr());
const auto* b = static_cast<const char*>(ref.addr());
for (int j = 0; j < pixmap.height();
++j, a += pixmap.rowBytes(), b += ref.rowBytes()) {
if (memcmp(a, b, pixmap.width() * SkColorTypeBytesPerPixel(ct))) {
ERRORF(reporter, "Failed. CT: %d, j: %d", ct, j);
break;
}
}
}
}
}
}
}