转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=3380

上一篇我们讲了如何建立D3D12的设备,并在其之上建立出11on12的设备。接下去就要开始一步一步转移到纯D3D12下了。

第一个应该转的是相对独立的资源,包括buffer和texture。建立D3D12的资源,之后用前文说的CreateWrappedResource转成D3D11的资源,继续交给D3D11on12渲染就可以了。这样仍然可以往前走一小步,保证引擎还能工作。

Buffer

Buffer包括vertex buffer、index buffer和constant buffer。

D3D12_HEAP_PROPERTIES heap_prop;
heap_prop.Type = D3D12_HEAP_TYPE_UPLOAD;
heap_prop.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heap_prop.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heap_prop.CreationNodeMask = 1;
heap_prop.VisibleNodeMask = 1;

D3D12_RESOURCE_DESC res_desc;
res_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
res_desc.Alignment = 0;
res_desc.Width = size_in_byte_;
res_desc.Height = 1;
res_desc.DepthOrArraySize = 1;
res_desc.MipLevels = 1;
res_desc.Format = DXGI_FORMAT_UNKNOWN;
res_desc.SampleDesc.Count = 1;
res_desc.SampleDesc.Quality = 0;
res_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
res_desc.Flags = D3D12_RESOURCE_FLAG_NONE;

ID3D12Resource* buffer_12;
TIF(d3d_12_device->CreateCommittedResource(&heap_prop, D3D12_HEAP_FLAG_NONE,
	&res_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
	IID_ID3D12Resource, reinterpret_cast<void**>(&buffer_12)));

这样就把D3D12的buffer建立出来了。需要注意的是,和D3D11不一样的是,D3D12在建立资源的时候不能直接提供初始的数据,这里需要用map/unmap自己把数据拷进去。

void* p;
buffer_12_->Map(0, nullptr, &p);
memcpy(p, subres_init, size_in_byte_);
buffer_12_->Unmap(0, nullptr);

之后,就能用CreateWrappedResource生成D3D11的buffer。另外,Map和Unmap也可以直接调用D3D12的,在12里不需要那些map的标志了。

Texture

Texture也可以用类似的方法,CreateCommittedResource建立12的texture,CreateWrappedResource转成11的texture,交给11on12渲染。甚至代码上都和前面的buffer几乎一样,所以我就不再贴一遍了。

有一个和以前不同的地方,12里texture不提供map/unmap。这就意味着你不能用以前的方式往里填数据,或者读数据出来。但12提供了CopyTextureRegion,可以把buffer或者texture的数据拷贝到texture里。有了这个,就能给每个texture建立一个对应的buffer,把线性的数据拷贝进去,在调用CopyTextureRegion把数据放入texture。

std::vector<D3D12_PLACED_SUBRESOURCE_FOOTPRINT> layouts(num_subres);
std::vector<uint64_t> row_sizes_in_bytes(num_subres);
std::vector<uint32_t> num_rows(num_subres);

uint64_t required_size = 0;
d3d_12_device->GetCopyableFootprints(&tex_desc, 0, num_subres, 0, &layouts[0], &num_rows[0], &row_sizes_in_bytes[0], &required_size);

uint8_t* p;
d3d_12_texture_upload_heaps_->Map(0, nullptr, reinterpret_cast<void**>(&p));
for (uint32_t i = 0; i < num_subres; ++ i)
{
	D3D12_SUBRESOURCE_DATA src_data;
	src_data.pData = subres_data[i].pSysMem;
	src_data.RowPitch = subres_data[i].SysMemPitch;
	src_data.SlicePitch = subres_data[i].SysMemSlicePitch;

	D3D12_MEMCPY_DEST dest_data;
	dest_data.pData = p + layouts[i].Offset;
	dest_data.RowPitch = layouts[i].Footprint.RowPitch;
	dest_data.SlicePitch = layouts[i].Footprint.RowPitch * num_rows[i];

	for (UINT z = 0; z < layouts[i].Footprint.Depth; ++ z)
	{
		uint8_t const * src_slice
			= reinterpret_cast(src_data.pData) + src_data.SlicePitch * z;
		uint8_t* dest_slice = reinterpret_cast<uint8_t*>(dest_data.pData) + dest_data.SlicePitch * z;
		for (UINT y = 0; y < num_rows[i]; ++ y)
		{
			memcpy(dest_slice + dest_data.RowPitch * y, src_slice + src_data.RowPitch * y,
				row_sizes_in_bytes[i]);
		}
	}
}

d3d_12_texture_upload_heaps_->Unmap(0, nullptr);

for (uint32_t i = 0; i < num_subres; ++ i)
{
	D3D12_TEXTURE_COPY_LOCATION src;
	src.pResource = d3d_12_texture_upload_heaps_.get();
	src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
	src.PlacedFootprint = layouts[i];

 	D3D12_TEXTURE_COPY_LOCATION dst;
	dst.pResource = d3d_12_texture_.get();
	dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
	dst.SubresourceIndex = i;
	cmd_list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
}

就这样,buffer里的线性数据就能拷贝到texture。同理也可以把texture拷贝到buffer。Map/Unmap也得依赖这样的方法。实际上在D3D11里,这些事情也存在,只是由驱动代劳了。现在这些被暴露到应用层实现。

总结

buffer和texture这样的资源,经过包装,就能在接口不变的情况下换用D3D12的实现。我们朝纯D3D12更近了一步。