diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Additions/common/crOpenGL/load.c | 1526 |
1 files changed, 1526 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/crOpenGL/load.c b/src/VBox/Additions/common/crOpenGL/load.c new file mode 100644 index 00000000..a4b0cf8c --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/load.c @@ -0,0 +1,1526 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_spu.h" +#include "cr_net.h" +#include "cr_error.h" +#include "cr_mem.h" +#include "cr_string.h" +#include "cr_net.h" +#include "cr_environment.h" +#include "cr_process.h" +#include "cr_rand.h" +#include "cr_netserver.h" +#include "stub.h" +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <iprt/initterm.h> +#include <iprt/thread.h> +#include <iprt/errcore.h> +#include <iprt/asm.h> +#ifndef WINDOWS +# include <sys/types.h> +# include <unistd.h> +#endif + +#ifdef VBOX_WITH_WDDM +#include <d3d9types.h> +#include <D3dumddi.h> +#endif + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) +# include <VBoxCrHgsmi.h> +#endif + +/** + * If you change this, see the comments in tilesortspu_context.c + */ +#define MAGIC_CONTEXT_BASE 500 + +#define CONFIG_LOOKUP_FILE ".crconfigs" + +#ifdef WINDOWS +#define PYTHON_EXE "python.exe" +#else +#define PYTHON_EXE "python" +#endif + +static bool stub_initialized = 0; +#ifdef WINDOWS +static CRmutex stub_init_mutex; +#define STUB_INIT_LOCK() do { crLockMutex(&stub_init_mutex); } while (0) +#define STUB_INIT_UNLOCK() do { crUnlockMutex(&stub_init_mutex); } while (0) +#else +#define STUB_INIT_LOCK() do { } while (0) +#define STUB_INIT_UNLOCK() do { } while (0) +#endif + +/* NOTE: 'SPUDispatchTable glim' is declared in NULLfuncs.py now */ +/* NOTE: 'SPUDispatchTable stubThreadsafeDispatch' is declared in tsfuncs.c */ +Stub stub; +#ifdef CHROMIUM_THREADSAFE +static bool g_stubIsCurrentContextTSDInited; +CRtsd g_stubCurrentContextTSD; +#endif + + +#ifndef VBOX_NO_NATIVEGL +static void stubInitNativeDispatch( void ) +{ +# define MAX_FUNCS 1000 + SPUNamedFunctionTable gl_funcs[MAX_FUNCS]; + int numFuncs; + + numFuncs = crLoadOpenGL( &stub.wsInterface, gl_funcs ); + + stub.haveNativeOpenGL = (numFuncs > 0); + + /* XXX call this after context binding */ + numFuncs += crLoadOpenGLExtensions( &stub.wsInterface, gl_funcs + numFuncs ); + + CRASSERT(numFuncs < MAX_FUNCS); + + crSPUInitDispatchTable( &stub.nativeDispatch ); + crSPUInitDispatch( &stub.nativeDispatch, gl_funcs ); + crSPUInitDispatchNops( &stub.nativeDispatch ); +# undef MAX_FUNCS +} +#endif /* !VBOX_NO_NATIVEGL */ + + +/** Pointer to the SPU's real glClear and glViewport functions */ +static ClearFunc_t origClear; +static ViewportFunc_t origViewport; +static SwapBuffersFunc_t origSwapBuffers; +static DrawBufferFunc_t origDrawBuffer; +static ScissorFunc_t origScissor; + +static void stubCheckWindowState(WindowInfo *window, GLboolean bFlushOnChange) +{ + bool bForceUpdate = false; + bool bChanged = false; + +#ifdef WINDOWS + /** @todo install hook and track for WM_DISPLAYCHANGE */ + { + DEVMODE devMode; + + devMode.dmSize = sizeof(DEVMODE); + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode); + + if (devMode.dmPelsWidth!=window->dmPelsWidth || devMode.dmPelsHeight!=window->dmPelsHeight) + { + crDebug("Resolution changed(%d,%d), forcing window Pos/Size update", devMode.dmPelsWidth, devMode.dmPelsHeight); + window->dmPelsWidth = devMode.dmPelsWidth; + window->dmPelsHeight = devMode.dmPelsHeight; + bForceUpdate = true; + } + } +#endif + + bChanged = stubUpdateWindowGeometry(window, bForceUpdate) || bForceUpdate; + +#if defined(GLX) || defined (WINDOWS) + if (stub.trackWindowVisibleRgn) + { + bChanged = stubUpdateWindowVisibileRegions(window) || bChanged; + } +#endif + + if (stub.trackWindowVisibility && window->type == CHROMIUM && window->drawable) { + const int mapped = stubIsWindowVisible(window); + if (mapped != window->mapped) { + crDebug("Dispatched: WindowShow(%i, %i)", window->spuWindow, mapped); + stub.spu->dispatch_table.WindowShow(window->spuWindow, mapped); + window->mapped = mapped; + bChanged = true; + } + } + + if (bFlushOnChange && bChanged) + { + stub.spu->dispatch_table.Flush(); + } +} + +static bool stubSystemWindowExist(WindowInfo *pWindow) +{ +#ifdef WINDOWS + if (pWindow->hWnd!=WindowFromDC(pWindow->drawable)) + { + return false; + } +#else + Window root; + int x, y; + unsigned int border, depth, w, h; + Display *dpy; + + dpy = stubGetWindowDisplay(pWindow); + + XLOCK(dpy); + if (!XGetGeometry(dpy, pWindow->drawable, &root, &x, &y, &w, &h, &border, &depth)) + { + XUNLOCK(dpy); + return false; + } + XUNLOCK(dpy); +#endif + + return true; +} + +static void stubCheckWindowsCB(unsigned long key, void *data1, void *data2) +{ + WindowInfo *pWindow = (WindowInfo *) data1; + ContextInfo *pCtx = (ContextInfo *) data2; + (void)key; + + if (pWindow == pCtx->currentDrawable + || pWindow->type!=CHROMIUM + || pWindow->pOwner!=pCtx) + { + return; + } + + if (!stubSystemWindowExist(pWindow)) + { +#ifdef WINDOWS + stubDestroyWindow(CR_CTX_CON(pCtx), (GLint)pWindow->hWnd); +#else + stubDestroyWindow(CR_CTX_CON(pCtx), (GLint)pWindow->drawable); +#endif + return; + } + + stubCheckWindowState(pWindow, GL_FALSE); +} + +static void stubCheckWindowsState(void) +{ + ContextInfo *context = stubGetCurrentContext(); + + CRASSERT(stub.trackWindowSize || stub.trackWindowPos); + + if (!context) + return; + +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + if (stub.bRunningUnderWDDM) + return; +#endif + + /* Try to keep a consistent locking order. */ + crHashtableLock(stub.windowTable); +#if defined(CR_NEWWINTRACK) && !defined(WINDOWS) + crLockMutex(&stub.mutex); +#endif + + stubCheckWindowState(context->currentDrawable, GL_TRUE); + crHashtableWalkUnlocked(stub.windowTable, stubCheckWindowsCB, context); + +#if defined(CR_NEWWINTRACK) && !defined(WINDOWS) + crUnlockMutex(&stub.mutex); +#endif + crHashtableUnlock(stub.windowTable); +} + + +/** + * Override the head SPU's glClear function. + * We're basically trapping this function so that we can poll the + * application window size at a regular interval. + */ +static void SPU_APIENTRY trapClear(GLbitfield mask) +{ + stubCheckWindowsState(); + /* call the original SPU glClear function */ + origClear(mask); +} + +/** + * As above, but for glViewport. Most apps call glViewport before + * glClear when a window is resized. + */ +static void SPU_APIENTRY trapViewport(GLint x, GLint y, GLsizei w, GLsizei h) +{ + stubCheckWindowsState(); + /* call the original SPU glViewport function */ + origViewport(x, y, w, h); +} + +/*static void SPU_APIENTRY trapSwapBuffers(GLint window, GLint flags) +{ + stubCheckWindowsState(); + origSwapBuffers(window, flags); +} + +static void SPU_APIENTRY trapDrawBuffer(GLenum buf) +{ + stubCheckWindowsState(); + origDrawBuffer(buf); +}*/ + +#if 0 /* unused */ +static void SPU_APIENTRY trapScissor(GLint x, GLint y, GLsizei w, GLsizei h) +{ + int winX, winY; + unsigned int winW, winH; + WindowInfo *pWindow; + ContextInfo *context = stubGetCurrentContext(); + (void)x; (void)y; (void)w; (void)h; + + pWindow = context->currentDrawable; + stubGetWindowGeometry(pWindow, &winX, &winY, &winW, &winH); + origScissor(0, 0, winW, winH); +} +#endif /* unused */ + +/** + * Use the GL function pointers in \<spu\> to initialize the static glim + * dispatch table. + */ +static void stubInitSPUDispatch(SPU *spu) +{ + crSPUInitDispatchTable( &stub.spuDispatch ); + crSPUCopyDispatchTable( &stub.spuDispatch, &(spu->dispatch_table) ); + + if (stub.trackWindowSize || stub.trackWindowPos || stub.trackWindowVisibleRgn) { + /* patch-in special glClear/Viewport function to track window sizing */ + origClear = stub.spuDispatch.Clear; + origViewport = stub.spuDispatch.Viewport; + origSwapBuffers = stub.spuDispatch.SwapBuffers; + origDrawBuffer = stub.spuDispatch.DrawBuffer; + origScissor = stub.spuDispatch.Scissor; + stub.spuDispatch.Clear = trapClear; + stub.spuDispatch.Viewport = trapViewport; + + /*stub.spuDispatch.SwapBuffers = trapSwapBuffers; + stub.spuDispatch.DrawBuffer = trapDrawBuffer;*/ + } + + crSPUCopyDispatchTable( &glim, &stub.spuDispatch ); +} + +#if 0 /** @todo stubSPUTearDown & stubSPUTearDownLocked are not referenced */ + +// Callback function, used to destroy all created contexts +static void hsWalkStubDestroyContexts(unsigned long key, void *data1, void *data2) +{ + (void)data1; (void)data2; + stubDestroyContext(key); +} + +/** + * This is called when we exit. + * We call all the SPU's cleanup functions. + */ +static void stubSPUTearDownLocked(void) +{ + crDebug("stubSPUTearDownLocked"); + +#ifdef WINDOWS +# ifndef CR_NEWWINTRACK + stubUninstallWindowMessageHook(); +# endif +#endif + +#ifdef CR_NEWWINTRACK + ASMAtomicWriteBool(&stub.bShutdownSyncThread, true); +#endif + + //delete all created contexts + stubMakeCurrent( NULL, NULL); + + /* the lock order is windowTable->contextTable (see wglMakeCurrent_prox, glXMakeCurrent) + * this is why we need to take a windowTable lock since we will later do stub.windowTable access & locking */ + crHashtableLock(stub.windowTable); + crHashtableWalk(stub.contextTable, hsWalkStubDestroyContexts, NULL); + crHashtableUnlock(stub.windowTable); + + /* shutdown, now trap any calls to a NULL dispatcher */ + crSPUCopyDispatchTable(&glim, &stubNULLDispatch); + + crSPUUnloadChain(stub.spu); + stub.spu = NULL; + +#ifndef Linux + crUnloadOpenGL(); +#endif + +#ifndef WINDOWS + crNetTearDown(); +#endif + +#ifdef GLX + if (stub.xshmSI.shmid>=0) + { + shmctl(stub.xshmSI.shmid, IPC_RMID, 0); + shmdt(stub.xshmSI.shmaddr); + } + crFreeHashtable(stub.pGLXPixmapsHash, crFree); +#endif + + crFreeHashtable(stub.windowTable, crFree); + crFreeHashtable(stub.contextTable, NULL); + + crMemset(&stub, 0, sizeof(stub)); + +} + +/** + * This is called when we exit. + * We call all the SPU's cleanup functions. + */ +static void stubSPUTearDown(void) +{ + STUB_INIT_LOCK(); + if (stub_initialized) + { + stubSPUTearDownLocked(); + stub_initialized = 0; + } + STUB_INIT_UNLOCK(); +} + +#endif /** @todo stubSPUTearDown & stubSPUTearDownLocked are not referenced */ + +static void stubSPUSafeTearDown(void) +{ +#ifdef CHROMIUM_THREADSAFE + CRmutex *mutex; +#endif + + if (!stub_initialized) return; + stub_initialized = 0; + +#ifdef CHROMIUM_THREADSAFE + mutex = &stub.mutex; + crLockMutex(mutex); +#endif + crDebug("stubSPUSafeTearDown"); + +#ifdef WINDOWS +# ifndef CR_NEWWINTRACK + stubUninstallWindowMessageHook(); +# endif +#endif + +#if defined(CR_NEWWINTRACK) + crUnlockMutex(mutex); +# if defined(WINDOWS) + if (stub.hSyncThread && RTThreadGetState(stub.hSyncThread)!=RTTHREADSTATE_TERMINATED) + { + HANDLE hNative; + DWORD ec=0; + + hNative = OpenThread(SYNCHRONIZE|THREAD_QUERY_INFORMATION|THREAD_TERMINATE, + false, RTThreadGetNative(stub.hSyncThread)); + if (!hNative) + { + crWarning("Failed to get handle for sync thread(%#x)", GetLastError()); + } + else + { + crDebug("Got handle %p for thread %#x", hNative, RTThreadGetNative(stub.hSyncThread)); + } + + ASMAtomicWriteBool(&stub.bShutdownSyncThread, true); + + if (PostThreadMessage(RTThreadGetNative(stub.hSyncThread), WM_QUIT, 0, 0)) + { + RTThreadWait(stub.hSyncThread, 1000, NULL); + + /*Same issue as on linux, RTThreadWait exits before system thread is terminated, which leads + * to issues as our dll goes to be unloaded. + *@todo + *We usually call this function from DllMain which seems to be holding some lock and thus we have to + * kill thread via TerminateThread. + */ + if (WaitForSingleObject(hNative, 100)==WAIT_TIMEOUT) + { + crDebug("Wait failed, terminating"); + if (!TerminateThread(hNative, 1)) + { + crDebug("TerminateThread failed"); + } + } + if (GetExitCodeThread(hNative, &ec)) + { + crDebug("Thread %p exited with ec=%i", hNative, ec); + } + else + { + crDebug("GetExitCodeThread failed(%#x)", GetLastError()); + } + } + else + { + crDebug("Sync thread killed before DLL_PROCESS_DETACH"); + } + + if (hNative) + { + CloseHandle(hNative); + } + } +#else + if (stub.hSyncThread!=NIL_RTTHREAD) + { + ASMAtomicWriteBool(&stub.bShutdownSyncThread, true); + { + int rc = RTThreadWait(stub.hSyncThread, RT_INDEFINITE_WAIT, NULL); + if (RT_FAILURE(rc)) + { + WARN(("RTThreadWait_join failed %i", rc)); + } + } + } +#endif + crLockMutex(mutex); +#endif + +#ifndef WINDOWS + crNetTearDown(); +#endif + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(mutex); + crFreeMutex(mutex); +#endif + crMemset(&stub, 0, sizeof(stub)); +} + + +static void stubExitHandler(void) +{ + stubSPUSafeTearDown(); + signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); +} + +/** + * Called when we receive a SIGTERM signal. + */ +static void stubSignalHandler(int signo) +{ + (void)signo; + stubSPUSafeTearDown(); + exit(0); /* this causes stubExitHandler() to be called */ +} + +#ifndef RT_OS_WINDOWS +# ifdef CHROMIUM_THREADSAFE +static void stubThreadTlsDtor(void *pvValue) +{ + ContextInfo *pCtx = (ContextInfo*)pvValue; + VBoxTlsRefRelease(pCtx); +} +# endif +#endif + + +/** + * Init variables in the stub structure, install signal handler. + */ +static void stubInitVars(void) +{ + WindowInfo *defaultWin; + +#ifdef CHROMIUM_THREADSAFE + crInitMutex(&stub.mutex); +#endif + + /* At the very least we want CR_RGB_BIT. */ + stub.haveNativeOpenGL = GL_FALSE; + stub.spu = NULL; + stub.appDrawCursor = 0; + stub.minChromiumWindowWidth = 0; + stub.minChromiumWindowHeight = 0; + stub.maxChromiumWindowWidth = 0; + stub.maxChromiumWindowHeight = 0; + stub.matchChromiumWindowCount = 0; + stub.matchChromiumWindowID = NULL; + stub.matchWindowTitle = NULL; + stub.ignoreFreeglutMenus = 0; + stub.threadSafe = GL_FALSE; + stub.trackWindowSize = 0; + stub.trackWindowPos = 0; + stub.trackWindowVisibility = 0; + stub.trackWindowVisibleRgn = 0; + stub.mothershipPID = 0; + stub.spu_dir = NULL; + + stub.freeContextNumber = MAGIC_CONTEXT_BASE; + stub.contextTable = crAllocHashtable(); +#ifndef RT_OS_WINDOWS +# ifdef CHROMIUM_THREADSAFE + if (!g_stubIsCurrentContextTSDInited) + { + crInitTSDF(&g_stubCurrentContextTSD, stubThreadTlsDtor); + g_stubIsCurrentContextTSDInited = true; + } +# endif +#endif + stubSetCurrentContext(NULL); + + stub.windowTable = crAllocHashtable(); + +#ifdef CR_NEWWINTRACK + stub.bShutdownSyncThread = false; + stub.hSyncThread = NIL_RTTHREAD; +#endif + + defaultWin = (WindowInfo *) crCalloc(sizeof(WindowInfo)); + defaultWin->type = CHROMIUM; + defaultWin->spuWindow = 0; /* window 0 always exists */ +#ifdef WINDOWS + defaultWin->hVisibleRegion = INVALID_HANDLE_VALUE; +#elif defined(GLX) + defaultWin->pVisibleRegions = NULL; + defaultWin->cVisibleRegions = 0; +#endif + crHashtableAdd(stub.windowTable, 0, defaultWin); + +#if 1 + atexit(stubExitHandler); + signal(SIGTERM, stubSignalHandler); + signal(SIGINT, stubSignalHandler); +#ifndef WINDOWS + signal(SIGPIPE, SIG_IGN); /* the networking code should catch this */ +#endif +#else + (void) stubExitHandler; + (void) stubSignalHandler; +#endif +} + + +#if 0 /* unused */ + +/** + * Return a free port number for the mothership to use, or -1 if we + * can't find one. + */ +static int +GenerateMothershipPort(void) +{ + const int MAX_PORT = 10100; + unsigned short port; + + /* generate initial port number randomly */ + crRandAutoSeed(); + port = (unsigned short) crRandInt(10001, MAX_PORT); + +#ifdef WINDOWS + /* XXX should implement a free port check here */ + return port; +#else + /* + * See if this port number really is free, try another if needed. + */ + { + struct sockaddr_in servaddr; + int so_reuseaddr = 1; + int sock, k; + + /* create socket */ + sock = socket(AF_INET, SOCK_STREAM, 0); + CRASSERT(sock > 2); + + /* deallocate socket/port when we exit */ + k = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char *) &so_reuseaddr, sizeof(so_reuseaddr)); + CRASSERT(k == 0); + + /* initialize the servaddr struct */ + crMemset(&servaddr, 0, sizeof(servaddr) ); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_ANY); + + while (port < MAX_PORT) { + /* Bind to the given port number, return -1 if we fail */ + servaddr.sin_port = htons((unsigned short) port); + k = bind(sock, (struct sockaddr *) &servaddr, sizeof(servaddr)); + if (k) { + /* failed to create port. try next one. */ + port++; + } + else { + /* free the socket/port now so mothership can make it */ + close(sock); + return port; + } + } + } +#endif /* WINDOWS */ + return -1; +} + + +/** + * Try to determine which mothership configuration to use for this program. + */ +static char ** +LookupMothershipConfig(const char *procName) +{ + const int procNameLen = crStrlen(procName); + FILE *f; + const char *home; + char configPath[1000]; + + /* first, check if the CR_CONFIG env var is set */ + { + const char *conf = crGetenv("CR_CONFIG"); + if (conf && crStrlen(conf) > 0) + return crStrSplit(conf, " "); + } + + /* second, look up config name from config file */ + home = crGetenv("HOME"); + if (home) + sprintf(configPath, "%s/%s", home, CONFIG_LOOKUP_FILE); + else + crStrcpy(configPath, CONFIG_LOOKUP_FILE); /* from current dir */ + /* Check if the CR_CONFIG_PATH env var is set. */ + { + const char *conf = crGetenv("CR_CONFIG_PATH"); + if (conf) + crStrcpy(configPath, conf); /* from env var */ + } + + f = fopen(configPath, "r"); + if (!f) { + return NULL; + } + + while (!feof(f)) { + char line[1000]; + char **args; + fgets(line, 999, f); + line[crStrlen(line) - 1] = 0; /* remove trailing newline */ + if (crStrncmp(line, procName, procNameLen) == 0 && + (line[procNameLen] == ' ' || line[procNameLen] == '\t')) + { + crWarning("Using Chromium configuration for %s from %s", + procName, configPath); + args = crStrSplit(line + procNameLen + 1, " "); + return args; + } + } + fclose(f); + return NULL; +} + + +static int Mothership_Awake = 0; + + +/** + * Signal handler to determine when mothership is ready. + */ +static void +MothershipPhoneHome(int signo) +{ + crDebug("Got signal %d: mothership is awake!", signo); + Mothership_Awake = 1; +} + +#endif /* 0 */ + +static void stubSetDefaultConfigurationOptions(void) +{ + unsigned char key[16]= {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + stub.appDrawCursor = 0; + stub.minChromiumWindowWidth = 0; + stub.minChromiumWindowHeight = 0; + stub.maxChromiumWindowWidth = 0; + stub.maxChromiumWindowHeight = 0; + stub.matchChromiumWindowID = NULL; + stub.numIgnoreWindowID = 0; + stub.matchWindowTitle = NULL; + stub.ignoreFreeglutMenus = 0; + stub.trackWindowSize = 1; + stub.trackWindowPos = 1; + stub.trackWindowVisibility = 1; + stub.trackWindowVisibleRgn = 1; + stub.matchChromiumWindowCount = 0; + stub.spu_dir = NULL; + crNetSetRank(0); + crNetSetContextRange(32, 35); + crNetSetNodeRange("iam0", "iamvis20"); + crNetSetKey(key,sizeof(key)); + stub.force_pbuffers = 0; + +#ifdef WINDOWS +# ifdef VBOX_WITH_WDDM + stub.bRunningUnderWDDM = false; +# endif +#endif +} + +#ifdef CR_NEWWINTRACK +# ifdef VBOX_WITH_WDDM +static void stubDispatchVisibleRegions(WindowInfo *pWindow) +{ + DWORD dwCount; + LPRGNDATA lpRgnData; + + dwCount = GetRegionData(pWindow->hVisibleRegion, 0, NULL); + lpRgnData = crAlloc(dwCount); + + if (lpRgnData) + { + GetRegionData(pWindow->hVisibleRegion, dwCount, lpRgnData); + crDebug("Dispatched WindowVisibleRegion (%i, cRects=%i)", pWindow->spuWindow, lpRgnData->rdh.nCount); + stub.spuDispatch.WindowVisibleRegion(pWindow->spuWindow, lpRgnData->rdh.nCount, (GLint*) lpRgnData->Buffer); + crFree(lpRgnData); + } + else crWarning("GetRegionData failed, VisibleRegions update failed"); +} + +# endif /* VBOX_WITH_WDDM */ + +static void stubSyncTrCheckWindowsCB(unsigned long key, void *data1, void *data2) +{ + WindowInfo *pWindow = (WindowInfo *) data1; + (void)key; (void) data2; + + if (pWindow->type!=CHROMIUM || pWindow->spuWindow==0) + { + return; + } + + stub.spu->dispatch_table.VBoxPackSetInjectID(pWindow->u32ClientID); + + if (!stubSystemWindowExist(pWindow)) + { +#ifdef WINDOWS + stubDestroyWindow(0, (GLint)pWindow->hWnd); +#else + stubDestroyWindow(0, (GLint)pWindow->drawable); +#endif + /*No need to flush here as crWindowDestroy does it*/ + return; + } + +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + if (stub.bRunningUnderWDDM) + return; +#endif + stubCheckWindowState(pWindow, GL_TRUE); +} + +static DECLCALLBACK(int) stubSyncThreadProc(RTTHREAD ThreadSelf, void *pvUser) +{ +#ifdef WINDOWS + MSG msg; +# ifdef VBOX_WITH_WDDM + HMODULE hVBoxD3D = NULL; + GLint spuConnection = 0; +# endif +#endif + + (void) pvUser; + + crDebug("Sync thread started"); +#ifdef WINDOWS + PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); +# ifdef VBOX_WITH_WDDM + hVBoxD3D = NULL; + if (!GetModuleHandleEx(0, VBOX_MODNAME_DISPD3D, &hVBoxD3D)) + { + crDebug("GetModuleHandleEx failed err %d", GetLastError()); + hVBoxD3D = NULL; + } + + if (hVBoxD3D) + { + crDebug("running with " VBOX_MODNAME_DISPD3D); + stub.trackWindowVisibleRgn = 0; + stub.bRunningUnderWDDM = true; + } +# endif /* VBOX_WITH_WDDM */ +#endif /* WINDOWS */ + + crLockMutex(&stub.mutex); +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + spuConnection = +#endif + stub.spu->dispatch_table.VBoxPackSetInjectThread(NULL); +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + if (stub.bRunningUnderWDDM && !spuConnection) + { + crError("VBoxPackSetInjectThread failed!"); + } +#endif + crUnlockMutex(&stub.mutex); + + RTThreadUserSignal(ThreadSelf); + + while(!stub.bShutdownSyncThread) + { +#ifdef WINDOWS + if (!PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) + { +# ifdef VBOX_WITH_WDDM + if (stub.bRunningUnderWDDM) + { + + } + else +# endif + { + crHashtableWalk(stub.windowTable, stubSyncTrCheckWindowsCB, NULL); + RTThreadSleep(50); + } + } + else + { + if (WM_QUIT==msg.message) + { + crDebug("Sync thread got WM_QUIT"); + break; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +#else + /* Try to keep a consistent locking order. */ + crHashtableLock(stub.windowTable); + crLockMutex(&stub.mutex); + crHashtableWalkUnlocked(stub.windowTable, stubSyncTrCheckWindowsCB, NULL); + crUnlockMutex(&stub.mutex); + crHashtableUnlock(stub.windowTable); + RTThreadSleep(50); +#endif + } + +#ifdef VBOX_WITH_WDDM + if (spuConnection) + { + stub.spu->dispatch_table.VBoxConDestroy(spuConnection); + } + if (hVBoxD3D) + { + FreeLibrary(hVBoxD3D); + } +#endif + crDebug("Sync thread stopped"); + return 0; +} +#endif /* CR_NEWWINTRACK */ + +/** + * Do one-time initializations for the faker. + * Returns TRUE on success, FALSE otherwise. + */ +static bool +stubInitLocked(void) +{ + /* Here is where we contact the mothership to find out what we're supposed + * to be doing. Networking code in a DLL initializer. I sure hope this + * works :) + * + * HOW can I pass the mothership address to this if I already know it? + */ + + char response[1024]; + char **spuchain; + int num_spus; + int *spu_ids; + char **spu_names; + const char *app_id; + int i; + int disable_sync = 0; +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + HMODULE hVBoxD3D = NULL; +#endif + + stubInitVars(); + + crGetProcName(response, 1024); + crDebug("Stub launched for %s", response); + +#if defined(CR_NEWWINTRACK) && !defined(WINDOWS) + /** @todo when vm boots with compiz turned on, new code causes hang in xcb_wait_for_reply in the sync thread + * as at the start compiz runs our code under XGrabServer. + */ + if (!crStrcmp(response, "compiz") || !crStrcmp(response, "compiz_real") || !crStrcmp(response, "compiz.real") + || !crStrcmp(response, "compiz-bin")) + { + disable_sync = 1; + } +#endif + + /** @todo check if it'd be of any use on other than guests, no use for windows */ + app_id = crGetenv( "CR_APPLICATION_ID_NUMBER" ); + + crNetInit( NULL, NULL ); + +#ifndef WINDOWS + { + CRNetServer ns; + + ns.name = "vboxhgcm://host:0"; + ns.buffer_size = 1024; + crNetServerConnect(&ns +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , NULL +#endif + ); + if (!ns.conn) + { + crWarning("Failed to connect to host. Make sure 3D acceleration is enabled for this VM."); +# ifdef VBOXOGL_FAKEDRI + return false; +# else + exit(1); +# endif + } + else + { + crNetFreeConnection(ns.conn); + } + } +#endif + + strcpy(response, "2 0 feedback 1 pack"); + spuchain = crStrSplit( response, " " ); + num_spus = crStrToInt( spuchain[0] ); + spu_ids = (int *) crAlloc( num_spus * sizeof( *spu_ids ) ); + spu_names = (char **) crAlloc( num_spus * sizeof( *spu_names ) ); + for (i = 0 ; i < num_spus ; i++) + { + spu_ids[i] = crStrToInt( spuchain[2*i+1] ); + spu_names[i] = crStrdup( spuchain[2*i+2] ); + crDebug( "SPU %d/%d: (%d) \"%s\"", i+1, num_spus, spu_ids[i], spu_names[i] ); + } + + stubSetDefaultConfigurationOptions(); + +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + hVBoxD3D = NULL; + if (!GetModuleHandleEx(0, VBOX_MODNAME_DISPD3D, &hVBoxD3D)) + { + crDebug("GetModuleHandleEx failed err %d", GetLastError()); + hVBoxD3D = NULL; + } + + if (hVBoxD3D) + { + disable_sync = 1; + crDebug("running with %s", VBOX_MODNAME_DISPD3D); + stub.trackWindowVisibleRgn = 0; + /** @todo should we enable that? */ + stub.trackWindowSize = 0; + stub.trackWindowPos = 0; + stub.trackWindowVisibility = 0; + stub.bRunningUnderWDDM = true; + } +#endif + + stub.spu = crSPULoadChain( num_spus, spu_ids, spu_names, stub.spu_dir, NULL ); + + crFree( spuchain ); + crFree( spu_ids ); + for (i = 0; i < num_spus; ++i) + crFree(spu_names[i]); + crFree( spu_names ); + + // spu chain load failed somewhere + if (!stub.spu) { + return false; + } + + crSPUInitDispatchTable( &glim ); + + /* This is unlikely to change -- We still want to initialize our dispatch + * table with the functions of the first SPU in the chain. */ + stubInitSPUDispatch( stub.spu ); + + /* we need to plug one special stub function into the dispatch table */ + glim.GetChromiumParametervCR = stub_GetChromiumParametervCR; + +#if !defined(VBOX_NO_NATIVEGL) + /* Load pointers to native OpenGL functions into stub.nativeDispatch */ + stubInitNativeDispatch(); +#endif + +/*crDebug("stub init"); +raise(SIGINT);*/ + +#ifdef WINDOWS +# ifndef CR_NEWWINTRACK + stubInstallWindowMessageHook(); +# endif +#endif + +#ifdef CR_NEWWINTRACK + { + int rc; + + RTR3InitDll(RTR3INIT_FLAGS_UNOBTRUSIVE); + + if (!disable_sync) + { + crDebug("Starting sync thread"); + + rc = RTThreadCreate(&stub.hSyncThread, stubSyncThreadProc, NULL, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "Sync"); + if (RT_FAILURE(rc)) + { + crError("Failed to start sync thread! (%x)", rc); + } + RTThreadUserWait(stub.hSyncThread, 60 * 1000); + RTThreadUserReset(stub.hSyncThread); + + crDebug("Going on"); + } + } +#endif + +#ifdef GLX + stub.xshmSI.shmid = -1; + stub.bShmInitFailed = GL_FALSE; + stub.pGLXPixmapsHash = crAllocHashtable(); + + stub.bXExtensionsChecked = GL_FALSE; + stub.bHaveXComposite = GL_FALSE; + stub.bHaveXFixes = GL_FALSE; +#endif + + return true; +} + +/** + * Do one-time initializations for the faker. + * Returns TRUE on success, FALSE otherwise. + */ +bool +stubInit(void) +{ + bool bRc = true; + /* we need to serialize the initialization, otherwise racing is possible + * for XPDM-based d3d when a d3d switcher is testing the gl lib in two or more threads + * NOTE: the STUB_INIT_LOCK/UNLOCK is a NOP for non-win currently */ + STUB_INIT_LOCK(); + if (!stub_initialized) + bRc = stub_initialized = stubInitLocked(); + STUB_INIT_UNLOCK(); + return bRc; +} + +/* Sigh -- we can't do initialization at load time, since Windows forbids + * the loading of other libraries from DLLMain. */ + +#ifdef WINDOWS +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#if 1//def DEBUG_misha + /* debugging: this is to be able to catch first-chance notifications + * for exceptions other than EXCEPTION_BREAKPOINT in kernel debugger */ +# define VDBG_VEHANDLER +#endif + +#ifdef VDBG_VEHANDLER +# include <dbghelp.h> +# include <cr_string.h> +static PVOID g_VBoxVehHandler = NULL; +static DWORD g_VBoxVehEnable = 0; + +/* generate a crash dump on exception */ +#define VBOXVEH_F_DUMP 0x00000001 +/* generate a debugger breakpoint exception */ +#define VBOXVEH_F_BREAK 0x00000002 +/* exit on exception */ +#define VBOXVEH_F_EXIT 0x00000004 + +static DWORD g_VBoxVehFlags = 0; + +typedef BOOL WINAPI FNVBOXDBG_MINIDUMPWRITEDUMP(HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + PMINIDUMP_CALLBACK_INFORMATION CallbackParam); +typedef FNVBOXDBG_MINIDUMPWRITEDUMP *PFNVBOXDBG_MINIDUMPWRITEDUMP; + +static HMODULE g_hVBoxMdDbgHelp = NULL; +static PFNVBOXDBG_MINIDUMPWRITEDUMP g_pfnVBoxMdMiniDumpWriteDump = NULL; +static size_t g_cVBoxMdFilePrefixLen = 0; +static WCHAR g_aszwVBoxMdFilePrefix[MAX_PATH]; +static WCHAR g_aszwVBoxMdDumpCount = 0; +static MINIDUMP_TYPE g_enmVBoxMdDumpType = MiniDumpNormal + | MiniDumpWithDataSegs + | MiniDumpWithFullMemory + | MiniDumpWithHandleData +//// | MiniDumpFilterMemory +//// | MiniDumpScanMemory +// | MiniDumpWithUnloadedModules +//// | MiniDumpWithIndirectlyReferencedMemory +//// | MiniDumpFilterModulePaths +// | MiniDumpWithProcessThreadData +// | MiniDumpWithPrivateReadWriteMemory +//// | MiniDumpWithoutOptionalData +// | MiniDumpWithFullMemoryInfo +// | MiniDumpWithThreadInfo +// | MiniDumpWithCodeSegs +// | MiniDumpWithFullAuxiliaryState +// | MiniDumpWithPrivateWriteCopyMemory +// | MiniDumpIgnoreInaccessibleMemory +// | MiniDumpWithTokenInformation +//// | MiniDumpWithModuleHeaders +//// | MiniDumpFilterTriage + ; + + + +#define VBOXMD_DUMP_DIR_DEFAULT "C:\\dumps" +#define VBOXMD_DUMP_NAME_PREFIX_W L"VBoxDmp_" + +static HMODULE loadSystemDll(const char *pszName) +{ +#ifndef DEBUG + char szPath[MAX_PATH]; + UINT cchPath = GetSystemDirectoryA(szPath, sizeof(szPath)); + size_t cbName = strlen(pszName) + 1; + if (cchPath + 1 + cbName > sizeof(szPath)) + { + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return NULL; + } + szPath[cchPath] = '\\'; + memcpy(&szPath[cchPath + 1], pszName, cbName); + return LoadLibraryA(szPath); +#else + return LoadLibraryA(pszName); +#endif +} + +static DWORD vboxMdMinidumpCreate(struct _EXCEPTION_POINTERS *pExceptionInfo) +{ + WCHAR aszwMdFileName[MAX_PATH]; + HANDLE hProcess = GetCurrentProcess(); + DWORD ProcessId = GetCurrentProcessId(); + MINIDUMP_EXCEPTION_INFORMATION ExceptionInfo; + HANDLE hFile; + DWORD winErr = ERROR_SUCCESS; + + if (!g_pfnVBoxMdMiniDumpWriteDump) + { + if (!g_hVBoxMdDbgHelp) + { + g_hVBoxMdDbgHelp = loadSystemDll("DbgHelp.dll"); + if (!g_hVBoxMdDbgHelp) + return GetLastError(); + } + + g_pfnVBoxMdMiniDumpWriteDump = (PFNVBOXDBG_MINIDUMPWRITEDUMP)GetProcAddress(g_hVBoxMdDbgHelp, "MiniDumpWriteDump"); + if (!g_pfnVBoxMdMiniDumpWriteDump) + return GetLastError(); + } + + ++g_aszwVBoxMdDumpCount; + + memcpy(aszwMdFileName, g_aszwVBoxMdFilePrefix, g_cVBoxMdFilePrefixLen * sizeof (g_aszwVBoxMdFilePrefix[0])); + swprintf(aszwMdFileName + g_cVBoxMdFilePrefixLen, RT_ELEMENTS(aszwMdFileName) - g_cVBoxMdFilePrefixLen, L"%d_%d.dmp", ProcessId, g_aszwVBoxMdDumpCount); + + hFile = CreateFileW(aszwMdFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + return GetLastError(); + + ExceptionInfo.ThreadId = GetCurrentThreadId(); + ExceptionInfo.ExceptionPointers = pExceptionInfo; + ExceptionInfo.ClientPointers = FALSE; + + if (!g_pfnVBoxMdMiniDumpWriteDump(hProcess, ProcessId, hFile, g_enmVBoxMdDumpType, &ExceptionInfo, NULL, NULL)) + winErr = GetLastError(); + + CloseHandle(hFile); + return winErr; +} + +LONG WINAPI vboxVDbgVectoredHandler(struct _EXCEPTION_POINTERS *pExceptionInfo) +{ + PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; + PCONTEXT pContextRecord = pExceptionInfo->ContextRecord; + switch (pExceptionRecord->ExceptionCode) + { + case EXCEPTION_BREAKPOINT: + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_STACK_OVERFLOW: + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_INT_DIVIDE_BY_ZERO: + case EXCEPTION_ILLEGAL_INSTRUCTION: + if (g_VBoxVehFlags & VBOXVEH_F_BREAK) + { + BOOL fBreak = TRUE; +#ifndef DEBUG_misha + if (pExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) + { + HANDLE hProcess = GetCurrentProcess(); + BOOL fDebuggerPresent = FALSE; + /* we do not want to generate breakpoint exceptions recursively, so do it only when running under debugger */ + if (CheckRemoteDebuggerPresent(hProcess, &fDebuggerPresent)) + fBreak = !!fDebuggerPresent; + else + fBreak = FALSE; /* <- the function has failed, don't break for sanity */ + } +#endif + + if (fBreak) + { + RT_BREAKPOINT(); + } + } + + if (g_VBoxVehFlags & VBOXVEH_F_DUMP) + vboxMdMinidumpCreate(pExceptionInfo); + + if (g_VBoxVehFlags & VBOXVEH_F_EXIT) + exit(1); + break; + default: + break; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +void vboxVDbgVEHandlerRegister() +{ + CRASSERT(!g_VBoxVehHandler); + g_VBoxVehHandler = AddVectoredExceptionHandler(1,vboxVDbgVectoredHandler); + CRASSERT(g_VBoxVehHandler); +} + +void vboxVDbgVEHandlerUnregister() +{ + ULONG uResult; + if (g_VBoxVehHandler) + { + uResult = RemoveVectoredExceptionHandler(g_VBoxVehHandler); + CRASSERT(uResult); + g_VBoxVehHandler = NULL; + } +} +#endif + +/* Windows crap */ +BOOL WINAPI DllMain(HINSTANCE hDLLInst, DWORD fdwReason, LPVOID lpvReserved) +{ + (void) lpvReserved; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + { + CRNetServer ns; + const char * env; +#if defined(DEBUG_misha) + HMODULE hCrUtil; + char aName[MAX_PATH]; + + GetModuleFileNameA(hDLLInst, aName, RT_ELEMENTS(aName)); + crDbgCmdSymLoadPrint(aName, hDLLInst); + + hCrUtil = GetModuleHandleA("VBoxOGLcrutil.dll"); + Assert(hCrUtil); + crDbgCmdSymLoadPrint("VBoxOGLcrutil.dll", hCrUtil); +#endif +#ifdef CHROMIUM_THREADSAFE + crInitTSD(&g_stubCurrentContextTSD); +#endif + + crInitMutex(&stub_init_mutex); + +#ifdef VDBG_VEHANDLER + env = crGetenv("CR_DBG_VEH_ENABLE"); + g_VBoxVehEnable = crStrParseI32(env, +# ifdef DEBUG_misha + 1 +# else + 0 +# endif + ); + + if (g_VBoxVehEnable) + { + char procName[1024]; + size_t cProcName; + size_t cChars; + + env = crGetenv("CR_DBG_VEH_FLAGS"); + g_VBoxVehFlags = crStrParseI32(env, + 0 +# ifdef DEBUG_misha + | VBOXVEH_F_BREAK +# else + | VBOXVEH_F_DUMP +# endif + ); + + env = crGetenv("CR_DBG_VEH_DUMP_DIR"); + if (!env) + env = VBOXMD_DUMP_DIR_DEFAULT; + + g_cVBoxMdFilePrefixLen = strlen(env); + + if (RT_ELEMENTS(g_aszwVBoxMdFilePrefix) <= g_cVBoxMdFilePrefixLen + 26 + (sizeof (VBOXMD_DUMP_NAME_PREFIX_W) - sizeof (WCHAR)) / sizeof (WCHAR)) + { + g_cVBoxMdFilePrefixLen = 0; + env = ""; + } + + mbstowcs_s(&cChars, g_aszwVBoxMdFilePrefix, g_cVBoxMdFilePrefixLen + 1, env, _TRUNCATE); + + Assert(cChars == g_cVBoxMdFilePrefixLen + 1); + + g_cVBoxMdFilePrefixLen = cChars - 1; + + if (g_cVBoxMdFilePrefixLen && g_aszwVBoxMdFilePrefix[g_cVBoxMdFilePrefixLen - 1] != L'\\') + g_aszwVBoxMdFilePrefix[g_cVBoxMdFilePrefixLen++] = L'\\'; + + memcpy(g_aszwVBoxMdFilePrefix + g_cVBoxMdFilePrefixLen, VBOXMD_DUMP_NAME_PREFIX_W, sizeof (VBOXMD_DUMP_NAME_PREFIX_W) - sizeof (WCHAR)); + g_cVBoxMdFilePrefixLen += (sizeof (VBOXMD_DUMP_NAME_PREFIX_W) - sizeof (WCHAR)) / sizeof (WCHAR); + + crGetProcName(procName, RT_ELEMENTS(procName)); + cProcName = strlen(procName); + + if (RT_ELEMENTS(g_aszwVBoxMdFilePrefix) > g_cVBoxMdFilePrefixLen + cProcName + 1 + 26) + { + mbstowcs_s(&cChars, g_aszwVBoxMdFilePrefix + g_cVBoxMdFilePrefixLen, cProcName + 1, procName, _TRUNCATE); + Assert(cChars == cProcName + 1); + g_cVBoxMdFilePrefixLen += cChars - 1; + g_aszwVBoxMdFilePrefix[g_cVBoxMdFilePrefixLen++] = L'_'; + } + + /* sanity */ + g_aszwVBoxMdFilePrefix[g_cVBoxMdFilePrefixLen] = L'\0'; + + env = crGetenv("CR_DBG_VEH_DUMP_TYPE"); + + g_enmVBoxMdDumpType = crStrParseI32(env, + MiniDumpNormal + | MiniDumpWithDataSegs + | MiniDumpWithFullMemory + | MiniDumpWithHandleData + //// | MiniDumpFilterMemory + //// | MiniDumpScanMemory + // | MiniDumpWithUnloadedModules + //// | MiniDumpWithIndirectlyReferencedMemory + //// | MiniDumpFilterModulePaths + // | MiniDumpWithProcessThreadData + // | MiniDumpWithPrivateReadWriteMemory + //// | MiniDumpWithoutOptionalData + // | MiniDumpWithFullMemoryInfo + // | MiniDumpWithThreadInfo + // | MiniDumpWithCodeSegs + // | MiniDumpWithFullAuxiliaryState + // | MiniDumpWithPrivateWriteCopyMemory + // | MiniDumpIgnoreInaccessibleMemory + // | MiniDumpWithTokenInformation + //// | MiniDumpWithModuleHeaders + //// | MiniDumpFilterTriage + ); + + vboxVDbgVEHandlerRegister(); + } +#endif + + crNetInit(NULL, NULL); + ns.name = "vboxhgcm://host:0"; + ns.buffer_size = 1024; + crNetServerConnect(&ns +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , NULL +#endif +); + if (!ns.conn) + { + crDebug("Failed to connect to host (is guest 3d acceleration enabled?), aborting ICD load."); +#ifdef VDBG_VEHANDLER + if (g_VBoxVehEnable) + vboxVDbgVEHandlerUnregister(); +#endif + return FALSE; + } + else + { + crNetFreeConnection(ns.conn); + } + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + VBoxCrHgsmiInit(); +#endif + break; + } + + case DLL_PROCESS_DETACH: + { + /* do exactly the same thing as for DLL_THREAD_DETACH since + * DLL_THREAD_DETACH is not called for the thread doing DLL_PROCESS_DETACH according to msdn docs */ + stubSetCurrentContext(NULL); + if (stub_initialized) + { + CRASSERT(stub.spu); + stub.spu->dispatch_table.VBoxDetachThread(); + } + + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + VBoxCrHgsmiTerm(); +#endif + + stubSPUSafeTearDown(); + +#ifdef CHROMIUM_THREADSAFE + crFreeTSD(&g_stubCurrentContextTSD); +#endif + +#ifdef VDBG_VEHANDLER + if (g_VBoxVehEnable) + vboxVDbgVEHandlerUnregister(); +#endif + break; + } + + case DLL_THREAD_ATTACH: + { + if (stub_initialized) + { + CRASSERT(stub.spu); + stub.spu->dispatch_table.VBoxAttachThread(); + } + break; + } + + case DLL_THREAD_DETACH: + { + stubSetCurrentContext(NULL); + if (stub_initialized) + { + CRASSERT(stub.spu); + stub.spu->dispatch_table.VBoxDetachThread(); + } + break; + } + + default: + break; + } + + return TRUE; +} +#endif |