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 }