OSDN Git Service

Enable wide color gamut rendering
authorRomain Guy <romainguy@google.com>
Mon, 17 Apr 2017 16:39:51 +0000 (09:39 -0700)
committerRomain Guy <romainguy@google.com>
Fri, 2 Jun 2017 18:02:13 +0000 (11:02 -0700)
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

22 files changed:
cmds/screencap/screencap.cpp
core/java/android/view/ThreadedRenderer.java
core/java/android/view/ViewRootImpl.java
core/java/android/view/WindowManager.java
core/jni/android_view_SurfaceControl.cpp
core/jni/android_view_ThreadedRenderer.cpp
libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
libs/hwui/renderthread/CanvasContext.cpp
libs/hwui/renderthread/CanvasContext.h
libs/hwui/renderthread/EglManager.cpp
libs/hwui/renderthread/EglManager.h
libs/hwui/renderthread/IRenderPipeline.h
libs/hwui/renderthread/OpenGLPipeline.cpp
libs/hwui/renderthread/OpenGLPipeline.h
libs/hwui/renderthread/RenderProxy.cpp
libs/hwui/renderthread/RenderProxy.h
packages/Shell/src/com/android/shell/Screenshooter.java
packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
services/core/java/com/android/server/display/DisplayTransformManager.java

index 5fedc9e..607e6e0 100644 (file)
 #include <ui/DisplayInfo.h>
 #include <ui/PixelFormat.h>
 
+#include <system/graphics.h>
+
 // TODO: Fix Skia.
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wunused-parameter"
 #include <SkImageEncoder.h>
 #include <SkData.h>
+#include <SkColorSpace.h>
 #pragma GCC diagnostic pop
 
 using namespace android;
 
 static uint32_t DEFAULT_DISPLAY_ID = ISurfaceComposer::eDisplayIdMain;
 
+#define COLORSPACE_UNKNOWN    0
+#define COLORSPACE_SRGB       1
+#define COLORSPACE_DISPLAY_P3 2
+
 static void usage(const char* pname)
 {
     fprintf(stderr,
@@ -67,6 +74,31 @@ static SkColorType flinger2skia(PixelFormat f)
     }
 }
 
+static sk_sp<SkColorSpace> dataSpaceToColorSpace(android_dataspace d)
+{
+    switch (d) {
+        case HAL_DATASPACE_V0_SRGB:
+            return SkColorSpace::MakeSRGB();
+        case HAL_DATASPACE_DISPLAY_P3:
+            return SkColorSpace::MakeRGB(
+                    SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kDCIP3_D65_Gamut);
+        default:
+            return nullptr;
+    }
+}
+
+static uint32_t dataSpaceToInt(android_dataspace d)
+{
+    switch (d) {
+        case HAL_DATASPACE_V0_SRGB:
+            return COLORSPACE_SRGB;
+        case HAL_DATASPACE_DISPLAY_P3:
+            return COLORSPACE_DISPLAY_P3;
+        default:
+            return COLORSPACE_UNKNOWN;
+    }
+}
+
 static status_t notifyMediaScanner(const char* fileName) {
     String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://");
     String8 fileUrl("\"");
@@ -139,6 +171,7 @@ int main(int argc, char** argv)
 
     void const* base = NULL;
     uint32_t w, s, h, f;
+    android_dataspace d;
     size_t size = 0;
 
     // Maps orientations from DisplayInfo to ISurfaceComposer
@@ -177,13 +210,15 @@ int main(int argc, char** argv)
         h = screenshot.getHeight();
         s = screenshot.getStride();
         f = screenshot.getFormat();
+        d = screenshot.getDataSpace();
         size = screenshot.getSize();
     }
 
     if (base != NULL) {
         if (png) {
             const SkImageInfo info =
-                SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType);
+                SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType,
+                    dataSpaceToColorSpace(d));
             SkPixmap pixmap(info, base, s * bytesPerPixel(f));
             struct FDWStream final : public SkWStream {
               size_t fBytesWritten = 0;
@@ -200,9 +235,11 @@ int main(int argc, char** argv)
                 notifyMediaScanner(fn);
             }
         } else {
+            uint32_t c = dataSpaceToInt(d);
             write(fd, &w, 4);
             write(fd, &h, 4);
             write(fd, &f, 4);
+            write(fd, &c, 4);
             size_t Bpp = bytesPerPixel(f);
             for (size_t y=0 ; y<h ; y++) {
                 write(fd, base, w*Bpp);
index da81efc..aacd687 100644 (file)
@@ -358,7 +358,6 @@ public final class ThreadedRenderer {
     private long mNativeProxy;
     private boolean mInitialized = false;
     private RenderNode mRootNode;
-    private Choreographer mChoreographer;
     private boolean mRootNodeNeedsUpdate;
 
     private boolean mEnabled;
@@ -425,8 +424,6 @@ public final class ThreadedRenderer {
     /**
      * Indicates whether threaded rendering is currently requested but not
      * necessarily enabled yet.
-     *
-     * @return True to request threaded rendering, false otherwise.
      */
     void setRequested(boolean requested) {
         mRequested = requested;
@@ -597,6 +594,13 @@ public final class ThreadedRenderer {
     }
 
     /**
+     * Enable/disable wide gamut rendering on this renderer.
+     */
+    void setWideGamut(boolean wideGamut) {
+        nSetWideGamut(mNativeProxy, wideGamut);
+    }
+
+    /**
      * Gets the current width of the surface. This is the width that the surface
      * was last set to in a call to {@link #setup(int, int, View.AttachInfo, Rect)}.
      *
@@ -933,11 +937,11 @@ public final class ThreadedRenderer {
             if (mInitialized) return;
             mInitialized = true;
             mAppContext = context.getApplicationContext();
-            initSched(context, renderProxy);
+            initSched(renderProxy);
             initGraphicsStats();
         }
 
-        private void initSched(Context context, long renderProxy) {
+        private void initSched(long renderProxy) {
             try {
                 int tid = nGetRenderThreadTid(renderProxy);
                 ActivityManager.getService().setRenderThread(tid);
@@ -1007,6 +1011,7 @@ public final class ThreadedRenderer {
     private static native void nSetLightCenter(long nativeProxy,
             float lightX, float lightY, float lightZ);
     private static native void nSetOpaque(long nativeProxy, boolean opaque);
+    private static native void nSetWideGamut(long nativeProxy, boolean wideGamut);
     private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
     private static native void nDestroy(long nativeProxy, long rootRenderNode);
     private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode);
index ded25ab..d57471c 100644 (file)
@@ -35,6 +35,7 @@ import android.app.ResourcesManager;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -960,8 +961,12 @@ public final class ViewRootImpl implements ViewParent,
                 final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0
                         || insets.top != 0 || insets.bottom != 0;
                 final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
+                final boolean wideGamut =
+                        attrs.getColorMode() == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT;
+
                 mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
                         attrs.getTitle().toString());
+                mAttachInfo.mThreadedRenderer.setWideGamut(wideGamut);
                 if (mAttachInfo.mThreadedRenderer != null) {
                     mAttachInfo.mHardwareAccelerated =
                             mAttachInfo.mHardwareAccelerationRequested = true;
index 3f91476..3a2c7e5 100644 (file)
@@ -2089,6 +2089,7 @@ public interface WindowManager extends ViewManager {
             out.writeInt(needsMenuKey);
             out.writeInt(accessibilityIdOfAnchor);
             TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
+            out.writeInt(mColorMode);
             out.writeLong(hideTimeoutMilliseconds);
         }
 
@@ -2143,6 +2144,7 @@ public interface WindowManager extends ViewManager {
             needsMenuKey = in.readInt();
             accessibilityIdOfAnchor = in.readInt();
             accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            mColorMode = in.readInt();
             hideTimeoutMilliseconds = in.readLong();
         }
 
@@ -2187,6 +2189,8 @@ public interface WindowManager extends ViewManager {
         /** {@hide} */
         public static final int ACCESSIBILITY_TITLE_CHANGED = 1 << 25;
         /** {@hide} */
+        public static final int COLOR_MODE_CHANGED = 1 << 26;
+        /** {@hide} */
         public static final int EVERYTHING_CHANGED = 0xffffffff;
 
         // internal buffer to backup/restore parameters under compatibility mode.
@@ -2364,6 +2368,11 @@ public interface WindowManager extends ViewManager {
                 changes |= ACCESSIBILITY_TITLE_CHANGED;
             }
 
+            if (mColorMode != o.mColorMode) {
+                mColorMode = o.mColorMode;
+                changes |= COLOR_MODE_CHANGED;
+            }
+
             // This can't change, it's only set at window creation time.
             hideTimeoutMilliseconds = o.hideTimeoutMilliseconds;
 
index 8b82314..df00d31 100644 (file)
@@ -221,11 +221,20 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz,
             return NULL;
         }
     }
+
+    sk_sp<SkColorSpace> colorSpace;
+    if (screenshot->getDataSpace() == HAL_DATASPACE_DISPLAY_P3) {
+        colorSpace = SkColorSpace::MakeRGB(
+                SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kDCIP3_D65_Gamut);
+    } else {
+        colorSpace = SkColorSpace::MakeSRGB();
+    }
+
     SkImageInfo screenshotInfo = SkImageInfo::Make(screenshot->getWidth(),
                                                    screenshot->getHeight(),
                                                    colorType,
                                                    alphaType,
-                                                   GraphicsJNI::defaultColorSpace());
+                                                   colorSpace);
 
     const size_t rowBytes =
             screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
index 4c530d7..3125753 100644 (file)
@@ -674,6 +674,12 @@ static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz,
     proxy->setOpaque(opaque);
 }
 
+static void android_view_ThreadedRenderer_setWideGamut(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jboolean wideGamut) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    proxy->setWideGamut(wideGamut);
+}
+
 static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
     LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
@@ -974,6 +980,7 @@ static const JNINativeMethod gMethods[] = {
     { "nSetup", "(JFII)V", (void*) android_view_ThreadedRenderer_setup },
     { "nSetLightCenter", "(JFFF)V", (void*) android_view_ThreadedRenderer_setLightCenter },
     { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
+    { "nSetWideGamut", "(JZ)V", (void*) android_view_ThreadedRenderer_setWideGamut },
     { "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
     { "nDestroy", "(JJ)V", (void*) android_view_ThreadedRenderer_destroy },
     { "nRegisterAnimatingRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_registerAnimatingRenderNode },
index ae13131..46cee67 100644 (file)
@@ -155,7 +155,8 @@ void SkiaOpenGLPipeline::onStop() {
     }
 }
 
-bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) {
+bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior,
+        ColorMode colorMode) {
 
     if (mEglSurface != EGL_NO_SURFACE) {
         mEglManager.destroySurface(mEglSurface);
@@ -163,7 +164,8 @@ bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior)
     }
 
     if (surface) {
-        mEglSurface = mEglManager.createSurface(surface);
+        const bool wideColorGamut = colorMode == ColorMode::WideColorGamut;
+        mEglSurface = mEglManager.createSurface(surface, wideColorGamut);
     }
 
     if (mEglSurface != EGL_NO_SURFACE) {
index 36685dd..caf9467 100644 (file)
@@ -40,7 +40,8 @@ public:
             FrameInfo* currentFrameInfo, bool* requireSwap) override;
     bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override;
     DeferredLayerUpdater* createTextureLayer() override;
-    bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior) override;
+    bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior,
+            renderthread::ColorMode colorMode) override;
     void onStop() override;
     bool isSurfaceReady() override;
     bool isContextReady() override;
index d28e605..e8e8ccd 100644 (file)
@@ -131,13 +131,15 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() {
 void SkiaVulkanPipeline::onStop() {
 }
 
-bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) {
+bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior,
+        ColorMode colorMode) {
     if (mVkSurface) {
         mVkManager.destroySurface(mVkSurface);
         mVkSurface = nullptr;
     }
 
     if (surface) {
+        // TODO: handle color mode
         mVkSurface = mVkManager.createSurface(surface);
     }
 
index aab1d7a..12935a6 100644 (file)
@@ -41,7 +41,8 @@ public:
             FrameInfo* currentFrameInfo, bool* requireSwap) override;
     bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override;
     DeferredLayerUpdater* createTextureLayer() override;
-    bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior) override;
+    bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior,
+            renderthread::ColorMode colorMode) override;
     void onStop() override;
     bool isSurfaceReady() override;
     bool isContextReady() override;
index 02a9ffa..0058e4d 100644 (file)
@@ -186,7 +186,8 @@ void CanvasContext::setSurface(Surface* surface) {
 
     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 @@ void CanvasContext::setOpaque(bool opaque) {
     mOpaque = opaque;
 }
 
+void CanvasContext::setWideGamut(bool wideGamut) {
+    mWideColorGamut = wideGamut;
+}
+
 bool CanvasContext::makeCurrent() {
     if (mStopped) return false;
 
index 33eda96..76623f9 100644 (file)
@@ -128,6 +128,7 @@ public:
             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 @@ private:
     nsecs_t mLastDropVsync = 0;
 
     bool mOpaque;
+    bool mWideColorGamut = false;
     BakedOpRenderer::LightInfo mLightInfo;
     FrameBuilder::LightGeometry mLightGeometry = { {0, 0, 0}, 0 };
 
index 44af5fd..53d42a2 100644 (file)
@@ -76,12 +76,16 @@ const char* EglManager::eglErrorString() {
 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 @@ void EglManager::initialize() {
         }
     }
 
-    loadConfig();
+    loadConfigs();
     createContext();
     createPBufferSurface();
     makeCurrent(mPBufferSurface);
@@ -143,6 +147,7 @@ void EglManager::initialize() {
 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 @@ void EglManager::initExtensions() {
     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 @@ void EglManager::loadConfig() {
             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 @@ void EglManager::createContext() {
             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 @@ void EglManager::createPBufferSurface() {
     }
 }
 
-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());
index 0251925..2982c23 100644 (file)
@@ -39,7 +39,7 @@ public:
 
     bool hasEglContext();
 
-    EGLSurface createSurface(EGLNativeWindowType window);
+    EGLSurface createSurface(EGLNativeWindowType window, bool wideColorGamut);
     void destroySurface(EGLSurface surface);
 
     void destroy();
@@ -68,7 +68,7 @@ private:
 
     void initExtensions();
     void createPBufferSurface();
-    void loadConfig();
+    void loadConfigs();
     void createContext();
     EGLint queryBufferAge(EGLSurface surface);
 
@@ -76,6 +76,7 @@ private:
 
     EGLDisplay mEglDisplay;
     EGLConfig mEglConfig;
+    EGLConfig mEglConfigWideGamut;
     EGLContext mEglContext;
     EGLSurface mPBufferSurface;
 
index 45f6718..46ac0d2 100644 (file)
@@ -44,6 +44,12 @@ enum class MakeCurrentResult {
     Succeeded
 };
 
+enum class ColorMode {
+    Srgb,
+    WideColorGamut,
+    // Hdr
+};
+
 class Frame;
 
 class IRenderPipeline {
@@ -61,7 +67,7 @@ public:
             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;
index e1ae585..9631fd6 100644 (file)
@@ -146,7 +146,7 @@ void OpenGLPipeline::onStop() {
     }
 }
 
-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 @@ bool OpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) {
     }
 
     if (surface) {
-        mEglSurface = mEglManager.createSurface(surface);
+        const bool wideColorGamut = colorMode == ColorMode::WideColorGamut;
+        mEglSurface = mEglManager.createSurface(surface, wideColorGamut);
     }
 
     if (mEglSurface != EGL_NO_SURFACE) {
index 6df8be4..c3cf803 100644 (file)
@@ -44,7 +44,7 @@ public:
             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;
index eed5238..83b3e3a 100644 (file)
@@ -227,6 +227,18 @@ void RenderProxy::setOpaque(bool opaque) {
     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();
 }
index b21772c..e1e2808 100644 (file)
@@ -69,7 +69,7 @@ namespace DumpFlags {
  */
 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 @@ public:
             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();
index 92c5fcc..8e27edf 100644 (file)
@@ -100,7 +100,7 @@ final class Screenshooter {
         // Rotate the screenshot to the current orientation
         if (rotation != ROTATION_FREEZE_0) {
             Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
-                    Bitmap.Config.ARGB_8888);
+                    Bitmap.Config.ARGB_8888, screenShot.hasAlpha(), screenShot.getColorSpace());
             Canvas canvas = new Canvas(unrotatedScreenShot);
             canvas.translate(unrotatedScreenShot.getWidth() / 2,
                     unrotatedScreenShot.getHeight() / 2);
index 2d47c7b..a7b845e 100644 (file)
@@ -578,7 +578,8 @@ class GlobalScreenshot {
         if (requiresRotation) {
             // Rotate the screenshot to the current orientation
             Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
-                    mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
+                    mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888,
+                    mScreenBitmap.hasAlpha(), mScreenBitmap.getColorSpace());
             Canvas c = new Canvas(ss);
             c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
             c.rotate(degrees);
index 6902b1a..ffee99f 100644 (file)
@@ -21,6 +21,7 @@ import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.MathUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -48,6 +49,10 @@ public class DisplayTransformManager {
      */
     public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300;
 
+    private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015;
+    private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
+    private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022;
+
     /**
      * Map of level -> color transformation matrix.
      */
@@ -66,6 +71,10 @@ public class DisplayTransformManager {
     @GuardedBy("mDaltonizerModeLock")
     private int mDaltonizerMode = -1;
 
+    private final Object mSaturationLock = new Object();
+    @GuardedBy("mSaturationLock")
+    private float mSaturation = 1.0f;
+
     /* package */ DisplayTransformManager() {
     }
 
@@ -156,6 +165,30 @@ public class DisplayTransformManager {
     }
 
     /**
+     * Returns the current saturation.
+     */
+    public float getSaturation() {
+        synchronized (mSaturationLock) {
+            return mSaturation;
+        }
+    }
+
+    /**
+     * Sets the saturation level of the display. The default value is 1.0.
+     *
+     * @param saturation A value between 0 (0% saturation, grayscale) and 2 (100% extra saturation)
+     */
+    public void setSaturation(float saturation) {
+        synchronized (mSaturationLock) {
+            saturation = MathUtils.constrain(saturation, 0.0f, 2.0f);
+            if (mSaturation != saturation) {
+                mSaturation = saturation;
+                applySaturation(saturation);
+            }
+        }
+    }
+
+    /**
      * Propagates the provided color transformation matrix to the SurfaceFlinger.
      */
     private static void applyColorMatrix(float[] m) {
@@ -172,7 +205,7 @@ public class DisplayTransformManager {
                 data.writeInt(0);
             }
             try {
-                flinger.transact(1015, data, null, 0);
+                flinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "Failed to set color transform", ex);
             } finally {
@@ -191,7 +224,7 @@ public class DisplayTransformManager {
             data.writeInterfaceToken("android.ui.ISurfaceComposer");
             data.writeInt(mode);
             try {
-                flinger.transact(1014, data, null, 0);
+                flinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "Failed to set Daltonizer mode", ex);
             } finally {
@@ -199,4 +232,23 @@ public class DisplayTransformManager {
             }
         }
     }
+
+    /**
+     * Propagates the provided saturation to the SurfaceFlinger.
+     */
+    private static void applySaturation(float saturation) {
+        final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+        if (flinger != null) {
+            final Parcel data = Parcel.obtain();
+            data.writeInterfaceToken("android.ui.ISurfaceComposer");
+            data.writeFloat(saturation);
+            try {
+                flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to set saturation", ex);
+            } finally {
+                data.recycle();
+            }
+        }
+    }
 }