Merge changes I61e8a679,Ic53a4225

* changes:
  Support caching of {LinearEffect, SkRuntimeEffect} pairs
  Introduce AutoBackendTexture into Skia-RenderEngine
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index cd7f37b..b878150 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -31,6 +31,9 @@
         "libui",
         "libutils",
     ],
+    include_dirs: [
+        "external/skia/src/gpu",
+    ],
     whole_static_libs: ["libskia"],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
@@ -75,6 +78,7 @@
 filegroup {
     name: "librenderengine_skia_sources",
     srcs: [
+        "skia/AutoBackendTexture.cpp",
         "skia/SkiaRenderEngine.cpp",
         "skia/SkiaGLRenderEngine.cpp",
         "skia/filters/BlurFilter.cpp",
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
new file mode 100644
index 0000000..d126c27
--- /dev/null
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AutoBackendTexture.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <utils/Trace.h>
+
+#include "log/log_main.h"
+#include "utils/Trace.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+// Converts an android dataspace to a supported SkColorSpace
+// Supported dataspaces are
+// 1. sRGB
+// 2. Display P3
+// 3. BT2020 PQ
+// 4. BT2020 HLG
+// Unknown primaries are mapped to BT709, and unknown transfer functions
+// are mapped to sRGB.
+static sk_sp<SkColorSpace> toSkColorSpace(ui::Dataspace dataspace) {
+    skcms_Matrix3x3 gamut;
+    switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
+        case HAL_DATASPACE_STANDARD_BT709:
+            gamut = SkNamedGamut::kSRGB;
+            break;
+        case HAL_DATASPACE_STANDARD_BT2020:
+            gamut = SkNamedGamut::kRec2020;
+            break;
+        case HAL_DATASPACE_STANDARD_DCI_P3:
+            gamut = SkNamedGamut::kDisplayP3;
+            break;
+        default:
+            ALOGV("Unsupported Gamut: %d, defaulting to sRGB", dataspace);
+            gamut = SkNamedGamut::kSRGB;
+            break;
+    }
+
+    switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
+        case HAL_DATASPACE_TRANSFER_LINEAR:
+            return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut);
+        case HAL_DATASPACE_TRANSFER_SRGB:
+            return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
+        case HAL_DATASPACE_TRANSFER_ST2084:
+            return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
+        case HAL_DATASPACE_TRANSFER_HLG:
+            return SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut);
+        default:
+            ALOGV("Unsupported Gamma: %d, defaulting to sRGB transfer", dataspace);
+            return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
+    }
+}
+
+AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer,
+                                       bool isRender) {
+    AHardwareBuffer_Desc desc;
+    AHardwareBuffer_describe(buffer, &desc);
+    bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
+    GrBackendFormat backendFormat =
+            GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+    mBackendTexture =
+            GrAHardwareBufferUtils::MakeBackendTexture(context, buffer, desc.width, desc.height,
+                                                       &mDeleteProc, &mUpdateProc, &mImageCtx,
+                                                       createProtectedImage, backendFormat,
+                                                       isRender);
+    mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
+}
+
+void AutoBackendTexture::unref(bool releaseLocalResources) {
+    if (releaseLocalResources) {
+        mSurface = nullptr;
+        mImage = nullptr;
+    }
+
+    mUsageCount--;
+    if (mUsageCount <= 0) {
+        if (mBackendTexture.isValid()) {
+            mDeleteProc(mImageCtx);
+            mBackendTexture = {};
+        }
+        delete this;
+    }
+}
+
+// releaseSurfaceProc is invoked by SkSurface, when the texture is no longer in use.
+// "releaseContext" contains an "AutoBackendTexture*".
+void AutoBackendTexture::releaseSurfaceProc(SkSurface::ReleaseContext releaseContext) {
+    AutoBackendTexture* textureRelease = reinterpret_cast<AutoBackendTexture*>(releaseContext);
+    textureRelease->unref(false);
+}
+
+// releaseImageProc is invoked by SkImage, when the texture is no longer in use.
+// "releaseContext" contains an "AutoBackendTexture*".
+void AutoBackendTexture::releaseImageProc(SkImage::ReleaseContext releaseContext) {
+    AutoBackendTexture* textureRelease = reinterpret_cast<AutoBackendTexture*>(releaseContext);
+    textureRelease->unref(false);
+}
+
+sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
+                                             GrDirectContext* context) {
+    ATRACE_CALL();
+
+    if (mBackendTexture.isValid()) {
+        mUpdateProc(mImageCtx, context);
+    }
+
+    sk_sp<SkImage> image =
+            SkImage::MakeFromTexture(context, mBackendTexture, kTopLeft_GrSurfaceOrigin, mColorType,
+                                     alphaType, toSkColorSpace(dataspace), releaseImageProc, this);
+    if (image.get()) {
+        // The following ref will be counteracted by releaseProc, when SkImage is discarded.
+        ref();
+    }
+
+    mImage = image;
+    mDataspace = dataspace;
+    LOG_ALWAYS_FATAL_IF(mImage == nullptr, "Unable to generate SkImage from buffer");
+    return mImage;
+}
+
+sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace,
+                                                        GrDirectContext* context) {
+    ATRACE_CALL();
+    if (!mSurface.get() || mDataspace != dataspace) {
+        sk_sp<SkSurface> surface =
+                SkSurface::MakeFromBackendTexture(context, mBackendTexture,
+                                                  kTopLeft_GrSurfaceOrigin, 0, mColorType,
+                                                  toSkColorSpace(dataspace), nullptr,
+                                                  releaseSurfaceProc, this);
+        if (surface.get()) {
+            // The following ref will be counteracted by releaseProc, when SkSurface is discarded.
+            ref();
+        }
+        mSurface = surface;
+    }
+
+    mDataspace = dataspace;
+    LOG_ALWAYS_FATAL_IF(mSurface == nullptr, "Unable to generate SkSurface");
+    return mSurface;
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
new file mode 100644
index 0000000..30f4b77
--- /dev/null
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <GrAHardwareBufferUtils.h>
+#include <GrDirectContext.h>
+#include <SkImage.h>
+#include <SkSurface.h>
+#include <sys/types.h>
+
+#include "android-base/macros.h"
+#include "ui/GraphicTypes.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+/**
+ * AutoBackendTexture manages GPU image  lifetime. It is a ref-counted object
+ * that keeps GPU resources alive until the last SkImage or SkSurface object using them is
+ * destroyed.
+ */
+class AutoBackendTexture {
+public:
+    // Local reference that supports RAII-style management of an AutoBackendTexture
+    // AutoBackendTexture by itself can't be managed in a similar fashion because
+    // of shared ownership with Skia objects, so we wrap it here instead.
+    class LocalRef {
+    public:
+        LocalRef() {}
+
+        ~LocalRef() {
+            // Destroying the texture is the same as setting it to null
+            setTexture(nullptr);
+        }
+
+        // Sets the texture to locally ref-track.
+        void setTexture(AutoBackendTexture* texture) {
+            if (mTexture != nullptr) {
+                mTexture->unref(true);
+            }
+
+            mTexture = texture;
+            if (mTexture != nullptr) {
+                mTexture->ref();
+            }
+        }
+
+        AutoBackendTexture* getTexture() const { return mTexture; }
+
+        DISALLOW_COPY_AND_ASSIGN(LocalRef);
+
+    private:
+        AutoBackendTexture* mTexture = nullptr;
+    };
+
+    // Creates a GrBackendTexture whose contents come from the provided buffer.
+    AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, bool isRender);
+
+    void ref() { mUsageCount++; }
+
+    // releaseLocalResources is true if the underlying SkImage and SkSurface
+    // should be deleted from local tracking.
+    void unref(bool releaseLocalResources);
+
+    // Makes a new SkImage from the texture content.
+    // As SkImages are immutable but buffer content is not, we create
+    // a new SkImage every time.
+    sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
+                             GrDirectContext* context);
+
+    // Makes a new SkSurface from the texture content, if needed.
+    sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace, GrDirectContext* context);
+
+private:
+    // The only way to invoke dtor is with unref, when mUsageCount is 0.
+    ~AutoBackendTexture() {}
+
+    GrBackendTexture mBackendTexture;
+    GrAHardwareBufferUtils::DeleteImageProc mDeleteProc;
+    GrAHardwareBufferUtils::UpdateImageProc mUpdateProc;
+    GrAHardwareBufferUtils::TexImageCtx mImageCtx;
+
+    static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext);
+    static void releaseImageProc(SkImage::ReleaseContext releaseContext);
+
+    int mUsageCount = 0;
+
+    sk_sp<SkImage> mImage = nullptr;
+    sk_sp<SkSurface> mSurface = nullptr;
+    ui::Dataspace mDataspace = ui::Dataspace::UNKNOWN;
+    SkColorType mColorType = kUnknown_SkColorType;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 579260b..74f342a 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -16,8 +16,10 @@
 
 //#define LOG_NDEBUG 0
 #include <cstdint>
+#include <memory>
 
 #include "SkImageInfo.h"
+#include "log/log_main.h"
 #include "system/graphics-base-v1.0.h"
 #undef LOG_TAG
 #define LOG_TAG "RenderEngine"
@@ -145,47 +147,6 @@
     return err;
 }
 
-// Converts an android dataspace to a supported SkColorSpace
-// Supported dataspaces are
-// 1. sRGB
-// 2. Display P3
-// 3. BT2020 PQ
-// 4. BT2020 HLG
-// Unknown primaries are mapped to BT709, and unknown transfer functions
-// are mapped to sRGB.
-static sk_sp<SkColorSpace> toColorSpace(ui::Dataspace dataspace) {
-    skcms_Matrix3x3 gamut;
-    switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
-        case HAL_DATASPACE_STANDARD_BT709:
-            gamut = SkNamedGamut::kSRGB;
-            break;
-        case HAL_DATASPACE_STANDARD_BT2020:
-            gamut = SkNamedGamut::kRec2020;
-            break;
-        case HAL_DATASPACE_STANDARD_DCI_P3:
-            gamut = SkNamedGamut::kDisplayP3;
-            break;
-        default:
-            ALOGV("Unsupported Gamut: %d, defaulting to sRGB", dataspace);
-            gamut = SkNamedGamut::kSRGB;
-            break;
-    }
-
-    switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
-        case HAL_DATASPACE_TRANSFER_LINEAR:
-            return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut);
-        case HAL_DATASPACE_TRANSFER_SRGB:
-            return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
-        case HAL_DATASPACE_TRANSFER_ST2084:
-            return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
-        case HAL_DATASPACE_TRANSFER_HLG:
-            return SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut);
-        default:
-            ALOGV("Unsupported Gamma: %d, defaulting to sRGB transfer", dataspace);
-            return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
-    }
-}
-
 std::unique_ptr<SkiaGLRenderEngine> SkiaGLRenderEngine::create(
         const RenderEngineCreationArgs& args) {
     // initialize EGL for the default display
@@ -457,7 +418,8 @@
 
 void SkiaGLRenderEngine::unbindExternalTextureBuffer(uint64_t bufferId) {
     std::lock_guard<std::mutex> lock(mRenderingMutex);
-    mImageCache.erase(bufferId);
+    mTextureCache.erase(bufferId);
+    mProtectedTextureCache.erase(bufferId);
 }
 
 status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
@@ -486,36 +448,36 @@
     }
 
     auto grContext = mInProtectedContext ? mProtectedGrContext : mGrContext;
-    auto& cache = mInProtectedContext ? mProtectedSurfaceCache : mSurfaceCache;
+    auto& cache = mInProtectedContext ? mProtectedTextureCache : mTextureCache;
     AHardwareBuffer_Desc bufferDesc;
     AHardwareBuffer_describe(buffer->toAHardwareBuffer(), &bufferDesc);
     LOG_ALWAYS_FATAL_IF(!hasUsage(bufferDesc, AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE),
                         "missing usage");
 
-    sk_sp<SkSurface> surface;
+    std::shared_ptr<AutoBackendTexture::LocalRef> surfaceTextureRef = nullptr;
     if (useFramebufferCache) {
         auto iter = cache.find(buffer->getId());
         if (iter != cache.end()) {
             ALOGV("Cache hit!");
-            surface = iter->second;
+            surfaceTextureRef = iter->second;
         }
     }
-    if (!surface) {
-        surface = SkSurface::MakeFromAHardwareBuffer(grContext.get(), buffer->toAHardwareBuffer(),
-                                                     GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
-                                                     mUseColorManagement
-                                                             ? toColorSpace(display.outputDataspace)
-                                                             : SkColorSpace::MakeSRGB(),
-                                                     nullptr);
-        if (useFramebufferCache && surface) {
+
+    if (surfaceTextureRef == nullptr || surfaceTextureRef->getTexture() == nullptr) {
+        surfaceTextureRef = std::make_shared<AutoBackendTexture::LocalRef>();
+        surfaceTextureRef->setTexture(
+                new AutoBackendTexture(mGrContext.get(), buffer->toAHardwareBuffer(), true));
+        if (useFramebufferCache) {
             ALOGD("Adding to cache");
-            cache.insert({buffer->getId(), surface});
+            cache.insert({buffer->getId(), surfaceTextureRef});
         }
     }
-    if (!surface) {
-        ALOGE("Failed to make surface");
-        return BAD_VALUE;
-    }
+
+    sk_sp<SkSurface> surface =
+            surfaceTextureRef->getTexture()->getOrCreateSurface(mUseColorManagement
+                                                                        ? display.outputDataspace
+                                                                        : ui::Dataspace::SRGB,
+                                                                mGrContext.get());
 
     auto canvas = surface->getCanvas();
     // Clear the entire canvas with a transparent black to prevent ghost images.
@@ -586,28 +548,35 @@
             const auto& item = layer->source.buffer;
             const auto bufferWidth = item.buffer->getBounds().width();
             const auto bufferHeight = item.buffer->getBounds().height();
-            sk_sp<SkImage> image;
-            auto iter = mImageCache.find(item.buffer->getId());
-            if (iter != mImageCache.end()) {
-                image = iter->second;
+            std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = nullptr;
+            auto iter = mTextureCache.find(item.buffer->getId());
+            if (iter != mTextureCache.end()) {
+                imageTextureRef = iter->second;
             } else {
-                image = SkImage::MakeFromAHardwareBuffer(
-                        item.buffer->toAHardwareBuffer(),
-                        item.isOpaque ? kOpaque_SkAlphaType
-                                      : (item.usePremultipliedAlpha ? kPremul_SkAlphaType
-                                                                    : kUnpremul_SkAlphaType),
-                        mUseColorManagement
-                                ? (needsToneMapping(layer->sourceDataspace, display.outputDataspace)
-                                           // If we need to map to linear space, then
-                                           // mark the source image with the same
-                                           // colorspace as the destination surface so
-                                           // that Skia's color management is a no-op.
-                                           ? toColorSpace(display.outputDataspace)
-                                           : toColorSpace(layer->sourceDataspace))
-                                : SkColorSpace::MakeSRGB());
-                mImageCache.insert({item.buffer->getId(), image});
+                imageTextureRef = std::make_shared<AutoBackendTexture::LocalRef>();
+                imageTextureRef->setTexture(new AutoBackendTexture(mGrContext.get(),
+                                                                   item.buffer->toAHardwareBuffer(),
+                                                                   false));
+                mTextureCache.insert({buffer->getId(), imageTextureRef});
             }
-
+            sk_sp<SkImage> image =
+                    imageTextureRef->getTexture()
+                            ->makeImage(mUseColorManagement
+                                                ? (needsToneMapping(layer->sourceDataspace,
+                                                                    display.outputDataspace)
+                                                           // If we need to map to linear space,
+                                                           // then mark the source image with the
+                                                           // same colorspace as the destination
+                                                           // surface so that Skia's color
+                                                           // management is a no-op.
+                                                           ? display.outputDataspace
+                                                           : layer->sourceDataspace)
+                                                : ui::Dataspace::SRGB,
+                                        item.isOpaque ? kOpaque_SkAlphaType
+                                                      : (item.usePremultipliedAlpha
+                                                                 ? kPremul_SkAlphaType
+                                                                 : kUnpremul_SkAlphaType),
+                                        mGrContext.get());
             SkMatrix matrix;
             if (layer->geometry.roundedCornersRadius > 0) {
                 const auto roundedRect = getRoundedRect(layer);
@@ -669,7 +638,16 @@
                                                    .outputDataspace = display.outputDataspace,
                                                    .undoPremultipliedAlpha = !item.isOpaque &&
                                                            item.usePremultipliedAlpha};
-                sk_sp<SkRuntimeEffect> runtimeEffect = buildRuntimeEffect(effect);
+
+                auto effectIter = mRuntimeEffects.find(effect);
+                sk_sp<SkRuntimeEffect> runtimeEffect = nullptr;
+                if (effectIter == mRuntimeEffects.end()) {
+                    runtimeEffect = buildRuntimeEffect(effect);
+                    mRuntimeEffects.insert({effect, runtimeEffect});
+                } else {
+                    runtimeEffect = effectIter->second;
+                }
+
                 paint.setShader(createLinearEffectShader(shader, effect, runtimeEffect,
                                                          display.maxLuminance,
                                                          layer->source.buffer.maxMasteringLuminance,
@@ -921,10 +899,7 @@
     return eglCreatePbufferSurface(display, placeholderConfig, attributes.data());
 }
 
-void SkiaGLRenderEngine::cleanFramebufferCache() {
-    mSurfaceCache.clear();
-    mProtectedSurfaceCache.clear();
-}
+void SkiaGLRenderEngine::cleanFramebufferCache() {}
 
 } // namespace skia
 } // namespace renderengine
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 965cb41..f5eed1e 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -29,9 +29,13 @@
 #include <mutex>
 #include <unordered_map>
 
+#include "AutoBackendTexture.h"
 #include "EGL/egl.h"
+#include "SkImageInfo.h"
 #include "SkiaRenderEngine.h"
+#include "android-base/macros.h"
 #include "filters/BlurFilter.h"
+#include "skia/filters/LinearEffect.h"
 
 namespace android {
 namespace renderengine {
@@ -92,8 +96,12 @@
 
     const bool mUseColorManagement;
 
-    // Cache of GL images that we'll store per GraphicBuffer ID
-    std::unordered_map<uint64_t, sk_sp<SkImage>> mImageCache GUARDED_BY(mRenderingMutex);
+    // Cache of GL textures that we'll store per GraphicBuffer ID
+    std::unordered_map<uint64_t, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
+            GUARDED_BY(mRenderingMutex);
+    std::unordered_map<uint64_t, std::shared_ptr<AutoBackendTexture::LocalRef>>
+            mProtectedTextureCache GUARDED_BY(mRenderingMutex);
+    std::unordered_map<LinearEffect, sk_sp<SkRuntimeEffect>, LinearEffectHasher> mRuntimeEffects;
     // Mutex guarding rendering operations, so that:
     // 1. GL operations aren't interleaved, and
     // 2. Internal state related to rendering that is potentially modified by
@@ -107,8 +115,6 @@
     // Same as above, but for protected content (eg. DRM)
     sk_sp<GrDirectContext> mProtectedGrContext;
 
-    std::unordered_map<uint64_t, sk_sp<SkSurface>> mSurfaceCache;
-    std::unordered_map<uint64_t, sk_sp<SkSurface>> mProtectedSurfaceCache;
     bool mInProtectedContext = false;
 };
 
diff --git a/libs/renderengine/skia/filters/LinearEffect.cpp b/libs/renderengine/skia/filters/LinearEffect.cpp
index b628b3e..b3d5d63 100644
--- a/libs/renderengine/skia/filters/LinearEffect.cpp
+++ b/libs/renderengine/skia/filters/LinearEffect.cpp
@@ -16,7 +16,10 @@
 
 #include "LinearEffect.h"
 
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
 #include <SkString.h>
+#include <utils/Trace.h>
 
 #include <optional>
 
@@ -427,6 +430,7 @@
 }
 
 sk_sp<SkRuntimeEffect> buildRuntimeEffect(const LinearEffect& linearEffect) {
+    ATRACE_CALL();
     SkString shaderString;
     generateEOTF(linearEffect.inputDataspace, shaderString);
     generateXYZTransforms(shaderString);
@@ -445,6 +449,7 @@
                                          sk_sp<SkRuntimeEffect> runtimeEffect,
                                          float maxDisplayLuminance, float maxMasteringLuminance,
                                          float maxContentLuminance) {
+    ATRACE_CALL();
     SkRuntimeShaderBuilder effectBuilder(runtimeEffect);
 
     effectBuilder.child("input") = shader;
diff --git a/libs/renderengine/skia/filters/LinearEffect.h b/libs/renderengine/skia/filters/LinearEffect.h
index 2615669..dadba32 100644
--- a/libs/renderengine/skia/filters/LinearEffect.h
+++ b/libs/renderengine/skia/filters/LinearEffect.h
@@ -63,6 +63,24 @@
     const bool undoPremultipliedAlpha = false;
 };
 
+static inline bool operator==(const LinearEffect& lhs, const LinearEffect& rhs) {
+    return lhs.inputDataspace == rhs.inputDataspace && lhs.outputDataspace == rhs.outputDataspace &&
+            lhs.undoPremultipliedAlpha == rhs.undoPremultipliedAlpha;
+}
+
+struct LinearEffectHasher {
+    // Inspired by art/runtime/class_linker.cc
+    // Also this is what boost:hash_combine does
+    static size_t HashCombine(size_t seed, size_t val) {
+        return seed ^ (val + 0x9e3779b9 + (seed << 6) + (seed >> 2));
+    }
+    size_t operator()(const LinearEffect& le) const {
+        size_t result = std::hash<ui::Dataspace>{}(le.inputDataspace);
+        result = HashCombine(result, std::hash<ui::Dataspace>{}(le.outputDataspace));
+        return HashCombine(result, std::hash<bool>{}(le.undoPremultipliedAlpha));
+    }
+};
+
 sk_sp<SkRuntimeEffect> buildRuntimeEffect(const LinearEffect& linearEffect);
 
 // Generates a shader resulting from applying the a linear effect created from