DirectX11 ฉันจะจัดการและอัปเดตบัฟเฟอร์คงที่หลายส่วนได้อย่างไร


13

เอาล่ะฉันมีเวลายากที่จะเข้าใจว่าบัฟเฟอร์คงที่จะถูกผูกไว้กับขั้นตอนท่อและปรับปรุง ฉันเข้าใจว่า DirectX11 สามารถมีบัฟเฟอร์คงที่ได้ถึง 15 shader ต่อสเตจและแต่ละบัฟเฟอร์สามารถเก็บค่าคงที่ได้มากถึง 4096 ค่า อย่างไรก็ตามฉันไม่เข้าใจว่า ID3D11Buffer COM ที่ใช้ในการโต้ตอบกับบัฟเฟอร์คงที่เป็นเพียงกลไก (หรือที่จับ) ที่ใช้สำหรับเติมช่องบัฟเฟอร์เหล่านี้หรือถ้าวัตถุอ้างอิงตัวอย่างของข้อมูลบัฟเฟอร์ที่ถูกผลักไปมา ระหว่าง GPU และ CPU

ฉันคิดว่าความสับสนของฉันในหัวข้อเป็นสาเหตุของปัญหาที่ฉันใช้บัฟเฟอร์คงที่สองแบบ

นี่คือตัวอย่างโค้ด shader

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 รู้ได้อย่างไรว่าบัฟเฟอร์ใดที่ฉันต้องการอัปเดตหรือแมป มันรู้จาก ID3D11Buffer COM ไหม?

แก้ไข -

เปลี่ยนแท็ก register register คงที่ในตัวอย่างด้านบน การใช้ (cb #) แทน (b #) ส่งผลกระทบต่อบัฟเฟอร์ไม่ให้อัปเดตอย่างถูกต้องด้วยเหตุผลบางประการ ไม่แน่ใจว่าฉันเลือกไวยากรณ์ดั้งเดิมหรือที่ใดก็ได้ แต่มันดูเหมือนจะเป็นปัญหาหลักของฉัน

คำตอบ:


18

ID3D11Buffer อ้างอิงหน่วยความจำจริงที่เก็บข้อมูลของคุณไม่ว่าจะเป็นบัฟเฟอร์จุดยอดบัฟเฟอร์คงที่หรืออะไรก็ตาม

บัฟเฟอร์คงทำงานเช่นเดียวกับบัฟเฟอร์จุดสุดยอดและบัฟเฟอร์ชนิดอื่น กล่าวคือข้อมูลในนั้นไม่สามารถเข้าถึงได้โดย GPU จนกว่าจะแสดงผลเฟรมจริงดังนั้นบัฟเฟอร์จะต้องยังคงใช้งานได้จนกว่า GPU จะเสร็จสิ้น คุณควรบัฟเฟอร์สองเท่าแต่ละบัฟเฟอร์คงที่ดังนั้นคุณจึงมีหนึ่งสำเนาเพื่ออัปเดตสำหรับเฟรมถัดไปและอีกหนึ่งสำเนาสำหรับ GPU ที่จะอ่านในขณะที่เรนเดอร์เฟรมปัจจุบัน สิ่งนี้คล้ายกับวิธีที่คุณทำบัฟเฟอร์จุดสุดยอดแบบไดนามิกสำหรับระบบอนุภาคหรือเช่นนั้น

register(cb0), register(cb1)การตั้งค่าในสอดคล้อง HLSL กับช่องใน VSSetConstantBuffers เมื่อคุณอัปเดตค่าคงที่ต่อเฟรมที่คุณต้องทำVSSetConstantBuffers(0, 1, &pBuffer)เพื่อตั้งค่า CB0 และเมื่อคุณอัปเดตค่าคงที่ต่อเฟรมที่คุณต้องทำVSSetConstantBuffers(1, 1, &pBuffer)เพื่อตั้งค่า CB1 การโทรแต่ละครั้งจะอัปเดตเฉพาะบัฟเฟอร์ที่อ้างถึงโดยพารามิเตอร์ start / count และไม่แตะตัวเลือกอื่น

คุณไม่จำเป็นต้องผูกบัฟเฟอร์เพื่ออัปเดตด้วย UpdateSubresource อันที่จริงแล้วมันไม่ควรถูกผูกไว้เมื่อคุณอัปเดตมิเช่นนั้นอาจเป็นการบังคับให้คนขับทำสำเนาหน่วยความจำเพิ่มเติมภายใน (ดูหน้า MSDNของ UpdateSubresource

ฉันไม่แน่ใจว่าคุณหมายถึงอะไรโดย "D3D11 รู้ได้อย่างไรว่าบัฟเฟอร์ใดที่ฉันต้องการอัปเดตหรือแมป" มันอัปเดตหรือแมปตัวชี้ที่คุณส่งต่อไป


3

ดูเหมือนจะมีความสับสนเกิดขึ้นมากมายในหัวข้อที่ต้องการผูกบัฟเฟอร์คงที่ใหม่หลังจากอัปเดตแล้ว ขณะที่ฉันเรียนรู้เกี่ยวกับสิ่งนี้เองฉันได้เห็นหัวข้อและการสนทนามากมายพร้อมความคิดเห็นที่ตรงข้ามกับสิ่งนี้ คือคำตอบที่ดีที่สุดที่นี่แนะนำเรียกXXSetConstantBuffersหลังจากที่คุณอัปเดตผ่านหรือUpdateSubresourceMap/Unmap

นอกจากนี้ตัวอย่าง D3D MSDN และเอกสารบางอย่างดูเหมือนจะใช้รูปแบบนี้การเชื่อมโยง (การเรียกXXSetConstantBuffers) ในแต่ละเฟรมหรือแม้กระทั่งต่อการดึงวัตถุพื้นฐานแม้ว่าจะอัปเดตเฉพาะบัฟเฟอร์ที่มีอยู่และไม่เปลี่ยนสล็อตเฉพาะด้วยบัฟเฟอร์ที่แตกต่างกันโดยสิ้นเชิง .

ฉันคิดว่าความเข้าใจผิดที่เลวร้ายที่สุดคือที่XXSetConstantBuffersจริงแล้ว "ส่งข้อมูลที่คุณได้อัปเดตไปยัง GPU ก่อนหน้านี้หรือแจ้งให้ทราบถึงการอัปเดตเพื่อรับค่าใหม่ - ซึ่งดูเหมือนว่าผิดทั้งหมด

อันที่จริงเมื่อใช้UpdateSubresourceหรือMap/Unmapเอกสารระบุว่า GPU อาจทำสำเนาหลายชุดภายในหากยังต้องการข้อมูลเก่า แต่นี่ไม่ใช่ข้อกังวลสำหรับผู้ใช้ของ API เมื่อมันมาถึงการอัพเดทบัฟเฟอร์ที่ถูกผูกไว้แล้ว ดังนั้นความจำเป็นในการหลุดอย่างชัดเจนจึงไม่จำเป็น

ในระหว่างการทดลองของฉันฉันได้ข้อสรุปว่าไม่จำเป็นต้องผูกบัฟเฟอร์ใหม่อีกครั้งXXSetConstantBuffersหลังจากอัปเดตจนกว่าจะไม่มีการผูกมัด! ตราบใดที่คุณใช้บัฟเฟอร์เดียวกัน (ใช้ร่วมกันระหว่าง shaders, ขั้นตอนการวางท่อคู่) ซึ่งถูกผูกไว้ครั้งเดียว (เช่นในช่วงเริ่มต้นขึ้น) คุณไม่จำเป็นต้องผูกมันใหม่อีกครั้งเพียงแค่อัปเดตพวกเขา

รหัสบางอย่างเพื่อแสดงธรรมชาติการทดสอบของฉันได้ดียิ่งขึ้น:

// 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

เพื่อ conlcude ดูเหมือนว่าไม่จำเป็นต้องมีการผูกมัดจนกว่าคุณจะเปลี่ยนบัฟเฟอร์ทั้งหมด แต่ตราบใดที่คุณแชร์บัฟเฟอร์และเลย์เอาต์ระหว่าง shaders (การปฏิบัติที่แนะนำ) ควรทำการผูกในกรณีเหล่านี้:

  • เมื่อเริ่มต้น - การเชื่อมโยงเริ่มต้น - หลังจากสร้างบัฟเฟอร์เช่น
  • หากคุณต้องการ / ได้รับการออกแบบให้ใช้มากกว่าหนึ่งบัฟเฟอร์ที่ผูกไว้กับสล็อตเฉพาะของหนึ่งหรือมากกว่าหนึ่งขั้นตอน
  • หลังจากล้างสถานะของ deviceContext (เมื่อปรับขนาดบัฟเฟอร์ / หน้าต่าง)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.