Partilhar via


Carregar diferentes tipos de recursos

Mostra como usar um buffer para carregar dados de buffer constante e dados de buffer de vértice para a GPU e como subalocar e colocar dados corretamente em buffers. O uso de um único buffer aumenta a flexibilidade de uso de memória e fornece aos aplicativos um controle mais rígido sobre o uso de memória. Também mostra as diferenças entre os modelos Direct3D 11 e Direct3D 12 para carregar diferentes tipos de recursos.

Carregue diferentes tipos de recursos

No Direct3D 12, você cria um buffer para acomodar diferentes tipos de dados de recursos para upload e copia dados de recursos para o mesmo buffer de maneira semelhante para dados de recursos diferentes. As exibições individuais são criadas para vincular esses dados de recursos ao pipeline de gráficos no modelo de vinculação de recursos do Direct3D 12.

No Direct3D 11, você cria buffers separados para diferentes tipos de dados de recursos (observe os diferentes BindFlags usados no código de exemplo do Direct3D 11 abaixo), vinculando explicitamente cada buffer de recursos ao pipeline gráfico e atualizando os dados de recursos com métodos diferentes com base em diferentes tipos de recursos.

Tanto no Direct3D 12 quanto no Direct3D 11, você deve usar recursos de upload somente onde a CPU gravará os dados uma vez e a GPU os lerá uma vez.

Em alguns casos,

  • a GPU lerá os dados várias vezes, ou
  • a GPU não lê os dados linearmente, ou
  • a renderização já é significativamente limitada pela GPU.

Nesses casos, a melhor opção pode ser usar ID3D12GraphicsCommandList::CopyTextureRegion ou ID3D12GraphicsCommandList::CopyBufferRegion para copiar os dados do buffer de carregamento para um recurso padrão.

Um recurso padrão pode residir na memória de vídeo física em GPUs discretas.

Exemplo de código: Direct3D 11

// Direct3D 11: Separate buffers for each resource type.

void main()
{
    // ...

    // Create a constant buffer.
    float constantBufferData[] = ...;

    D3D11_BUFFER_DESC constantBufferDesc = {0};  
    constantBufferDesc.ByteWidth = sizeof(constantBufferData);  
    constantBufferDesc.Usage = D3D11_USAGE_DYNAMIC;  
    constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;  
    constantBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;  

    ComPtr<ID3D11Buffer> constantBuffer;
    d3dDevice->CreateBuffer(  
        &constantBufferDesc,  
        NULL,
        &constantBuffer  
        );

    // Create a vertex buffer.
    float vertexBufferData[] = ...;

    D3D11_BUFFER_DESC vertexBufferDesc = { 0 };
    vertexBufferDesc.ByteWidth = sizeof(vertexBufferData);
    vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

    ComPtr<ID3D11Buffer> vertexBuffer;
    d3dDevice->CreateBuffer(
        &vertexBufferDesc,
        NULL,
        &vertexBuffer
        );

    // ...
}

void DrawFrame()
{
    // ...

    // Bind buffers to the graphics pipeline.
    d3dDeviceContext->VSSetConstantBuffers(0, 1, constantBuffer.Get());
    d3dDeviceContext->IASetVertexBuffers(0, 1, vertexBuffer.Get(), ...);

    // Update the constant buffer.
    D3D11_MAPPED_SUBRESOURCE mappedResource;  
    d3dDeviceContext->Map(
        constantBuffer.Get(),
        0, 
        D3D11_MAP_WRITE_DISCARD,
        0,
        &mappedResource
        );
    memcpy(mappedResource.pData, constantBufferData,
        sizeof(contatnBufferData));
    d3dDeviceContext->Unmap(constantBuffer.Get(), 0);  

    // Update the vertex buffer.
    d3dDeviceContext->UpdateSubresource(
        vertexBuffer.Get(),
        0,
        NULL,
        vertexBufferData,
        sizeof(vertexBufferData),
        0
    );

    // ...
}

Exemplo de código: Direct3D 12

// Direct3D 12: One buffer to accommodate different types of resources

ComPtr<ID3D12Resource> m_spUploadBuffer;
UINT8* m_pDataBegin = nullptr;    // starting position of upload buffer
UINT8* m_pDataCur = nullptr;      // current position of upload buffer
UINT8* m_pDataEnd = nullptr;      // ending position of upload buffer

void main()
{
    //
    // Initialize an upload buffer
    //

    InitializeUploadBuffer(64 * 1024);

    // ...
}

void DrawFrame()
{
    // ...

    // Set vertices data to the upload buffer.

    float vertices[] = ...;
    UINT verticesOffset = 0;
    ThrowIfFailed(
        SetDataToUploadBuffer(
            vertices, sizeof(float), sizeof(vertices) / sizeof(float),
            sizeof(float), 
            verticesOffset
            ));

    // Set constant data to the upload buffer.

    float constants[] = ...;
    UINT constantsOffset = 0;
    ThrowIfFailed(
        SetDataToUploadBuffer(
            constants, sizeof(float), sizeof(constants) / sizeof(float), 
            D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT, 
            constantsOffset
            ));

    // Create vertex buffer views for the new binding model.

    D3D12_VERTEX_BUFFER_VIEW vertexBufferViewDesc = {
        m_spUploadBuffer->GetGPUVirtualAddress() + verticesOffset,
        sizeof(vertices), // size
        sizeof(float) * 4,  // stride
    };

    commandList->IASetVertexBuffers( 
        0,
        1,
        &vertexBufferViewDesc,
        ));

    // Create constant buffer views for the new binding model.

    D3D12_CONSTANT_BUFFER_VIEW_DESC constantBufferViewDesc = {
        m_spUploadBuffer->GetGPUVirtualAddress() + constantsOffset,
        sizeof(constants) // size
         };

    d3dDevice->CreateConstantBufferView(
        &constantBufferViewDesc,
        ...
        ));

    // Continue command list building and execution ...
}

//
// Create an upload buffer and keep it always mapped.
//

HRESULT InitializeUploadBuffer(SIZE_T uSize)
{
    HRESULT hr = d3dDevice->CreateCommittedResource(
         &CD3DX12_HEAP_PROPERTIES( D3D12_HEAP_TYPE_UPLOAD ),    
               D3D12_HEAP_FLAG_NONE, 
               &CD3DX12_RESOURCE_DESC::Buffer( uSize ), 
               D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,  
               IID_PPV_ARGS( &m_spUploadBuffer ) );

    if (SUCCEEDED(hr))
    {
        void* pData;
        //
        // No CPU reads will be done from the resource.
        //
        CD3DX12_RANGE readRange(0, 0);
        m_spUploadBuffer->Map( 0, &readRange, &pData ); 
        m_pDataCur = m_pDataBegin = reinterpret_cast< UINT8* >( pData );
        m_pDataEnd = m_pDataBegin + uSize;
    }
    return hr;
}

//
// Sub-allocate from the buffer, with offset aligned.
//

HRESULT SuballocateFromBuffer(SIZE_T uSize, UINT uAlign)
{
    m_pDataCur = reinterpret_cast< UINT8* >(
        Align(reinterpret_cast< SIZE_T >(m_pDataCur), uAlign)
        );

    return (m_pDataCur + uSize > m_pDataEnd) ? E_INVALIDARG : S_OK;
}

//
// Place and copy data to the upload buffer.
//

HRESULT SetDataToUploadBuffer(
    const void* pData, 
    UINT bytesPerData, 
    UINT dataCount, 
    UINT alignment, 
    UINT& byteOffset
    )
{
    SIZE_T byteSize = bytesPerData * dataCount;
    HRESULT hr = SuballocateFromBuffer(byteSize, alignment);
    if (SUCCEEDED(hr))
    {
        byteOffset = UINT(m_pDataCur - m_pDataBegin);
        memcpy(m_pDataCur, pData, byteSize); 
        m_pDataCur += byteSize;
    }
    return hr;
}

//
// Align uLocation to the next multiple of uAlign.
//

UINT Align(UINT uLocation, UINT uAlign)
{
    if ( (0 == uAlign) || (uAlign & (uAlign-1)) )
    {
        ThrowException("non-pow2 alignment");
    }

    return ( (uLocation + (uAlign-1)) & ~(uAlign-1) );
}

Observe o uso das estruturas auxiliares CD3DX12_HEAP_PROPERTIES e CD3DX12_RESOURCE_DESC.

Constantes

Para definir constantes, vértices e índices num heap de upload ou readback, utilize as seguintes APIs.

Recursos

Recursos são o conceito Direct3D que abstrai o uso da memória física da GPU. Os recursos exigem espaço de endereço virtual da GPU para acessar a memória física. A criação de recursos é de thread livre.

Há três tipos de recursos com relação à criação de endereços virtuais e flexibilidade no Direct3D 12.

Recursos comprometidos

Recursos comprometidos são a ideia mais comum de recursos Direct3D ao longo das gerações. A criação de tal recurso aloca o intervalo de endereços virtuais, um heap implícito grande o suficiente para acomodar todo o recurso, e atribui o intervalo de endereços virtuais à memória física encapsulada pelo heap. As propriedades de heap implícitas devem ser passadas para assegurar uma paridade funcional com as versões anteriores do Direct3D. Consulte ID3D12Device::CreateCommittedResource.

Recursos reservados

Os recursos reservados são equivalentes aos recursos mosaico do Direct3D 11. Na sua criação, apenas um intervalo de endereços virtuais é alocado e não é mapeado para nenhum heap. O aplicativo mapeará esses recursos para pilhas mais tarde. As capacidades desses recursos estão atualmente inalteradas em relação ao Direct3D 11, pois podem ser mapeadas para uma pilha em uma granularidade de tile de 64 KB com UpdateTileMappings. Consulte ID3D12Device::CreateReservedResource.

Recursos colocados

Novo no Direct3D 12, você pode criar pilhas separadas dos recursos. Depois, você pode localizar vários recursos em um único heap. Você pode fazer isso sem criar recursos em mosaico ou reservados, habilitando as capacidades para todos os tipos de recursos que podem ser criados diretamente pelo seu aplicativo. Vários recursos podem se sobrepor e você deve usar o ID3D12GraphicsCommandList::ResourceBarrier para reutilizar a memória física corretamente. Consulte ID3D12Device::CreatePlacedResource.

Reflexão sobre o tamanho do recurso

Você deve usar a reflexão do tamanho do recurso para entender quanto espaço texturas com layouts de textura desconhecidos exigem em heaps. Buffers também são suportados, mas principalmente por uma questão de facilidade.

Você deve estar ciente das principais discrepâncias de alinhamento, para ajudar a empacotar recursos de forma mais densa.

Por exemplo, uma matriz de elemento único com um buffer de um byte retorna um de tamanho de 64 KB e um de alinhamento de 64 KB, porque os buffers só podem ser alinhados a 64 KB.

Além disso, uma matriz de três elementos, composta por duas texturas de texel único, alinhadas a 64 KB, e uma textura de texel único, alinhada a 4 MB, relata que os tamanhos diferem dependendo da ordem dos elementos na matriz. Se as texturas alinhadas de 4 MB estiverem no meio, o tamanho resultante de será de 12 MB. Caso contrário, o tamanho resultante é 8MB. O alinhamento retornado seria sempre de 4MB, o conjunto maior de todos os alinhamentos na matriz de recursos.

Consulte as seguintes APIs.

Alinhamento da memória intermédia

As restrições de alinhamento de buffer não mudaram desde o Direct3D 11, nomeadamente:

  • 4 MB para texturas com várias amostras.
  • 64 KB para texturas e buffers de amostra única.