简单计算着色器数据的输入和输出

在这篇文章中博主将会利用D3D中的几何着色器将向量数据送入GPU中,对向量数据进行处理计算出向量的长度,并将计算好的长度取回CPU中然后输入到文件中,现在的GPU拥有很强大的计算能力能够帮助CPU处理很多事情,以前N卡没有CUDA的时候粒子系统中粒子运动都是交给CPU去处理的,CPU本来就要对游戏整体负责,现在又要处理大量粒子的运动,大量粒子的运动会拖累游戏运行的效率,所以可以将一些计算交给GPGPU(通用GPU程序编程)。

struct Data
{
	XMFLOAT3 v1;
};

上面是传入GPU中向量的结构(可直接传入XMFLOAT3,加上结构是为了便于扩展)。

//NumDataElements为传入GPU中结构的数量
std::vector<Data> data(NumDataElements);

for(int i = 0; i < NumDataElements; ++i)
{
	//生成一些随机的值
	float r = MathHelper::RandF(0.577f, 5.773);
	data[i].v1 = XMFLOAT3(r, r, r);
}
	//创建一个默认缓存堆,data.data()返回的是一个指向数组的指针
	//byteSize为数组大小*结构体的大小,mInputUploadBuffer为存放上传堆指针的变量
	//在将向量传到GPU中的时候会先创建一个上传堆,再通过上传堆将数据传入到默认堆中
	//mInputUploadBuffer会保存在创建过程中的上传堆,防止上传堆中的数据还未传到默认堆中就已经销毁了(使用了ComPtr智能智能)
	mInputBuffer = d3dUtil::CreateDefaultBuffer(
		md3dDevice.Get(),
		mCommandList.Get(),
		data.data(),
		byteSize,
		mInputUploadBuffer);

	
	//创建一个无序访问的默认堆,outSize为sizeof(float)*向量的数量
	//mOutputBuffer保存默认堆资源,需要绑定到渲染管线上
	//D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS表示资源能被无序访问
	//D3D12_RESOURCE_STATE_UNORDERED_ACCESS表示默认堆能够无序访问
	ThrowIfFailed(md3dDevice->CreateCommittedResource(
		&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
		D3D12_HEAP_FLAG_NONE,
		&CD3DX12_RESOURCE_DESC::Buffer(outSize, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS),
		D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
		nullptr,
		IID_PPV_ARGS(&mOutputBuffer)));
	
	//创建一个读回堆,GPU堆上传堆只读,堆读回堆只写,所以要读取回来必须要D3D12_HEAP_TYPE_READBACK
	//outSize为sizeof(float)*向量的数量
	ThrowIfFailed(md3dDevice->CreateCommittedResource(
		&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_READBACK),
		D3D12_HEAP_FLAG_NONE,
		&CD3DX12_RESOURCE_DESC::Buffer(outSize),
		D3D12_RESOURCE_STATE_COPY_DEST,
		nullptr,
		IID_PPV_ARGS(&mReadBackBuffer)));

将上面的默认堆和无序访问堆绑定到渲染管线上,再通过计算着色器去处理向量的数据,下面是计算着色器的Shader代码:

struct Data
{
	float3 v1;
};

//传入GPU中保存为结构化缓存区
StructuredBuffer<Data> gInput : register(t0);
//RW表示可读写
RWStructuredBuffer<float> gOutput : register(u0);

//numthreads(64, 1, 1)表示线程组的维度大小,如果不知道可以去看我其他关于计算着色器的文章
[numthreads(64, 1, 1)]
void CS(int3 dtid : SV_DispatchThreadID)
{
    //计算向量长度并保存在无序访问的结构化缓存区中
    gOutput[dtid.x] = length(gInput[dtid.x].v1);
}

处理好的数据已经保存在无序访问的默认缓存堆中了,但是还是不能读取,上面已经说到了,只有读回的默认缓存区才可以在CPU中取出数据,所以这个时候就需要将无序访问堆中的数据拷贝到读回堆中。

	//将无序访问堆的属性设置为拷贝源状态
	mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mOutputBuffer.Get(),
		D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_SOURCE));

	//将数据拷贝读回堆中
	mCommandList->CopyResource(mReadBackBuffer.Get(), mOutputBuffer.Get());

	//将无序访问堆的属性设置回原来的状态
	mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mOutputBuffer.Get(),
		D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON));

拷贝完成后将要将数据映射成数组的状态了:

	//用以指向映射的数据
	float *mappedData = nullptr;
	//映射数据
	ThrowIfFailed(mReadBackBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mappedData)));

	std::ofstream fout("results.txt");
	//将处理好的数据输出到results.txt文件中
	for(int i = 0; i < NumDataElements; ++i)
	{
		fout << "(" << mappedData[i] << ")\t" << i << std::endl;
	}
	//输出完数据还需要停止映射,如果数据还未完成就不能断开映射
	mReadBackBuffer->Unmap(0, nullptr);

这就是GPU处理非图像数据的方法了,计算着色器在游戏开发中有着很大的用处,可以帮助处理粒子的运动信息,可以计算游戏物体爆炸运动的信息(计算好后需要交给几何着色器去处理),实际上GPU与CPU之间文件的拷贝对游戏效率也有这很大的影响,所以在处理少量数据的时候就不要使用计算着色器比如上面这个例子就有点得不偿失了,但是大量数据的时候就算着色器节省的时间大于GPU与CPU拷贝时间的时候,几何着色器就很好用了。
picture

未经允许不得转载:他日重逢 » 简单计算着色器数据的输入和输出

赞 (0) 打赏

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

隐藏
变装