Add ExternalTexture class into RenderEngine interface

ExternalTexture is an RAII structure that wraps raw GraphicBuffers that
are passed into RenderEngine. ExternalTexture's role is to help with
managing GPU resources of GraphicBuffers by mapping buffers into
textures, EGLImages, or AutoBackendTextures depending on the
RenderEngine backend. Under the hood, mapExternalTextureBuffer and
unmapExternalTextureBuffer (renamed from cacheExternalTextureBuffer and
unbindExternalTextureBuffer respectively) are used to help tie
resource management to the ExternalTexture lifetime.

The main motivation for this is that currently managing buffer
lifecycle has historically been errorprone and caused memory leaks, so
this improves code health.

As part of this:
* mapExternalTextureBuffer and unmapExternalTextureBuffer
are now protected methods, and are never called outside of RenderEngine
with the exception of creating and destroying ExternalTextures.
* Because GLESRenderEngine's output buffers are cached differently from
Skia RenderEngine, if there are output-only buffers then disable the
mapExternalTextureBuffer calls whenever GLESRenderEngine is used.
* Custom RAII classes in the Planner and in BufferLayerConsumer are now
removed since they're subsumed by ExternalTexture
* RenderSurface now controls its own management of ExternalTextures in a
small queue
* cleanFramebufferCache is now unimplemented for Skia, because
ExternalTextures are now deleted whenever a RenderSurface is deleted.

Bug: 180767535
Test: libsurfaceflinger_unittest
Test: libcompositionengine_test
Test: librenderengine_test
Test: Simulate virtual displays
Test: Screen reotation
Test: Movie playback on Google TV
Test: Force GPU composition
Test: screenshot

Change-Id: I222c71e6e1c67485cdeac49e2cb829289af9efec
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
index bb75878..2d61cf8 100644
--- a/libs/renderengine/skia/AutoBackendTexture.h
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -21,9 +21,9 @@
 #include <SkImage.h>
 #include <SkSurface.h>
 #include <sys/types.h>
+#include <ui/GraphicTypes.h>
 
 #include "android-base/macros.h"
-#include "ui/GraphicTypes.h"
 
 namespace android {
 namespace renderengine {
@@ -41,13 +41,18 @@
     // of shared ownership with Skia objects, so we wrap it here instead.
     class LocalRef {
     public:
-        LocalRef() {}
+        LocalRef(AutoBackendTexture* texture) { setTexture(texture); }
 
         ~LocalRef() {
             // Destroying the texture is the same as setting it to null
             setTexture(nullptr);
         }
 
+        AutoBackendTexture* getTexture() const { return mTexture; }
+
+        DISALLOW_COPY_AND_ASSIGN(LocalRef);
+
+    private:
         // Sets the texture to locally ref-track.
         void setTexture(AutoBackendTexture* texture) {
             if (mTexture != nullptr) {
@@ -59,12 +64,6 @@
                 mTexture->ref();
             }
         }
-
-        AutoBackendTexture* getTexture() const { return mTexture; }
-
-        DISALLOW_COPY_AND_ASSIGN(LocalRef);
-
-    private:
         AutoBackendTexture* mTexture = nullptr;
     };
 
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index 1db20c0..1c2b2fc 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -46,7 +46,7 @@
 } // namespace
 
 static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
-                             sp<GraphicBuffer> dstBuffer) {
+                             const std::shared_ptr<ExternalTexture>& dstTexture) {
     // Somewhat arbitrary dimensions, but on screen and slightly shorter, based
     // on actual use.
     FloatRect rect(0, 0, display.physicalDisplay.width(), display.physicalDisplay.height() - 30);
@@ -73,7 +73,7 @@
 
     auto layers = std::vector<const LayerSettings*>{&layer};
     // The identity matrix will generate the fast shader
-    renderengine->drawLayers(display, layers, dstBuffer, kUseFrameBufferCache, base::unique_fd(),
+    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd(),
                              nullptr);
     // This matrix, which has different scales for x and y, will
     // generate the slower (more general case) version, which has variants for translucent
@@ -86,13 +86,14 @@
     // clang-format on
     for (auto translucent : {false, true}) {
         layer.shadow.casterIsTranslucent = translucent;
-        renderengine->drawLayers(display, layers, dstBuffer, kUseFrameBufferCache,
+        renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
                                  base::unique_fd(), nullptr);
     }
 }
 
 static void drawImageLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
-                            sp<GraphicBuffer> dstBuffer, sp<GraphicBuffer> srcBuffer) {
+                            const std::shared_ptr<ExternalTexture>& dstTexture,
+                            const std::shared_ptr<ExternalTexture>& srcTexture) {
     const Rect& displayRect = display.physicalDisplay;
     FloatRect rect(0, 0, displayRect.width(), displayRect.height());
     LayerSettings layer{
@@ -103,7 +104,7 @@
                     },
             .source = PixelSource{.buffer =
                                           Buffer{
-                                                  .buffer = srcBuffer,
+                                                  .buffer = srcTexture,
                                                   .maxMasteringLuminance = 1000.f,
                                                   .maxContentLuminance = 1000.f,
                                           }},
@@ -126,7 +127,7 @@
                 layer.source.buffer.isOpaque = isOpaque;
                 for (auto alpha : {half(.23999f), half(1.0f)}) {
                     layer.alpha = alpha;
-                    renderengine->drawLayers(display, layers, dstBuffer, kUseFrameBufferCache,
+                    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
                                              base::unique_fd(), nullptr);
                 }
             }
@@ -135,7 +136,7 @@
 }
 
 static void drawSolidLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
-                            sp<GraphicBuffer> dstBuffer) {
+                            const std::shared_ptr<ExternalTexture>& dstTexture) {
     const Rect& displayRect = display.physicalDisplay;
     FloatRect rect(0, 0, displayRect.width(), displayRect.height());
     LayerSettings layer{
@@ -143,11 +144,11 @@
                     Geometry{
                             .boundaries = rect,
                     },
-            .alpha = 1,
             .source =
                     PixelSource{
                             .solidColor = half3(0.1f, 0.2f, 0.3f),
                     },
+            .alpha = 1,
     };
 
     auto layers = std::vector<const LayerSettings*>{&layer};
@@ -155,14 +156,14 @@
         layer.geometry.positionTransform = transform;
         for (float roundedCornersRadius : {0.0f, 0.05f, 50.f}) {
             layer.geometry.roundedCornersRadius = roundedCornersRadius;
-            renderengine->drawLayers(display, layers, dstBuffer, kUseFrameBufferCache,
+            renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
                                      base::unique_fd(), nullptr);
         }
     }
 }
 
 static void drawBlurLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
-                           sp<GraphicBuffer> dstBuffer) {
+                           const std::shared_ptr<ExternalTexture>& dstTexture) {
     const Rect& displayRect = display.physicalDisplay;
     FloatRect rect(0, 0, displayRect.width(), displayRect.height());
     LayerSettings layer{
@@ -176,7 +177,7 @@
     auto layers = std::vector<const LayerSettings*>{&layer};
     for (int radius : {9, 60}) {
         layer.backgroundBlurRadius = radius;
-        renderengine->drawLayers(display, layers, dstBuffer, kUseFrameBufferCache,
+        renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
                                  base::unique_fd(), nullptr);
     }
 }
@@ -214,6 +215,9 @@
     sp<GraphicBuffer> dstBuffer =
             new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888, 1,
                               usage, "primeShaderCache_dst");
+
+    const auto dstTexture = std::make_shared<ExternalTexture>(dstBuffer, *renderengine,
+                                                              ExternalTexture::Usage::WRITEABLE);
     // This buffer will be the source for the call to drawImageLayers. Draw
     // something to it as a placeholder for what an app draws. We should draw
     // something, but the details are not important. Make use of the shadow layer drawing step
@@ -222,11 +226,16 @@
             new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888, 1,
                               usage, "drawImageLayer_src");
 
-    drawSolidLayers(renderengine, display, dstBuffer);
-    drawShadowLayers(renderengine, display, srcBuffer);
-    drawBlurLayers(renderengine, display, dstBuffer);
+    const auto srcTexture =
+            std::make_shared<ExternalTexture>(srcBuffer, *renderengine,
+                                              ExternalTexture::Usage::READABLE |
+                                                      ExternalTexture::Usage::WRITEABLE);
+
+    drawSolidLayers(renderengine, display, dstTexture);
+    drawShadowLayers(renderengine, display, srcTexture);
+    drawBlurLayers(renderengine, display, dstTexture);
     // The majority of shaders are related to sampling images.
-    drawImageLayers(renderengine, display, dstBuffer, srcBuffer);
+    drawImageLayers(renderengine, display, dstTexture, srcTexture);
 
     // should be the same as AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;
     const int64_t usageExternal = GRALLOC_USAGE_HW_TEXTURE;
@@ -234,12 +243,12 @@
     sp<GraphicBuffer> externalBuffer =
             new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888, 1,
                               usageExternal, "primeShaderCache_external");
+    const auto externalTexture =
+            std::make_shared<ExternalTexture>(externalBuffer, *renderengine,
+                                              ExternalTexture::Usage::READABLE);
     // TODO(b/184665179) doubles number of image shader compilations, but only somewhere
     // between 6 and 8 will occur in real uses.
-    drawImageLayers(renderengine, display, dstBuffer, externalBuffer);
-    renderengine->unbindExternalTextureBuffer(externalBuffer->getId());
-
-    renderengine->unbindExternalTextureBuffer(srcBuffer->getId());
+    drawImageLayers(renderengine, display, dstTexture, externalTexture);
 
     const nsecs_t timeAfter = systemTime();
     const float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 3b2c7e5..e781584 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -329,8 +329,6 @@
 }
 
 SkiaGLRenderEngine::~SkiaGLRenderEngine() {
-    cleanFramebufferCache();
-
     std::lock_guard<std::mutex> lock(mRenderingMutex);
     if (mBlurFilter) {
         delete mBlurFilter;
@@ -484,7 +482,8 @@
             sourceTransfer != destTransfer;
 }
 
-void SkiaGLRenderEngine::cacheExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
+void SkiaGLRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
+                                                  bool isRenderable) {
     // Only run this if RE is running on its own thread. This way the access to GL
     // operations is guaranteed to be happening on the same thread.
     if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED) {
@@ -505,25 +504,41 @@
     auto& cache = mInProtectedContext ? mProtectedTextureCache : mTextureCache;
 
     std::lock_guard<std::mutex> lock(mRenderingMutex);
-    auto iter = cache.find(buffer->getId());
-    if (iter != cache.end()) {
-        ALOGV("Texture already exists in cache.");
-    } else {
+    mGraphicBufferExternalRefs[buffer->getId()]++;
+
+    if (const auto& iter = cache.find(buffer->getId()); iter == cache.end()) {
         std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
-                std::make_shared<AutoBackendTexture::LocalRef>();
-        imageTextureRef->setTexture(
-                new AutoBackendTexture(grContext.get(), buffer->toAHardwareBuffer(), false));
+                std::make_shared<AutoBackendTexture::LocalRef>(
+                        new AutoBackendTexture(grContext.get(), buffer->toAHardwareBuffer(),
+                                               isRenderable));
         cache.insert({buffer->getId(), imageTextureRef});
     }
     // restore the original state of the protected context if necessary
     useProtectedContext(protectedContextState);
 }
 
-void SkiaGLRenderEngine::unbindExternalTextureBuffer(uint64_t bufferId) {
+void SkiaGLRenderEngine::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
     ATRACE_CALL();
     std::lock_guard<std::mutex> lock(mRenderingMutex);
-    mTextureCache.erase(bufferId);
-    mProtectedTextureCache.erase(bufferId);
+    if (const auto& iter = mGraphicBufferExternalRefs.find(buffer->getId());
+        iter != mGraphicBufferExternalRefs.end()) {
+        if (iter->second == 0) {
+            ALOGW("Attempted to unmap GraphicBuffer <id: %" PRId64
+                  "> from RenderEngine texture, but the "
+                  "ref count was already zero!",
+                  buffer->getId());
+            mGraphicBufferExternalRefs.erase(buffer->getId());
+            return;
+        }
+
+        iter->second--;
+
+        if (iter->second == 0) {
+            mTextureCache.erase(buffer->getId());
+            mProtectedTextureCache.erase(buffer->getId());
+            mGraphicBufferExternalRefs.erase(buffer->getId());
+        }
+    }
 }
 
 sk_sp<SkShader> SkiaGLRenderEngine::createRuntimeEffectShader(sk_sp<SkShader> shader,
@@ -621,8 +636,8 @@
 
 status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
                                         const std::vector<const LayerSettings*>& layers,
-                                        const sp<GraphicBuffer>& buffer,
-                                        const bool useFramebufferCache,
+                                        const std::shared_ptr<ExternalTexture>& buffer,
+                                        const bool /*useFramebufferCache*/,
                                         base::unique_fd&& bufferFence, base::unique_fd* drawFence) {
     ATRACE_NAME("SkiaGL::drawLayers");
 
@@ -645,32 +660,18 @@
         return BAD_VALUE;
     }
 
-    validateOutputBufferUsage(buffer);
+    validateOutputBufferUsage(buffer->getBuffer());
 
     auto grContext = mInProtectedContext ? mProtectedGrContext : mGrContext;
     auto& cache = mInProtectedContext ? mProtectedTextureCache : mTextureCache;
-    AHardwareBuffer_Desc bufferDesc;
-    AHardwareBuffer_describe(buffer->toAHardwareBuffer(), &bufferDesc);
 
-    std::shared_ptr<AutoBackendTexture::LocalRef> surfaceTextureRef = nullptr;
-    if (useFramebufferCache) {
-        auto iter = cache.find(buffer->getId());
-        if (iter != cache.end()) {
-            ALOGV("Cache hit!");
-            ATRACE_NAME("Cache hit");
-            surfaceTextureRef = iter->second;
-        }
-    }
-
-    if (surfaceTextureRef == nullptr || surfaceTextureRef->getTexture() == nullptr) {
-        ATRACE_NAME("Cache miss");
-        surfaceTextureRef = std::make_shared<AutoBackendTexture::LocalRef>();
-        surfaceTextureRef->setTexture(
-                new AutoBackendTexture(grContext.get(), buffer->toAHardwareBuffer(), true));
-        if (useFramebufferCache) {
-            ALOGD("Adding to cache");
-            cache.insert({buffer->getId(), surfaceTextureRef});
-        }
+    std::shared_ptr<AutoBackendTexture::LocalRef> surfaceTextureRef;
+    if (const auto& it = cache.find(buffer->getBuffer()->getId()); it != cache.end()) {
+        surfaceTextureRef = it->second;
+    } else {
+        surfaceTextureRef = std::make_shared<AutoBackendTexture::LocalRef>(
+                new AutoBackendTexture(grContext.get(), buffer->getBuffer()->toAHardwareBuffer(),
+                                       true));
     }
 
     const ui::Dataspace dstDataspace =
@@ -876,18 +877,22 @@
         SkPaint paint;
         if (layer->source.buffer.buffer) {
             ATRACE_NAME("DrawImage");
-            validateInputBufferUsage(layer->source.buffer.buffer);
+            validateInputBufferUsage(layer->source.buffer.buffer->getBuffer());
             const auto& item = layer->source.buffer;
             std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = nullptr;
-            auto iter = cache.find(item.buffer->getId());
-            if (iter != cache.end()) {
+
+            if (const auto& iter = cache.find(item.buffer->getBuffer()->getId());
+                iter != cache.end()) {
                 imageTextureRef = iter->second;
             } else {
-                imageTextureRef = std::make_shared<AutoBackendTexture::LocalRef>();
-                imageTextureRef->setTexture(new AutoBackendTexture(grContext.get(),
-                                                                   item.buffer->toAHardwareBuffer(),
-                                                                   false));
-                cache.insert({item.buffer->getId(), imageTextureRef});
+                // If we didn't find the image in the cache, then create a local ref but don't cache
+                // it. If we're using skia, we're guaranteed to run on a dedicated GPU thread so if
+                // we didn't find anything in the cache then we intentionally did not cache this
+                // buffer's resources.
+                imageTextureRef = std::make_shared<AutoBackendTexture::LocalRef>(
+                        new AutoBackendTexture(grContext.get(),
+                                               item.buffer->getBuffer()->toAHardwareBuffer(),
+                                               false));
             }
 
             sk_sp<SkImage> image =
@@ -1200,15 +1205,6 @@
     return eglCreatePbufferSurface(display, placeholderConfig, attributes.data());
 }
 
-void SkiaGLRenderEngine::cleanFramebufferCache() {
-    // TODO(b/180767535) Remove this method and use b/180767535 instead, which would allow
-    // SF to control texture lifecycle more tightly rather than through custom hooks into RE.
-    std::lock_guard<std::mutex> lock(mRenderingMutex);
-    mRuntimeEffects.clear();
-    mProtectedTextureCache.clear();
-    mTextureCache.clear();
-}
-
 int SkiaGLRenderEngine::getContextPriority() {
     int value;
     eglQueryContext(mEGLDisplay, mEGLContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value);
@@ -1281,6 +1277,12 @@
         StringAppendF(&result, "Skia's Wrapped Objects:\n");
         gpuReporter.logOutput(result, true);
 
+        StringAppendF(&result, "RenderEngine tracked buffers: %zu\n",
+                      mGraphicBufferExternalRefs.size());
+        StringAppendF(&result, "Dumping buffer ids...\n");
+        for (const auto& [id, refCounts] : mGraphicBufferExternalRefs) {
+            StringAppendF(&result, "- 0x%" PRIx64 " - %d refs \n", id, refCounts);
+        }
         StringAppendF(&result, "RenderEngine AHB/BackendTexture cache size: %zu\n",
                       mTextureCache.size());
         StringAppendF(&result, "Dumping buffer ids...\n");
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 8e77c16..e71c560 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -23,6 +23,7 @@
 #include <GrDirectContext.h>
 #include <SkSurface.h>
 #include <android-base/thread_annotations.h>
+#include <renderengine/ExternalTexture.h>
 #include <renderengine/RenderEngine.h>
 #include <sys/types.h>
 
@@ -52,13 +53,12 @@
     ~SkiaGLRenderEngine() override EXCLUDES(mRenderingMutex);
 
     void primeCache() override;
-    void cacheExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override;
-    void unbindExternalTextureBuffer(uint64_t bufferId) override;
     status_t drawLayers(const DisplaySettings& display,
                         const std::vector<const LayerSettings*>& layers,
-                        const sp<GraphicBuffer>& buffer, const bool useFramebufferCache,
-                        base::unique_fd&& bufferFence, base::unique_fd* drawFence) override;
-    void cleanFramebufferCache() override;
+                        const std::shared_ptr<ExternalTexture>& buffer,
+                        const bool useFramebufferCache, base::unique_fd&& bufferFence,
+                        base::unique_fd* drawFence) override;
+    void cleanFramebufferCache() override {}
     int getContextPriority() override;
     bool isProtected() const override { return mInProtectedContext; }
     bool supportsProtectedContent() const override;
@@ -72,6 +72,8 @@
     void dump(std::string& result) override;
     size_t getMaxTextureSize() const override;
     size_t getMaxViewportDims() const override;
+    void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) override;
+    void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override;
 
 private:
     static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
@@ -114,7 +116,9 @@
     const PixelFormat mDefaultPixelFormat;
     const bool mUseColorManagement;
 
-    // Cache of GL textures that we'll store per GraphicBuffer ID
+    // Number of external holders of ExternalTexture references, per GraphicBuffer ID.
+    std::unordered_map<uint64_t, int32_t> mGraphicBufferExternalRefs GUARDED_BY(mRenderingMutex);
+    // Cache of GL textures that we'll store per GraphicBuffer ID, sliced by GPU context.
     std::unordered_map<uint64_t, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
             GUARDED_BY(mRenderingMutex);
     std::unordered_map<uint64_t, std::shared_ptr<AutoBackendTexture::LocalRef>>
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 51ef088..308c5ff 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -42,15 +42,12 @@
     virtual void primeCache() override{};
     virtual void genTextures(size_t /*count*/, uint32_t* /*names*/) override{};
     virtual void deleteTextures(size_t /*count*/, uint32_t const* /*names*/) override{};
-    virtual void cacheExternalTextureBuffer(const sp<GraphicBuffer>& /*buffer*/){};
-    virtual void unbindExternalTextureBuffer(uint64_t /*bufferId*/){};
-
     virtual bool isProtected() const override { return false; } // mInProtectedContext; }
     virtual bool supportsProtectedContent() const override { return false; };
     virtual bool useProtectedContext(bool /*useProtectedContext*/) override { return false; };
     virtual status_t drawLayers(const DisplaySettings& /*display*/,
                                 const std::vector<const LayerSettings*>& /*layers*/,
-                                const sp<GraphicBuffer>& /*buffer*/,
+                                const std::shared_ptr<ExternalTexture>& /*buffer*/,
                                 const bool /*useFramebufferCache*/,
                                 base::unique_fd&& /*bufferFence*/,
                                 base::unique_fd* /*drawFence*/) override {
@@ -60,6 +57,11 @@
     virtual int getContextPriority() override { return 0; }
     virtual void assertShadersCompiled(int numShaders) {}
     virtual int reportShadersCompiled() { return 0; }
+
+protected:
+    virtual void mapExternalTextureBuffer(const sp<GraphicBuffer>& /*buffer*/,
+                                          bool /*isRenderable*/) override;
+    virtual void unmapExternalTextureBuffer(const sp<GraphicBuffer>& /*buffer*/) override;
 };
 
 } // namespace skia