Condividi tramite


Interni della libreria

In questo argomento viene descritta la progettazione interna della libreria DirectXMath.

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

Guida per programmatori DirectXMath