| /* | |
| * 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 <log/log_main.h> | |
| #include <private/android/AHardwareBufferHelpers.h> | |
| // TODO: this should be including apex instead. | |
| #include <system/window.h> | |
| #include <vndk/window.h> | |
| 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{ | |
| .width = 1, | |
| .height = 1, | |
| .layers = 1, | |
| .format = mFormat, | |
| .usage = mUsage, | |
| .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<ReliableSurface*>(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<ReliableSurface*>(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<ReliableSurface*>(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<ReliableSurface*>(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<AHardwareBuffer_Format>(va_arg(args, int32_t)); | |
| break; | |
| case ANATIVEWINDOW_PERFORM_SET_BUFFERS_FORMAT: | |
| rs->mFormat = static_cast<AHardwareBuffer_Format>(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<ReliableSurface*>(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 |