Direct3D 12——计算着色器——将计算着色器的执行结果复制到系统内存

将计算着色器的执行结果复制到系统内存

一般来说,在用计算着色器对纹理进行处理之后,我们就会将结果在屏幕上显示出来,并根据呈现的效果来验证计算着色器的准确性。但是,如果使用结构化缓冲区参与运算, 或使用GPGPU进行通用计算,则运算结果可能根本就无法显示出来。所以当前的燃眉之急是如何将GPU端显存(您是否还记得,在通过UAV向结构化缓冲区写入数据时,缓冲区其实是位于 显存之中)里的运算结果回传至系统内存中。首先,应以堆属性D3D12_HEAP_TYPE_READBACK来创建系统内存,再通过ID3D12GraphicsCommandList: : CopyResource方法将GPU资源复制到系统内存资源之中。其次,系统内存资源必须与待复制的资源有着相同的类型与大小。最后,还需用映射 API函数对系统内存缓冲区进行映射,使CPU可以顺利地读取其中的数据。至此,我们就能将数据复制 到系统内存块中了,可令CPU端对其开展后续的处理,或存数据于文件,或执行所需的各种操作。
本章包含了一个名为“VecAdd”的结构化缓冲区演示程序,它的功能比较简单,就是将分别存于两个结构化缓冲区中向量的对应分量进行求和运算:

struct Data
{
    float3 v1;
    float2 v2;
};

StructuredBuffer<Data> glnputA : register(t0);
StructuredBuffer<Data> glnputB : register(t1);

RWStructuredBuffer<Data> gOutput : register(u0);

[numthreads(32, 1, 1)]
void CS(int3 dtid : SV_DispatchThreadID)
{
    gOutput[dtid.x].vl = glnputA[dtid.x].vl + glnputB[dtid.x].vl; 
    gOutput[dtid.x].v2 = glnputA[dtid.x].v2 + glnputB[dtid.x].v2;
}

为了方便起见,我们使每个结构化缓冲区中仅含有32个元素。因此,只需分派一个线程组即可(因为一个线程组即可同时处理32个数据元素)。待程序中的所有线程都完成计算着色器的运算任务之后, 我们将结果复制到系统内存,再保存于文件当中。下面的代码演示了如何创建系统内存缓冲区,以及怎样将GPU中的计算结果复制到CPU的内存:

//创建一个系统内存缓冲区,以便读回处理结果
ThrowlfFailed(md3dDevice->CreateCommittedResource(
                          &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_READBACK),
                           D3D12_HEAP_FLAG_NONE,
                          &CD3DX12_RESOURCE_DESC::Buffer(byteSize),
                           D3D12_RESOURCE_STATE_COPY_DEST,
                           nullptr,
                           IID_PPV_ARGS(&mReadBackBuffer)));

// ...
//
//计算着色器执行完毕
     struct Data
     {
        XMFLOAT3 vl;
        XMFLOAT2 v2;
     }//按计划将数据从默认缓冲区复制到回读缓冲区(即系统内存缓冲区)中
 mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(  
                                                             mOutputBuffer.Get(),
                                                             D3D12_RESOURCE_STATE_COMMON,
                                                             D3 Dl2_RES0URCE_STATE_C0PY_S0URCE));

mCommandList->CopyResource(mReadBackBuffer.Get(), mOutputBuffer.Get());

mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition( mOutputBuffer.Get(),D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON));

//命令记录完成
ThrowlfFailed (mCommanciList->Close ());

//将命令列表添加到命令队列中用于执行
ID3D12CommandList* cmdsLists[] = ( mCommandList.Get() }; mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

//等待命令执行完毕
FlushCommandQueue();

//对数据进行映射,以便CPU读取
Data* mappedData = nullptr;
ThrowlfFailed(mReadBackBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mappedData)));

std::ofstream fout("results.txt");

for(int i = 0; i < NumDataElements; ++i)
{
       fout << "("<< mappedData[i].vl.x <<","<<
               mappedData[i].vl.y<<","<<  
               mappedData[i].vl.z<<","<<
               mappedData[i].v2.x<<","<<
               mappedData[i].v2.y<<","<<std::endl;
}

mReadBackBuffer->Unmap(0, nullptr);

在这个演示程序中,我们用下列初始数据来填写两个输入缓冲区:

std::vector<Data> dataA(NumDataElements); 
std::vector<Data> dataB(NumDataElements);

for(int i = 0; i < NumDataElements; ++i)
{
    dataA[i].vl = XMFLOAT3(i, i, i);
    dataA[i].v2 = XMFLOAT2(i, 0);
    
    dataB[i].vl = XMFLOAT3(-i, i, 0.0f);
    dataB[i].v2 = XMFLOAT2(0,-i);
}

存有计算结果的文本文件应含有下列数据,据此我们便能确定计算着色器是否按预期完成任务:

(0,	0,	0,	0,	0)
(0,	2,	1,	1,	-1)
(0,	4,	2,	2,	-2)
(0,	6,	3,	3,	-3)
(0,	8,	4,	4,	-4)
(0,	10,	5,	5,	-5)
(0,	12,	6,	6,	-6)
(0,	14,	7,	1,	-7)
(0,	16,	8,	8,	-8)
(0,	18,	9,	9,	-9)
(0,	20,	10,	10,	-10)
(0,	22,	11,	11,	-11)
(0,	24,	12,	12,	-12)
(0,	26,	13,	13,	-13)
(0,	28,	14,	14,	-14)
(0,	30,	15,	15,	-15)
(0,	32,	16,	16,	-16)
(0,	34,	17,	17,	-17)
(0,	36,	18z	18,	-18)
(0,	38,	19,	19,	-19)
(0,	40,	20,	20,	-20)
(0,	42,	21,	21,	-21)
(0,	44,	22,	22,	-22)
(0,	46,	23,	23,	-23)
(0,	48,	24,	24,	-24)
(0,	50,	25,	25,	-25)
(0,	52,	26,	26,	-26)
(0,	54,	27,	27,	-27)
(0,	56,	28,	28,	-28)
(0,	58,	29,	29,	-29)
(0,	60,	30,	30,	-30)
(0,	62,	31,	31,	-31)

CPU与GPU之间的存储器复制操作最为缓慢。而对于图形处理这 一角度来说,我们更是永远都不想在每一帧都执行这种复制操作,因为这样频繁地搬运数据对程序的性能而言无疑是毁灭性的的打击。并不是什么大难题,因为GPU运算所节省的时间远超GPU向CPU复制所花费的时间 一再者说,针对GPGPU编程而言,并不是“每一帧”都要执行这种复制操作。举个例 子,假设某个应用程序要通过GPGPU编程来实现一个开销极大的图形处理计算。在运算 结束之后,再将其处理结果复制到CPU。在这种情况下,GPU并不会立即开始下一次处理,而是只有在用户发起另一次计算请求时,它才会为此重新开动起来。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值