Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

macosx_glsmp_ports.m (12254B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 #import "macosx_glimp.h"
     23 
     24 #include "tr_local.h"
     25 #import "macosx_local.h"
     26 #import "macosx_display.h"
     27 
     28 #import <AppKit/AppKit.h>
     29 #import <Foundation/Foundation.h>
     30 #import <mach/mach.h>
     31 #import <mach/mach_error.h>
     32 
     33 
     34 #warning Using Mach Ports SMP acceleration implementation
     35 
     36 /*
     37 ===========================================================
     38 
     39 SMP acceleration
     40 
     41 ===========================================================
     42 */
     43 
     44 #import <pthread.h>
     45 
     46 #define USE_MACH_PORTS 1
     47 
     48 // This is a small cover layer that makes for easier calling
     49 
     50 typedef struct _MsgPort {
     51 #if USE_MACH_PORTS    
     52     mach_port_t port; 
     53     id nsPort;
     54 #else    
     55     pthread_mutex_t mutex;
     56     pthread_cond_t condition;
     57     volatile unsigned int   status;
     58     unsigned int   msgCode;
     59     void          *msgData;
     60 #endif    
     61 } MsgPort;
     62  
     63 static BOOL portsInited = NO;
     64 static pthread_mutex_t logMutex;
     65 
     66 static unsigned int renderMsgOutstanding;
     67 static unsigned int rendererProcessingCommand;
     68 
     69 static MsgPort rendererMsgPort;
     70 static MsgPort frontEndMsgPort;
     71 
     72 enum {
     73     MsgNone,
     74     MsgPending,
     75 };
     76 
     77 enum {
     78     MsgCodeInvalid = 0,
     79     RenderCommandMsg = 1,
     80     RenderCompletedMsg = 2,
     81 };
     82 
     83 static /*inline*/ void MsgPortInit(MsgPort *port)
     84 {
     85 #if USE_MACH_PORTS  
     86     port->nsPort = [[NSMachPort alloc] init];
     87     port->port = [port->nsPort machPort];
     88       
     89     //rc = mach_port_allocate(mach_task_self(), MACH_PORT_TYPE_SEND_RECEIVE, &port->port);
     90     //if (rc) {
     91     //  fprintf(stderr, "MsgPortInit: mach_port_allocate returned: %d: %s \n",rc, mach_error_string(rc));
     92    // }
     93 #else
     94     int rc;
     95     rc = pthread_mutex_init(&port->mutex, NULL);
     96     if (rc) {
     97         ri.Printf(PRINT_ALL, "MsgPortInit: pthread_mutex_init returned: %d: %s\n", rc, strerror(rc));
     98     }
     99     rc = pthread_cond_init(&port->condition, NULL);
    100     if (rc) {
    101         ri.Printf(PRINT_ALL, "EventInit: pthread_cond_init returned %d: %s\n", rc, strerror(rc));
    102     }
    103     port->status = MsgNone;
    104     port->msgCode = MsgCodeInvalid;
    105     port->msgData = NULL;
    106 #endif    
    107 }
    108 
    109 static /*inline*/ void _SendMsg(MsgPort *port, unsigned int msgCode, void *msgData, 
    110                          const char *functionName, const char *portName, const char *msgName)
    111 {
    112     int rc;
    113     
    114 #if USE_MACH_PORTS
    115     mach_msg_header_t msg;
    116 
    117     //printf("SendMsg: %s %s %s (%d %08lx)\n",functionName, portName, msgName, msgCode, msgData);
    118 /*
    119  typedef	struct
    120  {
    121    mach_msg_bits_t	msgh_bits;
    122    mach_msg_size_t	msgh_size;
    123    mach_port_t		msgh_remote_port;
    124    mach_port_t		msgh_local_port;
    125    mach_msg_size_t 	msgh_reserved;
    126    mach_msg_id_t		msgh_id;
    127  } mach_msg_header_t;
    128 */
    129     msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE);
    130     msg.msgh_size=sizeof(msg);
    131     //msg.msg_type=MSG_TYPE_NORMAL;
    132     msg.msgh_local_port=MACH_PORT_NULL;
    133     msg.msgh_remote_port=port->port;
    134     msg.msgh_reserved = 0;
    135     msg.msgh_id=(mach_msg_id_t)msgData; // HACK
    136 
    137     rc = mach_msg_send(&msg);
    138     if(rc) {
    139         fprintf(stderr,"SendMsg: mach_msg_send returned %d: %s\n", rc, mach_error_string(rc));
    140     }
    141 #else    
    142     //printf("SendMsg: %s %s %s (%d %08lx)\n",functionName, portName, msgName, msgCode, msgData);
    143     rc = pthread_mutex_lock(&port->mutex);
    144     if(rc) {
    145         fprintf(stderr,"SendMsg: pthread_mutex_lock returned %d: %s\n", rc, strerror(rc));
    146     }
    147     
    148     /* Block until port is empty */
    149     while(port->status != MsgNone) {
    150       //fprintf(stderr, "SendMsg: %s blocking until port %s is empty\n", functionName, portName);      
    151       rc = pthread_cond_wait(&port->condition, &port->mutex);
    152       if(rc) {
    153         fprintf(stderr, "SendMsg: pthread_cond_wait returned %d: %s\n", rc, strerror(rc));
    154       }
    155     }
    156     
    157     /* Queue msg */
    158     port->msgCode = msgCode;
    159     port->msgData = msgData;
    160     port->status = MsgPending;
    161     
    162     /* Unlock port */
    163     rc = pthread_mutex_unlock(&port->mutex);
    164     if(rc) {
    165         fprintf(stderr, "SendMsg: pthread_mutex_unlock returned %d: %s\n", rc, strerror(rc));
    166     }    
    167 
    168     /* Wake up any threads blocked waiting for a message */
    169     rc = pthread_cond_broadcast(&port->condition);
    170     if(rc) {
    171        fprintf(stderr, "SendMsg: pthread_cond_broadcast returned %d: %s\n", rc, strerror(rc));
    172     }
    173 #endif            
    174 }
    175 
    176 static /*inline*/ void _WaitMsg(MsgPort *port, unsigned int *msgCode, void **msgData, 
    177                                 const char *functionName, const char *portName)
    178 {
    179     int rc;
    180 #if USE_MACH_PORTS
    181     mach_msg_empty_rcv_t msg;
    182 
    183     //printf("WaitMsg: %s %s\n",functionName, portName);
    184     
    185     msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE);
    186     msg.header.msgh_size= sizeof(msg);
    187     //msg.msg_type=MSG_TYPE_NORMAL;
    188     msg.header.msgh_local_port=port->port;
    189     msg.header.msgh_remote_port=MACH_PORT_NULL;
    190     msg.header.msgh_reserved = 0;
    191     msg.header.msgh_id=(mach_msg_id_t)msgData; // HACK
    192 
    193     rc = mach_msg_receive(&msg.header);
    194     if(rc) {
    195         fprintf(stderr,"SendMsg: mach_msg_receive returned %d: %s\n", rc, mach_error_string(rc));
    196     }
    197 
    198     *msgData = (void *)msg.header.msgh_id;
    199     //printf("WaitMsg: %s %s got %08lx\n",functionName, portName, *msgData);
    200 #else   
    201     //printf("WaitMsg: %s %s\n",functionName, portName);
    202     
    203     rc = pthread_mutex_lock(&port->mutex);
    204     if(rc) {
    205         fprintf(stderr, "WaitMsg: pthread_mutex_lock returned %d: %s\n", rc, strerror(rc));
    206     }
    207     
    208     /* Block until port is empty */
    209     while(port->status != MsgPending) {
    210       rc = pthread_cond_wait(&port->condition, &port->mutex);
    211       if(rc) {
    212         fprintf(stderr, "WaitMsg: pthread_cond_wait returned %d: %s\n", rc, strerror(rc));
    213       }
    214     }
    215     
    216     /* Remove msg */
    217     *msgCode = port->msgCode;
    218     *msgData = port->msgData;
    219 
    220     //printf("WaitMsg: %s %s got %d %08lx\n",functionName, portName, *msgCode, *msgData);
    221 
    222     port->status = MsgNone;
    223     port->msgCode = 0;
    224     port->msgData = NULL;
    225     
    226     rc = pthread_mutex_unlock(&port->mutex);
    227     if(rc) {
    228         fprintf(stderr, "WaitMsg: pthread_mutex_unlock returned %d: %s\n", rc, strerror(rc));
    229     }    
    230 
    231     /* Wake up any threads blocked waiting for port to be empty. */
    232     rc = pthread_cond_broadcast(&port->condition);
    233     if(rc) {
    234        fprintf(stderr, "SendMsg: pthread_cond_broadcast returned %d: %s\n", rc, strerror(rc));
    235     }
    236 #endif            
    237 }
    238 
    239 
    240 #define SendMsg(p, c, d) _SendMsg(p, c, d, __PRETTY_FUNCTION__, #p, #c)
    241 #define WaitMsg(p, c, d) _WaitMsg(p, c, d, __PRETTY_FUNCTION__, #p)
    242 
    243 #if 0
    244 static void _Log(const char *msg)
    245 {
    246     int rc;
    247     
    248     rc = pthread_mutex_lock(&logMutex);
    249     if (rc)
    250         ri.Printf(PRINT_ALL, "_Log: pthread_mutex_lock returned %d: %s\n", rc, strerror(rc));
    251 
    252     fputs(msg,stderr);
    253     fflush(stderr);
    254     
    255     rc = pthread_mutex_unlock(&logMutex);
    256     if (rc)
    257         ri.Printf(PRINT_ALL, "_Log: pthread_mutex_unlock returned %d: %s\n", rc, strerror(rc));
    258 }
    259 #endif
    260 
    261 
    262 //
    263 // The main Q3 SMP API
    264 //
    265 
    266 static void (*glimpRenderThread)( void ) = NULL;
    267 
    268 static void *GLimp_RenderThreadWrapper(void *arg)
    269 {
    270     Com_Printf("Render thread starting\n");
    271 
    272     glimpRenderThread();
    273 
    274 #ifndef USE_CGLMACROS
    275     // Unbind the context before we die
    276     OSX_GLContextClearCurrent();
    277 #endif
    278 
    279     // Send one last message back to front end before we die...
    280     // This is somewhat of a hack.. fixme.
    281     if (rendererProcessingCommand) {
    282         SendMsg(&frontEndMsgPort, RenderCompletedMsg, NULL);
    283         rendererProcessingCommand = NO;
    284     }
    285 
    286     Com_Printf("Render thread terminating\n");
    287 	
    288     return arg;
    289 }
    290 
    291 qboolean GLimp_SpawnRenderThread( void (*function)( void ) )
    292 {
    293     pthread_t renderThread;
    294     int       rc;
    295 
    296     if (!portsInited) {
    297         portsInited = YES;
    298         MsgPortInit(&rendererMsgPort);
    299         MsgPortInit(&frontEndMsgPort);
    300         renderMsgOutstanding = NO;
    301         rendererProcessingCommand = NO;
    302         pthread_mutex_init(&logMutex, NULL);
    303     }
    304     
    305     glimpRenderThread = function;
    306 
    307     rc = pthread_create(&renderThread,
    308                         NULL, // attributes
    309                         GLimp_RenderThreadWrapper,
    310                         NULL); // argument
    311     if (rc) {
    312         ri.Printf(PRINT_ALL, "pthread_create returned %d: %s", rc, strerror(rc));
    313         return qfalse;
    314     } else {
    315         rc = pthread_detach(renderThread);
    316         if (rc) {
    317             ri.Printf(PRINT_ALL, "pthread_detach returned %d: %s", rc, strerror(rc));
    318         }
    319     }
    320 
    321     return qtrue;
    322 }
    323 
    324 static	volatile void	*smpData;
    325 
    326 // TJW - This is calling in the rendering thread to wait until another
    327 // command buffer is ready.  The command buffer returned might be NULL,
    328 // indicating that the rendering thread should exit.
    329 void *GLimp_RendererSleep(void)
    330 {
    331     //_Log(__PRETTY_FUNCTION__ " entered");
    332     unsigned int msgCode;
    333     void *msgData;
    334     
    335     GLSTAMP("GLimp_RendererSleep start", 0);
    336 
    337 #ifndef USE_CGLMACROS
    338     // Clear the current context while we sleep so the main thread can access it
    339     OSX_GLContextClearCurrent();
    340 #endif
    341 
    342     // Let the main thread we are idle and that no work is queued
    343     //_Log("rs0\n");
    344     /* If we actually had some work to do, then tell the front end we completed it. */
    345     if (rendererProcessingCommand) {
    346         SendMsg(&frontEndMsgPort, RenderCompletedMsg, NULL);
    347         rendererProcessingCommand = NO;
    348     }
    349     
    350     // Wait for new msg
    351     for (;;) {
    352         WaitMsg(&rendererMsgPort, &msgCode, &msgData);
    353         if (1 || msgCode == RenderCommandMsg) {
    354             smpData = msgData;
    355             break;
    356         } else {
    357             printf("renderer received unknown message: %d\n",msgCode);
    358         }
    359     }
    360     
    361 #ifndef USE_CGLMACROS
    362     // We are going to render a frame... retake the context
    363     OSX_GLContextSetCurrent();
    364 #endif
    365 
    366     rendererProcessingCommand = YES;
    367     
    368     GLSTAMP("GLimp_RendererSleep end", 0);
    369 
    370     return (void *)smpData;
    371 }
    372 
    373 
    374 // TJW - This is from the main thread to wait until the rendering thread
    375 // has completed the command buffer that it has
    376 void GLimp_FrontEndSleep(void)
    377 {
    378     unsigned int msgCode;
    379     void *msgData;
    380     
    381     GLSTAMP("GLimp_FrontEndSleep start", 1);
    382 
    383     if (renderMsgOutstanding) {
    384         for (;;) {
    385             WaitMsg(&frontEndMsgPort, &msgCode, &msgData);
    386             if(1 || msgCode == RenderCompletedMsg) {
    387                 break;
    388             } else {
    389                 printf("front end received unknown message: %d\n",msgCode);
    390             }
    391         }
    392         renderMsgOutstanding = NO;
    393     }
    394 
    395 #ifndef USE_CGLMACROS
    396     // We are done waiting for the background thread, take the current context back.
    397     OSX_GLContextSetCurrent();
    398 #endif
    399 
    400     GLSTAMP("GLimp_FrontEndSleep end", 1);
    401 }
    402 
    403 
    404 // TJW - This is called in the main thread to issue another command
    405 // buffer to the rendering thread.  This is always called AFTER
    406 // GLimp_FrontEndSleep, so we know that there is no command
    407 // pending in 'smpData'.
    408 void GLimp_WakeRenderer( void *data )
    409 {
    410     GLSTAMP("GLimp_WakeRenderer start", 1);
    411 
    412 #ifndef USE_CGLMACROS
    413     // We want the background thread to draw stuff.  Give up the current context
    414     OSX_GLContextClearCurrent();
    415 #endif
    416 
    417     SendMsg(&rendererMsgPort, RenderCommandMsg, data);   
    418 	
    419     // Don't set flag saying that the renderer is processing something if it's just
    420     // being told to exit.
    421     //if(data != NULL)     
    422     renderMsgOutstanding = YES;
    423 
    424     GLSTAMP("GLimp_WakeRenderer end", 1);
    425 }