diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 64416c592..e0f111d84 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -19,6 +19,8 @@ add_library(common
   cpu_detect.h
   cubeb_audio_stream.cpp
   cubeb_audio_stream.h
+  event.cpp
+  event.h
   fifo_queue.h
   file_system.cpp
   file_system.h
diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj
index 5c958f25d..9e59e7caa 100644
--- a/src/common/common.vcxproj
+++ b/src/common/common.vcxproj
@@ -48,6 +48,7 @@
     <ClInclude Include="d3d11\staging_texture.h" />
     <ClInclude Include="d3d11\stream_buffer.h" />
     <ClInclude Include="d3d11\texture.h" />
+    <ClInclude Include="event.h" />
     <ClInclude Include="fifo_queue.h" />
     <ClInclude Include="file_system.h" />
     <ClInclude Include="gl\program.h" />
@@ -87,6 +88,7 @@
     <ClCompile Include="d3d11\staging_texture.cpp" />
     <ClCompile Include="d3d11\stream_buffer.cpp" />
     <ClCompile Include="d3d11\texture.cpp" />
+    <ClCompile Include="event.cpp" />
     <ClCompile Include="file_system.cpp" />
     <ClCompile Include="gl\program.cpp" />
     <ClCompile Include="gl\shader_cache.cpp" />
diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters
index bc01bd681..a741709c2 100644
--- a/src/common/common.vcxproj.filters
+++ b/src/common/common.vcxproj.filters
@@ -56,6 +56,7 @@
     <ClInclude Include="gl\shader_cache.h">
       <Filter>gl</Filter>
     </ClInclude>
+    <ClInclude Include="event.h" />
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="jit_code_buffer.cpp" />
@@ -108,6 +109,7 @@
     <ClCompile Include="gl\shader_cache.cpp">
       <Filter>gl</Filter>
     </ClCompile>
+    <ClCompile Include="event.cpp" />
   </ItemGroup>
   <ItemGroup>
     <Natvis Include="bitfield.natvis" />
diff --git a/src/common/event.cpp b/src/common/event.cpp
new file mode 100644
index 000000000..aad3b3a81
--- /dev/null
+++ b/src/common/event.cpp
@@ -0,0 +1,138 @@
+#include "event.h"
+#include "assert.h"
+
+#if defined(WIN32)
+#include <malloc.h>
+#include "windows_headers.h"
+#elif defined(__linux__) || defined(__APPLE__)
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+#ifdef __APPLE__
+#include <stdlib.h>
+#else
+#include <malloc.h>
+#endif
+#endif
+
+namespace Common {
+
+#if defined(WIN32)
+
+Event::Event(bool auto_reset /* = false */)
+{
+  m_event_handle = reinterpret_cast<void*>(CreateEvent(nullptr, auto_reset ? FALSE : TRUE, FALSE, nullptr));
+  Assert(m_event_handle != nullptr);
+}
+
+Event::~Event()
+{
+  CloseHandle(reinterpret_cast<HANDLE>(m_event_handle));
+}
+
+void Event::Signal()
+{
+  SetEvent(reinterpret_cast<HANDLE>(m_event_handle));
+}
+
+void Event::Wait()
+{
+  WaitForSingleObject(reinterpret_cast<HANDLE>(m_event_handle), INFINITE);
+}
+
+bool Event::TryWait(u32 timeout_in_ms)
+{
+  return (WaitForSingleObject(reinterpret_cast<HANDLE>(m_event_handle), timeout_in_ms) == WAIT_OBJECT_0);
+}
+
+void Event::Reset()
+{
+  ResetEvent(reinterpret_cast<HANDLE>(m_event_handle));
+}
+
+void Event::WaitForMultipleEvents(Event** events, u32 num_events)
+{
+  DebugAssert(num_events > 0);
+
+  HANDLE* event_handles = (HANDLE*)alloca(sizeof(HANDLE) * num_events);
+  for (u32 i = 0; i < num_events; i++)
+    event_handles[i] = reinterpret_cast<HANDLE>(events[i]->m_event_handle);
+
+  WaitForMultipleObjects(num_events, event_handles, TRUE, INFINITE);
+}
+
+#elif defined(__linux__) || defined(__APPLE__)
+
+Event::Event(bool auto_reset /*= false*/) : m_auto_reset(auto_reset)
+{
+  m_pipe_fds[0] = m_pipe_fds[1] = -1;
+#if defined(__linux__)
+  pipe2(m_pipe_fds, O_NONBLOCK);
+#else
+  pipe(m_pipe_fds);
+  fcntl(m_pipe_fds[0], F_SETFL, fcntl(m_pipe_fds[0], F_GETFL) | O_NONBLOCK);
+  fcntl(m_pipe_fds[1], F_SETFL, fcntl(m_pipe_fds[1], F_GETFL) | O_NONBLOCK);
+#endif
+  Assert(m_pipe_fds[0] >= 0 && m_pipe_fds[1] >= 0);
+}
+
+Event::~Event()
+{
+  close(m_pipe_fds[0]);
+  close(m_pipe_fds[1]);
+}
+
+void Event::Signal()
+{
+  char buf[1] = {0};
+  write(m_pipe_fds[1], buf, sizeof(buf));
+}
+
+void Event::Wait()
+{
+  pollfd pd = {};
+  pd.fd = m_pipe_fds[0];
+  pd.events = POLLRDNORM;
+  poll(&pd, 1, -1);
+
+  if (m_auto_reset)
+    Reset();
+}
+
+bool Event::TryWait(u32 timeout_in_ms)
+{
+  pollfd pd;
+  pd.fd = m_pipe_fds[0];
+  pd.events = POLLRDNORM;
+  if (poll(&pd, 1, timeout_in_ms) == 0)
+    return false;
+
+  if (m_auto_reset)
+    Reset();
+
+  return true;
+}
+
+void Event::Reset()
+{
+  char buf[1];
+  while (read(m_pipe_fds[0], buf, sizeof(buf)) > 0)
+    ;
+}
+
+void Event::WaitForMultipleEvents(Event** events, u32 num_events)
+{
+  DebugAssert(num_events > 0);
+
+  pollfd pd = {};
+  pd.events = POLLRDNORM;
+  for (u32 i = 0; i < num_events; i++)
+  {
+    pd.fd = events[i]->m_pipe_fds[0];
+    poll(&pd, 1, -1);
+  }
+}
+
+#endif
+
+} // namespace Common
diff --git a/src/common/event.h b/src/common/event.h
new file mode 100644
index 000000000..b975ea0c0
--- /dev/null
+++ b/src/common/event.h
@@ -0,0 +1,30 @@
+#pragma once
+#include "types.h"
+
+namespace Common {
+
+class Event
+{
+public:
+  Event(bool auto_reset = false);
+  ~Event();
+
+  void Reset();
+  void Signal();
+  void Wait();
+  bool TryWait(u32 timeout_in_ms);
+
+  static void WaitForMultipleEvents(Event** events, u32 num_events);
+
+private:
+#ifdef WIN32
+  void* m_event_handle;
+#elif defined(__linux__) || defined(__APPLE__)
+  int m_pipe_fds[2];
+  bool m_auto_reset;
+#else
+#error Unknown platform.
+#endif
+};
+
+} // namespace Common
\ No newline at end of file