/* =========================================================================== 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 #import #import #import #warning Using Mach Ports SMP acceleration implementation /* =========================================================== SMP acceleration =========================================================== */ #import #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); }