Add sw::WaitGroup to System/Synchronization.hpp

A synchronization primitive borrowed from golang:
https://golang.org/pkg/sync/#WaitGroup

Bug: b/133127573
Change-Id: Id3ec093c0de478cfe16fad4d064cf2c26288acf7
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/31680
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
diff --git a/src/System/Synchronization.hpp b/src/System/Synchronization.hpp
index 0015442..a6d0df2 100644
--- a/src/System/Synchronization.hpp
+++ b/src/System/Synchronization.hpp
@@ -15,6 +15,7 @@
 #ifndef sw_Synchronization_hpp
 #define sw_Synchronization_hpp
 
+#include <assert.h>
 #include <chrono>
 #include <condition_variable>
 #include <mutex>
@@ -23,6 +24,70 @@
 namespace sw
 {
 
+// WaitGroup is a synchronization primitive that allows you to wait for
+// collection of asynchronous tasks to finish executing.
+// Call add() before each task begins, and then call done() when after each task
+// is finished.
+// At the same time, wait() can be used to block until all tasks have finished.
+// WaitGroup takes its name after Golang's sync.WaitGroup.
+class WaitGroup
+{
+public:
+	// add() begins a new task.
+	void add()
+	{
+		std::unique_lock<std::mutex> lock(mutex);
+		++count_;
+	}
+
+	// done() is called when a task of the WaitGroup has been completed.
+	// Returns true if there are no more tasks currently running in the
+	// WaitGroup.
+	bool done()
+	{
+		std::unique_lock<std::mutex> lock(mutex);
+		assert(count_ > 0);
+		--count_;
+		if(count_ == 0)
+		{
+			condition.notify_all();
+		}
+		return count_ == 0;
+	}
+
+	// wait() blocks until all the tasks have been finished.
+	void wait()
+	{
+		std::unique_lock<std::mutex> lock(mutex);
+		condition.wait(lock, [this] { return count_ == 0; });
+	}
+
+	// wait() blocks until all the tasks have been finished or the timeout
+	// has been reached, returning true if all tasks have been completed, or
+	// false if the timeout has been reached.
+	template <class CLOCK, class DURATION>
+	bool wait(const std::chrono::time_point<CLOCK, DURATION>& timeout)
+	{
+		std::unique_lock<std::mutex> lock(mutex);
+		return condition.wait_until(lock, timeout, [this] { return count_ == 0; });
+	}
+
+	// count() returns the number of times add() has been called without a call
+	// to done().
+	// Note: No lock is held after count() returns, so the count may immediately
+	// change after returning.
+	int32_t count()
+	{
+		std::unique_lock<std::mutex> lock(mutex);
+		return count_;
+	}
+
+private:
+	int32_t count_ = 0; // guarded by mutex
+	std::mutex mutex;
+	std::condition_variable condition;
+};
+
 // Event is a synchronization mechanism used to indicate to waiting threads
 // when a boolean condition has become true.
 class Event