如何使顶点缓冲区可供计算着色器使用

问题描述 投票:0回答:1

我有一个使用 Direct3D 绘制 3D 模型的 C++ 程序。该程序按预期工作,即 3D 模型的图像被正确渲染。下一步是编写一个计算着色器,它将顶点和索引缓冲区作为输入。然后,当用户执行某个工作流程时,它会使用该数据进行一些计算。

要启用此功能,我需要使顶点和索引缓冲区可供计算着色器使用。这就是出错的地方,即一旦我更改代码以便计算着色器可以使用缓冲区,程序就不再渲染图像。

3D 模型由以下类型的顶点组成:

struct VertexWithNormal
{
  ::DirectX::XMFLOAT4 coordinates;
  ::DirectX::XMFLOAT4 normal;
  int                 whatever1;
  int                 whatever2;
  int                 whatever3;
  int                 whatever4;
};

然后通过以下代码将该布局提供给 GPU:

static const D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
{
  { "POSITION" , 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, offsetof(VertexWithNormal, coordinates), D3D11_INPUT_PER_VERTEX_DATA, 0 },
  { "NORMAL"   , 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, offsetof(VertexWithNormal, normal)     , D3D11_INPUT_PER_VERTEX_DATA, 0 },
  { "WHATEVER1", 0, DXGI_FORMAT_R32_SINT          , 0, offsetof(VertexWithNormal, whatever1)  , D3D11_INPUT_PER_VERTEX_DATA, 0 },
  { "WHATEVER2", 0, DXGI_FORMAT_R32_SINT          , 0, offsetof(VertexWithNormal, whatever2)  , D3D11_INPUT_PER_VERTEX_DATA, 0 },
  { "WHATEVER3", 0, DXGI_FORMAT_R32_SINT          , 0, offsetof(VertexWithNormal, whatever3)  , D3D11_INPUT_PER_VERTEX_DATA, 0 },
  { "WHATEVER4", 0, DXGI_FORMAT_R32_SINT          , 0, offsetof(VertexWithNormal, whatever4)  , D3D11_INPUT_PER_VERTEX_DATA, 0 },
};

winrt::check_hresult(_d3dDevice->CreateInputLayout(vertexDesc, ARRAYSIZE(vertexDesc), shaderCode, shaderSize, _inputLayout.put()));
_d3dContext->IASetInputLayout(_inputLayout.get());

然后通过以下代码创建顶点缓冲区:

UINT stride = sizeof(VertexWithNormal);
UINT offset = 0;

D3D11_SUBRESOURCE_DATA vertexBufferData{};
vertexBufferData.pSysMem = _model->GetVertexPointer();

D3D11_BUFFER_DESC vertexBufferDesc{};
vertexBufferDesc.Usage               = D3D11_USAGE_DEFAULT;
vertexBufferDesc.BindFlags           = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.ByteWidth           = 1234;
vertexBufferDesc.StructureByteStride = stride;

winrt::check_hresult(_d3dDevice->CreateBuffer(&vertexBufferDesc, &vertexBufferData, _vertexBuffer.put()));

ID3D11Buffer* vertexBuffer = _vertexBuffer.get();

_d3dContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);

最后,以下代码创建由 unsigned int 组成的索引缓冲区:

D3D11_SUBRESOURCE_DATA indexBufferData{};
indexBufferData.pSysMem = _model->GetIndexPointer();

D3D11_BUFFER_DESC indexBufferDesc{};
indexBufferDesc.Usage     = D3D11_USAGE_DEFAULT;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.ByteWidth = 4321;

winrt::check_hresult(_d3dDevice->CreateBuffer(&indexBufferDesc, &indexBufferData, _indexBuffer.put()));

_d3dContext->IASetIndexBuffer(_indexBuffer.get(), DXGI_FORMAT_R32_UINT, 0);

现在,计算着色器的 HLSL 代码应该如下所示:

struct Vertex
{
  float4 Coordinates;
  float4 Normal;
  int    Whatever1;
  int    Whatever2;
  int    Whatever3;
  int    Whatever4;
};

StructuredBuffer<Vertex> VertexBuffer : register(t0);
StructuredBuffer<uint>   IndexBuffer  : register(t1);;

[numthreads(128, 1, 1)]
void main(uint3 dispatchThreadId : SV_DispatchThreadID)
{
  uint vertexIndex = dispatchThreadId.x * 3;

  // Retrieve the actual values from the vertex and index buffers
  Vertex vertex0 = VertexBuffer[IndexBuffer[vertexIndex]];
  Vertex vertex1 = VertexBuffer[IndexBuffer[vertexIndex + 1u]];
  Vertex vertex2 = VertexBuffer[IndexBuffer[vertexIndex + 2u]];

  // Calculations with the vertices...
}

我的理解是,顶点和索引缓冲区需要映射到 HLSL 代码中的

StructuredBuffer
。显然,从计算着色器的角度来看,这是一个只读缓冲区,这正是我所需要的。我还尝试将这些缓冲区映射为常量缓冲区 (
cbuffer
),但尝试失败。

现在,我的理解是,我需要为顶点和索引缓冲区创建一个

ID3D11ShaderResourceView
,以便我可以通过调用 Direct3D 上下文的
CSSetShaderResources
使它们可供着色器使用。我尝试通过更改创建顶点缓冲区的代码来实现此目的,如下所示:

UINT stride = sizeof(VertexWithNormal);
UINT offset = 0;

D3D11_SUBRESOURCE_DATA vertexBufferData{};
vertexBufferData.pSysMem = _model->GetVertexPointer();

D3D11_BUFFER_DESC vertexBufferDesc{};
vertexBufferDesc.Usage               = D3D11_USAGE_DEFAULT;
// Compared to the working code, I'm ORing D3D11_BIND_SHADER_RESOURCE to D3D11_BIND_VERTEX_BUFFER
vertexBufferDesc.BindFlags           = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_SHADER_RESOURCE;
// Seems to be necessary, but adding the below line results in empty images, nothing is rendered anymore
vertexBufferDesc.MiscFlags           = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
vertexBufferDesc.ByteWidth           = 1234;
vertexBufferDesc.StructureByteStride = stride;

winrt::check_hresult(_d3dDevice->CreateBuffer(&vertexBufferDesc, &vertexBufferData, _vertexBuffer.put()));

ID3D11Buffer* vertexBuffer = _vertexBuffer.get();

_d3dContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);

// Code that seems to be necessary to make the vertex buffer available to the compute shader
D3D11_SHADER_RESOURCE_VIEW_DESC computeShaderResourceDesc{};
computeShaderResourceDesc.Format               = DXGI_FORMAT_UNKNOWN;
computeShaderResourceDesc.ViewDimension        = D3D11_SRV_DIMENSION_BUFFEREX;
computeShaderResourceDesc.BufferEx.NumElements = <actual size...>;

winrt::check_hresult(_d3dDevice->CreateShaderResourceView(vertexBuffer, &computeShaderResourceDesc, _vertexBufferShaderResourceView.put()));

ID3D11ShaderResourceView* vertexBufferShaderResourceView = _vertexBufferShaderResourceView.get();

_d3dContext->CSSetShaderResources(0, 1, &vertexBufferShaderResourceView);

但是,修改如上所示的代码会破坏渲染,即不再渲染 3D 模型的图像。

  1. 是否可以使用相同的顶点和索引缓冲区来渲染图像并从计算着色器读取它们?
  2. 如果1的答案是肯定的,我做错了什么?

编辑:

启用 DirectX 调试层会导致我运行程序时发出以下错误:

D3D11 ERROR: ID3D11Device::CreateBuffer: Buffers created with D3D11_RESOURCE_MISC_BUFFER_STRUCTURED cannot specify any of the following listed bind flags.  The following BindFlags bits (0x9) are set: D3D11_BIND_VERTEX_BUFFER (1), D3D11_BIND_INDEX_BUFFER (0), D3D11_BIND_CONSTANT_BUFFER (0), D3D11_BIND_STREAM_OUTPUT (0), D3D11_BIND_RENDER_TARGET (0), or D3D11_BIND_DEPTH_STENCIL (0). [ STATE_CREATION ERROR #68: CREATEBUFFER_INVALIDMISCFLAGS]
D3D11 ERROR: ID3D11Device::CreateBuffer: CreateBuffer returning E_INVALIDARG, meaning invalid parameters were passed. [ STATE_CREATION ERROR #69: CREATEBUFFER_INVALIDARG_RETURN]

删除

vertexBufferDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
然后会发出以下错误:

D3D11 ERROR: ID3D11Device::CreateShaderResourceView: The Format (0, UNKNOWN) cannot be used, when creating a View of a Buffer. [ STATE_CREATION ERROR #127: CREATESHADERRESOURCEVIEW_INVALIDFORMAT]
D3D11 ERROR: ID3D11Device::CreateShaderResourceView: Returning E_INVALIDARG, meaning invalid parameters were passed. [ STATE_CREATION ERROR #131: CREATESHADERRESOURCEVIEW_INVALIDARG_RETURN]

现在的问题是,需要将什么作为格式放在那里。目前还不知道。

directx directx-11 compute-shader vertex-buffer
1个回答
0
投票

我发现的唯一方法以及注释中暗示的从 DirectX 11 计算着色器访问顶点和索引缓冲区的方法是将它们绑定为

ByteAddressBuffer
。这不是很方便,因为顾名思义,所有读取操作都是通过表示为字节偏移量的地址完成的,并且从缓冲区中获取值很快就会变得很麻烦。

因为从着色器的角度来看,

ByteAddressBuffer
是只读缓冲区,因此需要创建着色器资源视图(SRV)才能将缓冲区绑定到着色器。为了实现这一目标,我必须修改我在问题中发布的代码,如下所述。

首先,必须使用附加标志(杂项标志

D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS
和绑定标志
D3D11_BIND_SHADER_RESOURCE
)创建顶点缓冲区。之后,必须创建它的 SRV,然后才能将其绑定到着色器:

UINT stride = sizeof(VertexWithNormal);
UINT offset = 0;

D3D11_SUBRESOURCE_DATA vertexBufferData{};
vertexBufferData.pSysMem = _model->GetVertexPointer();

D3D11_BUFFER_DESC vertexBufferDesc{};
vertexBufferDesc.Usage               = D3D11_USAGE_DEFAULT;
vertexBufferDesc.BindFlags           = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_SHADER_RESOURCE;
vertexBufferDesc.MiscFlags           = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;
vertexBufferDesc.ByteWidth           = 1234;
vertexBufferDesc.StructureByteStride = stride;

winrt::check_hresult(_d3dDevice->CreateBuffer(&vertexBufferDesc, &vertexBufferData, _vertexBuffer.put()));

ID3D11Buffer* vertexBuffer = _vertexBuffer.get();

_d3dContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);

D3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc{};
shaderResourceViewDesc.Format               = DXGI_FORMAT_R32_TYPELESS;
shaderResourceViewDesc.ViewDimension        = D3D11_SRV_DIMENSION_BUFFEREX;
shaderResourceViewDesc.BufferEx.Flags       = D3D11_BUFFEREX_SRV_FLAG_RAW;
shaderResourceViewDesc.BufferEx.NumElements = 1234 / 4; // The size of the buffer here seems to be specified in 4-byte elements, thus divide by 4

winrt::check_hresult(_d3dDevice->CreateShaderResourceView(vertexBuffer, &shaderResourceViewDesc, _vertexBufferShaderResourceView.put()));

ID3D11ShaderResourceView* vertexBufferShaderResourceView = _vertexBufferShaderResourceView.get();

_d3dContext->CSSetShaderResources(0, 1, &vertexBufferShaderResourceView);

索引缓冲区创建代码必须以相同的方式修改:

D3D11_SUBRESOURCE_DATA indexBufferData{};
indexBufferData.pSysMem = _model->GetIndexPointer();

D3D11_BUFFER_DESC indexBufferDesc{};
indexBufferDesc.Usage     = D3D11_USAGE_DEFAULT;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER | D3D11_BIND_SHADER_RESOURCE;
indexBufferDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;
indexBufferDesc.ByteWidth = 4321;

winrt::check_hresult(_d3dDevice->CreateBuffer(&indexBufferDesc, &indexBufferData, _indexBuffer.put()));

_d3dContext->IASetIndexBuffer(_indexBuffer.get(), DXGI_FORMAT_R32_UINT, 0);

D3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc{};
shaderResourceViewDesc.Format               = DXGI_FORMAT_R32_TYPELESS;
shaderResourceViewDesc.ViewDimension        = D3D11_SRV_DIMENSION_BUFFEREX;
shaderResourceViewDesc.BufferEx.Flags       = D3D11_BUFFEREX_SRV_FLAG_RAW;
shaderResourceViewDesc.BufferEx.NumElements = 4321 / 4; // The size of the buffer here seems to be specified in 4-byte elements, thus divide by 4

winrt::check_hresult(_d3dDevice->CreateShaderResourceView(_indexBuffer.get(), &shaderResourceViewDesc, _indexBufferShaderResourceView.put()));

ID3D11ShaderResourceView* indexBufferShaderResourceView = _indexBufferShaderResourceView.get();

_d3dContext->CSSetShaderResources(1, 1, &indexBufferShaderResourceView);

由于着色器现在需要访问具有字节偏移量的元素,因此 HLSL 代码中的

Vertex
结构不再可用,因此被删除。修改后的着色器代码如下所示:

ByteAddressBuffer VertexBuffer : register(t0);
ByteAddressBuffer IndexBuffer  : register(t1);

[numthreads(128, 1, 1)]
void main(uint3 dispatchThreadId : SV_DispatchThreadID)
{
  uint triangleIndex = dispatchThreadId.x * 3;

  // The below size is calculated based on the "VertexWithNormal" struct
  // defined in the C++ code:
  // 2 * sizeof(XMFLOAT4) + 4 * sizeof(int) = 2 * 4 * 4 + 4 * 4 = 48 bytes
  const uint vertexSizeInBytes = 48;

  // A triangle consists of 3 vertices. Thus, 3 vertex indices of the index
  // buffer form one triangle:
  // 3 * sizeof(int) = 3 * 4 = 12 bytes
  const uint indexOffset = triangleIndex * 12;

  const uint vertexOffset0 = IndexBuffer.Load(indexOffset)     * vertexSizeInBytes;
  const uint vertexOffset1 = IndexBuffer.Load(indexOffset + 4) * vertexSizeInBytes;
  const uint vertexOffset2 = IndexBuffer.Load(indexOffset + 8) * vertexSizeInBytes;

  const float4 vertexCoordinates0 = asfloat(VertexBuffer.Load4(vertexOffset0));
  const float4 vertexCoordinates1 = asfloat(VertexBuffer.Load4(vertexOffset1));
  const float4 vertexCoordinates2 = asfloat(VertexBuffer.Load4(vertexOffset2));

  // Calculations with the vertices...
}

以这种方式从顶点和索引缓冲区中提取信息很容易出错,但不幸的是我没有找到更好的方法。显然,DirectX 着色器编译器确实提供了更简化的获取值的方法,如here所述,但这似乎是比我可用的编译器版本更新的一部分。

© www.soinside.com 2019 - 2024. All rights reserved.