Readback test support for per-layer dimming

Dimming is only verified if setting display brightness is supported. The
test then does:
* Sets the display brightness to max, which is the same nit level that
is found in the display xml file
* Dims a layer by 10%

Bug: 213493262
Test: builds
Change-Id: I3b552682276be26fefd7ae5586ba42af0e574e31
diff --git a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/Android.bp b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/Android.bp
index 741572d..bd2c3b1 100644
--- a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/Android.bp
+++ b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/Android.bp
@@ -50,6 +50,7 @@
         "libgui",
         "libhidlbase",
         "libprocessgroup",
+        "libtinyxml2",
         "android.hardware.graphics.mapper@2.0",
         "android.hardware.graphics.mapper@2.1",
         "android.hardware.graphics.mapper@3.0",
diff --git a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/VtsHalGraphicsComposer3_ReadbackTest.cpp b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/VtsHalGraphicsComposer3_ReadbackTest.cpp
index e519221..3e0992e 100644
--- a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/VtsHalGraphicsComposer3_ReadbackTest.cpp
+++ b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/VtsHalGraphicsComposer3_ReadbackTest.cpp
@@ -24,10 +24,18 @@
 #include <composer-vts/include/ReadbackVts.h>
 #include <composer-vts/include/RenderEngineVts.h>
 #include <gtest/gtest.h>
+#include <ui/DisplayId.h>
+#include <ui/DisplayIdentification.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/GraphicBufferAllocator.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
+
+// tinyxml2 does implicit conversions >:(
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include <tinyxml2.h>
+#pragma clang diagnostic pop
 #include "composer-vts/include/GraphicsComposerCallback.h"
 
 namespace aidl::android::hardware::graphics::composer3::vts {
@@ -124,6 +132,76 @@
                 /*layerCount*/ 1u, usage, "VtsHalGraphicsComposer3_ReadbackTest");
     }
 
+    uint64_t getStableDisplayId(int64_t display) {
+        DisplayIdentification identification;
+        const auto error = mComposerClient->getDisplayIdentificationData(display, &identification);
+        EXPECT_TRUE(error.isOk());
+
+        if (const auto info = ::android::parseDisplayIdentificationData(
+                    static_cast<uint8_t>(identification.port), identification.data)) {
+            return info->id.value;
+        }
+
+        return ::android::PhysicalDisplayId::fromPort(static_cast<uint8_t>(identification.port))
+                .value;
+    }
+
+    // Gets the per-display XML config
+    std::unique_ptr<tinyxml2::XMLDocument> getDisplayConfigXml(int64_t display) {
+        std::stringstream pathBuilder;
+        pathBuilder << "/vendor/etc/displayconfig/display_id_" << getStableDisplayId(display)
+                    << ".xml";
+        const std::string path = pathBuilder.str();
+        auto document = std::make_unique<tinyxml2::XMLDocument>();
+        const tinyxml2::XMLError error = document->LoadFile(path.c_str());
+        if (error == tinyxml2::XML_SUCCESS) {
+            return document;
+        } else {
+            return nullptr;
+        }
+    }
+
+    // Gets the max display brightness for this display.
+    // If the display config xml does not exist, then assume that the display is not well-configured
+    // enough to provide a display brightness, so return nullopt.
+    std::optional<float> getMaxDisplayBrightnessNits(int64_t display) {
+        const auto document = getDisplayConfigXml(display);
+        if (!document) {
+            // Assume the device doesn't support display brightness
+            return std::nullopt;
+        }
+
+        const auto root = document->RootElement();
+        if (!root) {
+            // If there's somehow no root element, then this isn't a valid config
+            return std::nullopt;
+        }
+
+        const auto screenBrightnessMap = root->FirstChildElement("screenBrightnessMap");
+        if (!screenBrightnessMap) {
+            // A valid display config must have a screen brightness map
+            return std::nullopt;
+        }
+
+        auto point = screenBrightnessMap->FirstChildElement("point");
+        float maxNits = -1.f;
+        while (point != nullptr) {
+            const auto nits = point->FirstChildElement("nits");
+            if (nits) {
+                maxNits = std::max(maxNits, nits->FloatText(-1.f));
+            }
+            point = point->NextSiblingElement("point");
+        }
+
+        if (maxNits < 0.f) {
+            // If we got here, then there were no point elements containing a nit value, so this
+            // config isn't valid
+            return std::nullopt;
+        }
+
+        return maxNits;
+    }
+
     void writeLayers(const std::vector<std::shared_ptr<TestLayer>>& layers) {
         for (auto layer : layers) {
             layer->write(mWriter);
@@ -876,6 +954,90 @@
     }
 }
 
+TEST_P(GraphicsCompositionTest, SetLayerWhitePointDims) {
+    std::vector<DisplayCapability> capabilities;
+    const auto error = mComposerClient->getDisplayCapabilities(mPrimaryDisplay, &capabilities);
+    ASSERT_TRUE(error.isOk());
+
+    const bool brightnessSupport = std::find(capabilities.begin(), capabilities.end(),
+                                             DisplayCapability::BRIGHTNESS) != capabilities.end();
+
+    if (!brightnessSupport) {
+        GTEST_SUCCEED() << "Cannot verify dimming behavior without brightness support";
+        return;
+    }
+
+    const std::optional<float> maxBrightnessNitsOptional =
+            getMaxDisplayBrightnessNits(mPrimaryDisplay);
+
+    ASSERT_TRUE(maxBrightnessNitsOptional.has_value());
+
+    const float maxBrightnessNits = *maxBrightnessNitsOptional;
+
+    // Preconditions to successfully run are knowing the max brightness and successfully applying
+    // the max brightness
+    ASSERT_GT(maxBrightnessNits, 0.f);
+    mWriter.setDisplayBrightness(mPrimaryDisplay, 1.f);
+    execute();
+    ASSERT_TRUE(mReader.takeErrors().empty());
+
+    for (ColorMode mode : mTestColorModes) {
+        ASSERT_NO_FATAL_FAILURE(
+                mComposerClient->setColorMode(mPrimaryDisplay, mode, RenderIntent::COLORIMETRIC));
+
+        if (!getHasReadbackBuffer()) {
+            GTEST_SUCCEED() << "Readback not supported or unsupported pixelFormat/dataspace for "
+                               "color mode: "
+                            << toString(mode);
+            continue;
+        }
+        const common::Rect redRect = {0, 0, mDisplayWidth, mDisplayHeight / 2};
+        const common::Rect dimmerRedRect = {0, mDisplayHeight / 2, mDisplayWidth, mDisplayHeight};
+        const auto redLayer = std::make_shared<TestColorLayer>(mComposerClient, mPrimaryDisplay);
+        redLayer->setColor(RED);
+        redLayer->setDisplayFrame(redRect);
+        redLayer->setWhitePointNits(maxBrightnessNits);
+
+        const auto dimmerRedLayer =
+                std::make_shared<TestColorLayer>(mComposerClient, mPrimaryDisplay);
+        dimmerRedLayer->setColor(RED);
+        dimmerRedLayer->setDisplayFrame(dimmerRedRect);
+        // Intentionally use a small dimming ratio as some implementations may be more likely to
+        // kick into GPU composition to apply dithering when the dimming ratio is high.
+        static constexpr float kDimmingRatio = 0.9f;
+        dimmerRedLayer->setWhitePointNits(maxBrightnessNits * kDimmingRatio);
+
+        const std::vector<std::shared_ptr<TestLayer>> layers = {redLayer, dimmerRedLayer};
+        std::vector<Color> expectedColors(static_cast<size_t>(mDisplayWidth * mDisplayHeight));
+
+        ReadbackHelper::fillColorsArea(expectedColors, mDisplayWidth, redRect, RED);
+        ReadbackHelper::fillColorsArea(expectedColors, mDisplayWidth, dimmerRedRect, DIM_RED);
+
+        ReadbackBuffer readbackBuffer(mPrimaryDisplay, mComposerClient, mGraphicBuffer,
+                                      mDisplayWidth, mDisplayHeight, mPixelFormat, mDataspace);
+        ASSERT_NO_FATAL_FAILURE(readbackBuffer.setReadbackBuffer());
+
+        writeLayers(layers);
+        ASSERT_TRUE(mReader.takeErrors().empty());
+        mWriter.validateDisplay(mPrimaryDisplay, ComposerClientWriter::kNoTimestamp);
+        execute();
+        if (!mReader.takeChangedCompositionTypes(mPrimaryDisplay).empty()) {
+            GTEST_SUCCEED()
+                    << "Readback verification not supported for GPU composition for color mode: "
+                    << toString(mode);
+            continue;
+        }
+        mWriter.presentDisplay(mPrimaryDisplay);
+        execute();
+        ASSERT_TRUE(mReader.takeErrors().empty());
+
+        ASSERT_NO_FATAL_FAILURE(readbackBuffer.checkReadbackBuffer(expectedColors));
+        mTestRenderEngine->setRenderLayers(layers);
+        ASSERT_NO_FATAL_FAILURE(mTestRenderEngine->drawLayers());
+        ASSERT_NO_FATAL_FAILURE(mTestRenderEngine->checkColorBuffer(expectedColors));
+    }
+}
+
 class GraphicsBlendModeCompositionTest
     : public GraphicsCompositionTestBase,
       public testing::WithParamInterface<std::tuple<std::string, std::string>> {
diff --git a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/ReadbackVts.cpp b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/ReadbackVts.cpp
index deb5ac3..fa1b08d 100644
--- a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/ReadbackVts.cpp
+++ b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/ReadbackVts.cpp
@@ -40,6 +40,7 @@
     writer.setLayerTransform(mDisplay, mLayer, mTransform);
     writer.setLayerPlaneAlpha(mDisplay, mLayer, mAlpha);
     writer.setLayerBlendMode(mDisplay, mLayer, mBlendMode);
+    writer.setLayerWhitePointNits(mDisplay, mLayer, mWhitePointNits);
 }
 
 std::string ReadbackHelper::getColorModeString(ColorMode mode) {
@@ -102,6 +103,7 @@
             1.0f, 1.0f));
 
     layerSettings.geometry.positionTransform = scale * translation;
+    layerSettings.whitePointNits = mWhitePointNits;
 
     return layerSettings;
 }
diff --git a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/include/ReadbackVts.h b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/include/ReadbackVts.h
index 0fac2b3..8d84667 100644
--- a/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/include/ReadbackVts.h
+++ b/graphics/composer/aidl/android/hardware/graphics/composer3/vts/functional/composer-vts/include/ReadbackVts.h
@@ -43,6 +43,10 @@
 
 static const Color BLACK = {0.0f, 0.0f, 0.0f, 1.0f};
 static const Color RED = {1.0f, 0.0f, 0.0f, 1.0f};
+// DIM_RED is 90% dimmed from RED in linear space
+// hard-code as value 243 in 8-bit space here, as calculating it requires
+// oetf(eotf(value) * .9), which is a complex non-linear transformation
+static const Color DIM_RED = {243.f / 255.f, 0.0f, 0.0f, 1.0f};
 static const Color TRANSLUCENT_RED = {1.0f, 0.0f, 0.0f, 0.3f};
 static const Color GREEN = {0.0f, 1.0f, 0.0f, 1.0f};
 static const Color BLUE = {0.0f, 0.0f, 1.0f, 1.0f};
@@ -67,6 +71,7 @@
     void setDisplayFrame(Rect frame) { mDisplayFrame = frame; }
     void setSourceCrop(FRect crop) { mSourceCrop = crop; }
     void setZOrder(uint32_t z) { mZOrder = z; }
+    void setWhitePointNits(float whitePointNits) { mWhitePointNits = whitePointNits; }
 
     void setSurfaceDamage(std::vector<Rect> surfaceDamage) {
         mSurfaceDamage = std::move(surfaceDamage);
@@ -84,10 +89,13 @@
 
     int64_t getLayer() const { return mLayer; }
 
+    float getWhitePointNits() const { return mWhitePointNits; }
+
   protected:
     int64_t mDisplay;
     int64_t mLayer;
     Rect mDisplayFrame = {0, 0, 0, 0};
+    float mWhitePointNits = -1.f;
     std::vector<Rect> mSurfaceDamage;
     Transform mTransform = static_cast<Transform>(0);
     FRect mSourceCrop = {0, 0, 0, 0};