summaryrefslogtreecommitdiffstatshomepage
path: root/src/osd/windows/winwork.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osd/windows/winwork.c')
-rw-r--r--src/osd/windows/winwork.c775
1 files changed, 775 insertions, 0 deletions
diff --git a/src/osd/windows/winwork.c b/src/osd/windows/winwork.c
new file mode 100644
index 00000000000..602c25a15fc
--- /dev/null
+++ b/src/osd/windows/winwork.c
@@ -0,0 +1,775 @@
+//============================================================
+//
+// winwork.c - Win32 OSD core work item functions
+//
+// Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+// Visit http://mamedev.org for licensing and usage restrictions.
+//
+//============================================================
+
+// standard windows headers
+#define _WIN32_WINNT 0x0400
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <process.h>
+
+#ifdef __GNUC__
+#include <stdint.h>
+#endif
+
+// MAME headers
+#include "osdcore.h"
+
+
+//============================================================
+// DEBUGGING
+//============================================================
+
+#define KEEP_STATISTICS (0)
+
+
+
+//============================================================
+// PARAMETERS
+//============================================================
+
+#define SPIN_LOOP_TIME (osd_ticks_per_second() / 1000)
+
+
+
+//============================================================
+// MACROS
+//============================================================
+
+#if KEEP_STATISTICS
+#define add_to_stat(v,x) do { interlocked_add((v), (x)); } while (0)
+#define begin_timing(v) do { (v) -= osd_profiling_ticks(); } while (0)
+#define end_timing(v) do { (v) += osd_profiling_ticks(); } while (0)
+#else
+#define add_to_stat(v,x) do { } while (0)
+#define begin_timing(v) do { } while (0)
+#define end_timing(v) do { } while (0)
+#endif
+
+#ifndef YieldProcessor
+#ifdef __GNUC__
+INLINE void YieldProcessor(void)
+{
+ __asm__ __volatile__ ( "rep; nop" );
+}
+#else
+INLINE void YieldProcessor(void)
+{
+ __asm { rep nop }
+}
+#endif
+#endif
+
+
+
+//============================================================
+// TYPE DEFINITIONS
+//============================================================
+
+typedef struct _scalable_lock scalable_lock;
+struct _scalable_lock
+{
+ struct
+ {
+ volatile INT32 haslock; // do we have the lock?
+ INT32 filler[64/4-1]; // assumes a 64-byte cache line
+ } slot[WORK_MAX_THREADS]; // one slot per thread
+ volatile INT32 nextindex; // index of next slot to use
+};
+
+
+typedef struct _work_thread_info work_thread_info;
+struct _work_thread_info
+{
+ osd_work_queue * queue; // pointer back to the queue
+ HANDLE handle; // handle to the thread
+ HANDLE wakeevent; // wake event for the thread
+ volatile INT32 active; // are we actively processing work?
+
+#if KEEP_STATISTICS
+ INT32 itemsdone;
+ osd_ticks_t actruntime;
+ osd_ticks_t runtime;
+ osd_ticks_t spintime;
+ osd_ticks_t waittime;
+#endif
+};
+
+
+struct _osd_work_queue
+{
+ scalable_lock lock; // lock for protecting the queue
+ osd_work_item * volatile list; // list of items in the queue
+ osd_work_item ** volatile tailptr; // pointer to the tail pointer of work items in the queue
+ osd_work_item * volatile free; // free list of work items
+ volatile INT32 items; // items in the queue
+ volatile INT32 livethreads; // number of live threads
+ volatile INT32 waiting; // is someone waiting on the queue to complete?
+ volatile UINT8 exiting; // should the threads exit on their next opportunity?
+ UINT32 threads; // number of threads in this queue
+ UINT32 flags; // creation flags
+ work_thread_info * thread; // array of thread information
+ HANDLE doneevent; // event signalled when work is complete
+
+#if KEEP_STATISTICS
+ volatile INT32 itemsqueued; // total items queued
+ volatile INT32 setevents; // number of times we called SetEvent
+ volatile INT32 extraitems; // how many extra items we got after the first in the queue loop
+ volatile INT32 spinloops; // how many times spinning bought us more items
+#endif
+};
+
+
+struct _osd_work_item
+{
+ osd_work_item * next; // pointer to next item
+ osd_work_queue * queue; // pointer back to the owning queue
+ osd_work_callback callback; // callback function
+ void * param; // callback parameter
+ void * result; // callback result
+ HANDLE event; // event signalled when complete
+ UINT32 flags; // creation flags
+ volatile INT32 done; // is the item done?
+};
+
+
+
+//============================================================
+// FUNCTION PROTOTYPES
+//============================================================
+
+static int effective_num_processors(void);
+static unsigned __stdcall worker_thread_entry(void *param);
+static void worker_thread_process(osd_work_queue *queue, work_thread_info *thread);
+
+
+
+//============================================================
+// INLINE FUNCTIONS
+//============================================================
+
+INLINE void *compare_exchange_ptr(void * volatile *ptr, void *compare, void *exchange)
+{
+#ifdef PTR64
+ INT64 result = InterlockedCompareExchange64((LONGLONG *)ptr, (LONGLONG)exchange, (LONGLONG)compare);
+ return (void *)result;
+#else
+ INT32 result = InterlockedCompareExchange((LPLONG)ptr, (LONG)exchange, (LONG)compare);
+ return (void *)result;
+#endif
+}
+
+
+INLINE INT32 interlocked_exchange32(INT32 volatile *ptr, INT32 value)
+{
+ return InterlockedExchange((LPLONG)ptr, value);
+}
+
+
+INLINE INT32 interlocked_increment(INT32 volatile *ptr)
+{
+ return InterlockedIncrement((LPLONG)ptr);
+}
+
+
+INLINE INT32 interlocked_decrement(INT32 volatile *ptr)
+{
+ return InterlockedDecrement((LPLONG)ptr);
+}
+
+
+INLINE INT32 interlocked_add(INT32 volatile *ptr, INT32 add)
+{
+ return InterlockedExchangeAdd((LPLONG)ptr, add) + add;
+}
+
+
+
+//============================================================
+// Scalable Locks
+//============================================================
+
+INLINE void scalable_lock_init(scalable_lock *lock)
+{
+ memset(lock, 0, sizeof(*lock));
+ lock->slot[0].haslock = TRUE;
+}
+
+
+INLINE INT32 scalable_lock_acquire(scalable_lock *lock)
+{
+ INT32 myslot = (interlocked_increment(&lock->nextindex) - 1) & (WORK_MAX_THREADS - 1);
+ INT32 backoff = 1;
+
+ while (!lock->slot[myslot].haslock)
+ {
+ INT32 backcount;
+ for (backcount = 0; backcount < backoff; backcount++)
+ YieldProcessor();
+ backoff <<= 1;
+ }
+ lock->slot[myslot].haslock = FALSE;
+ return myslot;
+}
+
+
+INLINE void scalable_lock_release(scalable_lock *lock, INT32 myslot)
+{
+ interlocked_exchange32(&lock->slot[(myslot + 1) & (WORK_MAX_THREADS - 1)].haslock, TRUE);
+}
+
+
+
+//============================================================
+// osd_work_queue_alloc
+//============================================================
+
+osd_work_queue *osd_work_queue_alloc(int flags)
+{
+ int numprocs = effective_num_processors();
+ osd_work_queue *queue;
+ int threadnum;
+
+ // allocate a new queue
+ queue = malloc(sizeof(*queue));
+ if (queue == NULL)
+ goto error;
+ memset(queue, 0, sizeof(*queue));
+
+ // initialize basic queue members
+ queue->tailptr = (osd_work_item **)&queue->list;
+ queue->flags = flags;
+
+ // allocate events for the queue
+ queue->doneevent = CreateEvent(NULL, TRUE, TRUE, NULL); // manual reset, signalled
+ if (queue->doneevent == NULL)
+ goto error;
+
+ // initialize the critical section
+ scalable_lock_init(&queue->lock);
+
+ // determine how many threads to create...
+ // on a single-CPU system, create 1 thread for I/O queues, and 0 threads for everything else
+ if (numprocs == 1)
+ queue->threads = (flags & WORK_QUEUE_FLAG_IO) ? 1 : 0;
+
+ // on an n-CPU system, create (n-1) threads for multi queues, and 1 thread for everything else
+ else
+ queue->threads = (flags & WORK_QUEUE_FLAG_MULTI) ? (numprocs - 1) : 1;
+
+ // clamp to the maximum
+ queue->threads = MIN(queue->threads, WORK_MAX_THREADS);
+
+ // allocate memory for thread array (+1 to count the calling thread)
+ queue->thread = malloc((queue->threads + 1) * sizeof(queue->thread[0]));
+ if (queue->thread == NULL)
+ goto error;
+ memset(queue->thread, 0, (queue->threads + 1) * sizeof(queue->thread[0]));
+
+ // iterate over threads
+ for (threadnum = 0; threadnum < queue->threads; threadnum++)
+ {
+ work_thread_info *thread = &queue->thread[threadnum];
+ uintptr_t handle;
+
+ // set a pointer back to the queue
+ thread->queue = queue;
+
+ // create the per-thread wake event
+ thread->wakeevent = CreateEvent(NULL, FALSE, FALSE, NULL); // auto-reset, not signalled
+ if (thread->wakeevent == NULL)
+ goto error;
+
+ // create the thread
+ handle = _beginthreadex(NULL, 0, worker_thread_entry, thread, 0, NULL);
+ thread->handle = (HANDLE)handle;
+ if (thread->handle == NULL)
+ goto error;
+
+ // set its priority: I/O threads get high priority because they are assumed to be
+ // blocked most of the time; other threads just match the creator's priority
+ if (flags & WORK_QUEUE_FLAG_IO)
+ SetThreadPriority(thread->handle, THREAD_PRIORITY_ABOVE_NORMAL);
+ else
+ SetThreadPriority(thread->handle, GetThreadPriority(GetCurrentThread()));
+ }
+
+ // start a timer going for "waittime" on the main thread
+ begin_timing(queue->thread[queue->threads].waittime);
+ return queue;
+
+error:
+ osd_work_queue_free(queue);
+ return NULL;
+}
+
+
+//============================================================
+// osd_work_queue_items
+//============================================================
+
+int osd_work_queue_items(osd_work_queue *queue)
+{
+ // return the number of items currently in the queue
+ return queue->items;
+}
+
+
+//============================================================
+// osd_work_queue_wait
+//============================================================
+
+int osd_work_queue_wait(osd_work_queue *queue, osd_ticks_t timeout)
+{
+ // if no threads, no waiting
+ if (queue->threads == 0)
+ return TRUE;
+
+ // if no items, we're done
+ if (queue->items == 0)
+ return TRUE;
+
+ // if this is a multi queue, help out rather than doing nothing
+ if (queue->flags & WORK_QUEUE_FLAG_MULTI)
+ {
+ work_thread_info *thread = &queue->thread[queue->threads];
+ osd_ticks_t stopspin = osd_ticks() + timeout;
+
+ end_timing(thread->waittime);
+
+ // process what we can as a worker thread
+ worker_thread_process(queue, thread);
+
+ // if we're a high frequency queue, spin until done
+ if (queue->flags & WORK_QUEUE_FLAG_HIGH_FREQ)
+ {
+ // spin until we're done
+ begin_timing(thread->spintime);
+ while (queue->items != 0 && osd_ticks() < stopspin)
+ YieldProcessor();
+ end_timing(thread->spintime);
+
+ begin_timing(thread->waittime);
+ return (queue->items == 0);
+ }
+ begin_timing(thread->waittime);
+ }
+
+ // reset our done event and double-check the items before waiting
+ ResetEvent(queue->doneevent);
+ interlocked_exchange32(&queue->waiting, TRUE);
+ if (queue->items != 0)
+ WaitForSingleObject(queue->doneevent, timeout * 1000 / osd_ticks_per_second());
+ interlocked_exchange32(&queue->waiting, FALSE);
+
+ // return TRUE if we actually hit 0
+ return (queue->items == 0);
+}
+
+
+//============================================================
+// osd_work_queue_free
+//============================================================
+
+void osd_work_queue_free(osd_work_queue *queue)
+{
+ // if we have threads, clean them up
+ if (queue->threads > 0 && queue->thread != NULL)
+ {
+ int threadnum;
+
+ // stop the timer for "waittime" on the main thread
+ end_timing(queue->thread[queue->threads].waittime);
+
+ // signal all the threads to exit
+ queue->exiting = TRUE;
+ for (threadnum = 0; threadnum < queue->threads; threadnum++)
+ {
+ work_thread_info *thread = &queue->thread[threadnum];
+ if (thread->wakeevent != NULL)
+ SetEvent(thread->wakeevent);
+ }
+
+ // wait for all the threads to go away
+ for (threadnum = 0; threadnum < queue->threads; threadnum++)
+ {
+ work_thread_info *thread = &queue->thread[threadnum];
+
+ // block on the thread going away, then close the handle
+ if (thread->handle != NULL)
+ {
+ WaitForSingleObject(thread->handle, INFINITE);
+ CloseHandle(thread->handle);
+ }
+
+ // clean up the wake event
+ if (thread->wakeevent != NULL)
+ CloseHandle(thread->wakeevent);
+ }
+
+#if KEEP_STATISTICS
+ // output per-thread statistics
+ for (threadnum = 0; threadnum <= queue->threads; threadnum++)
+ {
+ work_thread_info *thread = &queue->thread[threadnum];
+ osd_ticks_t total = thread->runtime + thread->waittime + thread->spintime;
+ printf("Thread %d: items=%9d run=%5.2f%% (%5.2f%%) spin=%5.2f%% wait/other=%5.2f%%\n",
+ threadnum, thread->itemsdone,
+ (double)thread->runtime * 100.0 / (double)total,
+ (double)thread->actruntime * 100.0 / (double)total,
+ (double)thread->spintime * 100.0 / (double)total,
+ (double)thread->waittime * 100.0 / (double)total);
+ }
+#endif
+
+ // free the list
+ free(queue->thread);
+ }
+
+ // free all the events
+ if (queue->doneevent != NULL)
+ CloseHandle(queue->doneevent);
+
+ // free all items in the free list
+ while (queue->free != NULL)
+ {
+ osd_work_item *item = (osd_work_item *)queue->free;
+ queue->free = item->next;
+ if (item->event != NULL)
+ CloseHandle(item->event);
+ free(item);
+ }
+
+ // free all items in the active list
+ while (queue->list != NULL)
+ {
+ osd_work_item *item = (osd_work_item *)queue->list;
+ queue->list = item->next;
+ if (item->event != NULL)
+ CloseHandle(item->event);
+ free(item);
+ }
+
+#if KEEP_STATISTICS
+ printf("Items queued = %9d\n", queue->itemsqueued);
+ printf("SetEvent calls = %9d\n", queue->setevents);
+ printf("Extra items = %9d\n", queue->extraitems);
+ printf("Spin loops = %9d\n", queue->spinloops);
+#endif
+
+ // free the queue itself
+ free(queue);
+}
+
+
+//============================================================
+// osd_work_item_queue_multiple
+//============================================================
+
+osd_work_item *osd_work_item_queue_multiple(osd_work_queue *queue, osd_work_callback callback, INT32 numitems, void *parambase, INT32 paramstep, UINT32 flags)
+{
+ osd_work_item *itemlist = NULL;
+ osd_work_item **item_tailptr = &itemlist;
+ INT32 lockslot;
+ int itemnum;
+
+ // loop over items, building up a local list of work
+ for (itemnum = 0; itemnum < numitems; itemnum++)
+ {
+ osd_work_item *item;
+
+ // first allocate a new work item; try the free list first
+ do
+ {
+ item = (osd_work_item *)queue->free;
+ } while (item != NULL && compare_exchange_ptr((PVOID volatile *)&queue->free, item, item->next) != item);
+
+ // if nothing, allocate something new
+ if (item == NULL)
+ {
+ // allocate the item
+ item = malloc(sizeof(*item));
+ if (item == NULL)
+ return NULL;
+ item->event = NULL;
+ item->queue = queue;
+ }
+
+ // fill in the basics
+ item->next = NULL;
+ item->callback = callback;
+ item->param = parambase;
+ item->result = NULL;
+ item->flags = flags;
+ item->done = FALSE;
+
+ // advance to the next
+ *item_tailptr = item;
+ item_tailptr = &item->next;
+ parambase = (UINT8 *)parambase + paramstep;
+ }
+
+ // enqueue the whole thing within the critical section
+ lockslot = scalable_lock_acquire(&queue->lock);
+ *queue->tailptr = itemlist;
+ queue->tailptr = item_tailptr;
+ scalable_lock_release(&queue->lock, lockslot);
+
+ // increment the number of items in the queue
+ interlocked_add(&queue->items, numitems);
+ add_to_stat(&queue->itemsqueued, numitems);
+
+ // look for free threads to do the work
+ if (queue->livethreads < queue->threads)
+ {
+ int threadnum;
+
+ // iterate over all the threads
+ for (threadnum = 0; threadnum < queue->threads; threadnum++)
+ {
+ work_thread_info *thread = &queue->thread[threadnum];
+
+ // if this thread is not active, wake him up
+ if (!thread->active)
+ {
+ SetEvent(thread->wakeevent);
+ add_to_stat(&queue->setevents, 1);
+
+ // for non-shared, the first one we find is good enough
+ if (--numitems == 0)
+ break;
+ }
+ }
+ }
+
+ // if no threads, run the queue now on this thread
+ if (queue->threads == 0)
+ worker_thread_process(queue, &queue->thread[0]);
+
+ // only return the item if it won't get released automatically
+ return (flags & WORK_ITEM_FLAG_AUTO_RELEASE) ? NULL : *item_tailptr;
+}
+
+
+//============================================================
+// osd_work_item_wait
+//============================================================
+
+int osd_work_item_wait(osd_work_item *item, osd_ticks_t timeout)
+{
+ // if we're done already, just return
+ if (item->done)
+ return TRUE;
+
+ // if we don't have an event, create one
+ if (item->event == NULL)
+ item->event = CreateEvent(NULL, TRUE, FALSE, NULL); // manual reset, not signalled
+ else
+ ResetEvent(item->event);
+
+ // if we don't have an event, we need to spin (shouldn't ever really happen)
+ if (item->event != NULL)
+ {
+ osd_ticks_t stopspin = osd_ticks() + timeout;
+ while (!item->done && osd_ticks() < stopspin)
+ YieldProcessor();
+ }
+
+ // otherwise, block on the event until done
+ else if (!item->done)
+ WaitForSingleObject(item->event, timeout * 1000 / osd_ticks_per_second());
+
+ // return TRUE if the refcount actually hit 0
+ return item->done;
+}
+
+
+//============================================================
+// osd_work_item_result
+//============================================================
+
+void *osd_work_item_result(osd_work_item *item)
+{
+ return item->result;
+}
+
+
+//============================================================
+// osd_work_item_release
+//============================================================
+
+void osd_work_item_release(osd_work_item *item)
+{
+ osd_work_item *next;
+
+ // make sure we're done first
+ osd_work_item_wait(item, 100 * osd_ticks_per_second());
+
+ // add us to the free list on our queue
+ do
+ {
+ next = (osd_work_item *)item->queue->free;
+ item->next = next;
+ } while (compare_exchange_ptr((PVOID volatile *)&item->queue->free, next, item) != next);
+}
+
+
+//============================================================
+// effective_num_processors
+//============================================================
+
+static int effective_num_processors(void)
+{
+ char *procsoverride;
+ SYSTEM_INFO info;
+ int numprocs = 0;
+
+ // if the OSDPROCESSORS environment variable is set, use that value if valid
+ procsoverride = getenv("OSDPROCESSORS");
+ if (procsoverride != NULL && sscanf(procsoverride, "%d", &numprocs) == 1 && numprocs > 0)
+ return numprocs;
+
+ // otherwise, fetch the info from the system
+ GetSystemInfo(&info);
+
+ // max out at 4 for now since scaling above that seems to do poorly
+ return MIN(info.dwNumberOfProcessors, 4);
+}
+
+
+//============================================================
+// worker_thread_entry
+//============================================================
+
+static unsigned __stdcall worker_thread_entry(void *param)
+{
+ work_thread_info *thread = param;
+ osd_work_queue *queue = thread->queue;
+
+ // loop until we exit
+ for ( ;; )
+ {
+ // block waiting for work or exit
+ DWORD result = WAIT_OBJECT_0;
+
+ // bail on exit, and only wait if there are no pending items in queue
+ if (!queue->exiting && queue->list == NULL)
+ {
+ begin_timing(thread->waittime);
+ result = WaitForSingleObject(thread->wakeevent, INFINITE);
+ end_timing(thread->waittime);
+ }
+ if (queue->exiting)
+ break;
+
+ // indicate that we are live
+ interlocked_exchange32(&thread->active, TRUE);
+ interlocked_increment(&queue->livethreads);
+
+ // process work items
+ for ( ;; )
+ {
+ osd_ticks_t stopspin;
+
+ // process as much as we can
+ worker_thread_process(queue, thread);
+
+ // if we're a high frequency queue, spin for a while before giving up
+ if (queue->flags & WORK_QUEUE_FLAG_HIGH_FREQ)
+ {
+ // spin for a while looking for more work
+ begin_timing(thread->spintime);
+ stopspin = osd_ticks() + SPIN_LOOP_TIME;
+ while (queue->list == NULL && osd_ticks() < stopspin)
+ YieldProcessor();
+ end_timing(thread->spintime);
+ }
+
+ // if nothing more, release the processor
+ if (queue->list == NULL)
+ break;
+ add_to_stat(&queue->spinloops, 1);
+ }
+
+ // decrement the live thread count
+ interlocked_exchange32(&thread->active, FALSE);
+ interlocked_decrement(&queue->livethreads);
+ }
+ return 0;
+}
+
+
+//============================================================
+// worker_thread_process
+//============================================================
+
+static void worker_thread_process(osd_work_queue *queue, work_thread_info *thread)
+{
+ int threadid = thread - queue->thread;
+
+ begin_timing(thread->runtime);
+
+ // loop until everything is processed
+ while (queue->list != NULL)
+ {
+ osd_work_item *item;
+ INT32 lockslot;
+
+ // use a critical section to synchronize the removal of items
+ lockslot = scalable_lock_acquire(&queue->lock);
+ {
+ // pull the item from the queue
+ item = (osd_work_item *)queue->list;
+ if (item != NULL)
+ {
+ queue->list = item->next;
+ if (queue->list == NULL)
+ queue->tailptr = (osd_work_item **)&queue->list;
+ }
+ }
+ scalable_lock_release(&queue->lock, lockslot);
+
+ // process non-NULL items
+ if (item != NULL)
+ {
+ // call the callback and stash the result
+ begin_timing(thread->actruntime);
+ item->result = (*item->callback)(item->param, threadid);
+ end_timing(thread->actruntime);
+
+ // decrement the item count after we are done
+ interlocked_decrement(&queue->items);
+ interlocked_exchange32(&item->done, TRUE);
+ add_to_stat(&thread->itemsdone, 1);
+
+ // if it's an auto-release item, release it
+ if (item->flags & WORK_ITEM_FLAG_AUTO_RELEASE)
+ osd_work_item_release(item);
+
+ // set the result and signal the event
+ else if (item->event != NULL)
+ {
+ SetEvent(item->event);
+ add_to_stat(&item->queue->setevents, 1);
+ }
+
+ // if we removed an item and there's still work to do, bump the stats
+ if (queue->list != NULL)
+ add_to_stat(&queue->extraitems, 1);
+ }
+ }
+
+ // we don't need to set the doneevent for multi queues because they spin
+ if (queue->waiting)
+ {
+ SetEvent(queue->doneevent);
+ add_to_stat(&queue->setevents, 1);
+ }
+
+ end_timing(thread->runtime);
+}