/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ReliableSurface.h" #include #include // TODO: this should be including apex instead. #include #include namespace android::uirenderer::renderthread { // TODO: Re-enable after addressing more of the TODO's // With this disabled we won't have a good up-front signal that the surface is no longer valid, // however we can at least handle that reactively post-draw. There's just not a good mechanism // to propagate this error back to the caller constexpr bool DISABLE_BUFFER_PREFETCH = true; ReliableSurface::ReliableSurface(ANativeWindow* window) : mWindow(window) { LOG_ALWAYS_FATAL_IF(!mWindow, "Error, unable to wrap a nullptr"); ANativeWindow_acquire(mWindow); } ReliableSurface::~ReliableSurface() { clearReservedBuffer(); // Clear out the interceptors for proper hygiene. // As a concrete example, if the underlying ANativeWindow is associated with // an EGLSurface that is still in use, then if we don't clear out the // interceptors then we walk into undefined behavior. ANativeWindow_setCancelBufferInterceptor(mWindow, nullptr, nullptr); ANativeWindow_setDequeueBufferInterceptor(mWindow, nullptr, nullptr); ANativeWindow_setQueueBufferInterceptor(mWindow, nullptr, nullptr); ANativeWindow_setPerformInterceptor(mWindow, nullptr, nullptr); ANativeWindow_setQueryInterceptor(mWindow, nullptr, nullptr); ANativeWindow_release(mWindow); } void ReliableSurface::init() { int result = ANativeWindow_setCancelBufferInterceptor(mWindow, hook_cancelBuffer, this); LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set cancelBuffer interceptor: error = %d", result); result = ANativeWindow_setDequeueBufferInterceptor(mWindow, hook_dequeueBuffer, this); LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set dequeueBuffer interceptor: error = %d", result); result = ANativeWindow_setQueueBufferInterceptor(mWindow, hook_queueBuffer, this); LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set queueBuffer interceptor: error = %d", result); result = ANativeWindow_setPerformInterceptor(mWindow, hook_perform, this); LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set perform interceptor: error = %d", result); result = ANativeWindow_setQueryInterceptor(mWindow, hook_query, this); LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set query interceptor: error = %d", result); } int ReliableSurface::reserveNext() { if constexpr (DISABLE_BUFFER_PREFETCH) { return OK; } { std::lock_guard _lock{mMutex}; if (mReservedBuffer) { ALOGW("reserveNext called but there was already a buffer reserved?"); return OK; } if (mBufferQueueState != OK) { return UNKNOWN_ERROR; } if (mHasDequeuedBuffer) { return OK; } } // TODO: Update this to better handle when requested dimensions have changed // Currently the driver does this via query + perform but that's after we've already // reserved a buffer. Should we do that logic instead? Or should we drop // the backing Surface to the ground and go full manual on the IGraphicBufferProducer instead? int fenceFd = -1; ANativeWindowBuffer* buffer = nullptr; // Note that this calls back into our own hooked method. int result = ANativeWindow_dequeueBuffer(mWindow, &buffer, &fenceFd); { std::lock_guard _lock{mMutex}; LOG_ALWAYS_FATAL_IF(mReservedBuffer, "race condition in reserveNext"); mReservedBuffer = buffer; mReservedFenceFd.reset(fenceFd); } return result; } void ReliableSurface::clearReservedBuffer() { ANativeWindowBuffer* buffer = nullptr; int releaseFd = -1; { std::lock_guard _lock{mMutex}; if (mReservedBuffer) { ALOGW("Reserved buffer %p was never used", mReservedBuffer); buffer = mReservedBuffer; releaseFd = mReservedFenceFd.release(); } mReservedBuffer = nullptr; mReservedFenceFd.reset(); mHasDequeuedBuffer = false; } if (buffer) { // Note that clearReservedBuffer may be reentrant here, so // mReservedBuffer must be cleared once we reach here to avoid recursing // forever. ANativeWindow_cancelBuffer(mWindow, buffer, releaseFd); } } bool ReliableSurface::isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const { if (!mScratchBuffer || !windowBuffer) { return false; } ANativeWindowBuffer* scratchBuffer = AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); return windowBuffer == scratchBuffer; } ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer(int error) { std::lock_guard _lock{mMutex}; mBufferQueueState = error; if (mScratchBuffer) { return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); } AHardwareBuffer_Desc desc = AHardwareBuffer_Desc{ .usage = mUsage, .format = mFormat, .width = 1, .height = 1, .layers = 1, .rfu0 = 0, .rfu1 = 0, }; AHardwareBuffer* newBuffer; int result = AHardwareBuffer_allocate(&desc, &newBuffer); if (result != NO_ERROR) { // Allocate failed, that sucks ALOGW("Failed to allocate scratch buffer, error=%d", result); return nullptr; } mScratchBuffer.reset(newBuffer); return AHardwareBuffer_to_ANativeWindowBuffer(newBuffer); } int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window, ANativeWindow_dequeueBufferFn dequeueBuffer, void* data, ANativeWindowBuffer** buffer, int* fenceFd) { ReliableSurface* rs = reinterpret_cast(data); { std::lock_guard _lock{rs->mMutex}; if (rs->mReservedBuffer) { *buffer = rs->mReservedBuffer; *fenceFd = rs->mReservedFenceFd.release(); rs->mReservedBuffer = nullptr; return OK; } } int result = dequeueBuffer(window, buffer, fenceFd); if (result != OK) { ALOGW("dequeueBuffer failed, error = %d; switching to fallback", result); *buffer = rs->acquireFallbackBuffer(result); *fenceFd = -1; return *buffer ? OK : INVALID_OPERATION; } else { std::lock_guard _lock{rs->mMutex}; rs->mHasDequeuedBuffer = true; } return OK; } int ReliableSurface::hook_cancelBuffer(ANativeWindow* window, ANativeWindow_cancelBufferFn cancelBuffer, void* data, ANativeWindowBuffer* buffer, int fenceFd) { ReliableSurface* rs = reinterpret_cast(data); rs->clearReservedBuffer(); if (rs->isFallbackBuffer(buffer)) { if (fenceFd > 0) { close(fenceFd); } return OK; } return cancelBuffer(window, buffer, fenceFd); } int ReliableSurface::hook_queueBuffer(ANativeWindow* window, ANativeWindow_queueBufferFn queueBuffer, void* data, ANativeWindowBuffer* buffer, int fenceFd) { ReliableSurface* rs = reinterpret_cast(data); rs->clearReservedBuffer(); if (rs->isFallbackBuffer(buffer)) { if (fenceFd > 0) { close(fenceFd); } return OK; } return queueBuffer(window, buffer, fenceFd); } int ReliableSurface::hook_perform(ANativeWindow* window, ANativeWindow_performFn perform, void* data, int operation, va_list args) { // Drop the reserved buffer if there is one since this (probably) mutated buffer dimensions // TODO: Filter to things that only affect the reserved buffer // TODO: Can we mutate the reserved buffer in some cases? ReliableSurface* rs = reinterpret_cast(data); rs->clearReservedBuffer(); va_list argsCopy; va_copy(argsCopy, args); int result = perform(window, operation, argsCopy); { std::lock_guard _lock{rs->mMutex}; switch (operation) { case ANATIVEWINDOW_PERFORM_SET_USAGE: rs->mUsage = va_arg(args, uint32_t); break; case ANATIVEWINDOW_PERFORM_SET_USAGE64: rs->mUsage = va_arg(args, uint64_t); break; case ANATIVEWINDOW_PERFORM_SET_BUFFERS_GEOMETRY: /* width */ va_arg(args, uint32_t); /* height */ va_arg(args, uint32_t); rs->mFormat = static_cast(va_arg(args, int32_t)); break; case ANATIVEWINDOW_PERFORM_SET_BUFFERS_FORMAT: rs->mFormat = static_cast(va_arg(args, int32_t)); break; case NATIVE_WINDOW_SET_BUFFER_COUNT: size_t bufferCount = va_arg(args, size_t); if (bufferCount >= rs->mExpectedBufferCount) { rs->mDidSetExtraBuffers = true; } else { ALOGD("HOOK FAILED! Expected %zd got = %zd", rs->mExpectedBufferCount, bufferCount); } break; } } return result; } int ReliableSurface::hook_query(const ANativeWindow *window, ANativeWindow_queryFn query, void *data, int what, int *value) { ReliableSurface* rs = reinterpret_cast(data); int result = query(window, what, value); if (what == ANATIVEWINDOW_QUERY_MIN_UNDEQUEUED_BUFFERS && result == OK) { std::lock_guard _lock{rs->mMutex}; rs->mExpectedBufferCount = *value + 2; } return result; } }; // namespace android::uirenderer::renderthread