drm_hwcomposer: Set HDR metadata on the connector

Implement a function to set HDR metadata on the connector. Support HDR10
and HLG, which are common HDR types.

Change-Id: Id3dbe8eea2ee6b8ba700af23845a43e2070dd14e
Signed-off-by: Sasha McIntosh <sashamcintosh@google.com>
diff --git a/Android.bp b/Android.bp
index a8d95eb..fab7a98 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,6 +104,7 @@
 
         "utils/fd.cpp",
         "utils/properties.cpp",
+        "utils/LibdisplayEdidWrapper.cpp",
     ],
 }
 
diff --git a/drm/DrmAtomicStateManager.cpp b/drm/DrmAtomicStateManager.cpp
index 9a8769a..9ce9a93 100644
--- a/drm/DrmAtomicStateManager.cpp
+++ b/drm/DrmAtomicStateManager.cpp
@@ -150,6 +150,21 @@
       return -EINVAL;
   }
 
+  if (args.hdr_metadata && connector->GetHdrOutputMetadataProperty()) {
+    auto blob = drm->RegisterUserPropertyBlob(args.hdr_metadata.get(),
+                                              sizeof(hdr_output_metadata));
+    new_frame_state.hdr_metadata_blob = std::move(blob);
+    if (!new_frame_state.hdr_metadata_blob) {
+      ALOGE("Failed to create %s blob",
+            connector->GetHdrOutputMetadataProperty().GetName().c_str());
+      return -EINVAL;
+    }
+
+    if (!connector->GetHdrOutputMetadataProperty()
+             .AtomicSet(*pset, *new_frame_state.hdr_metadata_blob))
+      return -EINVAL;
+  }
+
   auto unused_planes = new_frame_state.used_planes;
 
   if (args.composition) {
diff --git a/drm/DrmAtomicStateManager.h b/drm/DrmAtomicStateManager.h
index 8d22b99..4af04d1 100644
--- a/drm/DrmAtomicStateManager.h
+++ b/drm/DrmAtomicStateManager.h
@@ -40,6 +40,7 @@
   std::shared_ptr<drm_color_ctm> color_matrix;
   std::optional<Colorspace> colorspace;
   std::optional<int32_t> content_type;
+  std::shared_ptr<hdr_output_metadata> hdr_metadata;
 
   std::shared_ptr<DrmFbIdHandle> writeback_fb;
   SharedFd writeback_release_fence;
@@ -84,6 +85,7 @@
 
     DrmModeUserPropertyBlobUnique mode_blob;
     DrmModeUserPropertyBlobUnique ctm_blob;
+    DrmModeUserPropertyBlobUnique hdr_metadata_blob;
 
     int release_fence_pt_index{};
 
diff --git a/drm/DrmConnector.cpp b/drm/DrmConnector.cpp
index 6be4067..37e1be4 100644
--- a/drm/DrmConnector.cpp
+++ b/drm/DrmConnector.cpp
@@ -143,6 +143,9 @@
 
   GetOptionalConnectorProperty("content type", &content_type_property_);
 
+  GetOptionalConnectorProperty("HDR_OUTPUT_METADATA",
+                               &hdr_output_metadata_property_);
+
   if (GetOptionalConnectorProperty("panel orientation", &panel_orientation_)) {
     panel_orientation_
         .AddEnumToMapReverse("Normal",
diff --git a/drm/DrmConnector.h b/drm/DrmConnector.h
index fc17206..c22d059 100644
--- a/drm/DrmConnector.h
+++ b/drm/DrmConnector.h
@@ -115,6 +115,10 @@
     return content_type_property_;
   }
 
+  auto &GetHdrOutputMetadataProperty() const {
+    return hdr_output_metadata_property_;
+  }
+
   auto &GetWritebackFbIdProperty() const {
     return writeback_fb_id_;
   }
@@ -169,6 +173,7 @@
   DrmProperty edid_property_;
   DrmProperty colorspace_property_;
   DrmProperty content_type_property_;
+  DrmProperty hdr_output_metadata_property_;
 
   DrmProperty link_status_property_;
   DrmProperty writeback_pixel_formats_;
diff --git a/hwc2_device/HwcDisplay.cpp b/hwc2_device/HwcDisplay.cpp
index a0c200e..5abbc4d 100644
--- a/hwc2_device/HwcDisplay.cpp
+++ b/hwc2_device/HwcDisplay.cpp
@@ -24,6 +24,7 @@
 #include <xf86drmMode.h>
 
 #include <hardware/gralloc.h>
+#include <ui/ColorSpace.h>
 #include <ui/GraphicBufferAllocator.h>
 #include <ui/GraphicBufferMapper.h>
 #include <ui/PixelFormat.h>
@@ -39,6 +40,7 @@
 #include "utils/properties.h"
 
 using ::android::DrmDisplayPipeline;
+using ColorGamut = ::android::ColorSpace;
 
 namespace android {
 
@@ -787,6 +789,7 @@
   args.color_matrix = color_matrix_;
   args.content_type = content_type_;
   args.colorspace = colorspace_;
+  args.hdr_metadata = hdr_metadata_;
 
   std::vector<LayerData> composition_layers;
   if (modeset_layer) {
@@ -816,6 +819,7 @@
   a_args.color_matrix = color_matrix_;
   a_args.content_type = content_type_;
   a_args.colorspace = colorspace_;
+  a_args.hdr_metadata = hdr_metadata_;
 
   uint32_t prev_vperiod_ns = 0;
   GetDisplayVsyncPeriod(&prev_vperiod_ns);
@@ -979,6 +983,18 @@
   staged_mode_change_time_ = change_time;
   staged_mode_config_id_ = config;
 
+  std::vector<ui::Hdr> hdr_types;
+  GetEdid()->GetSupportedHdrTypes(hdr_types);
+  if (hdr_types.empty()) {
+    hdr_metadata_.reset();
+    colorspace_ = Colorspace::kDefault;
+  } else {
+    auto ret = SetHdrOutputMetadata(hdr_types.front());
+    if (ret != HWC2::Error::None)
+      return ret;
+    colorspace_ = Colorspace::kBt2020Rgb;
+  }
+
   return HWC2::Error::None;
 }
 
@@ -1246,6 +1262,61 @@
                              (int32_t *)(outVsyncPeriod));
 }
 
+// Display primary values are coded as unsigned 16-bit values in units of
+// 0.00002, where 0x0000 represents zero and 0xC350 represents 1.0000.
+static uint64_t ToU16ColorValue(float in) {
+  constexpr float kPrimariesFixedPoint = 50000.F;
+  return static_cast<uint64_t>(kPrimariesFixedPoint * in);
+}
+
+HWC2::Error HwcDisplay::SetHdrOutputMetadata(ui::Hdr type) {
+  hdr_metadata_ = std::make_shared<hdr_output_metadata>();
+  hdr_metadata_->metadata_type = 0;
+  auto *m = &hdr_metadata_->hdmi_metadata_type1;
+  m->metadata_type = 0;
+
+  switch (type) {
+    case ui::Hdr::HDR10:
+      m->eotf = 2;  // PQ
+      break;
+    case ui::Hdr::HLG:
+      m->eotf = 3;  // HLG
+      break;
+    default:
+      return HWC2::Error::Unsupported;
+  }
+
+  // Most luminance values are coded as an unsigned 16-bit value in units of 1
+  // cd/m2, where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
+  std::vector<ui::Hdr> types;
+  float hdr_luminance[3]{0.F, 0.F, 0.F};
+  GetEdid()->GetHdrCapabilities(types, &hdr_luminance[0], &hdr_luminance[1],
+                                &hdr_luminance[2]);
+  m->max_display_mastering_luminance = m->max_cll = static_cast<uint64_t>(
+      hdr_luminance[0]);
+  m->max_fall = static_cast<uint64_t>(hdr_luminance[1]);
+  // The min luminance value is coded as an unsigned 16-bit value in units of
+  // 0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF
+  // represents 6.5535 cd/m2.
+  m->min_display_mastering_luminance = static_cast<uint64_t>(hdr_luminance[2] *
+                                                             10000.F);
+
+  auto gamut = ColorGamut::BT2020();
+  auto primaries = gamut.getPrimaries();
+  m->display_primaries[0].x = ToU16ColorValue(primaries[0].x);
+  m->display_primaries[0].y = ToU16ColorValue(primaries[0].y);
+  m->display_primaries[1].x = ToU16ColorValue(primaries[1].x);
+  m->display_primaries[1].y = ToU16ColorValue(primaries[1].y);
+  m->display_primaries[2].x = ToU16ColorValue(primaries[2].x);
+  m->display_primaries[2].y = ToU16ColorValue(primaries[2].y);
+
+  auto whitePoint = gamut.getWhitePoint();
+  m->white_point.x = ToU16ColorValue(whitePoint.x);
+  m->white_point.y = ToU16ColorValue(whitePoint.y);
+
+  return HWC2::Error::None;
+}
+
 #if __ANDROID_API__ > 29
 HWC2::Error HwcDisplay::GetDisplayConnectionType(uint32_t *outType) {
   if (IsInHeadlessMode()) {
diff --git a/hwc2_device/HwcDisplay.h b/hwc2_device/HwcDisplay.h
index 01ea33d..24c7465 100644
--- a/hwc2_device/HwcDisplay.h
+++ b/hwc2_device/HwcDisplay.h
@@ -284,6 +284,7 @@
   android_color_transform_t color_transform_hint_{};
   int32_t content_type_{};
   Colorspace colorspace_{};
+  std::shared_ptr<hdr_output_metadata> hdr_metadata_;
 
   std::shared_ptr<DrmKmsPlan> current_plan_;
 
@@ -297,6 +298,7 @@
   HWC2::Error Init();
 
   HWC2::Error SetActiveConfigInternal(uint32_t config, int64_t change_time);
+  HWC2::Error SetHdrOutputMetadata(ui::Hdr hdrType);
   auto GetEdid() -> EdidWrapperUnique & {
     return GetPipe().connector->Get()->GetParsedEdid();
   }
diff --git a/meson.build b/meson.build
index 97474e2..3d5c9f0 100644
--- a/meson.build
+++ b/meson.build
@@ -16,6 +16,7 @@
     'backend/Backend.cpp',
     'backend/BackendClient.cpp',
     'utils/fd.cpp',
+    'utils/LibdisplayEdidWrapper.cpp',
     'utils/properties.cpp',
 )
 
diff --git a/utils/EdidWrapper.h b/utils/EdidWrapper.h
index 3552001..867d1a0 100644
--- a/utils/EdidWrapper.h
+++ b/utils/EdidWrapper.h
@@ -24,6 +24,8 @@
 }
 #endif
 
+#include <ui/GraphicTypes.h>
+
 #include "drm/DrmUnique.h"
 #include "utils/log.h"
 
@@ -35,6 +37,16 @@
   EdidWrapper() = default;
   EdidWrapper(const EdidWrapper &) = delete;
   virtual ~EdidWrapper() = default;
+
+  virtual void GetSupportedHdrTypes(std::vector<ui::Hdr> &types) {
+    types.clear();
+  };
+  virtual void GetHdrCapabilities(std::vector<ui::Hdr> &types,
+                                  const float * /*max_luminance*/,
+                                  const float * /*max_average_luminance*/,
+                                  const float * /*min_luminance*/) {
+    GetSupportedHdrTypes(types);
+  };
 };
 
 #if HAS_LIBDISPLAY_INFO
@@ -42,26 +54,23 @@
 class LibdisplayEdidWrapper final : public EdidWrapper {
  public:
   LibdisplayEdidWrapper() = delete;
-  LibdisplayEdidWrapper(di_info *info) : info_(info) {
-  }
   ~LibdisplayEdidWrapper() override {
     di_info_destroy(info_);
   }
   static auto Create(DrmModePropertyBlobUnique blob)
-      -> std::unique_ptr<LibdisplayEdidWrapper> {
-    if (!blob)
-      return nullptr;
+      -> std::unique_ptr<LibdisplayEdidWrapper>;
 
-    auto *info = di_info_parse_edid(blob->data, blob->length);
-    if (!info) {
-      ALOGW("Failed to parse edid blob.");
-      return nullptr;
-    }
+  void GetSupportedHdrTypes(std::vector<ui::Hdr> &types) override;
 
-    return std::make_unique<LibdisplayEdidWrapper>(std::move(info));
-  }
+  void GetHdrCapabilities(std::vector<ui::Hdr> &types,
+                          const float *max_luminance,
+                          const float *max_average_luminance,
+                          const float *min_luminance) override;
 
  private:
+  LibdisplayEdidWrapper(di_info *info) : info_(std::move(info)) {
+  }
+
   di_info *info_{};
 };
 #endif
diff --git a/utils/LibdisplayEdidWrapper.cpp b/utils/LibdisplayEdidWrapper.cpp
new file mode 100644
index 0000000..5a17b93
--- /dev/null
+++ b/utils/LibdisplayEdidWrapper.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "drmhwc"
+
+#if HAS_LIBDISPLAY_INFO
+#include "utils/EdidWrapper.h"
+#include "utils/log.h"
+
+namespace android {
+
+auto LibdisplayEdidWrapper::Create(DrmModePropertyBlobUnique blob)
+    -> std::unique_ptr<LibdisplayEdidWrapper> {
+  if (!blob)
+    return nullptr;
+
+  auto *info = di_info_parse_edid(blob->data, blob->length);
+  if (!info) {
+    ALOGW("Failed to parse edid blob.");
+    return nullptr;
+  }
+
+  return std::unique_ptr<LibdisplayEdidWrapper>(
+      new LibdisplayEdidWrapper(std::move(info)));
+}
+
+void LibdisplayEdidWrapper::GetSupportedHdrTypes(std::vector<ui::Hdr> &types) {
+  types.clear();
+
+  const auto *hdr_static_meta = di_info_get_hdr_static_metadata(info_);
+  const auto *colorimetries = di_info_get_supported_signal_colorimetry(info_);
+  if (colorimetries->bt2020_cycc || colorimetries->bt2020_ycc ||
+      colorimetries->bt2020_rgb) {
+    if (hdr_static_meta->pq)
+      types.emplace_back(ui::Hdr::HDR10);
+    if (hdr_static_meta->hlg)
+      types.emplace_back(ui::Hdr::HLG);
+  }
+}
+
+void LibdisplayEdidWrapper::GetHdrCapabilities(
+    std::vector<ui::Hdr> &types, const float *max_luminance,
+    const float *max_average_luminance, const float *min_luminance) {
+  GetSupportedHdrTypes(types);
+
+  const auto *hdr_static_meta = di_info_get_hdr_static_metadata(info_);
+  max_luminance = &hdr_static_meta->desired_content_max_luminance;
+  max_average_luminance = &hdr_static_meta
+                               ->desired_content_max_frame_avg_luminance;
+  min_luminance = &hdr_static_meta->desired_content_min_luminance;
+}
+
+}  // namespace android
+#endif