Wwise SDK 2015.1.9
버전
menu_open
link
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_
이 페이지가 도움이 되었나요?
작업하는 프로젝트에 대해 알려주세요. 언제든지 도와드릴 준비가 되어 있습니다.
프로젝트를 등록하세요. 아무런 조건이나 의무 사항 없이 빠른 시작을 도와드리겠습니다.
Wwise를 시작해 보세요