Wwise SDK 2015.1.9
_win32_2_ak_platform_funcs_8h_source
Version
menu_open
link
Target Platform(s):
include/AK/Tools/Win32/AkPlatformFuncs.h
Go to the documentation of this file.00001 00002 // 00003 // AkPlatformFuncs.h 00004 // 00005 // Audiokinetic platform-dependent functions definition. 00006 // 00007 // Copyright (c) 2006 Audiokinetic Inc. / All Rights Reserved 00008 // 00010 00011 #ifndef _AK_PLATFORM_FUNCS_H_ 00012 #define _AK_PLATFORM_FUNCS_H_ 00013 00014 #include "malloc.h" 00015 #include <AK/Tools/Common/AkAssert.h> 00016 #include <AK/SoundEngine/Common/AkTypes.h> 00017 #include <windows.h> 00018 //#define AK_ENABLE_PERF_RECORDING 00019 #if defined(AK_ENABLE_PERF_RECORDING) 00020 #include <stdio.h> 00021 #endif 00022 00023 #ifdef AK_USE_THREAD_EMULATION 00024 #include <AK/Tools/Win32/ThreadEmulation.h> 00025 #endif 00026 00027 #if defined(_WIN64) 00028 // on 64 bit, removes warning C4985: 'ceil': attributes not present on previous declaration. 00029 // see http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=294649 00030 #include <math.h> 00031 #endif // _WIN64 00032 #include <intrin.h> 00033 00034 //----------------------------------------------------------------------------- 00035 // Platform-specific thread properties definition. 00036 //----------------------------------------------------------------------------- 00037 struct AkThreadProperties 00038 { 00039 int nPriority; 00040 AkUInt32 dwAffinityMask; 00041 AkUInt32 uStackSize; 00042 }; 00043 00044 //----------------------------------------------------------------------------- 00045 // External variables. 00046 //----------------------------------------------------------------------------- 00047 // g_fFreqRatio is used by time helpers to return time values in milliseconds. 00048 // It is declared and updated by the sound engine. 00049 namespace AK 00050 { 00051 extern AkReal32 g_fFreqRatio; 00052 } 00053 00054 //----------------------------------------------------------------------------- 00055 // Defines for Win32. 00056 //----------------------------------------------------------------------------- 00057 #define AK_DECLARE_THREAD_ROUTINE( FuncName ) DWORD WINAPI FuncName(LPVOID lpParameter) 00058 #define AK_THREAD_RETURN( _param_ ) return (_param_); 00059 #define AK_THREAD_ROUTINE_PARAMETER lpParameter 00060 #define AK_GET_THREAD_ROUTINE_PARAMETER_PTR(type) reinterpret_cast<type*>( AK_THREAD_ROUTINE_PARAMETER ) 00061 #define AK_RETURN_THREAD_OK 0x00000000 00062 #define AK_RETURN_THREAD_ERROR 0x00000001 00063 #if defined AK_CPU_X86_64 00064 #define AK_DEFAULT_STACK_SIZE (65536) 00065 #else 00066 #define AK_DEFAULT_STACK_SIZE (32768) 00067 #endif 00068 #define AK_THREAD_PRIORITY_NORMAL THREAD_PRIORITY_NORMAL 00069 #define AK_THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_ABOVE_NORMAL 00070 00071 // NULL objects 00072 #define AK_NULL_THREAD NULL 00073 00074 #define AK_INFINITE INFINITE 00075 00076 #define AkMakeLong(a,b) MAKELONG((a),(b)) 00077 00078 #define AkMax(x1, x2) (((x1) > (x2))? (x1): (x2)) 00079 #define AkMin(x1, x2) (((x1) < (x2))? (x1): (x2)) 00080 #define AkClamp(x, min, max) ((x) < (min)) ? (min) : (((x) > (max) ? (max) : (x))) 00081 00082 namespace AKPLATFORM 00083 { 00084 // Simple automatic event API 00085 // ------------------------------------------------------------------ 00086 00088 inline void AkClearEvent( AkEvent & out_event ) 00089 { 00090 out_event = NULL; 00091 } 00092 00094 inline AKRESULT AkCreateEvent( AkEvent & out_event ) 00095 { 00096 #ifdef AK_USE_METRO_API 00097 out_event = CreateEventEx(nullptr, nullptr, 0, STANDARD_RIGHTS_ALL|EVENT_MODIFY_STATE); 00098 #else 00099 out_event = ::CreateEvent( NULL, // No security attributes 00100 false, // Reset type: automatic 00101 false, // Initial signaled state: not signaled 00102 NULL // No name 00103 ); 00104 #endif 00105 return ( out_event ) ? AK_Success : AK_Fail; 00106 } 00107 00109 inline void AkDestroyEvent( AkEvent & io_event ) 00110 { 00111 if ( io_event ) 00112 ::CloseHandle( io_event ); 00113 io_event = NULL; 00114 } 00115 00117 inline void AkWaitForEvent( AkEvent & in_event ) 00118 { 00119 #ifdef AK_USE_METRO_API 00120 #ifdef AK_ENABLE_ASSERTS 00121 DWORD dwWaitResult = 00122 #endif // AK_ENABLE_ASSERTS 00123 ::WaitForSingleObjectEx( in_event, INFINITE, FALSE ); 00124 AKASSERT( dwWaitResult == WAIT_OBJECT_0 ); 00125 #else 00126 AKVERIFY( ::WaitForSingleObject( in_event, INFINITE ) == WAIT_OBJECT_0 ); 00127 #endif 00128 } 00129 00131 inline void AkSignalEvent( const AkEvent & in_event ) 00132 { 00133 AKVERIFY( ::SetEvent( in_event ) ); 00134 } 00135 00136 00137 // Atomic Operations 00138 // ------------------------------------------------------------------ 00139 00141 inline AkInt32 AkInterlockedIncrement( AkInt32 * pValue ) 00142 { 00143 return InterlockedIncrement( pValue ); 00144 } 00145 00147 inline AkInt32 AkInterlockedDecrement( AkInt32 * pValue ) 00148 { 00149 return InterlockedDecrement( pValue ); 00150 } 00151 00152 #ifdef AK_CPU_X86_64 00153 inline bool AkInterlockedCompareExchange( volatile AkInt64* io_pDest, AkInt64 in_newValue, AkInt64 in_expectedOldVal ) 00154 { 00155 return _InterlockedCompareExchange64(io_pDest, in_newValue, in_expectedOldVal) == in_expectedOldVal; 00156 } 00157 #endif 00158 00159 inline bool AkInterlockedCompareExchange( volatile AkInt32* io_pDest, AkInt32 in_newValue, AkInt32 in_expectedOldVal ) 00160 { 00161 return InterlockedCompareExchange(io_pDest, in_newValue, in_expectedOldVal) == in_expectedOldVal; 00162 } 00163 00164 #if defined AK_CPU_X86 || defined AK_CPU_ARM 00165 inline bool AkInterlockedCompareExchange( volatile AkIntPtr* io_pDest, AkIntPtr in_newValue, AkIntPtr in_expectedOldVal ) 00166 { 00167 return InterlockedCompareExchange((volatile LONG_PTR*)io_pDest, (LONG_PTR)in_newValue, (LONG_PTR)in_expectedOldVal) == in_expectedOldVal; 00168 } 00169 #endif 00170 00171 //Ensure that all write operations are complete. Necessary only on platforms that don't garentee the order of writes. 00172 inline void AkMemoryBarrier() 00173 { 00174 _ReadWriteBarrier(); 00175 } 00176 00177 // Threads 00178 // ------------------------------------------------------------------ 00179 00181 inline bool AkIsValidThread( AkThread * in_pThread ) 00182 { 00183 return (*in_pThread != AK_NULL_THREAD); 00184 } 00185 00187 inline void AkClearThread( AkThread * in_pThread ) 00188 { 00189 *in_pThread = AK_NULL_THREAD; 00190 } 00191 00193 inline void AkCloseThread( AkThread * in_pThread ) 00194 { 00195 AKASSERT( in_pThread ); 00196 AKASSERT( *in_pThread ); 00197 AKVERIFY( ::CloseHandle( *in_pThread ) ); 00198 AkClearThread( in_pThread ); 00199 } 00200 00201 #define AkExitThread( _result ) return _result; 00202 00204 inline void AkGetDefaultThreadProperties( AkThreadProperties & out_threadProperties ) 00205 { 00206 out_threadProperties.nPriority = AK_THREAD_PRIORITY_NORMAL; 00207 out_threadProperties.uStackSize= AK_DEFAULT_STACK_SIZE; 00208 out_threadProperties.dwAffinityMask = 0; 00209 } 00210 00212 inline void AkSetThreadName( DWORD in_dwThreadID, LPCSTR in_szThreadName ) 00213 { 00214 #if defined AK_USE_THREAD_EMULATION 00215 UNREFERENCED_PARAMETER( in_dwThreadID ); 00216 UNREFERENCED_PARAMETER( in_szThreadName ); 00217 #else 00218 const DWORD MS_VC_EXCEPTION=0x406D1388; 00219 00220 #pragma pack(push,8) 00221 typedef struct tagTHREADNAME_INFO 00222 { 00223 DWORD dwType; 00224 LPCSTR szName; 00225 DWORD dwThreadID; 00226 DWORD dwFlags; 00227 } THREADNAME_INFO; 00228 #pragma pack(pop) 00229 00230 THREADNAME_INFO info; 00231 info.dwType = 0x1000; 00232 info.szName = in_szThreadName; 00233 info.dwThreadID = in_dwThreadID; 00234 info.dwFlags = 0; 00235 00236 __try 00237 { 00238 RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); 00239 } 00240 #pragma warning(suppress: 6312 6322) 00241 __except(EXCEPTION_CONTINUE_EXECUTION) 00242 { 00243 } 00244 #endif 00245 } 00246 00248 inline void AkCreateThread( 00249 AkThreadRoutine pStartRoutine, // Thread routine. 00250 void * pParams, // Routine params. 00251 const AkThreadProperties & in_threadProperties, // Properties. NULL for default. 00252 AkThread * out_pThread, // Returned thread handle. 00253 const char * in_szThreadName ) // Opt thread name. 00254 { 00255 AKASSERT( out_pThread != NULL ); 00256 AKASSERT( (in_threadProperties.nPriority >= THREAD_PRIORITY_LOWEST && in_threadProperties.nPriority <= THREAD_PRIORITY_HIGHEST) 00257 || ( in_threadProperties.nPriority == THREAD_PRIORITY_TIME_CRITICAL ) ); 00258 00259 #ifdef AK_USE_THREAD_EMULATION 00260 UNREFERENCED_PARAMETER( in_threadProperties ); 00261 UNREFERENCED_PARAMETER( in_szThreadName ); 00262 *out_pThread = AK::ThreadEmulation::CreateThread( pStartRoutine, // Thread start routine 00263 pParams, // Thread function parameter 00264 0 ); // Creation flags: create running 00265 #else 00266 DWORD dwThreadID; 00267 *out_pThread = ::CreateThread( NULL, // No security attributes 00268 in_threadProperties.uStackSize, // StackSize (0 uses system default) 00269 pStartRoutine, // Thread start routine 00270 pParams, // Thread function parameter 00271 0, // Creation flags: create running 00272 &dwThreadID ); 00273 00274 // ::CreateThread() return NULL if it fails. 00275 if ( !*out_pThread ) 00276 { 00277 AkClearThread( out_pThread ); 00278 return; 00279 } 00280 00281 // Set thread name. 00282 AkSetThreadName( dwThreadID, in_szThreadName ); 00283 00284 // Set properties. 00285 if ( !::SetThreadPriority( *out_pThread, in_threadProperties.nPriority ) ) 00286 { 00287 AKASSERT( !"Failed setting IO thread priority" ); 00288 AkCloseThread( out_pThread ); 00289 return; 00290 } 00291 if ( in_threadProperties.dwAffinityMask ) 00292 { 00293 #ifndef AK_XBOXONE_ADK 00294 if ( !::SetThreadAffinityMask( *out_pThread, in_threadProperties.dwAffinityMask ) ) 00295 { 00296 AKASSERT( !"Failed setting IO thread affinity mask" ); 00297 AkCloseThread( out_pThread ); 00298 } 00299 #endif 00300 } 00301 #endif 00302 } 00303 00305 inline void AkWaitForSingleThread( AkThread * in_pThread ) 00306 { 00307 AKASSERT( in_pThread ); 00308 AKASSERT( *in_pThread ); 00309 #ifdef AK_USE_METRO_API 00310 ::WaitForSingleObjectEx( *in_pThread, INFINITE, FALSE ); 00311 #else 00312 ::WaitForSingleObject( *in_pThread, INFINITE ); 00313 #endif 00314 } 00315 00317 inline AkThreadID CurrentThread() 00318 { 00319 return ::GetCurrentThreadId(); 00320 } 00321 00323 inline void AkSleep( AkUInt32 in_ulMilliseconds ) 00324 { 00325 #ifdef AK_USE_THREAD_EMULATION 00326 AK::ThreadEmulation::Sleep( in_ulMilliseconds ); 00327 #else 00328 ::Sleep( in_ulMilliseconds ); 00329 #endif 00330 } 00331 00332 // Optimized memory functions 00333 // -------------------------------------------------------------------- 00334 00336 inline void AkMemCpy( void * pDest, const void * pSrc, AkUInt32 uSize ) 00337 { 00338 memcpy( pDest, pSrc, uSize ); 00339 } 00340 00342 inline void AkMemSet( void * pDest, AkInt32 iVal, AkUInt32 uSize ) 00343 { 00344 memset( pDest, iVal, uSize ); 00345 } 00346 00347 // Time functions 00348 // ------------------------------------------------------------------ 00349 00351 inline void PerformanceCounter( AkInt64 * out_piLastTime ) 00352 { 00353 ::QueryPerformanceCounter( (LARGE_INTEGER*)out_piLastTime ); 00354 } 00355 00357 inline void PerformanceFrequency( AkInt64 * out_piFreq ) 00358 { 00359 ::QueryPerformanceFrequency( (LARGE_INTEGER*)out_piFreq ); 00360 } 00361 00363 inline void UpdatePerformanceFrequency() 00364 { 00365 AkInt64 iFreq; 00366 PerformanceFrequency( &iFreq ); 00367 AK::g_fFreqRatio = (AkReal32)( iFreq / 1000 ); 00368 } 00369 00371 inline AkReal32 Elapsed( const AkInt64 & in_iNow, const AkInt64 & in_iStart ) 00372 { 00373 return ( in_iNow - in_iStart ) / AK::g_fFreqRatio; 00374 } 00375 00377 inline AkInt32 AkWideCharToChar( const wchar_t* in_pszUnicodeString, 00378 AkUInt32 in_uiOutBufferSize, 00379 char* io_pszAnsiString ) 00380 { 00381 int iWritten = ::WideCharToMultiByte(CP_ACP, // code page 00382 0, // performance and mapping flags 00383 in_pszUnicodeString, // wide-character string 00384 (int)AkMin( ( (AkUInt32)wcslen( in_pszUnicodeString )), in_uiOutBufferSize-1 ), // number of chars in string : -1 = NULL terminated string. 00385 io_pszAnsiString, // buffer for new string 00386 in_uiOutBufferSize, // size of buffer 00387 NULL, // default for unmappable chars 00388 NULL); // set when default char used 00389 io_pszAnsiString[iWritten] = 0; 00390 return iWritten; 00391 } 00392 00394 inline AkInt32 AkCharToWideChar( const char* in_pszAnsiString, 00395 AkUInt32 in_uiOutBufferSize, 00396 void* io_pvUnicodeStringBuffer ) 00397 { 00398 return ::MultiByteToWideChar( CP_ACP, // code page 00399 0, // performance and mapping flags 00400 in_pszAnsiString, // wide-character string 00401 -1, // number of chars in string : -1 = NULL terminated string. 00402 (wchar_t*)io_pvUnicodeStringBuffer, // buffer for new string 00403 in_uiOutBufferSize); // size of buffer 00404 } 00405 00407 inline AkInt32 AkUtf8ToWideChar( const char* in_pszUtf8String, 00408 AkUInt32 in_uiOutBufferSize, 00409 void* io_pvUnicodeStringBuffer ) 00410 { 00411 return ::MultiByteToWideChar( CP_UTF8, // code page 00412 0, // performance and mapping flags 00413 in_pszUtf8String, // wide-character string 00414 -1, // number of chars in string : -1 = NULL terminated string. 00415 (wchar_t*)io_pvUnicodeStringBuffer, // buffer for new string 00416 in_uiOutBufferSize); // size of buffer 00417 } 00418 00420 inline void SafeStrCpy( wchar_t * in_pDest, const wchar_t* in_pSrc, size_t in_uDestMaxNumChars ) 00421 { 00422 size_t iSizeCopy = AkMin( in_uDestMaxNumChars - 1, wcslen( in_pSrc ) + 1 ); 00423 wcsncpy_s( in_pDest, in_uDestMaxNumChars, in_pSrc, iSizeCopy ); 00424 in_pDest[iSizeCopy] = '\0'; 00425 } 00426 00428 inline void SafeStrCpy( char * in_pDest, const char* in_pSrc, size_t in_uDestMaxNumChars ) 00429 { 00430 size_t iSizeCopy = AkMin( in_uDestMaxNumChars - 1, strlen( in_pSrc ) + 1 ); 00431 strncpy_s( in_pDest, in_uDestMaxNumChars, in_pSrc, iSizeCopy ); 00432 in_pDest[iSizeCopy] = '\0'; 00433 } 00434 00436 inline void SafeStrCat( wchar_t * in_pDest, const wchar_t* in_pSrc, size_t in_uDestMaxNumChars ) 00437 { 00438 int iAvailableSize = (int)( in_uDestMaxNumChars - wcslen( in_pDest ) - 1 ); 00439 wcsncat_s( in_pDest, in_uDestMaxNumChars, in_pSrc, AkMin( iAvailableSize, (int)wcslen( in_pSrc ) ) ); 00440 } 00441 00443 inline void SafeStrCat( char * in_pDest, const char* in_pSrc, size_t in_uDestMaxNumChars ) 00444 { 00445 int iAvailableSize = (int)( in_uDestMaxNumChars - strlen( in_pDest ) - 1 ); 00446 strncat_s( in_pDest, in_uDestMaxNumChars, in_pSrc, AkMin( iAvailableSize, (int)strlen( in_pSrc ) ) ); 00447 } 00448 00450 #define AkAlloca( _size_ ) _alloca( _size_ ) 00451 00453 #if ! ( defined(AK_USE_METRO_API) || defined(AK_OPTIMIZED) ) 00454 inline void OutputDebugMsg( const wchar_t* in_pszMsg ) 00455 { 00456 OutputDebugStringW( in_pszMsg ); 00457 } 00458 00460 inline void OutputDebugMsg( const char* in_pszMsg ) 00461 { 00462 OutputDebugStringA( in_pszMsg ); 00463 } 00464 #else 00465 inline void OutputDebugMsg( const wchar_t* ){} 00466 inline void OutputDebugMsg( const char* ){} 00467 #endif 00468 00477 #define CONVERT_WIDE_TO_OSCHAR( _wstring_, _oscharstring_ ) ( _oscharstring_ ) = (AkOSChar*)( _wstring_ ) 00478 00487 #define CONVERT_CHAR_TO_OSCHAR( _astring_, _oscharstring_ ) \ 00488 _oscharstring_ = (AkOSChar*)AkAlloca( (1 + strlen( _astring_ )) * sizeof(AkOSChar)); \ 00489 AKPLATFORM::AkCharToWideChar( _astring_, (AkUInt32)(1 + strlen(_astring_ )), (AkOSChar*)( _oscharstring_ ) ) 00490 00499 #define CONVERT_OSCHAR_TO_WIDE( _osstring_, _wstring_ ) _wstring_ = _osstring_ 00500 00509 #define CONVERT_OSCHAR_TO_CHAR( _osstring_, _astring_ ) \ 00510 _astring_ = (char*)AkAlloca( 1 + wcslen( _osstring_ )); \ 00511 AKPLATFORM::AkWideCharToChar( _osstring_, AkUInt32(1 + wcslen( _osstring_ )), _astring_ ); 00512 00515 inline size_t AkUtf16StrLen( const AkUtf16* in_pStr ) 00516 { 00517 return ( wcslen( in_pStr ) ); 00518 } 00519 00522 inline size_t OsStrLen( const AkOSChar* in_pszString ) 00523 { 00524 return ( wcslen( in_pszString ) ); 00525 } 00526 00528 #define AK_OSPRINTF swprintf_s 00529 00536 inline int OsStrCmp( const AkOSChar* in_pszString1, const AkOSChar* in_pszString2 ) 00537 { 00538 return ( wcscmp( in_pszString1, in_pszString2 ) ); 00539 } 00540 00541 #define AK_UTF16_TO_WCHAR( in_pdDest, in_pSrc, in_MaxSize ) AKPLATFORM::SafeStrCpy( in_pdDest, in_pSrc, in_MaxSize ) 00542 #define AK_WCHAR_TO_UTF16( in_pdDest, in_pSrc, in_MaxSize ) AKPLATFORM::SafeStrCpy( in_pdDest, in_pSrc, in_MaxSize ) 00543 #define AK_UTF16_TO_OSCHAR( in_pdDest, in_pSrc, in_MaxSize ) AKPLATFORM::SafeStrCpy( in_pdDest, in_pSrc, in_MaxSize ) 00544 #define AK_UTF16_TO_CHAR( in_pdDest, in_pSrc, in_MaxSize ) AKPLATFORM::AkWideCharToChar( in_pSrc, in_MaxSize, in_pdDest ) 00545 #define AK_CHAR_TO_UTF16( in_pdDest, in_pSrc, in_MaxSize ) AKPLATFORM::AkCharToWideChar( in_pSrc, in_MaxSize, in_pdDest ) 00546 #define AK_OSCHAR_TO_UTF16( in_pdDest, in_pSrc, in_MaxSize ) AKPLATFORM::SafeStrCpy( in_pdDest, in_pSrc, in_MaxSize ) 00547 00548 // Use with AkOSChar. 00549 #define AK_PATH_SEPARATOR (L"\\") 00550 00551 #if defined(AK_ENABLE_PERF_RECORDING) 00552 00553 static AkUInt32 g_uAkPerfRecExecCount = 0; 00554 static AkReal32 g_fAkPerfRecExecTime = 0.f; 00555 00556 #define AK_PERF_RECORDING_RESET() \ 00557 AKPLATFORM::g_uAkPerfRecExecCount = 0;\ 00558 AKPLATFORM::g_fAkPerfRecExecTime = 0.f; 00559 00560 #define AK_PERF_RECORDING_START( __StorageName__, __uExecutionCountStart__, __uExecutionCountStop__ ) \ 00561 AkInt64 iAkPerfRecTimeBefore; \ 00562 if ( (AKPLATFORM::g_uAkPerfRecExecCount >= (__uExecutionCountStart__)) && (AKPLATFORM::g_uAkPerfRecExecCount <= (__uExecutionCountStop__)) ) \ 00563 AKPLATFORM::PerformanceCounter( &iAkPerfRecTimeBefore ); 00564 00565 #define AK_PERF_RECORDING_STOP( __StorageName__, __uExecutionCountStart__, __uExecutionCountStop__ ) \ 00566 if ( (AKPLATFORM::g_uAkPerfRecExecCount >= (__uExecutionCountStart__)) && (AKPLATFORM::g_uAkPerfRecExecCount <= (__uExecutionCountStop__)) ) \ 00567 { \ 00568 AkInt64 iAkPerfRecTimeAfter; \ 00569 AKPLATFORM::PerformanceCounter( &iAkPerfRecTimeAfter ); \ 00570 AKPLATFORM::g_fAkPerfRecExecTime += AKPLATFORM::Elapsed( iAkPerfRecTimeAfter, iAkPerfRecTimeBefore ); \ 00571 if ( AKPLATFORM::g_uAkPerfRecExecCount == (__uExecutionCountStop__) ) \ 00572 { \ 00573 AkReal32 fAverageExecutionTime = AKPLATFORM::g_fAkPerfRecExecTime/((__uExecutionCountStop__)-(__uExecutionCountStart__)); \ 00574 char str[256]; \ 00575 sprintf_s(str, 256, "%s average execution time: %f\n", __StorageName__, fAverageExecutionTime); \ 00576 AKPLATFORM::OutputDebugMsg( str ); \ 00577 } \ 00578 } \ 00579 AKPLATFORM::g_uAkPerfRecExecCount++; 00580 #endif // AK_ENABLE_PERF_RECORDING 00581 } 00582 00583 #endif // _AK_PLATFORM_FUNCS_H_
Was this page helpful?
Need Support?
Questions? Problems? Need more info? Contact us, and we can help!
Visit our Support pageTell us about your project. We're here to help.
Register your project and we'll help you get started with no strings attached!
Get started with Wwise