4

I'm trying to make a simple 3D modeling tool.

there is some work to move a vertex( or vertices ) for transform the model.

I used dynamic vertex buffer because thought it needs much update.

but performance is too low in high polygon model even though I change just one vertex.

is there other methods? or did I wrong way?

here is my D3D11_BUFFER_DESC

Usage = D3D11_USAGE_DYNAMIC;
CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
BindFlags = D3D11_BIND_VERTEX_BUFFER;
ByteWidth = sizeof(ST_Vertex) * _nVertexCount
D3D11_SUBRESOURCE_DATA d3dBufferData;
d3dBufferData.pSysMem = pVerticesInfo;
hr = pd3dDevice->CreateBuffer(&descBuffer, &d3dBufferData, &_pVertexBuffer);

and my update funtion

D3D11_MAPPED_SUBRESOURCE d3dMappedResource;
pImmediateContext->Map(_pVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &d3dMappedResource);

ST_Vertex* pBuffer = (ST_Vertex*)d3dMappedResource.pData;

for (int i = 0; i < vIndice.size(); ++i)
{
    pBuffer[vIndice[i]].xfPosition.x = pVerticesInfo[vIndice[i]].xfPosition.x;
    pBuffer[vIndice[i]].xfPosition.y = pVerticesInfo[vIndice[i]].xfPosition.y;
    pBuffer[vIndice[i]].xfPosition.z = pVerticesInfo[vIndice[i]].xfPosition.z;
}
pImmediateContext->Unmap(_pVertexBuffer, 0);

2 Answers2

3

As mentioned in the previous answer, you are updating your whole buffer every time, which will be slow depending on model size.

The solution is indeed to implement partial updates, there are two possibilities for it, you want to update a single vertex, or you want to update arbitrary indices (for example, you want to move N vertices in one go, in different locations, like vertex 1,20,23 for example.

The first solution is rather simple, first create your buffer with the following description :

Usage = D3D11_USAGE_DEFAULT;
CPUAccessFlags = 0;
BindFlags = D3D11_BIND_VERTEX_BUFFER;
ByteWidth = sizeof(ST_Vertex) * _nVertexCount
D3D11_SUBRESOURCE_DATA d3dBufferData;
d3dBufferData.pSysMem = pVerticesInfo;
hr = pd3dDevice->CreateBuffer(&descBuffer, &d3dBufferData, &_pVertexBuffer);

This makes sure your vertex buffer is gpu visible only.

Next create a second dynamic buffer which has the size of a single vertex (you do not need any bind flags in that case, as it will be used only for copies)

_pCopyVertexBuffer

Usage = D3D11_USAGE_DYNAMIC; //Staging works as well
CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
BindFlags = 0;
ByteWidth = sizeof(ST_Vertex);
D3D11_SUBRESOURCE_DATA d3dBufferData;
d3dBufferData.pSysMem = NULL;
hr = pd3dDevice->CreateBuffer(&descBuffer, &d3dBufferData, &_pCopyVertexBuffer);

when you move a vertex, copy the changed vertex in the copy buffer :

ST_Vertex changedVertex;

D3D11_MAPPED_SUBRESOURCE d3dMappedResource;
pImmediateContext->Map(_pVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &d3dMappedResource);

ST_Vertex* pBuffer = (ST_Vertex*)d3dMappedResource.pData;

pBuffer->xfPosition.x = changedVertex.xfPosition.x;
pBuffer->.xfPosition.y = changedVertex.xfPosition.y;
pBuffer->.xfPosition.z = changedVertex.xfPosition.z;

pImmediateContext->Unmap(_pVertexBuffer, 0);

Since you use D3D11_MAP_WRITE_DISCARD, make sure to write all attributes there (not only position).

Now once you done, you can use ID3D11DeviceContext::CopySubresourceRegion to only copy the modified vertex in the current location :

I assume that vertexID is the index of the modified vertex :

pd3DeviceContext->CopySubresourceRegion(_pVertexBuffer, 
0, //must be 0
vertexID * sizeof(ST_Vertex), //location of the vertex in you gpu vertex buffer
0, //must be 0
0, //must be 0
_pCopyVertexBuffer, 
0, //must be 0
NULL //in this case we copy the full content of _pCopyVertexBuffer, so we can set to null
);

Now if you want to update a list of vertices, things get more complicated and you have several options :

-First you apply this single vertex technique in a loop, this will work quite well if your changeset is small.

-If your changeset is very big (close to almost full vertex size, you can probably rewrite the whole buffer instead).

-An intermediate technique is to use compute shader to perform the updates (thats the one I normally use as its the most flexible version). Posting all c++ binding code would be way too long, but here is the concept :

  • your vertex buffer must have BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_UNORDERED_ACCESS; //this allows to write wioth compute
  • you need to create an ID3D11UnorderedAccessView for this buffer (so shader can write to it)
  • you need the following misc flags : D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS //this allows to write as RWByteAddressBuffer
  • you then create two dynamic structured buffers (I prefer those over byteaddress, but vertex buffer and structured is not allowed in dx11, so for the write one you need raw instead)
  • first structured buffer has a stride of ST_Vertex (this is your changeset)
  • second structured buffer has a stride of 4 (uint, these are the indices)
  • both structured buffers get an arbitrary element count (normally i use 1024 or 2048), so that will be the maximum amount of vertices you can update in a single pass.
  • both structured buffers you need an ID3D11ShaderResourceView (shader visible, read only)

Then update process is the following :

  • write modified vertices and locations in structured buffers (using map discard, if you have to copy less its ok)
  • attach both structured buffers for read
  • attach ID3D11UnorderedAccessView for write
  • set your compute shader
  • call dispatch
  • detach ID3D11UnorderedAccessView for write (this is VERY important)

This is a sample compute shader code (I assume you vertex is position only, for simplicity)

cbuffer cbUpdateCount : register(b0)
{
    uint updateCount;
};

RWByteAddressBuffer RWVertexPositionBuffer : register(u0);

StructuredBuffer<float3> ModifiedVertexBuffer : register(t0);
StructuredBuffer<uint> ModifiedVertexIndicesBuffer : register(t0);

//this is the stride of your vertex buffer, since here we use float3 it is 12 bytes
#define WRITE_STRIDE 12 

[numthreads(64, 1, 1)]
void CS( uint3 tid : SV_DispatchThreadID )
{
    //make sure you do not go part element count, as here we runs 64 threads at a time 
    if (tid.x >= updateCount) { return; }

    uint readIndex = tid.x;
    uint writeIndex = ModifiedVertexIndicesBuffer[readIndex];

    float3 vertex = ModifiedVertexBuffer[readIndex];
    //byte address buffers do not understand float, asuint is a binary cast.
    RWVertexPositionBuffer.Store3(writeIndex * WRITE_STRIDE, asuint(vertex));
}
mrvux
  • 8,523
  • 1
  • 27
  • 61
  • What is the difference between Alex's method and your first method? I don't understand why make buffer for one vertex. – I hate cucumber Sep 20 '20 at 13:21
  • In my case we avoid a D3D11_MAP_WRITE call, which can cause a stall on the gpu side. So we only perform a write discard on a single element (which will never stall, as if resource is in use it performs a renaming operation instead), then make the copy on the gpu side, which is also stall free. – mrvux Sep 29 '20 at 12:37
0

For the purposes of this question I'm going to assume you already have a mechanism for selecting a vertex from a list of vertices based upon ray casting or some other picking method and a mechanism for creating a displacement vector detailing how the vertex was moved in model space.

The method you have for updating the buffer is sufficient for anything less than a few hundred vertices, but on large scale models it becomes extremely slow. This is because you're updating everything, rather than the individual vertices you modified.

To fix this, you should only update the vertices you have changed, and to do that you need to create a change set.

In concept, a change set is nothing more than a set of changes made to the data - a list of the vertices that need to be updated. Since we already know which vertices were modified (otherwise we couldn't have manipulated them), we can map in the GPU buffer, go to that vertex specifically, and copy just those vertices into the GPU buffer.

In your vertex modification method, record the index of the vertex that was modified by the user:

//Modify the vertex coordinates based on mouse displacement
pVerticesInfo[SelectedVertexIndex].xfPosition.x += DisplacementVector.x;
pVerticesInfo[SelectedVertexIndex].xfPosition.y += DisplacementVector.y;
pVerticesInfo[SelectedVertexIndex].xfPosition.z += DisplacementVector.z;
//Add the changed vertex to the list of changes.
changedVertices.add(SelectedVertexIndex);
//And update the GPU buffer
UpdateD3DBuffer();

In UpdateD3DBuffer(), do the following:

D3D11_MAPPED_SUBRESOURCE d3dMappedResource;
pImmediateContext->Map(_pVertexBuffer, 0, D3D11_MAP_WRITE, 0, &d3dMappedResource);

ST_Vertex* pBuffer = (ST_Vertex*)d3dMappedResource.pData;

for (int i = 0; i < changedVertices.size(); ++i)
{
    pBuffer[changedVertices[i]].xfPosition.x = pVerticesInfo[changedVertices[i]].xfPosition.x;
    pBuffer[changedVertices[i]].xfPosition.y = pVerticesInfo[changedVertices[i]].xfPosition.y;
    pBuffer[changedVertices[i]].xfPosition.z = pVerticesInfo[changedVertices[i]].xfPosition.z;
}
pImmediateContext->Unmap(_pVertexBuffer, 0);
changedVertices.clear();

This has the effect of only updating the vertices that have changed, rather than all vertices in the model.

This also allows for some more complex manipulations. You can select multiple vertices and move them all as a group, select a whole face and move all the connected vertices, or move entire regions of the model relatively easily, assuming your picking method is capable of handling this.

In addition, if you record the change sets with enough information (the affected vertices and the displacement index), you can fairly easily implement an undo function by simply reversing the displacement vector and reapplying the selected change set.

Alex
  • 1,794
  • 9
  • 20
  • dont think that D3D11_MAP_WRITE_DISCARD will work in every architecture, you can end up with a rename and buffer data can be totally invalidated – mrvux Apr 17 '20 at 13:05
  • As per ms doc : D3D11_MAP_WRITE_DISCARD Resource is mapped for writing; the previous contents of the resource will be undefined. – mrvux Apr 17 '20 at 13:07
  • That's correct, but you can still use the same method - just use D3D11_MAP_WRITE instead. I've edited the code sample to reflect this. – Alex Apr 17 '20 at 13:45
  • indeed, but D3D11_MAP_WRITE will create stalls, which can produce their own problems. – mrvux Apr 17 '20 at 13:46
  • Yes you're only modifying the buffer with changedVertices, but, but Isn't the entire buffer uploaded to the GPU in this sample regardless? (I don't see any place where you're telling DirectX which region of data it should send to the GPU. – BjarkeCK Jul 14 '20 at 13:29
  • This is not a solution at all because: `Map cannot be called with MAP_WRITE access, because the Resource was created as D3D11_USAGE_DYNAMIC. D3D11_USAGE_DYNAMIC Resources must use either MAP_WRITE_DISCARD or MAP_WRITE_NO_OVERWRITE with Map`. – Măcelaru Tiberiu Apr 19 '21 at 17:28