| /* |
| * Copyright 2020 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 ATRACE_TAG ATRACE_TAG_GRAPHICS |
| |
| #include "RenderEngineThreaded.h" |
| |
| #include <sched.h> |
| #include <chrono> |
| #include <future> |
| |
| #include <android-base/stringprintf.h> |
| #include <private/gui/SyncFeatures.h> |
| #include <processgroup/processgroup.h> |
| #include <utils/Trace.h> |
| |
| #include "gl/GLESRenderEngine.h" |
| |
| using namespace std::chrono_literals; |
| |
| namespace android { |
| namespace renderengine { |
| namespace threaded { |
| |
| std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory, |
| RenderEngineType type) { |
| return std::make_unique<RenderEngineThreaded>(std::move(factory), type); |
| } |
| |
| RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type) |
| : RenderEngine(type) { |
| ATRACE_CALL(); |
| |
| std::lock_guard lockThread(mThreadMutex); |
| mThread = std::thread(&RenderEngineThreaded::threadMain, this, factory); |
| } |
| |
| RenderEngineThreaded::~RenderEngineThreaded() { |
| mRunning = false; |
| mCondition.notify_one(); |
| |
| if (mThread.joinable()) { |
| mThread.join(); |
| } |
| } |
| |
| status_t RenderEngineThreaded::setSchedFifo(bool enabled) { |
| static constexpr int kFifoPriority = 2; |
| static constexpr int kOtherPriority = 0; |
| |
| struct sched_param param = {0}; |
| int sched_policy; |
| if (enabled) { |
| sched_policy = SCHED_FIFO; |
| param.sched_priority = kFifoPriority; |
| } else { |
| sched_policy = SCHED_OTHER; |
| param.sched_priority = kOtherPriority; |
| } |
| |
| if (sched_setscheduler(0, sched_policy, ¶m) != 0) { |
| return -errno; |
| } |
| return NO_ERROR; |
| } |
| |
| // NO_THREAD_SAFETY_ANALYSIS is because std::unique_lock presently lacks thread safety annotations. |
| void RenderEngineThreaded::threadMain(CreateInstanceFactory factory) NO_THREAD_SAFETY_ANALYSIS { |
| ATRACE_CALL(); |
| |
| if (!SetTaskProfiles(0, {"SFRenderEnginePolicy"})) { |
| ALOGW("Failed to set render-engine task profile!"); |
| } |
| |
| if (setSchedFifo(true) != NO_ERROR) { |
| ALOGW("Couldn't set SCHED_FIFO"); |
| } |
| |
| mRenderEngine = factory(); |
| mIsProtected = mRenderEngine->isProtected(); |
| |
| pthread_setname_np(pthread_self(), mThreadName); |
| |
| { |
| std::scoped_lock lock(mInitializedMutex); |
| mIsInitialized = true; |
| } |
| mInitializedCondition.notify_all(); |
| |
| while (mRunning) { |
| const auto getNextTask = [this]() -> std::optional<Work> { |
| std::scoped_lock lock(mThreadMutex); |
| if (!mFunctionCalls.empty()) { |
| Work task = mFunctionCalls.front(); |
| mFunctionCalls.pop(); |
| return std::make_optional<Work>(task); |
| } |
| return std::nullopt; |
| }; |
| |
| const auto task = getNextTask(); |
| |
| if (task) { |
| (*task)(*mRenderEngine); |
| } |
| |
| std::unique_lock<std::mutex> lock(mThreadMutex); |
| mCondition.wait(lock, [this]() REQUIRES(mThreadMutex) { |
| return !mRunning || !mFunctionCalls.empty(); |
| }); |
| } |
| |
| // we must release the RenderEngine on the thread that created it |
| mRenderEngine.reset(); |
| } |
| |
| void RenderEngineThreaded::waitUntilInitialized() const { |
| std::unique_lock<std::mutex> lock(mInitializedMutex); |
| mInitializedCondition.wait(lock, [=] { return mIsInitialized; }); |
| } |
| |
| std::future<void> RenderEngineThreaded::primeCache() { |
| const auto resultPromise = std::make_shared<std::promise<void>>(); |
| std::future<void> resultFuture = resultPromise->get_future(); |
| ATRACE_CALL(); |
| // This function is designed so it can run asynchronously, so we do not need to wait |
| // for the futures. |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([resultPromise](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::primeCache"); |
| if (setSchedFifo(false) != NO_ERROR) { |
| ALOGW("Couldn't set SCHED_OTHER for primeCache"); |
| } |
| |
| instance.primeCache(); |
| resultPromise->set_value(); |
| |
| if (setSchedFifo(true) != NO_ERROR) { |
| ALOGW("Couldn't set SCHED_FIFO for primeCache"); |
| } |
| }); |
| } |
| mCondition.notify_one(); |
| |
| return resultFuture; |
| } |
| |
| void RenderEngineThreaded::dump(std::string& result) { |
| std::promise<std::string> resultPromise; |
| std::future<std::string> resultFuture = resultPromise.get_future(); |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([&resultPromise, &result](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::dump"); |
| std::string localResult = result; |
| instance.dump(localResult); |
| resultPromise.set_value(std::move(localResult)); |
| }); |
| } |
| mCondition.notify_one(); |
| // Note: This is an rvalue. |
| result.assign(resultFuture.get()); |
| } |
| |
| void RenderEngineThreaded::genTextures(size_t count, uint32_t* names) { |
| ATRACE_CALL(); |
| std::promise<void> resultPromise; |
| std::future<void> resultFuture = resultPromise.get_future(); |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([&resultPromise, count, names](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::genTextures"); |
| instance.genTextures(count, names); |
| resultPromise.set_value(); |
| }); |
| } |
| mCondition.notify_one(); |
| resultFuture.wait(); |
| } |
| |
| void RenderEngineThreaded::deleteTextures(size_t count, uint32_t const* names) { |
| ATRACE_CALL(); |
| std::promise<void> resultPromise; |
| std::future<void> resultFuture = resultPromise.get_future(); |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([&resultPromise, count, &names](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::deleteTextures"); |
| instance.deleteTextures(count, names); |
| resultPromise.set_value(); |
| }); |
| } |
| mCondition.notify_one(); |
| resultFuture.wait(); |
| } |
| |
| void RenderEngineThreaded::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, |
| bool isRenderable) { |
| ATRACE_CALL(); |
| // This function is designed so it can run asynchronously, so we do not need to wait |
| // for the futures. |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([=](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::mapExternalTextureBuffer"); |
| instance.mapExternalTextureBuffer(buffer, isRenderable); |
| }); |
| } |
| mCondition.notify_one(); |
| } |
| |
| void RenderEngineThreaded::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) { |
| ATRACE_CALL(); |
| // This function is designed so it can run asynchronously, so we do not need to wait |
| // for the futures. |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([=](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::unmapExternalTextureBuffer"); |
| instance.unmapExternalTextureBuffer(buffer); |
| }); |
| } |
| mCondition.notify_one(); |
| } |
| |
| size_t RenderEngineThreaded::getMaxTextureSize() const { |
| waitUntilInitialized(); |
| return mRenderEngine->getMaxTextureSize(); |
| } |
| |
| size_t RenderEngineThreaded::getMaxViewportDims() const { |
| waitUntilInitialized(); |
| return mRenderEngine->getMaxViewportDims(); |
| } |
| |
| bool RenderEngineThreaded::isProtected() const { |
| waitUntilInitialized(); |
| std::lock_guard lock(mThreadMutex); |
| return mIsProtected; |
| } |
| |
| bool RenderEngineThreaded::supportsProtectedContent() const { |
| waitUntilInitialized(); |
| return mRenderEngine->supportsProtectedContent(); |
| } |
| |
| void RenderEngineThreaded::useProtectedContext(bool useProtectedContext) { |
| if (isProtected() == useProtectedContext || |
| (useProtectedContext && !supportsProtectedContent())) { |
| return; |
| } |
| |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([useProtectedContext, this](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::useProtectedContext"); |
| instance.useProtectedContext(useProtectedContext); |
| if (instance.isProtected() != useProtectedContext) { |
| ALOGE("Failed to switch RenderEngine context."); |
| // reset the cached mIsProtected value to a good state, but this does not |
| // prevent other callers of this method and isProtected from reading the |
| // invalid cached value. |
| mIsProtected = instance.isProtected(); |
| } |
| }); |
| mIsProtected = useProtectedContext; |
| } |
| mCondition.notify_one(); |
| } |
| |
| void RenderEngineThreaded::cleanupPostRender() { |
| if (canSkipPostRenderCleanup()) { |
| return; |
| } |
| |
| // This function is designed so it can run asynchronously, so we do not need to wait |
| // for the futures. |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([=](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::unmapExternalTextureBuffer"); |
| instance.cleanupPostRender(); |
| }); |
| } |
| mCondition.notify_one(); |
| } |
| |
| bool RenderEngineThreaded::canSkipPostRenderCleanup() const { |
| waitUntilInitialized(); |
| return mRenderEngine->canSkipPostRenderCleanup(); |
| } |
| |
| status_t RenderEngineThreaded::drawLayers(const DisplaySettings& display, |
| const std::vector<const LayerSettings*>& layers, |
| const std::shared_ptr<ExternalTexture>& buffer, |
| const bool useFramebufferCache, |
| base::unique_fd&& bufferFence, |
| base::unique_fd* drawFence) { |
| ATRACE_CALL(); |
| std::promise<status_t> resultPromise; |
| std::future<status_t> resultFuture = resultPromise.get_future(); |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([&resultPromise, &display, &layers, &buffer, useFramebufferCache, |
| &bufferFence, &drawFence](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::drawLayers"); |
| status_t status = instance.drawLayers(display, layers, buffer, useFramebufferCache, |
| std::move(bufferFence), drawFence); |
| resultPromise.set_value(status); |
| }); |
| } |
| mCondition.notify_one(); |
| return resultFuture.get(); |
| } |
| |
| void RenderEngineThreaded::cleanFramebufferCache() { |
| ATRACE_CALL(); |
| // This function is designed so it can run asynchronously, so we do not need to wait |
| // for the futures. |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::cleanFramebufferCache"); |
| instance.cleanFramebufferCache(); |
| }); |
| } |
| mCondition.notify_one(); |
| } |
| |
| int RenderEngineThreaded::getContextPriority() { |
| std::promise<int> resultPromise; |
| std::future<int> resultFuture = resultPromise.get_future(); |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([&resultPromise](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::getContextPriority"); |
| int priority = instance.getContextPriority(); |
| resultPromise.set_value(priority); |
| }); |
| } |
| mCondition.notify_one(); |
| return resultFuture.get(); |
| } |
| |
| bool RenderEngineThreaded::supportsBackgroundBlur() { |
| waitUntilInitialized(); |
| return mRenderEngine->supportsBackgroundBlur(); |
| } |
| |
| void RenderEngineThreaded::onPrimaryDisplaySizeChanged(ui::Size size) { |
| // This function is designed so it can run asynchronously, so we do not need to wait |
| // for the futures. |
| { |
| std::lock_guard lock(mThreadMutex); |
| mFunctionCalls.push([size](renderengine::RenderEngine& instance) { |
| ATRACE_NAME("REThreaded::onPrimaryDisplaySizeChanged"); |
| instance.onPrimaryDisplaySizeChanged(size); |
| }); |
| } |
| mCondition.notify_one(); |
| } |
| |
| } // namespace threaded |
| } // namespace renderengine |
| } // namespace android |