| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| //#define LOG_NDEBUG 0 |
| #undef LOG_TAG |
| #define LOG_TAG "FakeComposer" |
| |
| #include "FakeComposerClient.h" |
| |
| #include <gui/SurfaceComposerClient.h> |
| |
| #include <log/log.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include <inttypes.h> |
| #include <time.h> |
| #include <algorithm> |
| #include <condition_variable> |
| #include <iostream> |
| #include <mutex> |
| #include <set> |
| #include <thread> |
| |
| constexpr Config NULL_DISPLAY_CONFIG = static_cast<Config>(0); |
| constexpr Display DEFAULT_DISPLAY = static_cast<Display>(1); |
| |
| using namespace sftest; |
| |
| using android::Condition; |
| using android::Mutex; |
| |
| using Clock = std::chrono::steady_clock; |
| using TimePoint = std::chrono::time_point<Clock>; |
| |
| namespace { |
| |
| // Internal state of a layer in the HWC API. |
| class LayerImpl { |
| public: |
| LayerImpl() = default; |
| |
| bool mValid = true; |
| RenderState mRenderState; |
| uint32_t mZ = 0; |
| }; |
| |
| // Struct for storing per frame rectangle state. Contains the render |
| // state shared to the test case. Basically a snapshot and a subset of |
| // LayerImpl sufficient to re-create the pixels of a layer for the |
| // frame. |
| struct FrameRect { |
| public: |
| FrameRect(Layer layer_, const RenderState& state, uint32_t z_) |
| : layer(layer_), renderState(state), z(z_) {} |
| |
| const Layer layer; |
| const RenderState renderState; |
| const uint32_t z; |
| }; |
| |
| // Collection of FrameRects forming one rendered frame. Could store |
| // related fences and other data in the future. |
| class Frame { |
| public: |
| Frame() = default; |
| std::vector<std::unique_ptr<FrameRect>> rectangles; |
| }; |
| |
| class DelayedEventGenerator { |
| public: |
| DelayedEventGenerator(std::function<void()> onTimerExpired) |
| : mOnTimerExpired(onTimerExpired), mThread([this]() { loop(); }) {} |
| |
| ~DelayedEventGenerator() { |
| ALOGI("DelayedEventGenerator exiting."); |
| { |
| std::unique_lock<std::mutex> lock(mMutex); |
| mRunning = false; |
| mWakeups.clear(); |
| mCondition.notify_one(); |
| } |
| mThread.join(); |
| ALOGI("DelayedEventGenerator exited."); |
| } |
| |
| void wakeAfter(std::chrono::nanoseconds waitTime) { |
| std::unique_lock<std::mutex> lock(mMutex); |
| mWakeups.insert(Clock::now() + waitTime); |
| mCondition.notify_one(); |
| } |
| |
| private: |
| void loop() { |
| while (true) { |
| // Lock scope |
| { |
| std::unique_lock<std::mutex> lock(mMutex); |
| mCondition.wait(lock, [this]() { return !mRunning || !mWakeups.empty(); }); |
| if (!mRunning && mWakeups.empty()) { |
| // This thread should only exit once the destructor has been called and all |
| // wakeups have been processed |
| return; |
| } |
| |
| // At this point, mWakeups will not be empty |
| |
| TimePoint target = *(mWakeups.begin()); |
| auto status = mCondition.wait_until(lock, target); |
| while (status == std::cv_status::no_timeout) { |
| // This was either a spurious wakeup or another wakeup was added, so grab the |
| // oldest point and wait again |
| target = *(mWakeups.begin()); |
| status = mCondition.wait_until(lock, target); |
| } |
| |
| // status must have been timeout, so we can finally clear this point |
| mWakeups.erase(target); |
| } |
| // Callback *without* locks! |
| mOnTimerExpired(); |
| } |
| } |
| |
| std::function<void()> mOnTimerExpired; |
| std::thread mThread; |
| std::mutex mMutex; |
| std::condition_variable mCondition; |
| bool mRunning = true; |
| std::set<TimePoint> mWakeups; |
| }; |
| |
| } // namespace |
| |
| FakeComposerClient::FakeComposerClient() |
| : mCallbacksOn(false), |
| mClient(nullptr), |
| mCurrentConfig(NULL_DISPLAY_CONFIG), |
| mVsyncEnabled(false), |
| mLayers(), |
| mDelayedEventGenerator( |
| std::make_unique<DelayedEventGenerator>([this]() { this->requestVSync(); })), |
| mSurfaceComposer(nullptr) {} |
| |
| FakeComposerClient::~FakeComposerClient() {} |
| |
| bool FakeComposerClient::hasCapability(hwc2_capability_t /*capability*/) { |
| return false; |
| } |
| |
| void FakeComposerClient::removeClient() { |
| ALOGV("removeClient"); |
| // TODO: Ahooga! Only thing current lifetime management choices in |
| // APIs make possible. Sad. |
| delete this; |
| } |
| |
| void FakeComposerClient::enableCallback(bool enable) { |
| ALOGV("enableCallback"); |
| mCallbacksOn = enable; |
| if (mCallbacksOn) { |
| mClient->onHotplug(DEFAULT_DISPLAY, IComposerCallback::Connection::CONNECTED); |
| } |
| } |
| |
| void FakeComposerClient::hotplugDisplay(Display display, IComposerCallback::Connection state) { |
| if (mCallbacksOn) { |
| mClient->onHotplug(display, state); |
| } |
| } |
| |
| uint32_t FakeComposerClient::getMaxVirtualDisplayCount() { |
| ALOGV("getMaxVirtualDisplayCount"); |
| return 1; |
| } |
| |
| Error FakeComposerClient::createVirtualDisplay(uint32_t /*width*/, uint32_t /*height*/, |
| PixelFormat* /*format*/, Display* /*outDisplay*/) { |
| ALOGV("createVirtualDisplay"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::destroyVirtualDisplay(Display /*display*/) { |
| ALOGV("destroyVirtualDisplay"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::createLayer(Display /*display*/, Layer* outLayer) { |
| ALOGV("createLayer"); |
| *outLayer = mLayers.size(); |
| auto newLayer = std::make_unique<LayerImpl>(); |
| mLayers.push_back(std::move(newLayer)); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::destroyLayer(Display /*display*/, Layer layer) { |
| ALOGV("destroyLayer"); |
| mLayers[layer]->mValid = false; |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::getActiveConfig(Display /*display*/, Config* outConfig) { |
| ALOGV("getActiveConfig"); |
| |
| // TODO Assert outConfig != nullptr |
| |
| // TODO This is my reading of the |
| // IComposerClient::getActiveConfig, but returning BAD_CONFIG |
| // seems to not fit SurfaceFlinger plans. See version 2 below. |
| // if (mCurrentConfig == NULL_DISPLAY_CONFIG) { |
| // return Error::BAD_CONFIG; |
| // } |
| //*outConfig = mCurrentConfig; |
| *outConfig = 1; // Very special config for you my friend |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::getClientTargetSupport(Display /*display*/, uint32_t /*width*/, |
| uint32_t /*height*/, PixelFormat /*format*/, |
| Dataspace /*dataspace*/) { |
| ALOGV("getClientTargetSupport"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::getColorModes(Display /*display*/, hidl_vec<ColorMode>* /*outModes*/) { |
| ALOGV("getColorModes"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::getDisplayAttribute(Display display, Config config, |
| IComposerClient::Attribute attribute, |
| int32_t* outValue) { |
| ALOGV("getDisplayAttribute (%d, %d, %d, %p)", static_cast<int>(display), |
| static_cast<int>(config), static_cast<int>(attribute), outValue); |
| |
| // TODO: SOOO much fun to be had with these alone |
| switch (attribute) { |
| case IComposerClient::Attribute::WIDTH: |
| *outValue = 1920; |
| break; |
| case IComposerClient::Attribute::HEIGHT: |
| *outValue = 1080; |
| break; |
| case IComposerClient::Attribute::VSYNC_PERIOD: |
| *outValue = 1666666666; |
| break; // TOOD: Tests break down if lowered to 16ms? |
| case IComposerClient::Attribute::DPI_X: |
| *outValue = 240; |
| break; |
| case IComposerClient::Attribute::DPI_Y: |
| *outValue = 240; |
| break; |
| default: |
| LOG_ALWAYS_FATAL("Say what!?! New attribute"); |
| } |
| |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::getDisplayConfigs(Display /*display*/, hidl_vec<Config>* outConfigs) { |
| ALOGV("getDisplayConfigs"); |
| // TODO assert display == 1, outConfigs != nullptr |
| |
| outConfigs->resize(1); |
| (*outConfigs)[0] = 1; |
| |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::getDisplayName(Display /*display*/, hidl_string* /*outName*/) { |
| ALOGV("getDisplayName"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::getDisplayType(Display /*display*/, |
| IComposerClient::DisplayType* outType) { |
| ALOGV("getDisplayType"); |
| // TODO: This setting nothing on the output had no effect on initial trials. Is first display |
| // assumed to be physical? |
| *outType = static_cast<IComposerClient::DisplayType>(HWC2_DISPLAY_TYPE_PHYSICAL); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::getDozeSupport(Display /*display*/, bool* /*outSupport*/) { |
| ALOGV("getDozeSupport"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::getHdrCapabilities(Display /*display*/, hidl_vec<Hdr>* /*outTypes*/, |
| float* /*outMaxLuminance*/, |
| float* /*outMaxAverageLuminance*/, |
| float* /*outMinLuminance*/) { |
| ALOGV("getHdrCapabilities"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setActiveConfig(Display /*display*/, Config config) { |
| ALOGV("setActiveConfig"); |
| mCurrentConfig = config; |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setColorMode(Display /*display*/, ColorMode /*mode*/) { |
| ALOGV("setColorMode"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setPowerMode(Display /*display*/, IComposerClient::PowerMode /*mode*/) { |
| ALOGV("setPowerMode"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setVsyncEnabled(Display /*display*/, IComposerClient::Vsync enabled) { |
| mVsyncEnabled = (enabled == IComposerClient::Vsync::ENABLE); |
| ALOGV("setVsyncEnabled(%s)", mVsyncEnabled ? "ENABLE" : "DISABLE"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setColorTransform(Display /*display*/, const float* /*matrix*/, |
| int32_t /*hint*/) { |
| ALOGV("setColorTransform"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setClientTarget(Display /*display*/, buffer_handle_t /*target*/, |
| int32_t /*acquireFence*/, int32_t /*dataspace*/, |
| const std::vector<hwc_rect_t>& /*damage*/) { |
| ALOGV("setClientTarget"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setOutputBuffer(Display /*display*/, buffer_handle_t /*buffer*/, |
| int32_t /*releaseFence*/) { |
| ALOGV("setOutputBuffer"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::validateDisplay( |
| Display /*display*/, std::vector<Layer>* /*outChangedLayers*/, |
| std::vector<IComposerClient::Composition>* /*outCompositionTypes*/, |
| uint32_t* /*outDisplayRequestMask*/, std::vector<Layer>* /*outRequestedLayers*/, |
| std::vector<uint32_t>* /*outRequestMasks*/) { |
| ALOGV("validateDisplay"); |
| // TODO: Assume touching nothing means All Korrekt! |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::acceptDisplayChanges(Display /*display*/) { |
| ALOGV("acceptDisplayChanges"); |
| // Didn't ask for changes because software is omnipotent. |
| return Error::NONE; |
| } |
| |
| bool layerZOrdering(const std::unique_ptr<FrameRect>& a, const std::unique_ptr<FrameRect>& b) { |
| return a->z <= b->z; |
| } |
| |
| Error FakeComposerClient::presentDisplay(Display /*display*/, int32_t* /*outPresentFence*/, |
| std::vector<Layer>* /*outLayers*/, |
| std::vector<int32_t>* /*outReleaseFences*/) { |
| ALOGV("presentDisplay"); |
| // TODO Leaving layers and their fences out for now. Doing so |
| // means that we've already processed everything. Important to |
| // test that the fences are respected, though. (How?) |
| |
| std::unique_ptr<Frame> newFrame(new Frame); |
| for (uint64_t layer = 0; layer < mLayers.size(); layer++) { |
| const LayerImpl& layerImpl = *mLayers[layer]; |
| |
| if (!layerImpl.mValid) continue; |
| |
| auto rect = std::make_unique<FrameRect>(layer, layerImpl.mRenderState, layerImpl.mZ); |
| newFrame->rectangles.push_back(std::move(rect)); |
| } |
| std::sort(newFrame->rectangles.begin(), newFrame->rectangles.end(), layerZOrdering); |
| { |
| Mutex::Autolock _l(mStateMutex); |
| mFrames.push_back(std::move(newFrame)); |
| mFramesAvailable.broadcast(); |
| } |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerCursorPosition(Display /*display*/, Layer /*layer*/, |
| int32_t /*x*/, int32_t /*y*/) { |
| ALOGV("setLayerCursorPosition"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerBuffer(Display /*display*/, Layer layer, buffer_handle_t buffer, |
| int32_t acquireFence) { |
| ALOGV("setLayerBuffer"); |
| LayerImpl& l = getLayerImpl(layer); |
| if (buffer != l.mRenderState.mBuffer) { |
| l.mRenderState.mSwapCount++; // TODO: Is setting to same value a swap or not? |
| } |
| l.mRenderState.mBuffer = buffer; |
| l.mRenderState.mAcquireFence = acquireFence; |
| |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerSurfaceDamage(Display /*display*/, Layer /*layer*/, |
| const std::vector<hwc_rect_t>& /*damage*/) { |
| ALOGV("setLayerSurfaceDamage"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerBlendMode(Display /*display*/, Layer layer, int32_t mode) { |
| ALOGV("setLayerBlendMode"); |
| getLayerImpl(layer).mRenderState.mBlendMode = static_cast<hwc2_blend_mode_t>(mode); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerColor(Display /*display*/, Layer layer, |
| IComposerClient::Color color) { |
| ALOGV("setLayerColor"); |
| getLayerImpl(layer).mRenderState.mLayerColor.r = color.r; |
| getLayerImpl(layer).mRenderState.mLayerColor.g = color.g; |
| getLayerImpl(layer).mRenderState.mLayerColor.b = color.b; |
| getLayerImpl(layer).mRenderState.mLayerColor.a = color.a; |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerCompositionType(Display /*display*/, Layer /*layer*/, |
| int32_t /*type*/) { |
| ALOGV("setLayerCompositionType"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerDataspace(Display /*display*/, Layer /*layer*/, |
| int32_t /*dataspace*/) { |
| ALOGV("setLayerDataspace"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerDisplayFrame(Display /*display*/, Layer layer, |
| const hwc_rect_t& frame) { |
| ALOGV("setLayerDisplayFrame (%d, %d, %d, %d)", frame.left, frame.top, frame.right, |
| frame.bottom); |
| getLayerImpl(layer).mRenderState.mDisplayFrame = frame; |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerPlaneAlpha(Display /*display*/, Layer layer, float alpha) { |
| ALOGV("setLayerPlaneAlpha"); |
| getLayerImpl(layer).mRenderState.mPlaneAlpha = alpha; |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerSidebandStream(Display /*display*/, Layer /*layer*/, |
| buffer_handle_t /*stream*/) { |
| ALOGV("setLayerSidebandStream"); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerSourceCrop(Display /*display*/, Layer layer, |
| const hwc_frect_t& crop) { |
| ALOGV("setLayerSourceCrop"); |
| getLayerImpl(layer).mRenderState.mSourceCrop = crop; |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerTransform(Display /*display*/, Layer layer, int32_t transform) { |
| ALOGV("setLayerTransform"); |
| getLayerImpl(layer).mRenderState.mTransform = static_cast<hwc_transform_t>(transform); |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerVisibleRegion(Display /*display*/, Layer layer, |
| const std::vector<hwc_rect_t>& visible) { |
| ALOGV("setLayerVisibleRegion"); |
| getLayerImpl(layer).mRenderState.mVisibleRegion = visible; |
| return Error::NONE; |
| } |
| |
| Error FakeComposerClient::setLayerZOrder(Display /*display*/, Layer layer, uint32_t z) { |
| ALOGV("setLayerZOrder"); |
| getLayerImpl(layer).mZ = z; |
| return Error::NONE; |
| } |
| |
| ////////////////////////////////////////////////////////////////// |
| |
| void FakeComposerClient::setClient(ComposerClient* client) { |
| mClient = client; |
| } |
| |
| void FakeComposerClient::requestVSync(uint64_t vsyncTime) { |
| if (mCallbacksOn) { |
| uint64_t timestamp = vsyncTime; |
| ALOGV("Vsync"); |
| if (timestamp == 0) { |
| struct timespec ts; |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| timestamp = ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec; |
| } |
| if (mSurfaceComposer != nullptr) { |
| mSurfaceComposer->injectVSync(timestamp); |
| } else { |
| mClient->onVsync(DEFAULT_DISPLAY, timestamp); |
| } |
| } |
| } |
| |
| void FakeComposerClient::runVSyncAfter(std::chrono::nanoseconds wait) { |
| mDelayedEventGenerator->wakeAfter(wait); |
| } |
| |
| LayerImpl& FakeComposerClient::getLayerImpl(Layer handle) { |
| // TODO Change these to an internal state check that can be |
| // invoked from the gtest? GTest macros do not seem all that safe |
| // when used outside the test class |
| EXPECT_GE(handle, static_cast<Layer>(0)); |
| EXPECT_LT(handle, mLayers.size()); |
| return *(mLayers[handle]); |
| } |
| |
| int FakeComposerClient::getFrameCount() const { |
| return mFrames.size(); |
| } |
| |
| static std::vector<RenderState> extractRenderState( |
| const std::vector<std::unique_ptr<FrameRect>>& internalRects) { |
| std::vector<RenderState> result; |
| result.reserve(internalRects.size()); |
| for (const std::unique_ptr<FrameRect>& rect : internalRects) { |
| result.push_back(rect->renderState); |
| } |
| return result; |
| } |
| |
| std::vector<RenderState> FakeComposerClient::getFrameRects(int frame) const { |
| Mutex::Autolock _l(mStateMutex); |
| return extractRenderState(mFrames[frame]->rectangles); |
| } |
| |
| std::vector<RenderState> FakeComposerClient::getLatestFrame() const { |
| Mutex::Autolock _l(mStateMutex); |
| return extractRenderState(mFrames[mFrames.size() - 1]->rectangles); |
| } |
| |
| void FakeComposerClient::runVSyncAndWait(std::chrono::nanoseconds maxWait) { |
| int currentFrame = 0; |
| { |
| Mutex::Autolock _l(mStateMutex); // I hope this is ok... |
| currentFrame = static_cast<int>(mFrames.size()); |
| requestVSync(); |
| } |
| waitUntilFrame(currentFrame + 1, maxWait); |
| } |
| |
| void FakeComposerClient::waitUntilFrame(int targetFrame, std::chrono::nanoseconds maxWait) const { |
| Mutex::Autolock _l(mStateMutex); |
| while (mFrames.size() < static_cast<size_t>(targetFrame)) { |
| android::status_t result = mFramesAvailable.waitRelative(mStateMutex, maxWait.count()); |
| if (result == android::TIMED_OUT) { |
| ALOGE("Waiting for frame %d (at frame %zu now) timed out after %lld ns", targetFrame, |
| mFrames.size(), maxWait.count()); |
| return; |
| } |
| } |
| } |
| |
| void FakeComposerClient::clearFrames() { |
| Mutex::Autolock _l(mStateMutex); |
| mFrames.clear(); |
| for (const std::unique_ptr<LayerImpl>& layer : mLayers) { |
| if (layer->mValid) { |
| layer->mRenderState.mSwapCount = 0; |
| } |
| } |
| } |
| |
| void FakeComposerClient::onSurfaceFlingerStart() { |
| mSurfaceComposer = nullptr; |
| do { |
| mSurfaceComposer = new android::SurfaceComposerClient; |
| android::status_t initResult = mSurfaceComposer->initCheck(); |
| if (initResult != android::NO_ERROR) { |
| ALOGD("Init result: %d", initResult); |
| mSurfaceComposer = nullptr; |
| std::this_thread::sleep_for(10ms); |
| } |
| } while (mSurfaceComposer == nullptr); |
| ALOGD("SurfaceComposerClient created"); |
| mSurfaceComposer->enableVSyncInjections(true); |
| } |
| |
| void FakeComposerClient::onSurfaceFlingerStop() { |
| mSurfaceComposer->dispose(); |
| mSurfaceComposer.clear(); |
| } |
| |
| // Includes destroyed layers, stored in order of creation. |
| int FakeComposerClient::getLayerCount() const { |
| return mLayers.size(); |
| } |
| |
| Layer FakeComposerClient::getLayer(size_t index) const { |
| // NOTE: If/when passing calls through to actual implementation, |
| // this might get more involving. |
| return static_cast<Layer>(index); |
| } |