Add deferred texture upload API.
authorbsalomon <bsalomon@google.com>
Fri, 11 Mar 2016 14:46:33 +0000 (06:46 -0800)
committerCommit bot <commit-bot@chromium.org>
Fri, 11 Mar 2016 14:46:33 +0000 (06:46 -0800)
Performs thread-safe decoding of SkImage in order to later create a texture-backed SkImage.

The client allocates storage for the data.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1776693002

Review URL: https://codereview.chromium.org/1776693002

include/core/SkImage.h
include/core/SkPixmap.h
include/gpu/GrContext.h
src/core/SkPixmap.cpp
src/gpu/GrContext.cpp
src/image/SkImage.cpp
src/image/SkImage_Gpu.cpp
tests/ImageTest.cpp

index 1b4aeb6..6be3b6e 100644 (file)
@@ -25,6 +25,7 @@ class SkPixelSerializer;
 class SkString;
 class SkSurface;
 class GrContext;
+class GrContextThreadSafeProxy;
 class GrTexture;
 
 #define SK_SUPPORT_LEGACY_IMAGEFACTORY
@@ -321,6 +322,44 @@ public:
      */
     sk_sp<SkImage> makeTextureImage(GrContext*) const;
 
+    /** Drawing params for which a deferred texture image data should be optimized. */
+    struct DeferredTextureImageUsageParams {
+        SkMatrix        fMatrix;
+        SkFilterQuality fQuality;
+    };
+
+    /**
+     * This method allows clients to capture the data necessary to turn a SkImage into a texture-
+     * backed image. If the original image is codec-backed this will decode into a format optimized
+     * for the context represented by the proxy. This method is thread safe with respect to the
+     * GrContext whence the proxy came. Clients allocate and manage the storage of the deferred
+     * texture data and control its lifetime. No cleanup is required, thus it is safe to simply free
+     * the memory out from under the data.
+     *
+     * The same method is used both for getting the size necessary for pre-uploaded texture data
+     * and for retrieving the data. The params array represents the set of draws over which to
+     * optimize the pre-upload data.
+     *
+     * When called with a null buffer this returns the size that the client must allocate in order
+     * to create deferred texture data for this image (or zero if this is an inappropriate
+     * candidate). The buffer allocated by the client should be 8 byte aligned.
+     *
+     * When buffer is not null this fills in the deferred texture data for this image in the
+     * provided buffer (assuming this is an appropriate candidate image and the buffer is
+     * appropriately aligned). Upon success the size written is returned, otherwise 0.
+     */
+    size_t getDeferredTextureImageData(const GrContextThreadSafeProxy&,
+                                       const DeferredTextureImageUsageParams[],
+                                       int paramCnt,
+                                       void* buffer) const;
+
+    /**
+     * Returns a texture-backed image from data produced in SkImage::getDeferredTextureImageData.
+     * The context must be the context that provided the proxy passed to
+     * getDeferredTextureImageData.
+     */
+    static sk_sp<SkImage> MakeFromDeferredTextureImageData(GrContext*, const void*, SkBudgeted);
+
     // Helper functions to convert to SkBitmap
 
     enum LegacyBitmapMode {
@@ -375,6 +414,7 @@ public:
     static SkImage* NewFromPicture(const SkPicture*, const SkISize& dimensions,
                                    const SkMatrix*, const SkPaint*);
     static SkImage* NewTextureFromPixmap(GrContext*, const SkPixmap&, SkBudgeted budgeted);
+    static SkImage* NewFromDeferredTextureImageData(GrContext*, const void*, SkBudgeted);
 
     SkImage* newSubset(const SkIRect& subset) const { return this->makeSubset(subset).release(); }
     SkImage* newTextureImage(GrContext* ctx) const { return this->makeTextureImage(ctx).release(); }
index 894b238..b43072b 100644 (file)
@@ -211,6 +211,12 @@ public:
     void alloc(const SkImageInfo&);
 
     /**
+     * Gets the size and optionally the rowBytes that would be allocated by SkAutoPixmapStorage if
+     * alloc/tryAlloc was called.
+     */
+    static size_t AllocSize(const SkImageInfo& info, size_t* rowBytes);
+
+    /**
      *  Returns an SkData object wrapping the allocated pixels memory, and resets the pixmap.
      *  If the storage hasn't been allocated, the result is NULL.
      */
index 4245b7f..6d67d3e 100644 (file)
@@ -24,6 +24,7 @@
 struct GrBatchAtlasConfig;
 class GrBatchFontCache;
 struct GrContextOptions;
+class GrContextThreadSafeProxy;
 class GrDrawingManager;
 class GrDrawContext;
 class GrDrawTarget;
@@ -61,6 +62,8 @@ public:
 
     virtual ~GrContext();
 
+    GrContextThreadSafeProxy* threadSafeProxy();
+
     /**
      * The GrContext normally assumes that no outsider is setting state
      * within the underlying 3D API's context/device/whatever. This call informs
@@ -359,25 +362,27 @@ public:
     SkDEBUGCODE(GrSingleOwner* debugSingleOwner() const { return &fSingleOwner; } )
 
 private:
-    GrGpu*                          fGpu;
-    const GrCaps*                   fCaps;
-    GrResourceCache*                fResourceCache;
+    GrGpu*                                  fGpu;
+    const GrCaps*                           fCaps;
+    GrResourceCache*                        fResourceCache;
     // this union exists because the inheritance of GrTextureProvider->GrResourceProvider
     // is in a private header.
     union {
-        GrResourceProvider*         fResourceProvider;
-        GrTextureProvider*          fTextureProvider;
+        GrResourceProvider*                 fResourceProvider;
+        GrTextureProvider*                  fTextureProvider;
     };
 
-    GrBatchFontCache*               fBatchFontCache;
-    SkAutoTDelete<GrLayerCache>     fLayerCache;
-    SkAutoTDelete<GrTextBlobCache>  fTextBlobCache;
+    SkAutoTUnref<GrContextThreadSafeProxy>  fThreadSafeProxy;
+
+    GrBatchFontCache*                       fBatchFontCache;
+    SkAutoTDelete<GrLayerCache>             fLayerCache;
+    SkAutoTDelete<GrTextBlobCache>          fTextBlobCache;
 
     // Set by OverbudgetCB() to request that GrContext flush before exiting a draw.
-    bool                            fFlushToReduceCacheSize;
-    bool                            fDidTestPMConversions;
-    int                             fPMToUPMConversion;
-    int                             fUPMToPMConversion;
+    bool                                    fFlushToReduceCacheSize;
+    bool                                    fDidTestPMConversions;
+    int                                     fPMToUPMConversion;
+    int                                     fUPMToPMConversion;
     // The sw backend may call GrContext::readSurfacePixels on multiple threads
     // We may transfer the responsibilty for using a mutex to the sw backend
     // when there are fewer code paths that lead to a readSurfacePixels call
@@ -388,26 +393,26 @@ private:
     // readSurfacePixels proceeds to grab it.
     // TODO: Stop pretending to make GrContext thread-safe for sw rasterization and provide
     // a mechanism to make a SkPicture safe for multithreaded sw rasterization.
-    SkMutex                         fReadPixelsMutex;
-    SkMutex                         fTestPMConversionsMutex;
+    SkMutex                                 fReadPixelsMutex;
+    SkMutex                                 fTestPMConversionsMutex;
 
     // In debug builds we guard against improper thread handling
     // This guard is passed to the GrDrawingManager and, from there to all the
     // GrDrawContexts.  It is also passed to the GrTextureProvider and SkGpuDevice.
-    mutable GrSingleOwner fSingleOwner;
+    mutable GrSingleOwner                   fSingleOwner;
 
     struct CleanUpData {
         PFCleanUpFunc fFunc;
         void*         fInfo;
     };
 
-    SkTDArray<CleanUpData>          fCleanUpData;
+    SkTDArray<CleanUpData>                  fCleanUpData;
 
-    const uint32_t                  fUniqueID;
+    const uint32_t                          fUniqueID;
 
-    SkAutoTDelete<GrDrawingManager> fDrawingManager;
+    SkAutoTDelete<GrDrawingManager>         fDrawingManager;
 
-    GrAuditTrail                    fAuditTrail;
+    GrAuditTrail                            fAuditTrail;
 
     // TODO: have the CMM use drawContexts and rm this friending
     friend class GrClipMaskManager; // the CMM is friended just so it can call 'drawingManager'
@@ -452,4 +457,23 @@ private:
     typedef SkRefCnt INHERITED;
 };
 
+/**
+ * Can be used to perform actions related to the generating GrContext in a thread safe manner. The
+ * proxy does not access the 3D API (e.g. OpenGL) that backs the generating GrContext.
+ */
+class GrContextThreadSafeProxy : public SkRefCnt {
+private:
+    GrContextThreadSafeProxy(const GrCaps* caps, uint32_t uniqueID)
+        : fCaps(SkRef(caps))
+        , fContextUniqueID(uniqueID) {}
+
+    SkAutoTUnref<const GrCaps>  fCaps;
+    uint32_t                    fContextUniqueID;
+
+    friend class GrContext;
+    friend class SkImage;
+
+    typedef SkRefCnt INHERITED;
+};
+
 #endif
index 57bb194..d7c4cff 100644 (file)
@@ -287,11 +287,19 @@ SkAutoPixmapStorage::~SkAutoPixmapStorage() {
     this->freeStorage();
 }
 
+size_t SkAutoPixmapStorage::AllocSize(const SkImageInfo& info, size_t* rowBytes) {
+    size_t rb = info.minRowBytes();
+    if (rowBytes) {
+        *rowBytes = rb;
+    }
+    return info.getSafeSize(rb);
+}
+
 bool SkAutoPixmapStorage::tryAlloc(const SkImageInfo& info) {
     this->freeStorage();
-    
-    size_t rb = info.minRowBytes();
-    size_t size = info.getSafeSize(rb);
+
+    size_t rb;
+    size_t size = AllocSize(info, &rb);
     if (0 == size) {
         return false;
     }
index a16d5c0..1f6934d 100644 (file)
@@ -128,6 +128,13 @@ GrContext::~GrContext() {
     fCaps->unref();
 }
 
+GrContextThreadSafeProxy* GrContext::threadSafeProxy() {
+    if (!fThreadSafeProxy) {
+        fThreadSafeProxy.reset(new GrContextThreadSafeProxy(fCaps, this->uniqueID()));
+    }
+    return SkRef(fThreadSafeProxy.get());
+}
+
 void GrContext::abandonContext() {
     ASSERT_SINGLE_OWNER
 
index 5586069..8a78f45 100644 (file)
@@ -359,6 +359,17 @@ sk_sp<SkImage> SkImage::MakeFromTexture(GrContext*, const GrBackendTextureDesc&,
     return nullptr;
 }
 
+size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy&,
+                                            const DeferredTextureImageUsageParams[],
+                                            int paramCnt, void* buffer) const {
+    return 0;
+}
+
+sk_sp<SkImage> SkImage::MakeFromDeferredTextureImageData(GrContext* context, const void*,
+                                                         SkBudgeted) {
+    return nullptr;
+}
+
 sk_sp<SkImage> SkImage::MakeFromAdoptedTexture(GrContext*, const GrBackendTextureDesc&,
                                                SkAlphaType) {
     return nullptr;
@@ -441,4 +452,9 @@ SkImage* SkImage::NewFromPicture(const SkPicture* picture, const SkISize& dimens
 SkImage* SkImage::NewTextureFromPixmap(GrContext* ctx, const SkPixmap& pmap, SkBudgeted budgeted) {
     return MakeTextureFromPixmap(ctx, pmap, budgeted).release();
 }
+
+SkImage* SkImage::NewFromDeferredTextureImageData(GrContext* ctx, const void* data,
+                                                  SkBudgeted budgeted) {
+    return MakeFromDeferredTextureImageData(ctx, data, budgeted).release();
+}
 #endif
index 6494fd1..cde4232 100644 (file)
@@ -327,6 +327,119 @@ sk_sp<SkImage> SkImage::MakeTextureFromPixmap(GrContext* ctx, const SkPixmap& pi
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+class DeferredTextureImage {
+public:
+    SkImage* newImage(GrContext* context, SkBudgeted) const;
+
+private:
+    uint32_t fContextUniqueID;
+    struct Data {
+        SkImageInfo fInfo;
+        void*       fPixelData;
+        size_t      fRowBytes;
+        int         fColorTableCnt;
+        uint32_t*   fColorTableData;
+    };
+    Data fData;
+
+    friend class SkImage;
+};
+
+size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& proxy,
+                                            const DeferredTextureImageUsageParams[],
+                                            int paramCnt, void* buffer) const {
+    const bool fillMode = SkToBool(buffer);
+    if (fillMode && !SkIsAlign8(reinterpret_cast<intptr_t>(buffer))) {
+        return 0;
+    }
+
+    SkAutoPixmapStorage pixmap;
+    SkImageInfo info;
+    size_t pixelSize = 0;
+    size_t ctSize = 0;
+    int ctCount = 0;
+    if (this->peekPixels(&pixmap)) {
+        info = pixmap.info();
+        pixelSize = SkAlign8(pixmap.getSafeSize());
+        if (pixmap.ctable()) {
+            ctCount = pixmap.ctable()->count();
+            ctSize = SkAlign8(pixmap.ctable()->count() * 4);
+        }
+    } else {
+        // Here we're just using presence of data to know whether there is a codec behind the image.
+        // In the future we will access the cacherator and get the exact data that we want to (e.g.
+        // yuv planes) upload.
+        SkAutoTUnref<SkData> data(this->refEncoded());
+        if (!data) {
+            return 0;
+        }
+        SkAlphaType at = this->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
+        info = SkImageInfo::MakeN32(this->width(), this->height(), at);
+        pixelSize = SkAlign8(SkAutoPixmapStorage::AllocSize(info, nullptr));
+        if (fillMode) {
+            pixmap.alloc(info);
+            if (!this->readPixels(pixmap, 0, 0, SkImage::kDisallow_CachingHint)) {
+                return 0;
+            }
+            SkASSERT(!pixmap.ctable());
+        }
+    }
+    size_t size = 0;
+    size_t dtiSize = SkAlign8(sizeof(DeferredTextureImage));
+    size += dtiSize;
+    size_t pixelOffset = size;
+    size += pixelSize;
+    size_t ctOffset = size;
+    size += ctSize;
+    if (!fillMode) {
+        return size;
+    }
+    intptr_t bufferAsInt = reinterpret_cast<intptr_t>(buffer);
+    void* pixels = reinterpret_cast<void*>(bufferAsInt + pixelOffset);
+    SkPMColor* ct = nullptr;
+    if (ctSize) {
+        ct = reinterpret_cast<SkPMColor*>(bufferAsInt + ctOffset);
+    }
+
+    memcpy(pixels, pixmap.addr(), pixmap.getSafeSize());
+    if (ctSize) {
+        memcpy(ct, pixmap.ctable()->readColors(), ctSize);
+    }
+
+    SkASSERT(info == pixmap.info());
+    size_t rowBytes = pixmap.rowBytes();
+    DeferredTextureImage* dti = new (buffer) DeferredTextureImage();
+    dti->fContextUniqueID = proxy.fContextUniqueID;
+    dti->fData.fInfo = info;
+    dti->fData.fPixelData = pixels;
+    dti->fData.fRowBytes = rowBytes;
+    dti->fData.fColorTableCnt = ctCount;
+    dti->fData.fColorTableData = ct;
+    return size;
+}
+
+sk_sp<SkImage> SkImage::MakeFromDeferredTextureImageData(GrContext* context, const void* data,
+                                                         SkBudgeted budgeted) {
+    if (!data) {
+        return nullptr;
+    }
+    const DeferredTextureImage* dti = reinterpret_cast<const DeferredTextureImage*>(data);
+
+    if (!context || context->uniqueID() != dti->fContextUniqueID) {
+        return nullptr;
+    }
+    SkAutoTUnref<SkColorTable> colorTable;
+    if (dti->fData.fColorTableCnt) {
+        SkASSERT(dti->fData.fColorTableData);
+        colorTable.reset(new SkColorTable(dti->fData.fColorTableData, dti->fData.fColorTableCnt));
+    }
+    SkPixmap pixmap;
+    pixmap.reset(dti->fData.fInfo, dti->fData.fPixelData, dti->fData.fRowBytes, colorTable.get());
+    return SkImage::MakeTextureFromPixmap(context, pixmap, budgeted);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
 GrTexture* GrDeepCopyTexture(GrTexture* src, SkBudgeted budgeted) {
     GrContext* ctx = src->getContext();
 
index aaf601d..9dfbe36 100644 (file)
@@ -822,4 +822,75 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(NewTextureFromPixmap, reporter, context) {
     }
 }
 
+DEF_GPUTEST_FOR_NATIVE_CONTEXT(DeferredTextureImage, reporter, context, glContext) {
+    SkAutoTUnref<GrContextThreadSafeProxy> proxy(context->threadSafeProxy());
+
+    GrContextFactory otherFactory;
+    GrContextFactory::ContextInfo otherContextInfo =
+        otherFactory.getContextInfo(GrContextFactory::kNative_GLContextType);
+
+    glContext->makeCurrent();
+    REPORTER_ASSERT(reporter, proxy);
+    struct {
+        std::function<SkImage *()> fImageFactory;
+        bool                       fExpectation;
+    } testCases[] = {
+        { create_image,          true },
+        { create_codec_image,    true },
+        { create_data_image,     true },
+        { create_picture_image,  false },
+        { [context] { return create_gpu_image(context); }, false },
+        // Create a texture image in a another GrContext.
+        { [glContext, otherContextInfo] {
+            otherContextInfo.fGLContext->makeCurrent();
+            SkImage *otherContextImage = create_gpu_image(otherContextInfo.fGrContext);
+            glContext->makeCurrent();
+            return otherContextImage;
+          }, false },
+    };
+
+
+    for (auto testCase : testCases) {
+        SkAutoTUnref<SkImage> image(testCase.fImageFactory());
+
+        // This isn't currently used in the implementation, just set any old values.
+        SkImage::DeferredTextureImageUsageParams params;
+        params.fQuality = kLow_SkFilterQuality;
+        params.fMatrix = SkMatrix::I();
+
+        size_t size = image->getDeferredTextureImageData(*proxy, &params, 1, nullptr);
+
+        static const char *const kFS[] = { "fail", "succeed" };
+        if (SkToBool(size) != testCase.fExpectation) {
+            ERRORF(reporter,  "This image was expected to %s but did not.",
+                   kFS[testCase.fExpectation]);
+        }
+        if (size) {
+            void* buffer = sk_malloc_throw(size);
+            void* misaligned = reinterpret_cast<void*>(reinterpret_cast<intptr_t>(buffer) + 3);
+            if (image->getDeferredTextureImageData(*proxy, &params, 1, misaligned)) {
+                ERRORF(reporter, "Should fail when buffer is misaligned.");
+            }
+            if (!image->getDeferredTextureImageData(*proxy, &params, 1, buffer)) {
+                ERRORF(reporter, "deferred image size succeeded but creation failed.");
+            } else {
+                for (auto budgeted : { SkBudgeted::kNo, SkBudgeted::kYes }) {
+                    SkAutoTUnref<SkImage> newImage(
+                        SkImage::NewFromDeferredTextureImageData(context, buffer, budgeted));
+                    REPORTER_ASSERT(reporter, SkToBool(newImage));
+                    if (newImage) {
+                        check_images_same(reporter, image, newImage);
+                    }
+                    // The other context should not be able to create images from texture data
+                    // created by the original context.
+                    SkAutoTUnref<SkImage> newImage2(SkImage::NewFromDeferredTextureImageData(
+                        otherContextInfo.fGrContext, buffer, budgeted));
+                    REPORTER_ASSERT(reporter, !newImage2);
+                    glContext->makeCurrent();
+                }
+            }
+            sk_free(buffer);
+        }
+    }
+}
 #endif