blob: 6c6a487b67af65ad91bf569c3f5e9011dcdf89ed [file] [log] [blame]
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// #define LOG_NDEBUG 0
#undef LOG_TAG
#define LOG_TAG "TransactionHandler"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include <cutils/trace.h>
#include <utils/Log.h>
#include "TransactionHandler.h"
namespace android {
void TransactionHandler::queueTransaction(TransactionState&& state) {
mLocklessTransactionQueue.push(std::move(state));
mPendingTransactionCount.fetch_add(1);
ATRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
}
std::vector<TransactionState> TransactionHandler::flushTransactions() {
while (!mLocklessTransactionQueue.isEmpty()) {
auto maybeTransaction = mLocklessTransactionQueue.pop();
if (!maybeTransaction.has_value()) {
break;
}
auto transaction = maybeTransaction.value();
mPendingTransactionQueues[transaction.applyToken].emplace(std::move(transaction));
}
// Collect transaction that are ready to be applied.
std::vector<TransactionState> transactions;
TransactionFlushState flushState;
flushState.queueProcessTime = systemTime();
// Transactions with a buffer pending on a barrier may be on a different applyToken
// than the transaction which satisfies our barrier. In fact this is the exact use case
// that the primitive is designed for. This means we may first process
// the barrier dependent transaction, determine it ineligible to complete
// and then satisfy in a later inner iteration of flushPendingTransactionQueues.
// The barrier dependent transaction was eligible to be presented in this frame
// but we would have prevented it without case. To fix this we continually
// loop through flushPendingTransactionQueues until we perform an iteration
// where the number of transactionsPendingBarrier doesn't change. This way
// we can continue to resolve dependency chains of barriers as far as possible.
int lastTransactionsPendingBarrier = 0;
int transactionsPendingBarrier = 0;
do {
lastTransactionsPendingBarrier = transactionsPendingBarrier;
// Collect transactions that are ready to be applied.
transactionsPendingBarrier = flushPendingTransactionQueues(transactions, flushState);
} while (lastTransactionsPendingBarrier != transactionsPendingBarrier);
mPendingTransactionCount.fetch_sub(transactions.size());
ATRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
return transactions;
}
TransactionHandler::TransactionReadiness TransactionHandler::applyFilters(
TransactionFlushState& flushState) {
auto ready = TransactionReadiness::Ready;
for (auto& filter : mTransactionReadyFilters) {
auto perFilterReady = filter(flushState);
switch (perFilterReady) {
case TransactionReadiness::NotReady:
case TransactionReadiness::NotReadyBarrier:
return perFilterReady;
case TransactionReadiness::ReadyUnsignaled:
case TransactionReadiness::ReadyUnsignaledSingle:
// If one of the filters allows latching an unsignaled buffer, latch this ready
// state.
ready = perFilterReady;
break;
case TransactionReadiness::Ready:
continue;
}
}
return ready;
}
int TransactionHandler::flushPendingTransactionQueues(std::vector<TransactionState>& transactions,
TransactionFlushState& flushState) {
int transactionsPendingBarrier = 0;
auto it = mPendingTransactionQueues.begin();
while (it != mPendingTransactionQueues.end()) {
auto& queue = it->second;
IBinder* queueToken = it->first.get();
// if we have already flushed a transaction with an unsignaled buffer then stop queue
// processing
if (std::find(flushState.queuesWithUnsignaledBuffers.begin(),
flushState.queuesWithUnsignaledBuffers.end(),
queueToken) != flushState.queuesWithUnsignaledBuffers.end()) {
continue;
}
while (!queue.empty()) {
auto& transaction = queue.front();
flushState.transaction = &transaction;
auto ready = applyFilters(flushState);
if (ready == TransactionReadiness::NotReadyBarrier) {
transactionsPendingBarrier++;
break;
} else if (ready == TransactionReadiness::NotReady) {
break;
}
// Transaction is ready move it from the pending queue.
flushState.firstTransaction = false;
removeFromStalledTransactions(transaction.id);
transactions.emplace_back(std::move(transaction));
queue.pop();
// If the buffer is unsignaled, then we don't want to signal other transactions using
// the buffer as a barrier.
auto& readyToApplyTransaction = transactions.back();
if (ready == TransactionReadiness::Ready) {
readyToApplyTransaction.traverseStatesWithBuffers([&](const layer_state_t& state) {
const bool frameNumberChanged = state.bufferData->flags.test(
BufferData::BufferDataChange::frameNumberChanged);
if (frameNumberChanged) {
flushState.bufferLayersReadyToPresent
.emplace_or_replace(state.surface.get(),
state.bufferData->frameNumber);
} else {
// Barrier function only used for BBQ which always includes a frame number.
// This value only used for barrier logic.
flushState.bufferLayersReadyToPresent
.emplace_or_replace(state.surface.get(),
std::numeric_limits<uint64_t>::max());
}
});
} else if (ready == TransactionReadiness::ReadyUnsignaledSingle) {
// Track queues with a flushed unsingaled buffer.
flushState.queuesWithUnsignaledBuffers.emplace_back(queueToken);
break;
}
}
if (queue.empty()) {
it = mPendingTransactionQueues.erase(it);
} else {
it = std::next(it, 1);
}
}
return transactionsPendingBarrier;
}
void TransactionHandler::addTransactionReadyFilter(TransactionFilter&& filter) {
mTransactionReadyFilters.emplace_back(std::move(filter));
}
bool TransactionHandler::hasPendingTransactions() {
return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty();
}
void TransactionHandler::onTransactionQueueStalled(const TransactionState& transaction,
sp<ITransactionCompletedListener>& listener) {
if (std::find(mStalledTransactions.begin(), mStalledTransactions.end(), transaction.id) !=
mStalledTransactions.end()) {
return;
}
mStalledTransactions.push_back(transaction.id);
listener->onTransactionQueueStalled();
}
void TransactionHandler::removeFromStalledTransactions(uint64_t id) {
auto it = std::find(mStalledTransactions.begin(), mStalledTransactions.end(), id);
if (it != mStalledTransactions.end()) {
mStalledTransactions.erase(it);
}
}
} // namespace android