| /* | |
| * 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 <private/android/AHardwareBufferHelpers.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; | |
| // TODO: Make surface less protected | |
| // This exists because perform is a varargs, and ANativeWindow has no va_list perform. | |
| // So wrapping/chaining that is hard. Telling the compiler to ignore protected is easy, so we do | |
| // that instead | |
| struct SurfaceExposer : Surface { | |
| // Make warnings happy | |
| SurfaceExposer() = delete; | |
| using Surface::setBufferCount; | |
| using Surface::setSwapInterval; | |
| using Surface::dequeueBuffer; | |
| using Surface::queueBuffer; | |
| using Surface::cancelBuffer; | |
| using Surface::lockBuffer_DEPRECATED; | |
| using Surface::perform; | |
| }; | |
| #define callProtected(surface, func, ...) ((*surface).*&SurfaceExposer::func)(__VA_ARGS__) | |
| ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) { | |
| LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr"); | |
| ANativeWindow::setSwapInterval = hook_setSwapInterval; | |
| ANativeWindow::dequeueBuffer = hook_dequeueBuffer; | |
| ANativeWindow::cancelBuffer = hook_cancelBuffer; | |
| ANativeWindow::queueBuffer = hook_queueBuffer; | |
| ANativeWindow::query = hook_query; | |
| ANativeWindow::perform = hook_perform; | |
| ANativeWindow::dequeueBuffer_DEPRECATED = hook_dequeueBuffer_DEPRECATED; | |
| ANativeWindow::cancelBuffer_DEPRECATED = hook_cancelBuffer_DEPRECATED; | |
| ANativeWindow::lockBuffer_DEPRECATED = hook_lockBuffer_DEPRECATED; | |
| ANativeWindow::queueBuffer_DEPRECATED = hook_queueBuffer_DEPRECATED; | |
| } | |
| ReliableSurface::~ReliableSurface() { | |
| clearReservedBuffer(); | |
| } | |
| void ReliableSurface::perform(int operation, va_list args) { | |
| std::lock_guard _lock{mMutex}; | |
| switch (operation) { | |
| case NATIVE_WINDOW_SET_USAGE: | |
| mUsage = va_arg(args, uint32_t); | |
| break; | |
| case NATIVE_WINDOW_SET_USAGE64: | |
| mUsage = va_arg(args, uint64_t); | |
| break; | |
| case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY: | |
| /* width */ va_arg(args, uint32_t); | |
| /* height */ va_arg(args, uint32_t); | |
| mFormat = va_arg(args, PixelFormat); | |
| break; | |
| case NATIVE_WINDOW_SET_BUFFERS_FORMAT: | |
| mFormat = va_arg(args, PixelFormat); | |
| break; | |
| } | |
| } | |
| int ReliableSurface::reserveNext() { | |
| { | |
| std::lock_guard _lock{mMutex}; | |
| if (mReservedBuffer) { | |
| ALOGW("reserveNext called but there was already a buffer reserved?"); | |
| return OK; | |
| } | |
| if (mInErrorState) { | |
| return UNKNOWN_ERROR; | |
| } | |
| if (mHasDequeuedBuffer) { | |
| return OK; | |
| } | |
| if constexpr (DISABLE_BUFFER_PREFETCH) { | |
| 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; | |
| int result = callProtected(mSurface, dequeueBuffer, &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) { | |
| callProtected(mSurface, cancelBuffer, buffer, releaseFd); | |
| } | |
| } | |
| int ReliableSurface::cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd) { | |
| clearReservedBuffer(); | |
| if (isFallbackBuffer(buffer)) { | |
| if (fenceFd > 0) { | |
| close(fenceFd); | |
| } | |
| return OK; | |
| } | |
| int result = callProtected(mSurface, cancelBuffer, buffer, fenceFd); | |
| return result; | |
| } | |
| int ReliableSurface::dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd) { | |
| { | |
| std::lock_guard _lock{mMutex}; | |
| if (mReservedBuffer) { | |
| *buffer = mReservedBuffer; | |
| *fenceFd = mReservedFenceFd.release(); | |
| mReservedBuffer = nullptr; | |
| return OK; | |
| } | |
| } | |
| int result = callProtected(mSurface, dequeueBuffer, buffer, fenceFd); | |
| if (result != OK) { | |
| ALOGW("dequeueBuffer failed, error = %d; switching to fallback", result); | |
| *buffer = acquireFallbackBuffer(); | |
| *fenceFd = -1; | |
| return *buffer ? OK : INVALID_OPERATION; | |
| } else { | |
| std::lock_guard _lock{mMutex}; | |
| mHasDequeuedBuffer = true; | |
| } | |
| return OK; | |
| } | |
| int ReliableSurface::queueBuffer(ANativeWindowBuffer* buffer, int fenceFd) { | |
| clearReservedBuffer(); | |
| if (isFallbackBuffer(buffer)) { | |
| if (fenceFd > 0) { | |
| close(fenceFd); | |
| } | |
| return OK; | |
| } | |
| int result = callProtected(mSurface, queueBuffer, buffer, fenceFd); | |
| return result; | |
| } | |
| 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() { | |
| std::lock_guard _lock{mMutex}; | |
| mInErrorState = true; | |
| if (mScratchBuffer) { | |
| return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); | |
| } | |
| AHardwareBuffer_Desc desc; | |
| desc.usage = mUsage; | |
| desc.format = mFormat; | |
| desc.width = 1; | |
| desc.height = 1; | |
| desc.layers = 1; | |
| desc.rfu0 = 0; | |
| desc.rfu1 = 0; | |
| AHardwareBuffer* newBuffer = nullptr; | |
| int err = AHardwareBuffer_allocate(&desc, &newBuffer); | |
| if (err) { | |
| // Allocate failed, that sucks | |
| ALOGW("Failed to allocate scratch buffer, error=%d", err); | |
| return nullptr; | |
| } | |
| mScratchBuffer.reset(newBuffer); | |
| return AHardwareBuffer_to_ANativeWindowBuffer(newBuffer); | |
| } | |
| Surface* ReliableSurface::getWrapped(const ANativeWindow* window) { | |
| return getSelf(window)->mSurface.get(); | |
| } | |
| int ReliableSurface::hook_setSwapInterval(ANativeWindow* window, int interval) { | |
| return callProtected(getWrapped(window), setSwapInterval, interval); | |
| } | |
| int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, | |
| int* fenceFd) { | |
| return getSelf(window)->dequeueBuffer(buffer, fenceFd); | |
| } | |
| int ReliableSurface::hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, | |
| int fenceFd) { | |
| return getSelf(window)->cancelBuffer(buffer, fenceFd); | |
| } | |
| int ReliableSurface::hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, | |
| int fenceFd) { | |
| return getSelf(window)->queueBuffer(buffer, fenceFd); | |
| } | |
| int ReliableSurface::hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, | |
| ANativeWindowBuffer** buffer) { | |
| ANativeWindowBuffer* buf; | |
| int fenceFd = -1; | |
| int result = window->dequeueBuffer(window, &buf, &fenceFd); | |
| if (result != OK) { | |
| return result; | |
| } | |
| sp<Fence> fence(new Fence(fenceFd)); | |
| int waitResult = fence->waitForever("dequeueBuffer_DEPRECATED"); | |
| if (waitResult != OK) { | |
| ALOGE("dequeueBuffer_DEPRECATED: Fence::wait returned an error: %d", waitResult); | |
| window->cancelBuffer(window, buf, -1); | |
| return waitResult; | |
| } | |
| *buffer = buf; | |
| return result; | |
| } | |
| int ReliableSurface::hook_cancelBuffer_DEPRECATED(ANativeWindow* window, | |
| ANativeWindowBuffer* buffer) { | |
| return window->cancelBuffer(window, buffer, -1); | |
| } | |
| int ReliableSurface::hook_lockBuffer_DEPRECATED(ANativeWindow* window, | |
| ANativeWindowBuffer* buffer) { | |
| // This method is a no-op in Surface as well | |
| return OK; | |
| } | |
| int ReliableSurface::hook_queueBuffer_DEPRECATED(ANativeWindow* window, | |
| ANativeWindowBuffer* buffer) { | |
| return window->queueBuffer(window, buffer, -1); | |
| } | |
| int ReliableSurface::hook_query(const ANativeWindow* window, int what, int* value) { | |
| return getWrapped(window)->query(what, value); | |
| } | |
| int ReliableSurface::hook_perform(ANativeWindow* window, int operation, ...) { | |
| // 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? | |
| getSelf(window)->clearReservedBuffer(); | |
| va_list args; | |
| va_start(args, operation); | |
| int result = callProtected(getWrapped(window), perform, operation, args); | |
| va_end(args); | |
| switch (operation) { | |
| case NATIVE_WINDOW_SET_BUFFERS_FORMAT: | |
| case NATIVE_WINDOW_SET_USAGE: | |
| case NATIVE_WINDOW_SET_USAGE64: | |
| va_start(args, operation); | |
| getSelf(window)->perform(operation, args); | |
| va_end(args); | |
| break; | |
| default: | |
| break; | |
| } | |
| return result; | |
| } | |
| }; // namespace android::uirenderer::renderthread |