diff options
-rw-r--r-- | Android.bp | 1 | ||||
-rw-r--r-- | Android.mk | 2 | ||||
-rw-r--r-- | libhistogram/Android.mk | 58 | ||||
-rw-r--r-- | libhistogram/color_sampling_tool.cpp | 97 | ||||
-rw-r--r-- | libhistogram/histogram_collector.cpp | 556 | ||||
-rw-r--r-- | libhistogram/histogram_collector.h | 70 | ||||
-rw-r--r-- | libhistogram/ringbuffer.cpp | 141 | ||||
-rw-r--r-- | libhistogram/ringbuffer.h | 85 | ||||
-rw-r--r-- | libhistogram/ringbuffer_test.cpp | 410 | ||||
-rw-r--r-- | libqservice/IQService.h | 2 | ||||
-rw-r--r-- | sdm/libs/hwc2/Android.mk | 5 | ||||
-rw-r--r-- | sdm/libs/hwc2/hwc_display.cpp | 25 | ||||
-rw-r--r-- | sdm/libs/hwc2/hwc_display.h | 12 | ||||
-rw-r--r-- | sdm/libs/hwc2/hwc_display_builtin.cpp | 54 | ||||
-rw-r--r-- | sdm/libs/hwc2/hwc_display_builtin.h | 20 | ||||
-rw-r--r-- | sdm/libs/hwc2/hwc_session.cpp | 66 | ||||
-rw-r--r-- | sdm/libs/hwc2/hwc_session.h | 2 |
17 files changed, 1603 insertions, 3 deletions
@@ -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"], @@ -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, ®->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); |