From cd9b55c70f081dbd004d347b51a793ce6fe3cacf Mon Sep 17 00:00:00 2001 From: Matthew Bouyack Date: Thu, 1 Jun 2017 14:37:29 -0700 Subject: [PATCH 001/704] Eliminate redundant changes to hardware vsync state. When transitioning between NORMAL and DOZE power modes we were redundantly calling resyncHardwareVsync. Similarly, when transitioning from DOZE_SUSPEND to OFF we were redundantly calling disableHardwareVsync. This change eliminates those redundant calls. Fixes bug 62235417 Change-Id: I513bbf94a7ab973ab258efe16436441ac2379b70 --- services/surfaceflinger/SurfaceFlinger.cpp | 6 ++++-- services/surfaceflinger/SurfaceFlinger_hwc1.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 7392006c6f..ee63ebcb9c 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3210,7 +3210,8 @@ void SurfaceFlinger::setPowerModeInternal(const sp& hw, ALOGW("Couldn't set SCHED_OTHER on display off"); } - if (type == DisplayDevice::DISPLAY_PRIMARY) { + if (type == DisplayDevice::DISPLAY_PRIMARY && + currentMode != HWC_POWER_MODE_DOZE_SUSPEND) { disableHardwareVsync(true); // also cancels any in-progress resync // FIXME: eventthread only knows about the main display right now @@ -3224,7 +3225,8 @@ void SurfaceFlinger::setPowerModeInternal(const sp& hw, mode == HWC_POWER_MODE_NORMAL) { // Update display while dozing getHwComposer().setPowerMode(type, mode); - if (type == DisplayDevice::DISPLAY_PRIMARY) { + if (type == DisplayDevice::DISPLAY_PRIMARY && + currentMode == HWC_POWER_MODE_DOZE_SUSPEND) { // FIXME: eventthread only knows about the main display right now mEventThread->onScreenAcquired(); resyncToHardwareVsync(true); diff --git a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp index 0b3a0d0d38..43cf99898a 100644 --- a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp +++ b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp @@ -2869,7 +2869,8 @@ void SurfaceFlinger::setPowerModeInternal(const sp& hw, ALOGW("Couldn't set SCHED_OTHER on display off"); } - if (type == DisplayDevice::DISPLAY_PRIMARY) { + if (type == DisplayDevice::DISPLAY_PRIMARY && + currentMode != HWC_POWER_MODE_DOZE_SUSPEND) { disableHardwareVsync(true); // also cancels any in-progress resync // FIXME: eventthread only knows about the main display right now @@ -2883,7 +2884,8 @@ void SurfaceFlinger::setPowerModeInternal(const sp& hw, mode == HWC_POWER_MODE_NORMAL) { // Update display while dozing getHwComposer().setPowerMode(type, mode); - if (type == DisplayDevice::DISPLAY_PRIMARY) { + if (type == DisplayDevice::DISPLAY_PRIMARY && + currentMode == HWC_POWER_MODE_DOZE_SUSPEND) { // FIXME: eventthread only knows about the main display right now mEventThread->onScreenAcquired(); resyncToHardwareVsync(true); -- GitLab From 161410b01a37bcc5522d8e91fe0a743989c86e70 Mon Sep 17 00:00:00 2001 From: chaviw Date: Thu, 27 Jul 2017 10:46:08 -0700 Subject: [PATCH 002/704] Only detach children from a different client than parent. The purpose of detach is prevent another client from modifying the layer. Therefore, layers with the same client as their parent shouldn't be detached because the parent can and should be able to still modify those layers. Test: Updated Transaction_test so it has a test when the parent detaches children with same client and detaches children with different client. Change-Id: Ieede3c7b472e6eaa567163876b71cc9d7e889daf --- services/surfaceflinger/Client.cpp | 3 +- services/surfaceflinger/Layer.cpp | 3 +- .../surfaceflinger/tests/Transaction_test.cpp | 48 ++++++++++++++++++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp index 8ba6cb9ba7..ea6541a734 100644 --- a/services/surfaceflinger/Client.cpp +++ b/services/surfaceflinger/Client.cpp @@ -140,7 +140,8 @@ status_t Client::createSurface( { sp parent = nullptr; if (parentHandle != nullptr) { - parent = getLayerUser(parentHandle); + auto layerHandle = reinterpret_cast(parentHandle.get()); + parent = layerHandle->owner.promote(); if (parent == nullptr) { return NAME_NOT_FOUND; } diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index b14fd7bb87..1903bb1a0d 100755 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2591,8 +2591,9 @@ bool Layer::detachChildren() { return; } + sp parentClient = mClientRef.promote(); sp client(child->mClientRef.promote()); - if (client != nullptr) { + if (client != nullptr && parentClient != client) { client->detachLayer(child); } }); diff --git a/services/surfaceflinger/tests/Transaction_test.cpp b/services/surfaceflinger/tests/Transaction_test.cpp index 4ce14f8d3a..5d9e8b3a30 100644 --- a/services/surfaceflinger/tests/Transaction_test.cpp +++ b/services/surfaceflinger/tests/Transaction_test.cpp @@ -990,7 +990,7 @@ TEST_F(ChildLayerTest, ReparentChildren) { } } -TEST_F(ChildLayerTest, DetachChildren) { +TEST_F(ChildLayerTest, DetachChildrenSameClient) { SurfaceComposerClient::openGlobalTransaction(); mChild->show(); mChild->setPosition(10, 10); @@ -1015,6 +1015,52 @@ TEST_F(ChildLayerTest, DetachChildren) { mChild->hide(); SurfaceComposerClient::closeGlobalTransaction(true); + // Since the child has the same client as the parent, it will not get + // detached and will be hidden. + { + ScreenCapture::captureScreen(&mCapture); + mCapture->expectFGColor(64, 64); + mCapture->expectFGColor(74, 74); + mCapture->expectFGColor(84, 84); + } +} + +TEST_F(ChildLayerTest, DetachChildrenDifferentClient) { + sp mNewComposerClient = new SurfaceComposerClient; + sp mChildNewClient = mNewComposerClient->createSurface( + String8("New Child Test Surface"), 10, 10, PIXEL_FORMAT_RGBA_8888, + 0, mFGSurfaceControl.get()); + + ASSERT_TRUE(mChildNewClient != NULL); + ASSERT_TRUE(mChildNewClient->isValid()); + + fillSurfaceRGBA8(mChildNewClient, 200, 200, 200); + + SurfaceComposerClient::openGlobalTransaction(); + mChild->hide(); + mChildNewClient->show(); + mChildNewClient->setPosition(10, 10); + mFGSurfaceControl->setPosition(64, 64); + SurfaceComposerClient::closeGlobalTransaction(true); + + { + ScreenCapture::captureScreen(&mCapture); + // Top left of foreground must now be visible + mCapture->expectFGColor(64, 64); + // But 10 pixels in we should see the child surface + mCapture->expectChildColor(74, 74); + // And 10 more pixels we should be back to the foreground surface + mCapture->expectFGColor(84, 84); + } + + SurfaceComposerClient::openGlobalTransaction(); + mFGSurfaceControl->detachChildren(); + SurfaceComposerClient::closeGlobalTransaction(true); + + SurfaceComposerClient::openGlobalTransaction(); + mChildNewClient->hide(); + SurfaceComposerClient::closeGlobalTransaction(true); + // Nothing should have changed. { ScreenCapture::captureScreen(&mCapture); -- GitLab From 41eaab40d6d5372c03824ea441c5c8346fe6e7ac Mon Sep 17 00:00:00 2001 From: Hsin-Yi Chen Date: Fri, 28 Jul 2017 11:28:52 +0800 Subject: [PATCH 003/704] Cast binder data type to uint64_t in high bits test 1. Cast fb->binder to uint64_t before shifting by 32 bits. It avoids undefined result when binder is 32-bit. 2. Swap EXPECT_EQ arguments. The first one is expected value and the second is actual value. Bug: 64118463 Test: ./binderLibTest on x86 emulator Change-Id: I4f736bd5b55db5af8598db0d5cd5fbd9ef323448 --- libs/binder/tests/binderLibTest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp index 34aec5be5b..74430e8a16 100644 --- a/libs/binder/tests/binderLibTest.cpp +++ b/libs/binder/tests/binderLibTest.cpp @@ -681,10 +681,10 @@ TEST_F(BinderLibTest, CheckHandleZeroBinderHighBitsZeroCookie) { const flat_binder_object *fb = reply.readObject(false); ASSERT_TRUE(fb != NULL); - EXPECT_EQ(fb->hdr.type, BINDER_TYPE_HANDLE); - EXPECT_EQ(ProcessState::self()->getStrongProxyForHandle(fb->handle), m_server); - EXPECT_EQ(fb->cookie, (binder_uintptr_t)0); - EXPECT_EQ(fb->binder >> 32, (binder_uintptr_t)0); + EXPECT_EQ(BINDER_TYPE_HANDLE, fb->hdr.type); + EXPECT_EQ(m_server, ProcessState::self()->getStrongProxyForHandle(fb->handle)); + EXPECT_EQ((binder_uintptr_t)0, fb->cookie); + EXPECT_EQ((uint64_t)0, (uint64_t)fb->binder >> 32); } TEST_F(BinderLibTest, FreedBinder) { -- GitLab From 00a4ea970bab16ef5675e866cd856a0d9919f0c5 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 8 Jun 2017 21:43:20 +0100 Subject: [PATCH 004/704] New impulse-based VelocityTracker strategy. New velocity calculation strategy for VelocityTracker. The strategy models the phone screen as a physical object that gets pushed by the finger. Upon liftoff, the total work done on the object (past 100ms of data and at most 20 most recent samples) is considered to be kinetic energy, which gets converted into equivalent velocity. Works well with "bad" data - unclean touch liftoff, repeated coordinates. Time-shift invariant. Performance otherwise similar to the current default strategy, quadratic unweighted least squares. Bug: 35412046 Test: Recorded bad scroll event on swordfish, this fixes the fling in the wrong direction. Also tested common usecase scenarios on sailfish, no regressions observed. Similar velocity values to lsq2 strategy. Change-Id: Ib439db0ce3b4fe84f59cf66683eae0b5df7776eb --- include/input/VelocityTracker.h | 34 ++++++ libs/input/VelocityTracker.cpp | 202 ++++++++++++++++++++++++++++++-- 2 files changed, 228 insertions(+), 8 deletions(-) diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h index 795f575a2e..ffa1614b55 100644 --- a/include/input/VelocityTracker.h +++ b/include/input/VelocityTracker.h @@ -264,6 +264,40 @@ private: Movement mMovements[HISTORY_SIZE]; }; +class ImpulseVelocityTrackerStrategy : public VelocityTrackerStrategy { +public: + ImpulseVelocityTrackerStrategy(); + virtual ~ImpulseVelocityTrackerStrategy(); + + virtual void clear(); + virtual void clearPointers(BitSet32 idBits); + virtual void addMovement(nsecs_t eventTime, BitSet32 idBits, + const VelocityTracker::Position* positions); + virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const; + +private: + // Sample horizon. + // We don't use too much history by default since we want to react to quick + // changes in direction. + static constexpr nsecs_t HORIZON = 100 * 1000000; // 100 ms + + // Number of samples to keep. + static constexpr size_t HISTORY_SIZE = 20; + + struct Movement { + nsecs_t eventTime; + BitSet32 idBits; + VelocityTracker::Position positions[MAX_POINTERS]; + + inline const VelocityTracker::Position& getPosition(uint32_t id) const { + return positions[idBits.getIndexOfBit(id)]; + } + }; + + size_t mIndex; + Movement mMovements[HISTORY_SIZE]; +}; + } // namespace android #endif // _LIBINPUT_VELOCITY_TRACKER_H diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index 62acea360e..cc74b9b5cf 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -105,7 +105,7 @@ static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool r // this is the strategy that applications will actually use. Be very careful // when adjusting the default strategy because it can dramatically affect // (often in a bad way) the user experience. -const char* VelocityTracker::DEFAULT_STRATEGY = "lsq2"; +const char* VelocityTracker::DEFAULT_STRATEGY = "impulse"; VelocityTracker::VelocityTracker(const char* strategy) : mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) { @@ -141,6 +141,11 @@ bool VelocityTracker::configureStrategy(const char* strategy) { } VelocityTrackerStrategy* VelocityTracker::createStrategy(const char* strategy) { + if (!strcmp("impulse", strategy)) { + // Physical model of pushing an object. Quality: VERY GOOD. + // Works with duplicate coordinates, unclean finger liftoff. + return new ImpulseVelocityTrackerStrategy(); + } if (!strcmp("lsq1", strategy)) { // 1st order least squares. Quality: POOR. // Frequently underfits the touch data especially when the finger accelerates @@ -352,9 +357,6 @@ bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const { // --- LeastSquaresVelocityTrackerStrategy --- -const nsecs_t LeastSquaresVelocityTrackerStrategy::HORIZON; -const uint32_t LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE; - LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy( uint32_t degree, Weighting weighting) : mDegree(degree), mWeighting(weighting) { @@ -863,10 +865,6 @@ void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, // --- LegacyVelocityTrackerStrategy --- -const nsecs_t LegacyVelocityTrackerStrategy::HORIZON; -const uint32_t LegacyVelocityTrackerStrategy::HISTORY_SIZE; -const nsecs_t LegacyVelocityTrackerStrategy::MIN_DURATION; - LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() { clear(); } @@ -979,4 +977,192 @@ bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, return true; } +// --- ImpulseVelocityTrackerStrategy --- + +ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy() { + clear(); +} + +ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() { +} + +void ImpulseVelocityTrackerStrategy::clear() { + mIndex = 0; + mMovements[0].idBits.clear(); +} + +void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { + BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); + mMovements[mIndex].idBits = remainingIdBits; +} + +void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, + const VelocityTracker::Position* positions) { + if (++mIndex == HISTORY_SIZE) { + mIndex = 0; + } + + Movement& movement = mMovements[mIndex]; + movement.eventTime = eventTime; + movement.idBits = idBits; + uint32_t count = idBits.count(); + for (uint32_t i = 0; i < count; i++) { + movement.positions[i] = positions[i]; + } +} + +/** + * Calculate the total impulse provided to the screen and the resulting velocity. + * + * The touchscreen is modeled as a physical object. + * Initial condition is discussed below, but for now suppose that v(t=0) = 0 + * + * The kinetic energy of the object at the release is E=0.5*m*v^2 + * Then vfinal = sqrt(2E/m). The goal is to calculate E. + * + * The kinetic energy at the release is equal to the total work done on the object by the finger. + * The total work W is the sum of all dW along the path. + * + * dW = F*dx, where dx is the piece of path traveled. + * Force is change of momentum over time, F = dp/dt = m dv/dt. + * Then substituting: + * dW = m (dv/dt) * dx = m * v * dv + * + * Summing along the path, we get: + * W = sum(dW) = sum(m * v * dv) = m * sum(v * dv) + * Since the mass stays constant, the equation for final velocity is: + * vfinal = sqrt(2*sum(v * dv)) + * + * Here, + * dv : change of velocity = (v[i+1]-v[i]) + * dx : change of distance = (x[i+1]-x[i]) + * dt : change of time = (t[i+1]-t[i]) + * v : instantaneous velocity = dx/dt + * + * The final formula is: + * vfinal = sqrt(2) * sqrt(sum((v[i]-v[i-1])*|v[i]|)) for all i + * The absolute value is needed to properly account for the sign. If the velocity over a + * particular segment descreases, then this indicates braking, which means that negative + * work was done. So for two positive, but decreasing, velocities, this contribution would be + * negative and will cause a smaller final velocity. + * + * Initial condition + * There are two ways to deal with initial condition: + * 1) Assume that v(0) = 0, which would mean that the screen is initially at rest. + * This is not entirely accurate. We are only taking the past X ms of touch data, where X is + * currently equal to 100. However, a touch event that created a fling probably lasted for longer + * than that, which would mean that the user has already been interacting with the touchscreen + * and it has probably already been moving. + * 2) Assume that the touchscreen has already been moving at a certain velocity, calculate this + * initial velocity and the equivalent energy, and start with this initial energy. + * Consider an example where we have the following data, consisting of 3 points: + * time: t0, t1, t2 + * x : x0, x1, x2 + * v : 0 , v1, v2 + * Here is what will happen in each of these scenarios: + * 1) By directly applying the formula above with the v(0) = 0 boundary condition, we will get + * vfinal = sqrt(2*(|v1|*(v1-v0) + |v2|*(v2-v1))). This can be simplified since v0=0 + * vfinal = sqrt(2*(|v1|*v1 + |v2|*(v2-v1))) = sqrt(2*(v1^2 + |v2|*(v2 - v1))) + * since velocity is a real number + * 2) If we treat the screen as already moving, then it must already have an energy (per mass) + * equal to 1/2*v1^2. Then the initial energy should be 1/2*v1*2, and only the second segment + * will contribute to the total kinetic energy (since we can effectively consider that v0=v1). + * This will give the following expression for the final velocity: + * vfinal = sqrt(2*(1/2*v1^2 + |v2|*(v2-v1))) + * This analysis can be generalized to an arbitrary number of samples. + * + * + * Comparing the two equations above, we see that the only mathematical difference + * is the factor of 1/2 in front of the first velocity term. + * This boundary condition would allow for the "proper" calculation of the case when all of the + * samples are equally spaced in time and distance, which should suggest a constant velocity. + * + * Note that approach 2) is sensitive to the proper ordering of the data in time, since + * the boundary condition must be applied to the oldest sample to be accurate. + */ +static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count) { + // The input should be in reversed time order (most recent sample at index i=0) + // t[i] is in nanoseconds, but due to FP arithmetic, convert to seconds inside this function + static constexpr float NANOS_PER_SECOND = 1E-9; + static constexpr float sqrt2 = 1.41421356237; + + if (count < 2) { + return 0; // if 0 or 1 points, velocity is zero + } + if (t[1] > t[0]) { // Algorithm will still work, but not perfectly + ALOGE("Samples provided to calculateImpulseVelocity in the wrong order"); + } + if (count == 2) { // if 2 points, basic linear calculation + if (t[1] == t[0]) { + ALOGE("Events have identical time stamps t=%" PRId64 ", setting velocity = 0", t[0]); + return 0; + } + return (x[1] - x[0]) / (NANOS_PER_SECOND * (t[1] - t[0])); + } + // Guaranteed to have at least 3 points here + float work = 0; + float vprev, vcurr; // v[i-1] and v[i], respectively + vprev = 0; + for (size_t i = count - 1; i > 0 ; i--) { // start with the oldest sample and go forward in time + if (t[i] == t[i-1]) { + ALOGE("Events have identical time stamps t=%" PRId64 ", skipping sample", t[i]); + continue; + } + vcurr = (x[i] - x[i-1]) / (NANOS_PER_SECOND * (t[i] - t[i-1])); + work += (vcurr - vprev) * fabsf(vcurr); + if (i == count - 1) { + work *= 0.5; // initial condition, case 2) above + } + vprev = vcurr; + } + return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2; +} + +bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, + VelocityTracker::Estimator* outEstimator) const { + outEstimator->clear(); + + // Iterate over movement samples in reverse time order and collect samples. + float x[HISTORY_SIZE]; + float y[HISTORY_SIZE]; + nsecs_t time[HISTORY_SIZE]; + size_t m = 0; // number of points that will be used for fitting + size_t index = mIndex; + const Movement& newestMovement = mMovements[mIndex]; + do { + const Movement& movement = mMovements[index]; + if (!movement.idBits.hasBit(id)) { + break; + } + + nsecs_t age = newestMovement.eventTime - movement.eventTime; + if (age > HORIZON) { + break; + } + + const VelocityTracker::Position& position = movement.getPosition(id); + x[m] = position.x; + y[m] = position.y; + time[m] = movement.eventTime; + index = (index == 0 ? HISTORY_SIZE : index) - 1; + } while (++m < HISTORY_SIZE); + + if (m == 0) { + return false; // no data + } + outEstimator->xCoeff[0] = 0; + outEstimator->yCoeff[0] = 0; + outEstimator->xCoeff[1] = calculateImpulseVelocity(time, x, m); + outEstimator->yCoeff[1] = calculateImpulseVelocity(time, y, m); + outEstimator->xCoeff[2] = 0; + outEstimator->yCoeff[2] = 0; + outEstimator->time = newestMovement.eventTime; + outEstimator->degree = 2; // similar results to 2nd degree fit + outEstimator->confidence = 1; +#if DEBUG_STRATEGY + ALOGD("velocity: (%f, %f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); +#endif + return true; +} + } // namespace android -- GitLab From 0aeec07971d86c635412c672bd69d91f589a0cd9 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 12 Jun 2017 15:01:41 +0100 Subject: [PATCH 005/704] Fix resampling logic for duplicate events. When events with identical coordinates are reported by the input driver, resampling can lead to false change of direction due to extrapolation. The added logic will compare the current event to the previous event, and will use the previously resampled values for the new event if the raw (as reported by the driver) coordinates of the two events match. This commit makes events with identical coordinates possible, so it must be submitted together with the new impulse-based VelocityTracker strategy commit. The currently used 2nd degree polynomical unweighted least squares strategy cannot handle consecutive events with identical coordinates. Bug: 35412046 Test: Recorded bad scroll event on swordfish, and replayed the event to reproduce this bug. To twitch is no longer observed. Also tested common usecase scenarios on sailfish, no regressions observed. Change-Id: Icb5cf6c76959f3514b8b94c09e38cc5434f31b23 --- include/input/InputTransport.h | 31 ++++++++++++---- libs/input/InputTransport.cpp | 68 +++++++++++++++++++--------------- 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index efa1ffbfee..0dd15b1221 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -370,14 +370,14 @@ private: int32_t idToIndex[MAX_POINTER_ID + 1]; PointerCoords pointers[MAX_POINTERS]; - void initializeFrom(const InputMessage* msg) { - eventTime = msg->body.motion.eventTime; + void initializeFrom(const InputMessage& msg) { + eventTime = msg.body.motion.eventTime; idBits.clear(); - for (uint32_t i = 0; i < msg->body.motion.pointerCount; i++) { - uint32_t id = msg->body.motion.pointers[i].properties.id; + for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) { + uint32_t id = msg.body.motion.pointers[i].properties.id; idBits.markBit(id); idToIndex[id] = i; - pointers[i].copyFrom(msg->body.motion.pointers[i].coords); + pointers[i].copyFrom(msg.body.motion.pointers[i].coords); } } @@ -402,7 +402,7 @@ private: lastResample.idBits.clear(); } - void addHistory(const InputMessage* msg) { + void addHistory(const InputMessage& msg) { historyCurrent ^= 1; if (historySize < 2) { historySize += 1; @@ -413,6 +413,21 @@ private: const History* getHistory(size_t index) const { return &history[(historyCurrent + index) & 1]; } + + bool recentCoordinatesAreIdentical(uint32_t id) const { + // Return true if the two most recently received "raw" coordinates are identical + if (historySize < 2) { + return false; + } + float currentX = getHistory(0)->getPointerById(id).getX(); + float currentY = getHistory(0)->getPointerById(id).getY(); + float previousX = getHistory(1)->getPointerById(id).getX(); + float previousY = getHistory(1)->getPointerById(id).getY(); + if (currentX == previousX && currentY == previousY) { + return true; + } + return false; + } }; Vector mTouchStates; @@ -432,8 +447,8 @@ private: Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent, int32_t* displayId); - void updateTouchState(InputMessage* msg); - void rewriteMessage(const TouchState& state, InputMessage* msg); + void updateTouchState(InputMessage& msg); + bool rewriteMessage(const TouchState& state, InputMessage& msg); void resampleTouchState(nsecs_t frameTime, MotionEvent* event, const InputMessage *next); diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index d5c592752e..d8dc9575ba 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -496,7 +496,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, MotionEvent* motionEvent = factory->createMotionEvent(); if (! motionEvent) return NO_MEMORY; - updateTouchState(&mMsg); + updateTouchState(mMsg); initializeMotionEvent(motionEvent, &mMsg); *outSeq = mMsg.body.motion.seq; *outEvent = motionEvent; @@ -564,7 +564,7 @@ status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory, uint32_t chain = 0; for (size_t i = 0; i < count; i++) { InputMessage& msg = batch.samples.editItemAt(i); - updateTouchState(&msg); + updateTouchState(msg); if (i) { SeqChain seqChain; seqChain.seq = msg.body.motion.seq; @@ -584,20 +584,19 @@ status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory, return OK; } -void InputConsumer::updateTouchState(InputMessage* msg) { +void InputConsumer::updateTouchState(InputMessage& msg) { if (!mResampleTouch || - !(msg->body.motion.source & AINPUT_SOURCE_CLASS_POINTER)) { + !(msg.body.motion.source & AINPUT_SOURCE_CLASS_POINTER)) { return; } - int32_t deviceId = msg->body.motion.deviceId; - int32_t source = msg->body.motion.source; - nsecs_t eventTime = msg->body.motion.eventTime; + int32_t deviceId = msg.body.motion.deviceId; + int32_t source = msg.body.motion.source; // Update the touch state history to incorporate the new input message. // If the message is in the past relative to the most recently produced resampled // touch, then use the resampled time and coordinates instead. - switch (msg->body.motion.action & AMOTION_EVENT_ACTION_MASK) { + switch (msg.body.motion.action & AMOTION_EVENT_ACTION_MASK) { case AMOTION_EVENT_ACTION_DOWN: { ssize_t index = findTouchState(deviceId, source); if (index < 0) { @@ -615,9 +614,8 @@ void InputConsumer::updateTouchState(InputMessage* msg) { if (index >= 0) { TouchState& touchState = mTouchStates.editItemAt(index); touchState.addHistory(msg); - if (eventTime < touchState.lastResample.eventTime) { - rewriteMessage(touchState, msg); - } else { + bool messageRewritten = rewriteMessage(touchState, msg); + if (!messageRewritten) { touchState.lastResample.idBits.clear(); } } @@ -628,7 +626,7 @@ void InputConsumer::updateTouchState(InputMessage* msg) { ssize_t index = findTouchState(deviceId, source); if (index >= 0) { TouchState& touchState = mTouchStates.editItemAt(index); - touchState.lastResample.idBits.clearBit(msg->body.motion.getActionId()); + touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId()); rewriteMessage(touchState, msg); } break; @@ -639,7 +637,7 @@ void InputConsumer::updateTouchState(InputMessage* msg) { if (index >= 0) { TouchState& touchState = mTouchStates.editItemAt(index); rewriteMessage(touchState, msg); - touchState.lastResample.idBits.clearBit(msg->body.motion.getActionId()); + touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId()); } break; } @@ -666,23 +664,28 @@ void InputConsumer::updateTouchState(InputMessage* msg) { } } -void InputConsumer::rewriteMessage(const TouchState& state, InputMessage* msg) { - for (uint32_t i = 0; i < msg->body.motion.pointerCount; i++) { - uint32_t id = msg->body.motion.pointers[i].properties.id; +bool InputConsumer::rewriteMessage(const TouchState& state, InputMessage& msg) { + bool messageRewritten = false; + nsecs_t eventTime = msg.body.motion.eventTime; + for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) { + uint32_t id = msg.body.motion.pointers[i].properties.id; if (state.lastResample.idBits.hasBit(id)) { - PointerCoords& msgCoords = msg->body.motion.pointers[i].coords; + PointerCoords& msgCoords = msg.body.motion.pointers[i].coords; const PointerCoords& resampleCoords = state.lastResample.getPointerById(id); + if (eventTime < state.lastResample.eventTime || + state.recentCoordinatesAreIdentical(id)) { + msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX()); + msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY()); #if DEBUG_RESAMPLING - ALOGD("[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id, - resampleCoords.getAxisValue(AMOTION_EVENT_AXIS_X), - resampleCoords.getAxisValue(AMOTION_EVENT_AXIS_Y), - msgCoords.getAxisValue(AMOTION_EVENT_AXIS_X), - msgCoords.getAxisValue(AMOTION_EVENT_AXIS_Y)); + ALOGD("[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id, + resampleCoords.getX(), resampleCoords.getY(), + msgCoords.getX(), msgCoords.getY()); #endif - msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX()); - msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY()); + messageRewritten = true; + } } } + return messageRewritten; } void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, @@ -710,6 +713,7 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, } // Ensure that the current sample has all of the pointers that need to be reported. + // Also ensure that the past two "real" touch events do not contain duplicate coordinates const History* current = touchState.getHistory(0); size_t pointerCount = event->getPointerCount(); for (size_t i = 0; i < pointerCount; i++) { @@ -717,6 +721,12 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, if (!current->idBits.hasBit(id)) { #if DEBUG_RESAMPLING ALOGD("Not resampled, missing id %d", id); +#endif + return; + } + if (touchState.recentCoordinatesAreIdentical(id)) { +#if DEBUG_RESAMPLING + ALOGD("Not resampled, past two historical events have duplicate coordinates"); #endif return; } @@ -729,12 +739,12 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, if (next) { // Interpolate between current sample and future sample. // So current->eventTime <= sampleTime <= future.eventTime. - future.initializeFrom(next); + future.initializeFrom(*next); other = &future; nsecs_t delta = future.eventTime - current->eventTime; if (delta < RESAMPLE_MIN_DELTA) { #if DEBUG_RESAMPLING - ALOGD("Not resampled, delta time is too small: %lld ns.", delta); + ALOGD("Not resampled, delta time is too small: %" PRId64 " ns.", delta); #endif return; } @@ -746,12 +756,12 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, nsecs_t delta = current->eventTime - other->eventTime; if (delta < RESAMPLE_MIN_DELTA) { #if DEBUG_RESAMPLING - ALOGD("Not resampled, delta time is too small: %lld ns.", delta); + ALOGD("Not resampled, delta time is too small: %" PRId64 " ns.", delta); #endif return; } else if (delta > RESAMPLE_MAX_DELTA) { #if DEBUG_RESAMPLING - ALOGD("Not resampled, delta time is too large: %lld ns.", delta); + ALOGD("Not resampled, delta time is too large: %" PRId64 " ns.", delta); #endif return; } @@ -759,7 +769,7 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, if (sampleTime > maxPredict) { #if DEBUG_RESAMPLING ALOGD("Sample time is too far in the future, adjusting prediction " - "from %lld to %lld ns.", + "from %" PRId64 " to %" PRId64 " ns.", sampleTime - current->eventTime, maxPredict - current->eventTime); #endif sampleTime = maxPredict; -- GitLab From 70b9838aed3ae09eb3b8f47c51c939fea3d9e87d Mon Sep 17 00:00:00 2001 From: Peng Xu Date: Mon, 7 Aug 2017 14:09:11 -0700 Subject: [PATCH 006/704] Clarify sensor NDK struct has to be backward compatible Bug: 35842870 Test: compiles Change-Id: I7ee89f88d09b2204b61cfd090a29c80a064191e9 --- include/android/sensor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/android/sensor.h b/include/android/sensor.h index 97b4a2ae60..43b991a24a 100644 --- a/include/android/sensor.h +++ b/include/android/sensor.h @@ -197,7 +197,7 @@ enum { * A sensor event. */ -/* NOTE: Must match hardware/sensors.h */ +/* NOTE: changes to these structs have to be backward compatible */ typedef struct ASensorVector { union { float v[3]; @@ -259,7 +259,7 @@ typedef struct { }; } AAdditionalInfoEvent; -/* NOTE: Must match hardware/sensors.h */ +/* NOTE: changes to this struct has to be backward compatible */ typedef struct ASensorEvent { int32_t version; /* sizeof(struct ASensorEvent) */ int32_t sensor; -- GitLab From 94e35b93537c7f9da018688ac8a8ba87f4689915 Mon Sep 17 00:00:00 2001 From: Steven Thomas Date: Wed, 26 Jul 2017 18:48:28 -0700 Subject: [PATCH 007/704] Use a separate hwcomposer hidl instance for vr flinger Improve robustness of vr flinger <--> surface flinger switching by having vr flinger use a separate hardware composer hidl instance instead of sharing the instance with surface flinger. Sharing the hardware composer instance has proven to be error prone, with situations where both the vr flinger thread and surface flinger main thread would write to the composer at the same time, causing hard to diagnose crashes (b/62925812). Instead of sharing the hardware composer instance, when switching to vr flinger we now delete the existing instance, create a new instance directed to the vr hardware composer shim, and vr flinger creates its own composer instance connected to the real hardware composer. By creating a separate composer instance for vr flinger, crashes like the ones found in b/62925812 are no longer impossible. Most of the changes in this commit are related to enabling surface flinger to delete HWComposer instances cleanly. In particular: - Previously the hardware composer callbacks (which come in on a hwbinder thread) would land in HWC2::Device and bubble up to the SurfaceFlinger object. But with the new behavior the HWC2::Device might be dead or in the process of being destroyed, so instead we have SurfaceFlinger receive the composer callbacks directly, and forward them to HWComposer and HWC2::Device. We include a composer id field in the callbacks so surface flinger can ignore stale callbacks from dead composer instances. - Object ownership for HWC2::Display and HWC2::Layer was shared by passing around shared_ptrs to these objects. This was problematic because they referenced and used the HWC2::Device, which can now be destroyed when switching to vr flinger. Simplify the ownership model by having HWC2::Device own (via unique_ptr<>) instances of HWC2::Display, which owns (again via unique_ptr<>) instances of HWC2::Layer. In cases where we previously passed std::shared_ptr<> to HWC2::Display or HWC2::Layer, instead pass non-owning HWC2::Display* and HWC2::Layer* pointers. This ensures clean composer instance teardown with no stale references to the deleted HWC2::Device. - When the hardware composer instance is destroyed and the HWC2::Layers are removed, notify the android::Layer via a callback, so it can remove the HWC2::Layer from its internal table of hardware composer layers. This removes the burden to explicitly clear out all hardware composer layers when switching to vr flinger, which has been a source of bugs. - We were missing an mStateLock lock in SurfaceFlinger::setVsyncEnabled(), which was necessary to ensure we were setting vsync on the correct hardware composer instance. Once that lock was added, surface flinger would sometimes deadlock when transitioning to vr flinger, because the surface flinger main thread would acquire mStateLock and then EventControlThread::mMutex, whereas the event control thread would acquire the locks in the opposite order. The changes in EventControlThread.cpp are to ensure it doesn't hold a lock on EventControlThread::mMutex while calling setVsyncEnabled(), to avoid the deadlock. I found that without a composer callback registered in vr flinger the vsync_event file wasn't getting vsync timestamps written, so vr flinger would get stuck in an infinite loop trying to parse a vsync timestamp. Since we need to have a callback anyway I changed the code in hardware_composer.cpp to get the vsync timestamp from the callback, as surface flinger does. I confirmed the timestamps are the same with either method, and this lets us remove some extra code for extracting the vsync timestamp that (probably) wasn't compatible with all devices we want to run on anyway. I also added a timeout to the vysnc wait so we'll see an error message in the log if we fail to wait for vsync, instead of looping forever. Bug: 62925812 Test: - Confirmed surface flinger <--> vr flinger switching is robust by switching devices on and off hundreds of times and observing no hardware composer related issues, surface flinger crashes, or hardware composer service crashes. - Confirmed 2d in vr works as before by going through the OOBE flow on a standalone. This also exercises virtual display creation and usage through surface flinger. - Added logs to confirm perfect layer/display cleanup when destroying hardware composer instances. - Tested normal 2d phone usage to confirm basic layer create/destroy functionality works as before. - Monitored surface flinger file descriptor usage across dozens of surface flinger <--> vr flinger transitions and observed no file descriptor leaks. - Confirmed the HWC1 code path still compiles. - Ran the surface flinger tests and confirmed there are no new test failures. - Ran the hardware composer hidl in passthrough mode on a Marlin and confirmed it works. - Ran CTS tests for virtual displays and confirmed they all pass. - Tested Android Auto and confirmed basic graphics functionality still works. Change-Id: I17dc0e060bfb5cb447ffbaa573b279fc6d2d8bd1 --- libs/vr/libvrflinger/display_service.cpp | 10 +- libs/vr/libvrflinger/display_service.h | 3 - libs/vr/libvrflinger/hardware_composer.cpp | 352 +++++------ libs/vr/libvrflinger/hardware_composer.h | 110 ++-- libs/vr/libvrflinger/include/dvr/vr_flinger.h | 3 - libs/vr/libvrflinger/vr_flinger.cpp | 4 - services/surfaceflinger/DisplayDevice.cpp | 6 +- .../DisplayHardware/ComposerHal.cpp | 4 + .../DisplayHardware/ComposerHal.h | 5 + .../surfaceflinger/DisplayHardware/HWC2.cpp | 549 ++++++++---------- .../surfaceflinger/DisplayHardware/HWC2.h | 124 ++-- .../DisplayHardware/HWComposer.cpp | 131 ++--- .../DisplayHardware/HWComposer.h | 39 +- .../surfaceflinger/EventControlThread.cpp | 43 +- services/surfaceflinger/Layer.cpp | 50 +- services/surfaceflinger/Layer.h | 34 +- .../RenderEngine/RenderEngine.cpp | 33 +- .../RenderEngine/RenderEngine.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 294 +++++----- services/surfaceflinger/SurfaceFlinger.h | 60 +- .../surfaceflinger/SurfaceFlinger_hwc1.cpp | 4 +- 21 files changed, 844 insertions(+), 1016 deletions(-) diff --git a/libs/vr/libvrflinger/display_service.cpp b/libs/vr/libvrflinger/display_service.cpp index 733edc659c..f350762e79 100644 --- a/libs/vr/libvrflinger/display_service.cpp +++ b/libs/vr/libvrflinger/display_service.cpp @@ -40,10 +40,8 @@ namespace dvr { DisplayService::DisplayService(Hwc2::Composer* hidl, RequestDisplayCallback request_display_callback) : BASE("DisplayService", - Endpoint::Create(display::DisplayProtocol::kClientPath)), - hardware_composer_(hidl, request_display_callback), - request_display_callback_(request_display_callback) { - hardware_composer_.Initialize(); + Endpoint::Create(display::DisplayProtocol::kClientPath)) { + hardware_composer_.Initialize(hidl, request_display_callback); } bool DisplayService::IsInitialized() const { @@ -398,10 +396,6 @@ pdx::Status DisplayService::DeleteGlobalBuffer(DvrGlobalBufferKey key) { return {0}; } -void DisplayService::OnHardwareComposerRefresh() { - hardware_composer_.OnHardwareComposerRefresh(); -} - void DisplayService::SetDisplayConfigurationUpdateNotifier( DisplayConfigurationUpdateNotifier update_notifier) { update_notifier_ = update_notifier; diff --git a/libs/vr/libvrflinger/display_service.h b/libs/vr/libvrflinger/display_service.h index 6efe264b09..55e33ab852 100644 --- a/libs/vr/libvrflinger/display_service.h +++ b/libs/vr/libvrflinger/display_service.h @@ -72,8 +72,6 @@ class DisplayService : public pdx::ServiceBase { void GrantDisplayOwnership() { hardware_composer_.Enable(); } void SeizeDisplayOwnership() { hardware_composer_.Disable(); } - void OnHardwareComposerRefresh(); - private: friend BASE; friend DisplaySurface; @@ -119,7 +117,6 @@ class DisplayService : public pdx::ServiceBase { pdx::Status HandleSurfaceMessage(pdx::Message& message); HardwareComposer hardware_composer_; - RequestDisplayCallback request_display_callback_; EpollEventDispatcher dispatcher_; DisplayConfigurationUpdateNotifier update_notifier_; diff --git a/libs/vr/libvrflinger/hardware_composer.cpp b/libs/vr/libvrflinger/hardware_composer.cpp index 11c137063e..f9a5dcd987 100644 --- a/libs/vr/libvrflinger/hardware_composer.cpp +++ b/libs/vr/libvrflinger/hardware_composer.cpp @@ -28,6 +28,8 @@ #include #include +using android::hardware::Return; +using android::hardware::Void; using android::pdx::LocalHandle; using android::pdx::rpc::EmptyVariant; using android::pdx::rpc::IfAnyOf; @@ -42,9 +44,6 @@ namespace { const char kBacklightBrightnessSysFile[] = "/sys/class/leds/lcd-backlight/brightness"; -const char kPrimaryDisplayVSyncEventFile[] = - "/sys/class/graphics/fb0/vsync_event"; - const char kPrimaryDisplayWaitPPEventFile[] = "/sys/class/graphics/fb0/wait_pp"; const char kDvrPerformanceProperty[] = "sys.dvr.performance"; @@ -86,22 +85,11 @@ bool SetThreadPolicy(const std::string& scheduler_class, } // anonymous namespace -// Layer static data. -Hwc2::Composer* Layer::hwc2_hidl_; -const HWCDisplayMetrics* Layer::display_metrics_; - // HardwareComposer static data; constexpr size_t HardwareComposer::kMaxHardwareLayers; HardwareComposer::HardwareComposer() - : HardwareComposer(nullptr, RequestDisplayCallback()) {} - -HardwareComposer::HardwareComposer( - Hwc2::Composer* hwc2_hidl, RequestDisplayCallback request_display_callback) - : initialized_(false), - hwc2_hidl_(hwc2_hidl), - request_display_callback_(request_display_callback), - callbacks_(new ComposerCallback) {} + : initialized_(false), request_display_callback_(nullptr) {} HardwareComposer::~HardwareComposer(void) { UpdatePostThreadState(PostThreadState::Quit, true); @@ -109,16 +97,19 @@ HardwareComposer::~HardwareComposer(void) { post_thread_.join(); } -bool HardwareComposer::Initialize() { +bool HardwareComposer::Initialize( + Hwc2::Composer* hidl, RequestDisplayCallback request_display_callback) { if (initialized_) { ALOGE("HardwareComposer::Initialize: already initialized."); return false; } + request_display_callback_ = request_display_callback; + HWC::Error error = HWC::Error::None; Hwc2::Config config; - error = hwc2_hidl_->getActiveConfig(HWC_DISPLAY_PRIMARY, &config); + error = hidl->getActiveConfig(HWC_DISPLAY_PRIMARY, &config); if (error != HWC::Error::None) { ALOGE("HardwareComposer: Failed to get current display config : %d", @@ -126,8 +117,8 @@ bool HardwareComposer::Initialize() { return false; } - error = - GetDisplayMetrics(HWC_DISPLAY_PRIMARY, config, &native_display_metrics_); + error = GetDisplayMetrics(hidl, HWC_DISPLAY_PRIMARY, config, + &native_display_metrics_); if (error != HWC::Error::None) { ALOGE( @@ -149,9 +140,6 @@ bool HardwareComposer::Initialize() { display_transform_ = HWC_TRANSFORM_NONE; display_metrics_ = native_display_metrics_; - // Pass hwc instance and metrics to setup globals for Layer. - Layer::InitializeGlobals(hwc2_hidl_, &native_display_metrics_); - post_thread_event_fd_.Reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)); LOG_ALWAYS_FATAL_IF( !post_thread_event_fd_, @@ -210,15 +198,11 @@ void HardwareComposer::UpdatePostThreadState(PostThreadStateType state, } void HardwareComposer::OnPostThreadResumed() { - hwc2_hidl_->resetCommands(); + hidl_.reset(new Hwc2::Composer("default")); + hidl_callback_ = new ComposerCallback; + hidl_->registerCallback(hidl_callback_); - // HIDL HWC seems to have an internal race condition. If we submit a frame too - // soon after turning on VSync we don't get any VSync signals. Give poor HWC - // implementations a chance to enable VSync before we continue. - EnableVsync(false); - std::this_thread::sleep_for(100ms); EnableVsync(true); - std::this_thread::sleep_for(100ms); // TODO(skiazyk): We need to do something about accessing this directly, // supposedly there is a backlight service on the way. @@ -240,9 +224,12 @@ void HardwareComposer::OnPostThreadPaused() { } active_layer_count_ = 0; - EnableVsync(false); + if (hidl_) { + EnableVsync(false); + } - hwc2_hidl_->resetCommands(); + hidl_callback_ = nullptr; + hidl_.reset(nullptr); // Trigger target-specific performance mode change. property_set(kDvrPerformanceProperty, "idle"); @@ -252,21 +239,21 @@ HWC::Error HardwareComposer::Validate(hwc2_display_t display) { uint32_t num_types; uint32_t num_requests; HWC::Error error = - hwc2_hidl_->validateDisplay(display, &num_types, &num_requests); + hidl_->validateDisplay(display, &num_types, &num_requests); if (error == HWC2_ERROR_HAS_CHANGES) { // TODO(skiazyk): We might need to inspect the requested changes first, but // so far it seems like we shouldn't ever hit a bad state. // error = hwc2_funcs_.accept_display_changes_fn_(hardware_composer_device_, // display); - error = hwc2_hidl_->acceptDisplayChanges(display); + error = hidl_->acceptDisplayChanges(display); } return error; } -int32_t HardwareComposer::EnableVsync(bool enabled) { - return (int32_t)hwc2_hidl_->setVsyncEnabled( +HWC::Error HardwareComposer::EnableVsync(bool enabled) { + return hidl_->setVsyncEnabled( HWC_DISPLAY_PRIMARY, (Hwc2::IComposerClient::Vsync)(enabled ? HWC2_VSYNC_ENABLE : HWC2_VSYNC_DISABLE)); @@ -274,7 +261,7 @@ int32_t HardwareComposer::EnableVsync(bool enabled) { HWC::Error HardwareComposer::Present(hwc2_display_t display) { int32_t present_fence; - HWC::Error error = hwc2_hidl_->presentDisplay(display, &present_fence); + HWC::Error error = hidl_->presentDisplay(display, &present_fence); // According to the documentation, this fence is signaled at the time of // vsync/DMA for physical displays. @@ -288,20 +275,21 @@ HWC::Error HardwareComposer::Present(hwc2_display_t display) { return error; } -HWC::Error HardwareComposer::GetDisplayAttribute(hwc2_display_t display, +HWC::Error HardwareComposer::GetDisplayAttribute(Hwc2::Composer* hidl, + hwc2_display_t display, hwc2_config_t config, hwc2_attribute_t attribute, int32_t* out_value) const { - return hwc2_hidl_->getDisplayAttribute( + return hidl->getDisplayAttribute( display, config, (Hwc2::IComposerClient::Attribute)attribute, out_value); } HWC::Error HardwareComposer::GetDisplayMetrics( - hwc2_display_t display, hwc2_config_t config, + Hwc2::Composer* hidl, hwc2_display_t display, hwc2_config_t config, HWCDisplayMetrics* out_metrics) const { HWC::Error error; - error = GetDisplayAttribute(display, config, HWC2_ATTRIBUTE_WIDTH, + error = GetDisplayAttribute(hidl, display, config, HWC2_ATTRIBUTE_WIDTH, &out_metrics->width); if (error != HWC::Error::None) { ALOGE( @@ -310,7 +298,7 @@ HWC::Error HardwareComposer::GetDisplayMetrics( return error; } - error = GetDisplayAttribute(display, config, HWC2_ATTRIBUTE_HEIGHT, + error = GetDisplayAttribute(hidl, display, config, HWC2_ATTRIBUTE_HEIGHT, &out_metrics->height); if (error != HWC::Error::None) { ALOGE( @@ -319,7 +307,8 @@ HWC::Error HardwareComposer::GetDisplayMetrics( return error; } - error = GetDisplayAttribute(display, config, HWC2_ATTRIBUTE_VSYNC_PERIOD, + error = GetDisplayAttribute(hidl, display, config, + HWC2_ATTRIBUTE_VSYNC_PERIOD, &out_metrics->vsync_period_ns); if (error != HWC::Error::None) { ALOGE( @@ -328,7 +317,7 @@ HWC::Error HardwareComposer::GetDisplayMetrics( return error; } - error = GetDisplayAttribute(display, config, HWC2_ATTRIBUTE_DPI_X, + error = GetDisplayAttribute(hidl, display, config, HWC2_ATTRIBUTE_DPI_X, &out_metrics->dpi.x); if (error != HWC::Error::None) { ALOGE( @@ -337,7 +326,7 @@ HWC::Error HardwareComposer::GetDisplayMetrics( return error; } - error = GetDisplayAttribute(display, config, HWC2_ATTRIBUTE_DPI_Y, + error = GetDisplayAttribute(hidl, display, config, HWC2_ATTRIBUTE_DPI_Y, &out_metrics->dpi.y); if (error != HWC::Error::None) { ALOGE( @@ -374,7 +363,7 @@ std::string HardwareComposer::Dump() { if (post_thread_resumed_) { stream << "Hardware Composer Debug Info:" << std::endl; - stream << hwc2_hidl_->dumpDebugInfo(); + stream << hidl_->dumpDebugInfo(); } return stream.str(); @@ -446,8 +435,8 @@ void HardwareComposer::PostLayers() { std::vector out_layers; std::vector out_fences; - error = hwc2_hidl_->getReleaseFences(HWC_DISPLAY_PRIMARY, &out_layers, - &out_fences); + error = hidl_->getReleaseFences(HWC_DISPLAY_PRIMARY, &out_layers, + &out_fences); ALOGE_IF(error != HWC::Error::None, "HardwareComposer::PostLayers: Failed to get release fences: %s", error.to_string().c_str()); @@ -546,7 +535,7 @@ void HardwareComposer::UpdateConfigBuffer() { } int HardwareComposer::PostThreadPollInterruptible( - const pdx::LocalHandle& event_fd, int requested_events) { + const pdx::LocalHandle& event_fd, int requested_events, int timeout_ms) { pollfd pfd[2] = { { .fd = event_fd.Get(), @@ -561,7 +550,7 @@ int HardwareComposer::PostThreadPollInterruptible( }; int ret, error; do { - ret = poll(pfd, 2, -1); + ret = poll(pfd, 2, timeout_ms); error = errno; ALOGW_IF(ret < 0, "HardwareComposer::PostThreadPollInterruptible: Error during " @@ -571,6 +560,8 @@ int HardwareComposer::PostThreadPollInterruptible( if (ret < 0) { return -error; + } else if (ret == 0) { + return -ETIMEDOUT; } else if (pfd[0].revents != 0) { return 0; } else if (pfd[1].revents != 0) { @@ -623,114 +614,17 @@ int HardwareComposer::ReadWaitPPState() { } } -// Reads the timestamp of the last vsync from the display driver. -// TODO(eieio): This is pretty driver specific, this should be moved to a -// separate class eventually. -int HardwareComposer::ReadVSyncTimestamp(int64_t* timestamp) { - const int event_fd = primary_display_vsync_event_fd_.Get(); - int ret, error; - - // The driver returns data in the form "VSYNC=". - std::array data; - data.fill('\0'); - - // Seek back to the beginning of the event file. - ret = lseek(event_fd, 0, SEEK_SET); - if (ret < 0) { - error = errno; - ALOGE( - "HardwareComposer::ReadVSyncTimestamp: Failed to seek vsync event fd: " - "%s", - strerror(error)); - return -error; - } - - // Read the vsync event timestamp. - ret = read(event_fd, data.data(), data.size()); - if (ret < 0) { - error = errno; - ALOGE_IF( - error != EAGAIN, - "HardwareComposer::ReadVSyncTimestamp: Error while reading timestamp: " - "%s", - strerror(error)); - return -error; - } - - ret = sscanf(data.data(), "VSYNC=%" PRIu64, - reinterpret_cast(timestamp)); - if (ret < 0) { - error = errno; - ALOGE( - "HardwareComposer::ReadVSyncTimestamp: Error while parsing timestamp: " - "%s", - strerror(error)); - return -error; - } - - return 0; -} - -// Blocks until the next vsync event is signaled by the display driver. -// TODO(eieio): This is pretty driver specific, this should be moved to a -// separate class eventually. -int HardwareComposer::BlockUntilVSync() { - // Vsync is signaled by POLLPRI on the fb vsync node. - return PostThreadPollInterruptible(primary_display_vsync_event_fd_, POLLPRI); -} - // Waits for the next vsync and returns the timestamp of the vsync event. If // vsync already passed since the last call, returns the latest vsync timestamp -// instead of blocking. This method updates the last_vsync_timeout_ in the -// process. -// -// TODO(eieio): This is pretty driver specific, this should be moved to a -// separate class eventually. +// instead of blocking. int HardwareComposer::WaitForVSync(int64_t* timestamp) { - int error; - - // Get the current timestamp and decide what to do. - while (true) { - int64_t current_vsync_timestamp; - error = ReadVSyncTimestamp(¤t_vsync_timestamp); - if (error < 0 && error != -EAGAIN) - return error; - - if (error == -EAGAIN) { - // Vsync was turned off, wait for the next vsync event. - error = BlockUntilVSync(); - if (error < 0 || error == kPostThreadInterrupted) - return error; - - // Try again to get the timestamp for this new vsync interval. - continue; - } - - // Check that we advanced to a later vsync interval. - if (TimestampGT(current_vsync_timestamp, last_vsync_timestamp_)) { - *timestamp = last_vsync_timestamp_ = current_vsync_timestamp; - return 0; - } - - // See how close we are to the next expected vsync. If we're within 1ms, - // sleep for 1ms and try again. - const int64_t ns_per_frame = display_metrics_.vsync_period_ns; - const int64_t threshold_ns = 1000000; // 1ms - - const int64_t next_vsync_est = last_vsync_timestamp_ + ns_per_frame; - const int64_t distance_to_vsync_est = next_vsync_est - GetSystemClockNs(); - - if (distance_to_vsync_est > threshold_ns) { - // Wait for vsync event notification. - error = BlockUntilVSync(); - if (error < 0 || error == kPostThreadInterrupted) - return error; - } else { - // Sleep for a short time (1 millisecond) before retrying. - error = SleepUntil(GetSystemClockNs() + threshold_ns); - if (error < 0 || error == kPostThreadInterrupted) - return error; - } + int error = PostThreadPollInterruptible( + hidl_callback_->GetVsyncEventFd(), POLLIN, /*timeout_ms*/ 1000); + if (error == kPostThreadInterrupted || error < 0) { + return error; + } else { + *timestamp = hidl_callback_->GetVsyncTime(); + return 0; } } @@ -749,7 +643,8 @@ int HardwareComposer::SleepUntil(int64_t wakeup_timestamp) { return -error; } - return PostThreadPollInterruptible(vsync_sleep_timer_fd_, POLLIN); + return PostThreadPollInterruptible( + vsync_sleep_timer_fd_, POLLIN, /*timeout_ms*/ -1); } void HardwareComposer::PostThread() { @@ -772,15 +667,6 @@ void HardwareComposer::PostThread() { strerror(errno)); #endif // ENABLE_BACKLIGHT_BRIGHTNESS - // Open the vsync event node for the primary display. - // TODO(eieio): Move this into a platform-specific class. - primary_display_vsync_event_fd_ = - LocalHandle(kPrimaryDisplayVSyncEventFile, O_RDONLY); - ALOGE_IF(!primary_display_vsync_event_fd_, - "HardwareComposer: Failed to open vsync event node for primary " - "display: %s", - strerror(errno)); - // Open the wait pingpong status node for the primary display. // TODO(eieio): Move this into a platform-specific class. primary_display_wait_pp_fd_ = @@ -951,7 +837,8 @@ bool HardwareComposer::UpdateLayerConfig() { // The bottom layer is opaque, other layers blend. HWC::BlendMode blending = layer_index == 0 ? HWC::BlendMode::None : HWC::BlendMode::Coverage; - layers_[layer_index].Setup(surfaces[layer_index], blending, + layers_[layer_index].Setup(surfaces[layer_index], native_display_metrics_, + hidl_.get(), blending, display_transform_, HWC::Composition::Device, layer_index); display_surfaces_.push_back(surfaces[layer_index]); @@ -979,50 +866,67 @@ void HardwareComposer::SetVSyncCallback(VSyncCallback callback) { vsync_callback_ = callback; } -void HardwareComposer::HwcRefresh(hwc2_callback_data_t /*data*/, - hwc2_display_t /*display*/) { - // TODO(eieio): implement invalidate callbacks. +void HardwareComposer::SetBacklightBrightness(int brightness) { + if (backlight_brightness_fd_) { + std::array text; + const int length = snprintf(text.data(), text.size(), "%d", brightness); + write(backlight_brightness_fd_.Get(), text.data(), length); + } } -void HardwareComposer::HwcVSync(hwc2_callback_data_t /*data*/, - hwc2_display_t /*display*/, - int64_t /*timestamp*/) { - ATRACE_NAME(__PRETTY_FUNCTION__); - // Intentionally empty. HWC may require a callback to be set to enable vsync - // signals. We bypass this callback thread by monitoring the vsync event - // directly, but signals still need to be enabled. +HardwareComposer::ComposerCallback::ComposerCallback() { + vsync_event_fd_.Reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)); + LOG_ALWAYS_FATAL_IF( + !vsync_event_fd_, + "Failed to create vsync event fd : %s", + strerror(errno)); } -void HardwareComposer::HwcHotplug(hwc2_callback_data_t /*callbackData*/, - hwc2_display_t /*display*/, - hwc2_connection_t /*connected*/) { - // TODO(eieio): implement display hotplug callbacks. +Return HardwareComposer::ComposerCallback::onHotplug( + Hwc2::Display /*display*/, + IComposerCallback::Connection /*conn*/) { + return Void(); } -void HardwareComposer::OnHardwareComposerRefresh() { - // TODO(steventhomas): Handle refresh. +Return HardwareComposer::ComposerCallback::onRefresh( + Hwc2::Display /*display*/) { + return hardware::Void(); } -void HardwareComposer::SetBacklightBrightness(int brightness) { - if (backlight_brightness_fd_) { - std::array text; - const int length = snprintf(text.data(), text.size(), "%d", brightness); - write(backlight_brightness_fd_.Get(), text.data(), length); +Return HardwareComposer::ComposerCallback::onVsync( + Hwc2::Display display, int64_t timestamp) { + if (display == HWC_DISPLAY_PRIMARY) { + std::lock_guard lock(vsync_mutex_); + vsync_time_ = timestamp; + int error = eventfd_write(vsync_event_fd_.Get(), 1); + LOG_ALWAYS_FATAL_IF(error != 0, "Failed writing to vsync event fd"); } + return Void(); +} + +const pdx::LocalHandle& +HardwareComposer::ComposerCallback::GetVsyncEventFd() const { + return vsync_event_fd_; } -void Layer::InitializeGlobals(Hwc2::Composer* hwc2_hidl, - const HWCDisplayMetrics* metrics) { - hwc2_hidl_ = hwc2_hidl; - display_metrics_ = metrics; +int64_t HardwareComposer::ComposerCallback::GetVsyncTime() { + std::lock_guard lock(vsync_mutex_); + eventfd_t event; + eventfd_read(vsync_event_fd_.Get(), &event); + LOG_ALWAYS_FATAL_IF(vsync_time_ < 0, + "Attempt to read vsync time before vsync event"); + int64_t return_val = vsync_time_; + vsync_time_ = -1; + return return_val; } void Layer::Reset() { - if (hwc2_hidl_ != nullptr && hardware_composer_layer_) { - hwc2_hidl_->destroyLayer(HWC_DISPLAY_PRIMARY, hardware_composer_layer_); + if (hidl_ != nullptr && hardware_composer_layer_) { + hidl_->destroyLayer(HWC_DISPLAY_PRIMARY, hardware_composer_layer_); hardware_composer_layer_ = 0; } + hidl_ = nullptr; z_order_ = 0; blending_ = HWC::BlendMode::None; transform_ = HWC::Transform::None; @@ -1034,29 +938,35 @@ void Layer::Reset() { } void Layer::Setup(const std::shared_ptr& surface, - HWC::BlendMode blending, HWC::Transform transform, - HWC::Composition composition_type, size_t z_order) { + const HWCDisplayMetrics& display_metrics, + Hwc2::Composer* hidl, HWC::BlendMode blending, + HWC::Transform transform, HWC::Composition composition_type, + size_t z_order) { Reset(); + hidl_ = hidl; z_order_ = z_order; blending_ = blending; transform_ = transform; composition_type_ = HWC::Composition::Invalid; target_composition_type_ = composition_type; source_ = SourceSurface{surface}; - CommonLayerSetup(); + CommonLayerSetup(display_metrics); } void Layer::Setup(const std::shared_ptr& buffer, - HWC::BlendMode blending, HWC::Transform transform, - HWC::Composition composition_type, size_t z_order) { + const HWCDisplayMetrics& display_metrics, + Hwc2::Composer* hidl, HWC::BlendMode blending, + HWC::Transform transform, HWC::Composition composition_type, + size_t z_order) { Reset(); + hidl_ = hidl; z_order_ = z_order; blending_ = blending; transform_ = transform; composition_type_ = HWC::Composition::Invalid; target_composition_type_ = composition_type; source_ = SourceBuffer{buffer}; - CommonLayerSetup(); + CommonLayerSetup(display_metrics); } void Layer::UpdateBuffer(const std::shared_ptr& buffer) { @@ -1076,7 +986,7 @@ IonBuffer* Layer::GetBuffer() { return source_.Visit(Visitor{}); } -void Layer::UpdateLayerSettings() { +void Layer::UpdateLayerSettings(const HWCDisplayMetrics& display_metrics) { if (!IsLayerSetup()) { ALOGE( "HardwareComposer::Layer::UpdateLayerSettings: Attempt to update " @@ -1087,7 +997,7 @@ void Layer::UpdateLayerSettings() { HWC::Error error; hwc2_display_t display = HWC_DISPLAY_PRIMARY; - error = hwc2_hidl_->setLayerCompositionType( + error = hidl_->setLayerCompositionType( display, hardware_composer_layer_, composition_type_.cast()); ALOGE_IF( @@ -1095,7 +1005,7 @@ void Layer::UpdateLayerSettings() { "Layer::UpdateLayerSettings: Error setting layer composition type: %s", error.to_string().c_str()); - error = hwc2_hidl_->setLayerBlendMode( + error = hidl_->setLayerBlendMode( display, hardware_composer_layer_, blending_.cast()); ALOGE_IF(error != HWC::Error::None, @@ -1104,41 +1014,39 @@ void Layer::UpdateLayerSettings() { // TODO(eieio): Use surface attributes or some other mechanism to control // the layer display frame. - error = hwc2_hidl_->setLayerDisplayFrame( + error = hidl_->setLayerDisplayFrame( display, hardware_composer_layer_, - {0, 0, display_metrics_->width, display_metrics_->height}); + {0, 0, display_metrics.width, display_metrics.height}); ALOGE_IF(error != HWC::Error::None, "Layer::UpdateLayerSettings: Error setting layer display frame: %s", error.to_string().c_str()); - error = hwc2_hidl_->setLayerVisibleRegion( + error = hidl_->setLayerVisibleRegion( display, hardware_composer_layer_, - {{0, 0, display_metrics_->width, display_metrics_->height}}); + {{0, 0, display_metrics.width, display_metrics.height}}); ALOGE_IF(error != HWC::Error::None, "Layer::UpdateLayerSettings: Error setting layer visible region: %s", error.to_string().c_str()); - error = - hwc2_hidl_->setLayerPlaneAlpha(display, hardware_composer_layer_, 1.0f); + error = hidl_->setLayerPlaneAlpha(display, hardware_composer_layer_, 1.0f); ALOGE_IF(error != HWC::Error::None, "Layer::UpdateLayerSettings: Error setting layer plane alpha: %s", error.to_string().c_str()); - error = - hwc2_hidl_->setLayerZOrder(display, hardware_composer_layer_, z_order_); + error = hidl_->setLayerZOrder(display, hardware_composer_layer_, z_order_); ALOGE_IF(error != HWC::Error::None, "Layer::UpdateLayerSettings: Error setting z_ order: %s", error.to_string().c_str()); } -void Layer::CommonLayerSetup() { +void Layer::CommonLayerSetup(const HWCDisplayMetrics& display_metrics) { HWC::Error error = - hwc2_hidl_->createLayer(HWC_DISPLAY_PRIMARY, &hardware_composer_layer_); + hidl_->createLayer(HWC_DISPLAY_PRIMARY, &hardware_composer_layer_); ALOGE_IF( error != HWC::Error::None, "Layer::CommonLayerSetup: Failed to create layer on primary display: %s", error.to_string().c_str()); - UpdateLayerSettings(); + UpdateLayerSettings(display_metrics); } void Layer::Prepare() { @@ -1157,12 +1065,12 @@ void Layer::Prepare() { if (!handle.get()) { if (composition_type_ == HWC::Composition::Invalid) { composition_type_ = HWC::Composition::SolidColor; - hwc2_hidl_->setLayerCompositionType( + hidl_->setLayerCompositionType( HWC_DISPLAY_PRIMARY, hardware_composer_layer_, composition_type_.cast()); Hwc2::IComposerClient::Color layer_color = {0, 0, 0, 0}; - hwc2_hidl_->setLayerColor(HWC_DISPLAY_PRIMARY, hardware_composer_layer_, - layer_color); + hidl_->setLayerColor(HWC_DISPLAY_PRIMARY, hardware_composer_layer_, + layer_color); } else { // The composition type is already set. Nothing else to do until a // buffer arrives. @@ -1170,15 +1078,15 @@ void Layer::Prepare() { } else { if (composition_type_ != target_composition_type_) { composition_type_ = target_composition_type_; - hwc2_hidl_->setLayerCompositionType( + hidl_->setLayerCompositionType( HWC_DISPLAY_PRIMARY, hardware_composer_layer_, composition_type_.cast()); } HWC::Error error{HWC::Error::None}; - error = hwc2_hidl_->setLayerBuffer(HWC_DISPLAY_PRIMARY, - hardware_composer_layer_, 0, handle, - acquire_fence_.Get()); + error = hidl_->setLayerBuffer(HWC_DISPLAY_PRIMARY, + hardware_composer_layer_, 0, handle, + acquire_fence_.Get()); ALOGE_IF(error != HWC::Error::None, "Layer::Prepare: Error setting layer buffer: %s", @@ -1187,9 +1095,9 @@ void Layer::Prepare() { if (!surface_rect_functions_applied_) { const float float_right = right; const float float_bottom = bottom; - error = hwc2_hidl_->setLayerSourceCrop(HWC_DISPLAY_PRIMARY, - hardware_composer_layer_, - {0, 0, float_right, float_bottom}); + error = hidl_->setLayerSourceCrop(HWC_DISPLAY_PRIMARY, + hardware_composer_layer_, + {0, 0, float_right, float_bottom}); ALOGE_IF(error != HWC::Error::None, "Layer::Prepare: Error setting layer source crop: %s", diff --git a/libs/vr/libvrflinger/hardware_composer.h b/libs/vr/libvrflinger/hardware_composer.h index a0c50e14d8..fc0efeeeb4 100644 --- a/libs/vr/libvrflinger/hardware_composer.h +++ b/libs/vr/libvrflinger/hardware_composer.h @@ -54,11 +54,6 @@ class Layer { public: Layer() {} - // Sets up the global state used by all Layer instances. This must be called - // before using any Layer methods. - static void InitializeGlobals(Hwc2::Composer* hwc2_hidl, - const HWCDisplayMetrics* metrics); - // Releases any shared pointers and fence handles held by this instance. void Reset(); @@ -72,6 +67,7 @@ class Layer { // HWC_FRAMEBUFFER_TARGET (unless you know what you are doing). // |index| is the index of this surface in the DirectDisplaySurface array. void Setup(const std::shared_ptr& surface, + const HWCDisplayMetrics& display_metrics, Hwc2::Composer* hidl, HWC::BlendMode blending, HWC::Transform transform, HWC::Composition composition_type, size_t z_roder); @@ -83,9 +79,10 @@ class Layer { // |transform| receives HWC_TRANSFORM_* values. // |composition_type| receives either HWC_FRAMEBUFFER for most layers or // HWC_FRAMEBUFFER_TARGET (unless you know what you are doing). - void Setup(const std::shared_ptr& buffer, HWC::BlendMode blending, - HWC::Transform transform, HWC::Composition composition_type, - size_t z_order); + void Setup(const std::shared_ptr& buffer, + const HWCDisplayMetrics& display_metrics, Hwc2::Composer* hidl, + HWC::BlendMode blending, HWC::Transform transform, + HWC::Composition composition_type, size_t z_order); // Layers that use a direct IonBuffer should call this each frame to update // which buffer will be used for the next PostLayers. @@ -121,7 +118,7 @@ class Layer { bool IsLayerSetup() const { return !source_.empty(); } // Applies all of the settings to this layer using the hwc functions - void UpdateLayerSettings(); + void UpdateLayerSettings(const HWCDisplayMetrics& display_metrics); int GetSurfaceId() const { int surface_id = -1; @@ -142,10 +139,9 @@ class Layer { } private: - void CommonLayerSetup(); + void CommonLayerSetup(const HWCDisplayMetrics& display_metrics); - static Hwc2::Composer* hwc2_hidl_; - static const HWCDisplayMetrics* display_metrics_; + Hwc2::Composer* hidl_ = nullptr; // The hardware composer layer and metrics to use during the prepare cycle. hwc2_layer_t hardware_composer_layer_ = 0; @@ -263,11 +259,10 @@ class HardwareComposer { static constexpr size_t kMaxHardwareLayers = 4; HardwareComposer(); - HardwareComposer(Hwc2::Composer* hidl, - RequestDisplayCallback request_display_callback); ~HardwareComposer(); - bool Initialize(); + bool Initialize(Hwc2::Composer* hidl, + RequestDisplayCallback request_display_callback); bool IsInitialized() const { return initialized_; } @@ -281,11 +276,6 @@ class HardwareComposer { // Get the HMD display metrics for the current display. display::Metrics GetHmdDisplayMetrics() const; - HWC::Error GetDisplayAttribute(hwc2_display_t display, hwc2_config_t config, - hwc2_attribute_t attributes, - int32_t* out_value) const; - HWC::Error GetDisplayMetrics(hwc2_display_t display, hwc2_config_t config, - HWCDisplayMetrics* out_metrics) const; std::string Dump(); void SetVSyncCallback(VSyncCallback callback); @@ -308,34 +298,31 @@ class HardwareComposer { int OnNewGlobalBuffer(DvrGlobalBufferKey key, IonBuffer& ion_buffer); void OnDeletedGlobalBuffer(DvrGlobalBufferKey key); - void OnHardwareComposerRefresh(); - private: - int32_t EnableVsync(bool enabled); + HWC::Error GetDisplayAttribute(Hwc2::Composer* hidl, hwc2_display_t display, + hwc2_config_t config, + hwc2_attribute_t attributes, + int32_t* out_value) const; + HWC::Error GetDisplayMetrics(Hwc2::Composer* hidl, hwc2_display_t display, + hwc2_config_t config, + HWCDisplayMetrics* out_metrics) const; + + HWC::Error EnableVsync(bool enabled); class ComposerCallback : public Hwc2::IComposerCallback { public: - ComposerCallback() {} - - hardware::Return onHotplug(Hwc2::Display /*display*/, - Connection /*connected*/) override { - // TODO(skiazyk): depending on how the server is implemented, we might - // have to set it up to synchronize with receiving this event, as it can - // potentially be a critical event for setting up state within the - // hwc2 module. That is, we (technically) should not call any other hwc - // methods until this method has been called after registering the - // callbacks. - return hardware::Void(); - } - - hardware::Return onRefresh(Hwc2::Display /*display*/) override { - return hardware::Void(); - } - - hardware::Return onVsync(Hwc2::Display /*display*/, - int64_t /*timestamp*/) override { - return hardware::Void(); - } + ComposerCallback(); + hardware::Return onHotplug(Hwc2::Display display, + Connection conn) override; + hardware::Return onRefresh(Hwc2::Display display) override; + hardware::Return onVsync(Hwc2::Display display, + int64_t timestamp) override; + const pdx::LocalHandle& GetVsyncEventFd() const; + int64_t GetVsyncTime(); + private: + std::mutex vsync_mutex_; + pdx::LocalHandle vsync_event_fd_; + int64_t vsync_time_ = -1; }; HWC::Error Validate(hwc2_display_t display); @@ -364,17 +351,18 @@ class HardwareComposer { void UpdatePostThreadState(uint32_t state, bool suspend); // Blocks until either event_fd becomes readable, or we're interrupted by a - // control thread. Any errors are returned as negative errno values. If we're - // interrupted, kPostThreadInterrupted will be returned. + // control thread, or timeout_ms is reached before any events occur. Any + // errors are returned as negative errno values, with -ETIMEDOUT returned in + // the case of a timeout. If we're interrupted, kPostThreadInterrupted will be + // returned. int PostThreadPollInterruptible(const pdx::LocalHandle& event_fd, - int requested_events); + int requested_events, + int timeout_ms); - // BlockUntilVSync, WaitForVSync, and SleepUntil are all blocking calls made - // on the post thread that can be interrupted by a control thread. If - // interrupted, these calls return kPostThreadInterrupted. + // WaitForVSync and SleepUntil are blocking calls made on the post thread that + // can be interrupted by a control thread. If interrupted, these calls return + // kPostThreadInterrupted. int ReadWaitPPState(); - int BlockUntilVSync(); - int ReadVSyncTimestamp(int64_t* timestamp); int WaitForVSync(int64_t* timestamp); int SleepUntil(int64_t wakeup_timestamp); @@ -398,11 +386,9 @@ class HardwareComposer { bool initialized_; - // Hardware composer HAL device from SurfaceFlinger. VrFlinger does not own - // this pointer. - Hwc2::Composer* hwc2_hidl_; + std::unique_ptr hidl_; + sp hidl_callback_; RequestDisplayCallback request_display_callback_; - sp callbacks_; // Display metrics of the physical display. HWCDisplayMetrics native_display_metrics_; @@ -433,7 +419,8 @@ class HardwareComposer { std::thread post_thread_; // Post thread state machine and synchronization primitives. - PostThreadStateType post_thread_state_{PostThreadState::Idle}; + PostThreadStateType post_thread_state_{ + PostThreadState::Idle | PostThreadState::Suspended}; std::atomic post_thread_quiescent_{true}; bool post_thread_resumed_{false}; pdx::LocalHandle post_thread_event_fd_; @@ -444,9 +431,6 @@ class HardwareComposer { // Backlight LED brightness sysfs node. pdx::LocalHandle backlight_brightness_fd_; - // Primary display vsync event sysfs node. - pdx::LocalHandle primary_display_vsync_event_fd_; - // Primary display wait_pingpong state sysfs node. pdx::LocalHandle primary_display_wait_pp_fd_; @@ -478,12 +462,6 @@ class HardwareComposer { static constexpr int kPostThreadInterrupted = 1; - static void HwcRefresh(hwc2_callback_data_t data, hwc2_display_t display); - static void HwcVSync(hwc2_callback_data_t data, hwc2_display_t display, - int64_t timestamp); - static void HwcHotplug(hwc2_callback_data_t callbackData, - hwc2_display_t display, hwc2_connection_t connected); - HardwareComposer(const HardwareComposer&) = delete; void operator=(const HardwareComposer&) = delete; }; diff --git a/libs/vr/libvrflinger/include/dvr/vr_flinger.h b/libs/vr/libvrflinger/include/dvr/vr_flinger.h index e7f41a7379..33cbc84d7d 100644 --- a/libs/vr/libvrflinger/include/dvr/vr_flinger.h +++ b/libs/vr/libvrflinger/include/dvr/vr_flinger.h @@ -29,9 +29,6 @@ class VrFlinger { void GrantDisplayOwnership(); void SeizeDisplayOwnership(); - // Called on a binder thread. - void OnHardwareComposerRefresh(); - // dump all vr flinger state. std::string Dump(); diff --git a/libs/vr/libvrflinger/vr_flinger.cpp b/libs/vr/libvrflinger/vr_flinger.cpp index 56405deeaa..fcf94f0865 100644 --- a/libs/vr/libvrflinger/vr_flinger.cpp +++ b/libs/vr/libvrflinger/vr_flinger.cpp @@ -133,10 +133,6 @@ void VrFlinger::SeizeDisplayOwnership() { display_service_->SeizeDisplayOwnership(); } -void VrFlinger::OnHardwareComposerRefresh() { - display_service_->OnHardwareComposerRefresh(); -} - std::string VrFlinger::Dump() { // TODO(karthikrs): Add more state information here. return display_service_->DumpState(0/*unused*/); diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 744dd50df5..248ef53f55 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -134,9 +134,11 @@ DisplayDevice::DisplayDevice( EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (config == EGL_NO_CONFIG) { #ifdef USE_HWC2 - config = RenderEngine::chooseEglConfig(display, PIXEL_FORMAT_RGBA_8888); + config = RenderEngine::chooseEglConfig(display, PIXEL_FORMAT_RGBA_8888, + /*logConfig*/ false); #else - config = RenderEngine::chooseEglConfig(display, format); + config = RenderEngine::chooseEglConfig(display, format, + /*logConfig*/ false); #endif } eglSurface = eglCreateWindowSurface(display, config, window, NULL); diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp index 704b17ef43..433a224e25 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp @@ -215,6 +215,10 @@ void Composer::registerCallback(const sp& callback) } } +bool Composer::isRemote() { + return mClient->isRemote(); +} + void Composer::resetCommands() { mWriter.reset(); } diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index 40d2a4c3aa..31a3c1d785 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -143,6 +143,11 @@ public: void registerCallback(const sp& callback); + // Returns true if the connected composer service is running in a remote + // process, false otherwise. This will return false if the service is + // configured in passthrough mode, for example. + bool isRemote(); + // Reset all pending commands in the command buffer. Useful if you want to // skip a frame but have already queued some commands. void resetCommands(); diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp index b749ce630d..78c0c8567a 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.cpp +++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp @@ -33,45 +33,6 @@ #include #include -extern "C" { - static void hotplug_hook(hwc2_callback_data_t callbackData, - hwc2_display_t displayId, int32_t intConnected) { - auto device = static_cast(callbackData); - auto display = device->getDisplayById(displayId); - if (display) { - auto connected = static_cast(intConnected); - device->callHotplug(std::move(display), connected); - } else { - ALOGE("Hotplug callback called with unknown display %" PRIu64, - displayId); - } - } - - static void refresh_hook(hwc2_callback_data_t callbackData, - hwc2_display_t displayId) { - auto device = static_cast(callbackData); - auto display = device->getDisplayById(displayId); - if (display) { - device->callRefresh(std::move(display)); - } else { - ALOGE("Refresh callback called with unknown display %" PRIu64, - displayId); - } - } - - static void vsync_hook(hwc2_callback_data_t callbackData, - hwc2_display_t displayId, int64_t timestamp) { - auto device = static_cast(callbackData); - auto display = device->getDisplayById(displayId); - if (display) { - device->callVsync(std::move(display), timestamp); - } else { - ALOGE("Vsync callback called with unknown display %" PRIu64, - displayId); - } - } -} - using android::Fence; using android::FloatRect; using android::GraphicBuffer; @@ -86,51 +47,78 @@ namespace HWC2 { namespace Hwc2 = android::Hwc2; +namespace { + +class ComposerCallbackBridge : public Hwc2::IComposerCallback { +public: + ComposerCallbackBridge(ComposerCallback* callback, int32_t sequenceId) + : mCallback(callback), mSequenceId(sequenceId), + mHasPrimaryDisplay(false) {} + + Return onHotplug(Hwc2::Display display, + IComposerCallback::Connection conn) override + { + HWC2::Connection connection = static_cast(conn); + if (!mHasPrimaryDisplay) { + LOG_ALWAYS_FATAL_IF(connection != HWC2::Connection::Connected, + "Initial onHotplug callback should be " + "primary display connected"); + mHasPrimaryDisplay = true; + mCallback->onHotplugReceived(mSequenceId, display, + connection, true); + } else { + mCallback->onHotplugReceived(mSequenceId, display, + connection, false); + } + return Void(); + } + + Return onRefresh(Hwc2::Display display) override + { + mCallback->onRefreshReceived(mSequenceId, display); + return Void(); + } + + Return onVsync(Hwc2::Display display, int64_t timestamp) override + { + mCallback->onVsyncReceived(mSequenceId, display, timestamp); + return Void(); + } + + bool HasPrimaryDisplay() { return mHasPrimaryDisplay; } + +private: + ComposerCallback* mCallback; + int32_t mSequenceId; + bool mHasPrimaryDisplay; +}; + +} // namespace anonymous + + // Device methods Device::Device(const std::string& serviceName) : mComposer(std::make_unique(serviceName)), mCapabilities(), mDisplays(), - mHotplug(), - mPendingHotplugs(), - mRefresh(), - mPendingRefreshes(), - mVsync(), - mPendingVsyncs() + mRegisteredCallback(false) { loadCapabilities(); - registerCallbacks(); } -Device::~Device() -{ - for (auto element : mDisplays) { - auto display = element.second.lock(); - if (!display) { - ALOGE("~Device: Found a display (%" PRId64 " that has already been" - " destroyed", element.first); - continue; - } - - DisplayType displayType = HWC2::DisplayType::Invalid; - auto error = display->getType(&displayType); - if (error != Error::None) { - ALOGE("~Device: Failed to determine type of display %" PRIu64 - ": %s (%d)", display->getId(), to_string(error).c_str(), - static_cast(error)); - continue; - } - - if (displayType == HWC2::DisplayType::Physical) { - error = display->setVsyncEnabled(HWC2::Vsync::Disable); - if (error != Error::None) { - ALOGE("~Device: Failed to disable vsync for display %" PRIu64 - ": %s (%d)", display->getId(), to_string(error).c_str(), - static_cast(error)); - } - } +void Device::registerCallback(ComposerCallback* callback, int32_t sequenceId) { + if (mRegisteredCallback) { + ALOGW("Callback already registered. Ignored extra registration " + "attempt."); + return; } + mRegisteredCallback = true; + sp callbackBridge( + new ComposerCallbackBridge(callback, sequenceId)); + mComposer->registerCallback(callbackBridge); + LOG_ALWAYS_FATAL_IF(!callbackBridge->HasPrimaryDisplay(), + "Registered composer callback but didn't get primary display"); } // Required by HWC2 device @@ -146,7 +134,7 @@ uint32_t Device::getMaxVirtualDisplayCount() const } Error Device::createVirtualDisplay(uint32_t width, uint32_t height, - android_pixel_format_t* format, std::shared_ptr* outDisplay) + android_pixel_format_t* format, Display** outDisplay) { ALOGI("Creating virtual display"); @@ -159,104 +147,66 @@ Error Device::createVirtualDisplay(uint32_t width, uint32_t height, return error; } - ALOGI("Created virtual display"); + auto display = std::make_unique( + *mComposer.get(), mCapabilities, displayId, DisplayType::Virtual); + *outDisplay = display.get(); *format = static_cast(intFormat); - *outDisplay = getDisplayById(displayId); - if (!*outDisplay) { - ALOGE("Failed to get display by id"); - return Error::BadDisplay; - } - (*outDisplay)->setConnected(true); + mDisplays.emplace(displayId, std::move(display)); + ALOGI("Created virtual display"); return Error::None; } -void Device::registerHotplugCallback(HotplugCallback hotplug) +void Device::destroyDisplay(hwc2_display_t displayId) { - ALOGV("registerHotplugCallback"); - mHotplug = hotplug; - for (auto& pending : mPendingHotplugs) { - auto& display = pending.first; - auto connected = pending.second; - ALOGV("Sending pending hotplug(%" PRIu64 ", %s)", display->getId(), - to_string(connected).c_str()); - mHotplug(std::move(display), connected); - } + ALOGI("Destroying display %" PRIu64, displayId); + mDisplays.erase(displayId); } -void Device::registerRefreshCallback(RefreshCallback refresh) -{ - mRefresh = refresh; - for (auto& pending : mPendingRefreshes) { - mRefresh(std::move(pending)); - } -} - -void Device::registerVsyncCallback(VsyncCallback vsync) -{ - mVsync = vsync; - for (auto& pending : mPendingVsyncs) { - auto& display = pending.first; - auto timestamp = pending.second; - mVsync(std::move(display), timestamp); - } -} - -// For use by Device callbacks +void Device::onHotplug(hwc2_display_t displayId, Connection connection) { + if (connection == Connection::Connected) { + auto display = getDisplayById(displayId); + if (display) { + if (display->isConnected()) { + ALOGW("Attempt to hotplug connect display %" PRIu64 + " , which is already connected.", displayId); + } else { + display->setConnected(true); + } + } else { + DisplayType displayType; + auto intError = mComposer->getDisplayType(displayId, + reinterpret_cast( + &displayType)); + auto error = static_cast(intError); + if (error != Error::None) { + ALOGE("getDisplayType(%" PRIu64 ") failed: %s (%d). " + "Aborting hotplug attempt.", + displayId, to_string(error).c_str(), intError); + return; + } -void Device::callHotplug(std::shared_ptr display, Connection connected) -{ - if (connected == Connection::Connected) { - if (!display->isConnected()) { - mComposer->setClientTargetSlotCount(display->getId()); - display->loadConfigs(); - display->setConnected(true); + auto newDisplay = std::make_unique( + *mComposer.get(), mCapabilities, displayId, displayType); + mDisplays.emplace(displayId, std::move(newDisplay)); + } + } else if (connection == Connection::Disconnected) { + // The display will later be destroyed by a call to + // destroyDisplay(). For now we just mark it disconnected. + auto display = getDisplayById(displayId); + if (display) { + display->setConnected(false); + } else { + ALOGW("Attempted to disconnect unknown display %" PRIu64, + displayId); } - } else { - display->setConnected(false); - mDisplays.erase(display->getId()); - } - - if (mHotplug) { - mHotplug(std::move(display), connected); - } else { - ALOGV("callHotplug called, but no valid callback registered, storing"); - mPendingHotplugs.emplace_back(std::move(display), connected); - } -} - -void Device::callRefresh(std::shared_ptr display) -{ - if (mRefresh) { - mRefresh(std::move(display)); - } else { - ALOGV("callRefresh called, but no valid callback registered, storing"); - mPendingRefreshes.emplace_back(std::move(display)); - } -} - -void Device::callVsync(std::shared_ptr display, nsecs_t timestamp) -{ - if (mVsync) { - mVsync(std::move(display), timestamp); - } else { - ALOGV("callVsync called, but no valid callback registered, storing"); - mPendingVsyncs.emplace_back(std::move(display), timestamp); } } // Other Device methods -std::shared_ptr Device::getDisplayById(hwc2_display_t id) { - if (mDisplays.count(id) != 0) { - auto strongDisplay = mDisplays[id].lock(); - ALOGE_IF(!strongDisplay, "Display %" PRId64 " is in mDisplays but is no" - " longer alive", id); - return strongDisplay; - } - - auto display = std::make_shared(*this, id); - mDisplays.emplace(id, display); - return display; +Display* Device::getDisplayById(hwc2_display_t id) { + auto iter = mDisplays.find(id); + return iter == mDisplays.end() ? nullptr : iter->second.get(); } // Device initialization methods @@ -271,84 +221,37 @@ void Device::loadCapabilities() } } -bool Device::hasCapability(HWC2::Capability capability) const -{ - return std::find(mCapabilities.cbegin(), mCapabilities.cend(), - capability) != mCapabilities.cend(); -} - -namespace { -class ComposerCallback : public Hwc2::IComposerCallback { -public: - ComposerCallback(Device* device) : mDevice(device) {} - - Return onHotplug(Hwc2::Display display, - Connection connected) override - { - hotplug_hook(mDevice, display, static_cast(connected)); - return Void(); - } - - Return onRefresh(Hwc2::Display display) override - { - refresh_hook(mDevice, display); - return Void(); - } - - Return onVsync(Hwc2::Display display, int64_t timestamp) override - { - vsync_hook(mDevice, display, timestamp); - return Void(); - } - -private: - Device* mDevice; -}; -} // namespace anonymous - -void Device::registerCallbacks() -{ - sp callback = new ComposerCallback(this); - mComposer->registerCallback(callback); -} - - -// For use by Display - -void Device::destroyVirtualDisplay(hwc2_display_t display) -{ - ALOGI("Destroying virtual display"); - auto intError = mComposer->destroyVirtualDisplay(display); - auto error = static_cast(intError); - ALOGE_IF(error != Error::None, "destroyVirtualDisplay(%" PRIu64 ") failed:" - " %s (%d)", display, to_string(error).c_str(), intError); - mDisplays.erase(display); -} - // Display methods -Display::Display(Device& device, hwc2_display_t id) - : mDevice(device), +Display::Display(android::Hwc2::Composer& composer, + const std::unordered_set& capabilities, + hwc2_display_t id, DisplayType type) + : mComposer(composer), + mCapabilities(capabilities), mId(id), mIsConnected(false), - mType(DisplayType::Invalid) + mType(type) { ALOGV("Created display %" PRIu64, id); - - auto intError = mDevice.mComposer->getDisplayType(mId, - reinterpret_cast(&mType)); - auto error = static_cast(intError); - if (error != Error::None) { - ALOGE("getDisplayType(%" PRIu64 ") failed: %s (%d)", - id, to_string(error).c_str(), intError); - } + setConnected(true); } -Display::~Display() -{ - ALOGV("Destroyed display %" PRIu64, mId); +Display::~Display() { + mLayers.clear(); + if (mType == DisplayType::Virtual) { - mDevice.destroyVirtualDisplay(mId); + ALOGV("Destroying virtual display"); + auto intError = mComposer.destroyVirtualDisplay(mId); + auto error = static_cast(intError); + ALOGE_IF(error != Error::None, "destroyVirtualDisplay(%" PRIu64 + ") failed: %s (%d)", mId, to_string(error).c_str(), intError); + } else if (mType == DisplayType::Physical) { + auto error = setVsyncEnabled(HWC2::Vsync::Disable); + if (error != Error::None) { + ALOGE("~Display: Failed to disable vsync for display %" PRIu64 + ": %s (%d)", mId, to_string(error).c_str(), + static_cast(error)); + } } } @@ -383,22 +286,35 @@ float Display::Config::Builder::getDefaultDensity() { Error Display::acceptChanges() { - auto intError = mDevice.mComposer->acceptDisplayChanges(mId); + auto intError = mComposer.acceptDisplayChanges(mId); return static_cast(intError); } -Error Display::createLayer(std::shared_ptr* outLayer) +Error Display::createLayer(Layer** outLayer) { + if (!outLayer) { + return Error::BadParameter; + } hwc2_layer_t layerId = 0; - auto intError = mDevice.mComposer->createLayer(mId, &layerId); + auto intError = mComposer.createLayer(mId, &layerId); auto error = static_cast(intError); if (error != Error::None) { return error; } - auto layer = std::make_shared(shared_from_this(), layerId); - mLayers.emplace(layerId, layer); - *outLayer = std::move(layer); + auto layer = std::make_unique( + mComposer, mCapabilities, mId, layerId); + *outLayer = layer.get(); + mLayers.emplace(layerId, std::move(layer)); + return Error::None; +} + +Error Display::destroyLayer(Layer* layer) +{ + if (!layer) { + return Error::BadParameter; + } + mLayers.erase(layer->getId()); return Error::None; } @@ -407,7 +323,7 @@ Error Display::getActiveConfig( { ALOGV("[%" PRIu64 "] getActiveConfig", mId); hwc2_config_t configId = 0; - auto intError = mDevice.mComposer->getActiveConfig(mId, &configId); + auto intError = mComposer.getActiveConfig(mId, &configId); auto error = static_cast(intError); if (error != Error::None) { @@ -430,12 +346,12 @@ Error Display::getActiveConfig( } Error Display::getChangedCompositionTypes( - std::unordered_map, Composition>* outTypes) + std::unordered_map* outTypes) { std::vector layerIds; std::vector types; - auto intError = mDevice.mComposer->getChangedCompositionTypes(mId, - &layerIds, &types); + auto intError = mComposer.getChangedCompositionTypes( + mId, &layerIds, &types); uint32_t numElements = layerIds.size(); auto error = static_cast(intError); error = static_cast(intError); @@ -464,7 +380,7 @@ Error Display::getChangedCompositionTypes( Error Display::getColorModes(std::vector* outModes) const { std::vector modes; - auto intError = mDevice.mComposer->getColorModes(mId, &modes); + auto intError = mComposer.getColorModes(mId, &modes); uint32_t numModes = modes.size(); auto error = static_cast(intError); if (error != Error::None) { @@ -489,19 +405,18 @@ std::vector> Display::getConfigs() const Error Display::getName(std::string* outName) const { - auto intError = mDevice.mComposer->getDisplayName(mId, outName); + auto intError = mComposer.getDisplayName(mId, outName); return static_cast(intError); } Error Display::getRequests(HWC2::DisplayRequest* outDisplayRequests, - std::unordered_map, LayerRequest>* - outLayerRequests) + std::unordered_map* outLayerRequests) { uint32_t intDisplayRequests; std::vector layerIds; std::vector layerRequests; - auto intError = mDevice.mComposer->getDisplayRequests(mId, - &intDisplayRequests, &layerIds, &layerRequests); + auto intError = mComposer.getDisplayRequests( + mId, &intDisplayRequests, &layerIds, &layerRequests); uint32_t numElements = layerIds.size(); auto error = static_cast(intError); if (error != Error::None) { @@ -535,7 +450,7 @@ Error Display::getType(DisplayType* outType) const Error Display::supportsDoze(bool* outSupport) const { bool intSupport = false; - auto intError = mDevice.mComposer->getDozeSupport(mId, &intSupport); + auto intError = mComposer.getDozeSupport(mId, &intSupport); auto error = static_cast(intError); if (error != Error::None) { return error; @@ -552,7 +467,7 @@ Error Display::getHdrCapabilities( float maxAverageLuminance = -1.0f; float minLuminance = -1.0f; std::vector intTypes; - auto intError = mDevice.mComposer->getHdrCapabilities(mId, &intTypes, + auto intError = mComposer.getHdrCapabilities(mId, &intTypes, &maxLuminance, &maxAverageLuminance, &minLuminance); auto error = static_cast(intError); @@ -571,25 +486,24 @@ Error Display::getHdrCapabilities( } Error Display::getReleaseFences( - std::unordered_map, sp>* outFences) const + std::unordered_map>* outFences) const { std::vector layerIds; std::vector fenceFds; - auto intError = mDevice.mComposer->getReleaseFences(mId, - &layerIds, &fenceFds); + auto intError = mComposer.getReleaseFences(mId, &layerIds, &fenceFds); auto error = static_cast(intError); uint32_t numElements = layerIds.size(); if (error != Error::None) { return error; } - std::unordered_map, sp> releaseFences; + std::unordered_map> releaseFences; releaseFences.reserve(numElements); for (uint32_t element = 0; element < numElements; ++element) { auto layer = getLayerById(layerIds[element]); if (layer) { sp fence(new Fence(fenceFds[element])); - releaseFences.emplace(std::move(layer), fence); + releaseFences.emplace(layer, fence); } else { ALOGE("getReleaseFences: invalid layer %" PRIu64 " found on display %" PRIu64, layerIds[element], mId); @@ -607,7 +521,7 @@ Error Display::getReleaseFences( Error Display::present(sp* outPresentFence) { int32_t presentFenceFd = -1; - auto intError = mDevice.mComposer->presentDisplay(mId, &presentFenceFd); + auto intError = mComposer.presentDisplay(mId, &presentFenceFd); auto error = static_cast(intError); if (error != Error::None) { return error; @@ -625,7 +539,7 @@ Error Display::setActiveConfig(const std::shared_ptr& config) config->getDisplayId(), mId); return Error::BadConfig; } - auto intError = mDevice.mComposer->setActiveConfig(mId, config->getId()); + auto intError = mComposer.setActiveConfig(mId, config->getId()); return static_cast(intError); } @@ -634,7 +548,7 @@ Error Display::setClientTarget(uint32_t slot, const sp& target, { // TODO: Properly encode client target surface damage int32_t fenceFd = acquireFence->dup(); - auto intError = mDevice.mComposer->setClientTarget(mId, slot, target, + auto intError = mComposer.setClientTarget(mId, slot, target, fenceFd, static_cast(dataspace), std::vector()); return static_cast(intError); @@ -642,15 +556,15 @@ Error Display::setClientTarget(uint32_t slot, const sp& target, Error Display::setColorMode(android_color_mode_t mode) { - auto intError = mDevice.mComposer->setColorMode(mId, - static_cast(mode)); + auto intError = mComposer.setColorMode( + mId, static_cast(mode)); return static_cast(intError); } Error Display::setColorTransform(const android::mat4& matrix, android_color_transform_t hint) { - auto intError = mDevice.mComposer->setColorTransform(mId, + auto intError = mComposer.setColorTransform(mId, matrix.asArray(), static_cast(hint)); return static_cast(intError); } @@ -660,7 +574,7 @@ Error Display::setOutputBuffer(const sp& buffer, { int32_t fenceFd = releaseFence->dup(); auto handle = buffer->getNativeBuffer()->handle; - auto intError = mDevice.mComposer->setOutputBuffer(mId, handle, fenceFd); + auto intError = mComposer.setOutputBuffer(mId, handle, fenceFd); close(fenceFd); return static_cast(intError); } @@ -668,14 +582,14 @@ Error Display::setOutputBuffer(const sp& buffer, Error Display::setPowerMode(PowerMode mode) { auto intMode = static_cast(mode); - auto intError = mDevice.mComposer->setPowerMode(mId, intMode); + auto intError = mComposer.setPowerMode(mId, intMode); return static_cast(intError); } Error Display::setVsyncEnabled(Vsync enabled) { auto intEnabled = static_cast(enabled); - auto intError = mDevice.mComposer->setVsyncEnabled(mId, intEnabled); + auto intError = mComposer.setVsyncEnabled(mId, intEnabled); return static_cast(intError); } @@ -683,8 +597,7 @@ Error Display::validate(uint32_t* outNumTypes, uint32_t* outNumRequests) { uint32_t numTypes = 0; uint32_t numRequests = 0; - auto intError = mDevice.mComposer->validateDisplay(mId, - &numTypes, &numRequests); + auto intError = mComposer.validateDisplay(mId, &numTypes, &numRequests); auto error = static_cast(intError); if (error != Error::None && error != Error::HasChanges) { return error; @@ -701,7 +614,8 @@ Error Display::presentOrValidate(uint32_t* outNumTypes, uint32_t* outNumRequests uint32_t numTypes = 0; uint32_t numRequests = 0; int32_t presentFenceFd = -1; - auto intError = mDevice.mComposer->presentOrValidateDisplay(mId, &numTypes, &numRequests, &presentFenceFd, state); + auto intError = mComposer.presentOrValidateDisplay( + mId, &numTypes, &numRequests, &presentFenceFd, state); auto error = static_cast(intError); if (error != Error::None && error != Error::HasChanges) { return error; @@ -720,15 +634,23 @@ Error Display::presentOrValidate(uint32_t* outNumTypes, uint32_t* outNumRequests void Display::discardCommands() { - mDevice.mComposer->resetCommands(); + mComposer.resetCommands(); } // For use by Device +void Display::setConnected(bool connected) { + if (!mIsConnected && connected && mType == DisplayType::Physical) { + mComposer.setClientTargetSlotCount(mId); + loadConfigs(); + } + mIsConnected = connected; +} + int32_t Display::getAttribute(hwc2_config_t configId, Attribute attribute) { int32_t value = 0; - auto intError = mDevice.mComposer->getDisplayAttribute(mId, configId, + auto intError = mComposer.getDisplayAttribute(mId, configId, static_cast(attribute), &value); auto error = static_cast(intError); @@ -760,7 +682,7 @@ void Display::loadConfigs() ALOGV("[%" PRIu64 "] loadConfigs", mId); std::vector configIds; - auto intError = mDevice.mComposer->getDisplayConfigs(mId, &configIds); + auto intError = mComposer.getDisplayConfigs(mId, &configIds); auto error = static_cast(intError); if (error != Error::None) { ALOGE("[%" PRIu64 "] getDisplayConfigs [2] failed: %s (%d)", mId, @@ -773,54 +695,51 @@ void Display::loadConfigs() } } -// For use by Layer - -void Display::destroyLayer(hwc2_layer_t layerId) -{ - auto intError =mDevice.mComposer->destroyLayer(mId, layerId); - auto error = static_cast(intError); - ALOGE_IF(error != Error::None, "destroyLayer(%" PRIu64 ", %" PRIu64 ")" - " failed: %s (%d)", mId, layerId, to_string(error).c_str(), - intError); - mLayers.erase(layerId); -} - // Other Display methods -std::shared_ptr Display::getLayerById(hwc2_layer_t id) const +Layer* Display::getLayerById(hwc2_layer_t id) const { if (mLayers.count(id) == 0) { return nullptr; } - auto layer = mLayers.at(id).lock(); - return layer; + return mLayers.at(id).get(); } // Layer methods -Layer::Layer(const std::shared_ptr& display, hwc2_layer_t id) - : mDisplay(display), - mDisplayId(display->getId()), - mDevice(display->getDevice()), - mId(id) +Layer::Layer(android::Hwc2::Composer& composer, + const std::unordered_set& capabilities, + hwc2_display_t displayId, hwc2_layer_t layerId) + : mComposer(composer), + mCapabilities(capabilities), + mDisplayId(displayId), + mId(layerId) { - ALOGV("Created layer %" PRIu64 " on display %" PRIu64, id, - display->getId()); + ALOGV("Created layer %" PRIu64 " on display %" PRIu64, layerId, displayId); } Layer::~Layer() { - auto display = mDisplay.lock(); - if (display) { - display->destroyLayer(mId); + auto intError = mComposer.destroyLayer(mDisplayId, mId); + auto error = static_cast(intError); + ALOGE_IF(error != Error::None, "destroyLayer(%" PRIu64 ", %" PRIu64 ")" + " failed: %s (%d)", mDisplayId, mId, to_string(error).c_str(), + intError); + if (mLayerDestroyedListener) { + mLayerDestroyedListener(this); } } +void Layer::setLayerDestroyedListener(std::function listener) { + LOG_ALWAYS_FATAL_IF(mLayerDestroyedListener && listener, + "Attempt to set layer destroyed listener multiple times"); + mLayerDestroyedListener = listener; +} + Error Layer::setCursorPosition(int32_t x, int32_t y) { - auto intError = mDevice.mComposer->setCursorPosition(mDisplayId, - mId, x, y); + auto intError = mComposer.setCursorPosition(mDisplayId, mId, x, y); return static_cast(intError); } @@ -828,8 +747,8 @@ Error Layer::setBuffer(uint32_t slot, const sp& buffer, const sp& acquireFence) { int32_t fenceFd = acquireFence->dup(); - auto intError = mDevice.mComposer->setLayerBuffer(mDisplayId, - mId, slot, buffer, fenceFd); + auto intError = mComposer.setLayerBuffer(mDisplayId, mId, slot, buffer, + fenceFd); return static_cast(intError); } @@ -839,7 +758,7 @@ Error Layer::setSurfaceDamage(const Region& damage) // rects for HWC Hwc2::Error intError = Hwc2::Error::NONE; if (damage.isRect() && damage.getBounds() == Rect::INVALID_RECT) { - intError = mDevice.mComposer->setLayerSurfaceDamage(mDisplayId, + intError = mComposer.setLayerSurfaceDamage(mDisplayId, mId, std::vector()); } else { size_t rectCount = 0; @@ -851,8 +770,7 @@ Error Layer::setSurfaceDamage(const Region& damage) rectArray[rect].right, rectArray[rect].bottom}); } - intError = mDevice.mComposer->setLayerSurfaceDamage(mDisplayId, - mId, hwcRects); + intError = mComposer.setLayerSurfaceDamage(mDisplayId, mId, hwcRects); } return static_cast(intError); @@ -861,24 +779,22 @@ Error Layer::setSurfaceDamage(const Region& damage) Error Layer::setBlendMode(BlendMode mode) { auto intMode = static_cast(mode); - auto intError = mDevice.mComposer->setLayerBlendMode(mDisplayId, - mId, intMode); + auto intError = mComposer.setLayerBlendMode(mDisplayId, mId, intMode); return static_cast(intError); } Error Layer::setColor(hwc_color_t color) { Hwc2::IComposerClient::Color hwcColor{color.r, color.g, color.b, color.a}; - auto intError = mDevice.mComposer->setLayerColor(mDisplayId, - mId, hwcColor); + auto intError = mComposer.setLayerColor(mDisplayId, mId, hwcColor); return static_cast(intError); } Error Layer::setCompositionType(Composition type) { auto intType = static_cast(type); - auto intError = mDevice.mComposer->setLayerCompositionType(mDisplayId, - mId, intType); + auto intError = mComposer.setLayerCompositionType( + mDisplayId, mId, intType); return static_cast(intError); } @@ -889,8 +805,7 @@ Error Layer::setDataspace(android_dataspace_t dataspace) } mDataSpace = dataspace; auto intDataspace = static_cast(dataspace); - auto intError = mDevice.mComposer->setLayerDataspace(mDisplayId, - mId, intDataspace); + auto intError = mComposer.setLayerDataspace(mDisplayId, mId, intDataspace); return static_cast(intError); } @@ -898,27 +813,24 @@ Error Layer::setDisplayFrame(const Rect& frame) { Hwc2::IComposerClient::Rect hwcRect{frame.left, frame.top, frame.right, frame.bottom}; - auto intError = mDevice.mComposer->setLayerDisplayFrame(mDisplayId, - mId, hwcRect); + auto intError = mComposer.setLayerDisplayFrame(mDisplayId, mId, hwcRect); return static_cast(intError); } Error Layer::setPlaneAlpha(float alpha) { - auto intError = mDevice.mComposer->setLayerPlaneAlpha(mDisplayId, - mId, alpha); + auto intError = mComposer.setLayerPlaneAlpha(mDisplayId, mId, alpha); return static_cast(intError); } Error Layer::setSidebandStream(const native_handle_t* stream) { - if (!mDevice.hasCapability(Capability::SidebandStream)) { + if (mCapabilities.count(Capability::SidebandStream) == 0) { ALOGE("Attempted to call setSidebandStream without checking that the " "device supports sideband streams"); return Error::Unsupported; } - auto intError = mDevice.mComposer->setLayerSidebandStream(mDisplayId, - mId, stream); + auto intError = mComposer.setLayerSidebandStream(mDisplayId, mId, stream); return static_cast(intError); } @@ -926,16 +838,14 @@ Error Layer::setSourceCrop(const FloatRect& crop) { Hwc2::IComposerClient::FRect hwcRect{ crop.left, crop.top, crop.right, crop.bottom}; - auto intError = mDevice.mComposer->setLayerSourceCrop(mDisplayId, - mId, hwcRect); + auto intError = mComposer.setLayerSourceCrop(mDisplayId, mId, hwcRect); return static_cast(intError); } Error Layer::setTransform(Transform transform) { auto intTransform = static_cast(transform); - auto intError = mDevice.mComposer->setLayerTransform(mDisplayId, - mId, intTransform); + auto intError = mComposer.setLayerTransform(mDisplayId, mId, intTransform); return static_cast(intError); } @@ -950,20 +860,19 @@ Error Layer::setVisibleRegion(const Region& region) rectArray[rect].right, rectArray[rect].bottom}); } - auto intError = mDevice.mComposer->setLayerVisibleRegion(mDisplayId, - mId, hwcRects); + auto intError = mComposer.setLayerVisibleRegion(mDisplayId, mId, hwcRects); return static_cast(intError); } Error Layer::setZOrder(uint32_t z) { - auto intError = mDevice.mComposer->setLayerZOrder(mDisplayId, mId, z); + auto intError = mComposer.setLayerZOrder(mDisplayId, mId, z); return static_cast(intError); } Error Layer::setInfo(uint32_t type, uint32_t appId) { - auto intError = mDevice.mComposer->setLayerInfo(mDisplayId, mId, type, appId); + auto intError = mComposer.setLayerInfo(mDisplayId, mId, type, appId); return static_cast(intError); } diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index 9bcda1eb0a..fbe4c7ebed 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -53,10 +53,26 @@ namespace HWC2 { class Display; class Layer; -typedef std::function, Connection)> - HotplugCallback; -typedef std::function)> RefreshCallback; -typedef std::function, nsecs_t)> VsyncCallback; +// Implement this interface to receive hardware composer events. +// +// These callback functions will generally be called on a hwbinder thread, but +// when first registering the callback the onHotplugReceived() function will +// immediately be called on the thread calling registerCallback(). +// +// All calls receive a sequenceId, which will be the value that was supplied to +// HWC2::Device::registerCallback(). It's used to help differentiate callbacks +// from different hardware composer instances. +class ComposerCallback { + public: + virtual void onHotplugReceived(int32_t sequenceId, hwc2_display_t display, + Connection connection, + bool primaryDisplay) = 0; + virtual void onRefreshReceived(int32_t sequenceId, + hwc2_display_t display) = 0; + virtual void onVsyncReceived(int32_t sequenceId, hwc2_display_t display, + int64_t timestamp) = 0; + virtual ~ComposerCallback() = default; +}; // C++ Wrapper around hwc2_device_t. Load all functions pointers // and handle callback registration. @@ -66,10 +82,8 @@ public: // Service name is expected to be 'default' or 'vr' for normal use. // 'vr' will slightly modify the behavior of the mComposer. Device(const std::string& serviceName); - ~Device(); - friend class HWC2::Display; - friend class HWC2::Layer; + void registerCallback(ComposerCallback* callback, int32_t sequenceId); // Required by HWC2 @@ -81,27 +95,14 @@ public: uint32_t getMaxVirtualDisplayCount() const; Error createVirtualDisplay(uint32_t width, uint32_t height, - android_pixel_format_t* format, - std::shared_ptr* outDisplay); - - void registerHotplugCallback(HotplugCallback hotplug); - void registerRefreshCallback(RefreshCallback refresh); - void registerVsyncCallback(VsyncCallback vsync); + android_pixel_format_t* format, Display** outDisplay); + void destroyDisplay(hwc2_display_t displayId); - // For use by callbacks - - void callHotplug(std::shared_ptr display, Connection connected); - void callRefresh(std::shared_ptr display); - void callVsync(std::shared_ptr display, nsecs_t timestamp); + void onHotplug(hwc2_display_t displayId, Connection connection); // Other Device methods - // This will create a Display if one is not found, but it will not be marked - // as connected. This Display may be null if the display has been torn down - // but has not been removed from the map yet. - std::shared_ptr getDisplayById(hwc2_display_t id); - - bool hasCapability(HWC2::Capability capability) const; + Display* getDisplayById(hwc2_display_t id); android::Hwc2::Composer* getComposer() { return mComposer.get(); } @@ -109,37 +110,23 @@ private: // Initialization methods void loadCapabilities(); - void registerCallbacks(); - - // For use by Display - - void destroyVirtualDisplay(hwc2_display_t display); // Member variables std::unique_ptr mComposer; - std::unordered_set mCapabilities; - std::unordered_map> mDisplays; - - HotplugCallback mHotplug; - std::vector, Connection>> - mPendingHotplugs; - RefreshCallback mRefresh; - std::vector> mPendingRefreshes; - VsyncCallback mVsync; - std::vector, nsecs_t>> mPendingVsyncs; + std::unordered_map> mDisplays; + bool mRegisteredCallback; }; // Convenience C++ class to access hwc2_device_t Display functions directly. -class Display : public std::enable_shared_from_this +class Display { public: - Display(Device& device, hwc2_display_t id); + Display(android::Hwc2::Composer& composer, + const std::unordered_set& capabilities, + hwc2_display_t id, DisplayType type); ~Display(); - friend class HWC2::Device; - friend class HWC2::Layer; - class Config { public: @@ -212,12 +199,12 @@ public: // Required by HWC2 [[clang::warn_unused_result]] Error acceptChanges(); - [[clang::warn_unused_result]] Error createLayer( - std::shared_ptr* outLayer); + [[clang::warn_unused_result]] Error createLayer(Layer** outLayer); + [[clang::warn_unused_result]] Error destroyLayer(Layer* layer); [[clang::warn_unused_result]] Error getActiveConfig( std::shared_ptr* outConfig) const; [[clang::warn_unused_result]] Error getChangedCompositionTypes( - std::unordered_map, Composition>* outTypes); + std::unordered_map* outTypes); [[clang::warn_unused_result]] Error getColorModes( std::vector* outModes) const; @@ -227,14 +214,13 @@ public: [[clang::warn_unused_result]] Error getName(std::string* outName) const; [[clang::warn_unused_result]] Error getRequests( DisplayRequest* outDisplayRequests, - std::unordered_map, LayerRequest>* - outLayerRequests); + std::unordered_map* outLayerRequests); [[clang::warn_unused_result]] Error getType(DisplayType* outType) const; [[clang::warn_unused_result]] Error supportsDoze(bool* outSupport) const; [[clang::warn_unused_result]] Error getHdrCapabilities( std::unique_ptr* outCapabilities) const; [[clang::warn_unused_result]] Error getReleaseFences( - std::unordered_map, + std::unordered_map>* outFences) const; [[clang::warn_unused_result]] Error present( android::sp* outPresentFence); @@ -266,32 +252,31 @@ public: // Other Display methods - Device& getDevice() const { return mDevice; } hwc2_display_t getId() const { return mId; } bool isConnected() const { return mIsConnected; } + void setConnected(bool connected); // For use by Device only private: - // For use by Device - - void setConnected(bool connected) { mIsConnected = connected; } int32_t getAttribute(hwc2_config_t configId, Attribute attribute); void loadConfig(hwc2_config_t configId); void loadConfigs(); - // For use by Layer - void destroyLayer(hwc2_layer_t layerId); - // This may fail (and return a null pointer) if no layer with this ID exists // on this display - std::shared_ptr getLayerById(hwc2_layer_t id) const; + Layer* getLayerById(hwc2_layer_t id) const; // Member variables - Device& mDevice; + // These are references to data owned by HWC2::Device, which will outlive + // this HWC2::Display, so these references are guaranteed to be valid for + // the lifetime of this object. + android::Hwc2::Composer& mComposer; + const std::unordered_set& mCapabilities; + hwc2_display_t mId; bool mIsConnected; DisplayType mType; - std::unordered_map> mLayers; + std::unordered_map> mLayers; // The ordering in this map matters, for getConfigs(), when it is // converted to a vector std::map> mConfigs; @@ -301,12 +286,18 @@ private: class Layer { public: - Layer(const std::shared_ptr& display, hwc2_layer_t id); + Layer(android::Hwc2::Composer& composer, + const std::unordered_set& capabilities, + hwc2_display_t displayId, hwc2_layer_t layerId); ~Layer(); - bool isAbandoned() const { return mDisplay.expired(); } hwc2_layer_t getId() const { return mId; } + // Register a listener to be notified when the layer is destroyed. When the + // listener function is called, the Layer will be in the process of being + // destroyed, so it's not safe to call methods on it. + void setLayerDestroyedListener(std::function listener); + [[clang::warn_unused_result]] Error setCursorPosition(int32_t x, int32_t y); [[clang::warn_unused_result]] Error setBuffer(uint32_t slot, const android::sp& buffer, @@ -333,11 +324,16 @@ public: [[clang::warn_unused_result]] Error setInfo(uint32_t type, uint32_t appId); private: - std::weak_ptr mDisplay; + // These are references to data owned by HWC2::Device, which will outlive + // this HWC2::Layer, so these references are guaranteed to be valid for + // the lifetime of this object. + android::Hwc2::Composer& mComposer; + const std::unordered_set& mCapabilities; + hwc2_display_t mDisplayId; - Device& mDevice; hwc2_layer_t mId; android_dataspace mDataSpace = HAL_DATASPACE_UNKNOWN; + std::function mLayerDestroyedListener; }; } // namespace HWC2 diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index abf7dd12d0..b096a3ae57 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -65,7 +65,6 @@ HWComposer::HWComposer(const std::string& serviceName) mFreeDisplaySlots(), mHwcDisplaySlots(), mCBContext(), - mEventHandler(nullptr), mVSyncCounts(), mRemainingHwcVirtualDisplays(0) { @@ -74,41 +73,15 @@ HWComposer::HWComposer(const std::string& serviceName) mVSyncCounts[i] = 0; } - loadHwcModule(serviceName); + mHwcDevice = std::make_unique(serviceName); + mRemainingHwcVirtualDisplays = mHwcDevice->getMaxVirtualDisplayCount(); } HWComposer::~HWComposer() {} -void HWComposer::setEventHandler(EventHandler* handler) -{ - if (handler == nullptr) { - ALOGE("setEventHandler: Rejected attempt to clear handler"); - return; - } - - bool wasNull = (mEventHandler == nullptr); - mEventHandler = handler; - - if (wasNull) { - auto hotplugHook = std::bind(&HWComposer::hotplug, this, - std::placeholders::_1, std::placeholders::_2); - mHwcDevice->registerHotplugCallback(hotplugHook); - auto invalidateHook = std::bind(&HWComposer::invalidate, this, - std::placeholders::_1); - mHwcDevice->registerRefreshCallback(invalidateHook); - auto vsyncHook = std::bind(&HWComposer::vsync, this, - std::placeholders::_1, std::placeholders::_2); - mHwcDevice->registerVsyncCallback(vsyncHook); - } -} - -// Load and prepare the hardware composer module. Sets mHwc. -void HWComposer::loadHwcModule(const std::string& serviceName) -{ - ALOGV("loadHwcModule"); - mHwcDevice = std::make_unique(serviceName); - - mRemainingHwcVirtualDisplays = mHwcDevice->getMaxVirtualDisplayCount(); +void HWComposer::registerCallback(HWC2::ComposerCallback* callback, + int32_t sequenceId) { + mHwcDevice->registerCallback(callback, sequenceId); } bool HWComposer::hasCapability(HWC2::Capability capability) const @@ -146,54 +119,51 @@ void HWComposer::validateChange(HWC2::Composition from, HWC2::Composition to) { } } -void HWComposer::hotplug(const std::shared_ptr& display, - HWC2::Connection connected) { - ALOGV("hotplug: %" PRIu64 ", %s", display->getId(), - to_string(connected).c_str()); - int32_t disp = 0; +void HWComposer::onHotplug(hwc2_display_t displayId, + HWC2::Connection connection) { + ALOGV("hotplug: %" PRIu64 ", %s", displayId, + to_string(connection).c_str()); + mHwcDevice->onHotplug(displayId, connection); if (!mDisplayData[0].hwcDisplay) { - ALOGE_IF(connected != HWC2::Connection::Connected, "Assumed primary" + ALOGE_IF(connection != HWC2::Connection::Connected, "Assumed primary" " display would be connected"); - mDisplayData[0].hwcDisplay = display; - mHwcDisplaySlots[display->getId()] = 0; - disp = DisplayDevice::DISPLAY_PRIMARY; + mDisplayData[0].hwcDisplay = mHwcDevice->getDisplayById(displayId); + mHwcDisplaySlots[displayId] = 0; } else { // Disconnect is handled through HWComposer::disconnectDisplay via // SurfaceFlinger's onHotplugReceived callback handling - if (connected == HWC2::Connection::Connected) { - mDisplayData[1].hwcDisplay = display; - mHwcDisplaySlots[display->getId()] = 1; + if (connection == HWC2::Connection::Connected) { + mDisplayData[1].hwcDisplay = mHwcDevice->getDisplayById(displayId); + mHwcDisplaySlots[displayId] = 1; } - disp = DisplayDevice::DISPLAY_EXTERNAL; } - mEventHandler->onHotplugReceived(this, disp, - connected == HWC2::Connection::Connected); } -void HWComposer::invalidate(const std::shared_ptr& /*display*/) { - mEventHandler->onInvalidateReceived(this); -} - -void HWComposer::vsync(const std::shared_ptr& display, - int64_t timestamp) { +bool HWComposer::onVsync(hwc2_display_t displayId, int64_t timestamp, + int32_t* outDisplay) { + auto display = mHwcDevice->getDisplayById(displayId); + if (!display) { + ALOGE("onVsync Failed to find display %" PRIu64, displayId); + return false; + } auto displayType = HWC2::DisplayType::Invalid; auto error = display->getType(&displayType); if (error != HWC2::Error::None) { - ALOGE("vsync: Failed to determine type of display %" PRIu64, + ALOGE("onVsync: Failed to determine type of display %" PRIu64, display->getId()); - return; + return false; } if (displayType == HWC2::DisplayType::Virtual) { ALOGE("Virtual display %" PRIu64 " passed to vsync callback", display->getId()); - return; + return false; } if (mHwcDisplaySlots.count(display->getId()) == 0) { ALOGE("Unknown physical display %" PRIu64 " passed to vsync callback", display->getId()); - return; + return false; } int32_t disp = mHwcDisplaySlots[display->getId()]; @@ -207,17 +177,21 @@ void HWComposer::vsync(const std::shared_ptr& display, if (timestamp == mLastHwVSync[disp]) { ALOGW("Ignoring duplicate VSYNC event from HWC (t=%" PRId64 ")", timestamp); - return; + return false; } mLastHwVSync[disp] = timestamp; } + if (outDisplay) { + *outDisplay = disp; + } + char tag[16]; snprintf(tag, sizeof(tag), "HW_VSYNC_%1u", disp); ATRACE_INT(tag, ++mVSyncCounts[disp] & 1); - mEventHandler->onVSyncReceived(this, disp, timestamp); + return true; } status_t HWComposer::allocateVirtualDisplay(uint32_t width, uint32_t height, @@ -236,7 +210,7 @@ status_t HWComposer::allocateVirtualDisplay(uint32_t width, uint32_t height, return INVALID_OPERATION; } - std::shared_ptr display; + HWC2::Display* display; auto error = mHwcDevice->createVirtualDisplay(width, height, format, &display); if (error != HWC2::Error::None) { @@ -265,13 +239,13 @@ status_t HWComposer::allocateVirtualDisplay(uint32_t width, uint32_t height, return NO_ERROR; } -std::shared_ptr HWComposer::createLayer(int32_t displayId) { +HWC2::Layer* HWComposer::createLayer(int32_t displayId) { if (!isValidDisplay(displayId)) { ALOGE("Failed to create layer on invalid display %d", displayId); return nullptr; } auto display = mDisplayData[displayId].hwcDisplay; - std::shared_ptr layer; + HWC2::Layer* layer; auto error = display->createLayer(&layer); if (error != HWC2::Error::None) { ALOGE("Failed to create layer on display %d: %s (%d)", displayId, @@ -281,6 +255,19 @@ std::shared_ptr HWComposer::createLayer(int32_t displayId) { return layer; } +void HWComposer::destroyLayer(int32_t displayId, HWC2::Layer* layer) { + if (!isValidDisplay(displayId)) { + ALOGE("Failed to destroy layer on invalid display %d", displayId); + return; + } + auto display = mDisplayData[displayId].hwcDisplay; + auto error = display->destroyLayer(layer); + if (error != HWC2::Error::None) { + ALOGE("Failed to destroy layer on display %d: %s (%d)", displayId, + to_string(error).c_str(), static_cast(error)); + } +} + nsecs_t HWComposer::getRefreshTimestamp(int32_t displayId) const { // this returns the last refresh timestamp. // if the last one is not available, we estimate it based on @@ -348,10 +335,8 @@ std::vector HWComposer::getColorModes(int32_t displayId) c displayId); return modes; } - const std::shared_ptr& hwcDisplay = - mDisplayData[displayId].hwcDisplay; - auto error = hwcDisplay->getColorModes(&modes); + auto error = mDisplayData[displayId].hwcDisplay->getColorModes(&modes); if (error != HWC2::Error::None) { ALOGE("getColorModes failed for display %d: %s (%d)", displayId, to_string(error).c_str(), static_cast(error)); @@ -471,7 +456,7 @@ status_t HWComposer::prepare(DisplayDevice& displayDevice) { return UNKNOWN_ERROR; } if (state == 1) { //Present Succeeded. - std::unordered_map, sp> releaseFences; + std::unordered_map> releaseFences; error = hwcDisplay->getReleaseFences(&releaseFences); displayData.releaseFences = std::move(releaseFences); displayData.lastPresentFence = outPresentFence; @@ -490,8 +475,7 @@ status_t HWComposer::prepare(DisplayDevice& displayDevice) { return BAD_INDEX; } - std::unordered_map, HWC2::Composition> - changedTypes; + std::unordered_map changedTypes; changedTypes.reserve(numTypes); error = hwcDisplay->getChangedCompositionTypes(&changedTypes); if (error != HWC2::Error::None) { @@ -503,8 +487,7 @@ status_t HWComposer::prepare(DisplayDevice& displayDevice) { displayData.displayRequests = static_cast(0); - std::unordered_map, HWC2::LayerRequest> - layerRequests; + std::unordered_map layerRequests; layerRequests.reserve(numRequests); error = hwcDisplay->getRequests(&displayData.displayRequests, &layerRequests); @@ -598,7 +581,7 @@ sp HWComposer::getPresentFence(int32_t displayId) const { } sp HWComposer::getLayerReleaseFence(int32_t displayId, - const std::shared_ptr& layer) const { + HWC2::Layer* layer) const { if (!isValidDisplay(displayId)) { ALOGE("getLayerReleaseFence: Invalid display"); return Fence::NO_FENCE; @@ -639,7 +622,7 @@ status_t HWComposer::presentAndGetReleaseFences(int32_t displayId) { return UNKNOWN_ERROR; } - std::unordered_map, sp> releaseFences; + std::unordered_map> releaseFences; error = hwcDisplay->getReleaseFences(&releaseFences); if (error != HWC2::Error::None) { ALOGE("presentAndGetReleaseFences: Failed to get release fences " @@ -787,6 +770,8 @@ void HWComposer::disconnectDisplay(int displayId) { auto hwcId = displayData.hwcDisplay->getId(); mHwcDisplaySlots.erase(hwcId); displayData.reset(); + + mHwcDevice->destroyDisplay(hwcId); } status_t HWComposer::setOutputBuffer(int32_t displayId, @@ -885,7 +870,7 @@ void HWComposer::dump(String8& result) const { HWComposer::DisplayData::DisplayData() : hasClientComposition(false), hasDeviceComposition(false), - hwcDisplay(), + hwcDisplay(nullptr), lastPresentFence(Fence::NO_FENCE), outbufHandle(nullptr), outbufAcquireFence(Fence::NO_FENCE), diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index 3dfb65b1e3..3640bb5a98 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -65,23 +65,14 @@ class String8; class HWComposer { public: - class EventHandler { - friend class HWComposer; - virtual void onVSyncReceived( - HWComposer* composer, int32_t disp, nsecs_t timestamp) = 0; - virtual void onHotplugReceived(HWComposer* composer, int32_t disp, bool connected) = 0; - virtual void onInvalidateReceived(HWComposer* composer) = 0; - protected: - virtual ~EventHandler() {} - }; - // Uses the named composer service. Valid choices for normal use // are 'default' and 'vr'. HWComposer(const std::string& serviceName); ~HWComposer(); - void setEventHandler(EventHandler* handler); + void registerCallback(HWC2::ComposerCallback* callback, + int32_t sequenceId); bool hasCapability(HWC2::Capability capability) const; @@ -91,7 +82,9 @@ public: android_pixel_format_t* format, int32_t* outId); // Attempts to create a new layer on this display - std::shared_ptr createLayer(int32_t displayId); + HWC2::Layer* createLayer(int32_t displayId); + // Destroy a previously created layer + void destroyLayer(int32_t displayId, HWC2::Layer* layer); // Asks the HAL what it can do status_t prepare(DisplayDevice& displayDevice); @@ -126,7 +119,7 @@ public: // Get last release fence for the given layer sp getLayerReleaseFence(int32_t displayId, - const std::shared_ptr& layer) const; + HWC2::Layer* layer) const; // Set the output buffer and acquire fence for a virtual display. // Returns INVALID_OPERATION if displayId is not a virtual display. @@ -142,6 +135,12 @@ public: // Events handling --------------------------------------------------------- + // Returns true if successful, false otherwise. The + // DisplayDevice::DisplayType of the display is returned as an output param. + bool onVsync(hwc2_display_t displayId, int64_t timestamp, + int32_t* outDisplay); + void onHotplug(hwc2_display_t displayId, HWC2::Connection connection); + void setVsyncEnabled(int32_t displayId, HWC2::Vsync enabled); // Query display parameters. Pass in a display index (e.g. @@ -169,19 +168,11 @@ public: private: static const int32_t VIRTUAL_DISPLAY_ID_BASE = 2; - void loadHwcModule(const std::string& serviceName); - bool isValidDisplay(int32_t displayId) const; static void validateChange(HWC2::Composition from, HWC2::Composition to); struct cb_context; - void invalidate(const std::shared_ptr& display); - void vsync(const std::shared_ptr& display, - int64_t timestamp); - void hotplug(const std::shared_ptr& display, - HWC2::Connection connected); - struct DisplayData { DisplayData(); ~DisplayData(); @@ -189,11 +180,10 @@ private: bool hasClientComposition; bool hasDeviceComposition; - std::shared_ptr hwcDisplay; + HWC2::Display* hwcDisplay; HWC2::DisplayRequest displayRequests; sp lastPresentFence; // signals when the last set op retires - std::unordered_map, sp> - releaseFences; + std::unordered_map> releaseFences; buffer_handle_t outbufHandle; sp outbufAcquireFence; mutable std::unordered_mapsetVsyncEnabled(HWC_DISPLAY_PRIMARY, mVsyncEnabled); -#else - mFlinger->eventControl(HWC_DISPLAY_PRIMARY, SurfaceFlinger::EVENT_VSYNC, - mVsyncEnabled); -#endif + enum class VsyncState {Unset, On, Off}; + auto currentVsyncState = VsyncState::Unset; while (true) { - status_t err = mCond.wait(mMutex); - if (err != NO_ERROR) { - ALOGE("error waiting for new events: %s (%d)", - strerror(-err), err); - return false; + auto requestedVsyncState = VsyncState::On; + { + Mutex::Autolock lock(mMutex); + requestedVsyncState = + mVsyncEnabled ? VsyncState::On : VsyncState::Off; + while (currentVsyncState == requestedVsyncState) { + status_t err = mCond.wait(mMutex); + if (err != NO_ERROR) { + ALOGE("error waiting for new events: %s (%d)", + strerror(-err), err); + return false; + } + requestedVsyncState = + mVsyncEnabled ? VsyncState::On : VsyncState::Off; + } } - if (vsyncEnabled != mVsyncEnabled) { + bool enable = requestedVsyncState == VsyncState::On; #ifdef USE_HWC2 - mFlinger->setVsyncEnabled(HWC_DISPLAY_PRIMARY, mVsyncEnabled); + mFlinger->setVsyncEnabled(HWC_DISPLAY_PRIMARY, enable); #else - mFlinger->eventControl(HWC_DISPLAY_PRIMARY, - SurfaceFlinger::EVENT_VSYNC, mVsyncEnabled); + mFlinger->eventControl(HWC_DISPLAY_PRIMARY, + SurfaceFlinger::EVENT_VSYNC, enable); #endif - vsyncEnabled = mVsyncEnabled; - } + currentVsyncState = requestedVsyncState; } return false; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 3903a5546f..54d4cbd613 100755 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -201,6 +201,12 @@ Layer::~Layer() { } mFlinger->deleteTextureAsync(mTextureName); mFrameTracker.logAndResetStats(mName); + +#ifdef USE_HWC2 + ALOGE_IF(!mHwcLayers.empty(), + "Found stale hardware composer layers when destroying " + "surface flinger layer"); +#endif } // --------------------------------------------------------------------------- @@ -303,7 +309,7 @@ void Layer::onRemoved() { mSurfaceFlingerConsumer->abandon(); #ifdef USE_HWC2 - clearHwcLayers(); + destroyAllHwcLayers(); #endif for (const auto& child : mCurrentChildren) { @@ -364,6 +370,48 @@ sp Layer::getProducer() const { // h/w composer set-up // --------------------------------------------------------------------------- +#ifdef USE_HWC2 +bool Layer::createHwcLayer(HWComposer* hwc, int32_t hwcId) { + LOG_ALWAYS_FATAL_IF(mHwcLayers.count(hwcId) != 0, + "Already have a layer for hwcId %d", hwcId); + HWC2::Layer* layer = hwc->createLayer(hwcId); + if (!layer) { + return false; + } + HWCInfo& hwcInfo = mHwcLayers[hwcId]; + hwcInfo.hwc = hwc; + hwcInfo.layer = layer; + layer->setLayerDestroyedListener( + [this, hwcId] (HWC2::Layer* /*layer*/){mHwcLayers.erase(hwcId);}); + return true; +} + +void Layer::destroyHwcLayer(int32_t hwcId) { + if (mHwcLayers.count(hwcId) == 0) { + return; + } + auto& hwcInfo = mHwcLayers[hwcId]; + LOG_ALWAYS_FATAL_IF(hwcInfo.layer == nullptr, + "Attempt to destroy null layer"); + LOG_ALWAYS_FATAL_IF(hwcInfo.hwc == nullptr, "Missing HWComposer"); + hwcInfo.hwc->destroyLayer(hwcId, hwcInfo.layer); + // The layer destroyed listener should have cleared the entry from + // mHwcLayers. Verify that. + LOG_ALWAYS_FATAL_IF(mHwcLayers.count(hwcId) != 0, + "Stale layer entry in mHwcLayers"); +} + +void Layer::destroyAllHwcLayers() { + size_t numLayers = mHwcLayers.size(); + for (size_t i = 0; i < numLayers; ++i) { + LOG_ALWAYS_FATAL_IF(mHwcLayers.empty(), "destroyAllHwcLayers failed"); + destroyHwcLayer(mHwcLayers.begin()->first); + } + LOG_ALWAYS_FATAL_IF(!mHwcLayers.empty(), + "All hardware composer layers should have been destroyed"); +} +#endif + Rect Layer::getContentCrop() const { // this is the crop rectangle that applies to the buffer // itself (as opposed to the window) diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 8df8c49bbe..1b7d0759d5 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -447,37 +447,21 @@ public: #ifdef USE_HWC2 // ----------------------------------------------------------------------- + bool createHwcLayer(HWComposer* hwc, int32_t hwcId); + void destroyHwcLayer(int32_t hwcId); + void destroyAllHwcLayers(); + bool hasHwcLayer(int32_t hwcId) { - if (mHwcLayers.count(hwcId) == 0) { - return false; - } - if (mHwcLayers[hwcId].layer->isAbandoned()) { - ALOGI("Erasing abandoned layer %s on %d", mName.string(), hwcId); - mHwcLayers.erase(hwcId); - return false; - } - return true; + return mHwcLayers.count(hwcId) > 0; } - std::shared_ptr getHwcLayer(int32_t hwcId) { + HWC2::Layer* getHwcLayer(int32_t hwcId) { if (mHwcLayers.count(hwcId) == 0) { return nullptr; } return mHwcLayers[hwcId].layer; } - void setHwcLayer(int32_t hwcId, std::shared_ptr&& layer) { - if (layer) { - mHwcLayers[hwcId].layer = layer; - } else { - mHwcLayers.erase(hwcId); - } - } - - void clearHwcLayers() { - mHwcLayers.clear(); - } - #endif // ----------------------------------------------------------------------- @@ -766,12 +750,14 @@ private: // HWC items, accessed from the main thread struct HWCInfo { HWCInfo() - : layer(), + : hwc(nullptr), + layer(nullptr), forceClientComposition(false), compositionType(HWC2::Composition::Invalid), clearClientTarget(false) {} - std::shared_ptr layer; + HWComposer* hwc; + HWC2::Layer* layer; bool forceClientComposition; HWC2::Composition compositionType; bool clearClientTarget; diff --git a/services/surfaceflinger/RenderEngine/RenderEngine.cpp b/services/surfaceflinger/RenderEngine/RenderEngine.cpp index ac2d8b2aba..57f468d2fa 100644 --- a/services/surfaceflinger/RenderEngine/RenderEngine.cpp +++ b/services/surfaceflinger/RenderEngine/RenderEngine.cpp @@ -64,7 +64,7 @@ RenderEngine* RenderEngine::create(EGLDisplay display, int hwcFormat, uint32_t f "EGL_ANDROIDX_no_config_context") && !findExtension(eglQueryStringImplementationANDROID(display, EGL_EXTENSIONS), "EGL_KHR_no_config_context")) { - config = chooseEglConfig(display, hwcFormat); + config = chooseEglConfig(display, hwcFormat, /*logConfig*/ true); } EGLint renderableType = 0; @@ -108,7 +108,7 @@ RenderEngine* RenderEngine::create(EGLDisplay display, int hwcFormat, uint32_t f EGLConfig dummyConfig = config; if (dummyConfig == EGL_NO_CONFIG) { - dummyConfig = chooseEglConfig(display, hwcFormat); + dummyConfig = chooseEglConfig(display, hwcFormat, /*logConfig*/ true); } EGLint attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE }; EGLSurface dummy = eglCreatePbufferSurface(display, dummyConfig, attribs); @@ -406,7 +406,8 @@ static status_t selectEGLConfig(EGLDisplay display, EGLint format, return err; } -EGLConfig RenderEngine::chooseEglConfig(EGLDisplay display, int format) { +EGLConfig RenderEngine::chooseEglConfig(EGLDisplay display, int format, + bool logConfig) { status_t err; EGLConfig config; @@ -427,18 +428,20 @@ EGLConfig RenderEngine::chooseEglConfig(EGLDisplay display, int format) { } } - // print some debugging info - EGLint r,g,b,a; - eglGetConfigAttrib(display, config, EGL_RED_SIZE, &r); - eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &g); - eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &b); - eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &a); - ALOGI("EGL information:"); - ALOGI("vendor : %s", eglQueryString(display, EGL_VENDOR)); - ALOGI("version : %s", eglQueryString(display, EGL_VERSION)); - ALOGI("extensions: %s", eglQueryString(display, EGL_EXTENSIONS)); - ALOGI("Client API: %s", eglQueryString(display, EGL_CLIENT_APIS)?:"Not Supported"); - ALOGI("EGLSurface: %d-%d-%d-%d, config=%p", r, g, b, a, config); + if (logConfig) { + // print some debugging info + EGLint r,g,b,a; + eglGetConfigAttrib(display, config, EGL_RED_SIZE, &r); + eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &g); + eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &b); + eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &a); + ALOGI("EGL information:"); + ALOGI("vendor : %s", eglQueryString(display, EGL_VENDOR)); + ALOGI("version : %s", eglQueryString(display, EGL_VERSION)); + ALOGI("extensions: %s", eglQueryString(display, EGL_EXTENSIONS)); + ALOGI("Client API: %s", eglQueryString(display, EGL_CLIENT_APIS)?:"Not Supported"); + ALOGI("EGLSurface: %d-%d-%d-%d, config=%p", r, g, b, a, config); + } return config; } diff --git a/services/surfaceflinger/RenderEngine/RenderEngine.h b/services/surfaceflinger/RenderEngine/RenderEngine.h index 56f582755e..954457946e 100644 --- a/services/surfaceflinger/RenderEngine/RenderEngine.h +++ b/services/surfaceflinger/RenderEngine/RenderEngine.h @@ -64,7 +64,7 @@ public: }; static RenderEngine* create(EGLDisplay display, int hwcFormat, uint32_t featureFlags); - static EGLConfig chooseEglConfig(EGLDisplay display, int format); + static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig); void primeCache() const; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 4154d6a87f..9dfdaa1d99 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -77,6 +77,7 @@ #include "MonitoredProducer.h" #include "SurfaceFlinger.h" +#include "DisplayHardware/ComposerHal.h" #include "DisplayHardware/FramebufferSurface.h" #include "DisplayHardware/HWComposer.h" #include "DisplayHardware/VirtualDisplaySurface.h" @@ -101,10 +102,24 @@ EGLAPI const char* eglQueryStringImplementationANDROID(EGLDisplay dpy, EGLint na namespace android { - using namespace android::hardware::configstore; using namespace android::hardware::configstore::V1_0; +namespace { +class ConditionalLock { +public: + ConditionalLock(Mutex& mutex, bool lock) : mMutex(mutex), mLocked(lock) { + if (lock) { + mMutex.lock(); + } + } + ~ConditionalLock() { if (mLocked) mMutex.unlock(); } +private: + Mutex& mMutex; + bool mLocked; +}; +} // namespace anonymous + // --------------------------------------------------------------------------- const String16 sHardwareTest("android.permission.HARDWARE_TEST"); @@ -147,9 +162,6 @@ SurfaceFlinger::SurfaceFlinger() mLayersRemoved(false), mLayersAdded(false), mRepaintEverything(0), - mHwc(nullptr), - mRealHwc(nullptr), - mVrHwc(nullptr), mHwcServiceName(getHwcServiceName()), mRenderEngine(nullptr), mBootTime(systemTime()), @@ -177,7 +189,9 @@ SurfaceFlinger::SurfaceFlinger() mTotalTime(0), mLastSwapTime(0), mNumLayers(0), - mVrFlingerRequestsDisplay(false) + mVrFlingerRequestsDisplay(false), + mMainThreadId(std::this_thread::get_id()), + mComposerSequenceId(0) { ALOGI("SurfaceFlinger is starting"); @@ -583,48 +597,46 @@ void SurfaceFlinger::init() { ALOGI("Phase offset NS: %" PRId64 "", vsyncPhaseOffsetNs); - { // Autolock scope - Mutex::Autolock _l(mStateLock); + Mutex::Autolock _l(mStateLock); - // initialize EGL for the default display - mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); - eglInitialize(mEGLDisplay, NULL, NULL); - - // start the EventThread - sp vsyncSrc = new DispSyncSource(&mPrimaryDispSync, - vsyncPhaseOffsetNs, true, "app"); - mEventThread = new EventThread(vsyncSrc, *this, false); - sp sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync, - sfVsyncPhaseOffsetNs, true, "sf"); - mSFEventThread = new EventThread(sfVsyncSrc, *this, true); - mEventQueue.setEventThread(mSFEventThread); + // initialize EGL for the default display + mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + eglInitialize(mEGLDisplay, NULL, NULL); - // set EventThread and SFEventThread to SCHED_FIFO to minimize jitter - struct sched_param param = {0}; - param.sched_priority = 2; - if (sched_setscheduler(mSFEventThread->getTid(), SCHED_FIFO, ¶m) != 0) { - ALOGE("Couldn't set SCHED_FIFO for SFEventThread"); - } - if (sched_setscheduler(mEventThread->getTid(), SCHED_FIFO, ¶m) != 0) { - ALOGE("Couldn't set SCHED_FIFO for EventThread"); - } + // start the EventThread + sp vsyncSrc = new DispSyncSource(&mPrimaryDispSync, + vsyncPhaseOffsetNs, true, "app"); + mEventThread = new EventThread(vsyncSrc, *this, false); + sp sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync, + sfVsyncPhaseOffsetNs, true, "sf"); + mSFEventThread = new EventThread(sfVsyncSrc, *this, true); + mEventQueue.setEventThread(mSFEventThread); - // Get a RenderEngine for the given display / config (can't fail) - mRenderEngine = RenderEngine::create(mEGLDisplay, - HAL_PIXEL_FORMAT_RGBA_8888, - hasWideColorDisplay ? RenderEngine::WIDE_COLOR_SUPPORT : 0); + // set EventThread and SFEventThread to SCHED_FIFO to minimize jitter + struct sched_param param = {0}; + param.sched_priority = 2; + if (sched_setscheduler(mSFEventThread->getTid(), SCHED_FIFO, ¶m) != 0) { + ALOGE("Couldn't set SCHED_FIFO for SFEventThread"); + } + if (sched_setscheduler(mEventThread->getTid(), SCHED_FIFO, ¶m) != 0) { + ALOGE("Couldn't set SCHED_FIFO for EventThread"); } - // Drop the state lock while we initialize the hardware composer. We drop - // the lock because on creation, it will call back into SurfaceFlinger to - // initialize the primary display. - LOG_ALWAYS_FATAL_IF(mVrFlingerRequestsDisplay, - "Starting with vr flinger active is not currently supported."); - mRealHwc = new HWComposer(mHwcServiceName); - mHwc = mRealHwc; - mHwc->setEventHandler(static_cast(this)); + // Get a RenderEngine for the given display / config (can't fail) + mRenderEngine = RenderEngine::create(mEGLDisplay, + HAL_PIXEL_FORMAT_RGBA_8888, + hasWideColorDisplay ? RenderEngine::WIDE_COLOR_SUPPORT : 0); - Mutex::Autolock _l(mStateLock); + // retrieve the EGL context that was selected/created + mEGLContext = mRenderEngine->getEGLContext(); + + LOG_ALWAYS_FATAL_IF(mEGLContext == EGL_NO_CONTEXT, + "couldn't create EGLContext"); + + LOG_ALWAYS_FATAL_IF(mVrFlingerRequestsDisplay, + "Starting with vr flinger active is not currently supported."); + mHwc.reset(new HWComposer(mHwcServiceName)); + mHwc->registerCallback(this, mComposerSequenceId); if (useVrFlinger) { auto vrFlingerRequestDisplayCallback = [this] (bool requestDisplay) { @@ -639,16 +651,6 @@ void SurfaceFlinger::init() { } } - // retrieve the EGL context that was selected/created - mEGLContext = mRenderEngine->getEGLContext(); - - LOG_ALWAYS_FATAL_IF(mEGLContext == EGL_NO_CONTEXT, - "couldn't create EGLContext"); - - // make the GLContext current so that we can create textures when creating - // Layers (which may happens before we render something) - getDefaultDisplayDeviceLocked()->makeCurrent(mEGLDisplay, mEGLContext); - mEventControlThread = new EventControlThread(this); mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY); @@ -1218,11 +1220,16 @@ void SurfaceFlinger::resyncWithRateLimit() { sLastResyncAttempted = now; } -void SurfaceFlinger::onVSyncReceived(HWComposer* composer, int32_t type, - nsecs_t timestamp) { +void SurfaceFlinger::onVsyncReceived(int32_t sequenceId, + hwc2_display_t displayId, int64_t timestamp) { Mutex::Autolock lock(mStateLock); - // Ignore any vsyncs from the non-active hardware composer. - if (composer != mHwc) { + // Ignore any vsyncs from a previous hardware composer. + if (sequenceId != mComposerSequenceId) { + return; + } + + int32_t type; + if (!mHwc->onVsync(displayId, timestamp, &type)) { return; } @@ -1230,7 +1237,7 @@ void SurfaceFlinger::onVSyncReceived(HWComposer* composer, int32_t type, { // Scope for the lock Mutex::Autolock _l(mHWVsyncLock); - if (type == 0 && mPrimaryHWVsyncEnabled) { + if (type == DisplayDevice::DISPLAY_PRIMARY && mPrimaryHWVsyncEnabled) { needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp); } } @@ -1248,7 +1255,7 @@ void SurfaceFlinger::getCompositorTiming(CompositorTiming* compositorTiming) { } void SurfaceFlinger::createDefaultDisplayDevice() { - const int32_t type = DisplayDevice::DISPLAY_PRIMARY; + const DisplayDevice::DisplayType type = DisplayDevice::DISPLAY_PRIMARY; wp token = mBuiltinDisplays[type]; // All non-virtual displays are currently considered secure. @@ -1279,28 +1286,49 @@ void SurfaceFlinger::createDefaultDisplayDevice() { mDisplays.add(token, hw); setActiveColorModeInternal(hw, HAL_COLOR_MODE_NATIVE); hw->setCompositionDataSpace(HAL_DATASPACE_UNKNOWN); -} -void SurfaceFlinger::onHotplugReceived(HWComposer* composer, int32_t disp, bool connected) { - ALOGV("onHotplugReceived(%d, %s)", disp, connected ? "true" : "false"); + // Add the primary display token to mDrawingState so we don't try to + // recreate the DisplayDevice for the primary display. + mDrawingState.displays.add(token, DisplayDeviceState(type, true)); - if (composer->isUsingVrComposer()) { - // We handle initializing the primary display device for the VR - // window manager hwc explicitly at the time of transition. - if (disp != DisplayDevice::DISPLAY_PRIMARY) { - ALOGE("External displays are not supported by the vr hardware composer."); + // make the GLContext current so that we can create textures when creating + // Layers (which may happens before we render something) + hw->makeCurrent(mEGLDisplay, mEGLContext); +} + +void SurfaceFlinger::onHotplugReceived(int32_t sequenceId, + hwc2_display_t display, HWC2::Connection connection, + bool primaryDisplay) { + ALOGV("onHotplugReceived(%d, %" PRIu64 ", %s, %s)", + sequenceId, display, + connection == HWC2::Connection::Connected ? + "connected" : "disconnected", + primaryDisplay ? "primary" : "external"); + + // Only lock if we're not on the main thread. This function is normally + // called on a hwbinder thread, but for the primary display it's called on + // the main thread with the state lock already held, so don't attempt to + // acquire it here. + ConditionalLock lock(mStateLock, + std::this_thread::get_id() != mMainThreadId); + + if (primaryDisplay) { + mHwc->onHotplug(display, connection); + if (!mBuiltinDisplays[DisplayDevice::DISPLAY_PRIMARY].get()) { + createBuiltinDisplayLocked(DisplayDevice::DISPLAY_PRIMARY); } - return; - } - - if (disp == DisplayDevice::DISPLAY_PRIMARY) { - Mutex::Autolock lock(mStateLock); - createBuiltinDisplayLocked(DisplayDevice::DISPLAY_PRIMARY); createDefaultDisplayDevice(); } else { + if (sequenceId != mComposerSequenceId) { + return; + } + if (mHwc->isUsingVrComposer()) { + ALOGE("External displays are not supported by the vr hardware composer."); + return; + } + mHwc->onHotplug(display, connection); auto type = DisplayDevice::DISPLAY_EXTERNAL; - Mutex::Autolock _l(mStateLock); - if (connected) { + if (connection == HWC2::Connection::Connected) { createBuiltinDisplayLocked(type); } else { mCurrentState.displays.removeItem(mBuiltinDisplays[type]); @@ -1312,46 +1340,31 @@ void SurfaceFlinger::onHotplugReceived(HWComposer* composer, int32_t disp, bool } } -void SurfaceFlinger::onInvalidateReceived(HWComposer* composer) { +void SurfaceFlinger::onRefreshReceived(int sequenceId, + hwc2_display_t /*display*/) { Mutex::Autolock lock(mStateLock); - if (composer == mHwc) { - repaintEverything(); - } else { - // This isn't from our current hardware composer. If it's a callback - // from the real composer, forward the refresh request to vr - // flinger. Otherwise ignore it. - if (!composer->isUsingVrComposer()) { - mVrFlinger->OnHardwareComposerRefresh(); - } + if (sequenceId != mComposerSequenceId) { + return; } + repaintEverything(); } void SurfaceFlinger::setVsyncEnabled(int disp, int enabled) { ATRACE_CALL(); + Mutex::Autolock lock(mStateLock); getHwComposer().setVsyncEnabled(disp, enabled ? HWC2::Vsync::Enable : HWC2::Vsync::Disable); } // Note: it is assumed the caller holds |mStateLock| when this is called -void SurfaceFlinger::resetHwcLocked() { +void SurfaceFlinger::resetDisplayState() { disableHardwareVsync(true); - clearHwcLayers(mDrawingState.layersSortedByZ); - clearHwcLayers(mCurrentState.layersSortedByZ); - for (size_t disp = 0; disp < mDisplays.size(); ++disp) { - clearHwcLayers(mDisplays[disp]->getVisibleLayersSortedByZ()); - } // Clear the drawing state so that the logic inside of // handleTransactionLocked will fire. It will determine the delta between // mCurrentState and mDrawingState and re-apply all changes when we make the // transition. mDrawingState.displays.clear(); - // Release virtual display hwcId during vr mode transition. - for (size_t displayId = 0; displayId < mDisplays.size(); ++displayId) { - const sp& displayDevice = mDisplays[displayId]; - if (displayDevice->getDisplayType() == DisplayDevice::DISPLAY_VIRTUAL) { - displayDevice->disconnect(getHwComposer()); - } - } + eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); mDisplays.clear(); } @@ -1363,57 +1376,54 @@ void SurfaceFlinger::updateVrFlinger() { return; } - if (vrFlingerRequestsDisplay && !mVrHwc) { - // Construct new HWComposer without holding any locks. - mVrHwc = new HWComposer("vr"); - - // Set up the event handlers. This step is neccessary to initialize the internal state of - // the hardware composer object properly. Our callbacks are designed such that if they are - // triggered between now and the point where the display is properly re-initialized, they - // will not have any effect, so this is safe to do here, before the lock is aquired. - mVrHwc->setEventHandler(static_cast(this)); - ALOGV("Vr HWC created"); + if (vrFlingerRequestsDisplay && !mHwc->getComposer()->isRemote()) { + ALOGE("Vr flinger is only supported for remote hardware composer" + " service connections. Ignoring request to transition to vr" + " flinger."); + mVrFlingerRequestsDisplay = false; + return; } Mutex::Autolock _l(mStateLock); - if (vrFlingerRequestsDisplay) { - resetHwcLocked(); - - mHwc = mVrHwc; - mVrFlinger->GrantDisplayOwnership(); + int currentDisplayPowerMode = getDisplayDeviceLocked( + mBuiltinDisplays[DisplayDevice::DISPLAY_PRIMARY])->getPowerMode(); - } else { + if (!vrFlingerRequestsDisplay) { mVrFlinger->SeizeDisplayOwnership(); + } - resetHwcLocked(); + resetDisplayState(); + mHwc.reset(); // Delete the current instance before creating the new one + mHwc.reset(new HWComposer( + vrFlingerRequestsDisplay ? "vr" : mHwcServiceName)); + mHwc->registerCallback(this, ++mComposerSequenceId); - mHwc = mRealHwc; + LOG_ALWAYS_FATAL_IF(!mHwc->getComposer()->isRemote(), + "Switched to non-remote hardware composer"); + + if (vrFlingerRequestsDisplay) { + mVrFlinger->GrantDisplayOwnership(); + } else { enableHardwareVsync(); } mVisibleRegionsDirty = true; invalidateHwcGeometry(); - // Explicitly re-initialize the primary display. This is because some other - // parts of this class rely on the primary display always being available. - createDefaultDisplayDevice(); - // Re-enable default display. - sp requestMessage = new LambdaMessage([&]() { - sp hw(getDisplayDevice(mBuiltinDisplays[DisplayDevice::DISPLAY_PRIMARY])); - setPowerModeInternal(hw, HWC_POWER_MODE_NORMAL); + sp hw(getDisplayDeviceLocked( + mBuiltinDisplays[DisplayDevice::DISPLAY_PRIMARY])); + setPowerModeInternal(hw, currentDisplayPowerMode, /*stateLockHeld*/ true); - // Reset the timing values to account for the period of the swapped in HWC - const auto& activeConfig = mHwc->getActiveConfig(HWC_DISPLAY_PRIMARY); - const nsecs_t period = activeConfig->getVsyncPeriod(); - mAnimFrameTracker.setDisplayRefreshPeriod(period); + // Reset the timing values to account for the period of the swapped in HWC + const auto& activeConfig = mHwc->getActiveConfig(HWC_DISPLAY_PRIMARY); + const nsecs_t period = activeConfig->getVsyncPeriod(); + mAnimFrameTracker.setDisplayRefreshPeriod(period); - // Use phase of 0 since phase is not known. - // Use latency of 0, which will snap to the ideal latency. - setCompositorTimingSnapped(0, period, 0); - }); - postMessageAsync(requestMessage); + // Use phase of 0 since phase is not known. + // Use latency of 0, which will snap to the ideal latency. + setCompositorTimingSnapped(0, period, 0); android_atomic_or(1, &mRepaintEverything); setTransactionFlags(eDisplayTransactionNeeded); @@ -1749,15 +1759,14 @@ void SurfaceFlinger::rebuildLayerStacks() { } else { // Clear out the HWC layer if this layer was // previously visible, but no longer is - layer->setHwcLayer(displayDevice->getHwcDisplayId(), - nullptr); + layer->destroyHwcLayer( + displayDevice->getHwcDisplayId()); } } else { // WM changes displayDevice->layerStack upon sleep/awake. // Here we make sure we delete the HWC layers even if // WM changed their layer stack. - layer->setHwcLayer(displayDevice->getHwcDisplayId(), - nullptr); + layer->destroyHwcLayer(displayDevice->getHwcDisplayId()); } }); } @@ -1872,10 +1881,7 @@ void SurfaceFlinger::setUpHWComposer() { for (size_t i = 0; i < currentLayers.size(); i++) { const auto& layer = currentLayers[i]; if (!layer->hasHwcLayer(hwcId)) { - auto hwcLayer = mHwc->createLayer(hwcId); - if (hwcLayer) { - layer->setHwcLayer(hwcId, std::move(hwcLayer)); - } else { + if (!layer->createHwcLayer(mHwc.get(), hwcId)) { layer->forceClientComposition(hwcId); continue; } @@ -2161,7 +2167,7 @@ void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags) if (state.surface != NULL) { // Allow VR composer to use virtual displays. - if (mUseHwcVirtualDisplays || mHwc == mVrHwc) { + if (mUseHwcVirtualDisplays || mHwc->isUsingVrComposer()) { int width = 0; int status = state.surface->query( NATIVE_WINDOW_WIDTH, &width); @@ -3285,7 +3291,8 @@ void SurfaceFlinger::onInitializeDisplays() { d.height = 0; displays.add(d); setTransactionState(state, displays, 0); - setPowerModeInternal(getDisplayDevice(d.token), HWC_POWER_MODE_NORMAL); + setPowerModeInternal(getDisplayDevice(d.token), HWC_POWER_MODE_NORMAL, + /*stateLockHeld*/ false); const auto& activeConfig = mHwc->getActiveConfig(HWC_DISPLAY_PRIMARY); const nsecs_t period = activeConfig->getVsyncPeriod(); @@ -3311,7 +3318,7 @@ void SurfaceFlinger::initializeDisplays() { } void SurfaceFlinger::setPowerModeInternal(const sp& hw, - int mode) { + int mode, bool stateLockHeld) { ALOGD("Set power mode=%d, type=%d flinger=%p", mode, hw->getDisplayType(), this); int32_t type = hw->getDisplayType(); @@ -3328,7 +3335,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& hw, } if (mInterceptor.isEnabled()) { - Mutex::Autolock _l(mStateLock); + ConditionalLock lock(mStateLock, !stateLockHeld); ssize_t idx = mCurrentState.displays.indexOfKey(hw->getDisplayToken()); if (idx < 0) { ALOGW("Surface Interceptor SavePowerMode: invalid display token"); @@ -3414,7 +3421,8 @@ void SurfaceFlinger::setPowerMode(const sp& display, int mode) { ALOGW("Attempt to set power mode = %d for virtual display", mMode); } else { - mFlinger.setPowerModeInternal(hw, mMode); + mFlinger.setPowerModeInternal( + hw, mMode, /*stateLockHeld*/ false); } return true; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5123b58913..058f4a1d3b 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -60,13 +60,20 @@ #include "SurfaceInterceptor.h" #include "StartPropertySetThread.h" +#ifdef USE_HWC2 +#include "DisplayHardware/HWC2.h" #include "DisplayHardware/HWComposer.h" +#else +#include "DisplayHardware/HWComposer_hwc1.h" +#endif + #include "Effects/Daltonizer.h" #include #include #include #include +#include #include namespace android { @@ -99,7 +106,11 @@ enum { class SurfaceFlinger : public BnSurfaceComposer, private IBinder::DeathRecipient, +#ifdef USE_HWC2 + private HWC2::ComposerCallback +#else private HWComposer::EventHandler +#endif { public: @@ -314,11 +325,20 @@ private: virtual void onFirstRef(); /* ------------------------------------------------------------------------ - * HWComposer::EventHandler interface + * HWC2::ComposerCallback / HWComposer::EventHandler interface */ - virtual void onVSyncReceived(HWComposer* composer, int type, nsecs_t timestamp); - virtual void onHotplugReceived(HWComposer* composer, int disp, bool connected); - virtual void onInvalidateReceived(HWComposer* composer); +#ifdef USE_HWC2 + void onVsyncReceived(int32_t sequenceId, hwc2_display_t display, + int64_t timestamp) override; + void onHotplugReceived(int32_t sequenceId, hwc2_display_t display, + HWC2::Connection connection, + bool primaryDisplay) override; + void onRefreshReceived(int32_t sequenceId, hwc2_display_t display) override; +#else + void onVSyncReceived(HWComposer* composer, int type, nsecs_t timestamp) override; + void onHotplugReceived(HWComposer* composer, int disp, bool connected) override; + void onInvalidateReceived(HWComposer* composer) override; +#endif /* ------------------------------------------------------------------------ * Message handling @@ -333,7 +353,12 @@ private: // called on the main thread in response to setActiveConfig() void setActiveConfigInternal(const sp& hw, int mode); // called on the main thread in response to setPowerMode() +#ifdef USE_HWC2 + void setPowerModeInternal(const sp& hw, int mode, + bool stateLockHeld); +#else void setPowerModeInternal(const sp& hw, int mode); +#endif // Called on the main thread in response to setActiveColorMode() void setActiveColorModeInternal(const sp& hw, android_color_mode_t colorMode); @@ -591,13 +616,7 @@ private: /* ------------------------------------------------------------------------ * VrFlinger */ - template - void clearHwcLayers(const T& layers) { - for (size_t i = 0; i < layers.size(); ++i) { - layers[i]->clearHwcLayers(); - } - } - void resetHwcLocked(); + void resetDisplayState(); // Check to see if we should handoff to vr flinger. void updateVrFlinger(); @@ -624,13 +643,14 @@ private: // access must be protected by mInvalidateLock volatile int32_t mRepaintEverything; - // current, real and vr hardware composers. - HWComposer* mHwc; + // The current hardware composer interface. When switching into and out of + // vr, our HWComposer instance will be recreated. + std::unique_ptr mHwc; + #ifdef USE_HWC2 - HWComposer* mRealHwc; - HWComposer* mVrHwc; const std::string mHwcServiceName; // "default" for real use, something else for testing. #endif + // constant members (no synchronization needed for access) RenderEngine* mRenderEngine; nsecs_t mBootTime; @@ -644,10 +664,6 @@ private: EGLDisplay mEGLDisplay; sp mBuiltinDisplays[DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES]; -#ifdef USE_HWC2 - std::unique_ptr mVrFlinger; -#endif - // Can only accessed from the main thread, these members // don't need synchronization State mDrawingState{LayerVector::StateSet::Drawing}; @@ -769,8 +785,14 @@ private: status_t CheckTransactCodeCredentials(uint32_t code); #ifdef USE_HWC2 + std::unique_ptr mVrFlinger; std::atomic mVrFlingerRequestsDisplay; static bool useVrFlinger; + std::thread::id mMainThreadId; + // The composer sequence id is a monotonically increasing integer that we + // use to differentiate callbacks from different hardware composer + // instances. Each hardware composer instance gets a different sequence id. + int32_t mComposerSequenceId; #endif float mSaturation = 1.0f; diff --git a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp index 7aaa42aa4b..78a04e08e1 100644 --- a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp +++ b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp @@ -535,8 +535,8 @@ void SurfaceFlinger::init() { // Initialize the H/W composer object. There may or may not be an // actual hardware composer underneath. - mHwc = new HWComposer(this, - *static_cast(this)); + mHwc.reset(new HWComposer(this, + *static_cast(this))); // get a RenderEngine for the given display / config (can't fail) mRenderEngine = RenderEngine::create(mEGLDisplay, -- GitLab From e327e9adfc85028d77d1252746c9ee07ff99f0b3 Mon Sep 17 00:00:00 2001 From: Courtney Goeltzenleuchter Date: Mon, 7 Aug 2017 17:13:04 -0600 Subject: [PATCH 008/704] Fix DEQP failure regarding VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT: Needed to teach GetNativeDataspace about the new color space. Bug: 64402817 Test: adb -s HT7391A00040 -d shell am start \ -n com.drawelements.deqp/android.app.NativeActivity \ -e cmdLine '"deqp --deqp-case=dEQP-VK.wsi.android.swapchain.* \ --deqp-log-filename=/sdcard/dEQP-Log.qpa"' Change-Id: I3fe7fefff1f074656d766f2b24639adcdbfd2fb7 --- vulkan/libvulkan/swapchain.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 069fb36fd0..a346c0ac76 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -430,6 +430,8 @@ android_dataspace GetNativeDataspace(VkColorSpaceKHR colorspace) { return HAL_DATASPACE_DISPLAY_P3; case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT: return HAL_DATASPACE_V0_SCRGB_LINEAR; + case VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT: + return HAL_DATASPACE_V0_SCRGB; case VK_COLOR_SPACE_DCI_P3_LINEAR_EXT: return HAL_DATASPACE_DCI_P3_LINEAR; case VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT: -- GitLab From a7de594782a5a7fcfd0300117f362f173631e1af Mon Sep 17 00:00:00 2001 From: Kalle Raita Date: Tue, 8 Aug 2017 14:34:04 -0700 Subject: [PATCH 009/704] Use HWC_DISPLAY_PRIMARY and _EXTERNAL Some of the code in the SurfaceFlinger and VR composer assume that the composer has display IDs HWC_DISPLAY_PRIMARY and HWC_DISPLAY_EXTERNAL for those specific displays. Modify test to follow the requirement. Bug: 64446190 Test: Run the test on Marlin Change-Id: I7fef17158d62bd7cd242b97d37724a229f9c7418 --- .../tests/fakehwc/FakeComposerClient.cpp | 5 +-- .../tests/fakehwc/FakeComposerClient.h | 10 +++++ .../tests/fakehwc/SFFakeHwc_test.cpp | 40 +++++++++---------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp index 60916f3ab9..d97ffa3148 100644 --- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp +++ b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp @@ -36,7 +36,6 @@ #include constexpr Config NULL_DISPLAY_CONFIG = static_cast(0); -constexpr Display DEFAULT_DISPLAY = static_cast(1); using namespace sftest; @@ -168,7 +167,7 @@ void FakeComposerClient::enableCallback(bool enable) { ALOGV("enableCallback"); mCallbacksOn = enable; if (mCallbacksOn) { - mClient->onHotplug(DEFAULT_DISPLAY, IComposerCallback::Connection::CONNECTED); + mClient->onHotplug(PRIMARY_DISPLAY, IComposerCallback::Connection::CONNECTED); } } @@ -507,7 +506,7 @@ void FakeComposerClient::requestVSync(uint64_t vsyncTime) { if (mSurfaceComposer != nullptr) { mSurfaceComposer->injectVSync(timestamp); } else { - mClient->onVsync(DEFAULT_DISPLAY, timestamp); + mClient->onVsync(PRIMARY_DISPLAY, timestamp); } } } diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h index 294abb2c59..2a5a8ad03f 100644 --- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h +++ b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h @@ -19,6 +19,9 @@ #include "ComposerClient.h" #include "RenderState.h" +// Needed for display type/ID enums +#include + #include #include @@ -40,6 +43,13 @@ class SurfaceComposerClient; namespace sftest { +// NOTE: The ID's need to be exactly these. VR composer and parts of +// the SurfaceFlinger assume the display IDs to have these values +// despite the enum being documented as a display type. +// TODO: Reference to actual documentation +constexpr Display PRIMARY_DISPLAY = static_cast(HWC_DISPLAY_PRIMARY); +constexpr Display EXTERNAL_DISPLAY = static_cast(HWC_DISPLAY_EXTERNAL); + class FakeComposerClient : public ComposerBase { public: FakeComposerClient(); diff --git a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp index 8902ede301..9ac3331892 100644 --- a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp +++ b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp @@ -168,17 +168,15 @@ void DisplayTest::SetUp() { android::hardware::ProcessState::self()->startThreadPool(); android::ProcessState::self()->startThreadPool(); - EXPECT_CALL(*mMockComposer, getDisplayType(1, _)) + EXPECT_CALL(*mMockComposer, getDisplayType(PRIMARY_DISPLAY, _)) .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayType::PHYSICAL), Return(Error::NONE))); - // Seems to be doubled right now, once for display ID 1 and once for 0. This sounds fishy - // but encoding that here exactly. - EXPECT_CALL(*mMockComposer, getDisplayAttribute(1, 1, _, _)) - .Times(5) - .WillRepeatedly(Invoke(mMockComposer, &MockComposerClient::getDisplayAttributeFake)); - // TODO: Find out what code is generating the ID 0. - EXPECT_CALL(*mMockComposer, getDisplayAttribute(0, 1, _, _)) - .Times(5) + // Primary display will be queried twice for all 5 attributes. One + // set of queries comes from the SurfaceFlinger proper an the + // other set from the VR composer. + // TODO: Is VR composer always present? Change to atLeast(5)? + EXPECT_CALL(*mMockComposer, getDisplayAttribute(PRIMARY_DISPLAY, 1, _, _)) + .Times(2 * 5) .WillRepeatedly(Invoke(mMockComposer, &MockComposerClient::getDisplayAttributeFake)); startSurfaceFlinger(); @@ -207,31 +205,32 @@ void DisplayTest::TearDown() { TEST_F(DisplayTest, Hotplug) { ALOGD("DisplayTest::Hotplug"); - EXPECT_CALL(*mMockComposer, getDisplayType(2, _)) + EXPECT_CALL(*mMockComposer, getDisplayType(EXTERNAL_DISPLAY, _)) .Times(2) .WillRepeatedly(DoAll(SetArgPointee<1>(IComposerClient::DisplayType::PHYSICAL), Return(Error::NONE))); // The attribute queries will get done twice. This is for defaults - EXPECT_CALL(*mMockComposer, getDisplayAttribute(2, 1, _, _)) + EXPECT_CALL(*mMockComposer, getDisplayAttribute(EXTERNAL_DISPLAY, 1, _, _)) .Times(2 * 3) .WillRepeatedly(Invoke(mMockComposer, &MockComposerClient::getDisplayAttributeFake)); // ... and then special handling for dimensions. Specifying this // rules later means that gmock will try them first, i.e., // ordering of width/height vs. the default implementation for // other queries is significant. - EXPECT_CALL(*mMockComposer, getDisplayAttribute(2, 1, IComposerClient::Attribute::WIDTH, _)) + EXPECT_CALL(*mMockComposer, + getDisplayAttribute(EXTERNAL_DISPLAY, 1, IComposerClient::Attribute::WIDTH, _)) .Times(2) .WillRepeatedly(DoAll(SetArgPointee<3>(400), Return(Error::NONE))); - EXPECT_CALL(*mMockComposer, getDisplayAttribute(2, 1, IComposerClient::Attribute::HEIGHT, _)) + EXPECT_CALL(*mMockComposer, + getDisplayAttribute(EXTERNAL_DISPLAY, 1, IComposerClient::Attribute::HEIGHT, _)) .Times(2) .WillRepeatedly(DoAll(SetArgPointee<3>(200), Return(Error::NONE))); // TODO: Width and height queries are not actually called. Display // info returns dimensions 0x0 in display info. Why? - mMockComposer->hotplugDisplay(static_cast(2), - IComposerCallback::Connection::CONNECTED); + mMockComposer->hotplugDisplay(EXTERNAL_DISPLAY, IComposerCallback::Connection::CONNECTED); { sp display( @@ -257,13 +256,11 @@ TEST_F(DisplayTest, Hotplug) { } } - mMockComposer->hotplugDisplay(static_cast(2), - IComposerCallback::Connection::DISCONNECTED); + mMockComposer->hotplugDisplay(EXTERNAL_DISPLAY, IComposerCallback::Connection::DISCONNECTED); mMockComposer->clearFrames(); - mMockComposer->hotplugDisplay(static_cast(2), - IComposerCallback::Connection::CONNECTED); + mMockComposer->hotplugDisplay(EXTERNAL_DISPLAY, IComposerCallback::Connection::CONNECTED); { sp display( @@ -288,8 +285,7 @@ TEST_F(DisplayTest, Hotplug) { ASSERT_EQ(NO_ERROR, surfaceControl->show()); } } - mMockComposer->hotplugDisplay(static_cast(2), - IComposerCallback::Connection::DISCONNECTED); + mMockComposer->hotplugDisplay(EXTERNAL_DISPLAY, IComposerCallback::Connection::DISCONNECTED); } //////////////////////////////////////////////// @@ -664,7 +660,7 @@ TEST_F(TransactionTest, LayerSetMatrix) { {{0.f, 1.f, 1.f, 0.f}, HWC_TRANSFORM_FLIP_H_ROT_90, {64, 64, 128, 128}}, {{0.f, 1.f, 1.f, 0.f}, HWC_TRANSFORM_FLIP_V_ROT_90, {64, 64, 128, 128}}}; // clang-format on - constexpr int TEST_COUNT = sizeof(MATRIX_TESTS)/sizeof(matrixTestData); + constexpr int TEST_COUNT = sizeof(MATRIX_TESTS) / sizeof(matrixTestData); for (int i = 0; i < TEST_COUNT; i++) { // TODO: How to leverage the HWC2 stringifiers? -- GitLab From 4ac0e7e66b9b8b8e76bc7f53399eb97201c11a7c Mon Sep 17 00:00:00 2001 From: Steven Thomas Date: Wed, 26 Jul 2017 18:48:28 -0700 Subject: [PATCH 010/704] Use a separate hwcomposer hidl instance for vr flinger Improve robustness of vr flinger <--> surface flinger switching by having vr flinger use a separate hardware composer hidl instance instead of sharing the instance with surface flinger. Sharing the hardware composer instance has proven to be error prone, with situations where both the vr flinger thread and surface flinger main thread would write to the composer at the same time, causing hard to diagnose crashes (b/62925812). Instead of sharing the hardware composer instance, when switching to vr flinger we now delete the existing instance, create a new instance directed to the vr hardware composer shim, and vr flinger creates its own composer instance connected to the real hardware composer. By creating a separate composer instance for vr flinger, crashes like the ones found in b/62925812 are no longer impossible. Most of the changes in this commit are related to enabling surface flinger to delete HWComposer instances cleanly. In particular: - Previously the hardware composer callbacks (which come in on a hwbinder thread) would land in HWC2::Device and bubble up to the SurfaceFlinger object. But with the new behavior the HWC2::Device might be dead or in the process of being destroyed, so instead we have SurfaceFlinger receive the composer callbacks directly, and forward them to HWComposer and HWC2::Device. We include a composer id field in the callbacks so surface flinger can ignore stale callbacks from dead composer instances. - Object ownership for HWC2::Display and HWC2::Layer was shared by passing around shared_ptrs to these objects. This was problematic because they referenced and used the HWC2::Device, which can now be destroyed when switching to vr flinger. Simplify the ownership model by having HWC2::Device own (via unique_ptr<>) instances of HWC2::Display, which owns (again via unique_ptr<>) instances of HWC2::Layer. In cases where we previously passed std::shared_ptr<> to HWC2::Display or HWC2::Layer, instead pass non-owning HWC2::Display* and HWC2::Layer* pointers. This ensures clean composer instance teardown with no stale references to the deleted HWC2::Device. - When the hardware composer instance is destroyed and the HWC2::Layers are removed, notify the android::Layer via a callback, so it can remove the HWC2::Layer from its internal table of hardware composer layers. This removes the burden to explicitly clear out all hardware composer layers when switching to vr flinger, which has been a source of bugs. - We were missing an mStateLock lock in SurfaceFlinger::setVsyncEnabled(), which was necessary to ensure we were setting vsync on the correct hardware composer instance. Once that lock was added, surface flinger would sometimes deadlock when transitioning to vr flinger, because the surface flinger main thread would acquire mStateLock and then EventControlThread::mMutex, whereas the event control thread would acquire the locks in the opposite order. The changes in EventControlThread.cpp are to ensure it doesn't hold a lock on EventControlThread::mMutex while calling setVsyncEnabled(), to avoid the deadlock. I found that without a composer callback registered in vr flinger the vsync_event file wasn't getting vsync timestamps written, so vr flinger would get stuck in an infinite loop trying to parse a vsync timestamp. Since we need to have a callback anyway I changed the code in hardware_composer.cpp to get the vsync timestamp from the callback, as surface flinger does. I confirmed the timestamps are the same with either method, and this lets us remove some extra code for extracting the vsync timestamp that (probably) wasn't compatible with all devices we want to run on anyway. I also added a timeout to the vysnc wait so we'll see an error message in the log if we fail to wait for vsync, instead of looping forever. Bug: 62925812 Test: - Confirmed surface flinger <--> vr flinger switching is robust by switching devices on and off hundreds of times and observing no hardware composer related issues, surface flinger crashes, or hardware composer service crashes. - Confirmed 2d in vr works as before by going through the OOBE flow on a standalone. This also exercises virtual display creation and usage through surface flinger. - Added logs to confirm perfect layer/display cleanup when destroying hardware composer instances. - Tested normal 2d phone usage to confirm basic layer create/destroy functionality works as before. - Monitored surface flinger file descriptor usage across dozens of surface flinger <--> vr flinger transitions and observed no file descriptor leaks. - Confirmed the HWC1 code path still compiles. - Ran the surface flinger tests and confirmed there are no new test failures. - Ran the hardware composer hidl in passthrough mode on a Marlin and confirmed it works. - Ran CTS tests for virtual displays and confirmed they all pass. - Tested Android Auto and confirmed basic graphics functionality still works. Change-Id: I17dc0e060bfb5cb447ffbaa573b279fc6d2d8bd1 Merged-In: I17dc0e060bfb5cb447ffbaa573b279fc6d2d8bd1 --- libs/vr/libvrflinger/display_service.cpp | 10 +- libs/vr/libvrflinger/display_service.h | 3 - libs/vr/libvrflinger/hardware_composer.cpp | 352 +++++------ libs/vr/libvrflinger/hardware_composer.h | 110 ++-- libs/vr/libvrflinger/include/dvr/vr_flinger.h | 3 - libs/vr/libvrflinger/vr_flinger.cpp | 4 - services/surfaceflinger/DisplayDevice.cpp | 6 +- .../DisplayHardware/ComposerHal.cpp | 4 + .../DisplayHardware/ComposerHal.h | 5 + .../surfaceflinger/DisplayHardware/HWC2.cpp | 549 ++++++++---------- .../surfaceflinger/DisplayHardware/HWC2.h | 124 ++-- .../DisplayHardware/HWComposer.cpp | 131 ++--- .../DisplayHardware/HWComposer.h | 39 +- .../surfaceflinger/EventControlThread.cpp | 43 +- services/surfaceflinger/Layer.cpp | 50 +- services/surfaceflinger/Layer.h | 34 +- .../RenderEngine/RenderEngine.cpp | 33 +- .../RenderEngine/RenderEngine.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 294 +++++----- services/surfaceflinger/SurfaceFlinger.h | 60 +- .../surfaceflinger/SurfaceFlinger_hwc1.cpp | 4 +- 21 files changed, 844 insertions(+), 1016 deletions(-) diff --git a/libs/vr/libvrflinger/display_service.cpp b/libs/vr/libvrflinger/display_service.cpp index 733edc659c..f350762e79 100644 --- a/libs/vr/libvrflinger/display_service.cpp +++ b/libs/vr/libvrflinger/display_service.cpp @@ -40,10 +40,8 @@ namespace dvr { DisplayService::DisplayService(Hwc2::Composer* hidl, RequestDisplayCallback request_display_callback) : BASE("DisplayService", - Endpoint::Create(display::DisplayProtocol::kClientPath)), - hardware_composer_(hidl, request_display_callback), - request_display_callback_(request_display_callback) { - hardware_composer_.Initialize(); + Endpoint::Create(display::DisplayProtocol::kClientPath)) { + hardware_composer_.Initialize(hidl, request_display_callback); } bool DisplayService::IsInitialized() const { @@ -398,10 +396,6 @@ pdx::Status DisplayService::DeleteGlobalBuffer(DvrGlobalBufferKey key) { return {0}; } -void DisplayService::OnHardwareComposerRefresh() { - hardware_composer_.OnHardwareComposerRefresh(); -} - void DisplayService::SetDisplayConfigurationUpdateNotifier( DisplayConfigurationUpdateNotifier update_notifier) { update_notifier_ = update_notifier; diff --git a/libs/vr/libvrflinger/display_service.h b/libs/vr/libvrflinger/display_service.h index 6efe264b09..55e33ab852 100644 --- a/libs/vr/libvrflinger/display_service.h +++ b/libs/vr/libvrflinger/display_service.h @@ -72,8 +72,6 @@ class DisplayService : public pdx::ServiceBase { void GrantDisplayOwnership() { hardware_composer_.Enable(); } void SeizeDisplayOwnership() { hardware_composer_.Disable(); } - void OnHardwareComposerRefresh(); - private: friend BASE; friend DisplaySurface; @@ -119,7 +117,6 @@ class DisplayService : public pdx::ServiceBase { pdx::Status HandleSurfaceMessage(pdx::Message& message); HardwareComposer hardware_composer_; - RequestDisplayCallback request_display_callback_; EpollEventDispatcher dispatcher_; DisplayConfigurationUpdateNotifier update_notifier_; diff --git a/libs/vr/libvrflinger/hardware_composer.cpp b/libs/vr/libvrflinger/hardware_composer.cpp index 11c137063e..f9a5dcd987 100644 --- a/libs/vr/libvrflinger/hardware_composer.cpp +++ b/libs/vr/libvrflinger/hardware_composer.cpp @@ -28,6 +28,8 @@ #include #include +using android::hardware::Return; +using android::hardware::Void; using android::pdx::LocalHandle; using android::pdx::rpc::EmptyVariant; using android::pdx::rpc::IfAnyOf; @@ -42,9 +44,6 @@ namespace { const char kBacklightBrightnessSysFile[] = "/sys/class/leds/lcd-backlight/brightness"; -const char kPrimaryDisplayVSyncEventFile[] = - "/sys/class/graphics/fb0/vsync_event"; - const char kPrimaryDisplayWaitPPEventFile[] = "/sys/class/graphics/fb0/wait_pp"; const char kDvrPerformanceProperty[] = "sys.dvr.performance"; @@ -86,22 +85,11 @@ bool SetThreadPolicy(const std::string& scheduler_class, } // anonymous namespace -// Layer static data. -Hwc2::Composer* Layer::hwc2_hidl_; -const HWCDisplayMetrics* Layer::display_metrics_; - // HardwareComposer static data; constexpr size_t HardwareComposer::kMaxHardwareLayers; HardwareComposer::HardwareComposer() - : HardwareComposer(nullptr, RequestDisplayCallback()) {} - -HardwareComposer::HardwareComposer( - Hwc2::Composer* hwc2_hidl, RequestDisplayCallback request_display_callback) - : initialized_(false), - hwc2_hidl_(hwc2_hidl), - request_display_callback_(request_display_callback), - callbacks_(new ComposerCallback) {} + : initialized_(false), request_display_callback_(nullptr) {} HardwareComposer::~HardwareComposer(void) { UpdatePostThreadState(PostThreadState::Quit, true); @@ -109,16 +97,19 @@ HardwareComposer::~HardwareComposer(void) { post_thread_.join(); } -bool HardwareComposer::Initialize() { +bool HardwareComposer::Initialize( + Hwc2::Composer* hidl, RequestDisplayCallback request_display_callback) { if (initialized_) { ALOGE("HardwareComposer::Initialize: already initialized."); return false; } + request_display_callback_ = request_display_callback; + HWC::Error error = HWC::Error::None; Hwc2::Config config; - error = hwc2_hidl_->getActiveConfig(HWC_DISPLAY_PRIMARY, &config); + error = hidl->getActiveConfig(HWC_DISPLAY_PRIMARY, &config); if (error != HWC::Error::None) { ALOGE("HardwareComposer: Failed to get current display config : %d", @@ -126,8 +117,8 @@ bool HardwareComposer::Initialize() { return false; } - error = - GetDisplayMetrics(HWC_DISPLAY_PRIMARY, config, &native_display_metrics_); + error = GetDisplayMetrics(hidl, HWC_DISPLAY_PRIMARY, config, + &native_display_metrics_); if (error != HWC::Error::None) { ALOGE( @@ -149,9 +140,6 @@ bool HardwareComposer::Initialize() { display_transform_ = HWC_TRANSFORM_NONE; display_metrics_ = native_display_metrics_; - // Pass hwc instance and metrics to setup globals for Layer. - Layer::InitializeGlobals(hwc2_hidl_, &native_display_metrics_); - post_thread_event_fd_.Reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)); LOG_ALWAYS_FATAL_IF( !post_thread_event_fd_, @@ -210,15 +198,11 @@ void HardwareComposer::UpdatePostThreadState(PostThreadStateType state, } void HardwareComposer::OnPostThreadResumed() { - hwc2_hidl_->resetCommands(); + hidl_.reset(new Hwc2::Composer("default")); + hidl_callback_ = new ComposerCallback; + hidl_->registerCallback(hidl_callback_); - // HIDL HWC seems to have an internal race condition. If we submit a frame too - // soon after turning on VSync we don't get any VSync signals. Give poor HWC - // implementations a chance to enable VSync before we continue. - EnableVsync(false); - std::this_thread::sleep_for(100ms); EnableVsync(true); - std::this_thread::sleep_for(100ms); // TODO(skiazyk): We need to do something about accessing this directly, // supposedly there is a backlight service on the way. @@ -240,9 +224,12 @@ void HardwareComposer::OnPostThreadPaused() { } active_layer_count_ = 0; - EnableVsync(false); + if (hidl_) { + EnableVsync(false); + } - hwc2_hidl_->resetCommands(); + hidl_callback_ = nullptr; + hidl_.reset(nullptr); // Trigger target-specific performance mode change. property_set(kDvrPerformanceProperty, "idle"); @@ -252,21 +239,21 @@ HWC::Error HardwareComposer::Validate(hwc2_display_t display) { uint32_t num_types; uint32_t num_requests; HWC::Error error = - hwc2_hidl_->validateDisplay(display, &num_types, &num_requests); + hidl_->validateDisplay(display, &num_types, &num_requests); if (error == HWC2_ERROR_HAS_CHANGES) { // TODO(skiazyk): We might need to inspect the requested changes first, but // so far it seems like we shouldn't ever hit a bad state. // error = hwc2_funcs_.accept_display_changes_fn_(hardware_composer_device_, // display); - error = hwc2_hidl_->acceptDisplayChanges(display); + error = hidl_->acceptDisplayChanges(display); } return error; } -int32_t HardwareComposer::EnableVsync(bool enabled) { - return (int32_t)hwc2_hidl_->setVsyncEnabled( +HWC::Error HardwareComposer::EnableVsync(bool enabled) { + return hidl_->setVsyncEnabled( HWC_DISPLAY_PRIMARY, (Hwc2::IComposerClient::Vsync)(enabled ? HWC2_VSYNC_ENABLE : HWC2_VSYNC_DISABLE)); @@ -274,7 +261,7 @@ int32_t HardwareComposer::EnableVsync(bool enabled) { HWC::Error HardwareComposer::Present(hwc2_display_t display) { int32_t present_fence; - HWC::Error error = hwc2_hidl_->presentDisplay(display, &present_fence); + HWC::Error error = hidl_->presentDisplay(display, &present_fence); // According to the documentation, this fence is signaled at the time of // vsync/DMA for physical displays. @@ -288,20 +275,21 @@ HWC::Error HardwareComposer::Present(hwc2_display_t display) { return error; } -HWC::Error HardwareComposer::GetDisplayAttribute(hwc2_display_t display, +HWC::Error HardwareComposer::GetDisplayAttribute(Hwc2::Composer* hidl, + hwc2_display_t display, hwc2_config_t config, hwc2_attribute_t attribute, int32_t* out_value) const { - return hwc2_hidl_->getDisplayAttribute( + return hidl->getDisplayAttribute( display, config, (Hwc2::IComposerClient::Attribute)attribute, out_value); } HWC::Error HardwareComposer::GetDisplayMetrics( - hwc2_display_t display, hwc2_config_t config, + Hwc2::Composer* hidl, hwc2_display_t display, hwc2_config_t config, HWCDisplayMetrics* out_metrics) const { HWC::Error error; - error = GetDisplayAttribute(display, config, HWC2_ATTRIBUTE_WIDTH, + error = GetDisplayAttribute(hidl, display, config, HWC2_ATTRIBUTE_WIDTH, &out_metrics->width); if (error != HWC::Error::None) { ALOGE( @@ -310,7 +298,7 @@ HWC::Error HardwareComposer::GetDisplayMetrics( return error; } - error = GetDisplayAttribute(display, config, HWC2_ATTRIBUTE_HEIGHT, + error = GetDisplayAttribute(hidl, display, config, HWC2_ATTRIBUTE_HEIGHT, &out_metrics->height); if (error != HWC::Error::None) { ALOGE( @@ -319,7 +307,8 @@ HWC::Error HardwareComposer::GetDisplayMetrics( return error; } - error = GetDisplayAttribute(display, config, HWC2_ATTRIBUTE_VSYNC_PERIOD, + error = GetDisplayAttribute(hidl, display, config, + HWC2_ATTRIBUTE_VSYNC_PERIOD, &out_metrics->vsync_period_ns); if (error != HWC::Error::None) { ALOGE( @@ -328,7 +317,7 @@ HWC::Error HardwareComposer::GetDisplayMetrics( return error; } - error = GetDisplayAttribute(display, config, HWC2_ATTRIBUTE_DPI_X, + error = GetDisplayAttribute(hidl, display, config, HWC2_ATTRIBUTE_DPI_X, &out_metrics->dpi.x); if (error != HWC::Error::None) { ALOGE( @@ -337,7 +326,7 @@ HWC::Error HardwareComposer::GetDisplayMetrics( return error; } - error = GetDisplayAttribute(display, config, HWC2_ATTRIBUTE_DPI_Y, + error = GetDisplayAttribute(hidl, display, config, HWC2_ATTRIBUTE_DPI_Y, &out_metrics->dpi.y); if (error != HWC::Error::None) { ALOGE( @@ -374,7 +363,7 @@ std::string HardwareComposer::Dump() { if (post_thread_resumed_) { stream << "Hardware Composer Debug Info:" << std::endl; - stream << hwc2_hidl_->dumpDebugInfo(); + stream << hidl_->dumpDebugInfo(); } return stream.str(); @@ -446,8 +435,8 @@ void HardwareComposer::PostLayers() { std::vector out_layers; std::vector out_fences; - error = hwc2_hidl_->getReleaseFences(HWC_DISPLAY_PRIMARY, &out_layers, - &out_fences); + error = hidl_->getReleaseFences(HWC_DISPLAY_PRIMARY, &out_layers, + &out_fences); ALOGE_IF(error != HWC::Error::None, "HardwareComposer::PostLayers: Failed to get release fences: %s", error.to_string().c_str()); @@ -546,7 +535,7 @@ void HardwareComposer::UpdateConfigBuffer() { } int HardwareComposer::PostThreadPollInterruptible( - const pdx::LocalHandle& event_fd, int requested_events) { + const pdx::LocalHandle& event_fd, int requested_events, int timeout_ms) { pollfd pfd[2] = { { .fd = event_fd.Get(), @@ -561,7 +550,7 @@ int HardwareComposer::PostThreadPollInterruptible( }; int ret, error; do { - ret = poll(pfd, 2, -1); + ret = poll(pfd, 2, timeout_ms); error = errno; ALOGW_IF(ret < 0, "HardwareComposer::PostThreadPollInterruptible: Error during " @@ -571,6 +560,8 @@ int HardwareComposer::PostThreadPollInterruptible( if (ret < 0) { return -error; + } else if (ret == 0) { + return -ETIMEDOUT; } else if (pfd[0].revents != 0) { return 0; } else if (pfd[1].revents != 0) { @@ -623,114 +614,17 @@ int HardwareComposer::ReadWaitPPState() { } } -// Reads the timestamp of the last vsync from the display driver. -// TODO(eieio): This is pretty driver specific, this should be moved to a -// separate class eventually. -int HardwareComposer::ReadVSyncTimestamp(int64_t* timestamp) { - const int event_fd = primary_display_vsync_event_fd_.Get(); - int ret, error; - - // The driver returns data in the form "VSYNC=". - std::array data; - data.fill('\0'); - - // Seek back to the beginning of the event file. - ret = lseek(event_fd, 0, SEEK_SET); - if (ret < 0) { - error = errno; - ALOGE( - "HardwareComposer::ReadVSyncTimestamp: Failed to seek vsync event fd: " - "%s", - strerror(error)); - return -error; - } - - // Read the vsync event timestamp. - ret = read(event_fd, data.data(), data.size()); - if (ret < 0) { - error = errno; - ALOGE_IF( - error != EAGAIN, - "HardwareComposer::ReadVSyncTimestamp: Error while reading timestamp: " - "%s", - strerror(error)); - return -error; - } - - ret = sscanf(data.data(), "VSYNC=%" PRIu64, - reinterpret_cast(timestamp)); - if (ret < 0) { - error = errno; - ALOGE( - "HardwareComposer::ReadVSyncTimestamp: Error while parsing timestamp: " - "%s", - strerror(error)); - return -error; - } - - return 0; -} - -// Blocks until the next vsync event is signaled by the display driver. -// TODO(eieio): This is pretty driver specific, this should be moved to a -// separate class eventually. -int HardwareComposer::BlockUntilVSync() { - // Vsync is signaled by POLLPRI on the fb vsync node. - return PostThreadPollInterruptible(primary_display_vsync_event_fd_, POLLPRI); -} - // Waits for the next vsync and returns the timestamp of the vsync event. If // vsync already passed since the last call, returns the latest vsync timestamp -// instead of blocking. This method updates the last_vsync_timeout_ in the -// process. -// -// TODO(eieio): This is pretty driver specific, this should be moved to a -// separate class eventually. +// instead of blocking. int HardwareComposer::WaitForVSync(int64_t* timestamp) { - int error; - - // Get the current timestamp and decide what to do. - while (true) { - int64_t current_vsync_timestamp; - error = ReadVSyncTimestamp(¤t_vsync_timestamp); - if (error < 0 && error != -EAGAIN) - return error; - - if (error == -EAGAIN) { - // Vsync was turned off, wait for the next vsync event. - error = BlockUntilVSync(); - if (error < 0 || error == kPostThreadInterrupted) - return error; - - // Try again to get the timestamp for this new vsync interval. - continue; - } - - // Check that we advanced to a later vsync interval. - if (TimestampGT(current_vsync_timestamp, last_vsync_timestamp_)) { - *timestamp = last_vsync_timestamp_ = current_vsync_timestamp; - return 0; - } - - // See how close we are to the next expected vsync. If we're within 1ms, - // sleep for 1ms and try again. - const int64_t ns_per_frame = display_metrics_.vsync_period_ns; - const int64_t threshold_ns = 1000000; // 1ms - - const int64_t next_vsync_est = last_vsync_timestamp_ + ns_per_frame; - const int64_t distance_to_vsync_est = next_vsync_est - GetSystemClockNs(); - - if (distance_to_vsync_est > threshold_ns) { - // Wait for vsync event notification. - error = BlockUntilVSync(); - if (error < 0 || error == kPostThreadInterrupted) - return error; - } else { - // Sleep for a short time (1 millisecond) before retrying. - error = SleepUntil(GetSystemClockNs() + threshold_ns); - if (error < 0 || error == kPostThreadInterrupted) - return error; - } + int error = PostThreadPollInterruptible( + hidl_callback_->GetVsyncEventFd(), POLLIN, /*timeout_ms*/ 1000); + if (error == kPostThreadInterrupted || error < 0) { + return error; + } else { + *timestamp = hidl_callback_->GetVsyncTime(); + return 0; } } @@ -749,7 +643,8 @@ int HardwareComposer::SleepUntil(int64_t wakeup_timestamp) { return -error; } - return PostThreadPollInterruptible(vsync_sleep_timer_fd_, POLLIN); + return PostThreadPollInterruptible( + vsync_sleep_timer_fd_, POLLIN, /*timeout_ms*/ -1); } void HardwareComposer::PostThread() { @@ -772,15 +667,6 @@ void HardwareComposer::PostThread() { strerror(errno)); #endif // ENABLE_BACKLIGHT_BRIGHTNESS - // Open the vsync event node for the primary display. - // TODO(eieio): Move this into a platform-specific class. - primary_display_vsync_event_fd_ = - LocalHandle(kPrimaryDisplayVSyncEventFile, O_RDONLY); - ALOGE_IF(!primary_display_vsync_event_fd_, - "HardwareComposer: Failed to open vsync event node for primary " - "display: %s", - strerror(errno)); - // Open the wait pingpong status node for the primary display. // TODO(eieio): Move this into a platform-specific class. primary_display_wait_pp_fd_ = @@ -951,7 +837,8 @@ bool HardwareComposer::UpdateLayerConfig() { // The bottom layer is opaque, other layers blend. HWC::BlendMode blending = layer_index == 0 ? HWC::BlendMode::None : HWC::BlendMode::Coverage; - layers_[layer_index].Setup(surfaces[layer_index], blending, + layers_[layer_index].Setup(surfaces[layer_index], native_display_metrics_, + hidl_.get(), blending, display_transform_, HWC::Composition::Device, layer_index); display_surfaces_.push_back(surfaces[layer_index]); @@ -979,50 +866,67 @@ void HardwareComposer::SetVSyncCallback(VSyncCallback callback) { vsync_callback_ = callback; } -void HardwareComposer::HwcRefresh(hwc2_callback_data_t /*data*/, - hwc2_display_t /*display*/) { - // TODO(eieio): implement invalidate callbacks. +void HardwareComposer::SetBacklightBrightness(int brightness) { + if (backlight_brightness_fd_) { + std::array text; + const int length = snprintf(text.data(), text.size(), "%d", brightness); + write(backlight_brightness_fd_.Get(), text.data(), length); + } } -void HardwareComposer::HwcVSync(hwc2_callback_data_t /*data*/, - hwc2_display_t /*display*/, - int64_t /*timestamp*/) { - ATRACE_NAME(__PRETTY_FUNCTION__); - // Intentionally empty. HWC may require a callback to be set to enable vsync - // signals. We bypass this callback thread by monitoring the vsync event - // directly, but signals still need to be enabled. +HardwareComposer::ComposerCallback::ComposerCallback() { + vsync_event_fd_.Reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)); + LOG_ALWAYS_FATAL_IF( + !vsync_event_fd_, + "Failed to create vsync event fd : %s", + strerror(errno)); } -void HardwareComposer::HwcHotplug(hwc2_callback_data_t /*callbackData*/, - hwc2_display_t /*display*/, - hwc2_connection_t /*connected*/) { - // TODO(eieio): implement display hotplug callbacks. +Return HardwareComposer::ComposerCallback::onHotplug( + Hwc2::Display /*display*/, + IComposerCallback::Connection /*conn*/) { + return Void(); } -void HardwareComposer::OnHardwareComposerRefresh() { - // TODO(steventhomas): Handle refresh. +Return HardwareComposer::ComposerCallback::onRefresh( + Hwc2::Display /*display*/) { + return hardware::Void(); } -void HardwareComposer::SetBacklightBrightness(int brightness) { - if (backlight_brightness_fd_) { - std::array text; - const int length = snprintf(text.data(), text.size(), "%d", brightness); - write(backlight_brightness_fd_.Get(), text.data(), length); +Return HardwareComposer::ComposerCallback::onVsync( + Hwc2::Display display, int64_t timestamp) { + if (display == HWC_DISPLAY_PRIMARY) { + std::lock_guard lock(vsync_mutex_); + vsync_time_ = timestamp; + int error = eventfd_write(vsync_event_fd_.Get(), 1); + LOG_ALWAYS_FATAL_IF(error != 0, "Failed writing to vsync event fd"); } + return Void(); +} + +const pdx::LocalHandle& +HardwareComposer::ComposerCallback::GetVsyncEventFd() const { + return vsync_event_fd_; } -void Layer::InitializeGlobals(Hwc2::Composer* hwc2_hidl, - const HWCDisplayMetrics* metrics) { - hwc2_hidl_ = hwc2_hidl; - display_metrics_ = metrics; +int64_t HardwareComposer::ComposerCallback::GetVsyncTime() { + std::lock_guard lock(vsync_mutex_); + eventfd_t event; + eventfd_read(vsync_event_fd_.Get(), &event); + LOG_ALWAYS_FATAL_IF(vsync_time_ < 0, + "Attempt to read vsync time before vsync event"); + int64_t return_val = vsync_time_; + vsync_time_ = -1; + return return_val; } void Layer::Reset() { - if (hwc2_hidl_ != nullptr && hardware_composer_layer_) { - hwc2_hidl_->destroyLayer(HWC_DISPLAY_PRIMARY, hardware_composer_layer_); + if (hidl_ != nullptr && hardware_composer_layer_) { + hidl_->destroyLayer(HWC_DISPLAY_PRIMARY, hardware_composer_layer_); hardware_composer_layer_ = 0; } + hidl_ = nullptr; z_order_ = 0; blending_ = HWC::BlendMode::None; transform_ = HWC::Transform::None; @@ -1034,29 +938,35 @@ void Layer::Reset() { } void Layer::Setup(const std::shared_ptr& surface, - HWC::BlendMode blending, HWC::Transform transform, - HWC::Composition composition_type, size_t z_order) { + const HWCDisplayMetrics& display_metrics, + Hwc2::Composer* hidl, HWC::BlendMode blending, + HWC::Transform transform, HWC::Composition composition_type, + size_t z_order) { Reset(); + hidl_ = hidl; z_order_ = z_order; blending_ = blending; transform_ = transform; composition_type_ = HWC::Composition::Invalid; target_composition_type_ = composition_type; source_ = SourceSurface{surface}; - CommonLayerSetup(); + CommonLayerSetup(display_metrics); } void Layer::Setup(const std::shared_ptr& buffer, - HWC::BlendMode blending, HWC::Transform transform, - HWC::Composition composition_type, size_t z_order) { + const HWCDisplayMetrics& display_metrics, + Hwc2::Composer* hidl, HWC::BlendMode blending, + HWC::Transform transform, HWC::Composition composition_type, + size_t z_order) { Reset(); + hidl_ = hidl; z_order_ = z_order; blending_ = blending; transform_ = transform; composition_type_ = HWC::Composition::Invalid; target_composition_type_ = composition_type; source_ = SourceBuffer{buffer}; - CommonLayerSetup(); + CommonLayerSetup(display_metrics); } void Layer::UpdateBuffer(const std::shared_ptr& buffer) { @@ -1076,7 +986,7 @@ IonBuffer* Layer::GetBuffer() { return source_.Visit(Visitor{}); } -void Layer::UpdateLayerSettings() { +void Layer::UpdateLayerSettings(const HWCDisplayMetrics& display_metrics) { if (!IsLayerSetup()) { ALOGE( "HardwareComposer::Layer::UpdateLayerSettings: Attempt to update " @@ -1087,7 +997,7 @@ void Layer::UpdateLayerSettings() { HWC::Error error; hwc2_display_t display = HWC_DISPLAY_PRIMARY; - error = hwc2_hidl_->setLayerCompositionType( + error = hidl_->setLayerCompositionType( display, hardware_composer_layer_, composition_type_.cast()); ALOGE_IF( @@ -1095,7 +1005,7 @@ void Layer::UpdateLayerSettings() { "Layer::UpdateLayerSettings: Error setting layer composition type: %s", error.to_string().c_str()); - error = hwc2_hidl_->setLayerBlendMode( + error = hidl_->setLayerBlendMode( display, hardware_composer_layer_, blending_.cast()); ALOGE_IF(error != HWC::Error::None, @@ -1104,41 +1014,39 @@ void Layer::UpdateLayerSettings() { // TODO(eieio): Use surface attributes or some other mechanism to control // the layer display frame. - error = hwc2_hidl_->setLayerDisplayFrame( + error = hidl_->setLayerDisplayFrame( display, hardware_composer_layer_, - {0, 0, display_metrics_->width, display_metrics_->height}); + {0, 0, display_metrics.width, display_metrics.height}); ALOGE_IF(error != HWC::Error::None, "Layer::UpdateLayerSettings: Error setting layer display frame: %s", error.to_string().c_str()); - error = hwc2_hidl_->setLayerVisibleRegion( + error = hidl_->setLayerVisibleRegion( display, hardware_composer_layer_, - {{0, 0, display_metrics_->width, display_metrics_->height}}); + {{0, 0, display_metrics.width, display_metrics.height}}); ALOGE_IF(error != HWC::Error::None, "Layer::UpdateLayerSettings: Error setting layer visible region: %s", error.to_string().c_str()); - error = - hwc2_hidl_->setLayerPlaneAlpha(display, hardware_composer_layer_, 1.0f); + error = hidl_->setLayerPlaneAlpha(display, hardware_composer_layer_, 1.0f); ALOGE_IF(error != HWC::Error::None, "Layer::UpdateLayerSettings: Error setting layer plane alpha: %s", error.to_string().c_str()); - error = - hwc2_hidl_->setLayerZOrder(display, hardware_composer_layer_, z_order_); + error = hidl_->setLayerZOrder(display, hardware_composer_layer_, z_order_); ALOGE_IF(error != HWC::Error::None, "Layer::UpdateLayerSettings: Error setting z_ order: %s", error.to_string().c_str()); } -void Layer::CommonLayerSetup() { +void Layer::CommonLayerSetup(const HWCDisplayMetrics& display_metrics) { HWC::Error error = - hwc2_hidl_->createLayer(HWC_DISPLAY_PRIMARY, &hardware_composer_layer_); + hidl_->createLayer(HWC_DISPLAY_PRIMARY, &hardware_composer_layer_); ALOGE_IF( error != HWC::Error::None, "Layer::CommonLayerSetup: Failed to create layer on primary display: %s", error.to_string().c_str()); - UpdateLayerSettings(); + UpdateLayerSettings(display_metrics); } void Layer::Prepare() { @@ -1157,12 +1065,12 @@ void Layer::Prepare() { if (!handle.get()) { if (composition_type_ == HWC::Composition::Invalid) { composition_type_ = HWC::Composition::SolidColor; - hwc2_hidl_->setLayerCompositionType( + hidl_->setLayerCompositionType( HWC_DISPLAY_PRIMARY, hardware_composer_layer_, composition_type_.cast()); Hwc2::IComposerClient::Color layer_color = {0, 0, 0, 0}; - hwc2_hidl_->setLayerColor(HWC_DISPLAY_PRIMARY, hardware_composer_layer_, - layer_color); + hidl_->setLayerColor(HWC_DISPLAY_PRIMARY, hardware_composer_layer_, + layer_color); } else { // The composition type is already set. Nothing else to do until a // buffer arrives. @@ -1170,15 +1078,15 @@ void Layer::Prepare() { } else { if (composition_type_ != target_composition_type_) { composition_type_ = target_composition_type_; - hwc2_hidl_->setLayerCompositionType( + hidl_->setLayerCompositionType( HWC_DISPLAY_PRIMARY, hardware_composer_layer_, composition_type_.cast()); } HWC::Error error{HWC::Error::None}; - error = hwc2_hidl_->setLayerBuffer(HWC_DISPLAY_PRIMARY, - hardware_composer_layer_, 0, handle, - acquire_fence_.Get()); + error = hidl_->setLayerBuffer(HWC_DISPLAY_PRIMARY, + hardware_composer_layer_, 0, handle, + acquire_fence_.Get()); ALOGE_IF(error != HWC::Error::None, "Layer::Prepare: Error setting layer buffer: %s", @@ -1187,9 +1095,9 @@ void Layer::Prepare() { if (!surface_rect_functions_applied_) { const float float_right = right; const float float_bottom = bottom; - error = hwc2_hidl_->setLayerSourceCrop(HWC_DISPLAY_PRIMARY, - hardware_composer_layer_, - {0, 0, float_right, float_bottom}); + error = hidl_->setLayerSourceCrop(HWC_DISPLAY_PRIMARY, + hardware_composer_layer_, + {0, 0, float_right, float_bottom}); ALOGE_IF(error != HWC::Error::None, "Layer::Prepare: Error setting layer source crop: %s", diff --git a/libs/vr/libvrflinger/hardware_composer.h b/libs/vr/libvrflinger/hardware_composer.h index a0c50e14d8..fc0efeeeb4 100644 --- a/libs/vr/libvrflinger/hardware_composer.h +++ b/libs/vr/libvrflinger/hardware_composer.h @@ -54,11 +54,6 @@ class Layer { public: Layer() {} - // Sets up the global state used by all Layer instances. This must be called - // before using any Layer methods. - static void InitializeGlobals(Hwc2::Composer* hwc2_hidl, - const HWCDisplayMetrics* metrics); - // Releases any shared pointers and fence handles held by this instance. void Reset(); @@ -72,6 +67,7 @@ class Layer { // HWC_FRAMEBUFFER_TARGET (unless you know what you are doing). // |index| is the index of this surface in the DirectDisplaySurface array. void Setup(const std::shared_ptr& surface, + const HWCDisplayMetrics& display_metrics, Hwc2::Composer* hidl, HWC::BlendMode blending, HWC::Transform transform, HWC::Composition composition_type, size_t z_roder); @@ -83,9 +79,10 @@ class Layer { // |transform| receives HWC_TRANSFORM_* values. // |composition_type| receives either HWC_FRAMEBUFFER for most layers or // HWC_FRAMEBUFFER_TARGET (unless you know what you are doing). - void Setup(const std::shared_ptr& buffer, HWC::BlendMode blending, - HWC::Transform transform, HWC::Composition composition_type, - size_t z_order); + void Setup(const std::shared_ptr& buffer, + const HWCDisplayMetrics& display_metrics, Hwc2::Composer* hidl, + HWC::BlendMode blending, HWC::Transform transform, + HWC::Composition composition_type, size_t z_order); // Layers that use a direct IonBuffer should call this each frame to update // which buffer will be used for the next PostLayers. @@ -121,7 +118,7 @@ class Layer { bool IsLayerSetup() const { return !source_.empty(); } // Applies all of the settings to this layer using the hwc functions - void UpdateLayerSettings(); + void UpdateLayerSettings(const HWCDisplayMetrics& display_metrics); int GetSurfaceId() const { int surface_id = -1; @@ -142,10 +139,9 @@ class Layer { } private: - void CommonLayerSetup(); + void CommonLayerSetup(const HWCDisplayMetrics& display_metrics); - static Hwc2::Composer* hwc2_hidl_; - static const HWCDisplayMetrics* display_metrics_; + Hwc2::Composer* hidl_ = nullptr; // The hardware composer layer and metrics to use during the prepare cycle. hwc2_layer_t hardware_composer_layer_ = 0; @@ -263,11 +259,10 @@ class HardwareComposer { static constexpr size_t kMaxHardwareLayers = 4; HardwareComposer(); - HardwareComposer(Hwc2::Composer* hidl, - RequestDisplayCallback request_display_callback); ~HardwareComposer(); - bool Initialize(); + bool Initialize(Hwc2::Composer* hidl, + RequestDisplayCallback request_display_callback); bool IsInitialized() const { return initialized_; } @@ -281,11 +276,6 @@ class HardwareComposer { // Get the HMD display metrics for the current display. display::Metrics GetHmdDisplayMetrics() const; - HWC::Error GetDisplayAttribute(hwc2_display_t display, hwc2_config_t config, - hwc2_attribute_t attributes, - int32_t* out_value) const; - HWC::Error GetDisplayMetrics(hwc2_display_t display, hwc2_config_t config, - HWCDisplayMetrics* out_metrics) const; std::string Dump(); void SetVSyncCallback(VSyncCallback callback); @@ -308,34 +298,31 @@ class HardwareComposer { int OnNewGlobalBuffer(DvrGlobalBufferKey key, IonBuffer& ion_buffer); void OnDeletedGlobalBuffer(DvrGlobalBufferKey key); - void OnHardwareComposerRefresh(); - private: - int32_t EnableVsync(bool enabled); + HWC::Error GetDisplayAttribute(Hwc2::Composer* hidl, hwc2_display_t display, + hwc2_config_t config, + hwc2_attribute_t attributes, + int32_t* out_value) const; + HWC::Error GetDisplayMetrics(Hwc2::Composer* hidl, hwc2_display_t display, + hwc2_config_t config, + HWCDisplayMetrics* out_metrics) const; + + HWC::Error EnableVsync(bool enabled); class ComposerCallback : public Hwc2::IComposerCallback { public: - ComposerCallback() {} - - hardware::Return onHotplug(Hwc2::Display /*display*/, - Connection /*connected*/) override { - // TODO(skiazyk): depending on how the server is implemented, we might - // have to set it up to synchronize with receiving this event, as it can - // potentially be a critical event for setting up state within the - // hwc2 module. That is, we (technically) should not call any other hwc - // methods until this method has been called after registering the - // callbacks. - return hardware::Void(); - } - - hardware::Return onRefresh(Hwc2::Display /*display*/) override { - return hardware::Void(); - } - - hardware::Return onVsync(Hwc2::Display /*display*/, - int64_t /*timestamp*/) override { - return hardware::Void(); - } + ComposerCallback(); + hardware::Return onHotplug(Hwc2::Display display, + Connection conn) override; + hardware::Return onRefresh(Hwc2::Display display) override; + hardware::Return onVsync(Hwc2::Display display, + int64_t timestamp) override; + const pdx::LocalHandle& GetVsyncEventFd() const; + int64_t GetVsyncTime(); + private: + std::mutex vsync_mutex_; + pdx::LocalHandle vsync_event_fd_; + int64_t vsync_time_ = -1; }; HWC::Error Validate(hwc2_display_t display); @@ -364,17 +351,18 @@ class HardwareComposer { void UpdatePostThreadState(uint32_t state, bool suspend); // Blocks until either event_fd becomes readable, or we're interrupted by a - // control thread. Any errors are returned as negative errno values. If we're - // interrupted, kPostThreadInterrupted will be returned. + // control thread, or timeout_ms is reached before any events occur. Any + // errors are returned as negative errno values, with -ETIMEDOUT returned in + // the case of a timeout. If we're interrupted, kPostThreadInterrupted will be + // returned. int PostThreadPollInterruptible(const pdx::LocalHandle& event_fd, - int requested_events); + int requested_events, + int timeout_ms); - // BlockUntilVSync, WaitForVSync, and SleepUntil are all blocking calls made - // on the post thread that can be interrupted by a control thread. If - // interrupted, these calls return kPostThreadInterrupted. + // WaitForVSync and SleepUntil are blocking calls made on the post thread that + // can be interrupted by a control thread. If interrupted, these calls return + // kPostThreadInterrupted. int ReadWaitPPState(); - int BlockUntilVSync(); - int ReadVSyncTimestamp(int64_t* timestamp); int WaitForVSync(int64_t* timestamp); int SleepUntil(int64_t wakeup_timestamp); @@ -398,11 +386,9 @@ class HardwareComposer { bool initialized_; - // Hardware composer HAL device from SurfaceFlinger. VrFlinger does not own - // this pointer. - Hwc2::Composer* hwc2_hidl_; + std::unique_ptr hidl_; + sp hidl_callback_; RequestDisplayCallback request_display_callback_; - sp callbacks_; // Display metrics of the physical display. HWCDisplayMetrics native_display_metrics_; @@ -433,7 +419,8 @@ class HardwareComposer { std::thread post_thread_; // Post thread state machine and synchronization primitives. - PostThreadStateType post_thread_state_{PostThreadState::Idle}; + PostThreadStateType post_thread_state_{ + PostThreadState::Idle | PostThreadState::Suspended}; std::atomic post_thread_quiescent_{true}; bool post_thread_resumed_{false}; pdx::LocalHandle post_thread_event_fd_; @@ -444,9 +431,6 @@ class HardwareComposer { // Backlight LED brightness sysfs node. pdx::LocalHandle backlight_brightness_fd_; - // Primary display vsync event sysfs node. - pdx::LocalHandle primary_display_vsync_event_fd_; - // Primary display wait_pingpong state sysfs node. pdx::LocalHandle primary_display_wait_pp_fd_; @@ -478,12 +462,6 @@ class HardwareComposer { static constexpr int kPostThreadInterrupted = 1; - static void HwcRefresh(hwc2_callback_data_t data, hwc2_display_t display); - static void HwcVSync(hwc2_callback_data_t data, hwc2_display_t display, - int64_t timestamp); - static void HwcHotplug(hwc2_callback_data_t callbackData, - hwc2_display_t display, hwc2_connection_t connected); - HardwareComposer(const HardwareComposer&) = delete; void operator=(const HardwareComposer&) = delete; }; diff --git a/libs/vr/libvrflinger/include/dvr/vr_flinger.h b/libs/vr/libvrflinger/include/dvr/vr_flinger.h index e7f41a7379..33cbc84d7d 100644 --- a/libs/vr/libvrflinger/include/dvr/vr_flinger.h +++ b/libs/vr/libvrflinger/include/dvr/vr_flinger.h @@ -29,9 +29,6 @@ class VrFlinger { void GrantDisplayOwnership(); void SeizeDisplayOwnership(); - // Called on a binder thread. - void OnHardwareComposerRefresh(); - // dump all vr flinger state. std::string Dump(); diff --git a/libs/vr/libvrflinger/vr_flinger.cpp b/libs/vr/libvrflinger/vr_flinger.cpp index 56405deeaa..fcf94f0865 100644 --- a/libs/vr/libvrflinger/vr_flinger.cpp +++ b/libs/vr/libvrflinger/vr_flinger.cpp @@ -133,10 +133,6 @@ void VrFlinger::SeizeDisplayOwnership() { display_service_->SeizeDisplayOwnership(); } -void VrFlinger::OnHardwareComposerRefresh() { - display_service_->OnHardwareComposerRefresh(); -} - std::string VrFlinger::Dump() { // TODO(karthikrs): Add more state information here. return display_service_->DumpState(0/*unused*/); diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 744dd50df5..248ef53f55 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -134,9 +134,11 @@ DisplayDevice::DisplayDevice( EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (config == EGL_NO_CONFIG) { #ifdef USE_HWC2 - config = RenderEngine::chooseEglConfig(display, PIXEL_FORMAT_RGBA_8888); + config = RenderEngine::chooseEglConfig(display, PIXEL_FORMAT_RGBA_8888, + /*logConfig*/ false); #else - config = RenderEngine::chooseEglConfig(display, format); + config = RenderEngine::chooseEglConfig(display, format, + /*logConfig*/ false); #endif } eglSurface = eglCreateWindowSurface(display, config, window, NULL); diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp index 704b17ef43..433a224e25 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp @@ -215,6 +215,10 @@ void Composer::registerCallback(const sp& callback) } } +bool Composer::isRemote() { + return mClient->isRemote(); +} + void Composer::resetCommands() { mWriter.reset(); } diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h index 40d2a4c3aa..31a3c1d785 100644 --- a/services/surfaceflinger/DisplayHardware/ComposerHal.h +++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h @@ -143,6 +143,11 @@ public: void registerCallback(const sp& callback); + // Returns true if the connected composer service is running in a remote + // process, false otherwise. This will return false if the service is + // configured in passthrough mode, for example. + bool isRemote(); + // Reset all pending commands in the command buffer. Useful if you want to // skip a frame but have already queued some commands. void resetCommands(); diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp index b749ce630d..78c0c8567a 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.cpp +++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp @@ -33,45 +33,6 @@ #include #include -extern "C" { - static void hotplug_hook(hwc2_callback_data_t callbackData, - hwc2_display_t displayId, int32_t intConnected) { - auto device = static_cast(callbackData); - auto display = device->getDisplayById(displayId); - if (display) { - auto connected = static_cast(intConnected); - device->callHotplug(std::move(display), connected); - } else { - ALOGE("Hotplug callback called with unknown display %" PRIu64, - displayId); - } - } - - static void refresh_hook(hwc2_callback_data_t callbackData, - hwc2_display_t displayId) { - auto device = static_cast(callbackData); - auto display = device->getDisplayById(displayId); - if (display) { - device->callRefresh(std::move(display)); - } else { - ALOGE("Refresh callback called with unknown display %" PRIu64, - displayId); - } - } - - static void vsync_hook(hwc2_callback_data_t callbackData, - hwc2_display_t displayId, int64_t timestamp) { - auto device = static_cast(callbackData); - auto display = device->getDisplayById(displayId); - if (display) { - device->callVsync(std::move(display), timestamp); - } else { - ALOGE("Vsync callback called with unknown display %" PRIu64, - displayId); - } - } -} - using android::Fence; using android::FloatRect; using android::GraphicBuffer; @@ -86,51 +47,78 @@ namespace HWC2 { namespace Hwc2 = android::Hwc2; +namespace { + +class ComposerCallbackBridge : public Hwc2::IComposerCallback { +public: + ComposerCallbackBridge(ComposerCallback* callback, int32_t sequenceId) + : mCallback(callback), mSequenceId(sequenceId), + mHasPrimaryDisplay(false) {} + + Return onHotplug(Hwc2::Display display, + IComposerCallback::Connection conn) override + { + HWC2::Connection connection = static_cast(conn); + if (!mHasPrimaryDisplay) { + LOG_ALWAYS_FATAL_IF(connection != HWC2::Connection::Connected, + "Initial onHotplug callback should be " + "primary display connected"); + mHasPrimaryDisplay = true; + mCallback->onHotplugReceived(mSequenceId, display, + connection, true); + } else { + mCallback->onHotplugReceived(mSequenceId, display, + connection, false); + } + return Void(); + } + + Return onRefresh(Hwc2::Display display) override + { + mCallback->onRefreshReceived(mSequenceId, display); + return Void(); + } + + Return onVsync(Hwc2::Display display, int64_t timestamp) override + { + mCallback->onVsyncReceived(mSequenceId, display, timestamp); + return Void(); + } + + bool HasPrimaryDisplay() { return mHasPrimaryDisplay; } + +private: + ComposerCallback* mCallback; + int32_t mSequenceId; + bool mHasPrimaryDisplay; +}; + +} // namespace anonymous + + // Device methods Device::Device(const std::string& serviceName) : mComposer(std::make_unique(serviceName)), mCapabilities(), mDisplays(), - mHotplug(), - mPendingHotplugs(), - mRefresh(), - mPendingRefreshes(), - mVsync(), - mPendingVsyncs() + mRegisteredCallback(false) { loadCapabilities(); - registerCallbacks(); } -Device::~Device() -{ - for (auto element : mDisplays) { - auto display = element.second.lock(); - if (!display) { - ALOGE("~Device: Found a display (%" PRId64 " that has already been" - " destroyed", element.first); - continue; - } - - DisplayType displayType = HWC2::DisplayType::Invalid; - auto error = display->getType(&displayType); - if (error != Error::None) { - ALOGE("~Device: Failed to determine type of display %" PRIu64 - ": %s (%d)", display->getId(), to_string(error).c_str(), - static_cast(error)); - continue; - } - - if (displayType == HWC2::DisplayType::Physical) { - error = display->setVsyncEnabled(HWC2::Vsync::Disable); - if (error != Error::None) { - ALOGE("~Device: Failed to disable vsync for display %" PRIu64 - ": %s (%d)", display->getId(), to_string(error).c_str(), - static_cast(error)); - } - } +void Device::registerCallback(ComposerCallback* callback, int32_t sequenceId) { + if (mRegisteredCallback) { + ALOGW("Callback already registered. Ignored extra registration " + "attempt."); + return; } + mRegisteredCallback = true; + sp callbackBridge( + new ComposerCallbackBridge(callback, sequenceId)); + mComposer->registerCallback(callbackBridge); + LOG_ALWAYS_FATAL_IF(!callbackBridge->HasPrimaryDisplay(), + "Registered composer callback but didn't get primary display"); } // Required by HWC2 device @@ -146,7 +134,7 @@ uint32_t Device::getMaxVirtualDisplayCount() const } Error Device::createVirtualDisplay(uint32_t width, uint32_t height, - android_pixel_format_t* format, std::shared_ptr* outDisplay) + android_pixel_format_t* format, Display** outDisplay) { ALOGI("Creating virtual display"); @@ -159,104 +147,66 @@ Error Device::createVirtualDisplay(uint32_t width, uint32_t height, return error; } - ALOGI("Created virtual display"); + auto display = std::make_unique( + *mComposer.get(), mCapabilities, displayId, DisplayType::Virtual); + *outDisplay = display.get(); *format = static_cast(intFormat); - *outDisplay = getDisplayById(displayId); - if (!*outDisplay) { - ALOGE("Failed to get display by id"); - return Error::BadDisplay; - } - (*outDisplay)->setConnected(true); + mDisplays.emplace(displayId, std::move(display)); + ALOGI("Created virtual display"); return Error::None; } -void Device::registerHotplugCallback(HotplugCallback hotplug) +void Device::destroyDisplay(hwc2_display_t displayId) { - ALOGV("registerHotplugCallback"); - mHotplug = hotplug; - for (auto& pending : mPendingHotplugs) { - auto& display = pending.first; - auto connected = pending.second; - ALOGV("Sending pending hotplug(%" PRIu64 ", %s)", display->getId(), - to_string(connected).c_str()); - mHotplug(std::move(display), connected); - } + ALOGI("Destroying display %" PRIu64, displayId); + mDisplays.erase(displayId); } -void Device::registerRefreshCallback(RefreshCallback refresh) -{ - mRefresh = refresh; - for (auto& pending : mPendingRefreshes) { - mRefresh(std::move(pending)); - } -} - -void Device::registerVsyncCallback(VsyncCallback vsync) -{ - mVsync = vsync; - for (auto& pending : mPendingVsyncs) { - auto& display = pending.first; - auto timestamp = pending.second; - mVsync(std::move(display), timestamp); - } -} - -// For use by Device callbacks +void Device::onHotplug(hwc2_display_t displayId, Connection connection) { + if (connection == Connection::Connected) { + auto display = getDisplayById(displayId); + if (display) { + if (display->isConnected()) { + ALOGW("Attempt to hotplug connect display %" PRIu64 + " , which is already connected.", displayId); + } else { + display->setConnected(true); + } + } else { + DisplayType displayType; + auto intError = mComposer->getDisplayType(displayId, + reinterpret_cast( + &displayType)); + auto error = static_cast(intError); + if (error != Error::None) { + ALOGE("getDisplayType(%" PRIu64 ") failed: %s (%d). " + "Aborting hotplug attempt.", + displayId, to_string(error).c_str(), intError); + return; + } -void Device::callHotplug(std::shared_ptr display, Connection connected) -{ - if (connected == Connection::Connected) { - if (!display->isConnected()) { - mComposer->setClientTargetSlotCount(display->getId()); - display->loadConfigs(); - display->setConnected(true); + auto newDisplay = std::make_unique( + *mComposer.get(), mCapabilities, displayId, displayType); + mDisplays.emplace(displayId, std::move(newDisplay)); + } + } else if (connection == Connection::Disconnected) { + // The display will later be destroyed by a call to + // destroyDisplay(). For now we just mark it disconnected. + auto display = getDisplayById(displayId); + if (display) { + display->setConnected(false); + } else { + ALOGW("Attempted to disconnect unknown display %" PRIu64, + displayId); } - } else { - display->setConnected(false); - mDisplays.erase(display->getId()); - } - - if (mHotplug) { - mHotplug(std::move(display), connected); - } else { - ALOGV("callHotplug called, but no valid callback registered, storing"); - mPendingHotplugs.emplace_back(std::move(display), connected); - } -} - -void Device::callRefresh(std::shared_ptr display) -{ - if (mRefresh) { - mRefresh(std::move(display)); - } else { - ALOGV("callRefresh called, but no valid callback registered, storing"); - mPendingRefreshes.emplace_back(std::move(display)); - } -} - -void Device::callVsync(std::shared_ptr display, nsecs_t timestamp) -{ - if (mVsync) { - mVsync(std::move(display), timestamp); - } else { - ALOGV("callVsync called, but no valid callback registered, storing"); - mPendingVsyncs.emplace_back(std::move(display), timestamp); } } // Other Device methods -std::shared_ptr Device::getDisplayById(hwc2_display_t id) { - if (mDisplays.count(id) != 0) { - auto strongDisplay = mDisplays[id].lock(); - ALOGE_IF(!strongDisplay, "Display %" PRId64 " is in mDisplays but is no" - " longer alive", id); - return strongDisplay; - } - - auto display = std::make_shared(*this, id); - mDisplays.emplace(id, display); - return display; +Display* Device::getDisplayById(hwc2_display_t id) { + auto iter = mDisplays.find(id); + return iter == mDisplays.end() ? nullptr : iter->second.get(); } // Device initialization methods @@ -271,84 +221,37 @@ void Device::loadCapabilities() } } -bool Device::hasCapability(HWC2::Capability capability) const -{ - return std::find(mCapabilities.cbegin(), mCapabilities.cend(), - capability) != mCapabilities.cend(); -} - -namespace { -class ComposerCallback : public Hwc2::IComposerCallback { -public: - ComposerCallback(Device* device) : mDevice(device) {} - - Return onHotplug(Hwc2::Display display, - Connection connected) override - { - hotplug_hook(mDevice, display, static_cast(connected)); - return Void(); - } - - Return onRefresh(Hwc2::Display display) override - { - refresh_hook(mDevice, display); - return Void(); - } - - Return onVsync(Hwc2::Display display, int64_t timestamp) override - { - vsync_hook(mDevice, display, timestamp); - return Void(); - } - -private: - Device* mDevice; -}; -} // namespace anonymous - -void Device::registerCallbacks() -{ - sp callback = new ComposerCallback(this); - mComposer->registerCallback(callback); -} - - -// For use by Display - -void Device::destroyVirtualDisplay(hwc2_display_t display) -{ - ALOGI("Destroying virtual display"); - auto intError = mComposer->destroyVirtualDisplay(display); - auto error = static_cast(intError); - ALOGE_IF(error != Error::None, "destroyVirtualDisplay(%" PRIu64 ") failed:" - " %s (%d)", display, to_string(error).c_str(), intError); - mDisplays.erase(display); -} - // Display methods -Display::Display(Device& device, hwc2_display_t id) - : mDevice(device), +Display::Display(android::Hwc2::Composer& composer, + const std::unordered_set& capabilities, + hwc2_display_t id, DisplayType type) + : mComposer(composer), + mCapabilities(capabilities), mId(id), mIsConnected(false), - mType(DisplayType::Invalid) + mType(type) { ALOGV("Created display %" PRIu64, id); - - auto intError = mDevice.mComposer->getDisplayType(mId, - reinterpret_cast(&mType)); - auto error = static_cast(intError); - if (error != Error::None) { - ALOGE("getDisplayType(%" PRIu64 ") failed: %s (%d)", - id, to_string(error).c_str(), intError); - } + setConnected(true); } -Display::~Display() -{ - ALOGV("Destroyed display %" PRIu64, mId); +Display::~Display() { + mLayers.clear(); + if (mType == DisplayType::Virtual) { - mDevice.destroyVirtualDisplay(mId); + ALOGV("Destroying virtual display"); + auto intError = mComposer.destroyVirtualDisplay(mId); + auto error = static_cast(intError); + ALOGE_IF(error != Error::None, "destroyVirtualDisplay(%" PRIu64 + ") failed: %s (%d)", mId, to_string(error).c_str(), intError); + } else if (mType == DisplayType::Physical) { + auto error = setVsyncEnabled(HWC2::Vsync::Disable); + if (error != Error::None) { + ALOGE("~Display: Failed to disable vsync for display %" PRIu64 + ": %s (%d)", mId, to_string(error).c_str(), + static_cast(error)); + } } } @@ -383,22 +286,35 @@ float Display::Config::Builder::getDefaultDensity() { Error Display::acceptChanges() { - auto intError = mDevice.mComposer->acceptDisplayChanges(mId); + auto intError = mComposer.acceptDisplayChanges(mId); return static_cast(intError); } -Error Display::createLayer(std::shared_ptr* outLayer) +Error Display::createLayer(Layer** outLayer) { + if (!outLayer) { + return Error::BadParameter; + } hwc2_layer_t layerId = 0; - auto intError = mDevice.mComposer->createLayer(mId, &layerId); + auto intError = mComposer.createLayer(mId, &layerId); auto error = static_cast(intError); if (error != Error::None) { return error; } - auto layer = std::make_shared(shared_from_this(), layerId); - mLayers.emplace(layerId, layer); - *outLayer = std::move(layer); + auto layer = std::make_unique( + mComposer, mCapabilities, mId, layerId); + *outLayer = layer.get(); + mLayers.emplace(layerId, std::move(layer)); + return Error::None; +} + +Error Display::destroyLayer(Layer* layer) +{ + if (!layer) { + return Error::BadParameter; + } + mLayers.erase(layer->getId()); return Error::None; } @@ -407,7 +323,7 @@ Error Display::getActiveConfig( { ALOGV("[%" PRIu64 "] getActiveConfig", mId); hwc2_config_t configId = 0; - auto intError = mDevice.mComposer->getActiveConfig(mId, &configId); + auto intError = mComposer.getActiveConfig(mId, &configId); auto error = static_cast(intError); if (error != Error::None) { @@ -430,12 +346,12 @@ Error Display::getActiveConfig( } Error Display::getChangedCompositionTypes( - std::unordered_map, Composition>* outTypes) + std::unordered_map* outTypes) { std::vector layerIds; std::vector types; - auto intError = mDevice.mComposer->getChangedCompositionTypes(mId, - &layerIds, &types); + auto intError = mComposer.getChangedCompositionTypes( + mId, &layerIds, &types); uint32_t numElements = layerIds.size(); auto error = static_cast(intError); error = static_cast(intError); @@ -464,7 +380,7 @@ Error Display::getChangedCompositionTypes( Error Display::getColorModes(std::vector* outModes) const { std::vector modes; - auto intError = mDevice.mComposer->getColorModes(mId, &modes); + auto intError = mComposer.getColorModes(mId, &modes); uint32_t numModes = modes.size(); auto error = static_cast(intError); if (error != Error::None) { @@ -489,19 +405,18 @@ std::vector> Display::getConfigs() const Error Display::getName(std::string* outName) const { - auto intError = mDevice.mComposer->getDisplayName(mId, outName); + auto intError = mComposer.getDisplayName(mId, outName); return static_cast(intError); } Error Display::getRequests(HWC2::DisplayRequest* outDisplayRequests, - std::unordered_map, LayerRequest>* - outLayerRequests) + std::unordered_map* outLayerRequests) { uint32_t intDisplayRequests; std::vector layerIds; std::vector layerRequests; - auto intError = mDevice.mComposer->getDisplayRequests(mId, - &intDisplayRequests, &layerIds, &layerRequests); + auto intError = mComposer.getDisplayRequests( + mId, &intDisplayRequests, &layerIds, &layerRequests); uint32_t numElements = layerIds.size(); auto error = static_cast(intError); if (error != Error::None) { @@ -535,7 +450,7 @@ Error Display::getType(DisplayType* outType) const Error Display::supportsDoze(bool* outSupport) const { bool intSupport = false; - auto intError = mDevice.mComposer->getDozeSupport(mId, &intSupport); + auto intError = mComposer.getDozeSupport(mId, &intSupport); auto error = static_cast(intError); if (error != Error::None) { return error; @@ -552,7 +467,7 @@ Error Display::getHdrCapabilities( float maxAverageLuminance = -1.0f; float minLuminance = -1.0f; std::vector intTypes; - auto intError = mDevice.mComposer->getHdrCapabilities(mId, &intTypes, + auto intError = mComposer.getHdrCapabilities(mId, &intTypes, &maxLuminance, &maxAverageLuminance, &minLuminance); auto error = static_cast(intError); @@ -571,25 +486,24 @@ Error Display::getHdrCapabilities( } Error Display::getReleaseFences( - std::unordered_map, sp>* outFences) const + std::unordered_map>* outFences) const { std::vector layerIds; std::vector fenceFds; - auto intError = mDevice.mComposer->getReleaseFences(mId, - &layerIds, &fenceFds); + auto intError = mComposer.getReleaseFences(mId, &layerIds, &fenceFds); auto error = static_cast(intError); uint32_t numElements = layerIds.size(); if (error != Error::None) { return error; } - std::unordered_map, sp> releaseFences; + std::unordered_map> releaseFences; releaseFences.reserve(numElements); for (uint32_t element = 0; element < numElements; ++element) { auto layer = getLayerById(layerIds[element]); if (layer) { sp fence(new Fence(fenceFds[element])); - releaseFences.emplace(std::move(layer), fence); + releaseFences.emplace(layer, fence); } else { ALOGE("getReleaseFences: invalid layer %" PRIu64 " found on display %" PRIu64, layerIds[element], mId); @@ -607,7 +521,7 @@ Error Display::getReleaseFences( Error Display::present(sp* outPresentFence) { int32_t presentFenceFd = -1; - auto intError = mDevice.mComposer->presentDisplay(mId, &presentFenceFd); + auto intError = mComposer.presentDisplay(mId, &presentFenceFd); auto error = static_cast(intError); if (error != Error::None) { return error; @@ -625,7 +539,7 @@ Error Display::setActiveConfig(const std::shared_ptr& config) config->getDisplayId(), mId); return Error::BadConfig; } - auto intError = mDevice.mComposer->setActiveConfig(mId, config->getId()); + auto intError = mComposer.setActiveConfig(mId, config->getId()); return static_cast(intError); } @@ -634,7 +548,7 @@ Error Display::setClientTarget(uint32_t slot, const sp& target, { // TODO: Properly encode client target surface damage int32_t fenceFd = acquireFence->dup(); - auto intError = mDevice.mComposer->setClientTarget(mId, slot, target, + auto intError = mComposer.setClientTarget(mId, slot, target, fenceFd, static_cast(dataspace), std::vector()); return static_cast(intError); @@ -642,15 +556,15 @@ Error Display::setClientTarget(uint32_t slot, const sp& target, Error Display::setColorMode(android_color_mode_t mode) { - auto intError = mDevice.mComposer->setColorMode(mId, - static_cast(mode)); + auto intError = mComposer.setColorMode( + mId, static_cast(mode)); return static_cast(intError); } Error Display::setColorTransform(const android::mat4& matrix, android_color_transform_t hint) { - auto intError = mDevice.mComposer->setColorTransform(mId, + auto intError = mComposer.setColorTransform(mId, matrix.asArray(), static_cast(hint)); return static_cast(intError); } @@ -660,7 +574,7 @@ Error Display::setOutputBuffer(const sp& buffer, { int32_t fenceFd = releaseFence->dup(); auto handle = buffer->getNativeBuffer()->handle; - auto intError = mDevice.mComposer->setOutputBuffer(mId, handle, fenceFd); + auto intError = mComposer.setOutputBuffer(mId, handle, fenceFd); close(fenceFd); return static_cast(intError); } @@ -668,14 +582,14 @@ Error Display::setOutputBuffer(const sp& buffer, Error Display::setPowerMode(PowerMode mode) { auto intMode = static_cast(mode); - auto intError = mDevice.mComposer->setPowerMode(mId, intMode); + auto intError = mComposer.setPowerMode(mId, intMode); return static_cast(intError); } Error Display::setVsyncEnabled(Vsync enabled) { auto intEnabled = static_cast(enabled); - auto intError = mDevice.mComposer->setVsyncEnabled(mId, intEnabled); + auto intError = mComposer.setVsyncEnabled(mId, intEnabled); return static_cast(intError); } @@ -683,8 +597,7 @@ Error Display::validate(uint32_t* outNumTypes, uint32_t* outNumRequests) { uint32_t numTypes = 0; uint32_t numRequests = 0; - auto intError = mDevice.mComposer->validateDisplay(mId, - &numTypes, &numRequests); + auto intError = mComposer.validateDisplay(mId, &numTypes, &numRequests); auto error = static_cast(intError); if (error != Error::None && error != Error::HasChanges) { return error; @@ -701,7 +614,8 @@ Error Display::presentOrValidate(uint32_t* outNumTypes, uint32_t* outNumRequests uint32_t numTypes = 0; uint32_t numRequests = 0; int32_t presentFenceFd = -1; - auto intError = mDevice.mComposer->presentOrValidateDisplay(mId, &numTypes, &numRequests, &presentFenceFd, state); + auto intError = mComposer.presentOrValidateDisplay( + mId, &numTypes, &numRequests, &presentFenceFd, state); auto error = static_cast(intError); if (error != Error::None && error != Error::HasChanges) { return error; @@ -720,15 +634,23 @@ Error Display::presentOrValidate(uint32_t* outNumTypes, uint32_t* outNumRequests void Display::discardCommands() { - mDevice.mComposer->resetCommands(); + mComposer.resetCommands(); } // For use by Device +void Display::setConnected(bool connected) { + if (!mIsConnected && connected && mType == DisplayType::Physical) { + mComposer.setClientTargetSlotCount(mId); + loadConfigs(); + } + mIsConnected = connected; +} + int32_t Display::getAttribute(hwc2_config_t configId, Attribute attribute) { int32_t value = 0; - auto intError = mDevice.mComposer->getDisplayAttribute(mId, configId, + auto intError = mComposer.getDisplayAttribute(mId, configId, static_cast(attribute), &value); auto error = static_cast(intError); @@ -760,7 +682,7 @@ void Display::loadConfigs() ALOGV("[%" PRIu64 "] loadConfigs", mId); std::vector configIds; - auto intError = mDevice.mComposer->getDisplayConfigs(mId, &configIds); + auto intError = mComposer.getDisplayConfigs(mId, &configIds); auto error = static_cast(intError); if (error != Error::None) { ALOGE("[%" PRIu64 "] getDisplayConfigs [2] failed: %s (%d)", mId, @@ -773,54 +695,51 @@ void Display::loadConfigs() } } -// For use by Layer - -void Display::destroyLayer(hwc2_layer_t layerId) -{ - auto intError =mDevice.mComposer->destroyLayer(mId, layerId); - auto error = static_cast(intError); - ALOGE_IF(error != Error::None, "destroyLayer(%" PRIu64 ", %" PRIu64 ")" - " failed: %s (%d)", mId, layerId, to_string(error).c_str(), - intError); - mLayers.erase(layerId); -} - // Other Display methods -std::shared_ptr Display::getLayerById(hwc2_layer_t id) const +Layer* Display::getLayerById(hwc2_layer_t id) const { if (mLayers.count(id) == 0) { return nullptr; } - auto layer = mLayers.at(id).lock(); - return layer; + return mLayers.at(id).get(); } // Layer methods -Layer::Layer(const std::shared_ptr& display, hwc2_layer_t id) - : mDisplay(display), - mDisplayId(display->getId()), - mDevice(display->getDevice()), - mId(id) +Layer::Layer(android::Hwc2::Composer& composer, + const std::unordered_set& capabilities, + hwc2_display_t displayId, hwc2_layer_t layerId) + : mComposer(composer), + mCapabilities(capabilities), + mDisplayId(displayId), + mId(layerId) { - ALOGV("Created layer %" PRIu64 " on display %" PRIu64, id, - display->getId()); + ALOGV("Created layer %" PRIu64 " on display %" PRIu64, layerId, displayId); } Layer::~Layer() { - auto display = mDisplay.lock(); - if (display) { - display->destroyLayer(mId); + auto intError = mComposer.destroyLayer(mDisplayId, mId); + auto error = static_cast(intError); + ALOGE_IF(error != Error::None, "destroyLayer(%" PRIu64 ", %" PRIu64 ")" + " failed: %s (%d)", mDisplayId, mId, to_string(error).c_str(), + intError); + if (mLayerDestroyedListener) { + mLayerDestroyedListener(this); } } +void Layer::setLayerDestroyedListener(std::function listener) { + LOG_ALWAYS_FATAL_IF(mLayerDestroyedListener && listener, + "Attempt to set layer destroyed listener multiple times"); + mLayerDestroyedListener = listener; +} + Error Layer::setCursorPosition(int32_t x, int32_t y) { - auto intError = mDevice.mComposer->setCursorPosition(mDisplayId, - mId, x, y); + auto intError = mComposer.setCursorPosition(mDisplayId, mId, x, y); return static_cast(intError); } @@ -828,8 +747,8 @@ Error Layer::setBuffer(uint32_t slot, const sp& buffer, const sp& acquireFence) { int32_t fenceFd = acquireFence->dup(); - auto intError = mDevice.mComposer->setLayerBuffer(mDisplayId, - mId, slot, buffer, fenceFd); + auto intError = mComposer.setLayerBuffer(mDisplayId, mId, slot, buffer, + fenceFd); return static_cast(intError); } @@ -839,7 +758,7 @@ Error Layer::setSurfaceDamage(const Region& damage) // rects for HWC Hwc2::Error intError = Hwc2::Error::NONE; if (damage.isRect() && damage.getBounds() == Rect::INVALID_RECT) { - intError = mDevice.mComposer->setLayerSurfaceDamage(mDisplayId, + intError = mComposer.setLayerSurfaceDamage(mDisplayId, mId, std::vector()); } else { size_t rectCount = 0; @@ -851,8 +770,7 @@ Error Layer::setSurfaceDamage(const Region& damage) rectArray[rect].right, rectArray[rect].bottom}); } - intError = mDevice.mComposer->setLayerSurfaceDamage(mDisplayId, - mId, hwcRects); + intError = mComposer.setLayerSurfaceDamage(mDisplayId, mId, hwcRects); } return static_cast(intError); @@ -861,24 +779,22 @@ Error Layer::setSurfaceDamage(const Region& damage) Error Layer::setBlendMode(BlendMode mode) { auto intMode = static_cast(mode); - auto intError = mDevice.mComposer->setLayerBlendMode(mDisplayId, - mId, intMode); + auto intError = mComposer.setLayerBlendMode(mDisplayId, mId, intMode); return static_cast(intError); } Error Layer::setColor(hwc_color_t color) { Hwc2::IComposerClient::Color hwcColor{color.r, color.g, color.b, color.a}; - auto intError = mDevice.mComposer->setLayerColor(mDisplayId, - mId, hwcColor); + auto intError = mComposer.setLayerColor(mDisplayId, mId, hwcColor); return static_cast(intError); } Error Layer::setCompositionType(Composition type) { auto intType = static_cast(type); - auto intError = mDevice.mComposer->setLayerCompositionType(mDisplayId, - mId, intType); + auto intError = mComposer.setLayerCompositionType( + mDisplayId, mId, intType); return static_cast(intError); } @@ -889,8 +805,7 @@ Error Layer::setDataspace(android_dataspace_t dataspace) } mDataSpace = dataspace; auto intDataspace = static_cast(dataspace); - auto intError = mDevice.mComposer->setLayerDataspace(mDisplayId, - mId, intDataspace); + auto intError = mComposer.setLayerDataspace(mDisplayId, mId, intDataspace); return static_cast(intError); } @@ -898,27 +813,24 @@ Error Layer::setDisplayFrame(const Rect& frame) { Hwc2::IComposerClient::Rect hwcRect{frame.left, frame.top, frame.right, frame.bottom}; - auto intError = mDevice.mComposer->setLayerDisplayFrame(mDisplayId, - mId, hwcRect); + auto intError = mComposer.setLayerDisplayFrame(mDisplayId, mId, hwcRect); return static_cast(intError); } Error Layer::setPlaneAlpha(float alpha) { - auto intError = mDevice.mComposer->setLayerPlaneAlpha(mDisplayId, - mId, alpha); + auto intError = mComposer.setLayerPlaneAlpha(mDisplayId, mId, alpha); return static_cast(intError); } Error Layer::setSidebandStream(const native_handle_t* stream) { - if (!mDevice.hasCapability(Capability::SidebandStream)) { + if (mCapabilities.count(Capability::SidebandStream) == 0) { ALOGE("Attempted to call setSidebandStream without checking that the " "device supports sideband streams"); return Error::Unsupported; } - auto intError = mDevice.mComposer->setLayerSidebandStream(mDisplayId, - mId, stream); + auto intError = mComposer.setLayerSidebandStream(mDisplayId, mId, stream); return static_cast(intError); } @@ -926,16 +838,14 @@ Error Layer::setSourceCrop(const FloatRect& crop) { Hwc2::IComposerClient::FRect hwcRect{ crop.left, crop.top, crop.right, crop.bottom}; - auto intError = mDevice.mComposer->setLayerSourceCrop(mDisplayId, - mId, hwcRect); + auto intError = mComposer.setLayerSourceCrop(mDisplayId, mId, hwcRect); return static_cast(intError); } Error Layer::setTransform(Transform transform) { auto intTransform = static_cast(transform); - auto intError = mDevice.mComposer->setLayerTransform(mDisplayId, - mId, intTransform); + auto intError = mComposer.setLayerTransform(mDisplayId, mId, intTransform); return static_cast(intError); } @@ -950,20 +860,19 @@ Error Layer::setVisibleRegion(const Region& region) rectArray[rect].right, rectArray[rect].bottom}); } - auto intError = mDevice.mComposer->setLayerVisibleRegion(mDisplayId, - mId, hwcRects); + auto intError = mComposer.setLayerVisibleRegion(mDisplayId, mId, hwcRects); return static_cast(intError); } Error Layer::setZOrder(uint32_t z) { - auto intError = mDevice.mComposer->setLayerZOrder(mDisplayId, mId, z); + auto intError = mComposer.setLayerZOrder(mDisplayId, mId, z); return static_cast(intError); } Error Layer::setInfo(uint32_t type, uint32_t appId) { - auto intError = mDevice.mComposer->setLayerInfo(mDisplayId, mId, type, appId); + auto intError = mComposer.setLayerInfo(mDisplayId, mId, type, appId); return static_cast(intError); } diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h index 9bcda1eb0a..fbe4c7ebed 100644 --- a/services/surfaceflinger/DisplayHardware/HWC2.h +++ b/services/surfaceflinger/DisplayHardware/HWC2.h @@ -53,10 +53,26 @@ namespace HWC2 { class Display; class Layer; -typedef std::function, Connection)> - HotplugCallback; -typedef std::function)> RefreshCallback; -typedef std::function, nsecs_t)> VsyncCallback; +// Implement this interface to receive hardware composer events. +// +// These callback functions will generally be called on a hwbinder thread, but +// when first registering the callback the onHotplugReceived() function will +// immediately be called on the thread calling registerCallback(). +// +// All calls receive a sequenceId, which will be the value that was supplied to +// HWC2::Device::registerCallback(). It's used to help differentiate callbacks +// from different hardware composer instances. +class ComposerCallback { + public: + virtual void onHotplugReceived(int32_t sequenceId, hwc2_display_t display, + Connection connection, + bool primaryDisplay) = 0; + virtual void onRefreshReceived(int32_t sequenceId, + hwc2_display_t display) = 0; + virtual void onVsyncReceived(int32_t sequenceId, hwc2_display_t display, + int64_t timestamp) = 0; + virtual ~ComposerCallback() = default; +}; // C++ Wrapper around hwc2_device_t. Load all functions pointers // and handle callback registration. @@ -66,10 +82,8 @@ public: // Service name is expected to be 'default' or 'vr' for normal use. // 'vr' will slightly modify the behavior of the mComposer. Device(const std::string& serviceName); - ~Device(); - friend class HWC2::Display; - friend class HWC2::Layer; + void registerCallback(ComposerCallback* callback, int32_t sequenceId); // Required by HWC2 @@ -81,27 +95,14 @@ public: uint32_t getMaxVirtualDisplayCount() const; Error createVirtualDisplay(uint32_t width, uint32_t height, - android_pixel_format_t* format, - std::shared_ptr* outDisplay); - - void registerHotplugCallback(HotplugCallback hotplug); - void registerRefreshCallback(RefreshCallback refresh); - void registerVsyncCallback(VsyncCallback vsync); + android_pixel_format_t* format, Display** outDisplay); + void destroyDisplay(hwc2_display_t displayId); - // For use by callbacks - - void callHotplug(std::shared_ptr display, Connection connected); - void callRefresh(std::shared_ptr display); - void callVsync(std::shared_ptr display, nsecs_t timestamp); + void onHotplug(hwc2_display_t displayId, Connection connection); // Other Device methods - // This will create a Display if one is not found, but it will not be marked - // as connected. This Display may be null if the display has been torn down - // but has not been removed from the map yet. - std::shared_ptr getDisplayById(hwc2_display_t id); - - bool hasCapability(HWC2::Capability capability) const; + Display* getDisplayById(hwc2_display_t id); android::Hwc2::Composer* getComposer() { return mComposer.get(); } @@ -109,37 +110,23 @@ private: // Initialization methods void loadCapabilities(); - void registerCallbacks(); - - // For use by Display - - void destroyVirtualDisplay(hwc2_display_t display); // Member variables std::unique_ptr mComposer; - std::unordered_set mCapabilities; - std::unordered_map> mDisplays; - - HotplugCallback mHotplug; - std::vector, Connection>> - mPendingHotplugs; - RefreshCallback mRefresh; - std::vector> mPendingRefreshes; - VsyncCallback mVsync; - std::vector, nsecs_t>> mPendingVsyncs; + std::unordered_map> mDisplays; + bool mRegisteredCallback; }; // Convenience C++ class to access hwc2_device_t Display functions directly. -class Display : public std::enable_shared_from_this +class Display { public: - Display(Device& device, hwc2_display_t id); + Display(android::Hwc2::Composer& composer, + const std::unordered_set& capabilities, + hwc2_display_t id, DisplayType type); ~Display(); - friend class HWC2::Device; - friend class HWC2::Layer; - class Config { public: @@ -212,12 +199,12 @@ public: // Required by HWC2 [[clang::warn_unused_result]] Error acceptChanges(); - [[clang::warn_unused_result]] Error createLayer( - std::shared_ptr* outLayer); + [[clang::warn_unused_result]] Error createLayer(Layer** outLayer); + [[clang::warn_unused_result]] Error destroyLayer(Layer* layer); [[clang::warn_unused_result]] Error getActiveConfig( std::shared_ptr* outConfig) const; [[clang::warn_unused_result]] Error getChangedCompositionTypes( - std::unordered_map, Composition>* outTypes); + std::unordered_map* outTypes); [[clang::warn_unused_result]] Error getColorModes( std::vector* outModes) const; @@ -227,14 +214,13 @@ public: [[clang::warn_unused_result]] Error getName(std::string* outName) const; [[clang::warn_unused_result]] Error getRequests( DisplayRequest* outDisplayRequests, - std::unordered_map, LayerRequest>* - outLayerRequests); + std::unordered_map* outLayerRequests); [[clang::warn_unused_result]] Error getType(DisplayType* outType) const; [[clang::warn_unused_result]] Error supportsDoze(bool* outSupport) const; [[clang::warn_unused_result]] Error getHdrCapabilities( std::unique_ptr* outCapabilities) const; [[clang::warn_unused_result]] Error getReleaseFences( - std::unordered_map, + std::unordered_map>* outFences) const; [[clang::warn_unused_result]] Error present( android::sp* outPresentFence); @@ -266,32 +252,31 @@ public: // Other Display methods - Device& getDevice() const { return mDevice; } hwc2_display_t getId() const { return mId; } bool isConnected() const { return mIsConnected; } + void setConnected(bool connected); // For use by Device only private: - // For use by Device - - void setConnected(bool connected) { mIsConnected = connected; } int32_t getAttribute(hwc2_config_t configId, Attribute attribute); void loadConfig(hwc2_config_t configId); void loadConfigs(); - // For use by Layer - void destroyLayer(hwc2_layer_t layerId); - // This may fail (and return a null pointer) if no layer with this ID exists // on this display - std::shared_ptr getLayerById(hwc2_layer_t id) const; + Layer* getLayerById(hwc2_layer_t id) const; // Member variables - Device& mDevice; + // These are references to data owned by HWC2::Device, which will outlive + // this HWC2::Display, so these references are guaranteed to be valid for + // the lifetime of this object. + android::Hwc2::Composer& mComposer; + const std::unordered_set& mCapabilities; + hwc2_display_t mId; bool mIsConnected; DisplayType mType; - std::unordered_map> mLayers; + std::unordered_map> mLayers; // The ordering in this map matters, for getConfigs(), when it is // converted to a vector std::map> mConfigs; @@ -301,12 +286,18 @@ private: class Layer { public: - Layer(const std::shared_ptr& display, hwc2_layer_t id); + Layer(android::Hwc2::Composer& composer, + const std::unordered_set& capabilities, + hwc2_display_t displayId, hwc2_layer_t layerId); ~Layer(); - bool isAbandoned() const { return mDisplay.expired(); } hwc2_layer_t getId() const { return mId; } + // Register a listener to be notified when the layer is destroyed. When the + // listener function is called, the Layer will be in the process of being + // destroyed, so it's not safe to call methods on it. + void setLayerDestroyedListener(std::function listener); + [[clang::warn_unused_result]] Error setCursorPosition(int32_t x, int32_t y); [[clang::warn_unused_result]] Error setBuffer(uint32_t slot, const android::sp& buffer, @@ -333,11 +324,16 @@ public: [[clang::warn_unused_result]] Error setInfo(uint32_t type, uint32_t appId); private: - std::weak_ptr mDisplay; + // These are references to data owned by HWC2::Device, which will outlive + // this HWC2::Layer, so these references are guaranteed to be valid for + // the lifetime of this object. + android::Hwc2::Composer& mComposer; + const std::unordered_set& mCapabilities; + hwc2_display_t mDisplayId; - Device& mDevice; hwc2_layer_t mId; android_dataspace mDataSpace = HAL_DATASPACE_UNKNOWN; + std::function mLayerDestroyedListener; }; } // namespace HWC2 diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index abf7dd12d0..b096a3ae57 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -65,7 +65,6 @@ HWComposer::HWComposer(const std::string& serviceName) mFreeDisplaySlots(), mHwcDisplaySlots(), mCBContext(), - mEventHandler(nullptr), mVSyncCounts(), mRemainingHwcVirtualDisplays(0) { @@ -74,41 +73,15 @@ HWComposer::HWComposer(const std::string& serviceName) mVSyncCounts[i] = 0; } - loadHwcModule(serviceName); + mHwcDevice = std::make_unique(serviceName); + mRemainingHwcVirtualDisplays = mHwcDevice->getMaxVirtualDisplayCount(); } HWComposer::~HWComposer() {} -void HWComposer::setEventHandler(EventHandler* handler) -{ - if (handler == nullptr) { - ALOGE("setEventHandler: Rejected attempt to clear handler"); - return; - } - - bool wasNull = (mEventHandler == nullptr); - mEventHandler = handler; - - if (wasNull) { - auto hotplugHook = std::bind(&HWComposer::hotplug, this, - std::placeholders::_1, std::placeholders::_2); - mHwcDevice->registerHotplugCallback(hotplugHook); - auto invalidateHook = std::bind(&HWComposer::invalidate, this, - std::placeholders::_1); - mHwcDevice->registerRefreshCallback(invalidateHook); - auto vsyncHook = std::bind(&HWComposer::vsync, this, - std::placeholders::_1, std::placeholders::_2); - mHwcDevice->registerVsyncCallback(vsyncHook); - } -} - -// Load and prepare the hardware composer module. Sets mHwc. -void HWComposer::loadHwcModule(const std::string& serviceName) -{ - ALOGV("loadHwcModule"); - mHwcDevice = std::make_unique(serviceName); - - mRemainingHwcVirtualDisplays = mHwcDevice->getMaxVirtualDisplayCount(); +void HWComposer::registerCallback(HWC2::ComposerCallback* callback, + int32_t sequenceId) { + mHwcDevice->registerCallback(callback, sequenceId); } bool HWComposer::hasCapability(HWC2::Capability capability) const @@ -146,54 +119,51 @@ void HWComposer::validateChange(HWC2::Composition from, HWC2::Composition to) { } } -void HWComposer::hotplug(const std::shared_ptr& display, - HWC2::Connection connected) { - ALOGV("hotplug: %" PRIu64 ", %s", display->getId(), - to_string(connected).c_str()); - int32_t disp = 0; +void HWComposer::onHotplug(hwc2_display_t displayId, + HWC2::Connection connection) { + ALOGV("hotplug: %" PRIu64 ", %s", displayId, + to_string(connection).c_str()); + mHwcDevice->onHotplug(displayId, connection); if (!mDisplayData[0].hwcDisplay) { - ALOGE_IF(connected != HWC2::Connection::Connected, "Assumed primary" + ALOGE_IF(connection != HWC2::Connection::Connected, "Assumed primary" " display would be connected"); - mDisplayData[0].hwcDisplay = display; - mHwcDisplaySlots[display->getId()] = 0; - disp = DisplayDevice::DISPLAY_PRIMARY; + mDisplayData[0].hwcDisplay = mHwcDevice->getDisplayById(displayId); + mHwcDisplaySlots[displayId] = 0; } else { // Disconnect is handled through HWComposer::disconnectDisplay via // SurfaceFlinger's onHotplugReceived callback handling - if (connected == HWC2::Connection::Connected) { - mDisplayData[1].hwcDisplay = display; - mHwcDisplaySlots[display->getId()] = 1; + if (connection == HWC2::Connection::Connected) { + mDisplayData[1].hwcDisplay = mHwcDevice->getDisplayById(displayId); + mHwcDisplaySlots[displayId] = 1; } - disp = DisplayDevice::DISPLAY_EXTERNAL; } - mEventHandler->onHotplugReceived(this, disp, - connected == HWC2::Connection::Connected); } -void HWComposer::invalidate(const std::shared_ptr& /*display*/) { - mEventHandler->onInvalidateReceived(this); -} - -void HWComposer::vsync(const std::shared_ptr& display, - int64_t timestamp) { +bool HWComposer::onVsync(hwc2_display_t displayId, int64_t timestamp, + int32_t* outDisplay) { + auto display = mHwcDevice->getDisplayById(displayId); + if (!display) { + ALOGE("onVsync Failed to find display %" PRIu64, displayId); + return false; + } auto displayType = HWC2::DisplayType::Invalid; auto error = display->getType(&displayType); if (error != HWC2::Error::None) { - ALOGE("vsync: Failed to determine type of display %" PRIu64, + ALOGE("onVsync: Failed to determine type of display %" PRIu64, display->getId()); - return; + return false; } if (displayType == HWC2::DisplayType::Virtual) { ALOGE("Virtual display %" PRIu64 " passed to vsync callback", display->getId()); - return; + return false; } if (mHwcDisplaySlots.count(display->getId()) == 0) { ALOGE("Unknown physical display %" PRIu64 " passed to vsync callback", display->getId()); - return; + return false; } int32_t disp = mHwcDisplaySlots[display->getId()]; @@ -207,17 +177,21 @@ void HWComposer::vsync(const std::shared_ptr& display, if (timestamp == mLastHwVSync[disp]) { ALOGW("Ignoring duplicate VSYNC event from HWC (t=%" PRId64 ")", timestamp); - return; + return false; } mLastHwVSync[disp] = timestamp; } + if (outDisplay) { + *outDisplay = disp; + } + char tag[16]; snprintf(tag, sizeof(tag), "HW_VSYNC_%1u", disp); ATRACE_INT(tag, ++mVSyncCounts[disp] & 1); - mEventHandler->onVSyncReceived(this, disp, timestamp); + return true; } status_t HWComposer::allocateVirtualDisplay(uint32_t width, uint32_t height, @@ -236,7 +210,7 @@ status_t HWComposer::allocateVirtualDisplay(uint32_t width, uint32_t height, return INVALID_OPERATION; } - std::shared_ptr display; + HWC2::Display* display; auto error = mHwcDevice->createVirtualDisplay(width, height, format, &display); if (error != HWC2::Error::None) { @@ -265,13 +239,13 @@ status_t HWComposer::allocateVirtualDisplay(uint32_t width, uint32_t height, return NO_ERROR; } -std::shared_ptr HWComposer::createLayer(int32_t displayId) { +HWC2::Layer* HWComposer::createLayer(int32_t displayId) { if (!isValidDisplay(displayId)) { ALOGE("Failed to create layer on invalid display %d", displayId); return nullptr; } auto display = mDisplayData[displayId].hwcDisplay; - std::shared_ptr layer; + HWC2::Layer* layer; auto error = display->createLayer(&layer); if (error != HWC2::Error::None) { ALOGE("Failed to create layer on display %d: %s (%d)", displayId, @@ -281,6 +255,19 @@ std::shared_ptr HWComposer::createLayer(int32_t displayId) { return layer; } +void HWComposer::destroyLayer(int32_t displayId, HWC2::Layer* layer) { + if (!isValidDisplay(displayId)) { + ALOGE("Failed to destroy layer on invalid display %d", displayId); + return; + } + auto display = mDisplayData[displayId].hwcDisplay; + auto error = display->destroyLayer(layer); + if (error != HWC2::Error::None) { + ALOGE("Failed to destroy layer on display %d: %s (%d)", displayId, + to_string(error).c_str(), static_cast(error)); + } +} + nsecs_t HWComposer::getRefreshTimestamp(int32_t displayId) const { // this returns the last refresh timestamp. // if the last one is not available, we estimate it based on @@ -348,10 +335,8 @@ std::vector HWComposer::getColorModes(int32_t displayId) c displayId); return modes; } - const std::shared_ptr& hwcDisplay = - mDisplayData[displayId].hwcDisplay; - auto error = hwcDisplay->getColorModes(&modes); + auto error = mDisplayData[displayId].hwcDisplay->getColorModes(&modes); if (error != HWC2::Error::None) { ALOGE("getColorModes failed for display %d: %s (%d)", displayId, to_string(error).c_str(), static_cast(error)); @@ -471,7 +456,7 @@ status_t HWComposer::prepare(DisplayDevice& displayDevice) { return UNKNOWN_ERROR; } if (state == 1) { //Present Succeeded. - std::unordered_map, sp> releaseFences; + std::unordered_map> releaseFences; error = hwcDisplay->getReleaseFences(&releaseFences); displayData.releaseFences = std::move(releaseFences); displayData.lastPresentFence = outPresentFence; @@ -490,8 +475,7 @@ status_t HWComposer::prepare(DisplayDevice& displayDevice) { return BAD_INDEX; } - std::unordered_map, HWC2::Composition> - changedTypes; + std::unordered_map changedTypes; changedTypes.reserve(numTypes); error = hwcDisplay->getChangedCompositionTypes(&changedTypes); if (error != HWC2::Error::None) { @@ -503,8 +487,7 @@ status_t HWComposer::prepare(DisplayDevice& displayDevice) { displayData.displayRequests = static_cast(0); - std::unordered_map, HWC2::LayerRequest> - layerRequests; + std::unordered_map layerRequests; layerRequests.reserve(numRequests); error = hwcDisplay->getRequests(&displayData.displayRequests, &layerRequests); @@ -598,7 +581,7 @@ sp HWComposer::getPresentFence(int32_t displayId) const { } sp HWComposer::getLayerReleaseFence(int32_t displayId, - const std::shared_ptr& layer) const { + HWC2::Layer* layer) const { if (!isValidDisplay(displayId)) { ALOGE("getLayerReleaseFence: Invalid display"); return Fence::NO_FENCE; @@ -639,7 +622,7 @@ status_t HWComposer::presentAndGetReleaseFences(int32_t displayId) { return UNKNOWN_ERROR; } - std::unordered_map, sp> releaseFences; + std::unordered_map> releaseFences; error = hwcDisplay->getReleaseFences(&releaseFences); if (error != HWC2::Error::None) { ALOGE("presentAndGetReleaseFences: Failed to get release fences " @@ -787,6 +770,8 @@ void HWComposer::disconnectDisplay(int displayId) { auto hwcId = displayData.hwcDisplay->getId(); mHwcDisplaySlots.erase(hwcId); displayData.reset(); + + mHwcDevice->destroyDisplay(hwcId); } status_t HWComposer::setOutputBuffer(int32_t displayId, @@ -885,7 +870,7 @@ void HWComposer::dump(String8& result) const { HWComposer::DisplayData::DisplayData() : hasClientComposition(false), hasDeviceComposition(false), - hwcDisplay(), + hwcDisplay(nullptr), lastPresentFence(Fence::NO_FENCE), outbufHandle(nullptr), outbufAcquireFence(Fence::NO_FENCE), diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index 3dfb65b1e3..3640bb5a98 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -65,23 +65,14 @@ class String8; class HWComposer { public: - class EventHandler { - friend class HWComposer; - virtual void onVSyncReceived( - HWComposer* composer, int32_t disp, nsecs_t timestamp) = 0; - virtual void onHotplugReceived(HWComposer* composer, int32_t disp, bool connected) = 0; - virtual void onInvalidateReceived(HWComposer* composer) = 0; - protected: - virtual ~EventHandler() {} - }; - // Uses the named composer service. Valid choices for normal use // are 'default' and 'vr'. HWComposer(const std::string& serviceName); ~HWComposer(); - void setEventHandler(EventHandler* handler); + void registerCallback(HWC2::ComposerCallback* callback, + int32_t sequenceId); bool hasCapability(HWC2::Capability capability) const; @@ -91,7 +82,9 @@ public: android_pixel_format_t* format, int32_t* outId); // Attempts to create a new layer on this display - std::shared_ptr createLayer(int32_t displayId); + HWC2::Layer* createLayer(int32_t displayId); + // Destroy a previously created layer + void destroyLayer(int32_t displayId, HWC2::Layer* layer); // Asks the HAL what it can do status_t prepare(DisplayDevice& displayDevice); @@ -126,7 +119,7 @@ public: // Get last release fence for the given layer sp getLayerReleaseFence(int32_t displayId, - const std::shared_ptr& layer) const; + HWC2::Layer* layer) const; // Set the output buffer and acquire fence for a virtual display. // Returns INVALID_OPERATION if displayId is not a virtual display. @@ -142,6 +135,12 @@ public: // Events handling --------------------------------------------------------- + // Returns true if successful, false otherwise. The + // DisplayDevice::DisplayType of the display is returned as an output param. + bool onVsync(hwc2_display_t displayId, int64_t timestamp, + int32_t* outDisplay); + void onHotplug(hwc2_display_t displayId, HWC2::Connection connection); + void setVsyncEnabled(int32_t displayId, HWC2::Vsync enabled); // Query display parameters. Pass in a display index (e.g. @@ -169,19 +168,11 @@ public: private: static const int32_t VIRTUAL_DISPLAY_ID_BASE = 2; - void loadHwcModule(const std::string& serviceName); - bool isValidDisplay(int32_t displayId) const; static void validateChange(HWC2::Composition from, HWC2::Composition to); struct cb_context; - void invalidate(const std::shared_ptr& display); - void vsync(const std::shared_ptr& display, - int64_t timestamp); - void hotplug(const std::shared_ptr& display, - HWC2::Connection connected); - struct DisplayData { DisplayData(); ~DisplayData(); @@ -189,11 +180,10 @@ private: bool hasClientComposition; bool hasDeviceComposition; - std::shared_ptr hwcDisplay; + HWC2::Display* hwcDisplay; HWC2::DisplayRequest displayRequests; sp lastPresentFence; // signals when the last set op retires - std::unordered_map, sp> - releaseFences; + std::unordered_map> releaseFences; buffer_handle_t outbufHandle; sp outbufAcquireFence; mutable std::unordered_mapsetVsyncEnabled(HWC_DISPLAY_PRIMARY, mVsyncEnabled); -#else - mFlinger->eventControl(HWC_DISPLAY_PRIMARY, SurfaceFlinger::EVENT_VSYNC, - mVsyncEnabled); -#endif + enum class VsyncState {Unset, On, Off}; + auto currentVsyncState = VsyncState::Unset; while (true) { - status_t err = mCond.wait(mMutex); - if (err != NO_ERROR) { - ALOGE("error waiting for new events: %s (%d)", - strerror(-err), err); - return false; + auto requestedVsyncState = VsyncState::On; + { + Mutex::Autolock lock(mMutex); + requestedVsyncState = + mVsyncEnabled ? VsyncState::On : VsyncState::Off; + while (currentVsyncState == requestedVsyncState) { + status_t err = mCond.wait(mMutex); + if (err != NO_ERROR) { + ALOGE("error waiting for new events: %s (%d)", + strerror(-err), err); + return false; + } + requestedVsyncState = + mVsyncEnabled ? VsyncState::On : VsyncState::Off; + } } - if (vsyncEnabled != mVsyncEnabled) { + bool enable = requestedVsyncState == VsyncState::On; #ifdef USE_HWC2 - mFlinger->setVsyncEnabled(HWC_DISPLAY_PRIMARY, mVsyncEnabled); + mFlinger->setVsyncEnabled(HWC_DISPLAY_PRIMARY, enable); #else - mFlinger->eventControl(HWC_DISPLAY_PRIMARY, - SurfaceFlinger::EVENT_VSYNC, mVsyncEnabled); + mFlinger->eventControl(HWC_DISPLAY_PRIMARY, + SurfaceFlinger::EVENT_VSYNC, enable); #endif - vsyncEnabled = mVsyncEnabled; - } + currentVsyncState = requestedVsyncState; } return false; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 3903a5546f..54d4cbd613 100755 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -201,6 +201,12 @@ Layer::~Layer() { } mFlinger->deleteTextureAsync(mTextureName); mFrameTracker.logAndResetStats(mName); + +#ifdef USE_HWC2 + ALOGE_IF(!mHwcLayers.empty(), + "Found stale hardware composer layers when destroying " + "surface flinger layer"); +#endif } // --------------------------------------------------------------------------- @@ -303,7 +309,7 @@ void Layer::onRemoved() { mSurfaceFlingerConsumer->abandon(); #ifdef USE_HWC2 - clearHwcLayers(); + destroyAllHwcLayers(); #endif for (const auto& child : mCurrentChildren) { @@ -364,6 +370,48 @@ sp Layer::getProducer() const { // h/w composer set-up // --------------------------------------------------------------------------- +#ifdef USE_HWC2 +bool Layer::createHwcLayer(HWComposer* hwc, int32_t hwcId) { + LOG_ALWAYS_FATAL_IF(mHwcLayers.count(hwcId) != 0, + "Already have a layer for hwcId %d", hwcId); + HWC2::Layer* layer = hwc->createLayer(hwcId); + if (!layer) { + return false; + } + HWCInfo& hwcInfo = mHwcLayers[hwcId]; + hwcInfo.hwc = hwc; + hwcInfo.layer = layer; + layer->setLayerDestroyedListener( + [this, hwcId] (HWC2::Layer* /*layer*/){mHwcLayers.erase(hwcId);}); + return true; +} + +void Layer::destroyHwcLayer(int32_t hwcId) { + if (mHwcLayers.count(hwcId) == 0) { + return; + } + auto& hwcInfo = mHwcLayers[hwcId]; + LOG_ALWAYS_FATAL_IF(hwcInfo.layer == nullptr, + "Attempt to destroy null layer"); + LOG_ALWAYS_FATAL_IF(hwcInfo.hwc == nullptr, "Missing HWComposer"); + hwcInfo.hwc->destroyLayer(hwcId, hwcInfo.layer); + // The layer destroyed listener should have cleared the entry from + // mHwcLayers. Verify that. + LOG_ALWAYS_FATAL_IF(mHwcLayers.count(hwcId) != 0, + "Stale layer entry in mHwcLayers"); +} + +void Layer::destroyAllHwcLayers() { + size_t numLayers = mHwcLayers.size(); + for (size_t i = 0; i < numLayers; ++i) { + LOG_ALWAYS_FATAL_IF(mHwcLayers.empty(), "destroyAllHwcLayers failed"); + destroyHwcLayer(mHwcLayers.begin()->first); + } + LOG_ALWAYS_FATAL_IF(!mHwcLayers.empty(), + "All hardware composer layers should have been destroyed"); +} +#endif + Rect Layer::getContentCrop() const { // this is the crop rectangle that applies to the buffer // itself (as opposed to the window) diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 8df8c49bbe..1b7d0759d5 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -447,37 +447,21 @@ public: #ifdef USE_HWC2 // ----------------------------------------------------------------------- + bool createHwcLayer(HWComposer* hwc, int32_t hwcId); + void destroyHwcLayer(int32_t hwcId); + void destroyAllHwcLayers(); + bool hasHwcLayer(int32_t hwcId) { - if (mHwcLayers.count(hwcId) == 0) { - return false; - } - if (mHwcLayers[hwcId].layer->isAbandoned()) { - ALOGI("Erasing abandoned layer %s on %d", mName.string(), hwcId); - mHwcLayers.erase(hwcId); - return false; - } - return true; + return mHwcLayers.count(hwcId) > 0; } - std::shared_ptr getHwcLayer(int32_t hwcId) { + HWC2::Layer* getHwcLayer(int32_t hwcId) { if (mHwcLayers.count(hwcId) == 0) { return nullptr; } return mHwcLayers[hwcId].layer; } - void setHwcLayer(int32_t hwcId, std::shared_ptr&& layer) { - if (layer) { - mHwcLayers[hwcId].layer = layer; - } else { - mHwcLayers.erase(hwcId); - } - } - - void clearHwcLayers() { - mHwcLayers.clear(); - } - #endif // ----------------------------------------------------------------------- @@ -766,12 +750,14 @@ private: // HWC items, accessed from the main thread struct HWCInfo { HWCInfo() - : layer(), + : hwc(nullptr), + layer(nullptr), forceClientComposition(false), compositionType(HWC2::Composition::Invalid), clearClientTarget(false) {} - std::shared_ptr layer; + HWComposer* hwc; + HWC2::Layer* layer; bool forceClientComposition; HWC2::Composition compositionType; bool clearClientTarget; diff --git a/services/surfaceflinger/RenderEngine/RenderEngine.cpp b/services/surfaceflinger/RenderEngine/RenderEngine.cpp index ac2d8b2aba..57f468d2fa 100644 --- a/services/surfaceflinger/RenderEngine/RenderEngine.cpp +++ b/services/surfaceflinger/RenderEngine/RenderEngine.cpp @@ -64,7 +64,7 @@ RenderEngine* RenderEngine::create(EGLDisplay display, int hwcFormat, uint32_t f "EGL_ANDROIDX_no_config_context") && !findExtension(eglQueryStringImplementationANDROID(display, EGL_EXTENSIONS), "EGL_KHR_no_config_context")) { - config = chooseEglConfig(display, hwcFormat); + config = chooseEglConfig(display, hwcFormat, /*logConfig*/ true); } EGLint renderableType = 0; @@ -108,7 +108,7 @@ RenderEngine* RenderEngine::create(EGLDisplay display, int hwcFormat, uint32_t f EGLConfig dummyConfig = config; if (dummyConfig == EGL_NO_CONFIG) { - dummyConfig = chooseEglConfig(display, hwcFormat); + dummyConfig = chooseEglConfig(display, hwcFormat, /*logConfig*/ true); } EGLint attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE }; EGLSurface dummy = eglCreatePbufferSurface(display, dummyConfig, attribs); @@ -406,7 +406,8 @@ static status_t selectEGLConfig(EGLDisplay display, EGLint format, return err; } -EGLConfig RenderEngine::chooseEglConfig(EGLDisplay display, int format) { +EGLConfig RenderEngine::chooseEglConfig(EGLDisplay display, int format, + bool logConfig) { status_t err; EGLConfig config; @@ -427,18 +428,20 @@ EGLConfig RenderEngine::chooseEglConfig(EGLDisplay display, int format) { } } - // print some debugging info - EGLint r,g,b,a; - eglGetConfigAttrib(display, config, EGL_RED_SIZE, &r); - eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &g); - eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &b); - eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &a); - ALOGI("EGL information:"); - ALOGI("vendor : %s", eglQueryString(display, EGL_VENDOR)); - ALOGI("version : %s", eglQueryString(display, EGL_VERSION)); - ALOGI("extensions: %s", eglQueryString(display, EGL_EXTENSIONS)); - ALOGI("Client API: %s", eglQueryString(display, EGL_CLIENT_APIS)?:"Not Supported"); - ALOGI("EGLSurface: %d-%d-%d-%d, config=%p", r, g, b, a, config); + if (logConfig) { + // print some debugging info + EGLint r,g,b,a; + eglGetConfigAttrib(display, config, EGL_RED_SIZE, &r); + eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &g); + eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &b); + eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &a); + ALOGI("EGL information:"); + ALOGI("vendor : %s", eglQueryString(display, EGL_VENDOR)); + ALOGI("version : %s", eglQueryString(display, EGL_VERSION)); + ALOGI("extensions: %s", eglQueryString(display, EGL_EXTENSIONS)); + ALOGI("Client API: %s", eglQueryString(display, EGL_CLIENT_APIS)?:"Not Supported"); + ALOGI("EGLSurface: %d-%d-%d-%d, config=%p", r, g, b, a, config); + } return config; } diff --git a/services/surfaceflinger/RenderEngine/RenderEngine.h b/services/surfaceflinger/RenderEngine/RenderEngine.h index 56f582755e..954457946e 100644 --- a/services/surfaceflinger/RenderEngine/RenderEngine.h +++ b/services/surfaceflinger/RenderEngine/RenderEngine.h @@ -64,7 +64,7 @@ public: }; static RenderEngine* create(EGLDisplay display, int hwcFormat, uint32_t featureFlags); - static EGLConfig chooseEglConfig(EGLDisplay display, int format); + static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig); void primeCache() const; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 609b15b382..0d93467125 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -77,6 +77,7 @@ #include "MonitoredProducer.h" #include "SurfaceFlinger.h" +#include "DisplayHardware/ComposerHal.h" #include "DisplayHardware/FramebufferSurface.h" #include "DisplayHardware/HWComposer.h" #include "DisplayHardware/VirtualDisplaySurface.h" @@ -101,10 +102,24 @@ EGLAPI const char* eglQueryStringImplementationANDROID(EGLDisplay dpy, EGLint na namespace android { - using namespace android::hardware::configstore; using namespace android::hardware::configstore::V1_0; +namespace { +class ConditionalLock { +public: + ConditionalLock(Mutex& mutex, bool lock) : mMutex(mutex), mLocked(lock) { + if (lock) { + mMutex.lock(); + } + } + ~ConditionalLock() { if (mLocked) mMutex.unlock(); } +private: + Mutex& mMutex; + bool mLocked; +}; +} // namespace anonymous + // --------------------------------------------------------------------------- const String16 sHardwareTest("android.permission.HARDWARE_TEST"); @@ -147,9 +162,6 @@ SurfaceFlinger::SurfaceFlinger() mLayersRemoved(false), mLayersAdded(false), mRepaintEverything(0), - mHwc(nullptr), - mRealHwc(nullptr), - mVrHwc(nullptr), mHwcServiceName(getHwcServiceName()), mRenderEngine(nullptr), mBootTime(systemTime()), @@ -177,7 +189,9 @@ SurfaceFlinger::SurfaceFlinger() mTotalTime(0), mLastSwapTime(0), mNumLayers(0), - mVrFlingerRequestsDisplay(false) + mVrFlingerRequestsDisplay(false), + mMainThreadId(std::this_thread::get_id()), + mComposerSequenceId(0) { ALOGI("SurfaceFlinger is starting"); @@ -583,48 +597,46 @@ void SurfaceFlinger::init() { ALOGI("Phase offset NS: %" PRId64 "", vsyncPhaseOffsetNs); - { // Autolock scope - Mutex::Autolock _l(mStateLock); + Mutex::Autolock _l(mStateLock); - // initialize EGL for the default display - mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); - eglInitialize(mEGLDisplay, NULL, NULL); - - // start the EventThread - sp vsyncSrc = new DispSyncSource(&mPrimaryDispSync, - vsyncPhaseOffsetNs, true, "app"); - mEventThread = new EventThread(vsyncSrc, *this, false); - sp sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync, - sfVsyncPhaseOffsetNs, true, "sf"); - mSFEventThread = new EventThread(sfVsyncSrc, *this, true); - mEventQueue.setEventThread(mSFEventThread); + // initialize EGL for the default display + mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + eglInitialize(mEGLDisplay, NULL, NULL); - // set EventThread and SFEventThread to SCHED_FIFO to minimize jitter - struct sched_param param = {0}; - param.sched_priority = 2; - if (sched_setscheduler(mSFEventThread->getTid(), SCHED_FIFO, ¶m) != 0) { - ALOGE("Couldn't set SCHED_FIFO for SFEventThread"); - } - if (sched_setscheduler(mEventThread->getTid(), SCHED_FIFO, ¶m) != 0) { - ALOGE("Couldn't set SCHED_FIFO for EventThread"); - } + // start the EventThread + sp vsyncSrc = new DispSyncSource(&mPrimaryDispSync, + vsyncPhaseOffsetNs, true, "app"); + mEventThread = new EventThread(vsyncSrc, *this, false); + sp sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync, + sfVsyncPhaseOffsetNs, true, "sf"); + mSFEventThread = new EventThread(sfVsyncSrc, *this, true); + mEventQueue.setEventThread(mSFEventThread); - // Get a RenderEngine for the given display / config (can't fail) - mRenderEngine = RenderEngine::create(mEGLDisplay, - HAL_PIXEL_FORMAT_RGBA_8888, - hasWideColorDisplay ? RenderEngine::WIDE_COLOR_SUPPORT : 0); + // set EventThread and SFEventThread to SCHED_FIFO to minimize jitter + struct sched_param param = {0}; + param.sched_priority = 2; + if (sched_setscheduler(mSFEventThread->getTid(), SCHED_FIFO, ¶m) != 0) { + ALOGE("Couldn't set SCHED_FIFO for SFEventThread"); + } + if (sched_setscheduler(mEventThread->getTid(), SCHED_FIFO, ¶m) != 0) { + ALOGE("Couldn't set SCHED_FIFO for EventThread"); } - // Drop the state lock while we initialize the hardware composer. We drop - // the lock because on creation, it will call back into SurfaceFlinger to - // initialize the primary display. - LOG_ALWAYS_FATAL_IF(mVrFlingerRequestsDisplay, - "Starting with vr flinger active is not currently supported."); - mRealHwc = new HWComposer(mHwcServiceName); - mHwc = mRealHwc; - mHwc->setEventHandler(static_cast(this)); + // Get a RenderEngine for the given display / config (can't fail) + mRenderEngine = RenderEngine::create(mEGLDisplay, + HAL_PIXEL_FORMAT_RGBA_8888, + hasWideColorDisplay ? RenderEngine::WIDE_COLOR_SUPPORT : 0); - Mutex::Autolock _l(mStateLock); + // retrieve the EGL context that was selected/created + mEGLContext = mRenderEngine->getEGLContext(); + + LOG_ALWAYS_FATAL_IF(mEGLContext == EGL_NO_CONTEXT, + "couldn't create EGLContext"); + + LOG_ALWAYS_FATAL_IF(mVrFlingerRequestsDisplay, + "Starting with vr flinger active is not currently supported."); + mHwc.reset(new HWComposer(mHwcServiceName)); + mHwc->registerCallback(this, mComposerSequenceId); if (useVrFlinger) { auto vrFlingerRequestDisplayCallback = [this] (bool requestDisplay) { @@ -639,16 +651,6 @@ void SurfaceFlinger::init() { } } - // retrieve the EGL context that was selected/created - mEGLContext = mRenderEngine->getEGLContext(); - - LOG_ALWAYS_FATAL_IF(mEGLContext == EGL_NO_CONTEXT, - "couldn't create EGLContext"); - - // make the GLContext current so that we can create textures when creating - // Layers (which may happens before we render something) - getDefaultDisplayDeviceLocked()->makeCurrent(mEGLDisplay, mEGLContext); - mEventControlThread = new EventControlThread(this); mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY); @@ -1218,11 +1220,16 @@ void SurfaceFlinger::resyncWithRateLimit() { sLastResyncAttempted = now; } -void SurfaceFlinger::onVSyncReceived(HWComposer* composer, int32_t type, - nsecs_t timestamp) { +void SurfaceFlinger::onVsyncReceived(int32_t sequenceId, + hwc2_display_t displayId, int64_t timestamp) { Mutex::Autolock lock(mStateLock); - // Ignore any vsyncs from the non-active hardware composer. - if (composer != mHwc) { + // Ignore any vsyncs from a previous hardware composer. + if (sequenceId != mComposerSequenceId) { + return; + } + + int32_t type; + if (!mHwc->onVsync(displayId, timestamp, &type)) { return; } @@ -1230,7 +1237,7 @@ void SurfaceFlinger::onVSyncReceived(HWComposer* composer, int32_t type, { // Scope for the lock Mutex::Autolock _l(mHWVsyncLock); - if (type == 0 && mPrimaryHWVsyncEnabled) { + if (type == DisplayDevice::DISPLAY_PRIMARY && mPrimaryHWVsyncEnabled) { needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp); } } @@ -1248,7 +1255,7 @@ void SurfaceFlinger::getCompositorTiming(CompositorTiming* compositorTiming) { } void SurfaceFlinger::createDefaultDisplayDevice() { - const int32_t type = DisplayDevice::DISPLAY_PRIMARY; + const DisplayDevice::DisplayType type = DisplayDevice::DISPLAY_PRIMARY; wp token = mBuiltinDisplays[type]; // All non-virtual displays are currently considered secure. @@ -1279,28 +1286,49 @@ void SurfaceFlinger::createDefaultDisplayDevice() { mDisplays.add(token, hw); setActiveColorModeInternal(hw, HAL_COLOR_MODE_NATIVE); hw->setCompositionDataSpace(HAL_DATASPACE_UNKNOWN); -} -void SurfaceFlinger::onHotplugReceived(HWComposer* composer, int32_t disp, bool connected) { - ALOGV("onHotplugReceived(%d, %s)", disp, connected ? "true" : "false"); + // Add the primary display token to mDrawingState so we don't try to + // recreate the DisplayDevice for the primary display. + mDrawingState.displays.add(token, DisplayDeviceState(type, true)); - if (composer->isUsingVrComposer()) { - // We handle initializing the primary display device for the VR - // window manager hwc explicitly at the time of transition. - if (disp != DisplayDevice::DISPLAY_PRIMARY) { - ALOGE("External displays are not supported by the vr hardware composer."); + // make the GLContext current so that we can create textures when creating + // Layers (which may happens before we render something) + hw->makeCurrent(mEGLDisplay, mEGLContext); +} + +void SurfaceFlinger::onHotplugReceived(int32_t sequenceId, + hwc2_display_t display, HWC2::Connection connection, + bool primaryDisplay) { + ALOGV("onHotplugReceived(%d, %" PRIu64 ", %s, %s)", + sequenceId, display, + connection == HWC2::Connection::Connected ? + "connected" : "disconnected", + primaryDisplay ? "primary" : "external"); + + // Only lock if we're not on the main thread. This function is normally + // called on a hwbinder thread, but for the primary display it's called on + // the main thread with the state lock already held, so don't attempt to + // acquire it here. + ConditionalLock lock(mStateLock, + std::this_thread::get_id() != mMainThreadId); + + if (primaryDisplay) { + mHwc->onHotplug(display, connection); + if (!mBuiltinDisplays[DisplayDevice::DISPLAY_PRIMARY].get()) { + createBuiltinDisplayLocked(DisplayDevice::DISPLAY_PRIMARY); } - return; - } - - if (disp == DisplayDevice::DISPLAY_PRIMARY) { - Mutex::Autolock lock(mStateLock); - createBuiltinDisplayLocked(DisplayDevice::DISPLAY_PRIMARY); createDefaultDisplayDevice(); } else { + if (sequenceId != mComposerSequenceId) { + return; + } + if (mHwc->isUsingVrComposer()) { + ALOGE("External displays are not supported by the vr hardware composer."); + return; + } + mHwc->onHotplug(display, connection); auto type = DisplayDevice::DISPLAY_EXTERNAL; - Mutex::Autolock _l(mStateLock); - if (connected) { + if (connection == HWC2::Connection::Connected) { createBuiltinDisplayLocked(type); } else { mCurrentState.displays.removeItem(mBuiltinDisplays[type]); @@ -1312,46 +1340,31 @@ void SurfaceFlinger::onHotplugReceived(HWComposer* composer, int32_t disp, bool } } -void SurfaceFlinger::onInvalidateReceived(HWComposer* composer) { +void SurfaceFlinger::onRefreshReceived(int sequenceId, + hwc2_display_t /*display*/) { Mutex::Autolock lock(mStateLock); - if (composer == mHwc) { - repaintEverything(); - } else { - // This isn't from our current hardware composer. If it's a callback - // from the real composer, forward the refresh request to vr - // flinger. Otherwise ignore it. - if (!composer->isUsingVrComposer()) { - mVrFlinger->OnHardwareComposerRefresh(); - } + if (sequenceId != mComposerSequenceId) { + return; } + repaintEverything(); } void SurfaceFlinger::setVsyncEnabled(int disp, int enabled) { ATRACE_CALL(); + Mutex::Autolock lock(mStateLock); getHwComposer().setVsyncEnabled(disp, enabled ? HWC2::Vsync::Enable : HWC2::Vsync::Disable); } // Note: it is assumed the caller holds |mStateLock| when this is called -void SurfaceFlinger::resetHwcLocked() { +void SurfaceFlinger::resetDisplayState() { disableHardwareVsync(true); - clearHwcLayers(mDrawingState.layersSortedByZ); - clearHwcLayers(mCurrentState.layersSortedByZ); - for (size_t disp = 0; disp < mDisplays.size(); ++disp) { - clearHwcLayers(mDisplays[disp]->getVisibleLayersSortedByZ()); - } // Clear the drawing state so that the logic inside of // handleTransactionLocked will fire. It will determine the delta between // mCurrentState and mDrawingState and re-apply all changes when we make the // transition. mDrawingState.displays.clear(); - // Release virtual display hwcId during vr mode transition. - for (size_t displayId = 0; displayId < mDisplays.size(); ++displayId) { - const sp& displayDevice = mDisplays[displayId]; - if (displayDevice->getDisplayType() == DisplayDevice::DISPLAY_VIRTUAL) { - displayDevice->disconnect(getHwComposer()); - } - } + eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); mDisplays.clear(); } @@ -1363,57 +1376,54 @@ void SurfaceFlinger::updateVrFlinger() { return; } - if (vrFlingerRequestsDisplay && !mVrHwc) { - // Construct new HWComposer without holding any locks. - mVrHwc = new HWComposer("vr"); - - // Set up the event handlers. This step is neccessary to initialize the internal state of - // the hardware composer object properly. Our callbacks are designed such that if they are - // triggered between now and the point where the display is properly re-initialized, they - // will not have any effect, so this is safe to do here, before the lock is aquired. - mVrHwc->setEventHandler(static_cast(this)); - ALOGV("Vr HWC created"); + if (vrFlingerRequestsDisplay && !mHwc->getComposer()->isRemote()) { + ALOGE("Vr flinger is only supported for remote hardware composer" + " service connections. Ignoring request to transition to vr" + " flinger."); + mVrFlingerRequestsDisplay = false; + return; } Mutex::Autolock _l(mStateLock); - if (vrFlingerRequestsDisplay) { - resetHwcLocked(); - - mHwc = mVrHwc; - mVrFlinger->GrantDisplayOwnership(); + int currentDisplayPowerMode = getDisplayDeviceLocked( + mBuiltinDisplays[DisplayDevice::DISPLAY_PRIMARY])->getPowerMode(); - } else { + if (!vrFlingerRequestsDisplay) { mVrFlinger->SeizeDisplayOwnership(); + } - resetHwcLocked(); + resetDisplayState(); + mHwc.reset(); // Delete the current instance before creating the new one + mHwc.reset(new HWComposer( + vrFlingerRequestsDisplay ? "vr" : mHwcServiceName)); + mHwc->registerCallback(this, ++mComposerSequenceId); - mHwc = mRealHwc; + LOG_ALWAYS_FATAL_IF(!mHwc->getComposer()->isRemote(), + "Switched to non-remote hardware composer"); + + if (vrFlingerRequestsDisplay) { + mVrFlinger->GrantDisplayOwnership(); + } else { enableHardwareVsync(); } mVisibleRegionsDirty = true; invalidateHwcGeometry(); - // Explicitly re-initialize the primary display. This is because some other - // parts of this class rely on the primary display always being available. - createDefaultDisplayDevice(); - // Re-enable default display. - sp requestMessage = new LambdaMessage([&]() { - sp hw(getDisplayDevice(mBuiltinDisplays[DisplayDevice::DISPLAY_PRIMARY])); - setPowerModeInternal(hw, HWC_POWER_MODE_NORMAL); + sp hw(getDisplayDeviceLocked( + mBuiltinDisplays[DisplayDevice::DISPLAY_PRIMARY])); + setPowerModeInternal(hw, currentDisplayPowerMode, /*stateLockHeld*/ true); - // Reset the timing values to account for the period of the swapped in HWC - const auto& activeConfig = mHwc->getActiveConfig(HWC_DISPLAY_PRIMARY); - const nsecs_t period = activeConfig->getVsyncPeriod(); - mAnimFrameTracker.setDisplayRefreshPeriod(period); + // Reset the timing values to account for the period of the swapped in HWC + const auto& activeConfig = mHwc->getActiveConfig(HWC_DISPLAY_PRIMARY); + const nsecs_t period = activeConfig->getVsyncPeriod(); + mAnimFrameTracker.setDisplayRefreshPeriod(period); - // Use phase of 0 since phase is not known. - // Use latency of 0, which will snap to the ideal latency. - setCompositorTimingSnapped(0, period, 0); - }); - postMessageAsync(requestMessage); + // Use phase of 0 since phase is not known. + // Use latency of 0, which will snap to the ideal latency. + setCompositorTimingSnapped(0, period, 0); android_atomic_or(1, &mRepaintEverything); setTransactionFlags(eDisplayTransactionNeeded); @@ -1749,15 +1759,14 @@ void SurfaceFlinger::rebuildLayerStacks() { } else { // Clear out the HWC layer if this layer was // previously visible, but no longer is - layer->setHwcLayer(displayDevice->getHwcDisplayId(), - nullptr); + layer->destroyHwcLayer( + displayDevice->getHwcDisplayId()); } } else { // WM changes displayDevice->layerStack upon sleep/awake. // Here we make sure we delete the HWC layers even if // WM changed their layer stack. - layer->setHwcLayer(displayDevice->getHwcDisplayId(), - nullptr); + layer->destroyHwcLayer(displayDevice->getHwcDisplayId()); } }); } @@ -1872,10 +1881,7 @@ void SurfaceFlinger::setUpHWComposer() { for (size_t i = 0; i < currentLayers.size(); i++) { const auto& layer = currentLayers[i]; if (!layer->hasHwcLayer(hwcId)) { - auto hwcLayer = mHwc->createLayer(hwcId); - if (hwcLayer) { - layer->setHwcLayer(hwcId, std::move(hwcLayer)); - } else { + if (!layer->createHwcLayer(mHwc.get(), hwcId)) { layer->forceClientComposition(hwcId); continue; } @@ -2161,7 +2167,7 @@ void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags) if (state.surface != NULL) { // Allow VR composer to use virtual displays. - if (mUseHwcVirtualDisplays || mHwc == mVrHwc) { + if (mUseHwcVirtualDisplays || mHwc->isUsingVrComposer()) { int width = 0; int status = state.surface->query( NATIVE_WINDOW_WIDTH, &width); @@ -3285,7 +3291,8 @@ void SurfaceFlinger::onInitializeDisplays() { d.height = 0; displays.add(d); setTransactionState(state, displays, 0); - setPowerModeInternal(getDisplayDevice(d.token), HWC_POWER_MODE_NORMAL); + setPowerModeInternal(getDisplayDevice(d.token), HWC_POWER_MODE_NORMAL, + /*stateLockHeld*/ false); const auto& activeConfig = mHwc->getActiveConfig(HWC_DISPLAY_PRIMARY); const nsecs_t period = activeConfig->getVsyncPeriod(); @@ -3311,7 +3318,7 @@ void SurfaceFlinger::initializeDisplays() { } void SurfaceFlinger::setPowerModeInternal(const sp& hw, - int mode) { + int mode, bool stateLockHeld) { ALOGD("Set power mode=%d, type=%d flinger=%p", mode, hw->getDisplayType(), this); int32_t type = hw->getDisplayType(); @@ -3328,7 +3335,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& hw, } if (mInterceptor.isEnabled()) { - Mutex::Autolock _l(mStateLock); + ConditionalLock lock(mStateLock, !stateLockHeld); ssize_t idx = mCurrentState.displays.indexOfKey(hw->getDisplayToken()); if (idx < 0) { ALOGW("Surface Interceptor SavePowerMode: invalid display token"); @@ -3414,7 +3421,8 @@ void SurfaceFlinger::setPowerMode(const sp& display, int mode) { ALOGW("Attempt to set power mode = %d for virtual display", mMode); } else { - mFlinger.setPowerModeInternal(hw, mMode); + mFlinger.setPowerModeInternal( + hw, mMode, /*stateLockHeld*/ false); } return true; } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 5123b58913..058f4a1d3b 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -60,13 +60,20 @@ #include "SurfaceInterceptor.h" #include "StartPropertySetThread.h" +#ifdef USE_HWC2 +#include "DisplayHardware/HWC2.h" #include "DisplayHardware/HWComposer.h" +#else +#include "DisplayHardware/HWComposer_hwc1.h" +#endif + #include "Effects/Daltonizer.h" #include #include #include #include +#include #include namespace android { @@ -99,7 +106,11 @@ enum { class SurfaceFlinger : public BnSurfaceComposer, private IBinder::DeathRecipient, +#ifdef USE_HWC2 + private HWC2::ComposerCallback +#else private HWComposer::EventHandler +#endif { public: @@ -314,11 +325,20 @@ private: virtual void onFirstRef(); /* ------------------------------------------------------------------------ - * HWComposer::EventHandler interface + * HWC2::ComposerCallback / HWComposer::EventHandler interface */ - virtual void onVSyncReceived(HWComposer* composer, int type, nsecs_t timestamp); - virtual void onHotplugReceived(HWComposer* composer, int disp, bool connected); - virtual void onInvalidateReceived(HWComposer* composer); +#ifdef USE_HWC2 + void onVsyncReceived(int32_t sequenceId, hwc2_display_t display, + int64_t timestamp) override; + void onHotplugReceived(int32_t sequenceId, hwc2_display_t display, + HWC2::Connection connection, + bool primaryDisplay) override; + void onRefreshReceived(int32_t sequenceId, hwc2_display_t display) override; +#else + void onVSyncReceived(HWComposer* composer, int type, nsecs_t timestamp) override; + void onHotplugReceived(HWComposer* composer, int disp, bool connected) override; + void onInvalidateReceived(HWComposer* composer) override; +#endif /* ------------------------------------------------------------------------ * Message handling @@ -333,7 +353,12 @@ private: // called on the main thread in response to setActiveConfig() void setActiveConfigInternal(const sp& hw, int mode); // called on the main thread in response to setPowerMode() +#ifdef USE_HWC2 + void setPowerModeInternal(const sp& hw, int mode, + bool stateLockHeld); +#else void setPowerModeInternal(const sp& hw, int mode); +#endif // Called on the main thread in response to setActiveColorMode() void setActiveColorModeInternal(const sp& hw, android_color_mode_t colorMode); @@ -591,13 +616,7 @@ private: /* ------------------------------------------------------------------------ * VrFlinger */ - template - void clearHwcLayers(const T& layers) { - for (size_t i = 0; i < layers.size(); ++i) { - layers[i]->clearHwcLayers(); - } - } - void resetHwcLocked(); + void resetDisplayState(); // Check to see if we should handoff to vr flinger. void updateVrFlinger(); @@ -624,13 +643,14 @@ private: // access must be protected by mInvalidateLock volatile int32_t mRepaintEverything; - // current, real and vr hardware composers. - HWComposer* mHwc; + // The current hardware composer interface. When switching into and out of + // vr, our HWComposer instance will be recreated. + std::unique_ptr mHwc; + #ifdef USE_HWC2 - HWComposer* mRealHwc; - HWComposer* mVrHwc; const std::string mHwcServiceName; // "default" for real use, something else for testing. #endif + // constant members (no synchronization needed for access) RenderEngine* mRenderEngine; nsecs_t mBootTime; @@ -644,10 +664,6 @@ private: EGLDisplay mEGLDisplay; sp mBuiltinDisplays[DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES]; -#ifdef USE_HWC2 - std::unique_ptr mVrFlinger; -#endif - // Can only accessed from the main thread, these members // don't need synchronization State mDrawingState{LayerVector::StateSet::Drawing}; @@ -769,8 +785,14 @@ private: status_t CheckTransactCodeCredentials(uint32_t code); #ifdef USE_HWC2 + std::unique_ptr mVrFlinger; std::atomic mVrFlingerRequestsDisplay; static bool useVrFlinger; + std::thread::id mMainThreadId; + // The composer sequence id is a monotonically increasing integer that we + // use to differentiate callbacks from different hardware composer + // instances. Each hardware composer instance gets a different sequence id. + int32_t mComposerSequenceId; #endif float mSaturation = 1.0f; diff --git a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp index 7aaa42aa4b..78a04e08e1 100644 --- a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp +++ b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp @@ -535,8 +535,8 @@ void SurfaceFlinger::init() { // Initialize the H/W composer object. There may or may not be an // actual hardware composer underneath. - mHwc = new HWComposer(this, - *static_cast(this)); + mHwc.reset(new HWComposer(this, + *static_cast(this))); // get a RenderEngine for the given display / config (can't fail) mRenderEngine = RenderEngine::create(mEGLDisplay, -- GitLab From fe98f50b02746f4a1980d0fd31fd8126c5d4e445 Mon Sep 17 00:00:00 2001 From: Courtney Goeltzenleuchter Date: Thu, 27 Jul 2017 08:11:24 -0600 Subject: [PATCH 011/704] Add useful helper functions for tests Trying to reduce code duplication by putting common EGL functions used by opengl and surfaceflinger tests here. Test: adb shell /data/nativetest/test-opengl-gl2_basic/test-opengl-gl2_basic Change-Id: Id7a32aedc565f3fbe56fa62ceb79cbb1e8c80727 --- opengl/tests/lib/include/EGLUtils.h | 178 +++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 6 deletions(-) diff --git a/opengl/tests/lib/include/EGLUtils.h b/opengl/tests/lib/include/EGLUtils.h index 014c2611ae..29f4fe442d 100644 --- a/opengl/tests/lib/include/EGLUtils.h +++ b/opengl/tests/lib/include/EGLUtils.h @@ -20,11 +20,16 @@ #include #include +#include +#include +#include +#include #include #include -#include +#include +EGLAPI const char* eglQueryStringImplementationANDROID(EGLDisplay dpy, EGLint name); // ---------------------------------------------------------------------------- namespace android { @@ -47,6 +52,17 @@ public: EGLint const* attrs, EGLNativeWindowType window, EGLConfig* outConfig); + + static inline String8 printGLString(const char* name, GLenum s); + static inline String8 printEGLString(EGLDisplay dpy, const char* name, GLenum s); + static inline String8 checkEglError(const char* op, EGLBoolean returnVal); + static inline String8 checkGlError(const char* op); + static inline String8 printEGLConfiguration(EGLDisplay dpy, EGLConfig config); + static inline bool printEGLConfigurations(EGLDisplay dpy, String8& msg); + static inline bool printEGLConfigurations(FILE* output, EGLDisplay dpy); + static inline String8 decodeColorSpace(EGLint colorSpace); + static inline bool hasEglExtension(EGLDisplay dpy, const char* name); + static inline bool hasExtension(const char* exts, const char* name); }; // ---------------------------------------------------------------------------- @@ -91,9 +107,8 @@ status_t EGLUtils::selectConfigForPixelFormat( if (eglGetConfigs(dpy, NULL, 0, &numConfigs) == EGL_FALSE) return BAD_VALUE; - EGLConfig* const configs = (EGLConfig*)malloc(sizeof(EGLConfig)*numConfigs); - if (eglChooseConfig(dpy, attrs, configs, numConfigs, &n) == EGL_FALSE) { - free(configs); + std::vector configs(numConfigs); + if (eglChooseConfig(dpy, attrs, configs.data(), numConfigs, &n) == EGL_FALSE) { return BAD_VALUE; } @@ -108,8 +123,6 @@ status_t EGLUtils::selectConfigForPixelFormat( } } - free(configs); - if (i(glGetString(s)); + msg.appendFormat("GL %s = %s\n", name, v); + return msg; +} + +String8 EGLUtils::printEGLString(EGLDisplay dpy, const char* name, GLenum s) { + String8 msg; + const char* v = static_cast(eglQueryString(dpy, s)); + msg.appendFormat("GL %s = %s\n", name, v); + const char* va = (const char*)eglQueryStringImplementationANDROID(dpy, s); + msg.appendFormat("ImplementationANDROID: %s = %s\n", name, va); + return msg; +} + +String8 EGLUtils::checkEglError(const char* op, EGLBoolean returnVal = EGL_TRUE) { + String8 msg; + if (returnVal != EGL_TRUE) { + msg.appendFormat("%s() returned %d\n", op, returnVal); + } + + for (EGLint error = eglGetError(); error != EGL_SUCCESS; error = eglGetError()) { + msg.appendFormat("after %s() eglError %s (0x%x)\n", op, EGLUtils::strerror(error), error); + } + return msg; +} + +String8 EGLUtils::checkGlError(const char* op) { + String8 msg; + for (GLint error = glGetError(); error != GL_NO_ERROR; error = glGetError()) { + msg.appendFormat("after %s() glError (0x%x)\n", op, error); + } + return msg; +} + +String8 EGLUtils::printEGLConfiguration(EGLDisplay dpy, EGLConfig config) { +#define X(VAL) \ + { VAL, #VAL } + struct { + EGLint attribute; + const char* name; + } names[] = { + X(EGL_BUFFER_SIZE), + X(EGL_ALPHA_SIZE), + X(EGL_BLUE_SIZE), + X(EGL_GREEN_SIZE), + X(EGL_RED_SIZE), + X(EGL_DEPTH_SIZE), + X(EGL_STENCIL_SIZE), + X(EGL_CONFIG_CAVEAT), + X(EGL_CONFIG_ID), + X(EGL_LEVEL), + X(EGL_MAX_PBUFFER_HEIGHT), + X(EGL_MAX_PBUFFER_PIXELS), + X(EGL_MAX_PBUFFER_WIDTH), + X(EGL_NATIVE_RENDERABLE), + X(EGL_NATIVE_VISUAL_ID), + X(EGL_NATIVE_VISUAL_TYPE), + X(EGL_SAMPLES), + X(EGL_SAMPLE_BUFFERS), + X(EGL_SURFACE_TYPE), + X(EGL_TRANSPARENT_TYPE), + X(EGL_TRANSPARENT_RED_VALUE), + X(EGL_TRANSPARENT_GREEN_VALUE), + X(EGL_TRANSPARENT_BLUE_VALUE), + X(EGL_BIND_TO_TEXTURE_RGB), + X(EGL_BIND_TO_TEXTURE_RGBA), + X(EGL_MIN_SWAP_INTERVAL), + X(EGL_MAX_SWAP_INTERVAL), + X(EGL_LUMINANCE_SIZE), + X(EGL_ALPHA_MASK_SIZE), + X(EGL_COLOR_BUFFER_TYPE), + X(EGL_RENDERABLE_TYPE), + X(EGL_CONFORMANT), + }; +#undef X + + String8 msg; + for (size_t j = 0; j < sizeof(names) / sizeof(names[0]); j++) { + EGLint value = -1; + EGLint returnVal = eglGetConfigAttrib(dpy, config, names[j].attribute, &value); + EGLint error = eglGetError(); + if (returnVal && error == EGL_SUCCESS) { + msg.appendFormat(" %s: %d (0x%x)", names[j].name, value, value); + } + } + msg.append("\n"); + return msg; +} + +bool EGLUtils::printEGLConfigurations(EGLDisplay dpy, String8& msg) { + EGLint numConfig = 0; + EGLint returnVal = eglGetConfigs(dpy, NULL, 0, &numConfig); + msg.append(checkEglError("eglGetConfigs", returnVal)); + if (!returnVal) { + return false; + } + + msg.appendFormat("Number of EGL configuration: %d\n", numConfig); + + std::vector configs(numConfig); + + returnVal = eglGetConfigs(dpy, configs.data(), numConfig, &numConfig); + msg.append(checkEglError("eglGetConfigs", returnVal)); + if (!returnVal) { + return false; + } + + for (int i = 0; i < numConfig; i++) { + msg.appendFormat("Configuration %d\n", i); + msg.append(printEGLConfiguration(dpy, configs[i])); + } + + return true; +} + +bool EGLUtils::printEGLConfigurations(FILE* output, EGLDisplay dpy) { + String8 msg; + bool status = printEGLConfigurations(dpy, msg); + fprintf(output, "%s", msg.c_str()); + return status; +} + +String8 EGLUtils::decodeColorSpace(EGLint colorSpace) { + switch (colorSpace) { + case EGL_GL_COLORSPACE_SRGB_KHR: + return String8("EGL_GL_COLORSPACE_SRGB_KHR"); + case EGL_GL_COLORSPACE_DISPLAY_P3_EXT: + return String8("EGL_GL_COLORSPACE_DISPLAY_P3_EXT"); + case EGL_GL_COLORSPACE_LINEAR_KHR: + return String8("EGL_GL_COLORSPACE_LINEAR_KHR"); + default: + return String8::format("UNKNOWN ColorSpace %d", colorSpace); + } +} + +bool EGLUtils::hasExtension(const char* exts, const char* name) { + size_t nameLen = strlen(name); + if (exts) { + for (const char* match = strstr(exts, name); match; match = strstr(match + nameLen, name)) { + if (match[nameLen] == '\0' || match[nameLen] == ' ') { + return true; + } + } + } + return false; +} + +bool EGLUtils::hasEglExtension(EGLDisplay dpy, const char* name) { + return hasExtension(eglQueryString(dpy, EGL_EXTENSIONS), name); +} + // ---------------------------------------------------------------------------- }; // namespace android // ---------------------------------------------------------------------------- -- GitLab From dc7efea69c7d6a25a050e1c60171b5a1660e1f1c Mon Sep 17 00:00:00 2001 From: John Reck Date: Mon, 7 Aug 2017 11:18:40 -0700 Subject: [PATCH 012/704] Add ASharedMemory_dupFromJava NDK API Bug: 64394076 Test: SharedMemory CTS tests Change-Id: I0ee5badff48ce72c8a664180beacfcf89112f6bf (cherry picked from commit 9f5599b436b80366ecbea274914fd057fa3dcb91) --- include/android/sharedmem_jni.h | 82 +++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 include/android/sharedmem_jni.h diff --git a/include/android/sharedmem_jni.h b/include/android/sharedmem_jni.h new file mode 100644 index 0000000000..38980f3ccc --- /dev/null +++ b/include/android/sharedmem_jni.h @@ -0,0 +1,82 @@ +/* + * 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. + */ + +/** + * @addtogroup Memory + * @{ + */ + +/** + * @file sharedmem_jni.h + */ + +#ifndef ANDROID_SHARED_MEMORY_JNI_H +#define ANDROID_SHARED_MEMORY_JNI_H + +#include +#include +#include + +/****************************************************************** + * + * IMPORTANT NOTICE: + * + * This file is part of Android's set of stable system headers + * exposed by the Android NDK (Native Development Kit). + * + * Third-party source AND binary code relies on the definitions + * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES. + * + * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES) + * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS + * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY + * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES + */ + +/** + * Structures and functions for a shared memory buffer that can be shared across process. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#if __ANDROID_API__ >= __ANDROID_API_O_MR1__ + +/** + * Returns a dup'd FD from the given Java android.os.SharedMemory object. The returned file + * descriptor has all the same properties & capabilities as the FD returned from + * ASharedMemory_create(), however the protection flags will be the same as those of the + * android.os.SharedMemory object. + * + * Use close() to release the shared memory region. + * + * \param env The JNIEnv* pointer + * \param sharedMemory The Java android.os.SharedMemory object + * \return file descriptor that denotes the shared memory; -1 if the shared memory object is + * already closed or if the JNIEnv or jobject is NULL. + */ +int ASharedMemory_dupFromJava(JNIEnv* env, jobject sharedMemory); + +#endif + +#ifdef __cplusplus +}; +#endif + +#endif // ANDROID_SHARED_MEMORY_JNI_H + +/** @} */ -- GitLab From 588baa0c42f7f72316cb57beeea21d271d6ce848 Mon Sep 17 00:00:00 2001 From: Peng Xu Date: Thu, 10 Aug 2017 17:39:55 -0700 Subject: [PATCH 013/704] Adding OWNERS files for services/sensorservice, libs/sensor Test: n/a Change-Id: I696adec7831f95f22fc91988e644e55392c60b46 --- libs/sensor/OWNERS | 2 ++ services/sensorservice/OWNERS | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 libs/sensor/OWNERS create mode 100644 services/sensorservice/OWNERS diff --git a/libs/sensor/OWNERS b/libs/sensor/OWNERS new file mode 100644 index 0000000000..6a38a1ff14 --- /dev/null +++ b/libs/sensor/OWNERS @@ -0,0 +1,2 @@ +ashutoshj@google.com +pengxu@google.com diff --git a/services/sensorservice/OWNERS b/services/sensorservice/OWNERS new file mode 100644 index 0000000000..6a38a1ff14 --- /dev/null +++ b/services/sensorservice/OWNERS @@ -0,0 +1,2 @@ +ashutoshj@google.com +pengxu@google.com -- GitLab From 042baecf01205183376c55f734d7623d4ff96e21 Mon Sep 17 00:00:00 2001 From: Peng Xu Date: Wed, 9 Aug 2017 19:28:27 -0700 Subject: [PATCH 014/704] Checking exisitence before calling editValueFor in SensorDevice Bug: 26320541 Test: compiles, test a few sensor and they all works Change-Id: If0859e7560419f3955f29ae108f9268d0a2bbaa9 --- services/sensorservice/SensorDevice.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp index 7d9b0b730a..da3b2758d1 100644 --- a/services/sensorservice/SensorDevice.cpp +++ b/services/sensorservice/SensorDevice.cpp @@ -223,8 +223,13 @@ ssize_t SensorDevice::poll(sensors_event_t* buffer, size_t count) { } void SensorDevice::autoDisable(void *ident, int handle) { - Info& info( mActivationCount.editValueFor(handle) ); Mutex::Autolock _l(mLock); + ssize_t activationIndex = mActivationCount.indexOfKey(handle); + if (activationIndex < 0) { + ALOGW("Handle %d cannot be found in activation record", handle); + return; + } + Info& info(mActivationCount.editValueAt(activationIndex)); info.removeBatchParamsForIdent(ident); } @@ -235,7 +240,12 @@ status_t SensorDevice::activate(void* ident, int handle, int enabled) { bool actuateHardware = false; Mutex::Autolock _l(mLock); - Info& info( mActivationCount.editValueFor(handle) ); + ssize_t activationIndex = mActivationCount.indexOfKey(handle); + if (activationIndex < 0) { + ALOGW("Handle %d cannot be found in activation record", handle); + return BAD_VALUE; + } + Info& info(mActivationCount.editValueAt(activationIndex)); ALOGD_IF(DEBUG_CONNECTIONS, "SensorDevice::activate: ident=%p, handle=0x%08x, enabled=%d, count=%zu", @@ -329,7 +339,12 @@ status_t SensorDevice::batch( ident, handle, flags, samplingPeriodNs, maxBatchReportLatencyNs); Mutex::Autolock _l(mLock); - Info& info(mActivationCount.editValueFor(handle)); + ssize_t activationIndex = mActivationCount.indexOfKey(handle); + if (activationIndex < 0) { + ALOGW("Handle %d cannot be found in activation record", handle); + return BAD_VALUE; + } + Info& info(mActivationCount.editValueAt(activationIndex)); if (info.batchParams.indexOfKey(ident) < 0) { BatchParams params(samplingPeriodNs, maxBatchReportLatencyNs); -- GitLab From 4cd4009e811d3117d642607fc70485f26ce2bb63 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 14 Aug 2017 16:54:06 -0700 Subject: [PATCH 015/704] Switch back to lsq2 VelocityTracker strategy. The impulse VelocityTracker strategy is over-estimating certain injected input events, used for SystemUI jank performance tests. The current regression is caused by a combination of improperly spaced input events and the high sensitivity of the new strategy to that portion of the motion. Bug: 64680775 Test: bit CtsViewTestCases:.VelocityTrackerTest Change-Id: I4b387dcc4a13fe4295ee208490c49b2763a6bd19 --- libs/input/VelocityTracker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index cc74b9b5cf..a914408297 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -105,7 +105,7 @@ static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool r // this is the strategy that applications will actually use. Be very careful // when adjusting the default strategy because it can dramatically affect // (often in a bad way) the user experience. -const char* VelocityTracker::DEFAULT_STRATEGY = "impulse"; +const char* VelocityTracker::DEFAULT_STRATEGY = "lsq2"; VelocityTracker::VelocityTracker(const char* strategy) : mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) { -- GitLab From 152279d2725cd59369bcdf70f9622b011cae5a14 Mon Sep 17 00:00:00 2001 From: Courtney Goeltzenleuchter Date: Mon, 14 Aug 2017 18:18:30 -0600 Subject: [PATCH 016/704] Add method to get surface dataspace Add ability to query the dataspace of a FramebufferSurface so that we can include that in the DisplayDevice dumpsys data. Test: adb shell dumpsys SurfaceFlinger Look for DisplayDevice section for dataspace. Bug: 63146977 Change-Id: I1d30bd48782485a422db7a0a0af1e585bbffd508 --- libs/gui/Surface.cpp | 6 ++++++ libs/gui/include/gui/Surface.h | 2 ++ services/surfaceflinger/DisplayDevice.cpp | 11 ++++++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 5b1c599a13..6c9d88b599 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -1509,6 +1509,12 @@ int Surface::setBuffersDataSpace(android_dataspace dataSpace) return NO_ERROR; } +android_dataspace_t Surface::getBuffersDataSpace() { + ALOGV("Surface::getBuffersDataSpace"); + Mutex::Autolock lock(mMutex); + return mDataSpace; +} + void Surface::freeAllBuffers() { for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { mSlots[i].buffer = 0; diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h index 55dd6bf067..3fe29d955b 100644 --- a/libs/gui/include/gui/Surface.h +++ b/libs/gui/include/gui/Surface.h @@ -281,6 +281,8 @@ public: // detachNextBuffer, or attachBuffer call. status_t getAndFlushRemovedBuffers(std::vector>* out); + android_dataspace_t getBuffersDataSpace(); + protected: enum { NUM_BUFFER_SLOTS = BufferQueueDefs::NUM_BUFFER_SLOTS }; enum { DEFAULT_FORMAT = PIXEL_FORMAT_RGBA_8888 }; diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 248ef53f55..0244c1b862 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -617,6 +618,7 @@ uint32_t DisplayDevice::getPrimaryDisplayOrientationTransform() { void DisplayDevice::dump(String8& result) const { const Transform& tr(mGlobalTransform); + ANativeWindow* const window = mNativeWindow.get(); EGLint redSize, greenSize, blueSize, alphaSize; eglGetConfigAttrib(mDisplay, mConfig, EGL_RED_SIZE, &redSize); eglGetConfigAttrib(mDisplay, mConfig, EGL_GREEN_SIZE, &greenSize); @@ -626,9 +628,9 @@ void DisplayDevice::dump(String8& result) const { result.appendFormat(" type=%x, hwcId=%d, layerStack=%u, (%4dx%4d), ANativeWindow=%p " "(%d:%d:%d:%d), orient=%2d (type=%08x), " "flips=%u, isSecure=%d, powerMode=%d, activeConfig=%d, numLayers=%zu\n", - mType, mHwcDisplayId, mLayerStack, mDisplayWidth, mDisplayHeight, - mNativeWindow.get(), redSize, greenSize, blueSize, alphaSize, mOrientation, - tr.getType(), getPageFlipCount(), mIsSecure, mPowerMode, mActiveConfig, + mType, mHwcDisplayId, mLayerStack, mDisplayWidth, mDisplayHeight, window, + redSize, greenSize, blueSize, alphaSize, mOrientation, tr.getType(), + getPageFlipCount(), mIsSecure, mPowerMode, mActiveConfig, mVisibleLayersSortedByZ.size()); result.appendFormat(" v:[%d,%d,%d,%d], f:[%d,%d,%d,%d], s:[%d,%d,%d,%d]," "transform:[[%0.3f,%0.3f,%0.3f][%0.3f,%0.3f,%0.3f][%0.3f,%0.3f,%0.3f]]\n", @@ -636,6 +638,9 @@ void DisplayDevice::dump(String8& result) const { mFrame.left, mFrame.top, mFrame.right, mFrame.bottom, mScissor.left, mScissor.top, mScissor.right, mScissor.bottom, tr[0][0], tr[1][0], tr[2][0], tr[0][1], tr[1][1], tr[2][1], tr[0][2], tr[1][2], tr[2][2]); + auto const surface = static_cast(window); + android_dataspace dataspace = surface->getBuffersDataSpace(); + result.appendFormat(" dataspace: %s (%d)\n", dataspaceDetails(dataspace).c_str(), dataspace); String8 surfaceDump; mDisplaySurface->dumpAsString(surfaceDump); -- GitLab From d438af0a09866cdeeb9f4c0f17543a23be513998 Mon Sep 17 00:00:00 2001 From: Arthur Eubanks Date: Wed, 16 Aug 2017 17:12:33 -0700 Subject: [PATCH 017/704] Change log libraries in tests to be statically linked 32-bit versions of the log libraries aren't flashed on the phone, so we need to statically link them. Test: build and run the 3 installd tests (32-bit and 64-bit) Test: on walleye/bullhead Change-Id: I06bea24c6c52ffdb34045449e0b40eb2ac146410 --- cmds/installd/tests/Android.bp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cmds/installd/tests/Android.bp b/cmds/installd/tests/Android.bp index 630c1f3652..443dce6827 100644 --- a/cmds/installd/tests/Android.bp +++ b/cmds/installd/tests/Android.bp @@ -5,13 +5,13 @@ cc_test { srcs: ["installd_utils_test.cpp"], shared_libs: [ "libbase", - "liblog", "libutils", "libcutils", ], static_libs: [ - "libinstalld", "libdiskusage", + "libinstalld", + "liblog", ], } @@ -23,14 +23,14 @@ cc_test { "libbase", "libbinder", "libcutils", - "liblog", - "liblogwrap", "libselinux", "libutils", ], static_libs: [ - "libinstalld", "libdiskusage", + "libinstalld", + "liblog", + "liblogwrap", ], } @@ -42,13 +42,13 @@ cc_test { "libbase", "libbinder", "libcutils", - "liblog", - "liblogwrap", "libselinux", "libutils", ], static_libs: [ - "libinstalld", "libdiskusage", + "libinstalld", + "liblog", + "liblogwrap", ], } -- GitLab From 1d8d412b5d8c294c7a141bd62141c9e1750dcf73 Mon Sep 17 00:00:00 2001 From: David Hanna Jr Date: Tue, 15 Aug 2017 19:53:57 -0700 Subject: [PATCH 018/704] test-hwc2: Corrected rotation to be clock-wise Test: Ran on (heavily modified ryu) Bug: 64723426 Change-Id: I203fb313b6b2c865f6d9a0920a2ffd3714be5e96 --- services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp index 1d3a1d38ac..6873c455e9 100644 --- a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp +++ b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp @@ -570,8 +570,8 @@ int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle, * (100x50) at the end of the transformation. */ if (transform & HWC_TRANSFORM_ROT_90) { float tmp = xPos; - xPos = -yPos * dfW / dfH; - yPos = tmp * dfH / dfW; + xPos = yPos * dfW / dfH; + yPos = -tmp * dfH / dfW; } /* Change origin back to the top left corner of the -- GitLab From 7b097e2e3d9dd9444916ddf77d75ca394e6b753e Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Tue, 8 Aug 2017 16:31:37 -0700 Subject: [PATCH 019/704] Merge fences when needed for accurate timestamps. There's an optimization in ConsumerBase that checks the status of the current fence before merging it with a new fence. If the current fence has already signaled, then it just picks up the new fence without merging. Unfortunately, if the new fence is already signaled too, then it's possible that it signaled long before the current fence, which can result in an inaccurate timestamp with the current logic. The new logic merges the fences when the statuses of the current and new fences are the same. If they differ, then it takes the unsignaled fence. This fixes the reads done timestamps in the GetFrameTimestamps dEQP tests so that they are always monotonic and always arrive after rendering completes. Test: --deqp-case=dEQP-EGL*get_frame_timestamps* Bug: 37513882 Change-Id: I345e48aae0fbb3c28c2f2c0dc035e6b0fa70df43 --- libs/gui/ConsumerBase.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp index 5c6158c771..292f13de2d 100644 --- a/libs/gui/ConsumerBase.cpp +++ b/libs/gui/ConsumerBase.cpp @@ -324,16 +324,25 @@ status_t ConsumerBase::addReleaseFenceLocked(int slot, return OK; } - auto status = mSlots[slot].mFence->getStatus(); - - if (status == Fence::Status::Invalid) { - CB_LOGE("fence has invalid state"); + // Check status of fences first because merging is expensive. + // Merging an invalid fence with any other fence results in an + // invalid fence. + auto currentStatus = mSlots[slot].mFence->getStatus(); + if (currentStatus == Fence::Status::Invalid) { + CB_LOGE("Existing fence has invalid state"); return BAD_VALUE; } - if (status == Fence::Status::Signaled) { + auto incomingStatus = fence->getStatus(); + if (incomingStatus == Fence::Status::Invalid) { + CB_LOGE("New fence has invalid state"); mSlots[slot].mFence = fence; - } else { // status == Fence::Status::Unsignaled + return BAD_VALUE; + } + + // If both fences are signaled or both are unsignaled, we need to merge + // them to get an accurate timestamp. + if (currentStatus == incomingStatus) { char fenceName[32] = {}; snprintf(fenceName, 32, "%.28s:%d", mName.string(), slot); sp mergedFence = Fence::merge( @@ -346,7 +355,17 @@ status_t ConsumerBase::addReleaseFenceLocked(int slot, return BAD_VALUE; } mSlots[slot].mFence = mergedFence; + } else if (incomingStatus == Fence::Status::Unsignaled) { + // If one fence has signaled and the other hasn't, the unsignaled + // fence will approximately correspond with the correct timestamp. + // There's a small race if both fences signal at about the same time + // and their statuses are retrieved with unfortunate timing. However, + // by this point, they will have both signaled and only the timestamp + // will be slightly off; any dependencies after this point will + // already have been met. + mSlots[slot].mFence = fence; } + // else if (currentStatus == Fence::Status::Unsignaled) is a no-op. return OK; } -- GitLab From d0d7695ecbfd12aaecc8aec66aacb487b116ac0b Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Tue, 22 Aug 2017 13:08:37 -0700 Subject: [PATCH 020/704] Dumpstate: Add tombstone filtering Only package the ten latest tombstones. This recovers the old behavior when tombstones were limited to ten by tombstoned, and ensures that bugreports stay small in size. It is future work to optimize this, e.g., by packaging as many as possible. Bug: 64290162 Test: m Test: adb root && for ((i=0;i<50;i++)) ; do adb shell touch /data/tombstones/tombstone_$i ; done ; adb bugreport test.zip ; unzip -l test.zip | grep tomb Change-Id: I4072b5fbcf1e0314aa3eebeefbadc61d5ec10787 --- cmds/dumpstate/dumpstate.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index 9e77e8fbcb..b3d628cac5 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -144,10 +144,12 @@ static const CommandOptions AS_ROOT_20 = CommandOptions::WithTimeout(20).AsRoot( * Returns a vector of dump fds under |dir_path| with a given |file_prefix|. * The returned vector is sorted by the mtimes of the dumps. If |limit_by_mtime| * is set, the vector only contains files that were written in the last 30 minutes. + * If |limit_by_count| is set, the vector only contains the ten latest files. */ static std::vector* GetDumpFds(const std::string& dir_path, const std::string& file_prefix, - bool limit_by_mtime) { + bool limit_by_mtime, + bool limit_by_count = true) { const time_t thirty_minutes_ago = ds.now_ - 60 * 30; std::unique_ptr> dump_data(new std::vector()); @@ -190,6 +192,10 @@ static std::vector* GetDumpFds(const std::string& dir_path, std::sort(dump_data->begin(), dump_data->end()); + if (limit_by_count && dump_data->size() > 10) { + dump_data->erase(dump_data->begin() + 10, dump_data->end()); + } + return dump_data.release(); } -- GitLab From 0617894190ea0c3ee50889bee1d4df0f369b0761 Mon Sep 17 00:00:00 2001 From: chaviw Date: Thu, 27 Jul 2017 10:25:59 -0700 Subject: [PATCH 021/704] Add a re-parent function to re-parent a specific child. This is similar to reparentChildren, but the reparentChild will only re-parent a specific child to the new parent and not all children. Test: Added test in Transaction_test for reparentChild. Change-Id: I4275e0d5f1d5601b489956753c78a56d1a5d4c1c --- libs/gui/LayerState.cpp | 4 ++ libs/gui/SurfaceComposerClient.cpp | 23 +++++++++++ libs/gui/SurfaceControl.cpp | 7 ++++ libs/gui/include/gui/SurfaceComposerClient.h | 2 + libs/gui/include/gui/SurfaceControl.h | 6 +++ libs/gui/include/private/gui/LayerState.h | 6 ++- services/surfaceflinger/Layer.cpp | 38 +++++++++++++++++++ services/surfaceflinger/Layer.h | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 7 ++++ .../surfaceflinger/SurfaceFlinger_hwc1.cpp | 7 ++++ .../surfaceflinger/tests/Transaction_test.cpp | 29 ++++++++++++++ 11 files changed, 129 insertions(+), 1 deletion(-) diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 9b06e63610..573f6856d6 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -45,6 +45,8 @@ status_t layer_state_t::write(Parcel& output) const output.writeInt32(overrideScalingMode); output.writeStrongBinder(IInterface::asBinder(barrierGbp)); output.writeStrongBinder(relativeLayerHandle); + output.writeStrongBinder(parentHandleForChild); + output.writeStrongBinder(childHandle); output.write(transparentRegion); return NO_ERROR; } @@ -77,6 +79,8 @@ status_t layer_state_t::read(const Parcel& input) barrierGbp = interface_cast(input.readStrongBinder()); relativeLayerHandle = input.readStrongBinder(); + parentHandleForChild = input.readStrongBinder(); + childHandle = input.readStrongBinder(); input.read(transparentRegion); return NO_ERROR; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 7ae2672249..b0ae7e0bdf 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -176,6 +176,9 @@ public: status_t reparentChildren(const sp& client, const sp& id, const sp& newParentHandle); + status_t reparentChild(const sp& client, + const sp& id, const sp& newParentHandle, + const sp& childHandle); status_t detachChildren(const sp& client, const sp& id); status_t setOverrideScalingMode(const sp& client, @@ -493,6 +496,21 @@ status_t Composer::reparentChildren( return NO_ERROR; } +status_t Composer::reparentChild(const sp& client, + const sp& id, + const sp& newParentHandle, + const sp& childHandle) { + Mutex::Autolock lock(mLock); + layer_state_t* s = getLayerStateLocked(client, id); + if (!s) { + return BAD_INDEX; + } + s->what |= layer_state_t::eReparentChild; + s->parentHandleForChild = newParentHandle; + s->childHandle = childHandle; + return NO_ERROR; +} + status_t Composer::detachChildren( const sp& client, const sp& id) { @@ -831,6 +849,11 @@ status_t SurfaceComposerClient::reparentChildren(const sp& id, return getComposer().reparentChildren(this, id, newParentHandle); } +status_t SurfaceComposerClient::reparentChild(const sp& id, + const sp& newParentHandle, const sp& childHandle) { + return getComposer().reparentChild(this, id, newParentHandle, childHandle); +} + status_t SurfaceComposerClient::detachChildren(const sp& id) { return getComposer().detachChildren(this, id); } diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp index 58bd273de6..b9c5ef9580 100644 --- a/libs/gui/SurfaceControl.cpp +++ b/libs/gui/SurfaceControl.cpp @@ -191,6 +191,13 @@ status_t SurfaceControl::reparentChildren(const sp& newParentHandle) { return mClient->reparentChildren(mHandle, newParentHandle); } +status_t SurfaceControl::reparentChild(const sp& newParentHandle, + const sp& childHandle) { + status_t err = validate(); + if (err < 0) return err; + return mClient->reparentChild(mHandle, newParentHandle, childHandle); +} + status_t SurfaceControl::detachChildren() { status_t err = validate(); if (err < 0) return err; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 145c0597bd..6e2cb83544 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -161,6 +161,8 @@ public: const sp& handle, uint64_t frameNumber); status_t reparentChildren(const sp& id, const sp& newParentHandle); + status_t reparentChild(const sp& id, const sp& newParentHandle, + const sp& childHandle); status_t detachChildren(const sp& id); status_t setOverrideScalingMode(const sp& id, int32_t overrideScalingMode); diff --git a/libs/gui/include/gui/SurfaceControl.h b/libs/gui/include/gui/SurfaceControl.h index c15209d32c..d8b67ef96a 100644 --- a/libs/gui/include/gui/SurfaceControl.h +++ b/libs/gui/include/gui/SurfaceControl.h @@ -124,6 +124,12 @@ public: // Reparents all children of this layer to the new parent handle. status_t reparentChildren(const sp& newParentHandle); + // Reparents a specified child from this layer to the new parent handle. + // The child, parent, and new parent must all have the same client. + // This can be used instead of reparentChildren if the caller wants to + // only re-parent specific children. + status_t reparentChild(const sp& newParentHandle, const sp& childHandle); + // Detaches all child surfaces (and their children recursively) // from their SurfaceControl. // The child SurfaceControl's will not throw exceptions or return errors, diff --git a/libs/gui/include/private/gui/LayerState.h b/libs/gui/include/private/gui/LayerState.h index 307c764702..4f73e04e22 100644 --- a/libs/gui/include/private/gui/LayerState.h +++ b/libs/gui/include/private/gui/LayerState.h @@ -59,7 +59,8 @@ struct layer_state_t { eGeometryAppliesWithResize = 0x00001000, eReparentChildren = 0x00002000, eDetachChildren = 0x00004000, - eRelativeLayerChanged = 0x00008000 + eRelativeLayerChanged = 0x00008000, + eReparentChild = 0x00010000 }; layer_state_t() @@ -107,6 +108,9 @@ struct layer_state_t { sp relativeLayerHandle; + sp parentHandleForChild; + sp childHandle; + // non POD must be last. see write/read Region transparentRegion; }; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index e92565fd9c..c406f74bc7 100755 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2625,6 +2625,44 @@ bool Layer::reparentChildren(const sp& newParentHandle) { return true; } +bool Layer::reparentChild(const sp& newParentHandle, const sp& childHandle) { + if (newParentHandle == nullptr || childHandle == nullptr) { + return false; + } + + auto handle = static_cast(newParentHandle.get()); + sp newParent = handle->owner.promote(); + if (newParent == nullptr) { + ALOGE("Unable to promote Layer handle"); + return false; + } + + handle = static_cast(childHandle.get()); + sp child = handle->owner.promote(); + if (child == nullptr) { + ALOGE("Unable to promote child Layer handle"); + return false; + } + + if (mCurrentChildren.indexOf(child) < 0) { + ALOGE("Child layer is not child of current layer"); + return false; + } + + sp parentClient(mClientRef.promote()); + sp childClient(child->mClientRef.promote()); + sp newParentClient(newParent->mClientRef.promote()); + + if (parentClient != childClient || childClient != newParentClient) { + ALOGE("Current layer, child layer, and new parent layer must have the same client"); + return false; + } + + newParent->addChild(child); + mCurrentChildren.remove(child); + return true; +} + bool Layer::detachChildren() { traverseInZOrder(LayerVector::StateSet::Drawing, [this](Layer* child) { if (child == this) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index f7b82e4fb7..f94833b32f 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -241,6 +241,7 @@ public: bool setOverrideScalingMode(int32_t overrideScalingMode); void setInfo(uint32_t type, uint32_t appId); bool reparentChildren(const sp& layer); + bool reparentChild(const sp& newParentHandle, const sp& childHandle); bool detachChildren(); // If we have received a new buffer this frame, we will pass its surface diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 6a01f308a3..4b079d6fbb 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3103,6 +3103,13 @@ uint32_t SurfaceFlinger::setClientStateLocked( // We don't trigger a traversal here because if no other state is // changed, we don't want this to cause any more work } + // Always re-parent the children that explicitly requested to get + // re-parented before the general re-parent of all children. + if (what & layer_state_t::eReparentChild) { + if (layer->reparentChild(s.parentHandleForChild, s.childHandle)) { + flags |= eTransactionNeeded|eTraversalNeeded; + } + } if (what & layer_state_t::eReparentChildren) { if (layer->reparentChildren(s.reparentHandle)) { flags |= eTransactionNeeded|eTraversalNeeded; diff --git a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp index a92e1f9f89..71aa52dee6 100644 --- a/services/surfaceflinger/SurfaceFlinger_hwc1.cpp +++ b/services/surfaceflinger/SurfaceFlinger_hwc1.cpp @@ -2679,6 +2679,13 @@ uint32_t SurfaceFlinger::setClientStateLocked( // We don't trigger a traversal here because if no other state is // changed, we don't want this to cause any more work } + // Always re-parent the children that explicitly requested to get + // re-parented before the general re-parent of all children. + if (what & layer_state_t::eReparentChild) { + if (layer->reparentChild(s.parentHandleForChild, s.childHandle)) { + flags |= eTransactionNeeded|eTraversalNeeded; + } + } if (what & layer_state_t::eReparentChildren) { if (layer->reparentChildren(s.reparentHandle)) { flags |= eTransactionNeeded|eTraversalNeeded; diff --git a/services/surfaceflinger/tests/Transaction_test.cpp b/services/surfaceflinger/tests/Transaction_test.cpp index 4ce14f8d3a..aef5a5f93b 100644 --- a/services/surfaceflinger/tests/Transaction_test.cpp +++ b/services/surfaceflinger/tests/Transaction_test.cpp @@ -1123,4 +1123,33 @@ TEST_F(ChildLayerTest, Bug36858924) { fillSurfaceRGBA8(mFGSurfaceControl, 0, 255, 0); } +TEST_F(ChildLayerTest, ReparentChild) { + SurfaceComposerClient::openGlobalTransaction(); + mChild->show(); + mChild->setPosition(10, 10); + mFGSurfaceControl->setPosition(64, 64); + SurfaceComposerClient::closeGlobalTransaction(true); + + { + ScreenCapture::captureScreen(&mCapture); + // Top left of foreground must now be visible + mCapture->expectFGColor(64, 64); + // But 10 pixels in we should see the child surface + mCapture->expectChildColor(74, 74); + // And 10 more pixels we should be back to the foreground surface + mCapture->expectFGColor(84, 84); + } + mFGSurfaceControl->reparentChild(mBGSurfaceControl->getHandle(), mChild->getHandle()); + { + ScreenCapture::captureScreen(&mCapture); + mCapture->expectFGColor(64, 64); + // In reparenting we should have exposed the entire foreground surface. + mCapture->expectFGColor(74, 74); + // And the child layer should now begin at 10, 10 (since the BG + // layer is at (0, 0)). + mCapture->expectBGColor(9, 9); + mCapture->expectChildColor(10, 10); + } +} + } -- GitLab From 8c3b6ac2012ad6731f214fca6fea73fd13619d0c Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 24 Aug 2017 10:25:42 -0700 Subject: [PATCH 022/704] Fix layer Z dump %u -> %d to match that Z is an int32_t Test: manual Change-Id: Ia7f1f3575953e63eeb77a2f776142007f23ad1a4 --- services/surfaceflinger/Layer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index e92565fd9c..1355a44d3b 100755 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -2501,7 +2501,7 @@ void Layer::miniDump(String8& result, int32_t hwcId) const { const Layer::State& layerState(getDrawingState()); const HWCInfo& hwcInfo = mHwcLayers.at(hwcId); - result.appendFormat(" %10u | ", layerState.z); + result.appendFormat(" %10d | ", layerState.z); result.appendFormat("%10s | ", to_string(getCompositionType(hwcId)).c_str()); const Rect& frame = hwcInfo.displayFrame; -- GitLab From 4cab6aac0dca13a79701e86ac6ff3cbb1584a8ab Mon Sep 17 00:00:00 2001 From: David Hanna Jr Date: Tue, 15 Aug 2017 13:49:24 -0700 Subject: [PATCH 023/704] test-hwc2: Fix usage flags in test-hwc2 This uses 64-bit usage flags as 32-bit usage is depreciated and was partially causing problems with ryu Test: ran on ryu Change-Id: I56c2c3da1ae94d97e2accbe236333f46803aea14 --- .../tests/hwc2/Hwc2TestBuffer.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp index 6873c455e9..9ff9a4fac9 100644 --- a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp +++ b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -31,6 +32,7 @@ #include "Hwc2TestLayers.h" using namespace android; +using android::hardware::graphics::common::V1_0::BufferUsage; /* Returns a fence from egl */ typedef void (*FenceCallback)(int32_t fence, void* callbackArgs); @@ -396,8 +398,9 @@ int Hwc2TestBuffer::generateBuffer() { /* Create new graphic buffer with correct dimensions */ mGraphicBuffer = new GraphicBuffer(mBufferArea.width, mBufferArea.height, - mFormat, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER, - "hwc2_test_buffer"); + mFormat, BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | + BufferUsage::COMPOSER_OVERLAY, "hwc2_test_buffer"); + int ret = mGraphicBuffer->initCheck(); if (ret) { return ret; @@ -408,7 +411,8 @@ int Hwc2TestBuffer::generateBuffer() /* Locks the buffer for writing */ uint8_t* img; - mGraphicBuffer->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); + mGraphicBuffer->lock(static_cast(BufferUsage::CPU_WRITE_OFTEN), + (void**)(&img)); uint32_t stride = mGraphicBuffer->getStride(); @@ -469,8 +473,9 @@ int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle, { /* Create new graphic buffer with correct dimensions */ mGraphicBuffer = new GraphicBuffer(bufferArea.width, bufferArea.height, - mFormat, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER, - "hwc2_test_buffer"); + mFormat, BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | + BufferUsage::COMPOSER_OVERLAY, "hwc2_test_buffer"); + int ret = mGraphicBuffer->initCheck(); if (ret) { return ret; @@ -480,7 +485,8 @@ int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle, } uint8_t* img; - mGraphicBuffer->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); + mGraphicBuffer->lock(static_cast(BufferUsage::CPU_WRITE_OFTEN), + (void**)(&img)); uint32_t stride = mGraphicBuffer->getStride(); -- GitLab From 3f05602a8c33a4b66a47dc077eaba95f9f1e3977 Mon Sep 17 00:00:00 2001 From: David Hanna Jr Date: Thu, 27 Jul 2017 19:19:15 -0700 Subject: [PATCH 024/704] test-hwc2: Added virtual Display tests VirtualDisplay tests added. Test: Ran on (heavily modified) ryu Change-Id: I5cc92d11d4cde6c3407d71652f87ea3c3fb63228 --- services/surfaceflinger/tests/hwc2/Android.mk | 7 +- .../surfaceflinger/tests/hwc2/Hwc2Test.cpp | 216 +++++++++++++++++- .../tests/hwc2/Hwc2TestBuffer.cpp | 142 +++++++++--- .../tests/hwc2/Hwc2TestBuffer.h | 34 +++ .../tests/hwc2/Hwc2TestPixelComparator.cpp | 113 +++++++++ .../tests/hwc2/Hwc2TestPixelComparator.h | 59 +++++ .../tests/hwc2/Hwc2TestProperties.cpp | 8 +- .../tests/hwc2/Hwc2TestProperties.h | 5 +- .../tests/hwc2/Hwc2TestVirtualDisplay.cpp | 43 +++- .../tests/hwc2/Hwc2TestVirtualDisplay.h | 12 +- 10 files changed, 596 insertions(+), 43 deletions(-) create mode 100644 services/surfaceflinger/tests/hwc2/Hwc2TestPixelComparator.cpp create mode 100644 services/surfaceflinger/tests/hwc2/Hwc2TestPixelComparator.h diff --git a/services/surfaceflinger/tests/hwc2/Android.mk b/services/surfaceflinger/tests/hwc2/Android.mk index 203ced5c3a..6d20349794 100644 --- a/services/surfaceflinger/tests/hwc2/Android.mk +++ b/services/surfaceflinger/tests/hwc2/Android.mk @@ -36,7 +36,9 @@ LOCAL_SHARED_LIBRARIES := \ libui \ libgui \ liblog \ - libsync + libsync \ + libskia \ + android.hardware.graphics.common@1.0 LOCAL_STATIC_LIBRARIES := \ libbase \ libadf \ @@ -49,6 +51,7 @@ LOCAL_SRC_FILES := \ Hwc2TestLayers.cpp \ Hwc2TestBuffer.cpp \ Hwc2TestClientTarget.cpp \ - Hwc2TestVirtualDisplay.cpp + Hwc2TestVirtualDisplay.cpp \ + Hwc2TestPixelComparator.cpp include $(BUILD_NATIVE_TEST) diff --git a/services/surfaceflinger/tests/hwc2/Hwc2Test.cpp b/services/surfaceflinger/tests/hwc2/Hwc2Test.cpp index 4055527b13..4878c140ed 100644 --- a/services/surfaceflinger/tests/hwc2/Hwc2Test.cpp +++ b/services/surfaceflinger/tests/hwc2/Hwc2Test.cpp @@ -1775,6 +1775,145 @@ protected: } } + void createAndPresentVirtualDisplay(size_t layerCnt, + Hwc2TestCoverage coverage, + const std::unordered_map& + coverageExceptions) + { + Hwc2TestVirtualDisplay testVirtualDisplay(coverage); + hwc2_display_t display; + android_pixel_format_t desiredFormat = HAL_PIXEL_FORMAT_RGBA_8888; + + do { + // Items dependent on the display dimensions + hwc2_error_t err = HWC2_ERROR_NONE; + const UnsignedArea& dimension = + testVirtualDisplay.getDisplayDimension(); + ASSERT_NO_FATAL_FAILURE(createVirtualDisplay(dimension.width, + dimension.height, &desiredFormat, &display, &err)); + ASSERT_TRUE(err == HWC2_ERROR_NONE) + << "Cannot allocate virtual display"; + + ASSERT_NO_FATAL_FAILURE(setPowerMode(display, HWC2_POWER_MODE_ON)); + ASSERT_NO_FATAL_FAILURE(enableVsync(display)); + + std::vector configs; + ASSERT_NO_FATAL_FAILURE(getDisplayConfigs(display, &configs)); + + for (auto config : configs) { + ASSERT_NO_FATAL_FAILURE(setActiveConfig(display, config)); + + Area displayArea; + ASSERT_NO_FATAL_FAILURE(getActiveDisplayArea(display, + &displayArea)); + + std::vector layers; + ASSERT_NO_FATAL_FAILURE(createLayers(display, &layers, + layerCnt)); + Hwc2TestLayers testLayers(layers, coverage, displayArea, + coverageExceptions); + + /* + * Layouts that do not cover an entire virtual display will + * cause undefined behavior. + * Enable optimizeLayouts to avoid this. + */ + testLayers.optimizeLayouts(); + do { + // Items dependent on the testLayers properties + std::set clientLayers; + std::set clearLayers; + uint32_t numTypes, numRequests; + bool hasChanges, skip; + bool flipClientTarget; + int32_t presentFence; + Hwc2TestClientTarget testClientTarget; + buffer_handle_t outputBufferHandle; + android::base::unique_fd outputBufferReleaseFence; + + ASSERT_NO_FATAL_FAILURE(setLayerProperties(display, layers, + &testLayers, &skip)); + + if (skip) + continue; + + ASSERT_NO_FATAL_FAILURE(validateDisplay(display, &numTypes, + &numRequests, &hasChanges)); + + if (hasChanges) + EXPECT_LE(numTypes, static_cast(layers.size())) + << "wrong number of requests"; + + ASSERT_NO_FATAL_FAILURE(handleCompositionChanges(display, + testLayers, layers, numTypes, &clientLayers)); + + ASSERT_NO_FATAL_FAILURE(handleRequests(display, layers, + numRequests, &clearLayers, &flipClientTarget)); + ASSERT_NO_FATAL_FAILURE(setClientTarget(display, + &testClientTarget, testLayers, clientLayers, + clearLayers, flipClientTarget, displayArea)); + ASSERT_NO_FATAL_FAILURE(acceptDisplayChanges(display)); + + ASSERT_EQ(testVirtualDisplay.getOutputBuffer( + &outputBufferHandle, &outputBufferReleaseFence), 0); + ASSERT_NO_FATAL_FAILURE(setOutputBuffer(display, + outputBufferHandle, outputBufferReleaseFence)); + + EXPECT_NO_FATAL_FAILURE(presentDisplay(display, + &presentFence)); + ASSERT_NO_FATAL_FAILURE(closeFences(display, presentFence)); + + ASSERT_EQ(testVirtualDisplay.verifyOutputBuffer(&testLayers, + &layers, &clearLayers), 0); + + /* + * Upscaling the image causes minor pixel differences. + * Work around this by using some threshold. + * + * Fail test if we are off by more than 1% of our + * pixels. + */ + ComparatorResult& comparatorResult = ComparatorResult::get(); + int threshold = (dimension.width * dimension.height) / 100; + double diffPercent = (comparatorResult.getDifferentPixelCount() * 100.0) / + (dimension.width * dimension.height); + + if (comparatorResult.getDifferentPixelCount() != 0) + EXPECT_TRUE(false) + << comparatorResult.getDifferentPixelCount() << " pixels (" + << diffPercent << "%) are different."; + + if (comparatorResult.getDifferentPixelCount() > threshold) { + EXPECT_TRUE(false) + << "Mismatched pixel count exceeds threshold. " + << "Writing buffers to file."; + + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance() + ->current_test_info(); + + EXPECT_EQ(testVirtualDisplay.writeBuffersToFile( + test_info->name()), 0) + << "Failed to write buffers."; + } + + ASSERT_LE(comparatorResult.getDifferentPixelCount(), threshold) + << comparatorResult.getDifferentPixelCount() << " pixels (" + << diffPercent << "%) are different. " + << "Exceeds 1% threshold, terminating test. " + << "Test case: " << testLayers.dump(); + + } while (testLayers.advance()); + + ASSERT_NO_FATAL_FAILURE(destroyLayers(display, + std::move(layers))); + } + ASSERT_NO_FATAL_FAILURE(disableVsync(display)); + ASSERT_NO_FATAL_FAILURE(setPowerMode(display, HWC2_POWER_MODE_OFF)); + ASSERT_NO_FATAL_FAILURE(destroyVirtualDisplay(display)); + } while (testVirtualDisplay.advance()); + } + hwc2_device_t* mHwc2Device = nullptr; enum class Hwc2TestHotplugStatus { @@ -4479,7 +4618,7 @@ TEST_F(Hwc2Test, SET_OUTPUT_BUFFER) buffer_handle_t handle; android::base::unique_fd acquireFence; - if (testVirtualDisplay->getBuffer(&handle, &acquireFence) >= 0) + if (testVirtualDisplay->getOutputBuffer(&handle, &acquireFence) >= 0) EXPECT_NO_FATAL_FAILURE(test->setOutputBuffer(display, handle, acquireFence)); })); @@ -4499,7 +4638,7 @@ TEST_F(Hwc2Test, SET_OUTPUT_BUFFER_bad_display) ASSERT_NO_FATAL_FAILURE(test->getBadDisplay(&badDisplay)); - if (testVirtualDisplay->getBuffer(&handle, &acquireFence) < 0) + if (testVirtualDisplay->getOutputBuffer(&handle, &acquireFence) < 0) return; ASSERT_NO_FATAL_FAILURE(test->setOutputBuffer(badDisplay, @@ -4539,7 +4678,7 @@ TEST_F(Hwc2Test, SET_OUTPUT_BUFFER_unsupported) android::base::unique_fd acquireFence; hwc2_error_t err = HWC2_ERROR_NONE; - if (testVirtualDisplay.getBuffer(&handle, &acquireFence) < 0) + if (testVirtualDisplay.getOutputBuffer(&handle, &acquireFence) < 0) continue; ASSERT_NO_FATAL_FAILURE(setOutputBuffer(display, handle, @@ -4557,3 +4696,74 @@ TEST_F(Hwc2Test, DUMP) ASSERT_NO_FATAL_FAILURE(dump(&buffer)); } + +/* + * TODO(b/64724708): Hwc2TestPropertyName::BufferArea MUST be default for all + * virtual display tests as we don't handle this case correctly. + * + * Only default dataspace is supported in our drawing code. + */ +const std::unordered_map + virtualDisplayExceptions = + {{Hwc2TestPropertyName::BufferArea, Hwc2TestCoverage::Default}, + {Hwc2TestPropertyName::Dataspace, Hwc2TestCoverage::Default}}; + +/* TESTCASE: Tests that the HWC2 can present 1 layer with default coverage on a + * virtual display. */ +TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_default_1) +{ + Hwc2TestCoverage coverage = Hwc2TestCoverage::Default; + const size_t layerCnt = 1; + ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage, + virtualDisplayExceptions)); +} + +/* TESTCASE: Tests that the HWC2 can present 1 layer with basic coverage on a + * virtual display. */ +TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_basic_1) +{ + Hwc2TestCoverage coverage = Hwc2TestCoverage::Basic; + const size_t layerCnt = 1; + ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage, + virtualDisplayExceptions)); +} + +/* TESTCASE: Tests that the HWC2 can present 2 layers with default coverage on a + * virtual display. */ +TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_default_2) +{ + Hwc2TestCoverage coverage = Hwc2TestCoverage::Default; + const size_t layerCnt = 2; + ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage, + virtualDisplayExceptions)); +} + +/* TESTCASE: Tests that the HWC2 can present 3 layers with default coverage on a + * virtual display. */ +TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_default_3) +{ + Hwc2TestCoverage coverage = Hwc2TestCoverage::Default; + const size_t layerCnt = 3; + ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage, + virtualDisplayExceptions)); +} + +/* TESTCASE: Tests that the HWC2 can present 4 layers with default coverage on a + * virtual display. */ +TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_default_4) +{ + Hwc2TestCoverage coverage = Hwc2TestCoverage::Default; + const size_t layerCnt = 4; + ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage, + virtualDisplayExceptions)); +} + +/* TESTCASE: Tests that the HWC2 can present 5 layers with default coverage on a + * virtual display. */ +TEST_F(Hwc2Test, PRESENT_VIRTUAL_DISPLAY_default_5) +{ + Hwc2TestCoverage coverage = Hwc2TestCoverage::Default; + const size_t layerCnt = 5; + ASSERT_NO_FATAL_FAILURE(createAndPresentVirtualDisplay(layerCnt, coverage, + virtualDisplayExceptions)); +} diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp index 9ff9a4fac9..648456295d 100644 --- a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp +++ b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.cpp @@ -27,7 +27,8 @@ #include #include - +#include +#include #include "Hwc2TestBuffer.h" #include "Hwc2TestLayers.h" @@ -462,33 +463,22 @@ Hwc2TestClientTargetBuffer::Hwc2TestClientTargetBuffer() Hwc2TestClientTargetBuffer::~Hwc2TestClientTargetBuffer() { } -/* Generates a client target buffer using the layers assigned for client - * composition. Takes into account the individual layer properties such as +/* Generates a buffer from layersToDraw. + * Takes into account the individual layer properties such as * transform, blend mode, source crop, etc. */ -int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle, - int32_t* outFence, const Area& bufferArea, +static void compositeBufferFromLayers( + const android::sp& graphicBuffer, + android_pixel_format_t format, const Area& bufferArea, const Hwc2TestLayers* testLayers, - const std::set* clientLayers, + const std::set* layersToDraw, const std::set* clearLayers) { - /* Create new graphic buffer with correct dimensions */ - mGraphicBuffer = new GraphicBuffer(bufferArea.width, bufferArea.height, - mFormat, BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | - BufferUsage::COMPOSER_OVERLAY, "hwc2_test_buffer"); - - int ret = mGraphicBuffer->initCheck(); - if (ret) { - return ret; - } - if (!mGraphicBuffer->handle) { - return -EINVAL; - } - + /* Locks the buffer for writing */ uint8_t* img; - mGraphicBuffer->lock(static_cast(BufferUsage::CPU_WRITE_OFTEN), + graphicBuffer->lock(static_cast(BufferUsage::CPU_WRITE_OFTEN), (void**)(&img)); - uint32_t stride = mGraphicBuffer->getStride(); + uint32_t stride = graphicBuffer->getStride(); float bWDiv3 = bufferArea.width / 3; float bW2Div3 = bufferArea.width * 2 / 3; @@ -503,10 +493,10 @@ int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle, uint8_t r = 0, g = 0, b = 0; float a = 0.0f; - /* Cycle through each client layer from back to front and + /* Cycle through each layer from back to front and * update the pixel color. */ - for (auto layer = clientLayers->rbegin(); - layer != clientLayers->rend(); ++layer) { + for (auto layer = layersToDraw->rbegin(); + layer != layersToDraw->rend(); ++layer) { const hwc_rect_t df = testLayers->getDisplayFrame(*layer); @@ -688,14 +678,114 @@ int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle, } /* Set the pixel color */ - setColor(x, y, mFormat, stride, img, r, g, b, a * 255); + setColor(x, y, format, stride, img, r, g, b, a * 255); } } - mGraphicBuffer->unlock(); + graphicBuffer->unlock(); +} + +/* Generates a client target buffer using the layers assigned for client + * composition. Takes into account the individual layer properties such as + * transform, blend mode, source crop, etc. */ +int Hwc2TestClientTargetBuffer::get(buffer_handle_t* outHandle, + int32_t* outFence, const Area& bufferArea, + const Hwc2TestLayers* testLayers, + const std::set* clientLayers, + const std::set* clearLayers) +{ + /* Create new graphic buffer with correct dimensions */ + mGraphicBuffer = new GraphicBuffer(bufferArea.width, bufferArea.height, + mFormat, BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | + BufferUsage::COMPOSER_OVERLAY, "hwc2_test_buffer"); + + int ret = mGraphicBuffer->initCheck(); + if (ret) + return ret; + + if (!mGraphicBuffer->handle) + return -EINVAL; + + compositeBufferFromLayers(mGraphicBuffer, mFormat, bufferArea, testLayers, + clientLayers, clearLayers); *outFence = mFenceGenerator->get(); *outHandle = mGraphicBuffer->handle; return 0; } + +void Hwc2TestVirtualBuffer::updateBufferArea(const Area& bufferArea) +{ + mBufferArea.width = bufferArea.width; + mBufferArea.height = bufferArea.height; +} + +bool Hwc2TestVirtualBuffer::writeBufferToFile(std::string path) +{ + SkFILEWStream file(path.c_str()); + const SkImageInfo info = SkImageInfo::Make(mBufferArea.width, + mBufferArea.height, SkColorType::kRGBA_8888_SkColorType, + SkAlphaType::kPremul_SkAlphaType); + + uint8_t* img; + mGraphicBuffer->lock(static_cast(BufferUsage::CPU_WRITE_OFTEN), + (void**)(&img)); + + SkPixmap pixmap(info, img, mGraphicBuffer->getStride()); + bool result = file.isValid() && SkEncodeImage(&file, pixmap, + SkEncodedImageFormat::kPNG, 100); + + mGraphicBuffer->unlock(); + return result; +} + +/* Generates a buffer that holds the expected result of compositing all of our + * layers */ +int Hwc2TestExpectedBuffer::generateExpectedBuffer( + const Hwc2TestLayers* testLayers, + const std::vector* allLayers, + const std::set* clearLayers) +{ + mGraphicBuffer = new GraphicBuffer(mBufferArea.width, mBufferArea.height, + mFormat, BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN, + "hwc2_test_buffer"); + + int ret = mGraphicBuffer->initCheck(); + if (ret) + return ret; + + if (!mGraphicBuffer->handle) + return -EINVAL; + + const std::set allLayerSet(allLayers->begin(), + allLayers->end()); + + compositeBufferFromLayers(mGraphicBuffer, mFormat, mBufferArea, testLayers, + &allLayerSet, clearLayers); + + return 0; +} + +int Hwc2TestOutputBuffer::getOutputBuffer(buffer_handle_t* outHandle, + int32_t* outFence) +{ + if (mBufferArea.width == -1 || mBufferArea.height == -1) + return -EINVAL; + + mGraphicBuffer = new GraphicBuffer(mBufferArea.width, mBufferArea.height, + mFormat, BufferUsage::CPU_READ_OFTEN | + BufferUsage::GPU_RENDER_TARGET, "hwc2_test_buffer"); + + int ret = mGraphicBuffer->initCheck(); + if (ret) + return ret; + + if (!mGraphicBuffer->handle) + return -EINVAL; + + *outFence = -1; + *outHandle = mGraphicBuffer->handle; + + return 0; +} diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.h b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.h index b2b3a6696a..fd54fef2d7 100644 --- a/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.h +++ b/services/surfaceflinger/tests/hwc2/Hwc2TestBuffer.h @@ -71,4 +71,38 @@ protected: const android_pixel_format_t mFormat = HAL_PIXEL_FORMAT_RGBA_8888; }; + +class Hwc2TestVirtualBuffer { +public: + void updateBufferArea(const Area& bufferArea); + + bool writeBufferToFile(std::string path); + + android::sp& graphicBuffer() + { + return mGraphicBuffer; + } + +protected: + android::sp mGraphicBuffer; + + Area mBufferArea = {-1, -1}; + + const android_pixel_format_t mFormat = HAL_PIXEL_FORMAT_RGBA_8888; +}; + + +class Hwc2TestExpectedBuffer : public Hwc2TestVirtualBuffer { +public: + int generateExpectedBuffer(const Hwc2TestLayers* testLayers, + const std::vector* allLayers, + const std::set* clearLayers); +}; + + +class Hwc2TestOutputBuffer : public Hwc2TestVirtualBuffer { +public: + int getOutputBuffer(buffer_handle_t* outHandle, int32_t* outFence); +}; + #endif /* ifndef _HWC2_TEST_BUFFER_H */ diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestPixelComparator.cpp b/services/surfaceflinger/tests/hwc2/Hwc2TestPixelComparator.cpp new file mode 100644 index 0000000000..904b927abf --- /dev/null +++ b/services/surfaceflinger/tests/hwc2/Hwc2TestPixelComparator.cpp @@ -0,0 +1,113 @@ +/* + * 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. + */ + +#include +#include + +#include "Hwc2TestPixelComparator.h" + +using android::hardware::graphics::common::V1_0::BufferUsage; + +uint32_t ComparatorResult::getPixel(int32_t x, int32_t y, uint32_t stride, + uint8_t* img) const +{ + uint32_t r = img[(y * stride + x) * 4 + 0]; + uint32_t g = img[(y * stride + x) * 4 + 1]; + uint32_t b = img[(y * stride + x) * 4 + 2]; + uint32_t a = img[(y * stride + x) * 4 + 3]; + + uint32_t pixel = 0; + pixel |= r; + pixel |= g << 8; + pixel |= b << 16; + pixel |= a << 24; + return pixel; +} + +void ComparatorResult::CompareBuffers( + android::sp& resultBuffer, + android::sp& expectedBuffer) +{ + uint8_t* resultBufferImg; + uint8_t* expectedBufferImg; + resultBuffer->lock(static_cast(BufferUsage::CPU_READ_OFTEN), + (void**)(&resultBufferImg)); + + expectedBuffer->lock(static_cast(BufferUsage::CPU_READ_OFTEN), + (void**)(&expectedBufferImg)); + mComparisons.clear(); + int32_t mDifferentPixelCount = 0; + int32_t mBlankPixelCount = 0; + + for (uint32_t y = 0; y < resultBuffer->getHeight(); y++) { + for (uint32_t x = 0; x < resultBuffer->getWidth(); x++) { + uint32_t result = getPixel(x, y, resultBuffer->getStride(), + resultBufferImg); + uint32_t expected = getPixel(x, y, expectedBuffer->getStride(), + expectedBufferImg); + + if (result == 0) + mBlankPixelCount++; + + if (result != expected) + mDifferentPixelCount++; + + mComparisons.emplace_back(std::make_tuple(x, y, result, expected)); + } + } + resultBuffer->unlock(); + expectedBuffer->unlock(); +} + +std::string ComparatorResult::pixelDiff(uint32_t x, uint32_t y, + uint32_t resultPixel, uint32_t expectedPixel) const +{ + uint32_t resultAlpha = (resultPixel >> 24) & 0xFF; + uint32_t resultBlue = (resultPixel >> 16) & 0xFF; + uint32_t resultGreen = (resultPixel >> 8) & 0xFF; + uint32_t resultRed = resultPixel & 0xFF; + + uint32_t expectedAlpha = (expectedPixel >> 24) & 0xFF; + uint32_t expectedBlue = (expectedPixel >> 16) & 0xFF; + uint32_t expectedGreen = (expectedPixel >> 8) & 0xFF; + uint32_t expectedRed = expectedPixel & 0xFF; + + std::ostringstream stream; + + stream << "x: " << x << " y: " << y << std::endl; + stream << std::hex; + stream << "Result pixel: " << resultRed << "|" << resultGreen << "|" + << resultBlue << "|" << resultAlpha << std::endl; + + stream << "Expected pixel: " << expectedRed << "|" << expectedGreen << "|" + << expectedBlue << "|" << expectedAlpha << std::endl; + + return stream.str(); +} + +std::string ComparatorResult::dumpComparison() const +{ + std::ostringstream stream; + stream << "Number of different pixels: " << mDifferentPixelCount; + + for (const auto& comparison : mComparisons) { + if (std::get<2>(comparison) != std::get<3>(comparison)) + stream << pixelDiff(std::get<0>(comparison), + std::get<1>(comparison), std::get<2>(comparison), + std::get<3>(comparison)); + } + return stream.str(); +} diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestPixelComparator.h b/services/surfaceflinger/tests/hwc2/Hwc2TestPixelComparator.h new file mode 100644 index 0000000000..55fa936943 --- /dev/null +++ b/services/surfaceflinger/tests/hwc2/Hwc2TestPixelComparator.h @@ -0,0 +1,59 @@ +/* + * 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. + */ +#ifndef _HWC2_TEST_PIXEL_COMPARATOR_H +#define _HWC2_TEST_PIXEL_COMPARATOR_H + +#include +#include +#include +#include +#include + +class ComparatorResult { +public: + static ComparatorResult& get() + { + static ComparatorResult instance; + return instance; + } + + void CompareBuffers(android::sp& resultBuffer, + android::sp& expectedBuffer); + + std::string dumpComparison() const; + + ComparatorResult(const ComparatorResult&) = delete; + ComparatorResult(ComparatorResult&&) = delete; + ComparatorResult& operator=(ComparatorResult const&) = delete; + ComparatorResult& operator=(ComparatorResult&&) = delete; + + int32_t getDifferentPixelCount() const { return mDifferentPixelCount; } + int32_t getBlankPixelCount() const { return mBlankPixelCount; } + +private: + ComparatorResult() = default; + uint32_t getPixel(int32_t x, int32_t y, uint32_t stride, uint8_t* img) const; + std::string pixelDiff(uint32_t x, uint32_t y, uint32_t resultPixel, + uint32_t expectedPixel) const; + + int32_t mDifferentPixelCount; + int32_t mBlankPixelCount; + /* std::tuple */ + std::vector> + mComparisons; +}; + +#endif /* ifndef _HWC2_TEST_PIXEL_COMPARATOR_H */ diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestProperties.cpp b/services/surfaceflinger/tests/hwc2/Hwc2TestProperties.cpp index b5522de3a5..5b3bbeb708 100644 --- a/services/surfaceflinger/tests/hwc2/Hwc2TestProperties.cpp +++ b/services/surfaceflinger/tests/hwc2/Hwc2TestProperties.cpp @@ -335,9 +335,9 @@ std::string Hwc2TestDisplayDimension::dump() const return dmp.str(); } -void Hwc2TestDisplayDimension::setDependent(Hwc2TestBuffer* buffer) +void Hwc2TestDisplayDimension::setDependent(Hwc2TestVirtualBuffer* buffer) { - mBuffer = buffer; + mBuffers.insert(buffer); updateDependents(); } @@ -345,8 +345,8 @@ void Hwc2TestDisplayDimension::updateDependents() { const UnsignedArea& curr = get(); - if (mBuffer) - mBuffer->updateBufferArea({static_cast(curr.width), + for (Hwc2TestVirtualBuffer* buffer : mBuffers) + buffer->updateBufferArea({static_cast(curr.width), static_cast(curr.height)}); } diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestProperties.h b/services/surfaceflinger/tests/hwc2/Hwc2TestProperties.h index c2029aba04..cb811e06e2 100644 --- a/services/surfaceflinger/tests/hwc2/Hwc2TestProperties.h +++ b/services/surfaceflinger/tests/hwc2/Hwc2TestProperties.h @@ -243,6 +243,7 @@ protected: static const std::array mCompositionSupport; }; +class Hwc2TestVirtualBuffer; class Hwc2TestDisplayDimension : public Hwc2TestProperty { public: @@ -250,12 +251,12 @@ public: std::string dump() const; - void setDependent(Hwc2TestBuffer* buffer); + void setDependent(Hwc2TestVirtualBuffer* buffer); private: void updateDependents(); - Hwc2TestBuffer* mBuffer; + std::set mBuffers; static const std::vector mDefaultDisplayDimensions; static const std::vector mBasicDisplayDimensions; diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestVirtualDisplay.cpp b/services/surfaceflinger/tests/hwc2/Hwc2TestVirtualDisplay.cpp index d0fbc0b5ad..e6cceb82eb 100644 --- a/services/surfaceflinger/tests/hwc2/Hwc2TestVirtualDisplay.cpp +++ b/services/surfaceflinger/tests/hwc2/Hwc2TestVirtualDisplay.cpp @@ -15,14 +15,18 @@ */ #include +#include #include "Hwc2TestVirtualDisplay.h" +#define DIR_NAME "images" + Hwc2TestVirtualDisplay::Hwc2TestVirtualDisplay( Hwc2TestCoverage coverage) : mDisplayDimension(coverage) { - mDisplayDimension.setDependent(&mBuffer); + mDisplayDimension.setDependent(&mOutputBuffer); + mDisplayDimension.setDependent(&mExpectedBuffer); } std::string Hwc2TestVirtualDisplay::dump() const @@ -36,11 +40,11 @@ std::string Hwc2TestVirtualDisplay::dump() const return dmp.str(); } -int Hwc2TestVirtualDisplay::getBuffer(buffer_handle_t* outHandle, +int Hwc2TestVirtualDisplay::getOutputBuffer(buffer_handle_t* outHandle, android::base::unique_fd* outAcquireFence) { int32_t acquireFence; - int ret = mBuffer.get(outHandle, &acquireFence); + int ret = mOutputBuffer.getOutputBuffer(outHandle, &acquireFence); outAcquireFence->reset(acquireFence); return ret; } @@ -59,3 +63,36 @@ UnsignedArea Hwc2TestVirtualDisplay::getDisplayDimension() const { return mDisplayDimension.get(); } + +int Hwc2TestVirtualDisplay::verifyOutputBuffer(const Hwc2TestLayers* testLayers, + const std::vector* allLayers, + const std::set* clearLayers) +{ + int ret = mExpectedBuffer.generateExpectedBuffer(testLayers, allLayers, + clearLayers); + if (ret) + return ret; + + ComparatorResult::get().CompareBuffers(mOutputBuffer.graphicBuffer(), + mExpectedBuffer.graphicBuffer()); + + return 0; +} + +int Hwc2TestVirtualDisplay::writeBuffersToFile(std::string name) +{ + std::ostringstream expectedPath; + std::ostringstream resultPath; + int ret = mkdir(DIR_NAME, DEFFILEMODE); + if (ret && errno != EEXIST) + return ret; + + expectedPath << DIR_NAME << "/expected-" << name << ".png"; + resultPath << DIR_NAME << "/result-" << name << ".png"; + + if (!mExpectedBuffer.writeBufferToFile(expectedPath.str()) || + !mOutputBuffer.writeBufferToFile(resultPath.str())) + return -1; + + return 0; +} diff --git a/services/surfaceflinger/tests/hwc2/Hwc2TestVirtualDisplay.h b/services/surfaceflinger/tests/hwc2/Hwc2TestVirtualDisplay.h index 09420ef629..10c8ef0f1c 100644 --- a/services/surfaceflinger/tests/hwc2/Hwc2TestVirtualDisplay.h +++ b/services/surfaceflinger/tests/hwc2/Hwc2TestVirtualDisplay.h @@ -18,6 +18,7 @@ #define _HWC2_TEST_VIRTUAL_DISPLAY_H #include "Hwc2TestBuffer.h" +#include "Hwc2TestPixelComparator.h" #include "Hwc2TestProperties.h" #define HWC2_INCLUDE_STRINGIFICATION @@ -32,17 +33,22 @@ public: std::string dump() const; - int getBuffer(buffer_handle_t* outHandle, + int getOutputBuffer(buffer_handle_t* outHandle, android::base::unique_fd* outAcquireFence); + int verifyOutputBuffer(const Hwc2TestLayers* testLayers, + const std::vector* allLayers, + const std::set* clearLayers); + + int writeBuffersToFile(std::string name); void reset(); bool advance(); UnsignedArea getDisplayDimension() const; private: - Hwc2TestBuffer mBuffer; - + Hwc2TestOutputBuffer mOutputBuffer; + Hwc2TestExpectedBuffer mExpectedBuffer; Hwc2TestDisplayDimension mDisplayDimension; }; -- GitLab From 88bd1e09b433bf3c962177347da8d98dd48d80f4 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Fri, 25 Aug 2017 15:49:26 -0700 Subject: [PATCH 025/704] cpp: use proper nativehelper headers libnativehelper exports headers under nativehelper. These were available before incorrectly as global headers in order to give access to jni.h. Test: modules using cpp find headers Bug: 63762847 Change-Id: Id60fdbcba4830946183db194b8f65559994cddfa --- opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp | 2 +- opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp | 2 +- opengl/tools/glgen/stubs/gles11/common.cpp | 2 +- opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp b/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp index f6813fdc03..66836b586f 100644 --- a/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp +++ b/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp @@ -21,7 +21,7 @@ #pragma GCC diagnostic ignored "-Wunused-function" #include "jni.h" -#include "JNIHelp.h" +#include #include #include #include diff --git a/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp b/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp index 4df61d3126..fb75d814cf 100644 --- a/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp +++ b/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp @@ -21,7 +21,7 @@ #pragma GCC diagnostic ignored "-Wunused-function" #include "jni.h" -#include "JNIHelp.h" +#include #include #include #include diff --git a/opengl/tools/glgen/stubs/gles11/common.cpp b/opengl/tools/glgen/stubs/gles11/common.cpp index 7062c5751f..2163d7600d 100644 --- a/opengl/tools/glgen/stubs/gles11/common.cpp +++ b/opengl/tools/glgen/stubs/gles11/common.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp b/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp index 026cb371aa..03e16e9444 100644 --- a/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp +++ b/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp @@ -21,7 +21,7 @@ #pragma GCC diagnostic ignored "-Wunused-function" #include "jni.h" -#include "JNIHelp.h" +#include #include #include -- GitLab From c9674336c053c071022279211eeffc6c9e7c1e99 Mon Sep 17 00:00:00 2001 From: chaviw Date: Mon, 28 Aug 2017 12:32:18 -0700 Subject: [PATCH 026/704] Traverse all layers to check if parent exists to add child layer. With the current implementation, when adding a child layer, the code only looks in the layersSortedByZ vector to see if the parent exists. The layersSortedByZ vector only contains the top most layers and not the child layers. The current behavior prevents creating child layers that have a parent that is also a child layer. This won't allow for nested child layers. Instead, traverse the list of layers, which will include looking at the child layers, to see if the parent layer exists. Test: NestedChildren test in Transaction_test Change-Id: I6c1a773b65e450010733f74f459fc327c7af3aea --- services/surfaceflinger/SurfaceFlinger.cpp | 9 ++++++++- .../surfaceflinger/tests/Transaction_test.cpp | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 4b079d6fbb..2a9f923d3e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2777,7 +2777,14 @@ status_t SurfaceFlinger::addClientLayer(const sp& client, if (parent == nullptr) { mCurrentState.layersSortedByZ.add(lbc); } else { - if (mCurrentState.layersSortedByZ.indexOf(parent) < 0) { + bool found = false; + mCurrentState.traverseInZOrder([&](Layer* layer) { + if (layer == parent.get()) { + found = true; + } + }); + + if (!found) { ALOGE("addClientLayer called with a removed parent"); return NAME_NOT_FOUND; } diff --git a/services/surfaceflinger/tests/Transaction_test.cpp b/services/surfaceflinger/tests/Transaction_test.cpp index aef5a5f93b..d80ed92838 100644 --- a/services/surfaceflinger/tests/Transaction_test.cpp +++ b/services/surfaceflinger/tests/Transaction_test.cpp @@ -1152,4 +1152,19 @@ TEST_F(ChildLayerTest, ReparentChild) { } } +TEST_F(ChildLayerTest, NestedChildren) { + sp grandchild = mComposerClient->createSurface( + String8("Grandchild surface"), + 10, 10, PIXEL_FORMAT_RGBA_8888, + 0, mChild.get()); + fillSurfaceRGBA8(grandchild, 50, 50, 50); + + { + ScreenCapture::captureScreen(&mCapture); + // Expect the grandchild to begin at 64, 64 because it's a child of mChild layer + // which begins at 64, 64 + mCapture->checkPixel(64, 64, 50, 50, 50); + } +} + } -- GitLab From 278bfee01d324f3549882519726e59107c4275f6 Mon Sep 17 00:00:00 2001 From: George Burgess IV Date: Tue, 29 Aug 2017 14:45:23 -0700 Subject: [PATCH 027/704] vr: fix an incorrect format string. size_t should use %zu, not %d. This fixes a warning from clang. Bug: 64487164 Test: mma. Warning is gone. Change-Id: Id43cac8948091c91249bdd68e2e5b1a1fcdab814 --- libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp index f9f87ff1b8..6b86433a09 100644 --- a/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp +++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp @@ -429,7 +429,7 @@ pdx::Status BufferHubQueue::FreeAllBuffers() { if (!status) { ALOGE( "ProducerQueue::FreeAllBuffers: Failed to remove buffer at " - "slot=%d.", + "slot=%zu.", slot); last_error = status.error_status(); } -- GitLab From b334e6c1e949961da674afa244a66455bff9184e Mon Sep 17 00:00:00 2001 From: Ivan Lozano Date: Tue, 8 Aug 2017 12:51:42 -0700 Subject: [PATCH 028/704] Remove IPlayer.h, use AIDL generated interface. Part of migrating our binder interfaces to AIDL, this removes the IPlayer implementation and uses the new aidl generated binder interface library. Test: run cts --module CtsMediaTestCases Bug: 64223827 Change-Id: I5ded5e15681165503d6e2f5a24233add79373f9c --- include/audiomanager/IPlayer.h | 69 ----------- services/audiomanager/Android.bp | 1 - services/audiomanager/IPlayer.cpp | 189 ------------------------------ 3 files changed, 259 deletions(-) delete mode 100644 include/audiomanager/IPlayer.h delete mode 100644 services/audiomanager/IPlayer.cpp diff --git a/include/audiomanager/IPlayer.h b/include/audiomanager/IPlayer.h deleted file mode 100644 index de5c1c7b64..0000000000 --- a/include/audiomanager/IPlayer.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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. - */ - -#ifndef ANDROID_IPLAYER_H -#define ANDROID_IPLAYER_H - -#include -#include - -#include -#include -#include -#include - -namespace android { - -// ---------------------------------------------------------------------------- - -class IPlayer : public IInterface -{ -public: - DECLARE_META_INTERFACE(Player); - - virtual void start() = 0; - - virtual void pause() = 0; - - virtual void stop() = 0; - - virtual void setVolume(float vol) = 0; - - virtual void setPan(float pan) = 0; - - virtual void setStartDelayMs(int delayMs) = 0; - - virtual void applyVolumeShaper( - const sp& configuration, - const sp& operation) = 0; -}; - -// ---------------------------------------------------------------------------- - -class BnPlayer : public BnInterface -{ -public: - virtual status_t onTransact( uint32_t code, - const Parcel& data, - Parcel* reply, - uint32_t flags = 0); -}; - -// ---------------------------------------------------------------------------- - -}; // namespace android - -#endif // ANDROID_IPLAYER_H diff --git a/services/audiomanager/Android.bp b/services/audiomanager/Android.bp index 22b084abc6..12ad47e15a 100644 --- a/services/audiomanager/Android.bp +++ b/services/audiomanager/Android.bp @@ -3,7 +3,6 @@ cc_library_shared { srcs: [ "IAudioManager.cpp", - "IPlayer.cpp", ], shared_libs: [ diff --git a/services/audiomanager/IPlayer.cpp b/services/audiomanager/IPlayer.cpp deleted file mode 100644 index e8a9c3472f..0000000000 --- a/services/audiomanager/IPlayer.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/* -** -** Copyright 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_TAG "IPlayer" -//#define LOG_NDEBUG 0 -#include - -#include -#include - -#include - -#include - -namespace android { - -enum { - START = IBinder::FIRST_CALL_TRANSACTION, - PAUSE = IBinder::FIRST_CALL_TRANSACTION + 1, - STOP = IBinder::FIRST_CALL_TRANSACTION + 2, - SET_VOLUME = IBinder::FIRST_CALL_TRANSACTION + 3, - SET_PAN = IBinder::FIRST_CALL_TRANSACTION + 4, - SET_START_DELAY_MS = IBinder::FIRST_CALL_TRANSACTION + 5, - APPLY_VOLUME_SHAPER = IBinder::FIRST_CALL_TRANSACTION + 6, -}; - -class BpPlayer : public BpInterface -{ -public: - explicit BpPlayer(const sp& impl) - : BpInterface(impl) - { - } - - virtual void start() - { - Parcel data, reply; - data.writeInterfaceToken(IPlayer::getInterfaceDescriptor()); - remote()->transact(START, data, &reply); - } - - virtual void pause() - { - Parcel data, reply; - data.writeInterfaceToken(IPlayer::getInterfaceDescriptor()); - remote()->transact(PAUSE, data, &reply); - } - - virtual void stop() - { - Parcel data, reply; - data.writeInterfaceToken(IPlayer::getInterfaceDescriptor()); - remote()->transact(STOP, data, &reply); - } - - virtual void setVolume(float vol) - { - Parcel data, reply; - data.writeInterfaceToken(IPlayer::getInterfaceDescriptor()); - data.writeFloat(vol); - remote()->transact(SET_VOLUME, data, &reply); - } - - virtual void setPan(float pan) - { - Parcel data, reply; - data.writeInterfaceToken(IPlayer::getInterfaceDescriptor()); - data.writeFloat(pan); - remote()->transact(SET_PAN, data, &reply); - } - - virtual void setStartDelayMs(int32_t delayMs) { - Parcel data, reply; - data.writeInterfaceToken(IPlayer::getInterfaceDescriptor()); - data.writeInt32(delayMs); - remote()->transact(SET_START_DELAY_MS, data, &reply); - } - - virtual void applyVolumeShaper( - const sp& configuration, - const sp& operation) { - Parcel data, reply; - data.writeInterfaceToken(IPlayer::getInterfaceDescriptor()); - - status_t status = configuration.get() == nullptr - ? data.writeInt32(0) - : data.writeInt32(1) - ?: configuration->writeToParcel(&data); - if (status != NO_ERROR) { - ALOGW("applyVolumeShaper failed configuration parceling: %d", status); - return; // ignore error - } - - status = operation.get() == nullptr - ? status = data.writeInt32(0) - : data.writeInt32(1) - ?: operation->writeToParcel(&data); - if (status != NO_ERROR) { - ALOGW("applyVolumeShaper failed operation parceling: %d", status); - return; // ignore error - } - - status = remote()->transact(APPLY_VOLUME_SHAPER, data, &reply); - - ALOGW_IF(status != NO_ERROR, "applyVolumeShaper failed transact: %d", status); - return; // one way transaction, ignore error - } -}; - -IMPLEMENT_META_INTERFACE(Player, "android.media.IPlayer"); - -// ---------------------------------------------------------------------- - -status_t BnPlayer::onTransact( - uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) -{ - switch (code) { - case START: { - CHECK_INTERFACE(IPlayer, data, reply); - start(); - return NO_ERROR; - } break; - case PAUSE: { - CHECK_INTERFACE(IPlayer, data, reply); - pause(); - return NO_ERROR; - } - case STOP: { - CHECK_INTERFACE(IPlayer, data, reply); - stop(); - return NO_ERROR; - } break; - case SET_VOLUME: { - CHECK_INTERFACE(IPlayer, data, reply); - setVolume(data.readFloat()); - return NO_ERROR; - } break; - case SET_PAN: { - CHECK_INTERFACE(IPlayer, data, reply); - setPan(data.readFloat()); - return NO_ERROR; - } break; - case SET_START_DELAY_MS: { - CHECK_INTERFACE(IPlayer, data, reply); - setStartDelayMs(data.readInt32()); - return NO_ERROR; - } break; - case APPLY_VOLUME_SHAPER: { - CHECK_INTERFACE(IPlayer, data, reply); - sp configuration; - sp operation; - - int32_t present; - status_t status = data.readInt32(&present); - if (status == NO_ERROR && present != 0) { - configuration = new VolumeShaper::Configuration(); - status = configuration->readFromParcel(data); - } - status = status ?: data.readInt32(&present); - if (status == NO_ERROR && present != 0) { - operation = new VolumeShaper::Operation(); - status = operation->readFromParcel(data); - } - if (status == NO_ERROR) { - // one way transaction, no error returned - applyVolumeShaper(configuration, operation); - } - return NO_ERROR; - } break; - default: - return BBinder::onTransact(code, data, reply, flags); - } -} - -} // namespace android -- GitLab From e5afdb57e970f5813691ce9b0b5c7dd23faacfa0 Mon Sep 17 00:00:00 2001 From: Shubham Ajmera Date: Fri, 25 Aug 2017 13:07:44 -0700 Subject: [PATCH 029/704] Drop capabilities in reconcileSecondaryDexFiles ... while unlinking oat files. Test: adb shell cmd package reconcile-secondary-dex-files \ com.android.google.gms Bug: 64461549 Change-Id: Ib2c59686233faab22088fc40a706736feb9964ee --- cmds/installd/dexopt.cpp | 70 ++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp index 8c75ee5739..6159207998 100644 --- a/cmds/installd/dexopt.cpp +++ b/cmds/installd/dexopt.cpp @@ -1820,42 +1820,56 @@ bool reconcile_secondary_dex_file(const std::string& dex_path, return false; } - // The secondary dex does not exist anymore. Clear any generated files. - char oat_path[PKG_PATH_MAX]; - char oat_dir[PKG_PATH_MAX]; - char oat_isa_dir[PKG_PATH_MAX]; - bool result = true; - for (size_t i = 0; i < isas.size(); i++) { - if (!create_secondary_dex_oat_layout(dex_path, isas[i], oat_dir, oat_isa_dir, oat_path)) { - LOG(ERROR) << "Could not create secondary odex layout: " << dex_path; - result = false; - continue; - } + // As a security measure we want to unlink art artifacts with the reduced capabilities + // of the package user id. So we fork and drop capabilities in the child. + pid_t pid = fork(); + if (pid == 0) { + // The secondary dex does not exist anymore. Clear any generated files. + char oat_path[PKG_PATH_MAX]; + char oat_dir[PKG_PATH_MAX]; + char oat_isa_dir[PKG_PATH_MAX]; + bool result = true; + /* child -- drop privileges before continuing */ + drop_capabilities(uid); + for (size_t i = 0; i < isas.size(); i++) { + if (!create_secondary_dex_oat_layout(dex_path, + isas[i], + oat_dir, + oat_isa_dir, + oat_path)) { + LOG(ERROR) << "Could not create secondary odex layout: " + << dex_path; + result = false; + continue; + } - // Delete oat/vdex/art files. - result = unlink_if_exists(oat_path) && result; - result = unlink_if_exists(create_vdex_filename(oat_path)) && result; - result = unlink_if_exists(create_image_filename(oat_path)) && result; + // Delete oat/vdex/art files. + result = unlink_if_exists(oat_path) && result; + result = unlink_if_exists(create_vdex_filename(oat_path)) && result; + result = unlink_if_exists(create_image_filename(oat_path)) && result; - // Delete profiles. - std::string current_profile = create_current_profile_path( + // Delete profiles. + std::string current_profile = create_current_profile_path( multiuser_get_user_id(uid), dex_path, /*is_secondary*/true); - std::string reference_profile = create_reference_profile_path( + std::string reference_profile = create_reference_profile_path( dex_path, /*is_secondary*/true); - result = unlink_if_exists(current_profile) && result; - result = unlink_if_exists(reference_profile) && result; + result = unlink_if_exists(current_profile) && result; + result = unlink_if_exists(reference_profile) && result; - // We upgraded once the location of current profile for secondary dex files. - // Check for any previous left-overs and remove them as well. - std::string old_current_profile = dex_path + ".prof"; - result = unlink_if_exists(old_current_profile); + // We upgraded once the location of current profile for secondary dex files. + // Check for any previous left-overs and remove them as well. + std::string old_current_profile = dex_path + ".prof"; + result = unlink_if_exists(old_current_profile); - // Try removing the directories as well, they might be empty. - result = rmdir_if_empty(oat_isa_dir) && result; - result = rmdir_if_empty(oat_dir) && result; + // Try removing the directories as well, they might be empty. + result = rmdir_if_empty(oat_isa_dir) && result; + result = rmdir_if_empty(oat_dir) && result; + } + result ? _exit(0) : _exit(1); } - return result; + int return_code = wait_child(pid); + return return_code == 0; } // Helper for move_ab, so that we can have common failure-case cleanup. -- GitLab From d4b607ef71cf2d76206151ce321239cad40cdd68 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 13 Jun 2017 12:21:59 +0100 Subject: [PATCH 030/704] Native test for VelocityTracker Adding VelocityTracker native tests for checking the velocity output for certain inputs. Adding two types of test cases: 1) Generated manually. These are simple test cases like linear motion, repeated coordinates, etc. 2) Generated by recording an actual fling. Used to reproduce the swordfish fling bug referenced below. 3) Recorded flings on sailfish for additional tests. Currently, the make target for the test is libinput_tests_VelocityTracker_test. Bug: 35412046 Test: adb shell /data/nativetest64/libinput_tests/VelocityTracker_test Change-Id: I2f71100f8dc6216667b0698270a27c98cd401565 --- libs/input/VelocityTracker.cpp | 2 + libs/input/tests/Android.bp | 2 + libs/input/tests/VelocityTracker_test.cpp | 664 ++++++++++++++++++++++ 3 files changed, 668 insertions(+) create mode 100644 libs/input/tests/VelocityTracker_test.cpp diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp index a914408297..f5b896b803 100644 --- a/libs/input/VelocityTracker.cpp +++ b/libs/input/VelocityTracker.cpp @@ -75,7 +75,9 @@ static std::string vectorToString(const float* a, uint32_t m) { str += " ]"; return str; } +#endif +#if DEBUG_STRATEGY static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) { std::string str; str = "["; diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 029a42091e..e3e1dec00b 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -6,6 +6,7 @@ cc_test { "InputChannel_test.cpp", "InputEvent_test.cpp", "InputPublisherAndConsumer_test.cpp", + "VelocityTracker_test.cpp", ], shared_libs: [ "libinput", @@ -13,6 +14,7 @@ cc_test { "libutils", "libbinder", "libui", + "libbase", ] } diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp new file mode 100644 index 0000000000..43b6012e0d --- /dev/null +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -0,0 +1,664 @@ +/* + * 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_TAG "VelocityTracker_test" + +#include + +#include +#include +#include

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 diff --git a/docs/images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png b/docs/images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png index c309ac5ea887eb36bc8bc1082d418a81fc1b5200..4b45e3aa9cc7ab20e962370eba644a061d8fa578 100644 GIT binary patch literal 15510 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 N@O1TaS?83{1OV6|GeQ6W literal 22690 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 RZUzPh22WQ%mvv4FO#sv4KR5sY diff --git a/docs/images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png b/docs/images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png index 414fad49a919e07e185a5530dfff3979f8ca23a9..748d1a29626e3425008c2a5829b457fae74dd745 100644 GIT binary patch literal 15016 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 literal 21822 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 tk?FnXkPO$pw4mNKYs0kN= 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 X#C<58A@2YK0|SGntDnm{r-UW|*V;E; literal 32384 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 X3)n6GEWEgTe~DWM4fKe~@2 diff --git a/docs/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png b/docs/images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png index 4ce2125f3c911c10b2e92094087154b7694715e1..9776874bb0191c6e2274973b36703f48d58f8ebe 100644 GIT binary patch literal 22087 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 literal 32315 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 C+x6oB -- GitLab