D3D9获取GPU渲染的最终图像数据
将GPU中的数据读到CPU的方法总结。
1:先将仅GPU可读写的subresource拷贝到一份CPU也可以读写的subresource(核心调用ID3D11DeviceContext::CopyResource)。
2:然后通过Map函数,Get a pointer(D3D11_MAPPED_SUBRESOURCE) to the data contained in a subresource, and deny the GPU access to that subresource。
3:将上面得到的(D3D11_MAPPED_SUBRESOURCE)强制转换为CPU可以理解的struct或者class或者float等类型的指针。
4:对上面得到的CPU指针读写即可得到或者更改GPU中的数据。
5:最后Unmap()刚刚Map()的Resource。
/*示例:我们想将GPU中的计算结果ID3D11Buffer* m_pTestBuffer读出来。我们先通过调用函数
ID3D11Buffer *CreateAndCopyToDebugBuffer(ID3D11Buffer* sourceBuffer)得到一份它的CPU可以读写的拷贝buffer。*/
HRESULT hr=S_OK;
ID3D11Buffer* resultBuffer=NULL;
resultBuffer=CreateAndCopyToDebugBuffer(m_pTestBuffer);
/*然后通过Map函数,Get a pointer ( D3D11_MAPPED_SUBRESOURCE *) to the data
contained in a subresource, and deny the GPU access to that subresource。*/
D3D11_MAPPED_SUBRESOURCE resultResources;
ZeroMemory(&resultResources,sizeof(D3D11_MAPPED_SUBRESOURCE));
IFR(m_pContext->Map(resultBuffer,0,D3D11_MAP_READ,0,&resultResources));
/*将上面得到的D3D11_MAPPED_SUBRESOURCE::VOID*强制转换为CPU可以理解的struct或者class或者float等类型的指针。*/
TestBufType* p=NULL;
p=(TestBufType*)resultResources.pData;
/*对上面得到的CPU指针p读写即可得到或者更改GPU中ID3D11Buffer* m_pTestBuffer的数据。*/
m_pGPUTestResult=new TestBufType[uNumSimpleElements];
for(int i=0;i<uNumSimpleElements;i++)
{
m_pGPUTestResult[i].pos.x=p[i].pos.x;
m_pGPUTestResult[i].pos.y=p[i].pos.y;
m_pGPUTestResult[i].velocity.x=p[i].velocity.x;
m_pGPUTestResult[i].velocity.y=p[i].velocity.y;
}
/*最后Unmap()刚刚Map()的Resource,使得这个resource的CPU指针无效,GPU重新获得对该resource的读写权限。*/
m_pContext->Unmap(resultBuffer,0);
我们最后反过来看下最开始调用的将仅GPU读写的resource拷贝到CPU的resource的函数CreateAndCopyToDebugBuffer(),里面用来描述拷贝出来的resource的D3D11_BUFFER_DESC有两个要注意的点,CPUAccessFlags和Usage。其中CPUAccessFlags根据需要可以设置为D3D11_CPU_ACCESS_READ和D3D11_CPU_ACCESS_WRITE。然后Usage就一定要设置为D3D11_USAGE_STAGING,它表明这个resource是从GPU拷贝到CPU的。CreateAndCopyToDebugBuffer()的实现如下:
ID3D11Buffer* DemoApp::CreateAndCopyToDebugBuffer(ID3D11Buffer* pSrcBuffer)
{
ID3D11Buffer* debugBuffer;
ZeroMemory(&debugBuffer,sizeof(ID3D11Buffer));
D3D11_BUFFER_DESC bfDESC;
ZeroMemory(&bfDESC,sizeof(D3D11_BUFFER_DESC));
pSrcBuffer->GetDesc(&bfDESC);
bfDESC.BindFlags=0;
bfDESC.MiscFlags=0;
bfDESC.CPUAccessFlags=D3D11_CPU_ACCESS_READ;
bfDESC.Usage=D3D11_USAGE_STAGING;
if(SUCCEEDED(m_pDevice->CreateBuffer(&bfDESC,NULL,&debugBuffer)))
{
m_pContext->CopyResource(debugBuffer,pSrcBuffer);
}
return debugBuffer;
}
前面说过可以通过map函数来用cpu来读写gpu的计算数据。这几天又看了些文章后,感觉自己前面的理解有点肤浅。再来补充总结下。
首先如果想更新GPU中的resource,
- 1.是可以通过map()函数来让CPU读写GPU的数据;
- 2.还可以通过ID3D11DeviceContext::CopyResource(),ID3D11DeviceContext::UpdateSubresource()等来直接让GPU写更新它自己的resource。
对于上面几个方法的异同呢,可以参考SDK如下: Each usage dictates a tradeoff between functionality and performance. In general, resource accessing is accomplished with the following APIs. CPU access is done with ID3D11DeviceContext::Map. GPU access is done with ID3D11DeviceContext::CopySubresourceRegion, ID3D11DeviceContext::CopyResource, or ID3D11DeviceContext::UpdateSubresource.
从上简单看出采用Map(),通过CPU能access resource,剩下的方法是直接让GPU来access resource。 为了将让我们理解的数据(int,float之类)输出到屏幕控制台之类的(gpu没有传统的i/o功能),我们还是要通过cpu来读写gpu中的数据。当然如果只是GPU内部的计算,就让GPU来读写就可以了。
至于在创建哪种GPU读写或者CPU读写的buffer的时候,要非常注意。其中我们都会先创建一个D3D11_BUFFER_DESC来描述我们的buffer。在D3D11_BUFFER_DESC里面,有个D3D11_USAGE Usage,它是来描述我们的resource将来要被怎样使用(比如只可以被GPU读写?可以被GPU读,CPU写?之类的)。然后改Usage分为下面四种:
D3D11_USAGE_DEFAULT :A resource that requires read and write access by the GPU. This is likely to be the most common usage choice.
D3D11_USAGE_IMMUTABLE:A resource that can only be read by the GPU. It cannot be written by the GPU, and cannot be accessed at all by the CPU. This type of resource must be initialized when it is created, since it cannot be changed after creation.
D3D11_USAGE_DYNAMIC:A resource that is accessible by both the GPU (read only) and the CPU (write only). A dynamic resource is a good choice for a resource that will be updated by the CPU at least once per frame. To update a dynamic resource, use a Map method.
D3D11_USAGE_STAGING:A resource that supports data transfer (copy) from the GPU to the CPU。
一般来说我们用的最多就是D3D11_USAGE_DEFAULT了,它支持也仅给GPU读写,但CPU不行。 要注意的一点是 D3D11_USAGE_DEFAULT和D3D11_USAGE_IMMUTABLE是不能用map()函数的,他们都是仅给GPU来access的。所以一般来说为了让CPU来读 D3D11_USAGE_DEFAULT的resource,我们就只能采取前面博文讲的迂回的方法:新创建一个D3D11_USAGE_STAGING的resource,简称staResource。然后把D3D11_USAGE_DEFAULT的resource,简称defResource的数据通过ID3D11DeviceContext::CopyResource()拷贝到刚新创建的staResource中,最后再通过Map()函数来对刚得到的staResource来读写。
还有个我也不明白的地方是D3D11_USAGE_DEFAULT,SDk说它只支持GPU读写,但CPU不行。但却可以通过UpdateSubresource()来更新D3D11_USAGE_DEFAULT创建的buffer。而UpdateSubresource()的官方SDK解释是:The CPU copies data from memory to a subresource created in non-mappable memory。这里我也弄混了。