SF/HWC2: Add support for color transforms

Adds support for color transforms using the setColorTransform method
of HWC2.

This means that instead of always falling back to client composition
when applying a transform, SurfaceFlinger will allow the device to
make that decision. If all layers fall back to client composition, the
SKIP_CLIENT_COLOR_TRANSFORM capability allows the device greater
control over whether SF should apply the transform or whether it
should allow the device to apply it to the client target buffer.

Bug: 19539930
Change-Id: I47a3d5453a3c47a8dd105ab77cce7f9c9687e925
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index ae6ba98..87a0e9a 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -317,9 +317,12 @@
             "Capability size has changed");
     uint32_t numCapabilities = 0;
     mHwcDevice->getCapabilities(mHwcDevice, &numCapabilities, nullptr);
-    mCapabilities.resize(numCapabilities);
-    auto asInt = reinterpret_cast<int32_t*>(mCapabilities.data());
+    std::vector<Capability> capabilities(numCapabilities);
+    auto asInt = reinterpret_cast<int32_t*>(capabilities.data());
     mHwcDevice->getCapabilities(mHwcDevice, &numCapabilities, asInt);
+    for (auto capability : capabilities) {
+        mCapabilities.emplace(capability);
+    }
 }
 
 bool Device::hasCapability(HWC2::Capability capability) const
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 68781da..beb7bc6 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -33,6 +33,7 @@
 #include <functional>
 #include <string>
 #include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 namespace android {
@@ -66,7 +67,7 @@
 
     std::string dump() const;
 
-    const std::vector<Capability>& getCapabilities() const {
+    const std::unordered_set<Capability>& getCapabilities() const {
         return mCapabilities;
     };
 
@@ -181,7 +182,7 @@
     HWC2_PFN_SET_LAYER_VISIBLE_REGION mSetLayerVisibleRegion;
     HWC2_PFN_SET_LAYER_Z_ORDER mSetLayerZOrder;
 
-    std::vector<Capability> mCapabilities;
+    std::unordered_set<Capability> mCapabilities;
     std::unordered_map<hwc2_display_t, std::weak_ptr<Display>> mDisplays;
 
     HotplugCallback mHotplug;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 133e5f1..199848f 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -144,6 +144,11 @@
     mRemainingHwcVirtualDisplays = mHwcDevice->getMaxVirtualDisplayCount();
 }
 
+bool HWComposer::hasCapability(HWC2::Capability capability) const
+{
+    return mHwcDevice->getCapabilities().count(capability) > 0;
+}
+
 bool HWComposer::isValidDisplay(int32_t displayId) const {
     return static_cast<size_t>(displayId) < mDisplayData.size() &&
             mDisplayData[displayId].hwcDisplay;
@@ -692,6 +697,28 @@
     return NO_ERROR;
 }
 
+status_t HWComposer::setColorTransform(int32_t displayId,
+        const mat4& transform) {
+    if (!isValidDisplay(displayId)) {
+        ALOGE("setColorTransform: Display %d is not valid", displayId);
+        return BAD_INDEX;
+    }
+
+    auto& displayData = mDisplayData[displayId];
+    bool isIdentity = transform == mat4();
+    auto error = displayData.hwcDisplay->setColorTransform(transform,
+            isIdentity ? HAL_COLOR_TRANSFORM_IDENTITY :
+            HAL_COLOR_TRANSFORM_ARBITRARY_MATRIX);
+    if (error != HWC2::Error::None) {
+        ALOGE("setColorTransform: Failed to set transform on display %d: "
+                "%s (%d)", displayId, to_string(error).c_str(),
+                static_cast<int32_t>(error));
+        return UNKNOWN_ERROR;
+    }
+
+    return NO_ERROR;
+}
+
 void HWComposer::disconnectDisplay(int displayId) {
     LOG_ALWAYS_FATAL_IF(displayId < 0);
     auto& displayData = mDisplayData[displayId];
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index aa233df..17676ae 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -81,6 +81,8 @@
 
     void setEventHandler(EventHandler* handler);
 
+    bool hasCapability(HWC2::Capability capability) const;
+
     // Attempts to allocate a virtual display. If the virtual display is created
     // on the HWC device, outId will contain its HWC ID.
     status_t allocateVirtualDisplay(uint32_t width, uint32_t height,
@@ -104,6 +106,9 @@
     // set active config
     status_t setActiveConfig(int32_t displayId, size_t configId);
 
+    // Sets a color transform to be applied to the result of composition
+    status_t setColorTransform(int32_t displayId, const mat4& transform);
+
     // reset state when an external, non-virtual display is disconnected
     void disconnectDisplay(int32_t displayId);
 
diff --git a/services/surfaceflinger/Effects/Daltonizer.cpp b/services/surfaceflinger/Effects/Daltonizer.cpp
index feb8936..a104e8f 100644
--- a/services/surfaceflinger/Effects/Daltonizer.cpp
+++ b/services/surfaceflinger/Effects/Daltonizer.cpp
@@ -19,21 +19,14 @@
 
 namespace android {
 
-Daltonizer::Daltonizer() :
-    mType(deuteranomaly), mMode(simulation), mDirty(true) {
-}
-
-Daltonizer::~Daltonizer() {
-}
-
-void Daltonizer::setType(Daltonizer::ColorBlindnessTypes type) {
+void Daltonizer::setType(ColorBlindnessType type) {
     if (type != mType) {
         mDirty = true;
         mType = type;
     }
 }
 
-void Daltonizer::setMode(Daltonizer::Mode mode) {
+void Daltonizer::setMode(ColorBlindnessMode mode) {
     if (mode != mMode) {
         mDirty = true;
         mMode = mode;
@@ -49,6 +42,11 @@
 }
 
 void Daltonizer::update() {
+    if (mType == ColorBlindnessType::None) {
+        mColorTransform = mat4();
+        return;
+    }
+
     // converts a linear RGB color to the XYZ space
     const mat4 rgb2xyz( 0.4124, 0.2126, 0.0193, 0,
                         0.3576, 0.7152, 0.1192, 0,
@@ -149,24 +147,25 @@
     mat4 correction(0);
 
     switch (mType) {
-        case protanopia:
-        case protanomaly:
+        case ColorBlindnessType::Protanomaly:
             simulation = lms2lmsp;
-            if (mMode == Daltonizer::correction)
+            if (mMode == ColorBlindnessMode::Correction)
                 correction = errp;
             break;
-        case deuteranopia:
-        case deuteranomaly:
+        case ColorBlindnessType::Deuteranomaly:
             simulation = lms2lmsd;
-            if (mMode == Daltonizer::correction)
+            if (mMode == ColorBlindnessMode::Correction)
                 correction = errd;
             break;
-        case tritanopia:
-        case tritanomaly:
+        case ColorBlindnessType::Tritanomaly:
             simulation = lms2lmst;
-            if (mMode == Daltonizer::correction)
+            if (mMode == ColorBlindnessMode::Correction)
                 correction = errt;
             break;
+        case ColorBlindnessType::None:
+            // We already caught this at the beginning of the method, but the
+            // compiler doesn't know that
+            break;
     }
 
     mColorTransform = lms2rgb *
diff --git a/services/surfaceflinger/Effects/Daltonizer.h b/services/surfaceflinger/Effects/Daltonizer.h
index e816437..d21b155 100644
--- a/services/surfaceflinger/Effects/Daltonizer.h
+++ b/services/surfaceflinger/Effects/Daltonizer.h
@@ -21,27 +21,22 @@
 
 namespace android {
 
+enum class ColorBlindnessType {
+    None,               // Disables the Daltonizer
+    Protanomaly,        // L (red) cone deficient
+    Deuteranomaly,      // M (green) cone deficient (most common)
+    Tritanomaly         // S (blue) cone deficient
+};
+
+enum class ColorBlindnessMode {
+    Simulation,
+    Correction
+};
+
 class Daltonizer {
 public:
-    enum ColorBlindnessTypes {
-        protanopia,         // L (red) cone missing
-        deuteranopia,       // M (green) cone missing
-        tritanopia,         // S (blue) cone missing
-        protanomaly,        // L (red) cone deficient
-        deuteranomaly,      // M (green) cone deficient (most common)
-        tritanomaly         // S (blue) cone deficient
-    };
-
-    enum Mode {
-        simulation,
-        correction
-    };
-
-    Daltonizer();
-    ~Daltonizer();
-
-    void setType(ColorBlindnessTypes type);
-    void setMode(Mode mode);
+    void setType(ColorBlindnessType type);
+    void setMode(ColorBlindnessMode mode);
 
     // returns the color transform to apply in the shader
     const mat4& operator()();
@@ -49,9 +44,9 @@
 private:
     void update();
 
-    ColorBlindnessTypes mType;
-    Mode mMode;
-    bool mDirty;
+    ColorBlindnessType mType = ColorBlindnessType::None;
+    ColorBlindnessMode mMode = ColorBlindnessMode::Simulation;
+    bool mDirty = true;
     mat4 mColorTransform;
 };
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 5c7db2b..f298399 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -152,7 +152,6 @@
         mPrimaryDispSync("PrimaryDispSync"),
         mPrimaryHWVsyncEnabled(false),
         mHWVsyncAvailable(false),
-        mDaltonize(false),
         mHasColorMatrix(false),
         mHasPoweredOff(false),
         mFrameBuckets(),
@@ -1224,8 +1223,7 @@
                     }
 
                     layer->setGeometry(displayDevice);
-                    if (mDebugDisableHWC || mDebugRegion || mDaltonize ||
-                            mHasColorMatrix) {
+                    if (mDebugDisableHWC || mDebugRegion) {
                         layer->forceClientComposition(hwcId);
                     }
                 }
@@ -1233,6 +1231,9 @@
         }
     }
 
+
+    mat4 colorMatrix = mColorMatrix * mDaltonizer();
+
     // Set the per-frame data
     for (size_t displayId = 0; displayId < mDisplays.size(); ++displayId) {
         auto& displayDevice = mDisplays[displayId];
@@ -1240,11 +1241,18 @@
         if (hwcId < 0) {
             continue;
         }
+        if (colorMatrix != mPreviousColorMatrix) {
+            status_t result = mHwc->setColorTransform(hwcId, colorMatrix);
+            ALOGE_IF(result != NO_ERROR, "Failed to set color transform on "
+                    "display %zd: %d", displayId, result);
+        }
         for (auto& layer : displayDevice->getVisibleLayersSortedByZ()) {
             layer->setPerFrameData(displayDevice);
         }
     }
 
+    mPreviousColorMatrix = colorMatrix;
+
     for (size_t displayId = 0; displayId < mDisplays.size(); ++displayId) {
         auto& displayDevice = mDisplays[displayId];
         if (!displayDevice->isDisplayOn()) {
@@ -1926,18 +1934,7 @@
         }
     }
 
-    if (CC_LIKELY(!mDaltonize && !mHasColorMatrix)) {
-        if (!doComposeSurfaces(hw, dirtyRegion)) return;
-    } else {
-        RenderEngine& engine(getRenderEngine());
-        mat4 colorMatrix = mColorMatrix;
-        if (mDaltonize) {
-            colorMatrix = colorMatrix * mDaltonizer();
-        }
-        mat4 oldMatrix = engine.setupColorTransform(colorMatrix);
-        doComposeSurfaces(hw, dirtyRegion);
-        engine.setupColorTransform(oldMatrix);
-    }
+    if (!doComposeSurfaces(hw, dirtyRegion)) return;
 
     // update the swap region and clear the dirty region
     hw->swapRegion.orSelf(dirtyRegion);
@@ -1952,6 +1949,15 @@
     ALOGV("doComposeSurfaces");
 
     const auto hwcId = displayDevice->getHwcDisplayId();
+
+    mat4 oldColorMatrix;
+    const bool applyColorMatrix = !mHwc->hasDeviceComposition(hwcId) &&
+            !mHwc->hasCapability(HWC2::Capability::SkipClientColorTransform);
+    if (applyColorMatrix) {
+        mat4 colorMatrix = mColorMatrix * mDaltonizer();
+        oldColorMatrix = getRenderEngine().setupColorTransform(colorMatrix);
+    }
+
     bool hasClientComposition = mHwc->hasClientComposition(hwcId);
     if (hasClientComposition) {
         ALOGV("hasClientComposition");
@@ -2069,6 +2075,10 @@
         }
     }
 
+    if (applyColorMatrix) {
+        getRenderEngine().setupColorTransform(oldColorMatrix);
+    }
+
     // disable scissor at the end of the frame
     mRenderEngine->disableScissor();
     return true;
@@ -2944,8 +2954,7 @@
     colorizer.bold(result);
     result.append("h/w composer state:\n");
     colorizer.reset(result);
-    bool hwcDisabled = mDebugDisableHWC || mDebugRegion || mDaltonize ||
-            mHasColorMatrix;
+    bool hwcDisabled = mDebugDisableHWC || mDebugRegion;
     result.appendFormat("  h/w composer %s\n",
             hwcDisabled ? "disabled" : "enabled");
     hwc.dump(result);
@@ -3100,16 +3109,24 @@
                 // daltonize
                 n = data.readInt32();
                 switch (n % 10) {
-                    case 1: mDaltonizer.setType(Daltonizer::protanomaly);   break;
-                    case 2: mDaltonizer.setType(Daltonizer::deuteranomaly); break;
-                    case 3: mDaltonizer.setType(Daltonizer::tritanomaly);   break;
+                    case 1:
+                        mDaltonizer.setType(ColorBlindnessType::Protanomaly);
+                        break;
+                    case 2:
+                        mDaltonizer.setType(ColorBlindnessType::Deuteranomaly);
+                        break;
+                    case 3:
+                        mDaltonizer.setType(ColorBlindnessType::Tritanomaly);
+                        break;
+                    default:
+                        mDaltonizer.setType(ColorBlindnessType::None);
+                        break;
                 }
                 if (n >= 10) {
-                    mDaltonizer.setMode(Daltonizer::correction);
+                    mDaltonizer.setMode(ColorBlindnessMode::Correction);
                 } else {
-                    mDaltonizer.setMode(Daltonizer::simulation);
+                    mDaltonizer.setMode(ColorBlindnessMode::Simulation);
                 }
-                mDaltonize = n > 0;
                 invalidateHwcGeometry();
                 repaintEverything();
                 return NO_ERROR;
@@ -3117,15 +3134,14 @@
             case 1015: {
                 // apply a color matrix
                 n = data.readInt32();
-                mHasColorMatrix = n ? 1 : 0;
                 if (n) {
                     // color matrix is sent as mat3 matrix followed by vec3
                     // offset, then packed into a mat4 where the last row is
                     // the offset and extra values are 0
                     for (size_t i = 0 ; i < 4; i++) {
-                      for (size_t j = 0; j < 4; j++) {
-                          mColorMatrix[i][j] = data.readFloat();
-                      }
+                        for (size_t j = 0; j < 4; j++) {
+                            mColorMatrix[i][j] = data.readFloat();
+                        }
                     }
                 } else {
                     mColorMatrix = mat4();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 0df39a4..f063aaf 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -527,8 +527,11 @@
      */
 
     Daltonizer mDaltonizer;
+#ifndef USE_HWC2
     bool mDaltonize;
+#endif
 
+    mat4 mPreviousColorMatrix;
     mat4 mColorMatrix;
     bool mHasColorMatrix;
 
diff --git a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp
index 69fb8c5..7062ea2 100644
--- a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp
+++ b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp
@@ -3080,14 +3080,20 @@
                 // daltonize
                 n = data.readInt32();
                 switch (n % 10) {
-                    case 1: mDaltonizer.setType(Daltonizer::protanomaly);   break;
-                    case 2: mDaltonizer.setType(Daltonizer::deuteranomaly); break;
-                    case 3: mDaltonizer.setType(Daltonizer::tritanomaly);   break;
+                    case 1:
+                        mDaltonizer.setType(ColorBlindnessType::Protanomaly);
+                        break;
+                    case 2:
+                        mDaltonizer.setType(ColorBlindnessType::Deuteranomaly);
+                        break;
+                    case 3:
+                        mDaltonizer.setType(ColorBlindnessType::Tritanomaly);
+                        break;
                 }
                 if (n >= 10) {
-                    mDaltonizer.setMode(Daltonizer::correction);
+                    mDaltonizer.setMode(ColorBlindnessMode::Correction);
                 } else {
-                    mDaltonizer.setMode(Daltonizer::simulation);
+                    mDaltonizer.setMode(ColorBlindnessMode::Simulation);
                 }
                 mDaltonize = n > 0;
                 invalidateHwcGeometry();