Componentes internos da biblioteca
Este tópico descreve o design interno da biblioteca DirectXMath.
- Convenções de Convocação
- Equivalência de Tipo de Biblioteca de Gráficos
- constantes globais na biblioteca DirectXMath
- SSE do Windows versus SSE2
- Variantes de rotina
- Inconsistências da plataforma
- Extensões específicas da plataforma
- Tópicos relacionados
Convenções de chamada
Para melhorar a portabilidade e otimizar o layout de dados, você precisa usar as convenções de chamada apropriadas para cada plataforma suportada pela 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 Windows de 32 bits, há duas convenções de chamada disponíveis para a passagem eficiente de valores __m128 (que implementa XMVECTOR nessa plataforma). O padrão é __fastcall, que pode passar os três primeiros valores de __m128 (XMVECTOR instâncias) como argumentos para uma função em um registro de SSE/SSE2. __fastcall passa os argumentos restantes através da pilha.
Os compiladores mais recentes do Microsoft Visual Studio oferecem suporte a uma nova convenção de chamada, __vectorcall, que pode passar até seis valores de __m128 (XMVECTOR instâncias) como argumentos para uma função em um registro de SSE/SSE2. Ele também pode passar agregados vetoriais heterogêneos (também conhecidos como XMMATRIX) através registros de 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 uma passagem eficiente de valores de __m128. O padrão é __fastcall, que passa todos os __m128 valores na pilha.
Os compiladores mais recentes do Visual Studio oferecem suporte à convenção de chamada de __vectorcall, que pode passar até seis valores de __m128 (XMVECTOR instâncias) como argumentos para uma função em um registro de SSE/SSE2. Ele também pode passar agregados vetoriais heterogêneos (também conhecidos como XMMATRIX) através registros de SSE/SSE2 se houver espaço suficiente.
para Windows em ARM
O Windows no ARM & ARM64 suporta a passagem dos primeiros quatro valores de __n128 (XMVECTOR instâncias) no registro.
solução DirectXMath
Os FXMVECTOR, GXMVECTOR, HXMVECTORe de CXMVECTOR suportam estas convenções:
- Use o FXMVECTOR alias para passar até as três primeiras instâncias de XMVECTOR usado como argumentos para uma função.
- Use o alias de GXMVECTOR para passar a 4ª instância de um XMVECTOR usado como argumento para uma função.
- Use o alias de HXMVECTOR para passar a 5ª e 6ª instâncias de um XMVECTOR usado como argumento para uma função. Para obter informações sobre considerações adicionais, consulte a documentação __vectorcall.
- Use o alias CXMVECTOR para passar quaisquer outras instâncias 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 de XMVECTOR e, em seguida, usar CXMVECTOR para o resto.
Os FXMMATRIX e aliases de CXMMATRIX ajudam a dar suporte ao aproveitamento do argumento HVA que passa com __vectorcall.
- Use o FXMMATRIX alias para passar o primeiro XMMATRIX como um argumento para a função. Isso pressupõe que você não tenha mais de dois argumentos de FXMVECTOR ou mais de dois argumentos de FXMVECTOR flutuante, duplo ou à 'direita' da matriz. Para obter informações sobre considerações adicionais, consulte a documentação __vectorcall.
- Use o CXMMATRIX alias caso contrário.
Devido às 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++.
Seguem-se exemplos de declarações que ilustram esta 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 forma (os parâmetros devem ser passados por valor para que o compilador os considere para passagem no registro):
Para aplicativos do Windows de 32 bits
Quando utiliza __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 utiliza __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
Quando utiliza __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 utiliza __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 em 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 em linha e, em muitos casos, o compilador não precisará usar convenções de chamada para essas funções, há casos em que o compilador pode decidir que é mais eficiente não embutir 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 suportar o uso da Biblioteca DirectXMath, muitos tipos e estruturas de Biblioteca DirectXMath são equivalentes às implementações do Windows dos tipos D3DDECLTYPE e D3DFORMAT, bem como aos tipos DXGI_FORMAT.
DirectXMath | D3DDECLTYPE | D3DFORMAT | DXGI_FORMAT |
---|---|---|---|
XMBYTE2 | DXGI_FORMAT_R8G8_SINT | ||
XMBYTE4 | D3DDECLTYPE_BYTE4 (Apenas Xbox) | D3DFMT_x8x8x8x8 | DXGI_FORMAT_x8x8x8x8_SINT |
XMBYTEN2 | D3DFMT_V8U8 | DXGI_FORMAT_R8G8_SNORM | |
XMBYTEN4 | D3DDECLTYPE_BYTE4N (Apenas Xbox) | D3DFMT_x8x8x8x8 | DXGI_FORMAT_x8x8x8x8_SNORM |
XMCOLOR | D3DDECLTYPE_D3DCOLOR | D3DFMT_A8R8G8B8 | DXGI_FORMAT_B8G8R8A8_UNORM (DXGI 1.1+) |
XMDEC4 | D3DDECLTYPE_DEC4 (Apenas Xbox) | D3DDECLTYPE_DEC3 (Apenas Xbox) | |
XMDECN4 | D3DDECLTYPE_DEC4N (Apenas Xbox) | D3DDECLTYPE_DEC3N (Apenas 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 e XMStoreUDecN4_XR.) |
XMUDEC4 | D3DDECLTYPE_UDEC4 (Apenas Xbox) D3DDECLTYPE_UDEC3 (Apenas Xbox) |
D3DFMT_A2R10G10B10 D3DFMT_A2B10G10R10 |
DXGI_FORMAT_R10G10B10A2_UINT |
XMUDECN4 | D3DDECLTYPE_UDEC4N (Apenas Xbox) D3DDECLTYPE_UDEC3N (Apenas 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 (Apenas 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 fazer uso de várias constantes internas globais em sua implementação. Por convenção, tais constantes globais internas são prefixadas por g_XM. Normalmente, eles são um dos seguintes tipos: XMVECTORU32, XMVECTORF32ou 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. SSE2 é suportado por 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
O Windows 8 para x86 ou posterior requer suporte para SSE2. Todas as versões do Windows x64 requerem suporte para SSE2. Windows em ARM / ARM64 requer ARM_NEON.
Variantes de rotina
Existem várias variantes de funções DirectXMath que facilitam o seu 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 vetorial. O nome dessas funções termina em "R", como XMVector3InBoundsR. As funções retornam um registro de comparação como um valor de retorno UINT ou como um parâmetro UINT out. Você pode usar o XMComparision* macros para testar o valor.
- Funções de lote para executar operações de estilo de lote em matrizes vetoriais 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 dão 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 da plataforma
A biblioteca DirectXMath destina-se ao uso em aplicativos gráficos e jogos sensíveis ao desempenho. Portanto, a implementação é projetada para a velocidade ideal fazendo o processamento normal em todas as plataformas suportadas. Os resultados em condições-limite, particularmente aqueles que geram especiais de ponto flutuante, provavelmente variam de alvo para alvo. 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ínseca de 32 bits do Windows ou a palavra de controle SSE para Windows de 32 bits e 64 bits. Além disso, haverá diferenças nas condições de fronteira entre vários fornecedores de CPU.
Não use DirectXMath em aplicações científicas ou outras onde a precisão numérica é primordial. Além disso, essa limitação se reflete na falta de suporte para cálculos de precisão dupla ou outros estendidos.
Observação
Os _XM_NO_INTRINSICS_ caminhos de código escalar geralmente são escritos para fins de conformidade, não de desempenho. Os resultados da sua condição limite também variam.
Extensões específicas da plataforma
A biblioteca DirectXMath destina-se a simplificar a programação C++ SIMD, fornecendo excelente suporte para plataformas x86, x64 e Windows RT usando instruções intrínsecas amplamente suportadas (SSE2 e ARM-NEON).
Há momentos, no entanto, em que instruções específicas da plataforma podem ser benéficas. Devido à forma como o DirectXMath é implementado, em muitos casos é trivial usar tipos DirectXMath diretamente em instruções intrínsecas suportadas pelo compilador padrão e usar o DirectXMath como o caminho de fallback para plataformas que não suportam a instrução estendida.
Por exemplo, aqui está um exemplo simplificado de aproveitamento da instrução de produto ponto SSE 4.1. Observe que você deve proteger explicitamente o caminho de código para evitar gerar exceções de instrução inválidas em tempo de execução. Certifique-se de que os caminhos de código façam 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 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