Як я можу керувати та оновлювати постійні буфери постійних шейдерів?


13

Гаразд, мені важко зрозуміти, як постійні буфери прив’язані до стадії конвеєра та оновлюються. Я розумію, що DirectX11 може мати до 15 шейдерних константних буферів на етапі, і кожен буфер може містити до 4096 констант. Однак я не розумію, чи ID3D11Buffer COM, який використовується для взаємодії з постійними буферами, є лише механізмом (або ручкою), що використовується для заповнення цих буферних прорізів, або якщо об'єкт насправді посилається на певний екземпляр даних буфера, який висувається вперед і назад між графічним процесором і процесором.

Я думаю, що моя плутанина з цієї теми є причиною проблеми, з якою я використовую два різних постійних буфера.

Ось приклад коду шейдера.

cbuffer PerFrame : register(b0) {
    float4x4 view;
};

cbuffer PerObject : register(b1) {
    float4x4 scale;
    float4x4 rotation;
    float4x4 translation;
};

Як організується мій код, камера буде обробляти оновлення відповідних даних на кадр, а GameObjects оновлюватиме власні дані на кожен об'єкт. Обидва класи мають свій ID3D11Buffer, який використовується для цього (Використовуючи архітектуру концентратора, тому один клас GameObject буде обробляти рендеринг усіх встановлених GameObjects у світі).

Проблема полягає в тому, що я можу отримувати оновлений лише один раз, залежно від місця, і я припускаю, що в порядку оновлення один буфер заповнюється, а інший виводиться з нуля.

Це по суті мій код. Обидва класи використовують однакову логіку оновлення.

static PerObjectShaderBuffer _updatedBuffer; // PerFrameShaderBuffer if Camera class
_updatedBuffer.scale       = _rScale;
_updatedBuffer.rotation    = _rRotation;
_updatedBuffer.translation = _rTranslation;
pDeviceContext->UpdateSubresource(pShaderBuffer, 0 , 0, &_updatedBuffer, 0, 0);

pDeviceContext->VSSetShader(pVShader->GetShaderPtr(), 0, 0);
pDeviceContext->PSSetShader(pPShader->GetShaderPtr(), 0, 0);
pDeviceContext->VSSetConstantBuffers(1, 1, &pShaderBuffer);
pDeviceContext->IASetVertexBuffers(0, 1, &pVertexBuffer, &vStride, &_offset );
pDeviceContext->IASetPrimitiveTopology(topologyType);
pDeviceContext->Draw(bufSize, 0);

Мої основні питання -

  • Чи потрібно встановити або прив’язати ShaderBuffer, щоб оновити його за допомогою виклику UpdateSubresource? (Мається на увазі маніпулювати нею лише тоді, коли він знаходиться в конвеєрі) Або це сукупність даних, які надсилатимуться при виклику VSSetConstantBuffer? (Значення порядку прив’язки та оновлення даних не має значення, я можу оновити їх у конвеєрі чи якось на процесорі)
  • Під час встановлення або прив'язки буфера, чи потрібно мені посилатись на слот 0 для оновлення буфера PerFrame та слот 1 для оновлення буфера PerObject? Чи може якась плутанина з цим викликом у моєму коді викликати перезапис усіх буферів?
  • Як D3D11 знає, який буфер я хочу оновити чи зіставити? Чи знає це від використовуваного COM ID3D11Buffer?

Редагувати -

Змінено теги регістра постійного буфера в наведеному вище прикладі. Використання (cb #) замість (b #) вплинуло на неправильне оновлення буферів з певних причин. Не впевнений, де я взяв оригінальний синтаксис, чи він взагалі дійсний, але, здається, це була моя основна проблема.

Відповіді:


18

ID3D11Buffer посилається на фактичний фрагмент пам'яті, який зберігає ваші дані, будь то буфер вершин, постійний буфер чи інше.

Постійні буфери працюють так само, як вершинні буфери та інші види буферів. А саме, до них дані не мають доступу до GPU, поки він фактично не надає фрейм, тому буфер повинен залишатися дійсним, поки з ним не буде виконано GPU. Ви повинні подвоїти кожен постійний буфер, тому у вас є одна копія для оновлення наступного кадру та одна копія для читання GPU під час надання поточного кадру. Це аналогічно тому, як ви робили б динамічні вершинні буфери для системи частинок або подібного.

register(cb0), register(cb1)Налаштування в HLSL відповідають з пазами в VSSetConstantBuffers. Коли ви оновлюєте постійні константи, які ви робите VSSetConstantBuffers(0, 1, &pBuffer)для встановлення CB0, а коли ви оновлюєте ті об'єкти, які ви зробили б VSSetConstantBuffers(1, 1, &pBuffer)для встановлення CB1 Кожен виклик оновлює лише буфери, на які посилаються параметри початку / підрахунку, і не торкається інших.

Вам не потрібно прив'язувати буфер, щоб оновити його за допомогою UpdateSubresource. Насправді він не повинен прив'язуватися під час оновлення, інакше це може змусити драйвера робити додаткові копії пам'яті внутрішньо (див. Сторінку MSDN для UpdateSubresource, зокрема зауваження щодо суперечки про зменшення сторінки).

Я не впевнений, що ви розумієте під "Як D3D11 знає, який буфер я хочу оновити чи зіставити?" Він оновлює або карта того, вказівник якого ви передали.


3

Здається, існує велика плутанина навколо теми необхідності повторного зв’язування постійних буферів після їх оновлення. Коли я сам про це дізнаюся, я побачив багато тем і дискусій з протилежними думками з цього приводу. А саме найкраща відповідь тут, рекомендуючи дзвонити XXSetConstantBuffersпісля оновлення через UpdateSubresourceабо Map/Unmap.

Крім того, деякі зразки та документація D3D MSDN, здається, використовують цей шаблон, прив'язуючи (викликаючи XXSetConstantBuffers) на кожному кадрі або навіть на основі намальованого об'єкта, хоча вони лише оновлюють існуючий буфер, а не змінюють конкретний слот абсолютно іншим буфером .

Я думаю, що найгірше помилкове уявлення полягає в тому, що XXSetConstantBuffersнасправді "надсилає попередньо оновлені вами дані до GPU або повідомляє його про оновлення, щоб прийняти нові значення - що, здається, є абсолютно неправильним.

Дійсно, при використанні UpdateSubresourceабо Map/Unmapдокументації зазначено, що GPU може зробити внутрішньо кілька копій, якщо для цього все ще потрібні старі дані, але це не викликає занепокоєння для користувача API, коли справа стосується оновлення вже пов'язаного буфера. Звідси необхідність явного незв’язування видається зайвою.

Під час експерименту я прийшов до висновку, що повторно зв’язувати буфери через XXSetConstantBuffersоновлення не потрібно, якщо вони вже не пов'язані! Поки ви використовуєте ті самі буфери (спільні між шейдерами, рівними стадіями конвеєра), які один раз зв'язані (наприклад, на етапі запуску, наприклад), вам не потрібно їх повторно зв'язувати - просто оновіть їх.

Деякі коди, щоб краще показати мою природу експериментів:

// Memory double of the buffer (static)
ConstBuffer* ShaderBase::CBuffer = (ConstBuffer*)_aligned_malloc(sizeof(ConstBuffer), 16);
// Hardware resource pointer (static)
ID3D11Buffer* ShaderBase::m_HwBuffer = nullptr;

void ShaderBase::Buffer_init()
{
     // Prepare buffer description etc.
     // Create one global buffer shared across shaders
     result = device->CreateBuffer(&cBufferDesc, NULL, &m_HwBuffer);
     BindConstBuffer();
}

...

void ShaderBase::BindConstBuffer()
{
     // Bind buffer to both VS and PS stages since it's a big global one
     deviceContext->VSSetConstantBuffers(0, 1, &m_HwBuffer);
     deviceContext->PSSetConstantBuffers(0, 1, &m_HwBuffer);
}

...
bool ShaderBase::UpdateConstBuffers()
{
    ...
    D3D11_MAPPED_SUBRESOURCE mappedResource;

    // Lock the constant buffer so it can be written to.
    deviceContext->Map(m_HwBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);

    // Get a pointer to the data in the constant buffer.
    ConstBuffer* dataPtr = (ConstBuffer*)mappedResource.pData;
    memcpy(dataPtr, CBuffer, sizeof(ConstBuffer));

    // Unlock the constant buffer.
    deviceContext->Unmap(m_HwBuffer, 0);
    return true;
}

// May be called multiple times per frame (multiple render passes)
void DrawObjects()
{
    // Simplified version
    for each Mesh _m to be drawn
    {
        // Some changes are per frame - but since we have only one global buffer to which we 
        // write with write-discard we need to set all of the values again when we update per-object
        ShaderBase::CBuffer->view = view;
        ShaderBase::CBuffer->projection = projection;
        ShaderBase::CBuffer->cameraPosition = m_Camera->GetPosition();

        ... 

        ShaderBase::CBuffer->lightDirection = m_Light->GetDirection();

        ShaderBase::CBuffer->lightView = lightView;
        ShaderBase::CBuffer->lightProjection = lightProjection;
        ShaderBase::CBuffer->world = worldTransform;

        // Only update! No rebind!
        if (ShaderBase::UpdateConstBuffers() == false)
            return false;

        _m->LoadIABuffers(); // Set the vertex and index buffers for the mesh
        deviceContext->DrawIndexed(_m->indexCount, 0, 0);
    }
}

Ось деякі теми з Інтернету (форуми gamedev), які, здається, приймають та рекомендують такий підхід: http://www.gamedev.net/topic/649410-set-constant-buffers-every-frame/?view=findpost&p=5105032 та http://www.gamedev.net/topic/647203-updating-constant-buffers/#entry5090000

Для того, щоб вирішити, дійсно здається, що прив'язка не потрібна, якщо ви повністю не зміните буфер, але поки ви ділитесь буферами та їх компонуванням між шейдерами (рекомендована практика) прив'язку слід робити в таких випадках:

  • При запуску - початкове прив'язування - після створення буфера, наприклад.
  • Якщо вам потрібно / призначено використовувати більше одного буфера, прив'язаного до певного слоту однієї або декількох стадій.
  • Після очищення стану пристрою Context (при зміні розмірів буферів / вікон)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.