Enable wide color gamut rendering

When wide color gamut rendering is requested, hwui will now
use an rgba16f scRGB-nl surface for rendering. This change
also fixes the way screenshots are handled in the platform
to behave properly with wide gamut rendering.

This change does not affect hardware layers. They also
need to use rgba16f scRGB-nl; this will be addressed in
another CL.

Bug: 29940137
Test: CtsUiRenderingTestCases, CtsGraphicsTestCases

Change-Id: I68fd96c451652136c566ec48fb0e97c2a7a257c5
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 02a9ffa..0058e4d 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -186,7 +186,8 @@
 
     mNativeSurface = surface;
 
-    bool hasSurface = mRenderPipeline->setSurface(surface, mSwapBehavior);
+    ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::Srgb;
+    bool hasSurface = mRenderPipeline->setSurface(surface, mSwapBehavior, colorMode);
 
     mFrameNumber = -1;
 
@@ -241,6 +242,10 @@
     mOpaque = opaque;
 }
 
+void CanvasContext::setWideGamut(bool wideGamut) {
+    mWideColorGamut = wideGamut;
+}
+
 bool CanvasContext::makeCurrent() {
     if (mStopped) return false;
 
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 33eda96..76623f9 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -128,6 +128,7 @@
             uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
     void setLightCenter(const Vector3& lightCenter);
     void setOpaque(bool opaque);
+    void setWideGamut(bool wideGamut);
     bool makeCurrent();
     void prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
             int64_t syncQueued, RenderNode* target);
@@ -240,6 +241,7 @@
     nsecs_t mLastDropVsync = 0;
 
     bool mOpaque;
+    bool mWideColorGamut = false;
     BakedOpRenderer::LightInfo mLightInfo;
     FrameBuilder::LightGeometry mLightGeometry = { {0, 0, 0}, 0 };
 
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 44af5fd..53d42a2 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -76,12 +76,16 @@
 static struct {
     bool bufferAge = false;
     bool setDamage = false;
+    bool noConfigContext = false;
+    bool pixelFormatFloat = false;
+    bool glColorSpace = false;
 } EglExtensions;
 
 EglManager::EglManager(RenderThread& thread)
         : mRenderThread(thread)
         , mEglDisplay(EGL_NO_DISPLAY)
         , mEglConfig(nullptr)
+        , mEglConfigWideGamut(nullptr)
         , mEglContext(EGL_NO_CONTEXT)
         , mPBufferSurface(EGL_NO_SURFACE)
         , mCurrentSurface(EGL_NO_SURFACE) {
@@ -116,7 +120,7 @@
         }
     }
 
-    loadConfig();
+    loadConfigs();
     createContext();
     createPBufferSurface();
     makeCurrent(mPBufferSurface);
@@ -143,6 +147,7 @@
 void EglManager::initExtensions() {
     auto extensions = StringUtils::split(
             eglQueryString(mEglDisplay, EGL_EXTENSIONS));
+
     // For our purposes we don't care if EGL_BUFFER_AGE is a result of
     // EGL_EXT_buffer_age or EGL_KHR_partial_update as our usage is covered
     // under EGL_KHR_partial_update and we don't need the expanded scope
@@ -152,13 +157,17 @@
     EglExtensions.setDamage = extensions.has("EGL_KHR_partial_update");
     LOG_ALWAYS_FATAL_IF(!extensions.has("EGL_KHR_swap_buffers_with_damage"),
             "Missing required extension EGL_KHR_swap_buffers_with_damage");
+
+    EglExtensions.glColorSpace = extensions.has("EGL_KHR_gl_colorspace");
+    EglExtensions.noConfigContext = extensions.has("EGL_KHR_no_config_context");
+    EglExtensions.pixelFormatFloat = extensions.has("EGL_EXT_pixel_format_float");
 }
 
 bool EglManager::hasEglContext() {
     return mEglDisplay != EGL_NO_DISPLAY;
 }
 
-void EglManager::loadConfig() {
+void EglManager::loadConfigs() {
     ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior));
     EGLint swapBehavior = (mSwapBehavior == SwapBehavior::Preserved)
             ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
@@ -175,19 +184,44 @@
             EGL_NONE
     };
 
-    EGLint num_configs = 1;
-    if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs)
-            || num_configs != 1) {
+    EGLint numConfigs = 1;
+    if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, numConfigs, &numConfigs)
+            || numConfigs != 1) {
         if (mSwapBehavior == SwapBehavior::Preserved) {
             // Try again without dirty regions enabled
             ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
             mSwapBehavior = SwapBehavior::Discard;
-            loadConfig();
+            loadConfigs();
+            return; // the call to loadConfigs() we just made picks the wide gamut config
         } else {
             // Failed to get a valid config
             LOG_ALWAYS_FATAL("Failed to choose config, error = %s", eglErrorString());
         }
     }
+
+    if (EglExtensions.pixelFormatFloat) {
+        // If we reached this point, we have a valid swap behavior
+        EGLint attribs16F[] = {
+                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT,
+                EGL_RED_SIZE, 16,
+                EGL_GREEN_SIZE, 16,
+                EGL_BLUE_SIZE, 16,
+                EGL_ALPHA_SIZE, 16,
+                EGL_DEPTH_SIZE, 0,
+                EGL_STENCIL_SIZE, Stencil::getStencilSize(),
+                EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
+                EGL_NONE
+        };
+
+        numConfigs = 1;
+        if (!eglChooseConfig(mEglDisplay, attribs16F, &mEglConfigWideGamut, numConfigs, &numConfigs)
+                || numConfigs != 1) {
+            LOG_ALWAYS_FATAL(
+                    "Device claims wide gamut support, cannot find matching config, error = %s",
+                    eglErrorString());
+        }
+    }
 }
 
 void EglManager::createContext() {
@@ -195,7 +229,9 @@
             EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
             EGL_NONE
     };
-    mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs);
+    mEglContext = eglCreateContext(mEglDisplay,
+            EglExtensions.noConfigContext ? ((EGLConfig) nullptr) : mEglConfig,
+            EGL_NO_CONTEXT, attribs);
     LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT,
         "Failed to create context, error = %s", eglErrorString());
 }
@@ -210,18 +246,60 @@
     }
 }
 
-EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
+EGLSurface EglManager::createSurface(EGLNativeWindowType window, bool wideColorGamut) {
     initialize();
 
+    wideColorGamut = wideColorGamut && EglExtensions.glColorSpace
+            && EglExtensions.pixelFormatFloat && EglExtensions.noConfigContext;
+
+    // The color space we want to use depends on whether linear blending is turned
+    // on and whether the app has requested wide color gamut rendering. When wide
+    // color gamut rendering is off, the app simply renders in the display's native
+    // color gamut.
+    //
+    // When wide gamut rendering is off:
+    // - Blending is done by default in gamma space, which requires using a
+    //   linear EGL color space (the GPU uses the color values as is)
+    // - If linear blending is on, we must use the sRGB EGL color space (the
+    //   GPU will perform sRGB to linear and linear to SRGB conversions before
+    //   and after blending)
+    //
+    // When wide gamut rendering is on we cannot rely on the GPU performing
+    // linear blending for us. We use two different color spaces to tag the
+    // surface appropriately for SurfaceFlinger:
+    // - Gamma blending (default) requires the use of the scRGB-nl color space
+    // - Linear blending requires the use of the scRGB color space
+
+    // Not all Android targets support the EGL_GL_COLOR_SPACE_KHR extension
+    // We insert to placeholders to set EGL_GL_COLORSPACE_KHR and its value.
+    // According to section 3.4.1 of the EGL specification, the attributes
+    // list is considered empty if the first entry is EGL_NONE
     EGLint attribs[] = {
-#ifdef ANDROID_ENABLE_LINEAR_BLENDING
-            EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR,
-            EGL_COLORSPACE, EGL_COLORSPACE_sRGB,
-#endif
+            EGL_NONE, EGL_NONE,
             EGL_NONE
     };
 
-    EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, attribs);
+    if (EglExtensions.glColorSpace) {
+        attribs[0] = EGL_GL_COLORSPACE_KHR;
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+        if (wideColorGamut) {
+            attribs[1] = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT;
+        } else {
+            attribs[1] = EGL_GL_COLORSPACE_SRGB_KHR;
+        }
+#else
+        if (wideColorGamut) {
+            // TODO: this should be using scRGB-nl, not scRGB, we need an extension for this
+            // TODO: in the meantime SurfaceFlinger just assumes that scRGB is scRGB-nl
+            attribs[1] = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT;
+        } else {
+            attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
+        }
+#endif
+    }
+
+    EGLSurface surface = eglCreateWindowSurface(mEglDisplay,
+            wideColorGamut ? mEglConfigWideGamut : mEglConfig, window, attribs);
     LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
             "Failed to create EGLSurface for window %p, eglErr = %s",
             (void*) window, eglErrorString());
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 0251925..2982c23 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -39,7 +39,7 @@
 
     bool hasEglContext();
 
-    EGLSurface createSurface(EGLNativeWindowType window);
+    EGLSurface createSurface(EGLNativeWindowType window, bool wideColorGamut);
     void destroySurface(EGLSurface surface);
 
     void destroy();
@@ -68,7 +68,7 @@
 
     void initExtensions();
     void createPBufferSurface();
-    void loadConfig();
+    void loadConfigs();
     void createContext();
     EGLint queryBufferAge(EGLSurface surface);
 
@@ -76,6 +76,7 @@
 
     EGLDisplay mEglDisplay;
     EGLConfig mEglConfig;
+    EGLConfig mEglConfigWideGamut;
     EGLContext mEglContext;
     EGLSurface mPBufferSurface;
 
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 45f6718..46ac0d2 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -44,6 +44,12 @@
     Succeeded
 };
 
+enum class ColorMode {
+    Srgb,
+    WideColorGamut,
+    // Hdr
+};
+
 class Frame;
 
 class IRenderPipeline {
@@ -61,7 +67,7 @@
             FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
     virtual bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) = 0;
     virtual DeferredLayerUpdater* createTextureLayer() = 0;
-    virtual bool setSurface(Surface* window, SwapBehavior swapBehavior) = 0;
+    virtual bool setSurface(Surface* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0;
     virtual void onStop() = 0;
     virtual bool isSurfaceReady() = 0;
     virtual bool isContextReady() = 0;
diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp
index e1ae585..9631fd6 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.cpp
+++ b/libs/hwui/renderthread/OpenGLPipeline.cpp
@@ -146,7 +146,7 @@
     }
 }
 
-bool OpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) {
+bool OpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, ColorMode colorMode) {
 
     if (mEglSurface != EGL_NO_SURFACE) {
         mEglManager.destroySurface(mEglSurface);
@@ -154,7 +154,8 @@
     }
 
     if (surface) {
-        mEglSurface = mEglManager.createSurface(surface);
+        const bool wideColorGamut = colorMode == ColorMode::WideColorGamut;
+        mEglSurface = mEglManager.createSurface(surface, wideColorGamut);
     }
 
     if (mEglSurface != EGL_NO_SURFACE) {
diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h
index 6df8be4..c3cf803 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.h
+++ b/libs/hwui/renderthread/OpenGLPipeline.h
@@ -44,7 +44,7 @@
             FrameInfo* currentFrameInfo, bool* requireSwap) override;
     bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override;
     DeferredLayerUpdater* createTextureLayer() override;
-    bool setSurface(Surface* window, SwapBehavior swapBehavior) override;
+    bool setSurface(Surface* window, SwapBehavior swapBehavior, ColorMode colorMode) override;
     void onStop() override;
     bool isSurfaceReady() override;
     bool isContextReady() override;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index eed5238..83b3e3a3 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -227,6 +227,18 @@
     post(task);
 }
 
+CREATE_BRIDGE2(setWideGamut, CanvasContext* context, bool wideGamut) {
+    args->context->setWideGamut(args->wideGamut);
+    return nullptr;
+}
+
+void RenderProxy::setWideGamut(bool wideGamut) {
+    SETUP_TASK(setWideGamut);
+    args->context = mContext;
+    args->wideGamut = wideGamut;
+    post(task);
+}
+
 int64_t* RenderProxy::frameInfo() {
     return mDrawFrameTask.frameInfo();
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index b21772c..e1e2808 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -69,7 +69,7 @@
  */
 class ANDROID_API RenderProxy {
 public:
-    ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode, IContextFactory* contextFactory);
+    ANDROID_API RenderProxy(bool opaque, RenderNode* rootNode, IContextFactory* contextFactory);
     ANDROID_API virtual ~RenderProxy();
 
     // Won't take effect until next EGLSurface creation
@@ -85,6 +85,7 @@
             uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
     ANDROID_API void setLightCenter(const Vector3& lightCenter);
     ANDROID_API void setOpaque(bool opaque);
+    ANDROID_API void setWideGamut(bool wideGamut);
     ANDROID_API int64_t* frameInfo();
     ANDROID_API int syncAndDrawFrame();
     ANDROID_API void destroy();