Sign in to follow this  

Wrapping (D3D11) buffers

This topic is 367 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

In the design of my new engine, I try to make my Renderer system/ namespace independent of the API, in this case D3D11.

So far this all worked fine, but I have a question when it comes to mesh buffers (index and vertex).

 

After getting valuable feedback in an earlier thread I've decided to add a IBuffer wrapper.

So basically a mesh contains 2 IBuffer objects, and the IBuffers create and manage the D3D11 VertexBuffer and IndexBuffer.

 

So far so good, but now I have to create the buffers, for which I need the device.

I could pass the D3D11 device pointer to the MeshCreate member functions of the mesh class, but then it would still depend on the D3D11 renderer/ in this case the device.

Another solution would be to store all vertices and indices in the Mesh object with 2 const ref getters, then from the D3D11 Buffer manager, create the buffers using the Getters from the mesh, to get the vertices and indices. In this case I need to make the IBuffers of the Mesh public (which shouldn't perse be an issue, because the buffer inside is private).

 

?What would be a good approach on this?

 

Below some code snippets and the high over design I'm applying.

// The IBuffer

class IBuffer
{
public:
	IBuffer(ID3D11Device *pDevice, const uint pNrElements, const uint pElementByteSize, const bool pDynamic, const bool pGPUWrite, const std::vector<DirectX::XMFLOAT3> &pVertices);
	IBuffer(ID3D11Device *pDevice, const uint pNrElements, const uint pElementByteSize, const bool pDynamic, const bool pGPUWrite, const std::vector<uint> &pIndices);

	~IBuffer();

	ID3D11Buffer*	GetBufferPtr()	const;

private:
	void CreateBufferDesc();
	
	CComPtr<ID3D11Buffer>	mBuffer;
	D3D11_BUFFER_DESC		mBufferDesc;

	uint					mNrElements;
	uint					mElementByteSize;

	bool					mDynamic;
	bool					mGPUWrite;
};

// One of the IBuffer creations through the constructor

IBuffer::IBuffer(ID3D11Device *pDevice, const uint pNrElements, const uint pElementByteSize, const bool pDynamic, const bool pGPUWrite, const std::vector<DirectX::XMFLOAT3> &pVertices) :
	mNrElements(pNrElements), mElementByteSize(pElementByteSize), mDynamic(pDynamic), mGPUWrite(pGPUWrite)
{	
	CreateBufferDesc();
	mBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

	D3D11_SUBRESOURCE_DATA vinitData;
	vinitData.pSysMem = &pVertices[0];

	if(FAILED(pDevice->CreateBuffer(&mBufferDesc, &vinitData, &mBuffer)))
	{
		CLOG(ERROR, "RENDERER") << error_new_ibuffer.c_str();
		mBuffer = nullptr;
	}
	else CLOG(DEBUG, "D3DRENDERER") << debug_ibuffer_vtx_new.c_str() << "dynamic: " << mDynamic << ", GPUWrite: " << mGPUWrite << ", elements: " << mNrElements << ", element size: " << mElementByteSize;
}

void IBuffer::CreateBufferDesc()
{
	mBufferDesc.Usage = D3D11_USAGE_DEFAULT;

	if(!mDynamic && mGPUWrite)	mBufferDesc.Usage = D3D11_USAGE_DEFAULT;		// GPU read and write
	if(!mDynamic && !mGPUWrite)	mBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;		// GPU read only
	if(mDynamic)				mBufferDesc.Usage = D3D11_USAGE_DYNAMIC;		// Dynamic buffer

	mBufferDesc.ByteWidth = mElementByteSize * mNrElements;
	mBufferDesc.CPUAccessFlags = 0;
	if(mDynamic) mBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

	mBufferDesc.MiscFlags = 0;
}

// The Mesh class

class C3dMesh 
{
public:
	C3dMesh(const uint pMeshId);
	~C3dMesh();

// more stuff

	// MESH creation and loading
	void CreateBox(const float pWidth, const float pHeight, const float pDepth);

private:
	std::unique_ptr<IBuffer>		mVtxBuffer;
	std::unique_ptr<IBuffer>		mIndexBuffer;

// more stuff
}

// Mesh creation, box example

void C3dMesh::CreateBox(const float pWidth, const float pHeight, const float pDepth)
{
	CGeometryGenerator::MeshData box;

	CGeometryGenerator geoGen;
	geoGen.CreateBox(1.0f, 1.0f, 1.0f, box);

	mNrVertices	= box.Vertices.size();
	mNrFaces	= box.Indices.size();

	std::vector<Vertex::PosNormTanTex> vertices(mNrVertices);

	UINT k = 0;
	for(size_t i=0;i<box.Vertices.size();++i,++k) vertices[k] = box.Vertices[i];

	// Create vertexbuffer -> needs D3D11 device pointer???
	mVtxBuffer = std::make_unique<IBuffer>(Device, mNrVertices, sizeof(Crealysm::RENDERER::Vertex::PosNormTanTex), false, false, vertices);


}

crealysm11_design_v0.6.jpg

Edited by cozzie

Share this post


Link to post
Share on other sites

I could pass the D3D11 device pointer to the MeshCreate member functions of the mesh class, but then it would still depend on the D3D11 renderer/ in this case the device.

 
An abstract IBuffer wouldn't take a ID3D11Device as an argument, it would take an abstract IDevice.
D3DBuffer (implementation of the IBuffer interface) would retrieve an ID3D11Device from the abstract IDevice.
 
FWIW in my engine, to create a buffer, the abstract device class (called GpuDevice for me) has a CreateBuffer function (inside out to yours) -- but both are valid.

So basically a mesh contains 2 IBuffer objects, and the IBuffers create and manage the D3D11 VertexBuffer and IndexBuffer.

In many APIs you can store indices and vertices within the one buffer object (at different offsets). In all APIs you can store many meshes within a single buffer object (at different offsets). So in general, having a "one mesh owns two buffers" relationship is overly conservative. 

Another solution would be to store all vertices and indices in the Mesh object with 2 const ref getters, then from the D3D11 Buffer manager, create the buffers using the Getters from the mesh, to get the vertices and indices.

That has the downside that a mesh is required to actually store the vertex/index buffer in CPU-accessible RAM, which you don't need 99% of the time.

Share this post


Link to post
Share on other sites

I agree with Hodgman, if you are going to abstract your graphics library  than you should do it the right way. This means making sure that the user has no idea which underlying library you're using. Create a buffer wrapper class, either a CVertexBuffer/CIndexBuffer .. etc.. and create a graphics wrapper class, i.e CGraphicsDevice. Abstract the buffer flags with an enum, EBufFlags, and then you would create a vertex buffer like this : 

enum EBufFlags
{
  BUF_ShaderResource,
  BUF_Static,
  BUF_Dynamic
  ...
};

class IVertexBuffer : public IGFXResource
{
   ...
};

class IGraphicsDevice
{
public:

   virtual IVertexBuffer* CreateVertexBuffer(uint32 BufferSize, uint32 BufferFlags, const void* BufferData , ....) = 0;


};


With this method, you can separate your graphics abstractions, D3D/OGL/Vulkan/etc... into dlls and keep your global namespace clean of API specific functions.

Share this post


Link to post
Share on other sites

Thanks both. Completely abstracting both the device and buffer is a bridge to far (with my current knowledge), and doesn't feel right (even if it would be asking more help/ explicit code suggestions).

 

But... you triggered me to find a good comprise for a better solution which keeps in mind the following rules:

- the mesh (in the renderer system) should not have it's personal vertex and index buffer

- the mesh should not have a direct dependency on the API (in this case D3D11)

- the D3D11 low-level renderer should take care of the buffers

 

So my solution is:

- the low-level renderer get a BufferManager

- the BufferManager stores a vector of IBuffers

- creating and managent (+ retrieving) of the D3D11 buffers is also done through the BufferManager

- a mesh simply gets 4 new properties, which are not API dependent:

-- mVtxBufferHandle (or ID)

-- mIndexBufferHandle (or ID)

-- mVtxBufferOffset  (/mVtxBufferStart)

-- mIndexBufferOffset (/mIndexBufferStart)

 

This should work and keep things separated.

The only disadvantage I can think of, is that a mesh's data has to be stored for a short while, till the buffers are created. Afterwards it can be cleaned up.

 

Btw; this is basically the way I've thought of if when I created the design (see above), but somewhere along the way it went in a different direction.

Edited by cozzie

Share this post


Link to post
Share on other sites

Well you can add the low level buffer manager classes,.. and then when you need API specific textures, .. the low level texture manager classes, .. and then when you need to create a RenderTargetView for a specific mip level, a render target view manager,  and keep adding manager classes.. but at the end of the day, it will be a lot more complicated and time consuming than just creating a quick wrapper class, and returning a created resource from it, but that is your choice. Why does your mesh class have a vertex buffer and index buffer offset? . You usually have a class "CMeshChunk" which contains offsets into the vertex and index buffers of the owning "CMesh".  And from my point of view, it is okay for your mesh to have cpu side data, that helps with serializing and mesh data updating.

Share this post


Link to post
Share on other sites

Thanks, clear.

What you call 'MeshChunk' I called renderable so far (basically a submesh).

It keeps the class/ code organized when I add that instead of a set of 4 variables in more places. So good idea.

 

I actually do have a CBuffer and RenderTarget manager too, which so far works out pretty well.

There might be a risk that in the future (when things grow larger) I have to change the approach, but for now I don't see reasons too (for the goals I have).

Share this post


Link to post
Share on other sites

This topic is 367 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this