Interni della libreria
In questo argomento viene descritta la progettazione interna della libreria DirectXMath.
- Convenzioni di chiamata
- Equivalenza del tipo di libreria grafica
- Costanti globali nella libreria DirectXMath
- Windows SSE e SSE2
- Varianti di routine
- Incoerenze della piattaforma
- Estensioni specifiche della piattaforma
- Argomenti correlati
Convenzioni di chiamata
Per migliorare la portabilità e ottimizzare il layout dei dati, è necessario usare le convenzioni di chiamata appropriate per ogni piattaforma supportata dalla libreria DirectXMath. In particolare, quando si passano oggetti XMVECTOR come parametri, definiti come allineati su un limite di 16 byte, esistono diversi set di requisiti di chiamata, a seconda della piattaforma di destinazione:
Per Windows a 32 bit
Per Windows a 32 bit, sono disponibili due convenzioni di chiamata per il passaggio efficiente di valori di __m128 (che implementa XMVECTOR su tale piattaforma). Lo standard è __fastcall, che può passare i primi tre valori __m128 (istanze XMVECTOR ) come argomenti a una funzione in un registro SSE/SSE2 . __fastcall passa gli argomenti rimanenti tramite lo stack.
I compilatori di Microsoft Visual Studio più recenti supportano una nuova convenzione di chiamata, __vectorcall, che può passare fino a sei valori __m128 (istanze XMVECTOR ) come argomenti di una funzione in un registro SSE/SSE2 . Può anche passare aggregazioni di vettori eterogenei (note anche come XMMATRIX) tramite registri SSE/SSE2 se è disponibile spazio sufficiente.
Per le edizioni a 64 bit di Windows
Per Windows a 64 bit, sono disponibili due convenzioni di chiamata per il passaggio efficiente di valori __m128 . Lo standard è __fastcall, che passa tutti i valori __m128 nello stack.
I compilatori di Visual Studio più recenti supportano la convenzione di chiamata __vectorcall, che può passare fino a sei valori __m128 (istanze XMVECTOR ) come argomenti di una funzione in un registro SSE/SSE2 . Può anche passare aggregazioni di vettori eterogenei (note anche come XMMATRIX) tramite registri SSE/SSE2 se è disponibile spazio sufficiente.
Per Windows in ARM
Windows in ARM & ARM64 supporta il passaggio dei primi quattro valori __n128 (istanze XMVECTOR ) nel registro.
Soluzione DirectXMath
Gli alias FXMVECTOR, GXMVECTOR, HXMVECTOR e CXMVECTOR supportano queste convenzioni:
- Usare l'alias FXMVECTOR per passare fino alle prime tre istanze di XMVECTOR usate come argomenti di una funzione.
- Usare l'alias GXMVECTOR per passare la 4a istanza di un XMVECTOR usato come argomento a una funzione.
- Usare l'alias HXMVECTOR per passare le 5 e le 6 istanze di un XMVECTOR usato come argomento per una funzione. Per informazioni sulle considerazioni aggiuntive, vedere la documentazione __vectorcall.
- Usare l'alias CXMVECTOR per passare eventuali altre istanze di XMVECTOR usate come argomenti.
Nota
Per i parametri di output, usare sempre XMVECTOR* o XMVECTOR& e ignorarli rispetto alle regole precedenti per i parametri di input.
A causa delle limitazioni con __vectorcall, è consigliabile non usare GXMVECTOR o HXMVECTOR per i costruttori C++. È sufficiente usare FXMVECTOR per i primi tre valori XMVECTOR, quindi usare CXMVECTOR per il resto.
Gli alias FXMMATRIX e CXMMATRIX consentono di supportare l'uso dell'argomento HVA passando con __vectorcall.
- Usare l'alias FXMMATRIX per passare il primo XMMATRIX come argomento alla funzione. Ciò presuppone che non siano presenti più di due argomenti FXMVECTOR o più di due argomenti float, double o FXMVECTOR a destra della matrice. Per informazioni sulle considerazioni aggiuntive, vedere la documentazione __vectorcall.
- In caso contrario, usare l'alias CXMMATRIX .
A causa delle limitazioni con __vectorcall, è consigliabile non usare mai FXMMATRIX per i costruttori C++. Basta usare CXMMATRIX.
Oltre agli alias di tipo, è necessario usare anche l'annotazione XM_CALLCONV per assicurarsi che la funzione usi la convenzione di chiamata appropriata (__fastcall rispetto a __vectorcall) in base al compilatore e all'architettura. A causa delle limitazioni con __vectorcall, è consigliabile non usare XM_CALLCONV per i costruttori C++.
Di seguito sono riportate dichiarazioni di esempio che illustrano questa convenzione:
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);
Per supportare queste convenzioni di chiamata, questi alias di tipo vengono definiti come segue (i parametri devono essere passati per valore per il compilatore per considerarli per il passaggio nel registro):
Per le app di Windows a 32 bit
Quando si 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;
Quando si usa __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;
Per le app di Windows native a 64 bit
Quando si 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;
Quando si usa __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 su ARM
typedef const XMVECTOR FXMVECTOR;
typedef const XMVECTOR GXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;
typedef const XMMATRIX& FXMMATRIX;
typedef const XMMATRIX& CXMMATRIX;
Nota
Sebbene tutte le funzioni siano dichiarate inline e in molti casi il compilatore non dovrà usare le convenzioni di chiamata per queste funzioni, esistono casi in cui il compilatore può decidere che è più efficiente non inline la funzione e in questi casi si vuole la convenzione di chiamata migliore possibile per ogni piattaforma.
Equivalenza del tipo di libreria grafica
Per supportare l'uso della libreria DirectXMath, molti tipi e strutture della libreria DirectXMath sono equivalenti alle implementazioni di Windows dei tipi D3DDECLTYPE e D3DFORMAT, nonché ai tipi di 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 (usare XMLoadUDecN4_XR e 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 |
Costanti globali nella libreria DirectXMath
Per ridurre le dimensioni del segmento di dati, la libreria DirectXMath usa la macro XMGLOBALCONST per utilizzare una serie di costanti interne globali nella relativa implementazione. Per convenzione, tali costanti globali interne sono precedute da g_XM. In genere, sono uno dei tipi seguenti: XMVECTORU32, XMVECTORF32 o XMVECTORI32.
Queste costanti globali interne sono soggette a modifiche nelle revisioni future della libreria DirectXMath. Usare funzioni pubbliche che incapsulano le costanti quando possibile anziché usare direttamente i valori globali di g_XM . È anche possibile dichiarare costanti globali personalizzate usando XMGLOBALCONST.
Windows SSE e SSE2
Il set di istruzioni SSE fornisce supporto solo per vettori a virgola mobile e precisione singola. DirectXMath deve usare il set di istruzioni SSE2 per fornire il supporto del vettore integer. SSE2 è supportato da tutti i processori Intel dopo l'introduzione di Pentium 4, tutti i processori AMD K8 e versioni successive e tutti i processori che supportano x64.
Nota
Windows 8 per x86 o versioni successive richiede il supporto per SSE2. Tutte le versioni di Windows x64 richiedono il supporto per SSE2. Windows in ARM/ ARM64 richiede ARM_NEON.
Varianti di routine
Esistono diverse varianti di funzioni DirectXMath che semplificano l'esecuzione del lavoro:
- Funzioni di confronto per creare complicate diramazione condizionale in base a un numero minore di operazioni di confronto tra vettori. Il nome di queste funzioni termina in "R", ad esempio XMVector3InBoundsR. Le funzioni restituiscono un record di confronto come valore restituito UINT o come parametro out UINT. È possibile usare le macro XMComparision* per testare il valore.
- Funzioni batch per l'esecuzione di operazioni in stile batch su matrici vettoriali più grandi. Il nome di queste funzioni termina in "Stream", ad esempio XMVector3TransformStream. Le funzioni operano su una matrice di input e generano una matrice di output. In genere, accettano uno stride di input e output.
- Funzioni di stima che implementano una stima più veloce anziché un risultato più lento e più accurato. Il nome di queste funzioni termina in "Est", ad esempio XMVector3NormalizeEst. L'impatto sulla qualità e sulle prestazioni dell'uso della stima varia da piattaforma a piattaforma, ma è consigliabile usare varianti di stima per il codice sensibile alle prestazioni.
Incoerenze della piattaforma
La libreria DirectXMath è destinata all'uso in applicazioni e giochi grafici sensibili alle prestazioni. Pertanto, l'implementazione è progettata per una velocità ottimale di elaborazione normale in tutte le piattaforme supportate. I risultati in condizioni di limite, in particolare quelli che generano speciali a virgola mobile, possono variare da destinazione a destinazione. Questo comportamento dipenderà anche da altre impostazioni di runtime, ad esempio la parola di controllo x87 per la destinazione senza intrinseci di Windows 32 bit o la parola di controllo SSE per Windows a 32 bit e 64 bit. Inoltre, ci saranno differenze nelle condizioni di limite tra vari fornitori di CPU.
Non usare DirectXMath in applicazioni scientifiche o altre applicazioni in cui l'accuratezza numerica è fondamentale. Questa limitazione si riflette anche nella mancanza di supporto per calcoli di precisione doppia o estesa.
Nota
I percorsi di codice scalari _XM_NO_INTRINSICS_ vengono in genere scritti per la conformità, non per le prestazioni. Anche i risultati della condizione limite variano.
Estensioni specifiche della piattaforma
La libreria DirectXMath è progettata per semplificare la programmazione SIMD C++ fornendo un supporto eccellente per le piattaforme x86, x64 e Windows RT usando istruzioni intrinseche ampiamente supportate (SSE2 e ARM-NEON).
In alcuni casi, tuttavia, le istruzioni specifiche della piattaforma possono rivelarsi utili. A causa della modalità di implementazione di DirectXMath, in molti casi è semplice usare i tipi DirectXMath direttamente nelle istruzioni intrinseche supportate dal compilatore standard e usare DirectXMath come percorso di fallback per le piattaforme che non supportano l'istruzione estesa.
Ecco, ad esempio, un esempio semplificato di utilizzo dell'istruzione dot-product SSE 4.1. Si noti che è necessario proteggere in modo esplicito il percorso del codice per evitare di generare eccezioni di istruzioni non valide in fase di esecuzione. Assicurarsi che i percorsi di codice eseseguono operazioni sufficienti per giustificare il costo aggiuntivo della diramazione, la complessità della gestione di più percorsi di codice e così via.
#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;
}
Per altre informazioni sulle estensioni specifiche della piattaforma, vedi:
DirectXMath: SSE, SSE2 e ARM-NEON
DirectXMath: SSE3 e SSSE3
DirectXMath: SSE4.1 e SSE4.2
DirectXMath: AVX
DirectXMath: F16C e FMA
DirectXMath: AVX2
DirectXMath: ARM64