c++vertex-bufferdirectx-12

Adding vertices on mouse click in DirectX 12


I'm trying to implement a functionality where a vertex is added whenever the user clicks on the viewport.

So far I've managed to draw vertices with the mouse using D3D_PRIMITIVE_TOPOLOGY_LINESTRIP, but the working implementation simply creates a new vertex buffer every click^^

That's how I came to my new implementation after hours of research and trial & error because I totally don't get how a D3D12_HEAP_TYPE_UPLOAD works combined with a D3D12_HEAP_TYPE_DEFAULT

I've noticed a strange behavior: Whenever I reach 10 clicks (the defined number of DYNAMIC_VERTEX_BUFFER_MAX_VERTICES), the linestrip is drawn. Before I don't reach the vertex buffer size, nothing is drawn.

In addition I ran PIX debugger to see the contents of my vertex buffer. The vertices are initially stored in the buffer with (0, 0, 0, 0) and when I reach the limit (10 clicks), they magically appear with their correct values.

Here are 2 screenshots in comparison

[Vertices NOT empty after 10 clicks][1] [1]: https://i.sstatic.net/oah1Y.png

[Vertices empty( < 10 clicks][2] [2]: https://i.sstatic.net/PWJgz.png

Here is the implementation I came along with -> the call order is CreateDefaultBuffer once and then every time a vertex is added I call UpdateUploadHeap and then BindVertexBufferView

void CreateDefaultBuffer(ID3D12Device& device, ComPtr<ID3D12GraphicsCommandList> commandList, const void* rawData, const UINT bufferSize)
    {
        // create default vertex buffer
        const CD3DX12_HEAP_PROPERTIES heapProp(D3D12_HEAP_TYPE_DEFAULT);
        const auto buf = CD3DX12_RESOURCE_DESC::Buffer(BufferSize * DYNAMIC_VERTEX_BUFFER_MAX_VERTICES); //DYNAMIC_VERTEX_BUFFER_MAX_VERTICES is 10
        auto hr = device.CreateCommittedResource(
            &heapProp,
            D3D12_HEAP_FLAG_NONE,
            &buf,
            D3D12_RESOURCE_STATE_COPY_DEST,
            nullptr,
            IID_PPV_ARGS(&D3DBuffer));
        VALIDATE((hr == ERROR_SUCCESS), "CreateCommittedResource failed");

        D3DBuffer->SetName(L"Dynamic Vertex Buffer"); // set name for debugger

        const auto transition = 
            CD3DX12_RESOURCE_BARRIER::Transition(D3DBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST,
                                                 D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
        commandList->ResourceBarrier(1, &transition);

        
    }

    void UpdateUploaeHeap(ID3D12Device& device, ComPtr<ID3D12GraphicsCommandList> commandList, const void* rawData, const UINT bufferSize)
    {
        // create upload buffer to copy vertices into default buffer
        const CD3DX12_HEAP_PROPERTIES heapPropUpload(D3D12_HEAP_TYPE_UPLOAD);
        const auto bufUpload = CD3DX12_RESOURCE_DESC::Buffer(bufferSize);
        const auto hr = device.CreateCommittedResource(
            &heapPropUpload,
            D3D12_HEAP_FLAG_NONE,
            &bufUpload,
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(D3DBufferUpload.ReleaseAndGetAddressOf()));
        VALIDATE((hr == ERROR_SUCCESS), "CreateCommittedResource failed");

        D3DBufferUpload->SetName(L"Upload Buffer for copying into Dynamic Vertex Buffer"); // set name for debugger

        D3D12_SUBRESOURCE_DATA vertexData = {};
        vertexData.pData = rawData;
        vertexData.RowPitch = bufferSize;
        vertexData.SlicePitch = vertexData.RowPitch;

        UpdateSubresources(commandList.Get(), D3DBuffer.Get(), D3DBufferUpload.Get(), 0, 0, 1, &vertexData);
    }

    D3D12_VERTEX_BUFFER_VIEW vertexBufferView;

    void BindVertexBufferView()
    {
        vertexBufferView.BufferLocation = D3DBuffer->GetGPUVirtualAddress();
        vertexBufferView.StrideInBytes = sizeof(meshData.vertices[0]);
        vertexBufferView.SizeInBytes = static_cast<UINT>(meshData.vertices.size()) * vertexBufferView.StrideInBytes;
    }```






Solution

  • Resources allocated from the D3D12_HEAP_TYPE_DEFAULT heap are placed in memory where the GPU can access them. There's no guarantee that the CPU can access it at all. In some architectures, there's no difference and all memory can be accessed by both at all times (Unified Memory Architecture such as the Xbox). In other architectures, default memory is dedicated video RAM that is not accessible from the CPU at all.

    In order to place data into D3D12_HEAP_TYPE_DEFAULT from CPU system memory, you need to copy the data into a resource allocated from the D3D12_HEAP_TYPE_UPLOAD heap. This memory is explicitly designated to be visible to both the CPU and GPU. This can be the "PCIe aperture" or some other mechanism.

    You can render directly from D3D12_HEAP_TYPE_UPLOAD memory for VBs/IBs and textures. This is equivalent to the DirectX 11 concept of D3D11_USAGE_DYNAMIC. That said, there's usually a performance penalty to rendering directly from this type of memory.

    You also use D3D12_HEAP_TYPE_UPLOAD heap memory directly for Constant Buffers (CB) as these are modified a lot during render.

    A key thing to remember is that DirectX 12 does not do "buffer renaming" for you. If you change a resource while it's being used by the GPU, it's going to cause problems with the render. The application is responsible for ensuring the resource is no longer being used for drawing before you change it.

    For example, after you have called the UpdateSubresources helper nothing is actually on the video card yet. You have to Close the associated command-list and Execute it. Then sometime later the copy is actually performed by the GPU (or DMA engine). The resource barriers take care of the dependencies between resources in the same command-list, but you still have to make sure you don't change the resource data from the CPU until it's actually done.

    The practical up-shot of this fact is that if you are setting a resource in the UPLOAD heap, then using it in a command-list, you must leave it alone and alive until the task you scheduled is actually completed on the GPU. If you want to render with a different version of the same resource, you need to create a new instance of it for the next draw or next frame. You have to wait 2 or 3 frames worth of rendering to know that the original resource you used is actually done.

    There are a number of ways to handle the recycling the memory and tracking its usage lifetime, but it looks like you have just a single resource which isn't going to work unless you stall out the GPU every frame. For a 'dynamic' VB/IB, you going to need 2 or 3 copies of it so you can swap between them each frame.

    See DirectX Tool Kit for DirectX 12 and in particular the ResourceUploadBatch and GraphicsMemory classes.