0
\$\begingroup\$

I'm trying to take the DX12 Cube Demo that comes with VS2017 Community and draw three cubes instead of one. Unfortunately, the VertexShader doesn't seem to be getting my instance data.

In my InputLayout of CreateDeviceDependentResources, I'm trying to put my InstanceBuffer in Slot 1 per https://www.braynzarsoft.net/viewtutorial/q16390-33-instancing-with-indexed-primitives
(Yes, I know the Braynzarsoft Example is D3D11) :

    ...
    static const D3D12_INPUT_ELEMENT_DESC inputLayout[] =
    {
        // Vertex Buffer Data
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        /* { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, Old Microsoft stuff */ 
        { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        // Instance Buffer Data
        { "INSTANCE_STUFF", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, 0, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },  
    };
    ...

Then later in CreateDeviceDependentResources, I try to upload my InstanceBuffer data between the areas where the sample uploads the VertexBuffer and IndexBuffer:

    ...
    // Instance positions
    VertexPosition instancePositions[] = 
    {
        XMFLOAT3(3.0f, 0.0f, 0.0f), 
        XMFLOAT3(3.0f, 0.0f, 0.0f),
        XMFLOAT3(-3.0f, 0.0f, 0.0f)
    };

    const UINT instanceBufferSize = sizeof(instancePositions);

    // Create the instance buffer resource in the GPU's default heap and copy instance data into it using the upload heap.
    // The upload resource must not be released until after the GPU has finished using it.
    Microsoft::WRL::ComPtr<ID3D12Resource> instanceBufferUpload;

    CD3DX12_RESOURCE_DESC instanceBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(instanceBufferSize);
    DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
        &defaultHeapProperties,
        D3D12_HEAP_FLAG_NONE,
        &instanceBufferDesc,
        D3D12_RESOURCE_STATE_COPY_DEST,
        nullptr,
        IID_PPV_ARGS(&m_instanceBuffer)));

    DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
        &uploadHeapProperties,
        D3D12_HEAP_FLAG_NONE,
        &instanceBufferDesc,
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(&instanceBufferUpload)));

    NAME_D3D12_OBJECT(m_instanceBuffer);

    // Upload the instance buffer to the GPU.
    {
        D3D12_SUBRESOURCE_DATA instanceData = {};
        instanceData.pData = reinterpret_cast<BYTE*>(instancePositions);
        instanceData.RowPitch = instanceBufferSize;
        instanceData.SlicePitch = instanceData.RowPitch;

        UpdateSubresources(m_commandList.Get(), m_instanceBuffer.Get(), instanceBufferUpload.Get(), 0, 0, 1, &instanceData);

        CD3DX12_RESOURCE_BARRIER instanceBufferResourceBarrier =
            CD3DX12_RESOURCE_BARRIER::Transition(m_instanceBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
        m_commandList->ResourceBarrier(1, &instanceBufferResourceBarrier);
    }
    ...

While uploading my InstanceBuffer above, I'm trying to mimic the same pattern that Microsoft used when they uploaded the VertexBuffer. The main difference I can see is that for my InstanceBuffer, I'm using a VertexPostion that I defined in ShaderStructures.h instead of the VertexPostionColor that Microsoft used for the VertexBuffer. The VertexPosition is the same as the VertexPositionColor except that the "DirectX::XMFLOAT3 color;" definition was removed from the end.

My mangling of the SampleVertexShader.hlsl file then looks like this:

// A constant buffer that stores the three basic column-major matrices for composing geometry.
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
};

// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
    float3 pos : POSITION;
    float3 color : COLOR0;
    float3 instancePos : INSTANCE_STUFF;
};

// Per-pixel color data passed through the pixel shader.
struct PixelShaderInput
{
    float4 pos : SV_POSITION;
    float3 color : COLOR0;  
};

// Simple shader to do vertex processing on the GPU.
PixelShaderInput main(VertexShaderInput input)
{
    PixelShaderInput output;
    float4 pos = float4(input.pos, 1.0f);
    float4 instPos = float4(input.instancePos, 1.0f);

    pos.x += instPos.x;
    pos.y += instPos.y;
    pos.z += instPos.z;

    // Transform the vertex position into projected space.
    pos = mul(pos, model);
    pos = mul(pos, view);
    pos = mul(pos, projection);
    output.pos = pos;

    // Pass the color through without modification.
    output.color = input.color;

    return output;
}

Unfortunately, when the GPU gets to the main routine of the VertexShader, instPos.x, instPos.y and instPos.z all seem to be zero.

Will you all please kindly show me what I am doing wrong?

EDIT: I finally found the Graphics Debugger... and it its doesn't say anything about my m_instanceBuffer. I don't know what I'm doing but I would have expected m_instanceBuffer to show up as a Vertex Buffer in Slot 1 (See Picture):

Graphics Analyzer m_commandList Summary

\$\endgroup\$
  • \$\begingroup\$ Not sure but it looks like one of the bigger mistakes I seemed to be making was that I'm apparently supposed to call m_commandList->IASetVertextBuffers once for each slot: m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView); m_commandList->IASetVertexBuffers(1, 1, &m_instanceBufferView); I was only using the Microsoft call with m_vertexBufferView as an argument. I got the idea to call for each slot from: reddit.com/r/GraphicsProgramming/comments/79lloi/… \$\endgroup\$ – Shawn Eary Sep 4 '18 at 2:44
0
\$\begingroup\$

I eventually got this working. As I said, my biggest mistake was apparently the fact that I wasn't handling the m_vertexBufferView properly per Tubby94's Reddit post that I mentioned in the comments.

The primary missing code to handle that appears at the end of Sample3DSceneRenderer.cpp:CreateDeviceDependentResources. So my CreateDeviceDependentResources function should have looked more like this
(notice the m_instanceBufferView code near the end):

...
static const D3D12_INPUT_ELEMENT_DESC inputLayout[] =
{
    // Vertex Buffer Data
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    /* { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, Old Microsoft stuff */ 
    { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    // Instance Buffer Data
    { "INSTANCE_STUFF", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, 0, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 },  
};
...
// Instance positions
VertexPosition instancePositions[] = 
{
    XMFLOAT3(3.0f, 0.0f, 0.0f), 
    XMFLOAT3(3.0f, 0.0f, 0.0f),
    XMFLOAT3(-3.0f, 0.0f, 0.0f)
};

const UINT instanceBufferSize = sizeof(instancePositions);

// Create the instance buffer resource in the GPU's default heap and copy instance data into it using the upload heap.
// The upload resource must not be released until after the GPU has finished using it.
Microsoft::WRL::ComPtr<ID3D12Resource> instanceBufferUpload;

CD3DX12_RESOURCE_DESC instanceBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(instanceBufferSize);
DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
    &defaultHeapProperties,
    D3D12_HEAP_FLAG_NONE,
    &instanceBufferDesc,
    D3D12_RESOURCE_STATE_COPY_DEST,
    nullptr,
    IID_PPV_ARGS(&m_instanceBuffer)));

DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
    &uploadHeapProperties,
    D3D12_HEAP_FLAG_NONE,
    &instanceBufferDesc,
    D3D12_RESOURCE_STATE_GENERIC_READ,
    nullptr,
    IID_PPV_ARGS(&instanceBufferUpload)));

NAME_D3D12_OBJECT(m_instanceBuffer);

// Upload the instance buffer to the GPU.
{
    D3D12_SUBRESOURCE_DATA instanceData = {};
    instanceData.pData = reinterpret_cast<BYTE*>(instancePositions);
    instanceData.RowPitch = instanceBufferSize;
    instanceData.SlicePitch = instanceData.RowPitch;

    UpdateSubresources(m_commandList.Get(), m_instanceBuffer.Get(), instanceBufferUpload.Get(), 0, 0, 1, &instanceData);

    CD3DX12_RESOURCE_BARRIER instanceBufferResourceBarrier =
        CD3DX12_RESOURCE_BARRIER::Transition(m_instanceBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
    m_commandList->ResourceBarrier(1, &instanceBufferResourceBarrier);
}

...
// Create vertex/index buffer views.
m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
m_vertexBufferView.StrideInBytes = sizeof(VertexPositionColor);
m_vertexBufferView.SizeInBytes = sizeof(cubeVertices);

m_instanceBufferView.BufferLocation = m_instanceBuffer->GetGPUVirtualAddress();
m_instanceBufferView.StrideInBytes = sizeof(VertexPosition);
m_instanceBufferView.SizeInBytes = sizeof(someInstanceData);

m_indexBufferView.BufferLocation = m_indexBuffer->GetGPUVirtualAddress();
m_indexBufferView.SizeInBytes = sizeof(cubeIndices);
m_indexBufferView.Format = DXGI_FORMAT_R16_UINT;
...

Then, in Sample3DSceneRenderer.cpp:Render I should have called IASetVertexBuffers on m_instanceBufferView as Tubby94 said:

...
PIXBeginEvent(m_commandList.Get(), 0, L"Draw the cube");
{
    ...
    m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
    m_commandList->IASetVertexBuffers(1, 1, &m_instanceBufferView);
    m_commandList->IASetIndexBuffer(&m_indexBufferView);
    m_commandList->DrawIndexedInstanced(36, 3, 0, 0, 0);
    ...
}
... 

The change to the ShaderStructures.h file is fairly intuitive but would look something like this:

namespace NameSpaceOfTheApp
{
    // Used to send per-instance data to the vertex shader
    struct VertexPosition
    {
        DirectX::XMFLOAT3 pos;
    }; 

    // Used to send per-vertex data to the vertex shader.
    struct VertexPositionColor
    {
        DirectX::XMFLOAT3 pos;
        DirectX::XMFLOAT3 color;
    };

    // Constant buffer used to send MVP matrices to the vertex shader.
    struct ModelViewProjectionConstantBuffer
    {
        DirectX::XMFLOAT4X4 model;
        DirectX::XMFLOAT4X4 view;
        DirectX::XMFLOAT4X4 projection;
    };
}

I don't have the exact code for this specific question anymore, but I have a walkthrough for a very similar problem posted here:

https://www.utilars.com/Blog/shawn-eary/2018/9/19/dx12Instancing

So the basic answer to the question follows from the comment above about me failing to perform the mechanism as Tubby94 suggested. Basically, I wasn't setting m_vertexBufferView up correctly. One thing in particular I failed to so was call IASetVertexBuffers on it in the Render function of Sample3DSceneRenderer.cpp

\$\endgroup\$
  • \$\begingroup\$ The original problem is solved, but now I have a new problem that should probably be addressed on a different question. The code above pushes the instancing data once per run via the CreateDeviceDependentResources function. This works fine for a static demo, but now I can't change the instancing information as the application runs because it is only pushed into the pipeline at the start of the application... \$\endgroup\$ – Shawn Eary Jan 17 at 2:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.