库内部结构
本主题介绍 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 解决方案
FXMVECTOR、GXMVECTOR、HXMVECTOR 和 CXMVECTOR 别名支持这些约定:
- 使用 FXMVECTOR 别名最多可传递 XMVECTOR 作为函数参数的前三个实例。
- 使用 GXMVECTOR 别名来传递作为函数参数的 XMVECTOR 的第 4 个实例。
- 使用 HXMVECTOR 别名来传递作为函数参数的 XMVECTOR 的第 5 和 6 个实例。 有关其他注意事项的信息,请参阅 __vectorcall 文档。
- 使用 CXMVECTOR 别名传递用作参数的 XMVECTOR 实例。
注意
对于输出参数,应始终使用 XMVECTOR* 或 XMVECTOR&,而忽略前面关于输入参数的规则。
由于 __vectorcall 的限制,建议不要在 C++ 构造函数中使用 GXMVECTOR 或 HXMVECTOR。 只需对前三个 XMVECTOR 值使用 FXMVECTOR,然后对其余值使用 CXMVECTOR。
FXMMATRIX 和 CXMMATRIX 别名有助于利用 __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 库类型和结构等同于 D3DDECLTYPE 和 D3DFORMAT 类型以及 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_XR 和 XMStoreUDecN4_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。 通常,它们属于以下类型之一:XMVECTORU32、XMVECTORF32 或 XMVECTORI32。
这些内部全局常量可能会在 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