diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/ipc/CanvasRenderThread.cpp | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/gfx/ipc/CanvasRenderThread.cpp b/gfx/ipc/CanvasRenderThread.cpp new file mode 100644 index 0000000000..853806a222 --- /dev/null +++ b/gfx/ipc/CanvasRenderThread.cpp @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasRenderThread.h" + +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/gfx/CanvasManagerParent.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/CanvasTranslator.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/webrender/RenderThread.h" +#include "nsThread.h" +#include "prsystem.h" +#include "transport/runnable_utils.h" + +bool NS_IsInCanvasThreadOrWorker() { + return mozilla::gfx::CanvasRenderThread::IsInCanvasRenderOrWorkerThread(); +} + +namespace mozilla::gfx { + +static StaticRefPtr<CanvasRenderThread> sCanvasRenderThread; +static mozilla::BackgroundHangMonitor* sBackgroundHangMonitor; +#ifdef DEBUG +static bool sCanvasRenderThreadEverStarted = false; +#endif + +CanvasRenderThread::CanvasRenderThread(nsCOMPtr<nsIThread>&& aThread, + nsCOMPtr<nsIThreadPool>&& aWorkers, + bool aCreatedThread) + : mMutex("CanvasRenderThread::mMutex"), + mThread(std::move(aThread)), + mWorkers(std::move(aWorkers)), + mCreatedThread(aCreatedThread) {} + +CanvasRenderThread::~CanvasRenderThread() = default; + +// static +void CanvasRenderThread::Start() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sCanvasRenderThread); + +#ifdef DEBUG + // Check to ensure nobody will try to ever start us more than once during + // the process' lifetime (in particular after Stop). + MOZ_ASSERT(!sCanvasRenderThreadEverStarted); + sCanvasRenderThreadEverStarted = true; +#endif + + int32_t threadPref = + StaticPrefs::gfx_canvas_remote_worker_threads_AtStartup(); + uint32_t threadLimit; + if (threadPref < 0) { + // Given that the canvas workers are receiving instructions from + // content processes, it probably doesn't make sense to have more than + // half the number of processors doing canvas drawing. We set the + // lower limit to 2, so that even on single processor systems, if + // there is more than one window with canvas drawing, the OS can + // manage the load between them. + threadLimit = std::max(2, PR_GetNumberOfProcessors() / 2); + } else { + threadLimit = uint32_t(threadPref); + } + + // We don't spawn any workers if the user set the limit to 0. Instead we will + // use the CanvasRenderThread virtual thread. + nsCOMPtr<nsIThreadPool> workers; + if (threadLimit > 0) { + workers = SharedThreadPool::Get("CanvasWorkers"_ns, threadLimit); + if (NS_WARN_IF(!workers)) { + return; + } + } + + nsCOMPtr<nsIThread> thread; + if (!gfxVars::SupportsThreadsafeGL()) { + thread = wr::RenderThread::GetRenderThread(); + MOZ_ASSERT(thread); + } else if (!gfxVars::UseCanvasRenderThread()) { + thread = layers::CompositorThread(); + MOZ_ASSERT(thread); + } + + if (thread) { + sCanvasRenderThread = new CanvasRenderThread( + std::move(thread), std::move(workers), /* aCreatedThread */ false); + return; + } + + // This is 4M, which is higher than the default 256K. + // Increased with bug 1753349 to accommodate the `chromium/5359` branch of + // ANGLE, which has large peak stack usage for some pathological shader + // compilations. + // + // Previously increased to 512K to accommodate Mesa in bug 1753340. + // + // Previously increased to 320K to avoid a stack overflow in the + // Intel Vulkan driver initialization in bug 1716120. + // + // Note: we only override it if it's limited already. + const uint32_t stackSize = + nsIThreadManager::DEFAULT_STACK_SIZE ? 4096 << 10 : 0; + + nsresult rv = NS_NewNamedThread( + "CanvasRenderer", getter_AddRefs(thread), + NS_NewRunnableFunction( + "CanvasRender::BackgroundHangSetup", + []() { + sBackgroundHangMonitor = new mozilla::BackgroundHangMonitor( + "CanvasRendererBHM", + /* Timeout values are powers-of-two to enable us get better + data. 128ms is chosen for transient hangs because 8Hz should + be the minimally acceptable goal for Compositor + responsiveness (normal goal is 60Hz). */ + 128, + /* 2048ms is chosen for permanent hangs because it's longer than + * most Compositor hangs seen in the wild, but is short enough + * to not miss getting native hang stacks. */ + 2048); + nsCOMPtr<nsIThread> thread = NS_GetCurrentThread(); + nsThread* nsthread = static_cast<nsThread*>(thread.get()); + nsthread->SetUseHangMonitor(true); + nsthread->SetPriority(nsISupportsPriority::PRIORITY_HIGH); + }), + {.stackSize = stackSize}); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + sCanvasRenderThread = new CanvasRenderThread( + std::move(thread), std::move(workers), /* aCreatedThread */ true); +} + +// static +void CanvasRenderThread::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + // It is possible we never initialized this thread in the parent process, + // because we used the GPU process instead. + if (!sCanvasRenderThread) { + MOZ_ASSERT(XRE_IsParentProcess()); + return; + } + + // This closes all of the IPDL actors with possibly active task queues. + CanvasManagerParent::Shutdown(); + + // Any task queues that are in the process of shutting down are tracked in + // mPendingShutdownTaskQueues. We need to block on each one until all events + // are flushed so that we can safely teardown RemoteTextureMap afterwards. + while (true) { + RefPtr<TaskQueue> taskQueue; + { + MutexAutoLock lock(sCanvasRenderThread->mMutex); + + auto& pendingQueues = sCanvasRenderThread->mPendingShutdownTaskQueues; + if (pendingQueues.IsEmpty()) { + break; + } + + taskQueue = pendingQueues.PopLastElement(); + } + taskQueue->AwaitShutdownAndIdle(); + } + + bool createdThread = sCanvasRenderThread->mCreatedThread; + nsCOMPtr<nsIThread> oldThread = sCanvasRenderThread->GetCanvasRenderThread(); + nsCOMPtr<nsIThreadPool> oldWorkers = sCanvasRenderThread->mWorkers; + + // Ensure that we flush the CanvasRenderThread event queue before clearing our + // singleton. + NS_DispatchAndSpinEventLoopUntilComplete( + "CanvasRenderThread::Shutdown"_ns, oldThread, + NS_NewRunnableFunction("CanvasRenderThread::Shutdown", []() -> void {})); + + // Null out sCanvasRenderThread before we enter synchronous Shutdown, + // from here on we are to be considered shut down for our consumers. + sCanvasRenderThread = nullptr; + + if (oldWorkers) { + oldWorkers->Shutdown(); + } + + // We do a synchronous shutdown here while spinning the MT event loop, but + // only if we created a dedicated CanvasRender thread. + if (createdThread) { + oldThread->Shutdown(); + } +} + +// static +bool CanvasRenderThread::IsInCanvasRenderThread() { + return sCanvasRenderThread && + sCanvasRenderThread->mThread == NS_GetCurrentThread(); +} + +/* static */ bool CanvasRenderThread::IsInCanvasWorkerThread() { + // It is possible there are no worker threads, and the worker is the same as + // the CanvasRenderThread itself. + return sCanvasRenderThread && + ((sCanvasRenderThread->mWorkers && + sCanvasRenderThread->mWorkers->IsOnCurrentThread()) || + (!sCanvasRenderThread->mWorkers && + sCanvasRenderThread->mThread == NS_GetCurrentThread())); +} + +/* static */ bool CanvasRenderThread::IsInCanvasRenderOrWorkerThread() { + // It is possible there are no worker threads, and the worker is the same as + // the CanvasRenderThread itself. + return sCanvasRenderThread && + (sCanvasRenderThread->mThread == NS_GetCurrentThread() || + (sCanvasRenderThread->mWorkers && + sCanvasRenderThread->mWorkers->IsOnCurrentThread())); +} + +// static +already_AddRefed<nsIThread> CanvasRenderThread::GetCanvasRenderThread() { + nsCOMPtr<nsIThread> thread; + if (sCanvasRenderThread) { + thread = sCanvasRenderThread->mThread; + } + return thread.forget(); +} + +/* static */ already_AddRefed<TaskQueue> +CanvasRenderThread::CreateWorkerTaskQueue() { + if (!sCanvasRenderThread || !sCanvasRenderThread->mWorkers) { + return nullptr; + } + + return TaskQueue::Create(do_AddRef(sCanvasRenderThread->mWorkers), + "CanvasWorker") + .forget(); +} + +/* static */ void CanvasRenderThread::ShutdownWorkerTaskQueue( + TaskQueue* aTaskQueue) { + MOZ_ASSERT(aTaskQueue); + + aTaskQueue->BeginShutdown(); + + if (!sCanvasRenderThread) { + MOZ_ASSERT_UNREACHABLE("No CanvasRenderThread!"); + return; + } + + MutexAutoLock lock(sCanvasRenderThread->mMutex); + auto& pendingQueues = sCanvasRenderThread->mPendingShutdownTaskQueues; + pendingQueues.AppendElement(aTaskQueue); +} + +/* static */ void CanvasRenderThread::FinishShutdownWorkerTaskQueue( + TaskQueue* aTaskQueue) { + if (!sCanvasRenderThread) { + return; + } + + MutexAutoLock lock(sCanvasRenderThread->mMutex); + sCanvasRenderThread->mPendingShutdownTaskQueues.RemoveElement(aTaskQueue); +} + +/* static */ void CanvasRenderThread::Dispatch( + already_AddRefed<nsIRunnable> aRunnable) { + if (!sCanvasRenderThread) { + MOZ_DIAGNOSTIC_ASSERT(false, + "Dispatching after CanvasRenderThread shutdown!"); + return; + } + sCanvasRenderThread->mThread->Dispatch(std::move(aRunnable)); +} + +} // namespace mozilla::gfx |