Elementos internos de biblioteca

En este tema se describe el diseño interno de la biblioteca DirectXMath.

Convenciones de llamada

Para mejorar la portabilidad y optimizar el diseño de datos, debe usar las convenciones de llamada adecuadas para cada plataforma compatible con la biblioteca directXMath. En concreto, cuando se pasan objetos XMVECTOR como parámetros, que se definen como alineados en un límite de 16 bytes, hay diferentes conjuntos de requisitos de llamada, según la plataforma de destino:

Para Windows de 32 bits

Para Windows de 32 bits, hay dos convenciones de llamada disponibles para pasar de forma eficaz __m128 valores (que implementa XMVECTOR en esa plataforma). El estándar es __fastcall, que puede pasar los tres primeros valores __m128 (instancias XMVECTOR ) como argumentos a una función en un registro SSE/SSE2 . __fastcall pasa los argumentos restantes a través de la pila.

Los compiladores más recientes de Microsoft Visual Studio admiten una nueva convención de llamada, __vectorcall, que puede pasar hasta seis valores __m128 (instancias XMVECTOR ) como argumentos para una función en un registro SSE/SSE2 . También puede pasar agregados vectoriales heterogéneos (también conocidos como XMMATRIX) a través de registros SSE/SSE2 si hay suficiente espacio.

Para ediciones de 64 bits de Windows

Para Windows de 64 bits, hay dos convenciones de llamada disponibles para pasar de forma eficaz los valores de __m128. El estándar es __fastcall, que pasa todos los valores __m128 de la pila.

Los compiladores de Visual Studio más recientes admiten la convención de llamada de __vectorcall, que puede pasar hasta seis valores de __m128 (instancias XMVECTOR ) como argumentos para una función en un registro SSE/SSE2 . También puede pasar agregados vectoriales heterogéneos (también conocidos como XMMATRIX) a través de registros SSE/SSE2 si hay suficiente espacio.

Para Windows en ARM

Windows en ARM & ARM64 admite pasar los cuatro primeros valores de __n128 (instancias XMVECTOR ) en el registro.

Solución DirectXMath

Los alias FXMVECTOR, GXMVECTOR, HXMVECTOR y CXMVECTOR admiten estas convenciones:

  • Use el alias FXMVECTOR para pasar hasta las tres primeras instancias de XMVECTOR usadas como argumentos para una función.
  • Use el alias GXMVECTOR para pasar la 4ª instancia de un XMVECTOR que se usa como argumento para una función.
  • Use el alias HXMVECTOR para pasar las instancias 5 y 6 de un XMVECTOR que se usa como argumento para una función. Para obtener información sobre consideraciones adicionales, consulte la documentación de __vectorcall.
  • Use el alias CXMVECTOR para pasar cualquier otra instancia de XMVECTOR utilizada como argumentos.

Nota

Para los parámetros de salida, use siempre XMVECTOR* o XMVECTOR& y ignorelos con respecto a las reglas anteriores para los parámetros de entrada.

 

Debido a las limitaciones de __vectorcall, se recomienda no usar GXMVECTOR o HXMVECTOR para constructores de C++. Solo tiene que usar FXMVECTOR para los tres primeros valores XMVECTOR y, a continuación, usar CXMVECTOR para el resto.

Los alias FXMMATRIX y CXMMATRIX ayudan a aprovechar el argumento HVA pasando con __vectorcall.

  • Use el alias FXMMATRIX para pasar el primer XMMATRIX como argumento a la función. Esto supone que no tiene más de dos argumentos FXMVECTOR o más de dos argumentos float, double o FXMVECTOR a la "derecha" de la matriz. Para obtener información sobre consideraciones adicionales, consulte la documentación de __vectorcall.
  • Use el alias CXMMATRIX de lo contrario.

Debido a las limitaciones de __vectorcall, se recomienda no usar nunca FXMMATRIX para constructores de C++. Simplemente use CXMMATRIX.

Además de los alias de tipo, también debe usar la anotación XM_CALLCONV para asegurarse de que la función usa la convención de llamada adecuada (__fastcall frente a __vectorcall) en función del compilador y la arquitectura. Debido a las limitaciones de __vectorcall, se recomienda no usar XM_CALLCONV para constructores de C++.

A continuación se muestran declaraciones de ejemplo que ilustran esta convención:

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);

Para admitir estas convenciones de llamada, estos alias de tipo se definen de la siguiente manera (los parámetros se deben pasar por valor para que el compilador los considere para pasar en el registro):

Para aplicaciones de Windows de 32 bits

Cuando se usa __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;

Al usar __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;

Para aplicaciones windows nativas de 64 bits

Cuando se usa __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;

Al usar __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 en ARM

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

Nota

Aunque todas las funciones se declaran insertadas y, en muchos casos, el compilador no tendrá que usar convenciones de llamada para estas funciones, hay casos en los que el compilador puede decidir que es más eficaz no insertar la función y, en estos casos, queremos la mejor convención de llamada posible para cada plataforma.

 

Equivalencia de tipos de biblioteca de gráficos

Para admitir el uso de directXMath Library, muchos tipos y estructuras de la biblioteca directXMath son equivalentes a las implementaciones de Windows de los tipos D3DDECLTYPE y D3DFORMAT , así como a los tipos de DXGI_FORMAT .

DirectXMath D3DDECLTYPE D3DFORMAT DXGI_FORMAT
XMBYTE2 DXGI_FORMAT_R8G8_SINT
XMBYTE4 D3DDECLTYPE_BYTE4 (solo Xbox) D3DFMT_x8x8x8x8 DXGI_FORMAT_x8x8x8x8_SINT
XMBYTEN2 D3DFMT_V8U8 DXGI_FORMAT_R8G8_SNORM
XMBYTEN4 D3DDECLTYPE_BYTE4N (solo Xbox) D3DFMT_x8x8x8x8 DXGI_FORMAT_x8x8x8x8_SNORM
XMCOLOR D3DDECLTYPE_D3DCOLOR D3DFMT_A8R8G8B8 DXGI_FORMAT_B8G8R8A8_UNORM (DXGI 1.1+)
XMDEC4 D3DDECLTYPE_DEC4 (solo Xbox) D3DDECLTYPE_DEC3 (solo Xbox)
XMDECN4 D3DDECLTYPE_DEC4N (solo Xbox) D3DDECLTYPE_DEC3N (solo 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 (use XMLoadUDecN4_XR y XMStoreUDecN4_XR).
XMUDEC4 D3DDECLTYPE_UDEC4 (solo Xbox)
D3DDECLTYPE_UDEC3 (solo Xbox)
D3DFMT_A2R10G10B10
D3DFMT_A2B10G10R10
DXGI_FORMAT_R10G10B10A2_UINT
XMUDECN4 D3DDECLTYPE_UDEC4N (solo Xbox)
D3DDECLTYPE_UDEC3N (solo 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 (solo Xbox) D3DFMT_x16x16x16x16 DXGI_FORMAT_R16G16B16A16_UINT
XMUSHORTN4 D3DDECLTYPE_USHORT4N D3DFMT_x16x16x16x16 DXGI_FORMAT_R16G16B16A16_UNORM

 

Constantes globales en la biblioteca DirectXMath

Para reducir el tamaño del segmento de datos, la biblioteca DirectXMath usa la macro XMGLOBALCONST para hacer uso de una serie de constantes internas globales en su implementación. Por convención, estas constantes globales internas tienen el prefijo g_XM. Normalmente, son uno de los siguientes tipos: XMVECTORU32, XMVECTORF32 o XMVECTORI32.

Estas constantes globales internas están sujetas a cambios en futuras revisiones de la biblioteca DirectXMath. Use funciones públicas que encapsulan las constantes siempre que sea posible en lugar de usar directamente g_XM valores globales. También puede declarar sus propias constantes globales mediante XMGLOBALCONST.

SSE de Windows frente a SSE2

El conjunto de instrucciones SSE solo proporciona compatibilidad con vectores de punto flotante de precisión sencilla. DirectXMath debe usar el conjunto de instrucciones SSE2 para proporcionar compatibilidad con vectores enteros. SSE2 es compatible con todos los procesadores Intel desde la introducción de Pentium 4, todos los procesadores AMD K8 y posteriores, y todos los procesadores compatibles con x64.

Nota

Windows 8 para x86 o posterior requiere compatibilidad con SSE2. Todas las versiones de Windows x64 requieren compatibilidad con SSE2. Windows en ARM/ARM64 requiere ARM_NEON.

 

Variantes rutinarias

Hay varias variantes de las funciones directXMath que facilitan el trabajo:

  • Funciones de comparación para crear bifurcaciones condicionales complicadas basadas en un número menor de operaciones de comparación de vectores. El nombre de estas funciones termina en "R", como XMVector3InBoundsR. Las funciones devuelven un registro de comparación como un valor devuelto UINT o como un parámetro de salida de UINT. Puede usar las macros XMComparision* para probar el valor.
  • Funciones de batch para realizar operaciones de estilo por lotes en matrices de vectores más grandes. El nombre de estas funciones termina en "Stream", como XMVector3TransformStream. Las funciones funcionan en una matriz de entradas y generan una matriz de salidas. Normalmente, toman un paso de entrada y salida.
  • Funciones de estimación que implementan una estimación más rápida en lugar de un resultado más lento y preciso. El nombre de estas funciones termina en "Est", como XMVector3NormalizeEst. El impacto en la calidad y el rendimiento del uso de la estimación varía de la plataforma a la plataforma, pero se recomienda usar variantes de estimación para código sensible al rendimiento.

Incoherencias de la plataforma

La biblioteca DirectXMath está pensada para su uso en aplicaciones y juegos gráficos sensibles al rendimiento. Por lo tanto, la implementación está diseñada para una velocidad óptima que realiza el procesamiento normal en todas las plataformas compatibles. Los resultados en condiciones de límite, especialmente aquellos que generan especiales de punto flotante, pueden variar de destino a destino. Este comportamiento también dependerá de otras opciones de configuración en tiempo de ejecución, como la palabra de control x87 para el destino no intrínseco de Windows 32 bits o la palabra de control SSE para Windows de 32 bits y 64 bits. Además, habrá diferencias en las condiciones de límite entre varios proveedores de CPU.

No use DirectXMath en aplicaciones científicas u otras aplicaciones en las que la precisión numérica sea primordial. Además, esta limitación se refleja en la falta de compatibilidad con cálculos de precisión dobles u otros extendidos.

Nota

Por lo general, las rutas de acceso de código escalares _XM_NO_INTRINSICS_ se escriben para el cumplimiento, no para el rendimiento. Los resultados de la condición de límite también variarán.

 

Extensiones específicas de la plataforma

La biblioteca DirectXMath está pensada para simplificar la programación SIMD de C++ que proporciona una excelente compatibilidad con las plataformas x86, x64 y Windows RT mediante instrucciones intrínsecas ampliamente compatibles (SSE2 y ARM-NEON).

Sin embargo, hay ocasiones en las que las instrucciones específicas de la plataforma pueden resultar beneficiosas. Debido a la forma en que se implementa DirectXMath, en muchos casos es trivial usar tipos DirectXMath directamente en instrucciones intrínsecas compatibles con el compilador estándar y usar DirectXMath como ruta de acceso de reserva para plataformas que no admiten la instrucción extendida.

Por ejemplo, este es un ejemplo simplificado de aprovechar la instrucción SSE 4.1 dot-product. Tenga en cuenta que debe proteger explícitamente la ruta de acceso de código para evitar generar excepciones de instrucciones no válidas en tiempo de ejecución. Asegúrese de que las rutas de acceso de código realizan un trabajo lo suficientemente significativo como para justificar el costo adicional de la bifurcación, la complejidad del mantenimiento de varias rutas de acceso de código, etc.

#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;
}

Para obtener más información sobre las extensiones específicas de la plataforma, consulta:

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

Guía de programación de DirectXMath