summaryrefslogtreecommitdiffstats
path: root/libhistogram/histogram_collector.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libhistogram/histogram_collector.cpp')
-rw-r--r--libhistogram/histogram_collector.cpp556
1 files changed, 556 insertions, 0 deletions
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;
+ }
+ }
+ }
+ }
+}