aboutsummaryrefslogtreecommitdiffstats
path: root/code/macosx/macosx_glsmp_ports.m
diff options
context:
space:
mode:
Diffstat (limited to 'code/macosx/macosx_glsmp_ports.m')
-rwxr-xr-xcode/macosx/macosx_glsmp_ports.m425
1 files changed, 425 insertions, 0 deletions
diff --git a/code/macosx/macosx_glsmp_ports.m b/code/macosx/macosx_glsmp_ports.m
new file mode 100755
index 0000000..ce6dc9f
--- /dev/null
+++ b/code/macosx/macosx_glsmp_ports.m
@@ -0,0 +1,425 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Foobar; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#import "macosx_glimp.h"
+
+#include "tr_local.h"
+#import "macosx_local.h"
+#import "macosx_display.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+#import <mach/mach.h>
+#import <mach/mach_error.h>
+
+
+#warning Using Mach Ports SMP acceleration implementation
+
+/*
+===========================================================
+
+SMP acceleration
+
+===========================================================
+*/
+
+#import <pthread.h>
+
+#define USE_MACH_PORTS 1
+
+// This is a small cover layer that makes for easier calling
+
+typedef struct _MsgPort {
+#if USE_MACH_PORTS
+ mach_port_t port;
+ id nsPort;
+#else
+ pthread_mutex_t mutex;
+ pthread_cond_t condition;
+ volatile unsigned int status;
+ unsigned int msgCode;
+ void *msgData;
+#endif
+} MsgPort;
+
+static BOOL portsInited = NO;
+static pthread_mutex_t logMutex;
+
+static unsigned int renderMsgOutstanding;
+static unsigned int rendererProcessingCommand;
+
+static MsgPort rendererMsgPort;
+static MsgPort frontEndMsgPort;
+
+enum {
+ MsgNone,
+ MsgPending,
+};
+
+enum {
+ MsgCodeInvalid = 0,
+ RenderCommandMsg = 1,
+ RenderCompletedMsg = 2,
+};
+
+static /*inline*/ void MsgPortInit(MsgPort *port)
+{
+#if USE_MACH_PORTS
+ port->nsPort = [[NSMachPort alloc] init];
+ port->port = [port->nsPort machPort];
+
+ //rc = mach_port_allocate(mach_task_self(), MACH_PORT_TYPE_SEND_RECEIVE, &port->port);
+ //if (rc) {
+ // fprintf(stderr, "MsgPortInit: mach_port_allocate returned: %d: %s \n",rc, mach_error_string(rc));
+ // }
+#else
+ int rc;
+ rc = pthread_mutex_init(&port->mutex, NULL);
+ if (rc) {
+ ri.Printf(PRINT_ALL, "MsgPortInit: pthread_mutex_init returned: %d: %s\n", rc, strerror(rc));
+ }
+ rc = pthread_cond_init(&port->condition, NULL);
+ if (rc) {
+ ri.Printf(PRINT_ALL, "EventInit: pthread_cond_init returned %d: %s\n", rc, strerror(rc));
+ }
+ port->status = MsgNone;
+ port->msgCode = MsgCodeInvalid;
+ port->msgData = NULL;
+#endif
+}
+
+static /*inline*/ void _SendMsg(MsgPort *port, unsigned int msgCode, void *msgData,
+ const char *functionName, const char *portName, const char *msgName)
+{
+ int rc;
+
+#if USE_MACH_PORTS
+ mach_msg_header_t msg;
+
+ //printf("SendMsg: %s %s %s (%d %08lx)\n",functionName, portName, msgName, msgCode, msgData);
+/*
+ typedef struct
+ {
+ mach_msg_bits_t msgh_bits;
+ mach_msg_size_t msgh_size;
+ mach_port_t msgh_remote_port;
+ mach_port_t msgh_local_port;
+ mach_msg_size_t msgh_reserved;
+ mach_msg_id_t msgh_id;
+ } mach_msg_header_t;
+*/
+ msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE);
+ msg.msgh_size=sizeof(msg);
+ //msg.msg_type=MSG_TYPE_NORMAL;
+ msg.msgh_local_port=MACH_PORT_NULL;
+ msg.msgh_remote_port=port->port;
+ msg.msgh_reserved = 0;
+ msg.msgh_id=(mach_msg_id_t)msgData; // HACK
+
+ rc = mach_msg_send(&msg);
+ if(rc) {
+ fprintf(stderr,"SendMsg: mach_msg_send returned %d: %s\n", rc, mach_error_string(rc));
+ }
+#else
+ //printf("SendMsg: %s %s %s (%d %08lx)\n",functionName, portName, msgName, msgCode, msgData);
+ rc = pthread_mutex_lock(&port->mutex);
+ if(rc) {
+ fprintf(stderr,"SendMsg: pthread_mutex_lock returned %d: %s\n", rc, strerror(rc));
+ }
+
+ /* Block until port is empty */
+ while(port->status != MsgNone) {
+ //fprintf(stderr, "SendMsg: %s blocking until port %s is empty\n", functionName, portName);
+ rc = pthread_cond_wait(&port->condition, &port->mutex);
+ if(rc) {
+ fprintf(stderr, "SendMsg: pthread_cond_wait returned %d: %s\n", rc, strerror(rc));
+ }
+ }
+
+ /* Queue msg */
+ port->msgCode = msgCode;
+ port->msgData = msgData;
+ port->status = MsgPending;
+
+ /* Unlock port */
+ rc = pthread_mutex_unlock(&port->mutex);
+ if(rc) {
+ fprintf(stderr, "SendMsg: pthread_mutex_unlock returned %d: %s\n", rc, strerror(rc));
+ }
+
+ /* Wake up any threads blocked waiting for a message */
+ rc = pthread_cond_broadcast(&port->condition);
+ if(rc) {
+ fprintf(stderr, "SendMsg: pthread_cond_broadcast returned %d: %s\n", rc, strerror(rc));
+ }
+#endif
+}
+
+static /*inline*/ void _WaitMsg(MsgPort *port, unsigned int *msgCode, void **msgData,
+ const char *functionName, const char *portName)
+{
+ int rc;
+#if USE_MACH_PORTS
+ mach_msg_empty_rcv_t msg;
+
+ //printf("WaitMsg: %s %s\n",functionName, portName);
+
+ msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE);
+ msg.header.msgh_size= sizeof(msg);
+ //msg.msg_type=MSG_TYPE_NORMAL;
+ msg.header.msgh_local_port=port->port;
+ msg.header.msgh_remote_port=MACH_PORT_NULL;
+ msg.header.msgh_reserved = 0;
+ msg.header.msgh_id=(mach_msg_id_t)msgData; // HACK
+
+ rc = mach_msg_receive(&msg.header);
+ if(rc) {
+ fprintf(stderr,"SendMsg: mach_msg_receive returned %d: %s\n", rc, mach_error_string(rc));
+ }
+
+ *msgData = (void *)msg.header.msgh_id;
+ //printf("WaitMsg: %s %s got %08lx\n",functionName, portName, *msgData);
+#else
+ //printf("WaitMsg: %s %s\n",functionName, portName);
+
+ rc = pthread_mutex_lock(&port->mutex);
+ if(rc) {
+ fprintf(stderr, "WaitMsg: pthread_mutex_lock returned %d: %s\n", rc, strerror(rc));
+ }
+
+ /* Block until port is empty */
+ while(port->status != MsgPending) {
+ rc = pthread_cond_wait(&port->condition, &port->mutex);
+ if(rc) {
+ fprintf(stderr, "WaitMsg: pthread_cond_wait returned %d: %s\n", rc, strerror(rc));
+ }
+ }
+
+ /* Remove msg */
+ *msgCode = port->msgCode;
+ *msgData = port->msgData;
+
+ //printf("WaitMsg: %s %s got %d %08lx\n",functionName, portName, *msgCode, *msgData);
+
+ port->status = MsgNone;
+ port->msgCode = 0;
+ port->msgData = NULL;
+
+ rc = pthread_mutex_unlock(&port->mutex);
+ if(rc) {
+ fprintf(stderr, "WaitMsg: pthread_mutex_unlock returned %d: %s\n", rc, strerror(rc));
+ }
+
+ /* Wake up any threads blocked waiting for port to be empty. */
+ rc = pthread_cond_broadcast(&port->condition);
+ if(rc) {
+ fprintf(stderr, "SendMsg: pthread_cond_broadcast returned %d: %s\n", rc, strerror(rc));
+ }
+#endif
+}
+
+
+#define SendMsg(p, c, d) _SendMsg(p, c, d, __PRETTY_FUNCTION__, #p, #c)
+#define WaitMsg(p, c, d) _WaitMsg(p, c, d, __PRETTY_FUNCTION__, #p)
+
+#if 0
+static void _Log(const char *msg)
+{
+ int rc;
+
+ rc = pthread_mutex_lock(&logMutex);
+ if (rc)
+ ri.Printf(PRINT_ALL, "_Log: pthread_mutex_lock returned %d: %s\n", rc, strerror(rc));
+
+ fputs(msg,stderr);
+ fflush(stderr);
+
+ rc = pthread_mutex_unlock(&logMutex);
+ if (rc)
+ ri.Printf(PRINT_ALL, "_Log: pthread_mutex_unlock returned %d: %s\n", rc, strerror(rc));
+}
+#endif
+
+
+//
+// The main Q3 SMP API
+//
+
+static void (*glimpRenderThread)( void ) = NULL;
+
+static void *GLimp_RenderThreadWrapper(void *arg)
+{
+ Com_Printf("Render thread starting\n");
+
+ glimpRenderThread();
+
+#ifndef USE_CGLMACROS
+ // Unbind the context before we die
+ OSX_GLContextClearCurrent();
+#endif
+
+ // Send one last message back to front end before we die...
+ // This is somewhat of a hack.. fixme.
+ if (rendererProcessingCommand) {
+ SendMsg(&frontEndMsgPort, RenderCompletedMsg, NULL);
+ rendererProcessingCommand = NO;
+ }
+
+ Com_Printf("Render thread terminating\n");
+
+ return arg;
+}
+
+qboolean GLimp_SpawnRenderThread( void (*function)( void ) )
+{
+ pthread_t renderThread;
+ int rc;
+
+ if (!portsInited) {
+ portsInited = YES;
+ MsgPortInit(&rendererMsgPort);
+ MsgPortInit(&frontEndMsgPort);
+ renderMsgOutstanding = NO;
+ rendererProcessingCommand = NO;
+ pthread_mutex_init(&logMutex, NULL);
+ }
+
+ glimpRenderThread = function;
+
+ rc = pthread_create(&renderThread,
+ NULL, // attributes
+ GLimp_RenderThreadWrapper,
+ NULL); // argument
+ if (rc) {
+ ri.Printf(PRINT_ALL, "pthread_create returned %d: %s", rc, strerror(rc));
+ return qfalse;
+ } else {
+ rc = pthread_detach(renderThread);
+ if (rc) {
+ ri.Printf(PRINT_ALL, "pthread_detach returned %d: %s", rc, strerror(rc));
+ }
+ }
+
+ return qtrue;
+}
+
+static volatile void *smpData;
+
+// TJW - This is calling in the rendering thread to wait until another
+// command buffer is ready. The command buffer returned might be NULL,
+// indicating that the rendering thread should exit.
+void *GLimp_RendererSleep(void)
+{
+ //_Log(__PRETTY_FUNCTION__ " entered");
+ unsigned int msgCode;
+ void *msgData;
+
+ GLSTAMP("GLimp_RendererSleep start", 0);
+
+#ifndef USE_CGLMACROS
+ // Clear the current context while we sleep so the main thread can access it
+ OSX_GLContextClearCurrent();
+#endif
+
+ // Let the main thread we are idle and that no work is queued
+ //_Log("rs0\n");
+ /* If we actually had some work to do, then tell the front end we completed it. */
+ if (rendererProcessingCommand) {
+ SendMsg(&frontEndMsgPort, RenderCompletedMsg, NULL);
+ rendererProcessingCommand = NO;
+ }
+
+ // Wait for new msg
+ for (;;) {
+ WaitMsg(&rendererMsgPort, &msgCode, &msgData);
+ if (1 || msgCode == RenderCommandMsg) {
+ smpData = msgData;
+ break;
+ } else {
+ printf("renderer received unknown message: %d\n",msgCode);
+ }
+ }
+
+#ifndef USE_CGLMACROS
+ // We are going to render a frame... retake the context
+ OSX_GLContextSetCurrent();
+#endif
+
+ rendererProcessingCommand = YES;
+
+ GLSTAMP("GLimp_RendererSleep end", 0);
+
+ return (void *)smpData;
+}
+
+
+// TJW - This is from the main thread to wait until the rendering thread
+// has completed the command buffer that it has
+void GLimp_FrontEndSleep(void)
+{
+ unsigned int msgCode;
+ void *msgData;
+
+ GLSTAMP("GLimp_FrontEndSleep start", 1);
+
+ if (renderMsgOutstanding) {
+ for (;;) {
+ WaitMsg(&frontEndMsgPort, &msgCode, &msgData);
+ if(1 || msgCode == RenderCompletedMsg) {
+ break;
+ } else {
+ printf("front end received unknown message: %d\n",msgCode);
+ }
+ }
+ renderMsgOutstanding = NO;
+ }
+
+#ifndef USE_CGLMACROS
+ // We are done waiting for the background thread, take the current context back.
+ OSX_GLContextSetCurrent();
+#endif
+
+ GLSTAMP("GLimp_FrontEndSleep end", 1);
+}
+
+
+// TJW - This is called in the main thread to issue another command
+// buffer to the rendering thread. This is always called AFTER
+// GLimp_FrontEndSleep, so we know that there is no command
+// pending in 'smpData'.
+void GLimp_WakeRenderer( void *data )
+{
+ GLSTAMP("GLimp_WakeRenderer start", 1);
+
+#ifndef USE_CGLMACROS
+ // We want the background thread to draw stuff. Give up the current context
+ OSX_GLContextClearCurrent();
+#endif
+
+ SendMsg(&rendererMsgPort, RenderCommandMsg, data);
+
+ // Don't set flag saying that the renderer is processing something if it's just
+ // being told to exit.
+ //if(data != NULL)
+ renderMsgOutstanding = YES;
+
+ GLSTAMP("GLimp_WakeRenderer end", 1);
+}