Elementos internos da biblioteca

Este tópico descreve o design interno da biblioteca DirectXMath.

Convenções de chamada

Para aprimorar a portabilidade e otimizar o layout de dados, você precisa usar as convenções de chamada apropriadas para cada plataforma compatível com a Biblioteca DirectXMath. Especificamente, quando você passa objetos XMVECTOR como parâmetros, que são definidos como alinhados em um limite de 16 bytes, há diferentes conjuntos de requisitos de chamada, dependendo da plataforma de destino:

Para Windows de 32 bits

Para o Windows de 32 bits, há duas convenções de chamada disponíveis para a passagem eficiente de valores de __m128 (que implementa XMVECTOR nessa plataforma). O padrão é __fastcall, que pode passar os três primeiros valores de __m128 (instâncias XMVECTOR ) como argumentos para uma função em um registro SSE/SSE2 . __fastcall passa os argumentos restantes por meio da pilha.

Os compiladores mais recentes do Microsoft Visual Studio dão suporte a uma nova convenção de chamada, __vectorcall, que pode passar até seis valores de __m128 (instâncias XMVECTOR ) como argumentos para uma função em um registro SSE/SSE2 . Ele também pode passar agregações de vetor heterogêneas (também conhecidas como XMMATRIX) por meio de registros SSE/SSE2 se houver espaço suficiente.

Para edições de 64 bits do Windows

Para o Windows de 64 bits, há duas convenções de chamada disponíveis para a passagem eficiente de valores de __m128 . O padrão é __fastcall, que passa todos os valores __m128 na pilha.

Os compiladores mais recentes do Visual Studio dão suporte à convenção de chamada __vectorcall, que pode passar até seis valores de __m128 (instâncias XMVECTOR ) como argumentos para uma função em um registro SSE/SSE2 . Ele também pode passar agregações de vetor heterogêneas (também conhecidas como XMMATRIX) por meio de registros SSE/SSE2 se houver espaço suficiente.

Para Windows no ARM

O Windows no ARM64 & dá suporte à passagem dos quatro primeiros valores de __n128 (instâncias XMVECTOR ) no registro.

Solução DirectXMath

Os aliases FXMVECTOR, GXMVECTOR, HXMVECTOR e CXMVECTOR dão suporte a estas convenções:

  • Use o alias FXMVECTOR para passar para as três primeiras instâncias de XMVECTOR usadas como argumentos para uma função.
  • Use o alias GXMVECTOR para passar a 4ª instância de um XMVECTOR usado como argumento para uma função.
  • Use o alias HXMVECTOR para passar as 5ª e 6ª instâncias de um XMVECTOR usadas como argumento para uma função. Para obter informações sobre considerações adicionais, consulte a documentação do __vectorcall.
  • Use o alias CXMVECTOR para passar quaisquer instâncias adicionais de XMVECTOR usadas como argumentos.

Observação

Para parâmetros de saída, sempre use XMVECTOR* ou XMVECTOR& e ignore-os em relação às regras anteriores para parâmetros de entrada.

 

Devido a limitações com __vectorcall, recomendamos que você não use GXMVECTOR ou HXMVECTOR para construtores C++. Basta usar FXMVECTOR para os três primeiros valores XMVECTOR e, em seguida, usar CXMVECTOR para o restante.

Os aliases FXMMATRIX e CXMMATRIX ajudam a aproveitar a passagem do argumento HVA com __vectorcall.

  • Use o alias FXMMATRIX para passar o primeiro XMMATRIX como um argumento para a função. Isso pressupõe que você não tenha mais de dois argumentos FXMVECTOR ou mais de dois argumentos float, double ou FXMVECTOR à 'direita' da matriz. Para obter informações sobre considerações adicionais, consulte a documentação do __vectorcall.
  • Caso contrário, use o alias CXMMATRIX .

Devido a limitações com __vectorcall, recomendamos que você nunca use FXMMATRIX para construtores C++. Basta usar CXMMATRIX.

Além dos aliases de tipo, você também deve usar a anotação XM_CALLCONV para garantir que a função use a convenção de chamada apropriada (__fastcall versus __vectorcall) com base no compilador e na arquitetura. Devido a limitações com __vectorcall, recomendamos que você não use XM_CALLCONV para construtores C++.

Veja a seguir declarações de exemplo que ilustram essa convenção:

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 dar suporte a essas convenções de chamada, esses aliases de tipo são definidos da seguinte maneira (os parâmetros devem ser passados por valor para o compilador considerá-los para passagem no registro):

Para aplicativos do Windows de 32 bits

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

Ao 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 aplicativos nativos do Windows de 64 bits

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

Ao 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 no ARM

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

Observação

Embora todas as funções sejam declaradas embutidas e, em muitos casos, o compilador não precise usar convenções de chamada para essas funções, há casos em que o compilador pode decidir que é mais eficiente não embutido a função e, nesses casos, queremos a melhor convenção de chamada possível para cada plataforma.

 

Equivalência de tipo de biblioteca de gráficos

Para dar suporte ao uso da Biblioteca DirectXMath, muitos tipos e estruturas da Biblioteca DirectXMath são equivalentes às implementações do Windows dos tipos D3DDECLTYPE e D3DFORMAT , bem como os tipos de DXGI_FORMAT .

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

 

Constantes globais na biblioteca DirectXMath

Para reduzir o tamanho do segmento de dados, a Biblioteca DirectXMath usa a macro XMGLOBALCONST para usar várias constantes internas globais em sua implementação. Por convenção, essas constantes globais internas são prefixadas por g_XM. Normalmente, eles são um dos seguintes tipos: XMVECTORU32, XMVECTORF32 ou XMVECTORI32.

Essas constantes globais internas estão sujeitas a alterações em revisões futuras da Biblioteca DirectXMath. Use funções públicas que encapsulam as constantes quando possível, em vez do uso direto de g_XM valores globais. Você também pode declarar suas próprias constantes globais usando XMGLOBALCONST.

Windows SSE versus SSE2

O conjunto de instruções SSE fornece suporte apenas para vetores de ponto flutuante de precisão única. O DirectXMath deve usar o conjunto de instruções SSE2 para fornecer suporte a vetores inteiros. O SSE2 é compatível com todos os processadores Intel desde a introdução do Pentium 4, todos os processadores AMD K8 e posteriores e todos os processadores compatíveis com x64.

Observação

Windows 8 para x86 ou posterior requer suporte para SSE2. Todas as versões do Windows x64 exigem suporte para SSE2. O Windows no ARM/ARM64 requer ARM_NEON.

 

Variantes de rotina

Há várias variantes de funções DirectXMath que facilitam o trabalho:

  • Funções de comparação para criar ramificações condicionais complicadas com base em um número menor de operações de comparação de vetor. O nome dessas funções termina em "R", como XMVector3InBoundsR. As funções retornam um registro de comparação como um valor retornado UINT ou como um parâmetro UINT out. Você pode usar as macros XMComparision* para testar o valor.
  • Funções de lote para executar operações em estilo de lote em matrizes de vetor maiores. O nome dessas funções termina em "Stream", como XMVector3TransformStream. As funções operam em uma matriz de entradas e geram uma matriz de saídas. Normalmente, eles fazem um passo de entrada e saída.
  • Funções de estimativa que implementam uma estimativa mais rápida em vez de um resultado mais lento e preciso. O nome dessas funções termina em "Est", como XMVector3NormalizeEst. O impacto na qualidade e no desempenho do uso da estimativa varia de plataforma para plataforma, mas recomendamos que você use variantes de estimativa para código sensível ao desempenho.

Inconsistências de plataforma

A biblioteca DirectXMath destina-se ao uso em jogos e aplicativos gráficos sensíveis ao desempenho. Portanto, a implementação foi projetada para uma velocidade ideal para realizar o processamento normal em todas as plataformas com suporte. Os resultados em condições de limite, particularmente aqueles que geram especiais de ponto flutuante, provavelmente variam de destino para destino. Esse comportamento também dependerá de outras configurações de tempo de execução, como a palavra de controle x87 para o destino sem intrínsecos do Windows de 32 bits ou a palavra de controle SSE para Windows de 32 bits e 64 bits. Além disso, haverá diferenças nas condições de limite entre vários fornecedores de CPU.

Não use DirectXMath em aplicativos científicos ou outros em que a precisão numérica seja primordial. Além disso, essa limitação se reflete na falta de suporte para cálculos de precisão dupla ou outras estendidas.

Observação

Os caminhos de código escalar _XM_NO_INTRINSICS_ geralmente são escritos para conformidade, não para desempenho. Os resultados da condição de limite também variam.

 

Extensões específicas da plataforma

A biblioteca DirectXMath destina-se a simplificar a programação SIMD do C++ fornecendo excelente suporte para plataformas x86, x64 e Windows RT usando instruções intrínsecas amplamente compatíveis (SSE2 e ARM-NEON).

Há momentos, no entanto, em que instruções específicas da plataforma podem ser benéficas. Devido à maneira como o DirectXMath é implementado, em muitos casos é trivial usar tipos DirectXMath diretamente em instruções intrínsecas com suporte do compilador padrão e usar DirectXMath como o caminho de fallback para plataformas que não dão suporte à instrução estendida.

Por exemplo, aqui está um exemplo simplificado de aproveitar a instrução SSE 4.1 dot-product. Observe que você deve proteger explicitamente o caminho do código para evitar gerar exceções de instrução inválidas em tempo de execução. Verifique se os caminhos de código fazem um trabalho significativo o suficiente para justificar o custo adicional de ramificação, a complexidade de manter vários caminhos de código e assim por diante.

#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 obter mais informações sobre extensões específicas da plataforma, consulte:

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

Guia de programação do DirectXMath