Elementos internos de biblioteca
En este tema se describe el diseño interno de la biblioteca DirectXMath.
- Convenciones de llamada
- Equivalencia de tipos de biblioteca de gráficos
- Constantes globales en la biblioteca DirectXMath
- SSE de Windows frente a SSE2
- Variantes de rutina
- Incoherencias de plataforma
- Extensiones específicas de la plataforma
- Temas relacionados
Convenciones de llamada
Para mejorar la portabilidad y optimizar el diseño de los 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, en función de la plataforma de destino:
Para Windows de 32 bits
Para Windows de 32 bits, hay dos convenciones de llamada disponibles para el paso eficaz de valores de __m128 (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 el paso eficaz de valores de __m128. El estándar es __fastcall, que pasa todos los valores __m128 de la pila.
Los compiladores más recientes de Microsoft Visual Studio admiten la 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 Windows en ARM
Windows en ARM y ARM64 admite pasar los cuatro primeros valores __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 que se usan 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 la 5ª y 6ª instancia 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 las instancias adicionales de XMVECTOR que se usan como argumentos.
Nota:
Para los parámetros de salida, use siempre XMVECTOR* o XMVECTOR& y omítalos con respecto a las reglas anteriores para los parámetros de entrada.
Debido a limitaciones con __vectorcall, se recomienda no usar GXMVECTOR o HXMVECTOR para constructores de C++. Simplemente use FXMVECTOR para los tres primeros valores de XMVECTOR y, a continuación, use CXMVECTOR para el resto.
Los alias FXMMATRIX y CXMMATRIX ayudan a aprovechar el uso del argumento HVA que pasa 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 flotantes, dobles o FXMVECTOR a la "derecha" de la matriz. Para obtener información sobre consideraciones adicionales, consulte la documentación de __vectorcall.
- En caso contrario, use el alias CXMMATRIX.
Debido a las limitaciones de __vectorcall, se recomienda que nunca use 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 deben pasarse por valor para que el compilador los considere para pasar en el registro):
Para aplicaciones de Windows de 32 bits
Al usar __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 de Windows nativas de 64 bits
Al usar __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 en línea y, en muchos casos, el compilador no necesitará 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 la biblioteca DirectXMath, 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 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 valores globales de g_XM. También puede declarar sus propias constantes globales mediante XMGLOBALCONST.
SSE de Windows frente a SSE2
El conjunto de instrucciones SSE solo admite vectores de punto flotante de precisión única. 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 versiones 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 de rutina
Hay varias variantes de las funciones de 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 parámetro de salida de UINT. Puede usar las macros XMComparision* para probar el valor.
- Funciones por lotes para realizar operaciones de estilo por lotes en matrices vectoriales 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 intervalo 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 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 configuraciones 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 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á diseñada para simplificar la programación SIMD de C++ que proporciona una excelente compatibilidad con plataformas x86, x64 y Windows RT con 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 cómo 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 coste adicional de bifurcación, la complejidad de mantener 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 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, consulte:
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