/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- * 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/. */ #ifndef MOZILLA_GFX_TASKSCHEDULER_H_ #define MOZILLA_GFX_TASKSCHEDULER_H_ #include "mozilla/RefPtr.h" #include "mozilla/gfx/Types.h" #include "mozilla/RefCounted.h" #ifdef WIN32 #include "mozilla/gfx/JobScheduler_win32.h" #else #include "mozilla/gfx/JobScheduler_posix.h" #endif #include namespace mozilla { namespace gfx { class MultiThreadedJobQueue; class SyncObject; class WorkerThread; class JobScheduler { public: /// Return one of the queues that the drawing worker threads pull from, chosen /// pseudo-randomly. static MultiThreadedJobQueue* GetDrawingQueue() { return sSingleton->mDrawingQueues[ sSingleton->mNextQueue++ % sSingleton->mDrawingQueues.size() ]; } /// Return one of the queues that the drawing worker threads pull from with a /// hash to choose the queue. /// /// Calling this function several times with the same hash will yield the same queue. static MultiThreadedJobQueue* GetDrawingQueue(uint32_t aHash) { return sSingleton->mDrawingQueues[ aHash % sSingleton->mDrawingQueues.size() ]; } /// Return the task queue associated to the worker the task is pinned to if /// the task is pinned to a worker, or a random queue. static MultiThreadedJobQueue* GetQueueForJob(Job* aJob); /// Initialize the task scheduler with aNumThreads worker threads for drawing /// and aNumQueues task queues. /// /// The number of threads must be superior or equal to the number of queues /// (since for now a worker thread only pulls from one queue). static bool Init(uint32_t aNumThreads, uint32_t aNumQueues); /// Shut the scheduler down. /// /// This will block until worker threads are joined and deleted. static void ShutDown(); /// Returns true if there is a successfully initialized JobScheduler singleton. static bool IsEnabled() { return !!sSingleton; } /// Submit a task buffer to its associated queue. /// /// The caller looses ownership of the task buffer. static void SubmitJob(Job* aJobs); /// Convenience function to block the current thread until a given SyncObject /// is in the signaled state. /// /// The current thread will first try to steal jobs before blocking. static void Join(SyncObject* aCompletionSync); /// Process commands until the command buffer needs to block on a sync object, /// completes, yields, or encounters an error. /// /// Can be used on any thread. Worker threads basically loop over this, but the /// main thread can also dequeue pending task buffers and process them alongside /// the worker threads if it is about to block until completion anyway. /// /// The caller looses ownership of the task buffer. static JobStatus ProcessJob(Job* aJobs); protected: static JobScheduler* sSingleton; // queues of Job that are ready to be processed std::vector mDrawingQueues; std::vector mWorkerThreads; Atomic mNextQueue; }; /// Jobs are not reference-counted because they don't have shared ownership. /// The ownership of tasks can change when they are passed to certain methods /// of JobScheduler and SyncObject. See the docuumentaion of these classes. class Job { public: Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread = nullptr); virtual ~Job(); virtual JobStatus Run() = 0; /// For use in JobScheduler::SubmitJob. Don't use it anywhere else. //already_AddRefed GetAndResetStartSync(); SyncObject* GetStartSync() { return mStartSync; } bool IsPinnedToAThread() const { return !!mPinToThread; } WorkerThread* GetWorkerThread() { return mPinToThread; } protected: // An intrusive linked list of tasks waiting for a sync object to enter the // signaled state. When the task is not waiting for a sync object, mNextWaitingJob // should be null. This is only accessed from the thread that owns the task. Job* mNextWaitingJob; RefPtr mStartSync; RefPtr mCompletionSync; WorkerThread* mPinToThread; friend class SyncObject; }; class EventObject; /// This task will set an EventObject. /// /// Typically used as the final task, so that the main thread can block on the /// corresponfing EventObject until all of the tasks are processed. class SetEventJob : public Job { public: explicit SetEventJob(EventObject* aEvent, SyncObject* aStart, SyncObject* aCompletion = nullptr, WorkerThread* aPinToWorker = nullptr); ~SetEventJob(); JobStatus Run() override; EventObject* GetEvent() { return mEvent; } protected: RefPtr mEvent; }; /// A synchronization object that can be used to express dependencies and ordering between /// tasks. /// /// Jobs can register to SyncObjects in order to asynchronously wait for a signal. /// In practice, Job objects usually start with a sync object (startSyc) and end /// with another one (completionSync). /// a Job never gets processed before its startSync is in the signaled state, and /// signals its completionSync as soon as it finishes. This is how dependencies /// between tasks is expressed. class SyncObject final : public external::AtomicRefCounted { public: MOZ_DECLARE_REFCOUNTED_TYPENAME(SyncObject) /// Create a synchronization object. /// /// aNumPrerequisites represents the number of times the object must be signaled /// before actually entering the signaled state (in other words, it means the /// number of dependencies of this sync object). /// /// Explicitly specifying the number of prerequisites when creating sync objects /// makes it easy to start scheduling some of the prerequisite tasks while /// creating the others, which is how we typically use the task scheduler. /// Automatically determining the number of prerequisites using Job's constructor /// brings the risk that the sync object enters the signaled state while we /// are still adding prerequisites which is hard to fix without using muteces. explicit SyncObject(uint32_t aNumPrerequisites = 1); ~SyncObject(); /// Attempt to register a task. /// /// If the sync object is already in the signaled state, the buffer is *not* /// registered and the sync object does not take ownership of the task. /// If the object is not yet in the signaled state, it takes ownership of /// the task and places it in a list of pending tasks. /// Pending tasks will not be processed by the worker thread. /// When the SyncObject reaches the signaled state, it places the pending /// tasks back in the available buffer queue, so that they can be /// scheduled again. /// /// Returns true if the SyncOject is not already in the signaled state. /// This means that if this method returns true, the SyncObject has taken /// ownership of the Job. bool Register(Job* aJob); /// Signal the SyncObject. /// /// This decrements an internal counter. The sync object reaches the signaled /// state when the counter gets to zero. void Signal(); /// Returns true if mSignals is equal to zero. In other words, returns true /// if all prerequisite tasks have already signaled the sync object. bool IsSignaled(); /// Asserts that the number of added prerequisites is equal to the number /// specified in the constructor (does nothin in release builds). void FreezePrerequisites(); private: // Called by Job's constructor void AddSubsequent(Job* aJob); void AddPrerequisite(Job* aJob); void AddWaitingJob(Job* aJob); void SubmitWaitingJobs(); Atomic mSignals; Atomic mFirstWaitingJob; #ifdef DEBUG uint32_t mNumPrerequisites; Atomic mAddedPrerequisites; #endif friend class Job; friend class JobScheduler; }; /// Base class for worker threads. class WorkerThread { public: static WorkerThread* Create(MultiThreadedJobQueue* aJobQueue); virtual ~WorkerThread() {} void Run(); MultiThreadedJobQueue* GetJobQueue() { return mQueue; } protected: explicit WorkerThread(MultiThreadedJobQueue* aJobQueue); virtual void SetName(const char* aName) {} MultiThreadedJobQueue* mQueue; }; } // namespace } // namespace #endif