티스토리 뷰

개발/DirectX

XMVECTOR와 XMFLOAT

미누시 2019. 11. 26. 23:46

DirectX12 외에도 일반적인 선형 대수 및 DirectX 응용 프로그램에 공통적인 그래픽 수한 연산을 위한 SIMD(single instruction multiple data) 친화적인 C++ 형식(type) 및 함수를 제공합니다. 이 라이브러리는 Windows 32-bit(x86), Windows 64-bit(x64), Windows RT 플랫폼에서 SSE2 및 ARM-NEON 인트린직(instrinsic) 지원을 Visual Studio 컴파일러를 통해서 최적화된 버전으로 지원합니다.

이번 포스팅에서는 그 중에서 벡터의 표현을 나타내는 데 사용할 수 있는 XMFLOAT 및 XMVECTOR의 차이에 대해서 알아보겠습니다.

 

 

 

XMVECTOR


XMVECTOR 타입은 DirectXMath에서 사용되는 핵심 벡터 타입입니다. XMVECTOR는 SIMD 하드웨어 레지스터에 대응되는데, 즉 128비트(단일정밀도 부동소수점 4개, 32 * 4비트) 크기의 타입을 SIMD 명령 하나로 네 값을 한 번에 처리할 수 있습니다.

x64 플랫폼 및 SSE2가 활성화된 x86 플랫폼에서 이 타입은 다음과 같이 정의됩니다:

typedef __m128 XMVECTOR;

 

__m128이 바로 SIMD 타입으로써, 벡터 계산시 이 타입이어야만 SIMD의 장점을 발휘할 수 있습니다. 단일정밀도 4개로 구성된 값이므로 4차원 벡터를 표현할 수 있지만, 나머지 요소들을 0으로 채워넣음으로써 2차원 및 3차원 벡터에서도 사용할 수 있습니다.

 

 

제약

XMVECTOR는 16바이트 경계로 정렬(정합, alignment) 되어야 하는 제약이 있습니다. 이러한 제약은 타입이 지역 변수(local variable)이거나 전역 변수(global variable)일 경우 자동으로 제약을 만족하는 정렬이 일어나므로 신경쓰지 않아도 됩니다.

하지만, 클래스의 멤버 변수로써로는 이 타입 대신에 XMFLOAT2, XMFLOAT3, 또는 XMFLOAT4를 사용하는 것이 권장됩니다.

 

 

 

XMFLOAT


XMFLOAT... 타입은 구조체 형식으로 XMFLOAT3 타입의 경우 다음과 같이 정의됩니다 :

struct XMFLOAT3
{
    float x;
    float y;
    float z;

    XMFLOAT3() = default;

    XMFLOAT3(const XMFLOAT3&) = default;
    XMFLOAT3& operator=(const XMFLOAT3&) = default;

    XMFLOAT3(const XMFLAOT3&&) = default;
    XMFLAOT3& operator=(const XMFLOAT3&&) = default;

    XM_CONSTEXPR XMFLOAT3(float _x, float _y, float _z) : x(_x), y(_y), z(_z){}
    explicit XMFLOAT3(_In_reads_(3) const float* pArray) : x(pArray[0]), y(pArray[1]), z(pArray[2]) {}
};

 

구조체를 보시면 아시겠지만, 단순히 3 요소 x,y,z 및 생성자, 복사생성자, 이동생성자, 대입 연산자 관련 함수들로 정의되어 있습니다.

 

 

 

왜 클래스 멤버변수에서는 XMVECTOR 대신 XMFLOAT인가?


마이크로소프트의 DirectXMath Programming Guide 섹션에서는 다음과 같이 설명하고 있습니다 :

XMVECTOR는 SIMD 하드웨어 레지스터의 프록시입니다(SSE, NEON 등의 레지스터에 대한 별칭). 즉, 라이브러리가 레지스터 형식 및 정렬 제약사항에 대해서 추상화를 수행하고 있습니다.

그러므로 현재 사용중인 시스템에서 형식에 따른 정렬(alignment)를 수행할 필요없이 사용할 수 있지만, 다른 시스템에서는 이러한 제약조건을 만족하지 않아 실패할 수 있습니다(예를 들어, 시스템 아키텍처가 다를 수 있습니다).

XMVECTOR가 언급한 특성을 가지고 있으므로 컴파일러는 이러한 제약사항을 만족하기 위해 로컨 변수로 사용될 때 스택에, 또는 전역 변수로 사용할 때 데이터 섹션에 자동으로 정렬하여 배치하는 작업을 수행합니다. 적절한 규칙을 사용하면 매개 변수로써 함수에 안전하게 전달할 수 있습니다.

하지만, 힙에서 할당하는 것은 복잡합니다. XMVECTOR 또는 XMMATRIX를 힙에 할당할 클래스 또는 구조체의 멤버 변수로써 사용될 때 주의를 기울여야 합니다. Windows x64에서는 모든 힙이 16바이트 정렬로 할당되지만, Windows x86에서는 모든 힙이 8바이트 정렬을 만족하며 할당되기 때문입니다.

이러한 문제점을 C++에서 new/delete 연산자 오버로딩을 통해서 내부적으로 16바이트 정렬을 구현하거나, 연산자 오버로딩의 대안으로 pImpl(pointer to Implementation) 이디엄을 사용할 수도 있습니다. 이 때 Impl 클래스가 내부적으로 정렬되어 있어야 합니다.

하지만, 이러한 방법보다 클래스 및 구조체에서 XMVECTOR / XMMATRIX를 직접 사용하지 않는 것이 더 쉽고 간결한(compact) 경우가 많습니다. 대신 XMFLOAT2, XMFLOAT3, XMFLOAT4 등을 멤버 변수로 사용하고, 벡터 적재 및 벡터 저장 함수를 사용하여 데이터를 효과적으로 XMVECTOR 또는 XMMATRIX로 옮기고, 계산을 수행하고, 결과를 저장할 수 있습니다.

 

이 엄격한 정렬 제약사항은 load/store 오버헤드 발생시 프로그래머에게 명확하게 보이므로 의도적으로 설계딘 것입니다. 하지만, 이것이 굉장히 지루하다면(tedious) DirectX11 및 DirectX1W 툴킷에 대한 래퍼 라이브러리인 SimpleMath를 고려할 수 있겠습니다.

 

 

 

XMFLOAT과 XMVECTOR로의 변환


XMFLOAT 타입을 XMVECTOR에 적재할 때 다음의 함수들을 사용할 수 있습니다 :

// XMFLOAT2를 XMVECTOR에 적재
XMVECTOR XM_CALLCONV XMLoadFloat2(const XMFLOAT2* pSource);

// XMFLOAT3를 XMVECTOR에 적재
XMVECTOR XM_CALLCONV XMLoadFloat3(const XMFLOAT3* pSource);

// XMFLOAT4를 XMVECTOR에 적재
XMVECTOR XM_CALLCONV XMLoadFloat4(const XMFLOAT4* pSource);

 

반대로 XMVECTOR 타입을 XMFLOAT에 저장할 때 다음의 함수들을 사용할 수 있습니다 :

// XMVECTOR를 XMFLOAT2로 저장
void XM_CALLCONV XMStoreFloat2(XMFLOAT2* pDestination, FXMVECTOR V);

// XMVECTOR를 XMFLOAT3로 저장
void XM_CALLCONV XMStoreFloat3(XMFLOAT3* pDestination, FXMVECTOR V);

// XMVECTOR를 XMFLOAT4로 저장
void XM_CALLCONV XMStoreFloat4(XMFLOAT4* pDestination, FXMVECTOR V);

 

이 이외의 다양한 함수들은 관련 레퍼런스들을 참조하시길 바랍니다.

 

 

 

레퍼런스


참고자료
Microsoft | Windows 개발자 센터 - DirectXMath
Microsoft | Windows 개발자 센터 - DirextXMath Programming Guide
Stackoverflow - Why convert to XMFLOAT instead of using XMVECTOR directly?
Stackoverflow - 16 byte alignment issue
Stackoverflow - Working with DirectXMath and D3DXMath

댓글