DirectX讀取紋理數據到CPU


最近自己在研究一個問題:DX中給定一張Texture,當數據已經存在於GPU端后,應該如何做才能將紋理的數據讀取到CPU中?
要解決這個問題,首先應當知道DirectX中對於一個Texture的描述,這里我們以2DTexture為例,描述它的數據結構如下:

typedef struct D3D11_TEXTURE2D_DESC
{
    UINT Width;         			// 紋理寬度
    UINT Height;        			// 紋理高度
    UINT MipLevels;     			// 允許的Mip等級數
    UINT ArraySize;     			// 可以用於創建紋理數組,這里指定紋理的數目,單個紋理使用1
    DXGI_FORMAT Format; 			// DXGI支持的數據格式,默認DXGI_FORMAT_R8G8B8A8_UNORM
    DXGI_SAMPLE_DESC SampleDesc;    // MSAA描述
    D3D11_USAGE Usage;  			// 使用D3D11_USAGE枚舉值指定數據的CPU/GPU訪問權限
    UINT BindFlags;     			// 使用D3D11_BIND_FLAG枚舉來決定該數據的使用類型
    UINT CPUAccessFlags;    		// 使用D3D11_CPU_ACCESS_FLAG枚舉來決定CPU訪問權限
    UINT MiscFlags;     			// 使用D3D11_RESOURCE_MISC_FLAG枚舉
}   D3D11_TEXTURE2D_DESC;

typedef struct DXGI_SAMPLE_DESC
{
    UINT Count;                     // MSAA采樣數
    UINT Quality;                   // MSAA質量等級
} DXGI_SAMPLE_DESC;

其中Usage有四種類型:D3D11_USAGE_DEFAULT、D3D11_USAGE_IMMUTABLE、D3D11_USAGE_DYNAMIC、D3D11_USAGE_STAGING,它們的關系如下表所示:

D3D11_USAGE CPU讀 CPU寫 GPU讀 GPU寫
D3D11_USAGE_DEFAULT
D3D11_USAGE_IMMUTABLE
D3D11_USAGE_DYNAMIC
D3D11_USAGE_STAGING

DX中能夠支持CPU讀的只有D3D11_USAGE_STAGING,但是它是不能參與到渲染管線中的,因此BindFlags應當設為0。
除此之外,如果我們希望能對Texture進行CPU讀寫操作,那么CPUAccessFlags也要做相應的設置(設置為D3D11_CPU_ACCESS_WRITE或D3D11_CPU_ACCESS_READ)。
另外值得一提的是,這四種類型中性能最高的其實就是D3D11_USAGE_IMMUTABLE,但是這種類型的數據只能在一開始創建的時候設定,之后就不能更改了。

了解了以上內容后我們就可以知道,對於對於不是D3D11_USAGE_STAGING類型的Texture,我們其實是沒有直接的方法可以將它的數據讀取到CPU中的。但是我們可以曲線救國:創建一個D3D11_USAGE_STAGING類型的Texture a,然后將我們要取數據的Texture b復制到這個Texture a中,最后再讓CPU讀取a的數據,就等於變相讀取了b的數據。
具體可以看看代碼,我們首先創建了一個Texture b,把它設為一個Shader Resource和Render Target,並對它進行了一系列渲染為它賦值:

D3D11_TEXTURE2D_DESC texDesc;
texDesc.Width = w;
texDesc.Height = h;
texDesc.MipLevels = 1;
texDesc.ArraySize = 1;
texDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
texDesc.SampleDesc.Count = 1;
texDesc.SampleDesc.Quality = 0;
texDesc.Usage = D3D11_USAGE_DEFAULT;
texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
texDesc.CPUAccessFlags = 0;
texDesc.MiscFlags = 0;
device->CreateTexture2D(&texDesc, 0, &tex_b);		
device->CreateShaderResourceView(tex_b, 0, &irradianceSRV);
device->CreateRenderTargetView(tex_b, 0, &irradianceRTV);

接着,我們要想讀取b的數據到CPU,那么需要創建一個D3D11_USAGE_STAGING的Texture a,並且它的寬高、Format等信息需要和a一致:

ID3D11Resource* res = nullptr;
ID3D11Texture2D* tex_b = nullptr;

//這個SRV注意就是tex_b的SRV
irradianceSRV->GetResource(&res);

res->QueryInterface(&tex_b);
D3D11_TEXTURE2D_DESC desc;
tex_b->GetDesc(&desc);

ID3D11Texture2D* copy_tex;
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
device->CreateTexture2D(&desc, 0, &copy_tex);

然后要做的,就是進行資源復制,並調用context的Map函數:

context->CopyResource(copy_tex, res);

D3D11_MAPPED_SUBRESOURCE mappedResource;
context->Map(copy_tex, 0, D3D11_MAP_READ, 0, &mappedResource);

通過以上代碼,得到的mappedResource就是讀取到CPU的數據。mappedResource.pData就是從數據的起始地址,mappedResource.RowPitch記錄了這些數據中每一行的大小。
需要注意的一點是,RowPitch是經過內存對齊后的大小,一般是要比圖片一行數據的大小更大。
因此,讀取數據可以參考以下代碼:

int index;
float*img_data = new float[desc.Width * desc.Height * 3];
char* begin_data = reinterpret_cast<char*>(mappedResource.pData);
for(int i=0;i< desc.Height;i++)
	for (int j = 0; j < desc.Width; j++)
	{
		int y = desc.Height -1 - i;
		index = y * desc.Width + j;
		img_data[index * 3] = *reinterpret_cast<float*>(begin_data + i * mappedResource.RowPitch + j * 16);
		img_data[index * 3 + 1] = *reinterpret_cast<float*>(begin_data + i * mappedResource.RowPitch + j * 16 + 4);
		img_data[index * 3 + 2] = *reinterpret_cast<float*>(begin_data + i * mappedResource.RowPitch + j * 16 + 8);
	}

由於Map函數會封鎖GPU對該數據的訪問權限,最后記得要調用Unmap函數解除限制,並清除相應的臨時對象:

context->Unmap(copy_tex, 0);
SAFE_DELETE(img_data);
SAFE_RELEASE(res);
SAFE_RELEASE(tex);
SAFE_RELEASE(copy_tex);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM