From e18809d8ea1a9d47a592c91d3bfb703a5df08ac2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20D=C3=B6singer?= <stefan@codeweavers.com>
Date: Thu, 24 Oct 2013 18:56:13 +0200
Subject: wined3d: Use an event to block the worker thread when it is idle.

I could not find a canonical implementation for this. Suggestions are
welcome.

The main goal was to avoid SetEvent / WaitForSingleObject / ResetEvent
calls at all costs. They go straight to wineserver, bringing it to 25%
CPU time and cutting framerates in half. Hence the extra BOOL to tell
cs_submit when to set the event.

Finding the correct spin count is tricky. The value I set is high enough
to prevent 3DMark2001 car chase high detail from waiting. This benchmark
maps a lot of in-use managed buffers and thus waits for the CS. But what
the ideal number is depends a lot on the game and CPU.

A better heuristic is needed. E.g. we could tell the worker thread when
the main thread is waiting for a resource to become idle and avoid
waiting for the event in that case. We may have to use POSIX
synchronization primitives for efficiency. On the other hand, the
current implementation probably still spins way too much for an
application that throttles the framerate to 30 fps by waiting in its own
code. So it is probably only good enough for Microsoft Powerpoint.

We should also think about blocking the main thread with events if it is
waiting for the worker thread when waiting for resources, the swap
counter or space in the command Stream. This should preserve power when
vsync is turned on.
---
 dlls/wined3d/cs.c              | 51 +++++++++++++++++++++++++++++++++++++++---
 dlls/wined3d/wined3d_private.h |  5 +++++
 2 files changed, 53 insertions(+), 3 deletions(-)

diff --git a/dlls/wined3d/cs.c b/dlls/wined3d/cs.c
index 489fad0..8cbca6c 100644
--- a/dlls/wined3d/cs.c
+++ b/dlls/wined3d/cs.c
@@ -539,6 +539,9 @@ static void wined3d_cs_mt_submit(struct wined3d_cs *cs, size_t size)
     /* There is only one thread writing to queue.head, InterlockedExchange
      * is used for the memory barrier. */
     InterlockedExchange(&cs->queue.head, new_val);
+
+    if (InterlockedCompareExchange(&cs->waiting_for_event, FALSE, TRUE))
+        SetEvent(cs->event);
 }
 
 static void wined3d_cs_mt_submit_prio(struct wined3d_cs *cs, size_t size)
@@ -547,6 +550,9 @@ static void wined3d_cs_mt_submit_prio(struct wined3d_cs *cs, size_t size)
     /* There is only one thread writing to queue.head, InterlockedExchange
      * is used for the memory barrier. */
     InterlockedExchange(&cs->prio_queue.head, new_val);
+
+    if (InterlockedCompareExchange(&cs->waiting_for_event, FALSE, TRUE))
+        SetEvent(cs->event);
 }
 
 static UINT wined3d_cs_exec_nop(struct wined3d_cs *cs, const void *data)
@@ -2826,6 +2832,34 @@ static inline void poll_queries(struct wined3d_cs *cs)
     }
 }
 
+static inline BOOL queue_is_empty(const struct wined3d_cs_queue *queue)
+{
+    return *((volatile LONG *)&queue->head) == queue->tail;
+}
+
+static void wined3d_cs_wait_event(struct wined3d_cs *cs)
+{
+    InterlockedExchange(&cs->waiting_for_event, TRUE);
+
+    /* The main thread might enqueue a finish command and block on it
+     * after the worker thread decided to enter wined3d_cs_wait_event
+     * and before waiting_for_event was set to TRUE. Check again if
+     * the queues are empty */
+    if (!queue_is_empty(&cs->prio_queue) || !queue_is_empty(&cs->queue))
+    {
+        /* The main thread might have signalled the event, or be in the process
+         * of doing so. Wait for the event to reset it. ResetEvent is not good
+         * because the main thread might be beween the waiting_for_event reset
+         * and SignalEvent call. */
+        if (!InterlockedCompareExchange(&cs->waiting_for_event, FALSE, FALSE))
+            WaitForSingleObject(cs->event, INFINITE);
+    }
+    else
+    {
+        WaitForSingleObject(cs->event, INFINITE);
+    }
+}
+
 static DWORD WINAPI wined3d_cs_run(void *thread_param)
 {
     struct wined3d_cs *cs = thread_param;
@@ -2833,6 +2867,7 @@ static DWORD WINAPI wined3d_cs_run(void *thread_param)
     LONG tail;
     char poll = 0;
     struct wined3d_cs_queue *queue;
+    unsigned int spin_count = 0;
 
     TRACE("Started.\n");
 
@@ -2848,21 +2883,27 @@ static DWORD WINAPI wined3d_cs_run(void *thread_param)
         else
             poll++;
 
-        if (*((volatile LONG *)&cs->prio_queue.head) != cs->prio_queue.tail)
+        if (!queue_is_empty(&cs->prio_queue))
         {
             queue = &cs->prio_queue;
         }
-        else if (*((volatile LONG *)&cs->queue.head) != cs->queue.tail)
+        else if (!queue_is_empty(&cs->queue))
         {
             queue = &cs->queue;
-            if (*((volatile LONG *)&cs->prio_queue.head) != cs->prio_queue.tail)
+            if (!queue_is_empty(&cs->prio_queue))
                 queue = &cs->prio_queue;
         }
         else
         {
+            spin_count++;
+            if (spin_count >= WINED3D_CS_SPIN_COUNT && list_empty(&cs->query_poll_list))
+                wined3d_cs_wait_event(cs);
+
             continue;
         }
 
+        spin_count = 0;
+
         tail = queue->tail;
         opcode = *(const enum wined3d_cs_op *)&queue->data[tail];
 
@@ -2904,6 +2945,8 @@ struct wined3d_cs *wined3d_cs_create(struct wined3d_device *device)
     {
         cs->ops = &wined3d_cs_mt_ops;
 
+        cs->event = CreateEventW(NULL, FALSE, FALSE, NULL);
+
         if (!(cs->thread = CreateThread(NULL, 0, wined3d_cs_run, cs, 0, NULL)))
         {
             ERR("Failed to create wined3d command stream thread.\n");
@@ -2934,6 +2977,8 @@ void wined3d_cs_destroy(struct wined3d_cs *cs)
         CloseHandle(cs->thread);
         if (ret != WAIT_OBJECT_0)
             ERR("Wait failed (%#x).\n", ret);
+        if (!CloseHandle(cs->event))
+            ERR("Closing event failed.\n");
     }
 
     HeapFree(GetProcessHeap(), 0, cs);
diff --git a/dlls/wined3d/wined3d_private.h b/dlls/wined3d/wined3d_private.h
index 2f7dd75..63b67da 100644
--- a/dlls/wined3d/wined3d_private.h
+++ b/dlls/wined3d/wined3d_private.h
@@ -2583,6 +2583,8 @@ struct wined3d_cs_list
 };
 
 #define WINED3D_CS_QUEUE_SIZE 0x100000
+#define WINED3D_CS_SPIN_COUNT 10000000
+
 struct wined3d_cs_queue
 {
     LONG head, tail;
@@ -2612,6 +2614,9 @@ struct wined3d_cs
 
     LONG pending_presents;
     struct list query_poll_list;
+
+    HANDLE event;
+    BOOL waiting_for_event;
 };
 
 struct wined3d_cs *wined3d_cs_create(struct wined3d_device *device) DECLSPEC_HIDDEN;
-- 
2.1.3

