| /* |
| * Copyright (C) 2019 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 <gtest/gtest.h> |
| #include <gui/SurfaceComposerClient.h> |
| #include <gui/SurfaceControl.h> |
| #include <ui/Fence.h> |
| #include <utils/Timers.h> |
| #include <thread> |
| |
| namespace android { |
| |
| namespace { |
| |
| struct CallbackData { |
| CallbackData() = default; |
| CallbackData(nsecs_t time, const sp<Fence>& fence, |
| const std::vector<SurfaceControlStats>& stats) |
| : latchTime(time), presentFence(fence), surfaceControlStats(stats) {} |
| |
| nsecs_t latchTime; |
| sp<Fence> presentFence; |
| std::vector<SurfaceControlStats> surfaceControlStats; |
| }; |
| |
| class ExpectedResult { |
| public: |
| enum Transaction { |
| NOT_PRESENTED = 0, |
| PRESENTED, |
| }; |
| |
| enum Buffer { |
| NOT_ACQUIRED = 0, |
| ACQUIRED, |
| }; |
| |
| enum PreviousBuffer { |
| NOT_RELEASED = 0, |
| RELEASED, |
| UNKNOWN, |
| }; |
| |
| void reset() { |
| mTransactionResult = ExpectedResult::Transaction::NOT_PRESENTED; |
| mExpectedSurfaceResults.clear(); |
| } |
| |
| void addSurface(ExpectedResult::Transaction transactionResult, const sp<SurfaceControl>& layer, |
| ExpectedResult::Buffer bufferResult = ACQUIRED, |
| ExpectedResult::PreviousBuffer previousBufferResult = NOT_RELEASED) { |
| mTransactionResult = transactionResult; |
| mExpectedSurfaceResults.emplace(std::piecewise_construct, std::forward_as_tuple(layer), |
| std::forward_as_tuple(bufferResult, previousBufferResult)); |
| } |
| |
| void addSurfaces(ExpectedResult::Transaction transactionResult, |
| const std::vector<sp<SurfaceControl>>& layers, |
| ExpectedResult::Buffer bufferResult = ACQUIRED, |
| ExpectedResult::PreviousBuffer previousBufferResult = NOT_RELEASED) { |
| for (const auto& layer : layers) { |
| addSurface(transactionResult, layer, bufferResult, previousBufferResult); |
| } |
| } |
| |
| void addExpectedPresentTime(nsecs_t expectedPresentTime) { |
| mExpectedPresentTime = expectedPresentTime; |
| } |
| |
| void verifyCallbackData(const CallbackData& callbackData) const { |
| const auto& [latchTime, presentFence, surfaceControlStats] = callbackData; |
| if (mTransactionResult == ExpectedResult::Transaction::PRESENTED) { |
| ASSERT_GE(latchTime, 0) << "bad latch time"; |
| ASSERT_NE(presentFence, nullptr); |
| if (mExpectedPresentTime >= 0) { |
| ASSERT_EQ(presentFence->wait(3000), NO_ERROR); |
| ASSERT_GE(presentFence->getSignalTime(), mExpectedPresentTime - nsecs_t(5 * 1e6)); |
| // if the panel is running at 30 hz, at the worst case, our expected time just |
| // misses vsync and we have to wait another 33.3ms |
| ASSERT_LE(presentFence->getSignalTime(), |
| mExpectedPresentTime + nsecs_t(66.666666 * 1e6)); |
| } |
| } else { |
| ASSERT_EQ(presentFence, nullptr) << "transaction shouldn't have been presented"; |
| ASSERT_EQ(latchTime, -1) << "unpresented transactions shouldn't be latched"; |
| } |
| |
| ASSERT_EQ(surfaceControlStats.size(), mExpectedSurfaceResults.size()) |
| << "wrong number of surfaces"; |
| |
| for (const auto& stats : surfaceControlStats) { |
| ASSERT_NE(stats.surfaceControl, nullptr) << "returned null surface control"; |
| |
| const auto& expectedSurfaceResult = mExpectedSurfaceResults.find(stats.surfaceControl); |
| ASSERT_NE(expectedSurfaceResult, mExpectedSurfaceResults.end()) |
| << "unexpected surface control"; |
| expectedSurfaceResult->second.verifySurfaceControlStats(stats, latchTime); |
| } |
| } |
| |
| private: |
| class ExpectedSurfaceResult { |
| public: |
| ExpectedSurfaceResult(ExpectedResult::Buffer bufferResult, |
| ExpectedResult::PreviousBuffer previousBufferResult) |
| : mBufferResult(bufferResult), mPreviousBufferResult(previousBufferResult) {} |
| |
| void verifySurfaceControlStats(const SurfaceControlStats& surfaceControlStats, |
| nsecs_t latchTime) const { |
| const auto& |
| [surfaceControl, latch, acquireTime, presentFence, previousReleaseFence, |
| transformHint, |
| frameEvents] = surfaceControlStats; |
| |
| ASSERT_EQ(acquireTime > 0, mBufferResult == ExpectedResult::Buffer::ACQUIRED) |
| << "bad acquire time"; |
| ASSERT_LE(acquireTime, latchTime) << "acquire time should be <= latch time"; |
| |
| if (mPreviousBufferResult == ExpectedResult::PreviousBuffer::RELEASED) { |
| ASSERT_NE(previousReleaseFence, nullptr) |
| << "failed to set release prev buffer fence"; |
| } else if (mPreviousBufferResult == ExpectedResult::PreviousBuffer::NOT_RELEASED) { |
| ASSERT_EQ(previousReleaseFence, nullptr) |
| << "should not have set released prev buffer fence"; |
| } |
| } |
| |
| private: |
| ExpectedResult::Buffer mBufferResult; |
| ExpectedResult::PreviousBuffer mPreviousBufferResult; |
| }; |
| |
| struct SCHash { |
| std::size_t operator()(const sp<SurfaceControl>& sc) const { |
| return std::hash<IBinder*>{}(sc->getHandle().get()); |
| } |
| }; |
| ExpectedResult::Transaction mTransactionResult = ExpectedResult::Transaction::NOT_PRESENTED; |
| nsecs_t mExpectedPresentTime = -1; |
| std::unordered_map<sp<SurfaceControl>, ExpectedSurfaceResult, SCHash> mExpectedSurfaceResults; |
| }; |
| |
| class CallbackHelper { |
| public: |
| static void function(void* callbackContext, nsecs_t latchTime, const sp<Fence>& presentFence, |
| const std::vector<SurfaceControlStats>& stats) { |
| if (!callbackContext) { |
| ALOGE("failed to get callback context"); |
| } |
| CallbackHelper* helper = static_cast<CallbackHelper*>(callbackContext); |
| std::lock_guard lock(helper->mMutex); |
| helper->mCallbackDataQueue.emplace(latchTime, presentFence, stats); |
| helper->mConditionVariable.notify_all(); |
| } |
| |
| void getCallbackData(CallbackData* outData) { |
| std::unique_lock lock(mMutex); |
| |
| if (mCallbackDataQueue.empty()) { |
| ASSERT_NE(mConditionVariable.wait_for(lock, std::chrono::seconds(3)), |
| std::cv_status::timeout) |
| << "did not receive callback"; |
| } |
| |
| *outData = std::move(mCallbackDataQueue.front()); |
| mCallbackDataQueue.pop(); |
| } |
| |
| void verifyFinalState() { |
| // Wait to see if there are extra callbacks |
| std::this_thread::sleep_for(500ms); |
| |
| std::lock_guard lock(mMutex); |
| EXPECT_EQ(mCallbackDataQueue.size(), 0) << "extra callbacks received"; |
| mCallbackDataQueue = {}; |
| } |
| |
| void* getContext() { return static_cast<void*>(this); } |
| |
| std::mutex mMutex; |
| std::condition_variable mConditionVariable; |
| std::queue<CallbackData> mCallbackDataQueue; |
| }; |
| } |
| } // namespace android |