UE3 의 native 에는 유용한 매크로들이 존재한다. 이것들에 대해 살펴본다.
Game Thread → Render Thread 방향으로 커맨드를 전달하기 위한 매크로.
// RenderingThread.h #define ENQUEUE_RENDER_COMMAND( TypeName, Params ) \ { \ check( IsInGameThread() ); \ // 이 매크로를 사용하는 측은 당연히 Game Thread 여야 함 if ( GIsThreadedRendering ) \ // 랜더링 스레드가 수행중이라면 { \ ... [생략] \ // 랜더링 스레드에 의해 사용되는 링버퍼에 커맨드를 추가하는 로직 } \ else \ { \ TypeName TypeName##Command Params; \ // 랜더링 스레드가 수행중이지 않다면 바로 실행 TypeName##Command.Execute(); \ } \ }
위 매크로의 첫번째 파라메터로 들어가는 class 는 멤버함수로 Execute 가 있어야 한다는 전제가 깔려있는데, 이것은 사용자가 잊어버릴 수도 있는 매우 불친절한 전제이다. 그러므로 Execute 멤버함수를 생성해주고 이놈이 무엇을 수행할 지를 정의해주는 아래 매크로가 또 존재한다.
// RenderingThread.h #define ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER( TypeName, ParamType1, ParamName1, ParamValue1, Code ) \ class TypeName : public FRenderCommand \ { \ public: \ typename ParamType1 _ParamType1; \ TypeName( const _ParamType1& In##ParamName1 ) : \ ParamName1( In##ParamName1 ) \ {} \ virtual UINT Execute() \ { \ Code; \ return sizeof( *this ); \ } \ virtual const TCHAR* DescribeCommand() \ { \ return TEXT( #TypeName ); \ } \ private: \ ParamType1 ParamName1; \ }; \ ENQUEUE_RENDER_COMMAND( TypeName, (ParamValue1) );
그럼 이에 대한 예를 살펴본다.
// LaunchEngineLoop.cpp class FFrameEndSync { ... void Sync( UBOOL bAllowOneFrameThreadLag ) { ... ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER( FenceCommand, // class 이름 FEvent*, // Code 수행에 필요한 변수의 타입 EventToTrigger, // Code 수행에 필요한 변수 인스턴스 이름, 이것은 Code 어디엔가 사용되어져야 한다. Event[EventIndex], // 사용되어지는 실제 변수 { // Code. 여기서 위에 지정한 인스턴스 이름이 사용되는 것을 볼 수 있다. EventToTrigger->Trigger(); } ); ... } ... };
위 FFrameEndSync 는 Game Thread 와 Render Thread 간의 동기화를 맞추기 위해 사용되는 class 이다.
CPU 의 Cycle 을 측정하는 매크로.
// UnFile.h #define CLOCK_CYCLES( Timer ) { Timer -= appCycles(); } #define UNCLOCK_CYCLES( Timer ) { Timer += appCycles(); }
측정 시작 전에 CLOCK_CYCLES 를 호출하고 측정 완료 시점에 UNCLOCK_CYCLES 를 호출하는데 특이한 것은 시작할 때 현재 CPU Cycle 을 뺀다는 것이고 종료시점에 더한다는 것이다.
이것은 Timer 변수가 어떤 타입이 되었건 값의 범위를 넘어서서 생기는 winding 에는 상관하지 않고 최종적으로 변경된 CPU Cycle 차이만을 얻기위한 것이다.
특정 구간에서의 CPU 의 소모 Cycle 을 측정하는 매크로. 위 매크로의 작동방식과는 약간의 차이가 있다. 위 CLOCK_CYCLES 방식은 특정 변수에 기록하는 반면 이것은 Stats 에 기록한다.
// UnStats.h #define SCOPE_CYCLE_COUNTER( Stats ) \ FScopeCycleCounter CycleCount_##Stat( Stat );
앞에 FScope… 이 붙은 것은 특정 Scope1) 에서 class 인스턴스의 생성자와 소멸자가 자동으로 호출되는 특징을 이용하여 무엇을 측정하기 위한 class 의 prefix 라고 보면 된다.
FScope… 뒤에 CycleCounter 가 붙었으니 대충 특정 구간에서 CPU 의 Cycle 을 측정하는 class 정도로 생각하면 되겠다.
FScopeCycleCounter 는 내부적으로 FStatManager 를 이용하여 측정한다.