Condividi tramite


Caricamento di diversi tipi di risorse

Illustra come usare un buffer per caricare i dati del buffer costante e i dati del buffer dei vertici nella GPU e come allocare e posizionare correttamente i dati all'interno dei buffer. L'uso di un singolo buffer aumenta la flessibilità di utilizzo della memoria e offre alle applicazioni un maggiore controllo sull'utilizzo della memoria. Mostra anche le differenze tra i modelli Direct3D 11 e Direct3D 12 per il caricamento di diversi tipi di risorse.

Caricare diversi tipi di risorse

In Direct3D 12 si crea un buffer per supportare diversi tipi di dati delle risorse per il caricamento e si copiano i dati delle risorse nello stesso buffer in modo analogo per i diversi dati delle risorse. Le singole visualizzazioni vengono quindi create per associare tali dati di risorsa alla pipeline grafica nel modello di associazione di risorse Direct3D 12.

In Direct3D 11 si creano buffer separati per diversi tipi di dati delle risorse (si notino i diversi BindFlags usati nel codice di esempio Direct3D 11 riportato di seguito), associando in modo esplicito ogni buffer di risorse alla pipeline grafica e aggiornando i dati delle risorse con metodi diversi in base a tipi di risorse diversi.

In Direct3D 12 e Direct3D 11 è consigliabile usare le risorse di caricamento solo in cui la CPU scriverà i dati una sola volta e la GPU la leggerà una sola volta.

In alcuni casi,

  • la GPU leggerà i dati più volte o
  • la GPU non leggerà i dati in modo lineare o
  • il rendering è notevolmente limitato dalla GPU.

In questi casi, è preferibile usare ID3D12GraphicsCommandList::CopyTextureRegion o ID3D12GraphicsCommandList::CopyBufferRegion per copiare i dati del buffer di caricamento in una risorsa predefinita.

Una risorsa predefinita può risiedere nella memoria video fisica su GPU discrete.

Esempio di codice: 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
    );

    // ...
}

Esempio di codice: 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) );
}

Si noti l'uso delle strutture helper CD3DX12_HEAP_PROPERTIES e CD3DX12_RESOURCE_DESC.

Costanti

Per impostare costanti, vertici e indici all'interno di un heap di caricamento o readback, usare le API seguenti.

Risorse

Le risorse sono il concetto Direct3D che astrae l'utilizzo della memoria fisica GPU. Le risorse richiedono spazio indirizzi virtuali GPU per accedere alla memoria fisica. La creazione di risorse è a thread libero.

Esistono tre tipi di risorse rispetto alla creazione e alla flessibilità degli indirizzi virtuali in Direct3D 12.

Risorse di cui è stato eseguito il commit

Le risorse di cui è stato eseguito il commit sono l'idea più comune delle risorse Direct3D nelle generazioni. La creazione di una risorsa di questo tipo alloca l'intervallo di indirizzi virtuali, un heap implicito sufficientemente grande da adattare l'intera risorsa ed esegue il commit dell'intervallo di indirizzi virtuali nella memoria fisica incapsulata dall'heap. Le proprietà dell'heap implicite devono essere passate per corrispondere alla parità funzionale con le versioni precedenti di Direct3D. Fare riferimento a ID3D12Device::CreateCommittedResource.

Risorse riservate

Le risorse riservate sono equivalenti alle risorse affiancate direct3D 11. Durante la creazione, viene allocato solo un intervallo di indirizzi virtuali e non viene eseguito il mapping ad alcun heap. L'applicazione eseguirà il mapping di tali risorse agli heap in un secondo momento. Le funzionalità di tali risorse sono attualmente invariate rispetto a Direct3D 11, perché possono essere mappate a un heap a una granularità del riquadro di 64 KB con UpdateTileMappings. Fare riferimento a ID3D12Device::CreateReservedResource.

Risorse inserite

Novità per Direct3D 12, è possibile creare heap separati dalle risorse. Successivamente, è possibile individuare più risorse all'interno di un singolo heap. È possibile farlo senza creare risorse affiancate o riservate, abilitando le funzionalità per tutti i tipi di risorse che possono essere create direttamente dall'applicazione. Più risorse potrebbero sovrapporsi ed è necessario usare ID3D12GraphicsCommandList::ResourceBarrier per riutilizzare correttamente la memoria fisica. Fare riferimento a ID3D12Device::CreatePlacedResource.

Reflection delle dimensioni delle risorse

È necessario usare la reflection delle dimensioni delle risorse per comprendere la quantità di trame spaziali con layout di trama sconosciuti richiesti negli heap. I buffer sono supportati anche, ma soprattutto per praticità.

È necessario essere consapevoli delle principali discrepanze di allineamento, per consentire di comprimere le risorse più densamente.

Ad esempio, una matrice a singolo elemento con un buffer a un byte restituisce una dimensione di 64 KB e un allineamento di 64 KB, perché i buffer possono essere allineati solo a 64 KB.

Inoltre, una matrice di tre elementi con due trame allineate a texel singolo 64 KB e una trama allineata a texel singolo 4 MB segnala dimensioni diverse in base all'ordine della matrice. Se le trame allineate a 4 MB si trovano al centro, la dimensione risultante è 12 MB. In caso contrario, la dimensione risultante è 8 MB. L'allineamento restituito sarebbe sempre 4 MB, il superset di tutti gli allineamenti nella matrice di risorse.

Fare riferimento alle API seguenti.

Allineamento del buffer

Le restrizioni di allineamento del buffer non sono cambiate rispetto a Direct3D 11, in particolare:

  • 4 MB per trame a più campioni.
  • 64 KB per trame e buffer a campione singolo.