DirectX11 With Windows SDK--03 Index Buffer, Constant Buffer

Preface

A cube has eight vertices, but drawing a cube requires drawing 12 triangles. If you draw the cube the same way you did before, you need to provide 36 vertices, and the vertex data will be repeated four or even five times.This drawing method takes up a lot of memory space.

Next, we will talk about another drawing method, which provides only eight vertices of the cube, then uses an index array to indicate which vertices are used and in what order.

Before reading this chapter, learn the following:

chapter
02 Vertex/Pixel Shader Creation, Vertex Buffer
Discussion on Memory Layout and mul Function of Matrix in HLSL

Of course, I also recommend that you start learning about and working with the graphics debugger early to help you find problems that you may not be aware of when debugging your CPU:

chapter
Visual Studio Graphics Debugger Detailed Use Tutorial

Learning objectives:

  1. Master the creation and use of index buffers
  2. Master the creation, update and use of constant buffers
  3. Draw the first cube by index

DirectX11 With Windows SDK Full Directory

Github Project Source

Welcome to QQ Group: 727623616 can discuss DX11 together and report any problems here.

Index Buffer

Using an index buffer instead of a specified order of drawing can effectively reduce the footprint of the vertex buffer and avoid providing a large amount of duplicate vertex data.

Before using the index buffer, start with initializing the vertex array as follows:

// ******************
// Set Cube Vertex
//    5________ 6
//    /|      /|
//   /_|_____/ |
//  1|4|_ _ 2|_|7
//   | /     | /
//   |/______|/
//  0       3
VertexPosColor vertices[] =
{
	{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f) },
	{ XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
	{ XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },
	{ XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
	{ XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
	{ XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
	{ XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
	{ XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) }
};

Then the vertex buffer is created and used as before:

// Set vertex buffer description
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof vertices;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
// New vertex buffer
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices;
HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffer.GetAddressOf()));

// Enter vertex buffer settings for assembly phase
UINT stride = sizeof(VertexPosColor);	// Number of bytes spanned
UINT offset = 0;						// Starting offset

m_pd3dImmediateContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), &stride, &offset);

The index array is now initialized as follows:

// Index Array
WORD indices[] = {
	// Positive
	0, 1, 2,
	2, 3, 0,
	// Left side
	4, 5, 1,
	1, 0, 4,
	// Top Surface
	1, 5, 6,
	6, 2, 1,
	// Back
	7, 6, 5,
	5, 4, 7,
	// Right side
	3, 2, 6,
	6, 7, 3,
	// Bottom
	4, 0, 3,
	3, 7, 4
};

Then fill in the buffer description information and create an index buffer:

// Set index buffer description
D3D11_BUFFER_DESC ibd;
ZeroMemory(&ibd, sizeof(ibd));
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof indices;
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
// New Index Buffer
InitData.pSysMem = indices;
HR(m_pd3dDevice->CreateBuffer(&ibd, &InitData, m_pIndexBuffer.GetAddressOf()));

ID3D11DeviceContext::IASetIndexBuffer Method--Render Pipeline Input Assembly Stage Set Index Buffer

void ID3D11DeviceContext::IASetIndexBuffer( 
    ID3D11Buffer *pIndexBuffer,     // [In] Index Buffer
    DXGI_FORMAT Format,             // [In] Data Format
    UINT Offset);                   // [In] Byte Offset

When assembling, you need to specify the number of bytes per index:

DXGI_FORMAT Number of bytes Index Range
DXGI_FORMAT_R8_UINT 1 0-255
DXGI_FORMAT_R16_UINT 2 0-65535
DXGI_FORMAT_R32_UINT 4 0-2147483647

So we can do this:

// Input assembly stage index buffer settings
m_pd3dImmediateContext->IASetIndexBuffer(m_pIndexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);

Note: The current update will use a uniform index type of 32-bit unsigned integer.

Constant Buffer

In HLSL, the variable of the constant buffer is similar to the global constant on the C++ side and is used by the shader code.Here is an example of a HLSL constant buffer:

cbuffer ConstantBuffer : register(b0)
{
    matrix g_World; 
    matrix g_View;  
    matrix g_Proj;  
}

cbuffer is used to declare a constant buffer

Matrix is equivalent to float4x4, and vector s are equivalent to float4. Where a matrix in D3D is the dominant matrix by default, matrix in HLSL is the column dominant matrix by default.

register(b0) refers to the constant buffer that is in a buffer with a register index of 0

In the C++ application layer, the corresponding structure of a constant buffer can be:

struct ConstantBuffer
{
	XMMATRIX world;
	XMMATRIX view;
	XMMATRIX proj;
};

Currently, there are two runtime updates to a constant buffer:

  1. Specify Usage as D3D11_when creating the resourceUSAGE_DEFAULT, which allows constant buffers to be written from the GPU, needs to be updated with the ID3D11DeviceContext::UpdateSubresource method.
  2. Specify Usage as D3D11_when creating the resourceUSAGE_DYNAMIC, CPUAccessFlags are D3D11_CPU_ACCESS_WRITE, which allows constant buffers to be written from the CPU, first obtains the memory mapping through the ID3D11DeviceContext::Map method, then updates to the mapped memory area, and finally removes the occupancy through the ID3D11DeviceContext::Unmap method.

Not only constant buffers, but also general buffers and texture resource updates can use both methods.The former is suitable for data that is updated infrequently (at intervals) or only once.The latter is more appropriate for resources that need to be updated frequently, such as every few frames, or every frame updates one or more times.

Since most of the constant buffers need to be updated frequently, subsequent updates will mainly use DYNAMIC.The process of creating a buffer that supports DYNAMIC updates is as follows:

D3D11_BUFFER_DESC cbd;
ZeroMemory(&cbd, sizeof(cbd));
cbd.Usage = D3D11_USAGE_DYNAMIC;
cbd.ByteWidth = sizeof(ConstantBuffer);
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
// New constant buffer without initial data
HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffer.GetAddressOf()));

Note: When creating a constant buffer, the description parameter ByteWidth must be a multiple of 16, because the HLSL constant buffer itself and its read and write operations need to be strictly aligned to 16 bytes

Now let's see what functions are needed for both methods of updating buffers.

DYNAMIC Update

ID3D11DeviceContext::Map[1] Function--Gets a pointer to the data in the buffer and denies GPU access to the buffer

HRESULT ID3D11DeviceContext::Map(
    ID3D11Resource           *pResource,          // [In] Resource object containing ID3D11Resource interface
    UINT                     Subresource,         // [In] Buffer Resource Fill 0
    D3D11_MAP                MapType,             // [In]D3D11_MAP enumeration values, specifying read-write related operations
    UINT                     MapFlags,            // [In] Fill in 0, CPU needs to wait for GPU to finish using current buffer
    D3D11_MAPPED_SUBRESOURCE *pMappedResource     // [Out] Obtained memory mapped to a buffer
);

D3D11_The members of the MAP enumeration value type are as follows:

D3D11_MAP Members Meaning
D3D11_MAP_READ Resources mapped to memory are used for reading.This resource must be bound when it is created
D3D11_CPU_ACCESS_READ Label
D3D11_MAP_WRITE Resources mapped to memory are used for writing.This resource must be bound when it is created
D3D11_CPU_ACCESS_WRITE Tags
D3D11_MAP_READ_WRITE Resources mapped to memory are used for reading and writing.This resource must be bound when it is created
D3D11_CPU_ACCESS_READ and D3D11_CPU_ACCESS_WRITE Tags
D3D11_MAP_WRITE_DISCARD Resources mapped to memory are used for writing, and previous resource data will be discarded.this
Resources must be created with D3D11_boundCPU_ACCESS_WRITE and
D3D11_USAGE_DYNAMIC Label

D3D11_MAP_WRITE_NO_OVERWRITE A resource mapped to memory is used for writing, but it cannot replicate an existing resource.
This enumeration value can only be used for vertex/index buffers.This resource needs to be created
With D3D11_CPU_ACCESS_WRITE tag, not available in Direct3D 11
Set D3D11_BIND_CONSTANT_BUFFER tagged resource, but in
Possible after 11.1.Consult MSDN documentation specifically



The last mapped memory we can use memcpy_s function to update.

By default, if the resource to be accessed is still in use by the GPU, the CPU will block until the resource can be accessed.

Note: Never read mapped memory regions that only support write operations!Otherwise, this will result in a very significant performance loss.Even C++ code like this: *((int*)MappedResource.pData) = 0 triggers a read operation which triggers a loss of performance.This is represented in the x86 assembly as AND DWORD PTR [EAX], 0.

ID3D11DeviceContext::Unmap Function--Invalidate pointer to resource and re-enable GPU access to the resource

void ID3D11DeviceContext::Unmap(
    ID3D11Resource *pResource,      // [In] Resource object containing ID3D11Resource interface
    UINT           Subresource      // [In] Buffer Resource Fill 0
);

Now you need to use the mBuffer structure variable to update the constant buffer, where the view and proj matrices need to be transposed one time in advance to cancel out the transposition of the main matrix of the HLSL column, as far as the world matrix is already a unit matrix, you don't need it:

m_CBuffer.world = XMMatrixIdentity();	// The transposition of the unit matrix is itself
m_CBuffer.view = XMMatrixTranspose(XMMatrixLookAtLH(
		XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f),
		XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),
		XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f)
	));
m_CBuffer.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f));


D3D11_MAPPED_SUBRESOURCE mappedData;
HR(m_pd3dImmediateContext->Map(m_pConstantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
memcpy_s(mappedData.pData, sizeof(m_CBuffer), &m_CBuffer, sizeof(m_CBuffer));
m_pd3dImmediateContext->Unmap(m_pConstantBuffer.Get(), 0);

DEFAULT Update

ID3D11DeviceContext::UpdateSubresource Method [1] -- Update Buffer Data

This method can be used to update resources that are allowed to be written by the GPU:

void ID3D11DeviceContext::UpdateSubresource( 
    ID3D11Resource *pDstResource,   // [In] Constant Buffers that need to be updated
    UINT DstSubresource,            // [In] Buffer Resource Fill 0
    const D3D11_BOX *pDstBox,       // [In] Ignore, fill in nullptr
    const void *pSrcData,           // [In] Data Source for Update
    UINT SrcRowPitch,               // [In] Ignore, fill in 0
    UINT SrcDepthPitch);            // [In] Ignore, fill in 0

This method can only be used with D3D11_USAGE_DEFAULT or D3D11_USAGE_The resource created by STAGE and cannot be used for depth template buffers and multisample buffers.

ID3D11DeviceContext:: The performance of UpdateSubresource depends on whether there is competition for the resources of the buffer to be updated.For example, the GPU takes up the buffer while it is drawing, and the CPU issues an UpdateSubresource operation on the same buffer.

  1. UpdateSubresource copies the source data twice in case of resource competition.The first time the CPU copies a resource in temporary memory space so that the GPU command buffer can access it, before the method is returned.The second time the GPU copies from memory to an unmapped area of memory.The second copy usually occurs asynchronously because it is executed after the GPU command buffer is flushed.
  2. If there is no competition for resources, the behavior of UpdateSubresource depends on how the CPU thinks it will be faster: execute as the first step, or copy directly from the CPU to the final location of the memory resource.The specific behavior depends on the system.

The method itself involves a copy operation of the CPU, which is more expensive to run and requires sufficient memory space for display.

Note: If the constant buffer used to update the shader cannot be partially updated, the data must be completely updated.

This method can be updated in just one sentence:

m_CBuffer.world = XMMatrixIdentity();	// The transposition of the unit matrix is itself
m_CBuffer.view = XMMatrixTranspose(XMMatrixLookAtLH(
		XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f),
		XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),
		XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f)
	));
m_CBuffer.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f));

m_pd3dImmediateContext->UpdateSubresource(m_pConstantBuffer.Get(), 0, nullptr, &m_CBuffer, 0, 0);

ID3D11DeviceContext::*SSetConstantBuffers Method--Set Constant Buffer at a Coloring Stage of Rendering Pipeline

The * here can be V, H, D, C, G, P six programmable rendering pipeline stages, and the function parameters are basically the same.

Now that the data has been updated, we need to set a constant buffer for the vertex shading stage.

void ID3D11DeviceContext::VSSetConstantBuffers( 
    UINT StartSlot,     // [In] The starting index to put into the buffer, for example, if b0 is specified above, then this should be 0
    UINT NumBuffers,    // Number of buffers set
    ID3D11Buffer *const *ppConstantBuffers);    // [In] Buffer Array for Settings

The operation of binding a constant buffer usually only needs to be called once:

m_pd3dImmediateContext->VSSetConstantBuffers(0, 1, m_pConstantBuffer.GetAddressOf());

HLSL Code

The HLSL L code used in this routine is as follows:

//Cube.hlsli

cbuffer ConstantBuffer : register(b0)
{
    matrix World; 
    matrix View;  
    matrix Proj;  
}


struct VertexIn
{
	float3 posL : POSITION;
	float4 color : COLOR;
};

struct VertexOut
{
	float4 posH : SV_POSITION;
	float4 color : COLOR;
};

// Cube_VS.hlsl
#include "Cube.hlsli"

VertexOut VS(VertexIn vIn)
{
    VertexOut vOut;
    vOut.posH = mul(float4(vIn.posL, 1.0f), gWorld);  // mul is the matrix multiplication, the operator * requires the object to be
    vOut.posH = mul(vOut.posH, gView);               // Two matrices with the same number of rows and columns
    vOut.posH = mul(vOut.posH, gProj);               // Cij = Aij * Bij
    vOut.color = vIn.color;                         // Here the alpha channel defaults to 1.0
    return vOut;
}
// Cube_PS.hlsl
#include "Cube.hlsli"

float4 PS(VertexOut pIn) : SV_Target
{
    return pIn.color;   
}

Note: In HLSL, matrix multiplication cannot use the * operator, which requires two matrices to have the same number of rows and columns. The result of the operation is also a matrix with the same number of rows and columns. The procedure is: Cij = Aij * Bij.The mul function should be used instead.

GameApp::UpdateScene Method--Update data frame by frame

Before drawing, we have to let the cube rotate, otherwise we can only see the front of the cube.Here we rotate the cube around both the X and Y axes, modifying the world matrix (not to worry too much about whether the rotation matrix is set properly, just to demonstrate the effect):

void GameApp::UpdateScene(float dt)
{
	
	static float phi = 0.0f, theta = 0.0f;
	phi += 0.0001f, theta += 0.00015f;
	m_CBuffer.world = XMMatrixTranspose(XMMatrixRotationX(phi) * XMMatrixRotationY(theta));
	// Update Constant Buffer to Rotate Cube
	D3D11_MAPPED_SUBRESOURCE mappedData;
	HR(m_pd3dImmediateContext->Map(m_pConstantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
	memcpy_s(mappedData.pData, sizeof(m_CBuffer), &m_CBuffer, sizeof(m_CBuffer));
	m_pd3dImmediateContext->Unmap(m_pConstantBuffer.Get(), 0);
}

GameApp::DrawScene method

ID3D11DeviceContext::DrawIndexed method--Draws based on vertices and index buffers

After specifying the vertex buffer, index buffer, and original topology type in the input assembly phase, bind the constant buffer to the vertex shading phase, and finally draw using the ID3D11DeviceContext::DrawIndexed method:

void ID3D11DeviceContext::DrawIndexed( 
    UINT IndexCount,            // Number of Indexes
    UINT StartIndexLocation,    // Starting Index Location
    INT BaseVertexLocation);    // Starting vertex position

For example, if called as follows:

m_pd3dImmediateContext->DrawIndexed(6, 6, 4);

Suppose that the vertex buffer has 12 vertices and the index buffer has 12 indexes with values of {11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}.
The above call means that the vertex buffer we use as our base index from index 4, then the index buffer uses the corresponding index values from index 6 to 11, i.e. {5, 4, 3, 2, 1, 0}, and then adds the base index value 4 to get the final vertex of the original vertex buffer index of {9, 8, 7, 6, 5, 4}.

Now let's draw 12 triangles to make a cube.GameApp::DrawScene method is implemented as follows:

void GameApp::DrawScene()
{
	assert(m_pd3dImmediateContext);
	assert(m_pSwapChain);

	static float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f };	// RGBA = (0,0,0,255)
	m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), reinterpret_cast<const float*>(&black));
	m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

	// Draw Cube
	m_pd3dImmediateContext->DrawIndexed(36, 0, 0);
	HR(m_pSwapChain->Present(0, 0));
}

The results are as follows:

Exercises

The bold part is a custom title

  1. Try drawing a quadripyramid with only five vertices

  2. Try placing the vertex data of a quadripyramid and a cube in the same vertex buffer, and the index data in the same index buffer. Then use these two buffers to draw the two objects (with the quadripyramid on the left and the cube on the right, you can modify the vertex data, or you can use the transformation matrix)
  3. Try to create a dynamic vertex buffer, then write vertex data to the vertex buffer via Map and Unmap.

For updates to resources, here are two links:

Efficient_Buffer_Management

how-to-use-updatesubresource-and-map-unmap

DirectX11 With Windows SDK Full Directory

Github Project Source

Welcome to QQ Group: 727623616 can discuss DX11 together and report any problems here.

Tags: Windows SDK github

Posted on Fri, 22 May 2020 23:54:14 -0700 by explore