库内部结构

本主题介绍 DirectXMath 库的内部设计。

调用约定

为了增强可移植性并优化数据布局,则需要针对 DirectXMath 库支持的每个平台使用适当的调用约定。 具体来说,在将 XMVECTOR 对象作为参数传递(这些对象被定义为以 16 字节边界对齐)时,根据目标平台的不同,会有不同的调用要求:

对于 32 位 Windows

对于 32 位 Windows,有两种调用约定可用于有效传递 __m128 值(在该平台上实现 XMVECTOR)。 标准是 __fastcall,它可以将前三个 __m128 值(XMVECTOR 实例)作为参数传递给 SSE/SSE2 寄存器中的函数。 __fastcall 通过堆栈传递剩余参数。

较新的 Microsoft Visual Studio 编译器支持新的调用约定 __vectorcall,它可以将最多六个 __m128 值(XMVECTOR 实例)作为参数传递给 SSE/SSE2 寄存器中的函数。 如果有足够的空间,它还可以通过 SSE/SSE2 寄存器传递异构向量聚合(也称为 XMMATRIX)。

对于 Windows 的 64 位版本

对于 64 位 Windows,有两种调用约定可用于有效传递 __m128 值。 标准是 __fastcall,它会传递堆栈上的所有 __m128 值。

较新的 Visual Studio 编译器支持调用约定 __vectorcall,它可以将最多六个 __m128 值(XMVECTOR 实例)作为参数传递给 SSE/SSE2 寄存器中的函数。 如果有足够的空间,它还可以通过 SSE/SSE2 寄存器传递异构向量聚合(也称为 XMMATRIX)。

适用于 ARM 上的 Windows

ARM 和 ARM64 上的 Windows 支持在寄存器中传递前四个 __n128 值(XMVECTOR 实例)。

DirectXMath 解决方案

FXMVECTORGXMVECTORHXMVECTORCXMVECTOR 别名支持这些约定:

  • 使用 FXMVECTOR 别名最多可传递 XMVECTOR 作为函数参数的前三个实例。
  • 使用 GXMVECTOR 别名来传递作为函数参数的 XMVECTOR 的第 4 个实例。
  • 使用 HXMVECTOR 别名来传递作为函数参数的 XMVECTOR 的第 5 和 6 个实例。 有关其他注意事项的信息,请参阅 __vectorcall 文档。
  • 使用 CXMVECTOR 别名传递用作参数的 XMVECTOR 实例。

注意

对于输出参数,应始终使用 XMVECTOR* 或 XMVECTOR&,而忽略前面关于输入参数的规则。

 

由于 __vectorcall 的限制,建议不要在 C++ 构造函数中使用 GXMVECTORHXMVECTOR。 只需对前三个 XMVECTOR 值使用 FXMVECTOR,然后对其余值使用 CXMVECTOR

FXMMATRIXCXMMATRIX 别名有助于利用 __vectorcall 的 HVA 参数传递。

  • 使用 FXMMATRIX 别名将第一个 XMMATRIX 作为参数传递给函数。 前提是在矩阵的“右侧”没有两个以上的 FXMVECTOR 参数或两个以上的 float、double 或 FXMVECTOR 参数。 有关其他注意事项的信息,请参阅 __vectorcall 文档。
  • 否则,请使用 CXMMATRIX 别名。

由于 __vectorcall 的限制,建议不要在 C++ 构造函数中使用 FXMMATRIX。 只需使用 CXMMATRIX

除类型别名外,还必须使用 XM_CALLCONV 注释,以确保函数根据编译器和体系结构使用适当的调用约定(__fastcall 与 __vectorcall)。 由于 __vectorcall 的限制,建议不要在 C++ 构造函数中使用 XM_CALLCONV。

以下是说明这一约定的声明示例:

XMMATRIX XM_CALLCONV XMMatrixLookAtLH(FXMVECTOR EyePosition, FXMVECTOR FocusPosition, FXMVECTOR UpDirection);

XMMATRIX XM_CALLCONV XMMatrixTransformation2D(FXMVECTOR ScalingOrigin,  float ScalingOrientation, FXMVECTOR Scaling, FXMVECTOR RotationOrigin, float Rotation, GXMVECTOR Translation);

void XM_CALLCONV XMVectorSinCos(XMVECTOR* pSin, XMVECTOR* pCos, FXMVECTOR V);

XMVECTOR XM_CALLCONV XMVectorHermiteV(FXMVECTOR Position0, FXMVECTOR Tangent0, FXMVECTOR Position1, GXMVECTOR Tangent1, HXMVECTOR T);

XMMATRIX(FXMVECTOR R0, FXMVECTOR R1, FXMVECTOR R2, CXMVECTOR R3)

XMVECTOR XM_CALLCONV XMVector2Transform(FXMVECTOR V, FXMMATRIX M);

XMMATRIX XM_CALLCONV XMMatrixMultiplyTranspose(FXMMATRIX M1, CXMMATRIX M2);

为支持这些调用约定,这些类型别名定义如下(参数必须以值传递,编译器才会将其视为寄存器内传递):

对于 32 位 Windows 应用

使用 __fastcall 时:

typedef const XMVECTOR  FXMVECTOR;
typedef const XMVECTOR& GXMVECTOR;
typedef const XMVECTOR& HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;
typedef const XMMATRIX& FXMMATRIX;
typedef const XMMATRIX& CXMMATRIX;

使用 __vectorcall 时:

typedef const XMVECTOR  FXMVECTOR;
typedef const XMVECTOR  GXMVECTOR;
typedef const XMVECTOR  HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;
typedef const XMMATRIX  FXMMATRIX;
typedef const XMMATRIX& CXMMATRIX;

对于 64 位本机 Windows 应用

使用 __fastcall 时:

typedef const XMVECTOR& FXMVECTOR;
typedef const XMVECTOR& GXMVECTOR;
typedef const XMVECTOR& HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;
typedef const XMMATRIX& FXMMATRIX;
typedef const XMMATRIX& CXMMATRIX;

使用 __vectorcall 时:

typedef const XMVECTOR  FXMVECTOR;
typedef const XMVECTOR  GXMVECTOR;
typedef const XMVECTOR  HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;
typedef const XMMATRIX  FXMMATRIX;
typedef const XMMATRIX& CXMMATRIX;

基于 ARM 的 Windows

typedef const XMVECTOR  FXMVECTOR;
typedef const XMVECTOR  GXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;
typedef const XMMATRIX& FXMMATRIX;
typedef const XMMATRIX& CXMMATRIX;

注意

虽然所有函数都以内联方式声明,而且在很多情况下编译器不需要对这些函数使用调用约定,但在某些情况下,编译器可能会认为不内联函数更高效,在这种情况下,我们希望每个平台都能使用最好的调用约定。

 

图形库类型等效性

为了支持 DirectXMath 库的使用,许多 DirectXMath 库类型和结构等同于 D3DDECLTYPED3DFORMAT 类型以及 DXGI_FORMAT 类型的 Windows 实现。

DirectXMath D3DDECLTYPE D3DFORMAT DXGI_FORMAT
XMBYTE2 DXGI_FORMAT_R8G8_SINT
XMBYTE4 D3DDECLTYPE_BYTE4(仅限 Xbox) D3DFMT_x8x8x8x8 DXGI_FORMAT_x8x8x8x8_SINT
XMBYTEN2 D3DFMT_V8U8 DXGI_FORMAT_R8G8_SNORM
XMBYTEN4 D3DDECLTYPE_BYTE4N(仅限 Xbox) D3DFMT_x8x8x8x8 DXGI_FORMAT_x8x8x8x8_SNORM
XMCOLOR D3DDECLTYPE_D3DCOLOR D3DFMT_A8R8G8B8 DXGI_FORMAT_B8G8R8A8_UNORM (DXGI 1.1+)
XMDEC4 D3DDECLTYPE_DEC4(仅限 Xbox) D3DDECLTYPE_DEC3(仅限 Xbox)
XMDECN4 D3DDECLTYPE_DEC4N(仅限 Xbox) D3DDECLTYPE_DEC3N(仅限 Xbox)
XMFLOAT2 D3DDECLTYPE_FLOAT2 D3DFMT_G32R32F DXGI_FORMAT_R32G32_FLOAT
XMFLOAT2A D3DDECLTYPE_FLOAT2 D3DFMT_G32R32F DXGI_FORMAT_R32G32_FLOAT
XMFLOAT3 D3DDECLTYPE_FLOAT3 DXGI_FORMAT_R32G32B32_FLOAT
XMFLOAT3A D3DDECLTYPE_FLOAT3 DXGI_FORMAT_R32G32B32_FLOAT
XMFLOAT3PK DXGI_FORMAT_R11G11B10_FLOAT
XMFLOAT3SE DXGI_FORMAT_R9G9B9E5_SHAREDEXP
XMFLOAT4 D3DDECLTYPE_FLOAT4 D3DFMT_A32B32G32R32F DXGI_FORMAT_R32G32B32A32_FLOAT
XMFLOAT4A D3DDECLTYPE_FLOAT4 D3DFMT_A32B32G32R32F DXGI_FORMAT_R32G32B32A32_FLOAT
XMHALF2 D3DDECLTYPE_FLOAT16_2 D3DFMT_G16R16F DXGI_FORMAT_R16G16_FLOAT
XMHALF4 D3DDECLTYPE_FLOAT16_4 D3DFMT_A16B16G16R16F DXGI_FORMAT_R16G16B16A16_FLOAT
XMINT2 DXGI_FORMAT_R32G32_SINT
XMINT3 DXGI_FORMAT_R32G32B32_SINT
XMINT4 DXGI_FORMAT_R32G32B32A32_SINT
XMSHORT2 D3DDECLTYPE_SHORT2 D3DFMT_V16U16 DXGI_FORMAT_R16G16_SINT
XMSHORTN2 D3DDECLTYPE_SHORT2N D3DFMT_V16U16 DXGI_FORMAT_R16G16_SNORM
XMSHORT4 D3DDECLTYPE_SHORT4 D3DFMT_x16x16x16x16 DXGI_FORMAT_R16G16B16A16_SINT
XMSHORTN4 D3DDECLTYPE_SHORT4N D3DFMT_x16x16x16x16 DXGI_FORMAT_R16G16B16A16_SNORM
XMUBYTE2 DXGI_FORMAT_R8G8_UINT
XMUBYTEN2 D3DFMT_A8P8、D3DFMT_A8L8 DXGI_FORMAT_R8G8_UNORM
XMUINT2 DXGI_FORMAT_R32G32_UINT
XMUINT3 DXGI_FORMAT_R32G32B32_UINT
XMUINT4 DXGI_FORMAT_R32G32B32A32_UINT
XMU555 D3DFMT_X1R5G5B5、D3DFMT_A1R5G5B5 DXGI_FORMAT_B5G5R5A1_UNORM
XMU565 D3DFMT_R5G6B5 DXGI_FORMAT_B5G6R5_UNORM
XMUBYTE4 D3DDECLTYPE_UBYTE4 D3DFMT_x8x8x8x8 DXGI_FORMAT_x8x8x8x8_UINT
XMUBYTEN4 D3DDECLTYPE_UBYTE4N D3DFMT_x8x8x8x8 DXGI_FORMAT_x8x8x8x8_UNORM
DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM(使用 XMLoadUDecN4_XRXMStoreUDecN4_XR。)
XMUDEC4 D3DDECLTYPE_UDEC4(仅限 Xbox)
D3DDECLTYPE_UDEC3(仅限 Xbox)
D3DFMT_A2R10G10B10
D3DFMT_A2B10G10R10
DXGI_FORMAT_R10G10B10A2_UINT
XMUDECN4 D3DDECLTYPE_UDEC4N(仅限 Xbox)
D3DDECLTYPE_UDEC3N(仅限 Xbox)
D3DFMT_A2R10G10B10
D3DFMT_A2B10G10R10
DXGI_FORMAT_R10G10B10A2_UNORM
XMUNIBBLE4 D3DFMT_A4R4G4B4、D3DFMT_X4R4G4B4 DXGI_FORMAT_B4G4R4A4_UNORM (DXGI 1.2+)
XMUSHORT2 D3DDECLTYPE_USHORT2 D3DFMT_G16R16 DXGI_FORMAT_R16G16_UINT
XMUSHORTN2 D3DDECLTYPE_USHORT2N D3DFMT_G16R16 DXGI_FORMAT_R16G16_UNORM
XMUSHORT4 D3DDECLTYPE_USHORT4(仅限 Xbox) D3DFMT_x16x16x16x16 DXGI_FORMAT_R16G16B16A16_UINT
XMUSHORTN4 D3DDECLTYPE_USHORT4N D3DFMT_x16x16x16x16 DXGI_FORMAT_R16G16B16A16_UNORM

 

DirectXMath 库中的全局常量

为了减小数据段的大小,DirectXMath 库使用 XMGLOBALCONST 宏来在其实现中使用一些全局内部常量。 按照约定,此类内部全局常量的前缀为 g_XM。 通常,它们属于以下类型之一:XMVECTORU32XMVECTORF32XMVECTORI32

这些内部全局常量可能会在 DirectXMath 库的未来版本中发生变化。 尽可能使用封装常量的公共函数,而不是直接使用 g_XM 全局值。 还可以使用 XMGLOBALCONST 声明自己的全局常量。

Windows SSE 与 SSE2

SSE 指令集仅支持单精度浮点矢量。 DirectXMath 必须使用 SSE2 指令集来提供整数向量支持。 自 Pentium 4 发布以来,所有 Intel 处理器、所有 AMD K8 及更高版本处理器以及所有支持 x64 的处理器都支持 SSE2。

注意

x86 或更高版本的 Windows 8 要求支持 SSE2。 所有版本的 Windows x64 都需要 SSE2 支持。 ARM / ARM64 上的 Windows 需要 ARM_NEON。

 

例程变体

DirectXMath 函数有多种变体,便于更轻松地完成工作:

  • 比较函数,用于根据较少的向量比较操作创建复杂的条件分支。 这些函数的名称以“R”结尾,例如 XMVector3InBoundsR。 函数以 UINT 返回值或 UINT 输出参数的形式返回比较记录。 可以使用 XMComparision* 宏来测试数值。
  • 批处理函数,用于对较大的向量数组执行批处理式操作。 这些函数的名称以“Stream”结尾,例如 XMVector3TransformStream。 这些函数对输入数组进行操作,并产生输出数组。 通常,它们采用输入和输出步幅。
  • 估算函数可以实现更快的估算,而不是更慢、更准确的结果。 这些函数的名称以“Est”结尾,例如 XMVector3NormalizeEst。 使用估算对质量和性能的影响因平台而异,但我们建议对性能敏感的代码使用估算变量。

平台不一致性

DirectXMath 库用于对性能敏感的图形应用程序和游戏。 因此,在所有支持的平台上进行正常处理时,都能达到最佳速度。 边界条件下的结果,尤其是产生浮点特异性的结果很可能因目标而异。 这种行为还取决于其他运行时设置,如 Windows 32 位无内核目标的 x87 控制词,或 Windows 32 位和 64 位的 SSE 控制词。 此外,不同 CPU 供应商的边界条件也会有所不同。

不要在科学或其他对数字精确度要求极高的应用中使用 DirectXMath。 此外,这一限制还体现在不支持双精度或其他扩展精度计算。

注意

_XM_NO_INTRINSICS_ 标量代码路径通常是为了符合要求而编写的,而不是为了提高性能。 它们的边界条件结果也会有所不同。

 

特定平台扩展

DirectXMath 库旨在简化 C++ SIMD 编程,使用广泛支持的内在指令(SSE2 和 ARM-NEON)为 x86、x64 和 Windows RT 平台提供出色的支持。

不过,有时特定平台的说明可能会证明是有益的。 由于 DirectXMath 的实现方式,在许多情况下,直接在标准编译器支持的内部语句中使用 DirectXMath 类型,并在不支持扩展指令的平台上使用 DirectXMath 作为后备路径都是微不足道的。

例如,下面是一个利用 SSE 4.1 点积指令的简化示例。 请注意,必须明确保护代码路径,以避免在运行时产生无效指令异常。 确保代码路径能完成足够多的工作,以证明分支的额外成本、维护多个代码路径的复杂性等是合理的。

#include <Windows.h>
#include <stdio.h>

#include <DirectXMath.h>

#include <intrin.h>
#include <smmintrin.h>

using namespace DirectX;

bool g_bSSE41 = false;

void DetectCPUFeatures()
{
#ifndef _M_ARM
   // See __cpuid documentation for more information

   int CPUInfo[4] = {-1};
#if defined(__clang__) || defined(__GNUC__)
   __cpuid(0, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]);
#else
   __cpuid(CPUInfo, 0);
#endif

   if ( CPUInfo[0] >= 1 )
   {
#if defined(__clang__) || defined(__GNUC__)
        __cpuid(1, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]);
#else
        __cpuid(CPUInfo, 1);
#endif

       if ( CPUInfo[2] & 0x80000 )
           g_bSSE41 = true;
   }
#endif
}

int main()
{
   if ( !XMVerifyCPUSupport() )
       return -1;

   DetectCPUFeatures();

   ...

   XMVECTORF32 v1 = { 1.f, 2.f, 3.f, 4.f };
   XMVECTORF32 v2 = { 5.f, 6.f, 7.f, 8.f };

   XMVECTOR r2, r3, r4;

   if ( g_bSSE41 )
   {
#ifndef _M_ARM
       r2 = _mm_dp_ps( v1, v2, 0x3f );
       r3 = _mm_dp_ps( v1, v2, 0x7f );
       r4 = _mm_dp_ps( v1, v2, 0xff );
#endif
   }
   else
   {
       r2 = XMVector2Dot( v1, v2 );
       r3 = XMVector3Dot( v1, v2 );
       r4 = XMVector4Dot( v1, v2 );
   }

   ...

   return 0;
}

有关特定平台扩展的详细信息,请参阅:

DirectXMath:SSE、SSE2 和 ARM-NEON
DirectXMath:SSE3 和 SSSE3
DirectXMath:SSE4.1 和 SSE4.2
DirectXMath:AVX
DirectXMath:F16C 和 FMA
DirectXMath:AVX2
DirectXMath:ARM64

DirectXMath 编程指南