Внутренние компоненты библиотек

В этом разделе описывается внутренняя конструкция библиотеки DirectXMath.

Соглашения о вызовах

Чтобы повысить переносимость и оптимизировать макет данных, необходимо использовать соответствующие соглашения о вызовах для каждой платформы, поддерживаемой библиотекой DirectXMath. В частности, при передаче объектов XMVECTOR в качестве параметров, которые определяются как выровненные по границе 16-байтов, существуют различные наборы требований к вызову в зависимости от целевой платформы:

Для 32-разрядных Windows

Для 32-разрядной версии Windows существует два соглашения о вызовах, доступных для эффективного передачи значений __m128 (реализующих XMVECTOR на этой платформе). Стандартом является __fastcall, который может передавать первые три __m128 значения (экземпляры XMVECTOR ) в качестве аргументов функции в регистре SSE/SSE2 . __fastcall передает оставшиеся аргументы через стек.

Более новые компиляторы Microsoft Visual Studio поддерживают новое соглашение о вызовах, __vectorcall, которое может передавать до шести __m128 значений (экземпляры XMVECTOR) в качестве аргументов функции в регистре SSE/SSE2. Он также может передавать разнородные агрегаты векторов (также известные как XMMATRIX) через регистры SSE/SSE2, если достаточно места.

Для 64-разрядных выпусков Windows

Для 64-разрядной версии Windows существует два соглашения о вызовах, доступные для эффективного передачи значений __m128 . Стандарт __fastcall, который передает все __m128 значения в стеке.

Более новые компиляторы Visual Studio поддерживают соглашение о вызове __vectorcall, которое может передавать до шести __m128 значений (экземпляров XMVECTOR) в качестве аргументов функции в регистре SSE/SSE2. Он также может передавать разнородные агрегаты векторов (также известные как XMMATRIX) через регистры SSE/SSE2, если достаточно места.

Для Windows в ARM

Windows в ARM и ARM64 поддерживает передачу первых четырех __n128 значений (экземпляров XMVECTOR ) в регистре.

Решение DirectXMath

Псевдонимы FXMVECTOR, GXMVECTOR, HXMVECTOR и CXMVECTOR поддерживают следующие соглашения:

  • Используйте псевдоним FXMVECTOR для передачи до первых трех экземпляров XMVECTOR, используемых в качестве аргументов функции.
  • Используйте псевдоним GXMVECTOR для передачи 4-го экземпляра XMVECTOR, используемого в качестве аргумента функции.
  • Используйте псевдоним HXMVECTOR для передачи 5-го и 6-го экземпляров XMVECTOR, используемых в качестве аргумента функции. Дополнительные сведения см. в документации по __vectorcall.
  • Используйте псевдоним CXMVECTOR для передачи всех дополнительных экземпляров XMVECTOR, используемых в качестве аргументов.

Примечание.

Для выходных параметров всегда используйте XMVECTOR* или XMVECTOR& и игнорируйте их в отношении предыдущих правил входных параметров.

 

Из-за ограничений __vectorcall рекомендуется не использовать GXMVECTOR или HXMVECTOR для конструкторов C++. Просто используйте FXMVECTOR для первых трех значений XMVECTOR, а затем используйте CXMVECTOR для остальных.

Псевдонимы FXMMATRIX и CXMMATRIX помогают использовать преимущества аргумента HVA, передаваемого __vectorcall.

  • Используйте псевдоним FXMMATRIX, чтобы передать первый XMMATRIX в качестве аргумента функции. Предполагается, что у вас нет более двух аргументов FXMVECTOR или более двух аргументов float, double или FXMVECTOR вправо матрицы. Дополнительные сведения см. в документации по __vectorcall.
  • В противном случае используйте псевдоним CXMMATRIX.

Из-за ограничений __vectorcall рекомендуется никогда не использовать конструкторы FXMMATRIX для C++. Просто используйте CXMMATRIX.

Помимо псевдонимов типов, необходимо также использовать заметку XM_CALLCONV, чтобы убедиться, что функция использует соответствующее соглашение о вызовах (__fastcall и __vectorcall) на основе компилятора и архитектуры. Из-за ограничений __vectorcall рекомендуется не использовать XM_CALLCONV для конструкторов C++.

Ниже приведены примеры объявлений, которые иллюстрируют это соглашение:

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;

Windows на архитектуре ARM

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

Примечание.

Хотя все функции объявляются встроенными, и во многих случаях компилятору не нужно использовать соглашения о вызовах для этих функций, существуют случаи, когда компилятор может решить, что это более эффективно, чтобы не встраивать функцию, и в этих случаях мы хотим лучшего соглашения о вызовах для каждой платформы.

 

Эквивалентность типов графической библиотеки

Для поддержки использования библиотеки DirectXMath многие типы и структуры библиотек DirectXMath эквивалентны реализации D3DDECLTYPE и D3DFORMAT типов Windows, а также типов DXGI_FORMAT.

Directxmath D3DDECLTYPE D3DFORMAT DXGI_FORMAT
X МБ YTE2 DXGI_FORMAT_R8G8_SINT
X МБ YTE4 D3DDECLTYPE_BYTE4 (только Xbox) D3DFMT_x8x8x8x8 DXGI_FORMAT_x8x8x8x8_SINT
X МБ YTEN2 D3DFMT_V8U8 DXGI_FORMAT_R8G8_SNORM
X МБ YTEN4 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, чтобы обеспечить поддержку целочисленных векторов. SSE2 поддерживается всеми процессорами Intel с момента внедрения Процессоров Intel 4, всех процессоров AMD K8 и более поздних версий, а также всех процессоров с поддержкой x64.

Примечание.

Для Windows 8 для x86 или более поздней версии требуется поддержка SSE2. Для всех версий Windows x64 требуется поддержка SSE2. Для Windows в ARM или ARM64 требуется ARM_NEON.

 

Стандартные варианты

Существует несколько вариантов функций DirectXMath, которые упрощают работу:

  • Функции сравнения для создания сложной условной ветви на основе меньшего числа операций сравнения векторов. Имя этих функций заканчивается на "R", например XMVector3InBoundsR. Функции возвращают запись сравнения как возвращаемое значение UINT или как параметр UINT out. Для проверки значения можно использовать макросы XMComparision* .
  • Пакетные функции для выполнения операций с пакетным стилем для больших массивов векторов. Имя этих функций заканчивается в потоке, например XMVector3TransformStream. Функции работают с массивом входных данных и создают массив выходных данных. Как правило, они принимают входные и выходные шаги.
  • Функции оценки, реализующие более быструю оценку, а не более медленный, более точный результат. Имя этих функций заканчивается на "Est", например XMVector3NormalizeEst. Влияние на качество и производительность оценки зависит от платформы до платформы, но рекомендуется использовать варианты оценки для кода с учетом производительности.

Несоответствия платформы

Библиотека DirectXMath предназначена для использования в приложениях и играх с учетом производительности. Поэтому реализация предназначена для оптимальной скорости нормальной обработки на всех поддерживаемых платформах. Результаты в условиях границы, особенно те, которые создают специальные элементы с плавающей запятой, скорее всего, зависят от целевого объекта до целевого. Это поведение также будет зависеть от других параметров времени выполнения, таких как слово элемента управления x87 для целевого объекта Windows 32-разрядной версии без встроенных функций или слово элемента управления SSE для Windows 32-разрядной и 64-разрядной версии. Кроме того, существуют различия в условиях границ между различными поставщиками ЦП.

Не используйте DirectXMath в научных или других приложениях, где числовая точность является первостепенной. Кроме того, это ограничение отражается в отсутствии поддержки двойного или другого расширенных вычислений точности.

Примечание.

_XM_NO_INTRINSICS_ скалярные пути кода обычно записываются для соответствия требованиям, а не производительности. Их результаты условия границы также будут отличаться.

 

Расширения для конкретной платформы

Библиотека DirectXMath предназначена для упрощения программирования С++ SIMD, обеспечивая отличную поддержку платформ x86, x64 и Windows RT, используя широко поддерживаемые встроенные инструкции (SSE2 и ARM-NEON).

Однако есть времена, когда инструкции для конкретной платформы могут оказаться полезными. Благодаря реализации DirectXMath во многих случаях для использования типов DirectXMath непосредственно в стандартных встроенных инструкциях компилятора и использовать DirectXMath в качестве резервного пути для платформ, которые не поддерживают расширенную инструкцию.

Например, ниже приведен упрощенный пример использования инструкции SSE 4.1 dot-product. Обратите внимание, что необходимо явно защитить путь к коду, чтобы избежать создания недопустимых исключений инструкций во время выполнения. Убедитесь, что пути кода выполняют достаточную работу, чтобы оправдать дополнительные затраты на ветвление, сложность обслуживания нескольких путей кода и т. д.

#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 on MSDN 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