Reuse GrTexture instances when the same GrBackendTexture is used to

repeatedly fulfill a promise SkImage.

Bug: skia:8613

Change-Id: I35c76435d630d2daa034e0c3efb59666bfd6882a
Reviewed-on: https://skia-review.googlesource.com/c/175820
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Brian Salomon 2019-01-10 10:23:44 -05:00 committed by Skia Commit-Bot
parent cc10c4342e
commit 559c617137
20 changed files with 992 additions and 241 deletions

View File

@ -1975,7 +1975,10 @@ Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString
// This is here bc this is the first point where we have access to the context
promiseImageHelper.uploadAllToGPU(context);
// We draw N times, with a clear between.
// We draw N times, with a clear between. Between each run we invalidate and delete half of
// the textures backing promise images. So half the images exercise reusing a cached
// GrTexture and the other half exercise the case whem the client provides a different
// backing texture in fulfill.
for (int replay = 0; replay < fNumReplays; ++replay) {
if (replay > 0) {
// Clear the drawing of the previous replay
@ -1994,6 +1997,11 @@ Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString
// This drops the promiseImageHelper's refs on all the promise images if we're in
// the last run.
promiseImageHelper.reset();
} else {
// This ought to ensure that all promise image textures from the last pass are
// released.
context->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
promiseImageHelper.replaceEveryOtherPromiseTexture(context);
}
// Fourth, synchronously render the display lists into the dest tiles

View File

@ -207,6 +207,7 @@ skia_core_sources = [
"$_src/core/SkPixmap.cpp",
"$_src/core/SkPoint.cpp",
"$_src/core/SkPoint3.cpp",
"$_src/core/SkPromiseImageTexture.cpp",
"$_src/core/SkPtrRecorder.cpp",
"$_src/core/SkQuadClipper.cpp",
"$_src/core/SkQuadClipper.h",
@ -388,6 +389,7 @@ skia_core_sources = [
"$_include/core/SkPoint.h",
"$_include/core/SkPoint3.h",
"$_include/core/SkPreConfig.h",
"$_include/core/SkPromiseImageTexture.h",
"$_include/core/SkRect.h",
"$_include/core/SkRefCnt.h",
"$_include/core/SkRegion.h",

View File

@ -5,22 +5,21 @@
* found in the LICENSE file.
*/
#ifndef SkDeferredDisplayListMaker_DEFINED
#define SkDeferredDisplayListMaker_DEFINED
#ifndef SkDeferredDisplayListRecorder_DEFINED
#define SkDeferredDisplayListRecorder_DEFINED
#include "../private/SkDeferredDisplayList.h"
#include "SkImageInfo.h"
#include "SkRefCnt.h"
#include "SkSurfaceCharacterization.h"
#include "SkTypes.h"
#include "../private/SkDeferredDisplayList.h"
class GrBackendFormat;
class GrBackendTexture;
class GrContext;
class SkCanvas;
class SkImage;
class SkPromiseImageTexture;
class SkSurface;
struct SkYUVAIndex;
struct SkYUVASizeInfo;
@ -53,11 +52,17 @@ public:
std::unique_ptr<SkDeferredDisplayList> detach();
// Matches the defines in SkImage_GpuBase.h
typedef void* TextureContext;
typedef void (*TextureReleaseProc)(TextureContext textureContext);
typedef void (*TextureFulfillProc)(TextureContext textureContext, GrBackendTexture* outTexture);
typedef void (*PromiseDoneProc)(TextureContext textureContext);
using PromiseImageTextureContext = void*;
using PromiseImageTextureFulfillProc = SkPromiseImageTexture* (*)(PromiseImageTextureContext);
using PromiseImageTextureReleaseProc = void (*)(PromiseImageTextureContext,
const SkPromiseImageTexture*);
using PromiseImageTextureDoneProc = void (*)(PromiseImageTextureContext);
// Deprecated types. To be removed.
using LegacyPromiseImageTextureFulfillProc = void (*)(PromiseImageTextureContext,
GrBackendTexture*);
using LegacyPromiseImageTextureReleaseProc = void (*)(PromiseImageTextureContext);
using TextureContext = PromiseImageTextureContext;
/**
Create a new SkImage that is very similar to an SkImage created by MakeFromTexture. The main
@ -112,10 +117,23 @@ public:
SkColorType colorType,
SkAlphaType alphaType,
sk_sp<SkColorSpace> colorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContext);
PromiseImageTextureFulfillProc textureFulfillProc,
PromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContext);
/** Deprecated variant of above. */
sk_sp<SkImage> makePromiseTexture(const GrBackendFormat& backendFormat,
int width,
int height,
GrMipMapped mipMapped,
GrSurfaceOrigin origin,
SkColorType colorType,
SkAlphaType alphaType,
sk_sp<SkColorSpace> colorSpace,
LegacyPromiseImageTextureFulfillProc textureFulfillProc,
LegacyPromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContext);
/**
This entry point operates the same as 'makePromiseTexture' except that its
@ -132,12 +150,25 @@ public:
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContexts[]);
PromiseImageTextureFulfillProc textureFulfillProc,
PromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContexts[]);
/** Deprecated variant of above. */
sk_sp<SkImage> makeYUVAPromiseTexture(SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
const SkISize yuvaSizes[],
const SkYUVAIndex yuvaIndices[4],
int imageWidth,
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
LegacyPromiseImageTextureFulfillProc textureFulfillProc,
LegacyPromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContexts[]);
private:
private:
bool init();
const SkSurfaceCharacterization fCharacterization;

View File

@ -0,0 +1,50 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkPromiseImageTexture_DEFINED
#define SkPromiseImageTexture_DEFINED
#include "../private/GrResourceKey.h"
#include "GrBackendSurface.h"
#if SK_SUPPORT_GPU
/**
* This type is used to fulfill textures for PromiseImages. Once an instance is returned from a
* PromiseImageTextureFulfillProc it must remain valid until the corresponding
* PromiseImageTextureReleaseProc is called. For performance reasons it is recommended that the
* the client reuse a single PromiseImageTexture every time a given texture is returned by
* the PromiseImageTextureFulfillProc rather than recreating PromiseImageTextures representing
* the same underlying backend API texture.
*/
class SK_API SkPromiseImageTexture {
public:
SkPromiseImageTexture() = default;
SkPromiseImageTexture(const SkPromiseImageTexture&) = delete;
explicit SkPromiseImageTexture(const GrBackendTexture& backendTexture);
SkPromiseImageTexture(SkPromiseImageTexture&&);
~SkPromiseImageTexture();
SkPromiseImageTexture& operator=(const SkPromiseImageTexture&) = delete;
SkPromiseImageTexture& operator=(SkPromiseImageTexture&&);
const GrBackendTexture& backendTexture() const { return fBackendTexture; }
bool isValid() const { return SkToBool(fUniqueID); }
void addKeyToInvalidate(uint32_t contextID, const GrUniqueKey& key);
uint32_t uniqueID() const { return fUniqueID; }
#if GR_TEST_UTILS
SkTArray<GrUniqueKey> testingOnly_uniqueKeysToInvalidate() const;
#endif
private:
SkSTArray<1, GrUniqueKeyInvalidatedMessage> fMessages;
GrBackendTexture fBackendTexture;
uint32_t fUniqueID = SK_InvalidUniqueID;
static std::atomic<uint32_t> gUniqueID;
};
#endif
#endif

View File

@ -56,13 +56,16 @@ protected:
}
GrResourceKey& operator=(const GrResourceKey& that) {
SkASSERT(that.isValid());
if (this != &that) {
size_t bytes = that.size();
SkASSERT(SkIsAlign4(bytes));
fKey.reset(SkToInt(bytes / sizeof(uint32_t)));
memcpy(fKey.get(), that.fKey.get(), bytes);
this->validate();
if (!that.isValid()) {
this->reset();
} else {
size_t bytes = that.size();
SkASSERT(SkIsAlign4(bytes));
fKey.reset(SkToInt(bytes / sizeof(uint32_t)));
memcpy(fKey.get(), that.fKey.get(), bytes);
this->validate();
}
}
return *this;
}
@ -146,6 +149,7 @@ private:
size_t internalSize() const { return fKey[kDomainAndSize_MetaDataIdx] >> 16; }
void validate() const {
SkASSERT(this->isValid());
SkASSERT(fKey[kHash_MetaDataIdx] ==
GrResourceKeyHash(&fKey[kHash_MetaDataIdx] + 1,
this->internalSize() - sizeof(uint32_t)));
@ -327,6 +331,7 @@ static inline void gr_init_static_unique_key_once(SkAlignedSTStorage<1, GrUnique
// The cache listens for these messages to purge junk resources proactively.
class GrUniqueKeyInvalidatedMessage {
public:
GrUniqueKeyInvalidatedMessage() = default;
GrUniqueKeyInvalidatedMessage(const GrUniqueKey& key, uint32_t contextUniqueID)
: fKey(key), fContextID(contextUniqueID) {
SkASSERT(SK_InvalidUniqueID != contextUniqueID);
@ -341,7 +346,7 @@ public:
private:
GrUniqueKey fKey;
uint32_t fContextID;
uint32_t fContextID = SK_InvalidUniqueID;
};
static inline bool SkShouldPostMessageToBus(const GrUniqueKeyInvalidatedMessage& msg,

View File

@ -1389,17 +1389,23 @@ static inline GrPixelConfig GrColorTypeToPixelConfig(GrColorType config,
return kUnknown_GrPixelConfig;
}
class GrReleaseProcHelper : public SkWeakRefCnt {
class GrReleaseProcHelper : public SkRefCnt {
public:
// These match the definitions in SkImage, from whence they came
typedef void* ReleaseCtx;
typedef void (*ReleaseProc)(ReleaseCtx);
GrReleaseProcHelper(ReleaseProc proc, ReleaseCtx ctx) : fReleaseProc(proc), fReleaseCtx(ctx) {}
~GrReleaseProcHelper() override {}
~GrReleaseProcHelper() override {
if (fReleaseProc) {
fReleaseProc(fReleaseCtx);
}
}
void weak_dispose() const override {
void callAndClear() {
fReleaseProc(fReleaseCtx);
fReleaseProc = nullptr;
fReleaseCtx = nullptr;
}
private:

View File

@ -498,6 +498,7 @@
"~^GrPipelineDynamicStateTest$",
"~^InitialTextureClear$",
"~^PromiseImageTest$",
"~^PromiseImageTextureReuse$",
"~^ResourceAllocatorTest$",
"~^RGB565TextureTest$",
"~^RGBA4444TextureTest$",

View File

@ -765,6 +765,7 @@ def dm_flags(api, bot):
match.append('~^GrPipelineDynamicStateTest$')
match.append('~^InitialTextureClear$')
match.append('~^PromiseImageTest$')
match.append('~^PromiseImageTextureReuse$')
match.append('~^ResourceAllocatorTest$')
match.append('~^RGB565TextureTest$')
match.append('~^RGBA4444TextureTest$')

View File

@ -6,7 +6,7 @@
*/
#include "SkDeferredDisplayListRecorder.h"
#include "SkMessageBus.h"
#include "SkDeferredDisplayList.h"
#include "SkSurface.h"
#include "SkSurfaceCharacterization.h"
@ -31,26 +31,58 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture(
SkColorType colorType,
SkAlphaType alphaType,
sk_sp<SkColorSpace> colorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContext) {
PromiseImageTextureFulfillProc textureFulfillProc,
PromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContext) {
return nullptr;
}
sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture(
const GrBackendFormat& backendFormat,
int width,
int height,
GrMipMapped mipMapped,
GrSurfaceOrigin origin,
SkColorType colorType,
SkAlphaType alphaType,
sk_sp<SkColorSpace> colorSpace,
LegacyPromiseImageTextureFulfillProc textureFulfillProc,
LegacyPromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContext) {
return nullptr;
}
sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
const SkISize yuvaSizes[],
const SkYUVAIndex yuvaIndices[4],
int imageWidth,
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContexts[]) {
SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
const SkISize yuvaSizes[],
const SkYUVAIndex yuvaIndices[4],
int imageWidth,
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
PromiseImageTextureFulfillProc textureFulfillProc,
PromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContexts[]) {
return nullptr;
}
sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
const SkISize yuvaSizes[],
const SkYUVAIndex yuvaIndices[4],
int imageWidth,
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
LegacyPromiseImageTextureFulfillProc textureFulfillProc,
LegacyPromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContexts[]) {
return nullptr;
}
@ -60,10 +92,11 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
#include "GrProxyProvider.h"
#include "GrRenderTargetContext.h"
#include "GrTexture.h"
#include "SkGr.h"
#include "SkImage_Gpu.h"
#include "SkImage_GpuYUVA.h"
#include "SkMakeUnique.h"
#include "SkPromiseImageTexture.h"
#include "SkSurface_Gpu.h"
#include "SkYUVASizeInfo.h"
@ -216,10 +249,10 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture(
SkColorType colorType,
SkAlphaType alphaType,
sk_sp<SkColorSpace> colorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContext) {
PromiseImageTextureFulfillProc textureFulfillProc,
PromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContext) {
if (!fContext) {
return nullptr;
}
@ -235,10 +268,107 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture(
std::move(colorSpace),
textureFulfillProc,
textureReleaseProc,
promiseDoneProc,
textureDoneProc,
textureContext);
}
// Converts from the old legacy APIs based on GrBackendTexture to the new implementation based on
// PromiseImageTexture.
static void wrap_legacy(
SkDeferredDisplayListRecorder::LegacyPromiseImageTextureFulfillProc textureFulfillProc,
SkDeferredDisplayListRecorder::LegacyPromiseImageTextureReleaseProc textureReleaseProc,
SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc textureDoneProc,
const SkDeferredDisplayListRecorder::PromiseImageTextureContext textureContexts[],
int numTextures,
SkDeferredDisplayListRecorder::PromiseImageTextureFulfillProc* wrappedFulfillProc,
SkDeferredDisplayListRecorder::PromiseImageTextureReleaseProc* wrappedReleaseProc,
SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc* wrappedDoneProc,
SkDeferredDisplayListRecorder::PromiseImageTextureContext wrappedTextureContext[]) {
struct WrapperContext {
SkDeferredDisplayListRecorder::LegacyPromiseImageTextureFulfillProc fLegacyFulfill;
SkDeferredDisplayListRecorder::LegacyPromiseImageTextureReleaseProc fLegacyRelease;
SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc fDone;
SkDeferredDisplayListRecorder::PromiseImageTextureContext fOriginalContext;
std::unique_ptr<SkPromiseImageTexture> fPromiseImageTexture;
};
*wrappedFulfillProc = [](SkDeferredDisplayListRecorder::PromiseImageTextureContext context) {
auto* wc = static_cast<WrapperContext*>(context);
GrBackendTexture backendTexture;
wc->fLegacyFulfill(wc->fOriginalContext, &backendTexture);
wc->fPromiseImageTexture = skstd::make_unique<SkPromiseImageTexture>(backendTexture);
return wc->fPromiseImageTexture.get();
};
*wrappedReleaseProc = [](SkDeferredDisplayListRecorder::PromiseImageTextureContext context,
const SkPromiseImageTexture*) {
auto* wc = static_cast<WrapperContext*>(context);
wc->fLegacyRelease(wc->fOriginalContext);
wc->fPromiseImageTexture.reset();
};
*wrappedDoneProc = [](SkDeferredDisplayListRecorder::PromiseImageTextureContext context) {
const auto* wc = static_cast<WrapperContext*>(context);
wc->fDone(wc->fOriginalContext);
SkASSERT(!wc->fPromiseImageTexture);
delete wc;
};
for (int i = 0; i < numTextures; ++i) {
wrappedTextureContext[i] = new WrapperContext{textureFulfillProc, textureReleaseProc,
textureDoneProc, textureContexts[i], nullptr};
}
}
sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture(
const GrBackendFormat& backendFormat,
int width,
int height,
GrMipMapped mipMapped,
GrSurfaceOrigin origin,
SkColorType colorType,
SkAlphaType alphaType,
sk_sp<SkColorSpace> colorSpace,
LegacyPromiseImageTextureFulfillProc textureFulfillProc,
LegacyPromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContext) {
if (!fContext) {
return nullptr;
}
SkDeferredDisplayListRecorder::PromiseImageTextureFulfillProc wrappedFulfillProc;
SkDeferredDisplayListRecorder::PromiseImageTextureReleaseProc wrappedReleaseProc;
SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc wrappedDoneProc;
SkDeferredDisplayListRecorder::PromiseImageTextureContext wrappedTextureContext;
if (!textureDoneProc) {
return nullptr;
}
if (!textureFulfillProc || !textureReleaseProc) {
textureDoneProc(textureContext);
return nullptr;
}
wrap_legacy(textureFulfillProc,
textureReleaseProc,
textureDoneProc,
&textureContext,
1,
&wrappedFulfillProc,
&wrappedReleaseProc,
&wrappedDoneProc,
&wrappedTextureContext);
return SkImage_Gpu::MakePromiseTexture(fContext.get(),
backendFormat,
width,
height,
mipMapped,
origin,
colorType,
alphaType,
std::move(colorSpace),
wrappedFulfillProc,
wrappedReleaseProc,
wrappedDoneProc,
wrappedTextureContext);
}
sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
@ -248,10 +378,10 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContexts[]) {
PromiseImageTextureFulfillProc textureFulfillProc,
PromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContexts[]) {
if (!fContext) {
return nullptr;
}
@ -267,8 +397,66 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
std::move(imageColorSpace),
textureFulfillProc,
textureReleaseProc,
promiseDoneProc,
textureDoneProc,
textureContexts);
}
sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
const SkISize yuvaSizes[],
const SkYUVAIndex yuvaIndices[4],
int imageWidth,
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
LegacyPromiseImageTextureFulfillProc textureFulfillProc,
LegacyPromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContexts[]) {
if (!fContext) {
return nullptr;
}
int numTextures;
bool valid = SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures);
SkDeferredDisplayListRecorder::PromiseImageTextureFulfillProc wrappedFulfillProc;
SkDeferredDisplayListRecorder::PromiseImageTextureReleaseProc wrappedReleaseProc;
SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc wrappedDoneProc;
SkDeferredDisplayListRecorder::PromiseImageTextureContext wrappedTextureContexts[4];
if (!textureDoneProc) {
return nullptr;
}
if (!valid || !textureFulfillProc || !textureReleaseProc) {
for (int i = 0; i < numTextures; ++i) {
textureDoneProc(textureContexts[i]);
}
return nullptr;
}
wrap_legacy(textureFulfillProc,
textureReleaseProc,
textureDoneProc,
textureContexts,
numTextures,
&wrappedFulfillProc,
&wrappedReleaseProc,
&wrappedDoneProc,
wrappedTextureContexts);
return SkImage_GpuYUVA::MakePromiseYUVATexture(fContext.get(),
yuvColorSpace,
yuvaFormats,
yuvaSizes,
yuvaIndices,
imageWidth,
imageHeight,
imageOrigin,
std::move(imageColorSpace),
wrappedFulfillProc,
wrappedReleaseProc,
wrappedDoneProc,
wrappedTextureContexts);
}
#endif

View File

@ -0,0 +1,61 @@
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkPromiseImageTexture.h"
#include "SkMessageBus.h"
#if SK_SUPPORT_GPU
std::atomic<uint32_t> SkPromiseImageTexture::gUniqueID{1};
SkPromiseImageTexture::SkPromiseImageTexture(const GrBackendTexture& backendTexture) {
if (backendTexture.isValid()) {
fBackendTexture = backendTexture;
fUniqueID = gUniqueID++;
}
}
SkPromiseImageTexture::SkPromiseImageTexture(SkPromiseImageTexture&& that) {
*this = std::move(that);
}
SkPromiseImageTexture& SkPromiseImageTexture::operator=(SkPromiseImageTexture&& that) {
for (const auto& msg : fMessages) {
SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(msg);
}
fMessages = that.fMessages;
that.fMessages.reset();
fBackendTexture = that.fBackendTexture;
that.fBackendTexture = {};
fUniqueID = that.fUniqueID;
that.fUniqueID = SK_InvalidUniqueID;
return *this;
}
SkPromiseImageTexture::~SkPromiseImageTexture() {
for (const auto& msg : fMessages) {
SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(msg);
}
}
void SkPromiseImageTexture::addKeyToInvalidate(uint32_t contextID, const GrUniqueKey& key) {
SkASSERT(contextID != SK_InvalidUniqueID);
SkASSERT(key.isValid());
fMessages.emplace_back(key, contextID);
}
#if GR_TEST_UTILS
SkTArray<GrUniqueKey> SkPromiseImageTexture::testingOnly_uniqueKeysToInvalidate() const {
SkTArray<GrUniqueKey> results;
for (const auto& msg : fMessages) {
results.push_back(msg.key());
}
return results;
}
#endif
#endif

View File

@ -66,8 +66,7 @@ public:
* must be sure that if a resource of exists in the cache with the given unique key then it is
* of type T.
*/
template <typename T>
sk_sp<T> findByUniqueKey(const GrUniqueKey& key) {
template <typename T = GrGpuResource> sk_sp<T> findByUniqueKey(const GrUniqueKey& key) {
return sk_sp<T>(static_cast<T*>(this->findResourceByUniqueKey(key).release()));
}

View File

@ -322,10 +322,10 @@ sk_sp<SkImage> SkImage_Gpu::MakePromiseTexture(GrContext* context,
SkColorType colorType,
SkAlphaType alphaType,
sk_sp<SkColorSpace> colorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContext) {
PromiseImageTextureFulfillProc textureFulfillProc,
PromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc promiseDoneProc,
PromiseImageTextureContext textureContext) {
// The contract here is that if 'promiseDoneProc' is passed in it should always be called,
// even if creation of the SkImage fails. Once we call MakePromiseImageLazyProxy it takes
// responsibility for calling the done proc.

View File

@ -76,8 +76,9 @@ public:
@param colorSpace range of colors; may be nullptr
@param textureFulfillProc function called to get actual gpu texture
@param textureReleaseProc function called when texture can be released
@param promiseDoneProc function called when we will no longer call textureFulfillProc
@param textureContext state passed to textureFulfillProc and textureReleaseProc
@param textureDoneProc function called when we will no longer call textureFulfillProc
@param textureContext state passed to textureFulfillProc, textureReleaseProc, and
promiseDoneProc
@return created SkImage, or nullptr
*/
static sk_sp<SkImage> MakePromiseTexture(GrContext* context,
@ -89,10 +90,10 @@ public:
SkColorType colorType,
SkAlphaType alphaType,
sk_sp<SkColorSpace> colorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContext);
PromiseImageTextureFulfillProc textureFulfillProc,
PromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContext);
static sk_sp<SkImage> ConvertYUVATexturesToRGB(GrContext*, SkYUVColorSpace yuvColorSpace,
const GrBackendTexture yuvaTextures[],

View File

@ -5,6 +5,7 @@
* found in the LICENSE file.
*/
#include "SkImage_GpuBase.h"
#include "GrBackendSurface.h"
#include "GrClip.h"
#include "GrContext.h"
@ -12,11 +13,11 @@
#include "GrRenderTargetContext.h"
#include "GrTexture.h"
#include "GrTextureAdjuster.h"
#include "effects/GrYUVtoRGBEffect.h"
#include "SkBitmapCache.h"
#include "SkImage_Gpu.h"
#include "SkImage_GpuBase.h"
#include "SkPromiseImageTexture.h"
#include "SkReadPixelsRec.h"
#include "effects/GrYUVtoRGBEffect.h"
SkImage_GpuBase::SkImage_GpuBase(sk_sp<GrContext> context, int width, int height, uint32_t uniqueID,
SkAlphaType at, sk_sp<SkColorSpace> cs)
@ -391,9 +392,10 @@ bool SkImage_GpuBase::RenderYUVAToRGBA(GrContext* ctx, GrRenderTargetContext* re
sk_sp<GrTextureProxy> SkImage_GpuBase::MakePromiseImageLazyProxy(
GrContext* context, int width, int height, GrSurfaceOrigin origin, GrPixelConfig config,
GrBackendFormat backendFormat, GrMipMapped mipMapped,
SkImage_GpuBase::TextureFulfillProc fulfillProc,
SkImage_GpuBase::TextureReleaseProc releaseProc, SkImage_GpuBase::PromiseDoneProc doneProc,
SkImage_GpuBase::TextureContext textureContext) {
PromiseImageTextureFulfillProc fulfillProc,
PromiseImageTextureReleaseProc releaseProc,
PromiseImageTextureDoneProc doneProc,
PromiseImageTextureContext textureContext) {
SkASSERT(context);
SkASSERT(width > 0 && height > 0);
SkASSERT(doneProc);
@ -413,115 +415,169 @@ sk_sp<GrTextureProxy> SkImage_GpuBase::MakePromiseImageLazyProxy(
}
/**
* This helper class manages the ref counting for the the ReleaseProc and DoneProc for promise
* images. It holds a weak ref on the ReleaseProc (hard refs are owned by GrTextures). The weak
* ref allows us to reuse an outstanding ReleaseProc (because we dropped our GrTexture but the
* GrTexture isn't done on the GPU) without needing to call FulfillProc again. It also holds a
* hard ref on the DoneProc. The idea is that after every flush we may call the ReleaseProc so
* that the client can free up their GPU memory if they want to. The life time of the DoneProc
* matches that of any outstanding ReleaseProc as well as the PromiseLazyInstantiateCallback.
* Thus we won't call the DoneProc until all ReleaseProcs are finished and we are finished with
* the PromiseImageHelper (i.e. won't call FulfillProc again).
* This class is the lazy instantiation callback for promise images. It manages calling the
* client's Fulfill, Release, and Done procs. It attempts to reuse a GrTexture instance in
* cases where the client provides the same SkPromiseImageTexture for successive Fulfill calls.
* The created GrTexture is given a key based on a unique ID associated with the
* SkPromiseImageTexture. When the texture enters "idle" state (meaning it is not being used by
* the GPU and is at rest in the resource cache) the client's Release proc is called
* using GrTexture's idle proc mechanism. If the same SkPromiseImageTexture is provided for
* another fulfill we find the cached GrTexture. If the proxy, and therefore this object,
* is destroyed, we invalidate the GrTexture's key. Also if the client overwrites or
* destroys their SkPromiseImageTexture we invalidate the key.
*
* Currently a GrTexture is only reused for a given SkPromiseImageTexture if the
* SkPromiseImageTexture is reused in Fulfill for the same promise SkImage. However, we'd
* like to relax that so that a SkPromiseImageTexture can be reused with different promise
* SkImages that will reuse a single GrTexture.
*/
class PromiseLazyInstantiateCallback {
public:
PromiseLazyInstantiateCallback(SkImage_GpuBase::TextureFulfillProc fulfillProc,
SkImage_GpuBase::TextureReleaseProc releaseProc,
SkImage_GpuBase::PromiseDoneProc doneProc,
SkImage_GpuBase::TextureContext context,
PromiseLazyInstantiateCallback(PromiseImageTextureFulfillProc fulfillProc,
PromiseImageTextureReleaseProc releaseProc,
PromiseImageTextureDoneProc doneProc,
PromiseImageTextureContext context,
GrPixelConfig config)
: fFulfillProc(fulfillProc)
, fReleaseProc(releaseProc)
, fContext(context)
, fConfig(config) {
fDoneHelper.reset(new GrReleaseProcHelper(doneProc, context));
fDoneHelper = sk_make_sp<GrReleaseProcHelper>(doneProc, context);
static std::atomic<uint32_t> gUniqueID;
fUniqueID = gUniqueID.fetch_add(1) + 1;
}
~PromiseLazyInstantiateCallback() {
// If we've already released the texture then it is safe to call done now. Here we may
// be on any thread.
if (fIdleContext && fIdleContext->fWasReleased.load()) {
// We still own a ref on fDoneHelper so no other thread can be calling the done
// proc.
fDoneHelper->callAndClear();
}
// Remove the key from the texture so that the texture will be removed from the cache.
// If we didn't just call the done proc above then it will get called when the texture
// is removed from the cache after this message is processed.
if (fLastFulfilledKey.isValid()) {
SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
GrUniqueKeyInvalidatedMessage(fLastFulfilledKey, fContextID));
}
}
sk_sp<GrSurface> operator()(GrResourceProvider* resourceProvider) {
if (!resourceProvider) {
this->reset();
return nullptr;
}
sk_sp<GrTexture> cachedTexture;
SkASSERT(fLastFulfilledKey.isValid() == (fLastFulfillID > 0));
if (fLastFulfilledKey.isValid()) {
auto surf = resourceProvider->findByUniqueKey<GrSurface>(fLastFulfilledKey);
if (surf) {
cachedTexture = sk_ref_sp(surf->asTexture());
SkASSERT(cachedTexture);
}
}
// If the release callback hasn't been called already by releasing the GrTexture
// then we can be sure that won't happen so long as we have a ref to the texture.
// Moreoever, only this thread should be able to change the atomic to true, hence the
// relaxed memory order.
if (cachedTexture && !fIdleContext->fWasReleased.load(std::memory_order_relaxed)) {
return std::move(cachedTexture);
}
GrBackendTexture backendTexture;
SkPromiseImageTexture* promiseTexture = fFulfillProc(fContext);
if (!promiseTexture) {
fReleaseProc(fContext, nullptr);
return sk_sp<GrTexture>();
}
bool same = promiseTexture->uniqueID() == fLastFulfillID;
SkASSERT(!same || fLastFulfilledKey.isValid());
if (same && cachedTexture) {
SkASSERT(fIdleContext->unique());
// Reset the purgeable context so that we balance the new fulfill with a release.
fIdleContext->ref();
SkASSERT(fIdleContext->fReleaseProc == fReleaseProc);
SkASSERT(fIdleContext->fTextureContext == fContext);
// Memory order relaxed because only this thread can change fWasReleased to true.
fIdleContext->fWasReleased.store(false, std::memory_order_relaxed);
cachedTexture->setIdleProc(IdleProc, fIdleContext.get());
return std::move(cachedTexture);
} else if (cachedTexture) {
cachedTexture->resourcePriv().removeUniqueKey();
// We don't want calling the client's done proc to be tied to the old texture.
cachedTexture->setRelease(nullptr);
}
fLastFulfillID = promiseTexture->uniqueID();
backendTexture = promiseTexture->backendTexture();
backendTexture.fConfig = fConfig;
if (!backendTexture.isValid()) {
// Even though the GrBackendTexture is not valid, we must call the release
// proc to keep our contract of always calling Fulfill and Release in pairs.
fReleaseProc(fContext, promiseTexture);
return sk_sp<GrTexture>();
}
// Releases the promise helper if there are no outstanding hard refs. This means that we
// don't have any ReleaseProcs waiting to be called so we will need to do a fulfill.
if (fReleaseHelper && fReleaseHelper->weak_expired()) {
this->resetReleaseHelper();
auto tex = resourceProvider->wrapBackendTexture(backendTexture, kBorrow_GrWrapOwnership,
kRead_GrIOType);
if (!tex) {
// Even though we failed to wrap the backend texture, we must call the release
// proc to keep our contract of always calling Fulfill and Release in pairs.
fReleaseProc(fContext, promiseTexture);
return sk_sp<GrTexture>();
}
sk_sp<GrTexture> tex;
if (!fReleaseHelper) {
fFulfillProc(fContext, &fBackendTex);
fBackendTex.fConfig = fConfig;
if (!fBackendTex.isValid()) {
// Even though the GrBackendTexture is not valid, we must call the release
// proc to keep our contract of always calling Fulfill and Release in pairs.
fReleaseProc(fContext);
return sk_sp<GrTexture>();
}
tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership,
kRead_GrIOType);
if (!tex) {
// Even though the GrBackendTexture is not valid, we must call the release
// proc to keep our contract of always calling Fulfill and Release in pairs.
fReleaseProc(fContext);
return sk_sp<GrTexture>();
}
fReleaseHelper =
new SkPromiseReleaseProcHelper(fReleaseProc, fContext, fDoneHelper);
// Take a weak ref
fReleaseHelper->weak_ref();
} else {
SkASSERT(fBackendTex.isValid());
tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership,
kRead_GrIOType);
if (!tex) {
// We weren't able to make a texture here, but since we are in this branch
// of the calls (promiseHelper.fReleaseHelper is valid) there is already a
// texture out there which will call the release proc so we don't need to
// call it here.
return sk_sp<GrTexture>();
}
SkAssertResult(fReleaseHelper->try_ref());
}
SkASSERT(tex);
// Pass the hard ref off to the texture
tex->setRelease(sk_sp<GrReleaseProcHelper>(fReleaseHelper));
fIdleContext = sk_make_sp<IdleContext>(fReleaseProc, fContext, promiseTexture);
// The texture gets a ref, which is balanced when the idle callback is called.
fIdleContext->ref();
tex->setIdleProc(IdleProc, fIdleContext.get());
tex->setRelease(fDoneHelper);
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey::Builder builder(&fLastFulfilledKey, kDomain, 2, "promise");
builder[0] = promiseTexture->uniqueID();
builder[1] = fUniqueID;
builder.finish();
tex->resourcePriv().setUniqueKey(fLastFulfilledKey);
SkASSERT(fContextID == SK_InvalidUniqueID ||
fContextID == tex->getContext()->uniqueID());
fContextID = tex->getContext()->uniqueID();
promiseTexture->addKeyToInvalidate(fContextID, fLastFulfilledKey);
return std::move(tex);
}
private:
void reset() {
this->resetReleaseHelper();
fDoneHelper.reset();
struct IdleContext : public SkNVRefCnt<IdleContext> {
IdleContext(PromiseImageTextureReleaseProc proc, PromiseImageTextureContext context,
const SkPromiseImageTexture* texture)
: fReleaseProc(proc), fTextureContext(context), fPromiseImageTexture(texture) {}
PromiseImageTextureReleaseProc fReleaseProc;
PromiseImageTextureContext fTextureContext;
const SkPromiseImageTexture* fPromiseImageTexture;
std::atomic<bool> fWasReleased{false};
};
static void IdleProc(void* context) {
IdleContext* rc = static_cast<IdleContext*>(context);
SkASSERT(!rc->fWasReleased.load());
rc->fReleaseProc(rc->fTextureContext, rc->fPromiseImageTexture);
rc->fWasReleased.store(true);
// Drop the texture's implicit ref on the IdleContext.
rc->unref();
}
// Weak unrefs fReleaseHelper and sets it to null
void resetReleaseHelper() {
if (fReleaseHelper) {
fReleaseHelper->weak_unref();
fReleaseHelper = nullptr;
}
}
SkImage_GpuBase::TextureFulfillProc fFulfillProc;
SkImage_GpuBase::TextureReleaseProc fReleaseProc;
SkImage_GpuBase::TextureContext fContext;
PromiseImageTextureFulfillProc fFulfillProc;
PromiseImageTextureReleaseProc fReleaseProc;
PromiseImageTextureContext fContext;
GrPixelConfig fConfig;
// We cache the GrBackendTexture so that if we deleted the GrTexture but the the release
// proc has yet not been called (this can happen on Vulkan), then we can create a new
// texture without needing to call the fulfill proc again.
GrBackendTexture fBackendTex;
// The fReleaseHelper is used to track a weak ref on the release proc. This helps us make
// sure we are always pairing fulfill and release proc calls correctly.
SkPromiseReleaseProcHelper* fReleaseHelper = nullptr;
// We don't want to call the fDoneHelper until we are done with the PromiseImageHelper and
// all ReleaseHelpers are finished. Thus we hold a hard ref here and we will pass a hard ref
// to each fReleaseHelper we make.
sk_sp<IdleContext> fIdleContext;
sk_sp<GrReleaseProcHelper> fDoneHelper;
// ID of the last SkPromiseImageTexture given to us by the client.
uint32_t fLastFulfillID = 0;
// ID of the GrContext that we are interacting with.
uint32_t fContextID = SK_InvalidUniqueID;
// Unique ID of this lazy instantiation callback.
uint32_t fUniqueID;
GrUniqueKey fLastFulfilledKey;
} callback(fulfillProc, releaseProc, doneProc, textureContext, config);
GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();

View File

@ -10,6 +10,7 @@
#include "GrBackendSurface.h"
#include "GrTypesPriv.h"
#include "SkDeferredDisplayListRecorder.h"
#include "SkImage_Base.h"
#include "SkYUVAIndex.h"
@ -72,9 +73,12 @@ public:
: kOpaque_SkAlphaType;
}
typedef ReleaseContext TextureContext;
typedef void(*TextureFulfillProc)(TextureContext textureContext, GrBackendTexture* outTexture);
typedef void(*PromiseDoneProc)(TextureContext textureContext);
using PromiseImageTextureContext = SkDeferredDisplayListRecorder::PromiseImageTextureContext;
using PromiseImageTextureFulfillProc =
SkDeferredDisplayListRecorder::PromiseImageTextureFulfillProc;
using PromiseImageTextureReleaseProc =
SkDeferredDisplayListRecorder::PromiseImageTextureReleaseProc;
using PromiseImageTextureDoneProc = SkDeferredDisplayListRecorder::PromiseImageTextureDoneProc;
protected:
// Helper for making a lazy proxy for a promise image. The PromiseDoneProc we be called,
@ -83,8 +87,8 @@ protected:
// be null.
static sk_sp<GrTextureProxy> MakePromiseImageLazyProxy(
GrContext*, int width, int height, GrSurfaceOrigin, GrPixelConfig, GrBackendFormat,
GrMipMapped, SkImage_GpuBase::TextureFulfillProc, SkImage_GpuBase::TextureReleaseProc,
SkImage_GpuBase::PromiseDoneProc, SkImage_GpuBase::TextureContext);
GrMipMapped, PromiseImageTextureFulfillProc, PromiseImageTextureReleaseProc,
PromiseImageTextureDoneProc, PromiseImageTextureContext);
static bool RenderYUVAToRGBA(GrContext* ctx, GrRenderTargetContext* renderTargetContext,
const SkRect& rect, SkYUVColorSpace yuvColorSpace,
@ -99,31 +103,4 @@ private:
typedef SkImage_Base INHERITED;
};
/**
* This helper holds the normal hard ref for the Release proc as well as a hard ref on the DoneProc.
* Thus when a GrTexture is being released, it will unref both the ReleaseProc and DoneProc.
*/
class SkPromiseReleaseProcHelper : public GrReleaseProcHelper {
public:
SkPromiseReleaseProcHelper(SkImage_GpuBase::TextureReleaseProc releaseProc,
SkImage_GpuBase::TextureContext context,
sk_sp<GrReleaseProcHelper> doneHelper)
: INHERITED(releaseProc, context)
, fDoneProcHelper(std::move(doneHelper)) {
}
void weak_dispose() const override {
// Call the inherited weak_dispose first so that we call the ReleaseProc before the DoneProc
// if we hold the last ref to the DoneProc.
INHERITED::weak_dispose();
fDoneProcHelper.reset();
}
private:
mutable sk_sp<GrReleaseProcHelper> fDoneProcHelper;
typedef GrReleaseProcHelper INHERITED;
};
#endif

View File

@ -206,19 +206,20 @@ sk_sp<SkImage> SkImage::MakeFromYUVAPixmaps(
/////////////////////////////////////////////////////////////////////////////////////////////////
sk_sp<SkImage> SkImage_GpuYUVA::MakePromiseYUVATexture(GrContext* context,
SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
const SkISize yuvaSizes[],
const SkYUVAIndex yuvaIndices[4],
int imageWidth,
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContexts[]) {
sk_sp<SkImage> SkImage_GpuYUVA::MakePromiseYUVATexture(
GrContext* context,
SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
const SkISize yuvaSizes[],
const SkYUVAIndex yuvaIndices[4],
int imageWidth,
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
PromiseImageTextureFulfillProc textureFulfillProc,
PromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc promiseDoneProc,
PromiseImageTextureContext textureContexts[]) {
int numTextures;
bool valid = SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures);
@ -289,4 +290,3 @@ sk_sp<SkImage> SkImage_GpuYUVA::MakePromiseYUVATexture(GrContext* context,
kNeedNewImageUniqueID, yuvColorSpace, proxies, numTextures,
yuvaIndices, imageOrigin, std::move(imageColorSpace));
}

View File

@ -53,7 +53,6 @@ public:
// Returns a ref-ed texture proxy with miplevels
sk_sp<GrTextureProxy> asMippedTextureProxyRef() const;
/**
Create a new SkImage_GpuYUVA that's very similar to SkImage created by MakeFromYUVATextures.
The main difference is that the client doesn't have the backend textures on the gpu yet but
@ -90,8 +89,9 @@ public:
@param imageColorSpace range of colors; may be nullptr
@param textureFulfillProc function called to get actual gpu texture
@param textureReleaseProc function called when texture can be released
@param promiseDoneProc function called when we will no longer call textureFulfillProc
@param textureContext state passed to textureFulfillProc and textureReleaseProc
@param textureDoneProc function called when we will no longer call textureFulfillProc
@param textureContexts per-texture state passed to textureFulfillProc,
textureReleaseProc, and textureDoneProc
@return created SkImage, or nullptr
*/
static sk_sp<SkImage> MakePromiseYUVATexture(GrContext* context,
@ -103,10 +103,10 @@ public:
int height,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContexts[]);
PromiseImageTextureFulfillProc textureFulfillProc,
PromiseImageTextureReleaseProc textureReleaseProc,
PromiseImageTextureDoneProc textureDoneProc,
PromiseImageTextureContext textureContexts[]);
private:
// This array will usually only be sparsely populated.

View File

@ -11,6 +11,7 @@
#include "GrContextPriv.h"
#include "GrGpu.h"
#include "SkImage_Gpu.h"
#include "SkPromiseImageTexture.h"
using namespace sk_gpu_test;
@ -20,15 +21,39 @@ struct PromiseTextureChecker {
, fFulfillCount(0)
, fReleaseCount(0)
, fDoneCount(0) {}
GrBackendTexture fTexture;
SkPromiseImageTexture fTexture;
int fFulfillCount;
int fReleaseCount;
int fDoneCount;
static void Fulfill(void* self, GrBackendTexture* outTexture) {
static_cast<PromiseTextureChecker*>(self)->fFulfillCount++;
*outTexture = static_cast<PromiseTextureChecker*>(self)->fTexture;
GrBackendTexture fLastFulfilledTexture;
/**
* Replaces the backend texture that this checker will return from fulfill. Also, transfers
* ownership of the previous PromiseImageTexture to the caller, if they want to control when
* it is deleted. The default argument will remove the existing texture without installing a
* valid replacement.
*/
SkPromiseImageTexture replaceTexture(const GrBackendTexture& tex = GrBackendTexture()) {
// Can't change this while in active fulfillment.
SkASSERT(fFulfillCount == fReleaseCount);
auto temp = std::move(fTexture);
fTexture = SkPromiseImageTexture(tex);
return temp;
}
static void Release(void* self) {
SkTArray<GrUniqueKey> uniqueKeys() const {
return fTexture.testingOnly_uniqueKeysToInvalidate();
}
static SkPromiseImageTexture* Fulfill(void* self) {
auto checker = static_cast<PromiseTextureChecker*>(self);
SkASSERT(checker->fTexture.isValid());
checker->fFulfillCount++;
checker->fLastFulfilledTexture = checker->fTexture.backendTexture();
return &checker->fTexture;
}
static void Release(void* self, const SkPromiseImageTexture* texture) {
SkASSERT(&static_cast<PromiseTextureChecker*>(self)->fTexture == texture);
static_cast<PromiseTextureChecker*>(self)->fReleaseCount++;
}
static void Done(void* self) {
@ -236,3 +261,266 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTest, reporter, ctxInfo) {
gpu->deleteTestingOnlyBackendTexture(backendTex);
}
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(PromiseImageTextureReuse, reporter, ctxInfo) {
const int kWidth = 10;
const int kHeight = 10;
GrContext* ctx = ctxInfo.grContext();
GrGpu* gpu = ctx->contextPriv().getGpu();
GrBackendTexture backendTex1 = gpu->createTestingOnlyBackendTexture(
nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, true, GrMipMapped::kNo);
GrBackendTexture backendTex2 = gpu->createTestingOnlyBackendTexture(
nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, true, GrMipMapped::kNo);
GrBackendTexture backendTex3 = gpu->createTestingOnlyBackendTexture(
nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, true, GrMipMapped::kNo);
REPORTER_ASSERT(reporter, backendTex1.isValid());
REPORTER_ASSERT(reporter, backendTex2.isValid());
REPORTER_ASSERT(reporter, backendTex3.isValid());
GrBackendFormat backendFormat = backendTex1.getBackendFormat();
REPORTER_ASSERT(reporter, backendFormat.isValid());
REPORTER_ASSERT(reporter, backendFormat == backendTex2.getBackendFormat());
REPORTER_ASSERT(reporter, backendFormat == backendTex3.getBackendFormat());
PromiseTextureChecker promiseChecker(backendTex1);
GrSurfaceOrigin texOrigin = kTopLeft_GrSurfaceOrigin;
sk_sp<SkImage> refImg(
SkImage_Gpu::MakePromiseTexture(ctx, backendFormat, kWidth, kHeight,
GrMipMapped::kNo, texOrigin,
kRGBA_8888_SkColorType, kPremul_SkAlphaType,
nullptr,
PromiseTextureChecker::Fulfill,
PromiseTextureChecker::Release,
PromiseTextureChecker::Done,
&promiseChecker));
SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight);
sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info);
SkCanvas* canvas = surface->getCanvas();
int expectedFulfillCnt = 0;
int expectedReleaseCnt = 0;
int expectedDoneCnt = 0;
canvas->drawImage(refImg, 0, 0);
canvas->drawImage(refImg, 5, 5);
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
true,
expectedFulfillCnt,
expectedReleaseCnt,
true,
expectedDoneCnt,
reporter));
bool isVulkan = GrBackendApi::kVulkan == ctx->contextPriv().getBackend();
canvas->flush();
expectedFulfillCnt++;
expectedReleaseCnt++;
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
!isVulkan,
expectedFulfillCnt,
expectedReleaseCnt,
!isVulkan,
expectedDoneCnt,
reporter));
REPORTER_ASSERT(reporter, GrBackendTexture::TestingOnly_Equals(
promiseChecker.fLastFulfilledTexture, backendTex1));
// We should have put a GrTexture for this fulfillment into the cache.
auto keys = promiseChecker.uniqueKeys();
REPORTER_ASSERT(reporter, keys.count() == 1);
GrUniqueKey texKey1;
if (keys.count()) {
texKey1 = keys[0];
}
REPORTER_ASSERT(reporter, texKey1.isValid());
REPORTER_ASSERT(reporter, ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey1));
gpu->testingOnly_flushGpuAndSync();
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
true,
expectedFulfillCnt,
expectedReleaseCnt,
true,
expectedDoneCnt,
reporter));
REPORTER_ASSERT(reporter,
GrBackendTexture::TestingOnly_Equals(
promiseChecker.replaceTexture().backendTexture(), backendTex1));
gpu->deleteTestingOnlyBackendTexture(backendTex1);
ctx->contextPriv().getResourceCache()->purgeAsNeeded();
// We should have invalidated the key on the previously cached texture (after ensuring
// invalidation messages have been processed by calling purgeAsNeeded.)
REPORTER_ASSERT(reporter, !ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey1));
promiseChecker.replaceTexture(backendTex2);
canvas->drawImage(refImg, 0, 0);
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
true,
expectedFulfillCnt,
expectedReleaseCnt,
true,
expectedDoneCnt,
reporter));
canvas->flush();
expectedFulfillCnt++;
expectedReleaseCnt++;
// Second texture should be in the cache.
keys = promiseChecker.uniqueKeys();
REPORTER_ASSERT(reporter, keys.count() == 1);
GrUniqueKey texKey2;
if (keys.count()) {
texKey2 = keys[0];
}
REPORTER_ASSERT(reporter, texKey2.isValid() && texKey2 != texKey1);
REPORTER_ASSERT(reporter, ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey2));
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
!isVulkan,
expectedFulfillCnt,
expectedReleaseCnt,
!isVulkan,
expectedDoneCnt,
reporter));
REPORTER_ASSERT(reporter, GrBackendTexture::TestingOnly_Equals(
promiseChecker.fLastFulfilledTexture, backendTex2));
gpu->testingOnly_flushGpuAndSync();
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
true,
expectedFulfillCnt,
expectedReleaseCnt,
true,
expectedDoneCnt,
reporter));
// Because we have kept the SkPromiseImageTexture alive, we should be able to use it again and
// hit the cache.
ctx->contextPriv().getResourceCache()->purgeAsNeeded();
REPORTER_ASSERT(reporter, ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey2));
canvas->drawImage(refImg, 0, 0);
canvas->flush();
gpu->testingOnly_flushGpuAndSync();
expectedFulfillCnt++;
expectedReleaseCnt++;
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
true,
expectedFulfillCnt,
expectedReleaseCnt,
true,
expectedDoneCnt,
reporter));
// Make sure we didn't add another key and that the second texture is still alive in the cache.
keys = promiseChecker.uniqueKeys();
REPORTER_ASSERT(reporter, keys.count() == 1);
if (keys.count()) {
REPORTER_ASSERT(reporter, texKey2 == keys[0]);
}
ctx->contextPriv().getResourceCache()->purgeAsNeeded();
REPORTER_ASSERT(reporter, ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey2));
// Now we test keeping tex2 alive but fulfilling with a new texture.
SkPromiseImageTexture promiseImageTexture2 = promiseChecker.replaceTexture(backendTex3);
REPORTER_ASSERT(reporter, GrBackendTexture::TestingOnly_Equals(
promiseImageTexture2.backendTexture(), backendTex2));
canvas->drawImage(refImg, 0, 0);
canvas->flush();
gpu->testingOnly_flushGpuAndSync();
expectedFulfillCnt++;
expectedReleaseCnt++;
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
true,
expectedFulfillCnt,
expectedReleaseCnt,
true,
expectedDoneCnt,
reporter));
keys = promiseChecker.uniqueKeys();
REPORTER_ASSERT(reporter, keys.count() == 1);
GrUniqueKey texKey3;
if (keys.count()) {
texKey3 = keys[0];
}
ctx->contextPriv().getResourceCache()->purgeAsNeeded();
REPORTER_ASSERT(reporter, !ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey2));
REPORTER_ASSERT(reporter, ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey3));
gpu->deleteTestingOnlyBackendTexture(promiseImageTexture2.backendTexture());
// Make a new promise image also backed by texture 3.
sk_sp<SkImage> refImg2(
SkImage_Gpu::MakePromiseTexture(ctx, backendFormat, kWidth, kHeight,
GrMipMapped::kNo, texOrigin,
kRGBA_8888_SkColorType, kPremul_SkAlphaType,
nullptr,
PromiseTextureChecker::Fulfill,
PromiseTextureChecker::Release,
PromiseTextureChecker::Done,
&promiseChecker));
canvas->drawImage(refImg, 0, 0);
canvas->drawImage(refImg2, 1, 1);
canvas->flush();
gpu->testingOnly_flushGpuAndSync();
expectedFulfillCnt += 2;
expectedReleaseCnt += 2;
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
true,
expectedFulfillCnt,
expectedReleaseCnt,
true,
expectedDoneCnt,
reporter));
// We currently expect each promise image to make and cache its own GrTexture. We will likely
// try to make these share in the future.
keys = promiseChecker.uniqueKeys();
REPORTER_ASSERT(reporter, keys.count() == 2);
GrUniqueKey texKey4;
if (keys.count() == 2) {
REPORTER_ASSERT(reporter, texKey3 == keys[0]);
texKey4 = keys[1];
}
ctx->contextPriv().getResourceCache()->purgeAsNeeded();
REPORTER_ASSERT(reporter, ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey3));
REPORTER_ASSERT(reporter, ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey4));
// If we delete the SkPromiseImageTexture we should trigger both key removals.
REPORTER_ASSERT(reporter,
GrBackendTexture::TestingOnly_Equals(
promiseChecker.replaceTexture().backendTexture(), backendTex3));
ctx->contextPriv().getResourceCache()->purgeAsNeeded();
REPORTER_ASSERT(reporter, !ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey3));
REPORTER_ASSERT(reporter, !ctx->contextPriv().resourceProvider()->findByUniqueKey<>(texKey4));
gpu->deleteTestingOnlyBackendTexture(backendTex3);
// After deleting each image we should get a done call.
refImg.reset();
++expectedDoneCnt;
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
true,
expectedFulfillCnt,
expectedReleaseCnt,
true,
expectedDoneCnt,
reporter));
refImg2.reset();
++expectedDoneCnt;
REPORTER_ASSERT(reporter, check_fulfill_and_release_cnts(promiseChecker,
true,
expectedFulfillCnt,
expectedReleaseCnt,
true,
expectedDoneCnt,
reporter));
}

View File

@ -17,13 +17,30 @@
#include "SkYUVASizeInfo.h"
DDLPromiseImageHelper::PromiseImageCallbackContext::~PromiseImageCallbackContext() {
SkASSERT(fDoneCnt == fNumImages);
SkASSERT(!fUnreleasedFulfills);
SkASSERT(fTotalReleases == fTotalFulfills);
SkASSERT(!fTotalFulfills || fDoneCnt);
GrGpu* gpu = fContext->contextPriv().getGpu();
if (fBackendTexture.isValid()) {
gpu->deleteTestingOnlyBackendTexture(fBackendTexture);
if (fPromiseImageTexture.isValid()) {
gpu->deleteTestingOnlyBackendTexture(fPromiseImageTexture.backendTexture());
}
}
void DDLPromiseImageHelper::PromiseImageCallbackContext::setBackendTexture(
const GrBackendTexture& backendTexture) {
SkASSERT(!fUnreleasedFulfills);
if (fPromiseImageTexture.isValid()) {
GrGpu* gpu = fContext->contextPriv().getGpu();
if (fPromiseImageTexture.isValid()) {
gpu->deleteTestingOnlyBackendTexture(fPromiseImageTexture.backendTexture());
}
}
fPromiseImageTexture = SkPromiseImageTexture{backendTexture};
}
///////////////////////////////////////////////////////////////////////////////////////////////////
DDLPromiseImageHelper::~DDLPromiseImageHelper() {}
@ -96,7 +113,7 @@ void DDLPromiseImageHelper::uploadAllToGPU(GrContext* context) {
GrGpu* gpu = context->contextPriv().getGpu();
SkASSERT(gpu);
for (int i = 0; i < fImageInfo.count(); ++i) {
for (int i = 0; i < fImageInfo.count(); i += 2) {
const PromiseImageInfo& info = fImageInfo[i];
// DDL TODO: how can we tell if we need mipmapping!
@ -111,7 +128,7 @@ void DDLPromiseImageHelper::uploadAllToGPU(GrContext* context) {
callbackContext->setBackendTexture(create_yuva_texture(gpu, yuvPixmap,
info.yuvaIndices(), j));
SkAssertResult(callbackContext->backendTexture().isValid());
SkASSERT(callbackContext->promiseImageTexture()->isValid());
fImageInfo[i].setCallbackContext(j, std::move(callbackContext));
}
@ -133,7 +150,34 @@ void DDLPromiseImageHelper::uploadAllToGPU(GrContext* context) {
fImageInfo[i].setCallbackContext(0, std::move(callbackContext));
}
}
}
void DDLPromiseImageHelper::replaceEveryOtherPromiseTexture(GrContext* context) {
GrGpu* gpu = context->contextPriv().getGpu();
SkASSERT(gpu);
for (int i = 0; i < fImageInfo.count(); ++i) {
PromiseImageInfo& info = fImageInfo[i];
// DDL TODO: how can we tell if we need mipmapping!
if (info.isYUV()) {
int numPixmaps;
SkAssertResult(SkYUVAIndex::AreValidIndices(info.yuvaIndices(), &numPixmaps));
for (int j = 0; j < numPixmaps; ++j) {
const SkPixmap& yuvPixmap = info.yuvPixmap(j);
info.callbackContext(j)->setBackendTexture(
create_yuva_texture(gpu, yuvPixmap, info.yuvaIndices(), j));
SkASSERT(info.callbackContext(j)->promiseImageTexture()->isValid());
}
} else {
const SkBitmap& bm = info.normalBitmap();
info.callbackContext(0)->setBackendTexture(gpu->createTestingOnlyBackendTexture(
bm.getPixels(), bm.width(), bm.height(), bm.colorType(), false,
GrMipMapped::kNo, bm.rowBytes()));
// The GMs sometimes request too large an image
// SkAssertResult(callbackContext->backendTexture().isValid());
}
}
}
@ -206,7 +250,9 @@ sk_sp<SkImage> DDLPromiseImageHelper::PromiseImageCreator(const void* rawData,
DDLPromiseImageHelper::PromiseImageReleaseProc,
DDLPromiseImageHelper::PromiseImageDoneProc,
contexts);
for (int i = 0; i < textureCount; ++i) {
curImage.callbackContext(i)->wasAddedToImage();
}
} else {
const GrBackendTexture& backendTex = curImage.backendTexture(0);
GrBackendFormat backendFormat = backendTex.getBackendFormat();
@ -227,6 +273,7 @@ sk_sp<SkImage> DDLPromiseImageHelper::PromiseImageCreator(const void* rawData,
DDLPromiseImageHelper::PromiseImageReleaseProc,
DDLPromiseImageHelper::PromiseImageDoneProc,
(void*) curImage.refCallbackContext(0).release());
curImage.callbackContext(0)->wasAddedToImage();
}
perRecorderContext->fPromiseImages->push_back(image);
SkASSERT(image);

View File

@ -8,16 +8,17 @@
#ifndef PromiseImageHelper_DEFINED
#define PromiseImageHelper_DEFINED
#include "SkBitmap.h"
#include "SkTArray.h"
#include "GrBackendSurface.h"
#include "SkBitmap.h"
#include "SkCachedData.h"
#include "SkDeferredDisplayListRecorder.h"
#include "SkPromiseImageTexture.h"
#include "SkTArray.h"
#include "SkTLazy.h"
#include "SkYUVAIndex.h"
#include "SkYUVASizeInfo.h"
class GrContext;
class SkDeferredDisplayListRecorder;
class SkImage;
class SkPicture;
struct SkYUVAIndex;
@ -54,6 +55,10 @@ public:
void uploadAllToGPU(GrContext* context);
// Change the backing store texture for half the images. (Must ensure all fulfilled images are
// released before calling this.).
void replaceEveryOtherPromiseTexture(GrContext*);
// reinflate a deflated SKP, replacing all the indices with promise images.
sk_sp<SkPicture> reinflateSKP(SkDeferredDisplayListRecorder*,
SkData* compressedPicture,
@ -76,16 +81,37 @@ private:
~PromiseImageCallbackContext();
void setBackendTexture(const GrBackendTexture& backendTexture) {
SkASSERT(!fBackendTexture.isValid());
fBackendTexture = backendTexture;
void setBackendTexture(const GrBackendTexture& backendTexture);
void fulfill() {
SkASSERT(fUnreleasedFulfills >= 0);
++fUnreleasedFulfills;
++fTotalFulfills;
}
const GrBackendTexture& backendTexture() const { return fBackendTexture; }
void release() {
SkASSERT(fUnreleasedFulfills > 0);
--fUnreleasedFulfills;
++fTotalReleases;
}
void done() {
++fDoneCnt;
SkASSERT(fDoneCnt <= fNumImages);
}
void wasAddedToImage() { fNumImages++; }
SkPromiseImageTexture* promiseImageTexture() { return &fPromiseImageTexture; }
private:
GrContext* fContext;
GrBackendTexture fBackendTexture;
GrContext* fContext;
SkPromiseImageTexture fPromiseImageTexture;
int fNumImages = 0;
int fTotalFulfills = 0;
int fTotalReleases = 0;
int fUnreleasedFulfills = 0;
int fDoneCnt = 0;
typedef SkRefCnt INHERITED;
};
@ -134,7 +160,7 @@ private:
SkASSERT(index >= 0 && index < (this->isYUV() ? SkYUVASizeInfo::kMaxCount : 1));
fCallbackContexts[index] = callbackContext;
}
PromiseImageCallbackContext* callbackContext(int index) {
PromiseImageCallbackContext* callbackContext(int index) const {
SkASSERT(index >= 0 && index < (this->isYUV() ? SkYUVASizeInfo::kMaxCount : 1));
return fCallbackContexts[index].get();
}
@ -145,7 +171,7 @@ private:
const GrBackendTexture& backendTexture(int index) const {
SkASSERT(index >= 0 && index < (this->isYUV() ? SkYUVASizeInfo::kMaxCount : 1));
return fCallbackContexts[index]->backendTexture();
return fCallbackContexts[index]->promiseImageTexture()->backendTexture();
}
void setNormalBitmap(const SkBitmap& bm) { fBitmap = bm; }
@ -190,21 +216,24 @@ private:
SkTArray<sk_sp<SkImage>>* fPromiseImages;
};
static void PromiseImageFulfillProc(void* textureContext, GrBackendTexture* outTexture) {
static SkPromiseImageTexture* PromiseImageFulfillProc(void* textureContext) {
auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
SkASSERT(callbackContext->backendTexture().isValid());
*outTexture = callbackContext->backendTexture();
SkASSERT(callbackContext->promiseImageTexture()->isValid());
callbackContext->fulfill();
return callbackContext->promiseImageTexture();
}
static void PromiseImageReleaseProc(void* textureContext) {
#ifdef SK_DEBUG
static void PromiseImageReleaseProc(void* textureContext,
const SkPromiseImageTexture* texture) {
auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
SkASSERT(callbackContext->backendTexture().isValid());
#endif
callbackContext->release();
SkASSERT(texture == callbackContext->promiseImageTexture());
SkASSERT(callbackContext->promiseImageTexture()->isValid());
}
static void PromiseImageDoneProc(void* textureContext) {
auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
callbackContext->done();
callbackContext->unref();
}
@ -212,6 +241,7 @@ private:
bool isValidID(int id) const { return id >= 0 && id < fImageInfo.count(); }
const PromiseImageInfo& getInfo(int id) const { return fImageInfo[id]; }
void uploadImage(GrContext*, PromiseImageInfo*);
// returns -1 if not found
int findImage(SkImage* image) const;