Add wide-color support to SurfaceFlinger

Test: manual testing
Bug: 29940137

Change-Id: I9358b3c982e0205e598fd20cbf8d2e956591b3ac
diff --git a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
index 406e611..33ea2bc 100644
--- a/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
+++ b/services/surfaceflinger/RenderEngine/GLES20RenderEngine.cpp
@@ -14,11 +14,16 @@
  * limitations under the License.
  */
 
+//#define LOG_NDEBUG 0
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
+#include <ui/ColorSpace.h>
+#include <ui/DebugUtils.h>
 #include <ui/Rect.h>
 
 #include <utils/String8.h>
@@ -35,6 +40,71 @@
 #include "Mesh.h"
 #include "Texture.h"
 
+#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
+#include <configstore/Utils.h>
+
+#include <fstream>
+
+static constexpr bool outputDebugPPMs = false;
+
+// ---------------------------------------------------------------------------
+bool checkGlError(const char* op, int lineNumber) {
+    bool errorFound = false;
+    GLint error = glGetError();
+    while (error != GL_NO_ERROR) {
+        errorFound = true;
+        error = glGetError();
+        ALOGV("after %s() (line # %d) glError (0x%x)\n", op, lineNumber, error);
+    }
+    return errorFound;
+}
+
+void writePPM(const char* basename, GLuint width, GLuint height) {
+    ALOGV("writePPM #%s: %d x %d", basename, width, height);
+
+    std::vector<GLubyte> pixels(width * height * 4);
+    std::vector<GLubyte> outBuffer(width * height * 3);
+
+    // TODO(courtneygo): We can now have float formats, need
+    // to remove this code or update to support.
+    // Make returned pixels fit in uint32_t, one byte per component
+    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
+    if (checkGlError(__FUNCTION__, __LINE__)) {
+        return;
+    }
+
+    std::string filename(basename);
+    filename.append(".ppm");
+    std::ofstream file(filename.c_str(), std::ios::binary);
+    if (!file.is_open()) {
+        ALOGE("Unable to open file: %s", filename.c_str());
+        ALOGE("You may need to do: \"adb shell setenforce 0\" to enable "
+              "surfaceflinger to write debug images");
+        return;
+    }
+
+    file << "P6\n";
+    file << width << "\n";
+    file << height << "\n";
+    file << 255 << "\n";
+
+    auto ptr = reinterpret_cast<char*>(pixels.data());
+    auto outPtr = reinterpret_cast<char*>(outBuffer.data());
+    for (int y = height - 1; y >= 0; y--) {
+        char* data = ptr + y * width * sizeof(uint32_t);
+
+        for (GLuint x = 0; x < width; x++) {
+            // Only copy R, G and B components
+            outPtr[0] = data[0];
+            outPtr[1] = data[1];
+            outPtr[2] = data[2];
+            data += sizeof(uint32_t);
+            outPtr += 3;
+        }
+    }
+    file.write(reinterpret_cast<char*>(outBuffer.data()), outBuffer.size());
+}
+
 // ---------------------------------------------------------------------------
 namespace android {
 // ---------------------------------------------------------------------------
@@ -59,6 +129,26 @@
             GL_RGB, GL_UNSIGNED_SHORT_5_6_5, protTexData);
 
     //mColorBlindnessCorrection = M;
+
+    // retrieve wide-color and hdr settings from configstore
+    using namespace android::hardware::configstore;
+    using namespace android::hardware::configstore::V1_0;
+
+    mPlatformHasWideColor =
+            getBool<ISurfaceFlingerConfigs, &ISurfaceFlingerConfigs::hasWideColorDisplay>(false);
+    if (mPlatformHasWideColor) {
+        // Compute sRGB to DisplayP3 color transform
+        // NOTE: For now, we are limiting wide-color support to
+        // Display-P3 only.
+        mat3 srgbToP3 = ColorSpace::DisplayP3().getXYZtoRGB() * ColorSpace::sRGB().getRGBtoXYZ();
+
+        // color transform needs to be transposed and expanded to 4x4
+        // to be what the shader wants
+        // mat has an initializer that expands mat3 to mat4, but
+        // not an assignment operator
+        mat4 gamutTransform(transpose(srgbToP3));
+        mSrgbToDisplayP3 = gamutTransform;
+    }
 }
 
 GLES20RenderEngine::~GLES20RenderEngine() {
@@ -170,6 +260,42 @@
     }
 }
 
+#ifdef USE_HWC2
+void GLES20RenderEngine::setColorMode(android_color_mode mode) {
+    ALOGV("setColorMode: %s (0x%x)", decodeColorMode(mode).c_str(), mode);
+
+    if (mColorMode == mode) return;
+
+    if (!mPlatformHasWideColor || !mDisplayHasWideColor || mode == HAL_COLOR_MODE_SRGB ||
+        mode == HAL_COLOR_MODE_NATIVE) {
+        // We are returning back to our default color_mode
+        mUseWideColor = false;
+        mWideColorFrameCount = 0;
+    } else {
+        mUseWideColor = true;
+    }
+
+    mColorMode = mode;
+}
+
+void GLES20RenderEngine::setSourceDataSpace(android_dataspace source) {
+    if (source == HAL_DATASPACE_UNKNOWN) {
+        // Treat UNKNOWN as SRGB
+        source = HAL_DATASPACE_V0_SRGB;
+    }
+    mDataSpace = source;
+}
+
+void GLES20RenderEngine::setWideColor(bool hasWideColor) {
+    ALOGV("setWideColor: %s", hasWideColor ? "true" : "false");
+    mDisplayHasWideColor = hasWideColor;
+}
+
+bool GLES20RenderEngine::usesWideColor() {
+    return mUseWideColor;
+}
+#endif
+
 void GLES20RenderEngine::setupLayerTexturing(const Texture& texture) {
     GLuint target = texture.getTextureTarget();
     glBindTexture(target, texture.getTextureName());
@@ -242,8 +368,6 @@
 
 void GLES20RenderEngine::drawMesh(const Mesh& mesh) {
 
-    ProgramCache::getInstance().useProgram(mState);
-
     if (mesh.getTexCoordsSize()) {
         glEnableVertexAttribArray(Program::texCoords);
         glVertexAttribPointer(Program::texCoords,
@@ -259,7 +383,26 @@
             mesh.getByteStride(),
             mesh.getPositions());
 
-    glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
+    if (usesWideColor()) {
+        Description wideColorState = mState;
+        if (mDataSpace != HAL_DATASPACE_DISPLAY_P3) {
+            wideColorState.setColorMatrix(mState.getColorMatrix() * mSrgbToDisplayP3);
+            ALOGV("drawMesh: gamut transform applied");
+        }
+        ProgramCache::getInstance().useProgram(wideColorState);
+
+        glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
+
+        if (outputDebugPPMs) {
+            std::ostringstream out;
+            out << "/data/texture_out" << mWideColorFrameCount++;
+            writePPM(out.str().c_str(), mVpWidth, mVpHeight);
+        }
+    } else {
+        ProgramCache::getInstance().useProgram(mState);
+
+        glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
+    }
 
     if (mesh.getTexCoordsSize()) {
         glDisableVertexAttribArray(Program::texCoords);
@@ -268,6 +411,11 @@
 
 void GLES20RenderEngine::dump(String8& result) {
     RenderEngine::dump(result);
+    if (usesWideColor()) {
+        result.append("Wide-color: On");
+    } else {
+        result.append("Wide-color: Off");
+    }
 }
 
 // ---------------------------------------------------------------------------