summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--Android.mk2
-rw-r--r--libhistogram/Android.mk58
-rw-r--r--libhistogram/color_sampling_tool.cpp97
-rw-r--r--libhistogram/histogram_collector.cpp556
-rw-r--r--libhistogram/histogram_collector.h70
-rw-r--r--libhistogram/ringbuffer.cpp141
-rw-r--r--libhistogram/ringbuffer.h85
-rw-r--r--libhistogram/ringbuffer_test.cpp410
-rw-r--r--libqservice/IQService.h2
-rw-r--r--sdm/libs/hwc2/Android.mk5
-rw-r--r--sdm/libs/hwc2/hwc_display.cpp25
-rw-r--r--sdm/libs/hwc2/hwc_display.h12
-rw-r--r--sdm/libs/hwc2/hwc_display_builtin.cpp54
-rw-r--r--sdm/libs/hwc2/hwc_display_builtin.h20
-rw-r--r--sdm/libs/hwc2/hwc_session.cpp66
-rw-r--r--sdm/libs/hwc2/hwc_session.h2
17 files changed, 1603 insertions, 3 deletions
diff --git a/Android.bp b/Android.bp
index 9c9bb03c..e268dbfc 100644
--- a/Android.bp
+++ b/Android.bp
@@ -38,6 +38,7 @@ cc_library_headers {
"sdm/include",
"gralloc",
"libdebug",
+ "libhistogram",
],
header_libs: ["libhardware_headers", "//vendor/qcom/sm7150:display_intf_headers"],
export_header_lib_headers: ["libhardware_headers", "//vendor/qcom/sm7150:display_intf_headers"],
diff --git a/Android.mk b/Android.mk
index e8cab371..3376591d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -4,7 +4,7 @@ display-hals := include $(sdm-libs)/utils $(sdm-libs)/core libdebug
ifneq ($(TARGET_IS_HEADLESS), true)
display-hals += libcopybit liblight libmemtrack hdmi_cec \
- $(sdm-libs)/hwc2 gpu_tonemapper libdrmutils
+ $(sdm-libs)/hwc2 gpu_tonemapper libdrmutils libhistogram drm.vendor
endif
display-hals += gralloc
diff --git a/libhistogram/Android.mk b/libhistogram/Android.mk
new file mode 100644
index 00000000..d67b2285
--- /dev/null
+++ b/libhistogram/Android.mk
@@ -0,0 +1,58 @@
+# Copyright (C) 2018 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_HEADER_LIBRARIES := display_headers
+LOCAL_MODULE := libhistogram
+LOCAL_SRC_FILES := histogram_collector.cpp ringbuffer.cpp
+LOCAL_SHARED_LIBRARIES := libdrm.vendor liblog libcutils libutils
+LOCAL_CFLAGS := -DLOG_TAG=\"SDM-histogram\" -Wall -std=c++14 -Werror -fno-operator-names
+LOCAL_CLANG := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_VENDOR_MODULE := true
+
+include $(BUILD_STATIC_LIBRARY)
+
+
+include $(CLEAR_VARS)
+
+LOCAL_HEADER_LIBRARIES := display_headers
+LOCAL_MODULE := color_sampling_tool
+LOCAL_SRC_FILES := color_sampling_tool.cpp
+
+LOCAL_STATIC_LIBRARIES := libhistogram
+LOCAL_SHARED_LIBRARIES := libdrm.vendor liblog libcutils libutils libbase
+LOCAL_CFLAGS := -DLOG_TAG=\"SDM-histogram\" -Wall -std=c++14 -Werror -fno-operator-names
+LOCAL_CLANG := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_VENDOR_MODULE := true
+
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
+LOCAL_HEADER_LIBRARIES := display_headers
+LOCAL_MODULE := color_sampling_test
+LOCAL_SRC_FILES := ringbuffer_test.cpp
+
+LOCAL_STATIC_LIBRARIES := libhistogram libgtest libgmock
+LOCAL_SHARED_LIBRARIES := libdrm.vendor liblog libcutils libutils libbase
+LOCAL_CFLAGS := -DLOG_TAG=\"SDM-histogram\" -Wall -std=c++14 -Werror -fno-operator-names
+LOCAL_CLANG := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_VENDOR_MODULE := true
+
+include $(BUILD_EXECUTABLE)
diff --git a/libhistogram/color_sampling_tool.cpp b/libhistogram/color_sampling_tool.cpp
new file mode 100644
index 00000000..ca4da65a
--- /dev/null
+++ b/libhistogram/color_sampling_tool.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <chrono>
+#include <iostream>
+#include <fstream>
+#include <signal.h>
+#include <thread>
+#include <unistd.h>
+
+#include "histogram_collector.h"
+
+void sigint_handler(int) {
+}
+
+void show_usage(char* progname) {
+ std::cout << "Usage: ./" + std::string(progname) + " {options} \n"
+ << "Sample the V (as in HSV) channel of the pixels that were displayed onscreen.\n\n"
+ << "\tOptions:\n"
+ << "\t-h display this help message\n"
+ << "\t-o write output to specified filename\n"
+ << "\t-t NUM Collect results over NUM seconds, and then exit\n"
+ << "\t-m NUM Only store the last NUM frames of statistics\n";
+}
+
+int main(int argc, char** argv) {
+ struct sigaction sigHandler;
+ sigHandler.sa_handler = sigint_handler;
+ sigemptyset(&sigHandler.sa_mask);
+ sigHandler.sa_flags = 0;
+ sigaction(SIGINT, &sigHandler, NULL);
+
+ int c;
+ char * output_filename = NULL;
+ int timeout = -1;
+ while ((c = getopt(argc, argv, "o:t:h")) != -1) {
+ switch (c) {
+ case 'o': output_filename = optarg; break;
+ case 't': timeout = strtol(optarg, NULL, 10); break;
+ default:
+ case 'h': show_usage(argv[0]); return EXIT_SUCCESS;
+ }
+ }
+
+ histogram::HistogramCollector histogram;
+ histogram.start();
+
+ bool cancelled_during_wait = false;
+ if (timeout > 0) {
+ std::cout << "Sampling for " << timeout << " seconds.\n";
+ struct timespec request, remaining;
+ request.tv_sec = timeout;
+ request.tv_nsec = 0;
+ cancelled_during_wait = (nanosleep(&request, &remaining) != 0);
+ } else {
+ std::cout << "Sampling until Ctrl-C is pressed\n";
+ sigsuspend(&sigHandler.sa_mask);
+ }
+
+ std::cout << "Sampling results:\n";
+
+ histogram.stop();
+
+ if (cancelled_during_wait) {
+ std::cout << "Timed histogram collection cancelled via signal\n";
+ return EXIT_SUCCESS;
+ }
+
+ if (output_filename) {
+ std::cout << "\nWriting statistics to: " << output_filename << '\n';
+ std::ofstream output_file;
+ output_file.open(output_filename);
+ if (!output_file.is_open()) {
+ std::cerr << "Error, could not open given file: " << output_filename << "\n";
+ return EXIT_FAILURE;
+ }
+ output_file << histogram.Dump();
+ output_file.close();
+ } else {
+ std::cout << histogram.Dump() << '\n';
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/libhistogram/histogram_collector.cpp b/libhistogram/histogram_collector.cpp
new file mode 100644
index 00000000..868aaff5
--- /dev/null
+++ b/libhistogram/histogram_collector.cpp
@@ -0,0 +1,556 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <chrono>
+#include <ctime>
+#include <iomanip>
+#include <fcntl.h>
+#include <fstream>
+#include <log/log.h>
+#include <memory>
+#include <sstream>
+#include <sys/epoll.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <tuple>
+#include <unistd.h>
+#include <unordered_map>
+#include <vector>
+
+#include <drm/msm_drm.h>
+#include <drm/msm_drm_pp.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include "histogram_collector.h"
+#include "ringbuffer.h"
+
+namespace {
+
+class ManagedFd
+{
+public:
+ static std::unique_ptr<ManagedFd> create(int fd) {
+ if (fd < 0)
+ return nullptr;
+ return std::unique_ptr<ManagedFd>(new ManagedFd(fd));
+ }
+
+ ~ManagedFd() {
+ close(drmfd_);
+ }
+
+ operator int() const {
+ return drmfd_;
+ }
+
+private:
+ ManagedFd(ManagedFd const&) = delete;
+ ManagedFd& operator=(ManagedFd const&) = delete;
+
+ ManagedFd(int fd) : drmfd_(fd) {
+ }
+ int const drmfd_ = -1;
+};
+
+class DrmResources
+{
+public:
+ static std::unique_ptr<DrmResources> create(int drm_fd) {
+ auto resources = drmModeGetResources(drm_fd);
+ if (!resources || !resources->connectors || !resources->crtcs || !resources->encoders) {
+ return nullptr;
+ }
+ return std::unique_ptr<DrmResources>(new DrmResources(drm_fd, resources));
+ }
+
+ ~DrmResources() {
+ for (auto encoder : encoders_)
+ drmModeFreeEncoder(encoder.second);
+ for (auto crtc : crtcs_ )
+ drmModeFreeCrtc(crtc.second);
+ for (auto connector : connectors_)
+ drmModeFreeConnector(connector.second);
+ drmModeFreeResources(resources_);
+ }
+
+ drmModeConnectorPtr find_first_connector_of_type(uint32_t type) {
+ auto connector = std::find_if(connectors_.begin(), connectors_.end(),
+ [type] (auto const& c) { return c.second->connector_type == type; });
+ if (connector != connectors_.end()) {
+ return connector->second;
+ }
+ return nullptr;
+ }
+
+ drmModeEncoderPtr find_encoder_by_connector_and_type(drmModeConnectorPtr con, uint32_t type) {
+ for (auto i = 0; i < con->count_encoders; i++) {
+ auto enc = encoders_.find(con->encoders[i]);
+ if (enc != encoders_.end() && (enc->second->encoder_type == type)) {
+ return enc->second;
+ }
+ }
+ return nullptr;
+ }
+
+ bool find_histogram_supporting_crtc(int fd, drmModeEncoderPtr encoder,
+ drmModeCrtcPtr* crtc, int* histogram_ctrl, int* histogram_irq) {
+
+ for (auto i = 0; i < resources_->count_crtcs; i++) {
+ if (!(encoder->possible_crtcs & (1 << i)))
+ continue;
+
+ auto it = crtcs_.find(resources_->crtcs[i]);
+ if (it == crtcs_.end()) {
+ ALOGW("Could not find CRTC %i reported as possible by encoder %i",
+ resources_->crtcs[i], encoder->encoder_id);
+ continue;
+ }
+ *crtc = it->second;
+
+ int hist_ctl_found = -1;
+ int hist_irq_found = -1;
+ auto props = drmModeObjectGetProperties(fd, (*crtc)->crtc_id, DRM_MODE_OBJECT_CRTC);
+ for (auto j = 0u; j < props->count_props; j++) {
+ auto info = drmModeGetProperty(fd, props->props[j]);
+ if (std::string(info->name) == "SDE_DSPP_HIST_CTRL_V1") {
+ hist_ctl_found = props->props[j];
+ }
+ if (std::string(info->name) == "SDE_DSPP_HIST_IRQ_V1") {
+ hist_irq_found = props->props[j];
+ }
+ drmModeFreeProperty(info);
+ }
+ drmModeFreeObjectProperties(props);
+ if ((hist_ctl_found != -1 ) && (hist_irq_found != -1)) {
+ *histogram_ctrl = hist_ctl_found;
+ *histogram_irq = hist_irq_found;
+ return true;
+ }
+ }
+ return false;
+ }
+
+private:
+ DrmResources(DrmResources const&) = delete;
+ DrmResources& operator=(DrmResources const&) = delete;
+
+ DrmResources(int drm_fd, drmModeResPtr resources) :
+ resources_(resources),
+ crtcs_(resources_->count_crtcs),
+ connectors_(resources_->count_connectors),
+ encoders_(resources_->count_encoders) {
+
+ for (auto i = 0; i < resources_->count_connectors; i++) {
+ auto connector = drmModeGetConnector(drm_fd, resources_->connectors[i]);
+ connectors_[connector->connector_id] = connector;
+ }
+
+ for (auto i = 0; i < resources_->count_crtcs; i++) {
+ auto crtc = drmModeGetCrtc(drm_fd, resources_->crtcs[i]);
+ crtcs_[crtc->crtc_id] = crtc;
+ }
+
+ for (auto i = 0; i < resources_->count_encoders; i++) {
+ auto encoder = drmModeGetEncoder(drm_fd, resources_->encoders[i]);
+ encoders_[encoder->encoder_id] = encoder;
+ }
+ }
+
+ drmModeResPtr resources_;
+ std::unordered_map<int, drmModeCrtcPtr> crtcs_;
+ std::unordered_map<int, drmModeConnectorPtr> connectors_;
+ std::unordered_map<int, drmModeEncoderPtr> encoders_;
+};
+
+// Registering DRM_EVENT_CRTC_POWER does not trigger a notification on the DRM fd.
+struct PowerEventRegistration
+{
+ static std::unique_ptr<PowerEventRegistration> create(int drm_fd, int crtc_id) {
+ auto r = std::unique_ptr<PowerEventRegistration>(new PowerEventRegistration(drm_fd, crtc_id));
+ if (drmIoctl(drm_fd, DRM_IOCTL_MSM_REGISTER_EVENT, &r->req))
+ return nullptr;
+ return r;
+ }
+
+ ~PowerEventRegistration() {
+ drmIoctl(fd, DRM_IOCTL_MSM_DEREGISTER_EVENT, &req);
+ }
+private:
+ PowerEventRegistration(PowerEventRegistration const&) = delete;
+ PowerEventRegistration operator=(PowerEventRegistration const&) = delete;
+
+ PowerEventRegistration(int drm_fd, int crtc_id) :
+ fd(drm_fd) {
+ req.object_id = crtc_id;
+ req.object_type = DRM_MODE_OBJECT_CRTC;
+ req.event = DRM_EVENT_CRTC_POWER;
+ }
+
+ int const fd; //non-owning.
+ struct drm_msm_event_req req = {};
+};
+
+struct HistogramRAIIEnabler
+{
+ static std::unique_ptr<HistogramRAIIEnabler> create(int fd, int crtc_id, int histogram_prop) {
+ auto hist = std::unique_ptr<HistogramRAIIEnabler>(
+ new HistogramRAIIEnabler(fd, crtc_id, histogram_prop));
+ if (drmModeObjectSetProperty(fd, crtc_id, DRM_MODE_OBJECT_CRTC, histogram_prop, 1))
+ return nullptr;
+ return hist;
+ }
+
+ ~HistogramRAIIEnabler() {
+ drmModeObjectSetProperty(fd, crtc_id, DRM_MODE_OBJECT_CRTC, histogram_property, 0);
+ }
+
+private:
+ HistogramRAIIEnabler(HistogramRAIIEnabler const&) = delete;
+ HistogramRAIIEnabler& operator=(HistogramRAIIEnabler const&) = delete;
+
+ HistogramRAIIEnabler(int fd, int crtc_id, int histogram_property) :
+ fd(fd),
+ crtc_id(crtc_id),
+ histogram_property(histogram_property) {
+ }
+
+ int fd;
+ int crtc_id;
+ int histogram_property;
+};
+
+struct EventRegistration
+{
+ static std::unique_ptr<EventRegistration> create(
+ int drm_fd, int crtc_id, int histogram_property) {
+ auto reg = std::unique_ptr<EventRegistration>(
+ new EventRegistration(drm_fd, crtc_id, histogram_property));
+ if (!reg->property_registration ||
+ drmIoctl(drm_fd, DRM_IOCTL_MSM_REGISTER_EVENT, &reg->req))
+ return nullptr;
+ return reg;
+ }
+
+ ~EventRegistration() {
+ drmIoctl(fd, DRM_IOCTL_MSM_DEREGISTER_EVENT, &req);
+ }
+
+private:
+ EventRegistration(int drm_fd, int crtc_id, int histogram_property) :
+ property_registration(HistogramRAIIEnabler::create(drm_fd, crtc_id, histogram_property)),
+ fd(drm_fd) {
+ req.object_id = crtc_id;
+ req.object_type = DRM_MODE_OBJECT_CRTC;
+ req.event = DRM_EVENT_HISTOGRAM;
+ }
+ EventRegistration(EventRegistration const&) = delete;
+ EventRegistration operator&(EventRegistration const&) = delete;
+
+ //SDE_DSPP_HIST_CTRL_V1 must be turned on before receiving events
+ std::unique_ptr<HistogramRAIIEnabler> property_registration;
+ int const fd; //non-owning.
+ struct drm_msm_event_req req = {};
+};
+
+//These are not the DPMS enum encodings.
+enum class CrtcPowerState
+{
+ OFF,
+ ON,
+ UNKNOWN
+};
+
+constexpr static auto implementation_defined_max_frame_ringbuffer = 300;
+}
+
+histogram::HistogramCollector::HistogramCollector() :
+ histogram(histogram::Ringbuffer::create(
+ implementation_defined_max_frame_ringbuffer, std::make_unique<histogram::DefaultTimeKeeper>())) {
+}
+
+histogram::HistogramCollector::~HistogramCollector() {
+ stop();
+}
+
+namespace {
+static constexpr size_t numBuckets = 8;
+static_assert((HIST_V_SIZE % numBuckets) == 0,
+ "histogram cannot be rebucketed to smaller number of buckets");
+static constexpr int bucket_compression = HIST_V_SIZE / numBuckets;
+
+std::array<uint64_t, numBuckets> rebucketTo8Buckets(std::array<uint64_t, HIST_V_SIZE> const& frame) {
+ std::array<uint64_t, numBuckets> bins;
+ bins.fill(0);
+ for (auto i = 0u; i < HIST_V_SIZE; i++)
+ bins[i / bucket_compression] += frame[i];
+ return bins;
+}
+}
+
+std::string histogram::HistogramCollector::Dump() const {
+ uint64_t num_frames;
+ std::array<uint64_t, HIST_V_SIZE> all_sample_buckets;
+ std::tie(num_frames, all_sample_buckets) = histogram->collect_cumulative();
+ std::array<uint64_t, numBuckets> samples = rebucketTo8Buckets(all_sample_buckets);
+
+ std::stringstream ss;
+ ss << "Color Sampling, dark (0.0) to light (1.0): sampled frames: " << num_frames << '\n';
+ if (num_frames == 0) {
+ ss << "\tno color statistics collected\n";
+ return ss.str();
+ }
+
+ ss << std::fixed << std::setprecision(3);
+ ss << "\tbucket\t\t: # of displayed pixels at bucket value\n";
+ for (auto i = 0u; i < samples.size(); i++) {
+ ss << "\t" << i / static_cast<float>(samples.size()) <<
+ " to " << ( i + 1 ) / static_cast<float>(samples.size()) << "\t: " <<
+ samples[i] << '\n';
+ }
+
+ return ss.str();
+}
+
+HWC2::Error histogram::HistogramCollector::collect(
+ uint64_t max_frames,
+ uint64_t timestamp,
+ int32_t out_samples_size[NUM_HISTOGRAM_COLOR_COMPONENTS],
+ uint64_t* out_samples[NUM_HISTOGRAM_COLOR_COMPONENTS],
+ uint64_t* out_num_frames) const {
+
+ if (!out_samples_size || !out_num_frames)
+ return HWC2::Error::BadParameter;
+
+ out_samples_size[0] = 0;
+ out_samples_size[1] = 0;
+ out_samples_size[2] = numBuckets;
+ out_samples_size[3] = 0;
+
+ uint64_t num_frames;
+ std::array<uint64_t, HIST_V_SIZE> samples;
+
+ if (max_frames == 0 && timestamp == 0) {
+ std::tie(num_frames, samples) = histogram->collect_cumulative();
+ } else if (max_frames == 0) {
+ std::tie(num_frames, samples) = histogram->collect_after(timestamp);
+ } else if (timestamp == 0) {
+ std::tie(num_frames, samples) = histogram->collect_max(max_frames);
+ } else {
+ std::tie(num_frames, samples) = histogram->collect_max_after(timestamp, max_frames);
+ }
+
+ auto samples_rebucketed = rebucketTo8Buckets(samples);
+ *out_num_frames = num_frames;
+ if (out_samples && out_samples[2])
+ memcpy(out_samples[2], samples_rebucketed.data(), sizeof(uint64_t) * samples_rebucketed.size());
+
+ return HWC2::Error::None;
+}
+
+HWC2::Error histogram::HistogramCollector::getAttributes(int32_t* format,
+ int32_t* dataspace,
+ uint8_t* supported_components) const {
+ if (!format || !dataspace || !supported_components)
+ return HWC2::Error::BadParameter;
+
+ *format = HAL_PIXEL_FORMAT_HSV_888;
+ *dataspace = HAL_DATASPACE_UNKNOWN;
+ *supported_components = HWC2_FORMAT_COMPONENT_2;
+ return HWC2::Error::None;
+}
+
+void histogram::HistogramCollector::start() {
+ start(implementation_defined_max_frame_ringbuffer);
+}
+
+void histogram::HistogramCollector::start(uint64_t max_frames) {
+ std::unique_lock<decltype(thread_control)> lk(thread_control);
+ if (started) {
+ return;
+ }
+
+ if (pipe2(selfpipe, O_CLOEXEC | O_NONBLOCK )) {
+ ALOGE("histogram thread not started, could not create control pipe.");
+ return;
+ }
+ histogram = histogram::Ringbuffer::create(max_frames, std::make_unique<histogram::DefaultTimeKeeper>());
+ monitoring_thread = std::thread(&HistogramCollector::collecting_thread, this, selfpipe[0]);
+ started = true;
+}
+
+void histogram::HistogramCollector::stop() {
+ std::unique_lock<decltype(thread_control)> lk(thread_control);
+ if (!started) {
+ return;
+ }
+
+ char dummy = 's';
+ write(selfpipe[1], &dummy, 1);
+ if (monitoring_thread.joinable())
+ monitoring_thread.join();
+ close(selfpipe[0]);
+ close(selfpipe[1]);
+ started = false;
+}
+
+void histogram::HistogramCollector::collecting_thread(int selfpipe) {
+ if (prctl(PR_SET_NAME, "histogram-collector", 0, 0, 0))
+ ALOGW("could not set thread name for histogram collector.");
+
+ int const control_minor_version { 64 };
+ auto drm = ManagedFd::create(drmOpenControl(control_minor_version));
+ if (!drm) {
+ ALOGW("could not find DRM control node. Histogram collection disabled.");
+ return;
+ }
+ auto drm_resources = DrmResources::create(*drm);
+ if (!drm_resources) {
+ ALOGW("could not get DRM resources. Histogram collection disabled.");
+ return;
+ }
+
+ //Find the connector and encoder on the DSI. Check the possible CRTCs for support
+ //for the histogram property.
+ auto connector = drm_resources->find_first_connector_of_type(DRM_MODE_CONNECTOR_DSI);
+ if (!connector) {
+ ALOGE("Could not find connector. Histogram collection disabled.");
+ return;
+ }
+
+ auto encoder = drm_resources->find_encoder_by_connector_and_type(
+ connector, DRM_MODE_ENCODER_DSI);
+ if (!encoder) {
+ ALOGE("Could not find encoder. Histogram collection disabled.");
+ return;
+ }
+
+ auto histogram_property = -1;
+ auto histogram_irq = -1;
+ drmModeCrtcPtr crtc = nullptr;
+ if (!drm_resources->find_histogram_supporting_crtc(
+ *drm, encoder, &crtc, &histogram_property, &histogram_irq)) {
+ ALOGE("Could not find CRTC that supports color sampling. Histogram collection disabled.");
+ return;
+ }
+
+ // Set up event loop.
+ // Event loop will listen to 1) FD that exposes color sampling events (1 per displayed frame),
+ // and 2) a self-pipe that will indicate when this thread should shut down.
+ enum class EventType
+ {
+ DRM,
+ CTL,
+ NUM_EVENT_TYPES
+ };
+
+ struct epoll_event ev, events[static_cast<int>(EventType::NUM_EVENT_TYPES)];
+ auto epollfd = ManagedFd::create(epoll_create1(EPOLL_CLOEXEC));
+ if (!epollfd) {
+ ALOGE("Error creating epoll loop. Histogram collection disabled.");
+ return;
+ }
+
+ ev.events = EPOLLIN;
+ ev.data.u32 = static_cast<uint32_t>(EventType::DRM);
+ if (epoll_ctl(*epollfd, EPOLL_CTL_ADD, *drm, &ev) == -1) {
+ ALOGE("Error adding drm fd to epoll. Histogram collection disabled.");
+ return;
+ }
+
+ ev.events = EPOLLIN;
+ ev.data.u32 = static_cast<uint32_t>(EventType::CTL);
+ if (epoll_ctl(*epollfd, EPOLL_CTL_ADD, selfpipe, &ev) == -1) {
+ ALOGE("Error adding control fd to epoll. Histogram collection disabled.");
+ return;
+ }
+
+ if (fcntl(*drm, F_SETFL, fcntl(*drm, F_GETFL) | O_NONBLOCK)) {
+ ALOGE("Error making drm read nonblocking. Histogram collection disabled.");
+ return;
+ }
+
+ /* Attempting to set SDE_DSPP_HIST_CTRL_V1, SDE_DSPP_HIST_IRQ_V1, or DRM_EVENT_HISTOGRAM
+ * while the screen is off will result in an error.
+ *
+ * Since we have to wait on events (or poll the connector for power state), and then issue
+ * based on that info, there's no 100% certain way to know if enabling those histogram events
+ * are done when the screen is actually on. We work around this by retrying when those
+ * events fail, and not trying to enable those when we know the screen is off.
+ */
+ std::unique_ptr<EventRegistration> hist_registration = nullptr;
+ CrtcPowerState state = CrtcPowerState::UNKNOWN;
+ bool collecting = true;
+
+ auto power_registration = PowerEventRegistration::create(*drm, crtc->crtc_id);
+ if (!power_registration) {
+ ALOGE("could not register event to monitor power events. Histogram collection disabled.");
+ return;
+ }
+
+ while (collecting) {
+ if (state != CrtcPowerState::OFF) {
+ if (!hist_registration) {
+ hist_registration = EventRegistration::create(
+ *drm, crtc->crtc_id, histogram_property);
+ }
+
+ if (drmModeObjectSetProperty(*drm,
+ crtc->crtc_id, DRM_MODE_OBJECT_CRTC, histogram_irq, 1)) {
+ ALOGI("Failed to enable histogram property on crtc, will retry");
+ state = CrtcPowerState::OFF;
+ hist_registration = nullptr;
+ }
+ }
+
+ int nfds = epoll_wait(*epollfd, events, static_cast<int>(EventType::NUM_EVENT_TYPES), -1);
+ if (nfds == -1) {
+ if (errno != EINTR)
+ collecting = false;
+ continue;
+ }
+
+ for (auto i = 0; i < nfds; i++) {
+ if (events[i].data.u32 == static_cast<uint32_t>(EventType::CTL)) {
+ collecting = false;
+ } else if (events[i].data.u32 == static_cast<uint32_t>(EventType::DRM)) {
+ //VLA has a single int as blob id, or power mode
+ char buffer[sizeof(drm_msm_event_resp) + sizeof(uint32_t)];
+ auto size_read = read(*drm, buffer, sizeof(buffer));
+ if (size_read != sizeof(buffer)) {
+ ALOGW("Histogram event wrong size (%zu bytes, errno: %X). Skipping event.",
+ size_read, errno);
+ continue;
+ }
+
+ struct drm_msm_event_resp* response =
+ reinterpret_cast<struct drm_msm_event_resp*>(buffer);
+ if (response->base.type == DRM_EVENT_HISTOGRAM) {
+ uint32_t blob_id = *reinterpret_cast<uint32_t*>(response->data);
+ drmModePropertyBlobPtr blob = drmModeGetPropertyBlob(*drm, blob_id);
+ histogram->insert(*static_cast<struct drm_msm_hist*>(blob->data));
+ drmModeFreePropertyBlob(blob);
+ }
+
+ if (response->base.type == DRM_EVENT_CRTC_POWER) {
+ uint32_t state_raw = *reinterpret_cast<uint32_t*>(response->data);
+ state = (state_raw) ? CrtcPowerState::ON : CrtcPowerState::OFF;
+ }
+ }
+ }
+ }
+}
diff --git a/libhistogram/histogram_collector.h b/libhistogram/histogram_collector.h
new file mode 100644
index 00000000..d2527ddb
--- /dev/null
+++ b/libhistogram/histogram_collector.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef HISTOGRAM_HISTOGRAM_COLLECTOR_H_
+#define HISTOGRAM_HISTOGRAM_COLLECTOR_H_
+#include <string>
+#include <thread>
+#define HWC2_INCLUDE_STRINGIFICATION
+#define HWC2_USE_CPP11
+#include <hardware/hwcomposer2.h>
+#undef HWC2_INCLUDE_STRINGIFICATION
+#undef HWC2_USE_CPP11
+
+//number of enums in hwc2_format_color_component_t;
+#define NUM_HISTOGRAM_COLOR_COMPONENTS 4
+
+namespace histogram {
+
+class Ringbuffer;
+class HistogramCollector
+{
+public:
+ HistogramCollector();
+ ~HistogramCollector();
+
+ void start();
+ void start(uint64_t max_frames);
+ void stop();
+
+ std::string Dump() const;
+
+ HWC2::Error collect(uint64_t max_frames,
+ uint64_t timestamp,
+ int32_t samples_size[NUM_HISTOGRAM_COLOR_COMPONENTS],
+ uint64_t* samples[NUM_HISTOGRAM_COLOR_COMPONENTS],
+ uint64_t* numFrames) const;
+ HWC2::Error getAttributes(int32_t* format,
+ int32_t* dataspace,
+ uint8_t* supported_components) const;
+
+private:
+ HistogramCollector(HistogramCollector const&) = delete;
+ HistogramCollector& operator=(HistogramCollector const&) = delete;
+ void collecting_thread(int pipe);
+
+ std::mutex thread_control;
+ bool started = false;
+ std::thread monitoring_thread;
+ int selfpipe[2];
+
+ std::unique_ptr<histogram::Ringbuffer> histogram;
+
+};
+
+} // namespace histogram
+
+#endif // HISTOGRAM_HISTOGRAM_COLLECTOR_H_
diff --git a/libhistogram/ringbuffer.cpp b/libhistogram/ringbuffer.cpp
new file mode 100644
index 00000000..43d8c3f8
--- /dev/null
+++ b/libhistogram/ringbuffer.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#include <cutils/compiler.h>
+#include <algorithm>
+
+#include "ringbuffer.h"
+
+nsecs_t histogram::DefaultTimeKeeper::current_time() const {
+ return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+histogram::Ringbuffer::Ringbuffer(size_t ringbuffer_size, std::unique_ptr<histogram::TimeKeeper> tk) :
+ rb_max_size(ringbuffer_size),
+ timekeeper(std::move(tk)),
+ cumulative_frame_count(0) {
+ cumulative_bins.fill(0);
+}
+
+std::unique_ptr<histogram::Ringbuffer> histogram::Ringbuffer::create(
+ size_t ringbuffer_size, std::unique_ptr<histogram::TimeKeeper> tk) {
+ if ((ringbuffer_size == 0) || !tk)
+ return nullptr;
+ return std::unique_ptr<histogram::Ringbuffer>(new histogram::Ringbuffer(ringbuffer_size, std::move(tk)));
+}
+
+void histogram::Ringbuffer::update_cumulative(nsecs_t now,
+ uint64_t& count, std::array<uint64_t, HIST_V_SIZE>& bins) const {
+
+ if (ringbuffer.empty())
+ return;
+
+ count++;
+
+ const auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::nanoseconds(now - ringbuffer.front().start_timestamp));
+
+ for (auto i = 0u; i < bins.size(); i++) {
+ auto const increment = ringbuffer.front().histogram.data[i] * delta.count();
+ if (CC_UNLIKELY((bins[i] + increment < bins[i]) || (increment < ringbuffer.front().histogram.data[i]))) {
+ bins[i] = std::numeric_limits<uint64_t>::max();
+ } else {
+ bins[i] += increment;
+ }
+ }
+}
+
+void histogram::Ringbuffer::insert(drm_msm_hist const& frame) {
+ std::unique_lock<decltype(mutex)> lk(mutex);
+ auto now = timekeeper->current_time();
+ update_cumulative(now, cumulative_frame_count, cumulative_bins);
+
+ if (ringbuffer.size() == rb_max_size)
+ ringbuffer.pop_back();
+ if (!ringbuffer.empty())
+ ringbuffer.front().end_timestamp = now;
+ ringbuffer.push_front({frame, now, 0});
+}
+
+bool histogram::Ringbuffer::resize(size_t ringbuffer_size) {
+ std::unique_lock<decltype(mutex)> lk(mutex);
+ if (ringbuffer_size == 0)
+ return false;
+ rb_max_size = ringbuffer_size;
+ if (ringbuffer.size() > rb_max_size)
+ ringbuffer.resize(rb_max_size);
+ return true;
+}
+
+histogram::Ringbuffer::Sample histogram::Ringbuffer::collect_cumulative() const {
+ std::unique_lock<decltype(mutex)> lk(mutex);
+ histogram::Ringbuffer::Sample sample { cumulative_frame_count, cumulative_bins };
+ update_cumulative(timekeeper->current_time(), std::get<0>(sample), std::get<1>(sample));
+ return sample;
+}
+
+histogram::Ringbuffer::Sample histogram::Ringbuffer::collect_ringbuffer_all() const {
+ std::unique_lock<decltype(mutex)> lk(mutex);
+ return collect_max(ringbuffer.size(), lk);
+}
+
+histogram::Ringbuffer::Sample histogram::Ringbuffer::collect_after(
+ nsecs_t timestamp) const {
+ std::unique_lock<decltype(mutex)> lk(mutex);
+ return collect_max_after(timestamp, ringbuffer.size(), lk);
+}
+
+histogram::Ringbuffer::Sample histogram::Ringbuffer::collect_max(uint32_t max_frames) const {
+ std::unique_lock<decltype(mutex)> lk(mutex);
+ return collect_max(max_frames, lk);
+}
+
+histogram::Ringbuffer::Sample histogram::Ringbuffer::collect_max_after(
+ nsecs_t timestamp, uint32_t max_frames) const {
+ std::unique_lock<decltype(mutex)> lk(mutex);
+ return collect_max_after(timestamp, max_frames, lk);
+}
+
+histogram::Ringbuffer::Sample histogram::Ringbuffer::collect_max(
+ uint32_t max_frames, std::unique_lock<std::mutex> const&) const {
+ auto collect_first = std::min(static_cast<size_t>(max_frames), ringbuffer.size());
+ if (collect_first == 0)
+ return {0, {}};
+ std::array<uint64_t, HIST_V_SIZE> bins;
+ bins.fill(0);
+ for (auto it = ringbuffer.begin(); it != ringbuffer.begin() + collect_first; it++) {
+ nsecs_t end_timestamp = it->end_timestamp;
+ if (it == ringbuffer.begin() ) {
+ end_timestamp = timekeeper->current_time();
+ }
+ const auto time_displayed = std::chrono::nanoseconds(end_timestamp - it->start_timestamp);
+ const auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(time_displayed);
+ for (auto i = 0u; i < HIST_V_SIZE; i++) {
+ bins[i] += it->histogram.data[i] * delta.count();
+ }
+ }
+ return { collect_first, bins };
+}
+
+histogram::Ringbuffer::Sample histogram::Ringbuffer::collect_max_after(
+ nsecs_t timestamp, uint32_t max_frames, std::unique_lock<std::mutex> const& lk) const {
+ auto ts_filter_begin = std::lower_bound(
+ ringbuffer.begin(), ringbuffer.end(), HistogramEntry{{}, timestamp, 0},
+ [](auto const &a, auto const &b) { return a.start_timestamp >= b.start_timestamp; });
+
+ auto collect_last = std::min(std::distance(ringbuffer.begin(), ts_filter_begin),
+ static_cast<std::ptrdiff_t>(max_frames));
+ return collect_max(collect_last, lk);
+}
diff --git a/libhistogram/ringbuffer.h b/libhistogram/ringbuffer.h
new file mode 100644
index 00000000..a1165537
--- /dev/null
+++ b/libhistogram/ringbuffer.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+#include <sys/types.h>
+#include <tuple>
+#include <unistd.h>
+#include <memory>
+#include <drm/msm_drm.h>
+#include <drm/msm_drm_pp.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <array>
+#include <deque>
+#include <mutex>
+#include <utils/Timers.h>
+
+namespace histogram {
+
+struct TimeKeeper {
+ virtual nsecs_t current_time() const = 0;
+ virtual ~TimeKeeper() = default;
+protected:
+ TimeKeeper() = default;
+ TimeKeeper& operator=(TimeKeeper const&) = delete;
+ TimeKeeper(TimeKeeper const&) = delete;
+};
+
+struct DefaultTimeKeeper final : TimeKeeper
+{
+ nsecs_t current_time() const final;
+};
+
+class Ringbuffer
+{
+public:
+ static std::unique_ptr<Ringbuffer> create(size_t ringbuffer_size, std::unique_ptr<TimeKeeper> tk);
+ void insert(drm_msm_hist const& frame);
+ bool resize(size_t ringbuffer_size);
+
+ using Sample = std::tuple<uint64_t /* numFrames */, std::array<uint64_t, HIST_V_SIZE> /* bins */>;
+ Sample collect_cumulative() const;
+ Sample collect_ringbuffer_all() const;
+ Sample collect_after(nsecs_t timestamp) const;
+ Sample collect_max(uint32_t max_frames) const;
+ Sample collect_max_after(nsecs_t timestamp, uint32_t max_frames) const;
+ ~Ringbuffer() = default;
+
+private:
+ Ringbuffer(size_t ringbuffer_size, std::unique_ptr<TimeKeeper> tk);
+ Ringbuffer(Ringbuffer const&) = delete;
+ Ringbuffer& operator=(Ringbuffer const&) = delete;
+
+ Sample collect_max(uint32_t max_frames, std::unique_lock<std::mutex> const&) const;
+ Sample collect_max_after(nsecs_t timestamp, uint32_t max_frames, std::unique_lock<std::mutex> const&) const;
+ void update_cumulative(nsecs_t now, uint64_t& count, std::array<uint64_t, HIST_V_SIZE>& bins) const;
+
+ std::mutex mutable mutex;
+ struct HistogramEntry {
+ drm_msm_hist histogram;
+ nsecs_t start_timestamp;
+ nsecs_t end_timestamp;
+ };
+ std::deque<HistogramEntry> ringbuffer;
+ size_t rb_max_size;
+ std::unique_ptr<TimeKeeper> const timekeeper;
+
+ uint64_t cumulative_frame_count;
+ std::array<uint64_t, HIST_V_SIZE> cumulative_bins;
+};
+
+}
diff --git a/libhistogram/ringbuffer_test.cpp b/libhistogram/ringbuffer_test.cpp
new file mode 100644
index 00000000..8e01aad0
--- /dev/null
+++ b/libhistogram/ringbuffer_test.cpp
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <chrono>
+#include <numeric>
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include "ringbuffer.h"
+using namespace testing;
+using namespace std::chrono_literals;
+
+template <typename Rep, typename Per>
+nsecs_t toNsecs(std::chrono::duration<Rep, Per> time) {
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(time).count();
+}
+
+template <typename Rep, typename Per>
+uint64_t toMs(std::chrono::duration<Rep, Per> time) {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(time).count();
+}
+
+struct TimeKeeperWrapper : histogram::TimeKeeper {
+ TimeKeeperWrapper(std::shared_ptr<histogram::TimeKeeper> const &tk) : tk(tk) {}
+ nsecs_t current_time() const final { return tk->current_time(); }
+ std::shared_ptr<histogram::TimeKeeper> const tk;
+};
+
+struct TickingTimeKeeper : histogram::TimeKeeper {
+ void tick() { fake_time = fake_time + toNsecs(1ms); }
+
+ void increment_by(std::chrono::nanoseconds inc) { fake_time = fake_time + inc.count(); }
+
+ nsecs_t current_time() const final { return fake_time; }
+
+private:
+ nsecs_t mutable fake_time = 0;
+};
+
+void insertFrameIncrementTimeline(histogram::Ringbuffer &rb, TickingTimeKeeper &tk,
+ drm_msm_hist &frame) {
+ rb.insert(frame);
+ tk.tick();
+}
+
+class RingbufferTestCases : public ::testing::Test {
+ void SetUp() {
+ for (auto i = 0u; i < HIST_V_SIZE; i++) {
+ frame0.data[i] = fill_frame0;
+ frame1.data[i] = fill_frame1;
+ frame2.data[i] = fill_frame2;
+ frame3.data[i] = fill_frame3;
+ frame4.data[i] = fill_frame4;
+ frame_saturate.data[i] = std::numeric_limits<uint32_t>::max();
+ }
+ }
+
+protected:
+ std::unique_ptr<histogram::Ringbuffer> createFilledRingbuffer(
+ std::shared_ptr<TickingTimeKeeper> const &tk) {
+ auto rb = histogram::Ringbuffer::create(4, std::make_unique<TimeKeeperWrapper>(tk));
+ insertFrameIncrementTimeline(*rb, *tk, frame0);
+ insertFrameIncrementTimeline(*rb, *tk, frame1);
+ insertFrameIncrementTimeline(*rb, *tk, frame2);
+ insertFrameIncrementTimeline(*rb, *tk, frame3);
+ return rb;
+ }
+
+ uint64_t fill_frame0 = 9;
+ uint64_t fill_frame1 = 11;
+ uint64_t fill_frame2 = 303;
+ uint64_t fill_frame3 = 1030;
+ uint64_t fill_frame4 = 112200;
+ drm_msm_hist frame0;
+ drm_msm_hist frame1;
+ drm_msm_hist frame2;
+ drm_msm_hist frame3;
+ drm_msm_hist frame4;
+ drm_msm_hist frame_saturate;
+
+ int numFrames = 0;
+ std::array<uint64_t, HIST_V_SIZE> bins;
+};
+
+TEST_F(RingbufferTestCases, ZeroSizedRingbufferReturnsNull) {
+ EXPECT_THAT(histogram::Ringbuffer::create(0, std::make_unique<TickingTimeKeeper>()),
+ Eq(nullptr));
+}
+
+TEST_F(RingbufferTestCases, NullTimekeeperReturnsNull) {
+ EXPECT_THAT(histogram::Ringbuffer::create(10, nullptr), Eq(nullptr));
+}
+
+TEST_F(RingbufferTestCases, CollectionWithNoFrames) {
+ auto rb = histogram::Ringbuffer::create(1, std::make_unique<TickingTimeKeeper>());
+
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(0));
+ EXPECT_THAT(bins, Each(0));
+}
+
+TEST_F(RingbufferTestCases, SimpleTest) {
+ static constexpr int numInsertions = 3u;
+ auto tk = std::make_shared<TickingTimeKeeper>();
+ auto rb = histogram::Ringbuffer::create(numInsertions, std::make_unique<TimeKeeperWrapper>(tk));
+
+ drm_msm_hist frame;
+ for (auto i = 0u; i < HIST_V_SIZE; i++) {
+ frame.data[i] = i;
+ }
+
+ insertFrameIncrementTimeline(*rb, *tk, frame);
+ insertFrameIncrementTimeline(*rb, *tk, frame);
+ insertFrameIncrementTimeline(*rb, *tk, frame);
+
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+
+ ASSERT_THAT(bins.size(), Eq(HIST_V_SIZE));
+ for (auto i = 0u; i < bins.size(); i++) {
+ EXPECT_THAT(bins[i], Eq(toMs(3ms) * i));
+ }
+}
+
+TEST_F(RingbufferTestCases, TestEvictionSingle) {
+ int fill_frame0 = 9;
+ int fill_frame1 = 111;
+ drm_msm_hist frame0;
+ drm_msm_hist frame1;
+ for (auto i = 0u; i < HIST_V_SIZE; i++) {
+ frame0.data[i] = fill_frame0;
+ frame1.data[i] = fill_frame1;
+ }
+
+ auto tk = std::make_shared<TickingTimeKeeper>();
+ auto rb = histogram::Ringbuffer::create(1, std::make_unique<TimeKeeperWrapper>(tk));
+
+ insertFrameIncrementTimeline(*rb, *tk, frame0);
+
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(1));
+ EXPECT_THAT(bins, Each(fill_frame0));
+
+ insertFrameIncrementTimeline(*rb, *tk, frame1);
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(1));
+ EXPECT_THAT(bins, Each(fill_frame1));
+}
+
+TEST_F(RingbufferTestCases, TestEvictionMultiple) {
+ auto tk = std::make_shared<TickingTimeKeeper>();
+ auto rb = histogram::Ringbuffer::create(3, std::make_unique<TimeKeeperWrapper>(tk));
+
+ insertFrameIncrementTimeline(*rb, *tk, frame0);
+ insertFrameIncrementTimeline(*rb, *tk, frame1);
+ insertFrameIncrementTimeline(*rb, *tk, frame2);
+
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(3));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2));
+
+ insertFrameIncrementTimeline(*rb, *tk, frame3);
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(3));
+ EXPECT_THAT(bins, Each(fill_frame1 + fill_frame2 + fill_frame3));
+
+ insertFrameIncrementTimeline(*rb, *tk, frame0);
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(3));
+ EXPECT_THAT(bins, Each(fill_frame2 + fill_frame3 + fill_frame0));
+}
+
+TEST_F(RingbufferTestCases, TestResizeToZero) {
+ auto rb = histogram::Ringbuffer::create(4, std::make_unique<TickingTimeKeeper>());
+ EXPECT_FALSE(rb->resize(0));
+}
+
+TEST_F(RingbufferTestCases, TestResizeDown) {
+ auto tk = std::make_shared<TickingTimeKeeper>();
+ auto rb = createFilledRingbuffer(tk);
+
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(4));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2 + fill_frame3));
+
+ auto rc = rb->resize(2);
+ EXPECT_THAT(rc, Eq(true));
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(2));
+ EXPECT_THAT(bins, Each(fill_frame2 + fill_frame3));
+
+ insertFrameIncrementTimeline(*rb, *tk, frame0);
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(2));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame3));
+}
+
+TEST_F(RingbufferTestCases, TestResizeUp) {
+ auto tk = std::make_shared<TickingTimeKeeper>();
+ auto rb = histogram::Ringbuffer::create(2, std::make_unique<TimeKeeperWrapper>(tk));
+
+ insertFrameIncrementTimeline(*rb, *tk, frame0);
+ insertFrameIncrementTimeline(*rb, *tk, frame1);
+
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(2));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1));
+
+ auto rc = rb->resize(3);
+ EXPECT_THAT(rc, Eq(true));
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(2));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1));
+
+ insertFrameIncrementTimeline(*rb, *tk, frame2);
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(3));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2));
+
+ insertFrameIncrementTimeline(*rb, *tk, frame3);
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(3));
+ EXPECT_THAT(bins, Each(fill_frame1 + fill_frame2 + fill_frame3));
+}
+
+TEST_F(RingbufferTestCases, TestTimestampFiltering) {
+ auto rb = createFilledRingbuffer(std::make_shared<TickingTimeKeeper>());
+
+ std::tie(numFrames, bins) = rb->collect_after(toNsecs(1500us));
+ EXPECT_THAT(numFrames, Eq(2));
+ EXPECT_THAT(bins, Each(fill_frame2 + fill_frame3));
+
+ std::tie(numFrames, bins) = rb->collect_after(toNsecs(45000us));
+ EXPECT_THAT(numFrames, Eq(0));
+
+ std::tie(numFrames, bins) = rb->collect_after(0);
+ EXPECT_THAT(numFrames, Eq(4));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2 + fill_frame3));
+}
+
+TEST_F(RingbufferTestCases, TestTimestampFilteringSameTimestamp) {
+ auto tk = std::make_shared<TickingTimeKeeper>();
+ auto rb = histogram::Ringbuffer::create(4, std::make_unique<TimeKeeperWrapper>(tk));
+ insertFrameIncrementTimeline(*rb, *tk, frame0);
+ insertFrameIncrementTimeline(*rb, *tk, frame1);
+ insertFrameIncrementTimeline(*rb, *tk, frame2);
+ rb->insert(frame3);
+ rb->insert(frame4);
+ tk->tick();
+
+ std::tie(numFrames, bins) = rb->collect_after(toNsecs(3ms));
+ EXPECT_THAT(numFrames, Eq(2));
+ EXPECT_THAT(bins, Each(fill_frame4));
+}
+
+TEST_F(RingbufferTestCases, TestFrameFiltering) {
+ auto rb = createFilledRingbuffer(std::make_shared<TickingTimeKeeper>());
+
+ std::tie(numFrames, bins) = rb->collect_max(2);
+ EXPECT_THAT(numFrames, Eq(2));
+ EXPECT_THAT(bins, Each(fill_frame2 + fill_frame3));
+
+ std::tie(numFrames, bins) = rb->collect_max(0);
+ EXPECT_THAT(numFrames, Eq(0));
+ EXPECT_THAT(bins, Each(0));
+
+ std::tie(numFrames, bins) = rb->collect_max(3);
+ EXPECT_THAT(numFrames, Eq(3));
+ EXPECT_THAT(bins, Each(fill_frame1 + fill_frame2 + fill_frame3));
+
+ std::tie(numFrames, bins) = rb->collect_max(8);
+ EXPECT_THAT(numFrames, Eq(4));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2 + fill_frame3));
+}
+
+TEST_F(RingbufferTestCases, TestTimestampAndFrameFiltering) {
+ auto rb = createFilledRingbuffer(std::make_shared<TickingTimeKeeper>());
+
+ std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(1500us), 1);
+ EXPECT_THAT(numFrames, Eq(1));
+ EXPECT_THAT(bins, Each(fill_frame3));
+
+ std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(2500us), 0);
+ EXPECT_THAT(numFrames, Eq(0));
+ EXPECT_THAT(bins, Each(0));
+
+ std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(10ms), 100);
+ EXPECT_THAT(numFrames, Eq(0));
+ EXPECT_THAT(bins, Each(0));
+
+ std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(0ns), 10);
+ EXPECT_THAT(numFrames, Eq(4));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2 + fill_frame3));
+}
+
+TEST_F(RingbufferTestCases, TestTimestampAndFrameFilteringAndResize) {
+ auto rb = createFilledRingbuffer(std::make_shared<TickingTimeKeeper>());
+
+ std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(500us), 1);
+ EXPECT_THAT(numFrames, Eq(1));
+ EXPECT_THAT(bins, Each(fill_frame3));
+
+ std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(500us), 10);
+ EXPECT_THAT(numFrames, Eq(3));
+ EXPECT_THAT(bins, Each(fill_frame1 + fill_frame2 + fill_frame3));
+
+ rb->resize(2);
+ std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(500us), 10);
+ EXPECT_THAT(numFrames, Eq(2));
+ EXPECT_THAT(bins, Each(fill_frame2 + fill_frame3));
+}
+
+TEST_F(RingbufferTestCases, TestCumulativeCounts) {
+ auto tk = std::make_shared<TickingTimeKeeper>();
+ auto rb = histogram::Ringbuffer::create(1, std::make_unique<TimeKeeperWrapper>(tk));
+ insertFrameIncrementTimeline(*rb, *tk, frame0);
+
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(1));
+ EXPECT_THAT(bins, Each(fill_frame0));
+
+ insertFrameIncrementTimeline(*rb, *tk, frame1);
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+ EXPECT_THAT(numFrames, Eq(1));
+ EXPECT_THAT(bins, Each(fill_frame1));
+
+ std::tie(numFrames, bins) = rb->collect_cumulative();
+ EXPECT_THAT(numFrames, Eq(2));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1));
+ rb->insert(frame2);
+ auto weight0 = std::chrono::duration_cast<std::chrono::nanoseconds>(1h);
+ tk->increment_by(weight0);
+
+ std::tie(numFrames, bins) = rb->collect_cumulative();
+ EXPECT_THAT(numFrames, Eq(3));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + (fill_frame2 *
+ std::chrono::duration_cast<std::chrono::milliseconds>(weight0).count())));
+
+ auto weight1 = std::chrono::duration_cast<std::chrono::nanoseconds>(2min);
+ tk->increment_by(weight1);
+ std::tie(numFrames, bins) = rb->collect_cumulative();
+ EXPECT_THAT(numFrames, Eq(3));
+ EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + (fill_frame2 *
+ std::chrono::duration_cast<std::chrono::milliseconds>(weight0 + weight1).count())));
+}
+
+TEST_F(RingbufferTestCases, TestCumulativeCountsEmpty) {
+ auto tk = std::make_shared<TickingTimeKeeper>();
+ auto rb = histogram::Ringbuffer::create(1, std::make_unique<TimeKeeperWrapper>(tk));
+ std::tie(numFrames, bins) = rb->collect_cumulative();
+ EXPECT_THAT(numFrames, Eq(0));
+}
+
+TEST_F(RingbufferTestCases, TestCumulativeCountsSaturate) {
+ auto tk = std::make_shared<TickingTimeKeeper>();
+ auto rb = histogram::Ringbuffer::create(1, std::make_unique<TimeKeeperWrapper>(tk));
+ insertFrameIncrementTimeline(*rb, *tk, frame_saturate);
+ auto eon = std::chrono::nanoseconds(std::numeric_limits<uint64_t>::max());
+ tk->increment_by(eon);
+ std::tie(numFrames, bins) = rb->collect_cumulative();
+ EXPECT_THAT(numFrames, Eq(1));
+ EXPECT_THAT(bins, Each(std::numeric_limits<uint64_t>::max()));
+}
+
+TEST_F(RingbufferTestCases, TimeWeightingTest) {
+ static constexpr int numInsertions = 4u;
+ auto tk = std::make_shared<TickingTimeKeeper>();
+ auto rb = histogram::Ringbuffer::create(numInsertions, std::make_unique<TimeKeeperWrapper>(tk));
+
+ auto weight0 = std::chrono::duration_cast<std::chrono::nanoseconds>(1ms);
+ auto weight1 = std::chrono::duration_cast<std::chrono::nanoseconds>(1h);
+ auto weight2 = std::chrono::duration_cast<std::chrono::nanoseconds>(1s);
+ using gigasecond = std::chrono::duration<uint64_t, std::giga>;
+ auto weight3 = std::chrono::duration_cast<std::chrono::nanoseconds>(gigasecond(4));
+
+ rb->insert(frame0);
+ tk->increment_by(weight0);
+ rb->insert(frame1);
+ tk->increment_by(weight1);
+ rb->insert(frame2);
+ tk->increment_by(weight2);
+ rb->insert(frame3);
+ tk->increment_by(weight3);
+
+ std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
+
+ ASSERT_THAT(bins.size(), Eq(HIST_V_SIZE));
+ uint64_t expected_weight = fill_frame0 * toMs(weight0) + fill_frame1 * toMs(weight1) +
+ fill_frame2 * toMs(weight2) + fill_frame3 * toMs(weight3);
+ for (auto i = 0u; i < bins.size(); i++) {
+ EXPECT_THAT(bins[i], Eq(expected_weight));
+ }
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/libqservice/IQService.h b/libqservice/IQService.h
index 1236ed5c..d59d059f 100644
--- a/libqservice/IQService.h
+++ b/libqservice/IQService.h
@@ -86,6 +86,8 @@ public:
GET_SUPPORTED_DSI_CLK = 44, // Get supported DSI Clk.
SET_COLOR_MODE_FROM_CLIENT = 45, // Overrides the QDCM mode using the given mode ID
SET_PANEL_LUMINANCE = 46, // Set Panel Luminance attributes.
+
+ SET_COLOR_SAMPLING_ENABLED = 101, // Toggle the collection of display color stats
COMMAND_LIST_END = 400,
};
diff --git a/sdm/libs/hwc2/Android.mk b/sdm/libs/hwc2/Android.mk
index 042b9cd9..3a7c9bdd 100644
--- a/sdm/libs/hwc2/Android.mk
+++ b/sdm/libs/hwc2/Android.mk
@@ -39,7 +39,10 @@ LOCAL_SHARED_LIBRARIES := libsdmcore libqservice libbinder libhardware li
vendor.display.config@1.8 \
vendor.display.config@1.9 \
vendor.display.config@1.10 \
- vendor.display.config@1.11
+ vendor.display.config@1.11 \
+ libdrm.vendor
+
+LOCAL_STATIC_LIBRARIES := libhistogram
ifeq ($(TARGET_BOARD_AUTO), true)
LOCAL_CFLAGS += -DCONFIG_BASEID_FROM_PROP
diff --git a/sdm/libs/hwc2/hwc_display.cpp b/sdm/libs/hwc2/hwc_display.cpp
index beeb61fc..67f9fafe 100644
--- a/sdm/libs/hwc2/hwc_display.cpp
+++ b/sdm/libs/hwc2/hwc_display.cpp
@@ -2314,6 +2314,31 @@ bool HWCDisplay::IsDisplayCommandMode() {
return is_cmd_mode_;
}
+HWC2::Error HWCDisplay::SetDisplayedContentSamplingEnabledVndService(bool enabled) {
+ return HWC2::Error::Unsupported;
+}
+
+HWC2::Error HWCDisplay::SetDisplayedContentSamplingEnabled(int32_t enabled,
+ uint8_t component_mask, uint64_t max_frames) {
+
+ DLOGV("Request to start/stop histogram thread not supported on this display");
+ return HWC2::Error::Unsupported;
+}
+
+HWC2::Error HWCDisplay::GetDisplayedContentSamplingAttributes(int32_t* format,
+ int32_t* dataspace,
+ uint8_t* supported_components) {
+ return HWC2::Error::Unsupported;
+}
+
+HWC2::Error HWCDisplay::GetDisplayedContentSample(uint64_t max_frames,
+ uint64_t timestamp,
+ uint64_t* numFrames,
+ int32_t samples_size[NUM_HISTOGRAM_COLOR_COMPONENTS],
+ uint64_t* samples[NUM_HISTOGRAM_COLOR_COMPONENTS]) {
+ return HWC2::Error::Unsupported;
+}
+
// Skip SDM prepare if all the layers in the current draw cycle are marked as Skip and
// previous draw cycle had GPU Composition, as the resources for GPU Target layer have
// already been validated and configured to the driver.
diff --git a/sdm/libs/hwc2/hwc_display.h b/sdm/libs/hwc2/hwc_display.h
index 84c0eef6..71eb5a45 100644
--- a/sdm/libs/hwc2/hwc_display.h
+++ b/sdm/libs/hwc2/hwc_display.h
@@ -40,6 +40,7 @@
#include "hwc_layers.h"
#include "display_null.h"
#include "hwc_display_event_handler.h"
+#include "histogram_collector.h"
using android::hardware::graphics::common::V1_1::ColorMode;
using android::hardware::graphics::common::V1_1::Dataspace;
@@ -341,6 +342,17 @@ class HWCDisplay : public DisplayEventHandler {
virtual void PostPowerMode();
virtual void NotifyClientStatus(bool connected) { client_connected_ = connected; }
+ virtual HWC2::Error SetDisplayedContentSamplingEnabledVndService(bool enabled);
+ virtual HWC2::Error SetDisplayedContentSamplingEnabled(int32_t enabled, uint8_t component_mask, uint64_t max_frames);
+ virtual HWC2::Error GetDisplayedContentSamplingAttributes(int32_t* format,
+ int32_t* dataspace,
+ uint8_t* supported_components);
+ virtual HWC2::Error GetDisplayedContentSample(uint64_t max_frames,
+ uint64_t timestamp,
+ uint64_t* numFrames,
+ int32_t samples_size[NUM_HISTOGRAM_COLOR_COMPONENTS],
+ uint64_t* samples[NUM_HISTOGRAM_COLOR_COMPONENTS]);
+
protected:
static uint32_t throttling_refresh_rate_;
// Maximum number of layers supported by display manager.
diff --git a/sdm/libs/hwc2/hwc_display_builtin.cpp b/sdm/libs/hwc2/hwc_display_builtin.cpp
index cadac358..ef8ebb50 100644
--- a/sdm/libs/hwc2/hwc_display_builtin.cpp
+++ b/sdm/libs/hwc2/hwc_display_builtin.cpp
@@ -179,6 +179,7 @@ int HWCDisplayBuiltIn::Init() {
}
int HWCDisplayBuiltIn::Deinit() {
+ histogram.stop();
int status = HWCDisplay::Deinit();
if (status) {
return status;
@@ -189,6 +190,10 @@ int HWCDisplayBuiltIn::Deinit() {
return 0;
}
+std::string HWCDisplayBuiltIn::Dump() {
+ return HWCDisplay::Dump() + histogram.Dump();
+}
+
HWC2::Error HWCDisplayBuiltIn::Validate(uint32_t *out_num_types, uint32_t *out_num_requests) {
auto status = HWC2::Error::None;
DisplayError error = kErrorNone;
@@ -877,6 +882,55 @@ DisplayError HWCDisplayBuiltIn::DisablePartialUpdateOneFrame() {
return error;
}
+HWC2::Error HWCDisplayBuiltIn::SetDisplayedContentSamplingEnabledVndService(bool enabled) {
+ std::unique_lock<decltype(sampling_mutex)> lk(sampling_mutex);
+ vndservice_sampling_vote = enabled;
+ if (api_sampling_vote || vndservice_sampling_vote) {
+ histogram.start();
+ } else {
+ histogram.stop();
+ }
+ return HWC2::Error::None;
+}
+
+HWC2::Error HWCDisplayBuiltIn::SetDisplayedContentSamplingEnabled(int32_t enabled, uint8_t component_mask, uint64_t max_frames) {
+ if ((enabled != HWC2_DISPLAYED_CONTENT_SAMPLING_ENABLE) &&
+ (enabled != HWC2_DISPLAYED_CONTENT_SAMPLING_DISABLE))
+ return HWC2::Error::BadParameter;
+
+ std::unique_lock<decltype(sampling_mutex)> lk(sampling_mutex);
+ if (enabled == HWC2_DISPLAYED_CONTENT_SAMPLING_ENABLE) {
+ api_sampling_vote = true;
+ } else {
+ api_sampling_vote = false;
+ }
+
+ auto start = api_sampling_vote || vndservice_sampling_vote;
+ if (start && max_frames == 0) {
+ histogram.start();
+ } else if (start) {
+ histogram.start(max_frames);
+ } else {
+ histogram.stop();
+ }
+ return HWC2::Error::None;
+}
+
+HWC2::Error HWCDisplayBuiltIn::GetDisplayedContentSamplingAttributes(int32_t* format,
+ int32_t* dataspace,
+ uint8_t* supported_components) {
+ return histogram.getAttributes(format, dataspace, supported_components);
+}
+
+HWC2::Error HWCDisplayBuiltIn::GetDisplayedContentSample(uint64_t max_frames,
+ uint64_t timestamp,
+ uint64_t* numFrames,
+ int32_t samples_size[NUM_HISTOGRAM_COLOR_COMPONENTS],
+ uint64_t* samples[NUM_HISTOGRAM_COLOR_COMPONENTS])
+{
+ histogram.collect(max_frames, timestamp, samples_size, samples, numFrames);
+ return HWC2::Error::None;
+}
DisplayError HWCDisplayBuiltIn::SetMixerResolution(uint32_t width, uint32_t height) {
DisplayError error = display_intf_->SetMixerResolution(width, height);
diff --git a/sdm/libs/hwc2/hwc_display_builtin.h b/sdm/libs/hwc2/hwc_display_builtin.h
index eeeb9c38..3e9e6ebe 100644
--- a/sdm/libs/hwc2/hwc_display_builtin.h
+++ b/sdm/libs/hwc2/hwc_display_builtin.h
@@ -32,6 +32,7 @@
#include <string>
#include <vector>
+#include <mutex>
#include "cpuhint.h"
#include "hwc_display.h"
@@ -55,7 +56,7 @@ class HWCDisplayBuiltIn : public HWCDisplay {
HWCDisplay **hwc_display);
static void Destroy(HWCDisplay *hwc_display);
virtual int Init();
- virtual int Deinit();
+ virtual int Deinit() override;
virtual HWC2::Error Validate(uint32_t *out_num_types, uint32_t *out_num_requests);
virtual HWC2::Error Present(int32_t *out_retire_fence);
virtual HWC2::Error CommitLayerStack();
@@ -98,6 +99,16 @@ class HWCDisplayBuiltIn : public HWCDisplay {
virtual HWC2::Error UpdatePowerMode(HWC2::PowerMode mode);
virtual HWC2::Error PostCommitLayerStack(int32_t *out_retire_fence);
+ virtual HWC2::Error SetDisplayedContentSamplingEnabledVndService(bool enabled);
+ virtual HWC2::Error SetDisplayedContentSamplingEnabled(int32_t enabled, uint8_t component_mask, uint64_t max_frames) override;
+ virtual HWC2::Error GetDisplayedContentSamplingAttributes(int32_t* format, int32_t* dataspace,
+ uint8_t* supported_components) override;
+ virtual HWC2::Error GetDisplayedContentSample(uint64_t max_frames,
+ uint64_t timestamp, uint64_t* numFrames,
+ int32_t samples_size[NUM_HISTOGRAM_COLOR_COMPONENTS],
+ uint64_t* samples[NUM_HISTOGRAM_COLOR_COMPONENTS]) override;
+ std::string Dump() override;
+
private:
HWCDisplayBuiltIn(CoreInterface *core_intf, BufferAllocator *buffer_allocator,
HWCCallbacks *callbacks, HWCDisplayEventHandler *event_handler,
@@ -152,6 +163,13 @@ class HWCDisplayBuiltIn : public HWCDisplay {
// PMIC interface to notify secure display start/end
PMICInterface *pmic_intf_ = nullptr;
bool pmic_notification_pending_ = false;
+
+ // Members for Color sampling feature
+ histogram::HistogramCollector histogram;
+ std::mutex sampling_mutex;
+ bool api_sampling_vote = false;
+ bool vndservice_sampling_vote = false;
+
};
} // namespace sdm
diff --git a/sdm/libs/hwc2/hwc_session.cpp b/sdm/libs/hwc2/hwc_session.cpp
index 7b60720d..d0a6c59c 100644
--- a/sdm/libs/hwc2/hwc_session.cpp
+++ b/sdm/libs/hwc2/hwc_session.cpp
@@ -677,6 +677,39 @@ static int32_t SetLayerPerFrameMetadata(hwc2_device_t *device, hwc2_display_t di
num_elements, keys, metadata);
}
+static int32_t SetDisplayedContentSamplingEnabled(hwc2_device_t* device,
+ hwc2_display_t display,
+ int32_t enabled, uint8_t component_mask,
+ uint64_t max_frames) {
+ static constexpr int32_t validComponentMask =
+ HWC2_FORMAT_COMPONENT_0 | HWC2_FORMAT_COMPONENT_1 |
+ HWC2_FORMAT_COMPONENT_2 | HWC2_FORMAT_COMPONENT_3;
+ if (component_mask & ~validComponentMask) return HWC2_ERROR_BAD_PARAMETER;
+ return HWCSession::CallDisplayFunction(device, display,
+ &HWCDisplay::SetDisplayedContentSamplingEnabled,
+ enabled, component_mask, max_frames);
+}
+
+static int32_t GetDisplayedContentSamplingAttributes(hwc2_device_t* device,
+ hwc2_display_t display,
+ int32_t* format,
+ int32_t* dataspace,
+ uint8_t* supported_components) {
+ return HWCSession::CallDisplayFunction(device, display,
+ &HWCDisplay::GetDisplayedContentSamplingAttributes,
+ format, dataspace, supported_components);
+}
+
+static int32_t GetDisplayedContentSample(
+ hwc2_device_t* device, hwc2_display_t display, uint64_t max_frames, uint64_t timestamp,
+ uint64_t* numFrames,
+ int32_t samples_size[NUM_HISTOGRAM_COLOR_COMPONENTS],
+ uint64_t* samples[NUM_HISTOGRAM_COLOR_COMPONENTS]) {
+
+ return HWCSession::CallDisplayFunction(device, display, &HWCDisplay::GetDisplayedContentSample,
+ max_frames, timestamp, numFrames, samples_size, samples);
+}
+
static int32_t GetDisplayAttribute(hwc2_device_t *device, hwc2_display_t display,
hwc2_config_t config, int32_t int_attribute,
int32_t *out_value) {
@@ -1297,6 +1330,12 @@ hwc2_function_pointer_t HWCSession::GetFunction(struct hwc2_device *device,
return AsFP<HWC2_PFN_GET_DISPLAY_CAPABILITIES>(HWCSession::GetDisplayCapabilities);
case HWC2::FunctionDescriptor::GetDisplayBrightnessSupport:
return AsFP<HWC2_PFN_GET_DISPLAY_BRIGHTNESS_SUPPORT>(HWCSession::GetDisplayBrightnessSupport);
+ case HWC2::FunctionDescriptor::SetDisplayedContentSamplingEnabled:
+ return AsFP<HWC2_PFN_SET_DISPLAYED_CONTENT_SAMPLING_ENABLED>(SetDisplayedContentSamplingEnabled);
+ case HWC2::FunctionDescriptor::GetDisplayedContentSamplingAttributes:
+ return AsFP<HWC2_PFN_GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES>(GetDisplayedContentSamplingAttributes);
+ case HWC2::FunctionDescriptor::GetDisplayedContentSample:
+ return AsFP<HWC2_PFN_GET_DISPLAYED_CONTENT_SAMPLE>(GetDisplayedContentSample);
default:
DLOGD("Unknown/Unimplemented function descriptor: %d (%s)", int_descriptor,
to_string(descriptor).c_str());
@@ -1661,6 +1700,14 @@ android::status_t HWCSession::notifyCallback(uint32_t command, const android::Pa
status = SetQSyncMode(input_parcel);
break;
+ case qService::IQService::SET_COLOR_SAMPLING_ENABLED:
+ if (!input_parcel) {
+ DLOGE("QService command = %d: input_parcel needed.", command);
+ break;
+ }
+ status = setColorSamplingEnabled(input_parcel);
+ break;
+
case qService::IQService::SET_IDLE_PC:
if (!input_parcel) {
DLOGE("QService command = %d: input_parcel needed.", command);
@@ -1758,6 +1805,25 @@ android::status_t HWCSession::GetDisplayAttributesForConfig(const android::Parce
return error;
}
+android::status_t HWCSession::setColorSamplingEnabled(const android::Parcel* input_parcel)
+{
+ int dpy = input_parcel->readInt32();
+ int enabled_cmd = input_parcel->readInt32();
+ if (dpy < HWC_DISPLAY_PRIMARY || dpy >= HWC_NUM_DISPLAY_TYPES ||
+ enabled_cmd < 0 || enabled_cmd > 1) {
+ return android::BAD_VALUE;
+ }
+
+ SEQUENCE_WAIT_SCOPE_LOCK(locker_[dpy]);
+ if (!hwc_display_[dpy]) {
+ DLOGW("No display id %i active to enable histogram event", dpy);
+ return android::BAD_VALUE;
+ }
+
+ auto error = hwc_display_[dpy]->SetDisplayedContentSamplingEnabledVndService(enabled_cmd);
+ return (error == HWC2::Error::None) ? android::OK : android::BAD_VALUE;
+}
+
android::status_t HWCSession::ConfigureRefreshRate(const android::Parcel *input_parcel) {
SEQUENCE_WAIT_SCOPE_LOCK(locker_[HWC_DISPLAY_PRIMARY]);
diff --git a/sdm/libs/hwc2/hwc_session.h b/sdm/libs/hwc2/hwc_session.h
index e19a1667..965e29a5 100644
--- a/sdm/libs/hwc2/hwc_session.h
+++ b/sdm/libs/hwc2/hwc_session.h
@@ -368,6 +368,8 @@ class HWCSession : hwc2_device_t, HWCUEventListener, IDisplayConfig, public qCli
android::Parcel *output_parcel);
android::status_t SetPanelLuminanceAttributes(const android::Parcel *input_parcel);
+ android::status_t setColorSamplingEnabled(const android::Parcel *input_parcel);
+
void Refresh(hwc2_display_t display);
void HotPlug(hwc2_display_t display, HWC2::Connection state);