通过缓冲区上传纹理数据

上传 2D 或 3D 纹理数据类似于上传 1D 数据,不同之处在于应用程序需要更加关注与行间距相关的数据对齐方式。 缓冲区可以独立并发地从图形管道的多个部分使用,并且非常灵活。

通过缓冲区上传纹理数据

应用程序必须通过 ID3D12GraphicsCommandList::CopyTextureRegionID3D12GraphicsCommandList::CopyBufferRegion 上传数据。 纹理数据比其他资源数据更可能较大、被反复访问,并且更能从非线性内存布局的改进缓存一致性中受益。 在 D3D12 中使用缓冲区时,只要满足内存对齐要求,应用程序就可以完全控制与复制资源数据相关的数据放置和排列。

此示例详细说明了应用程序如何在将 2D 数据转换为 1D 后再放置到缓冲区中。 对于 mipmap 2D 方案,应用程序可以离散地平展每个子资源并快速使用 1D 子分配算法,或者使用更复杂的 2D 子分配技术来最大程度地减少视频内存利用率。 第一种方法应更频繁地使用,因为它更简单。 将数据打包到磁盘或跨网络时,第二种方法可能很有用。 在任一情况下,应用程序仍必须为每个子资源调用复制 API。

// Prepare a pBitmap in memory, with bitmapWidth, bitmapHeight, and pixel format of DXGI_FORMAT_B8G8R8A8_UNORM. 
//
// Sub-allocate from the buffer for texture data.
//

D3D12_SUBRESOURCE_FOOTPRINT pitchedDesc = { 0 };
pitchedDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
pitchedDesc.Width = bitmapWidth;
pitchedDesc.Height = bitmapHeight;
pitchedDesc.Depth = 1;
pitchedDesc.RowPitch = Align(bitmapWidth * sizeof(DWORD), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);

//
// Note that the helper function UpdateSubresource in D3DX12.h, and ID3D12Device::GetCopyableFootprints 
// can help applications fill out D3D12_SUBRESOURCE_FOOTPRINT and D3D12_PLACED_SUBRESOURCE_FOOTPRINT structures.
//
// Refer to the D3D12 Code example for the previous section "Uploading Different Types of Resources"
// for the code for SuballocateFromBuffer.
//

SuballocateFromBuffer(
    pitchedDesc.Height * pitchedDesc.RowPitch,
    D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT
    );

D3D12_PLACED_SUBRESOURCE_FOOTPRINT placedTexture2D = { 0 };
placedTexture2D.Offset = m_pDataCur – m_pDataBegin;
placedTexture2D.Footprint = pitchedDesc;

//
// Copy texture data from DWORD* pBitmap->pixels to the buffer
//

for (UINT y = 0; y < bitmapHeight; y++)
{
  UINT8 *pScan = m_pDataBegin + placedTexture2D.Offset + y * pitchedDesc.RowPitch;
  memcpy( pScan, &(pBitmap->pixels[y * bitmapWidth]), sizeof(DWORD) * bitmapWidth );
}

//
// Create default texture2D resource.
//

D3D12_RESOURCE_DESC  textureDesc { ... };

CComPtr<ID3D12Resource> texture2D;
d3dDevice->CreateCommittedResource( 
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), 
        D3D12_HEAP_FLAG_NONE, &textureDesc, 
        D3D12_RESOURCE_STATE_COPY_DEST, 
        nullptr, 
        IID_PPV_ARGS(&texture2D) );

//
// Copy heap data to texture2D.
//

commandList->CopyTextureRegion( 
        &CD3DX12_TEXTURE_COPY_LOCATION( texture2D, 0 ), 
        0, 0, 0, 
        &CD3DX12_TEXTURE_COPY_LOCATION( m_spUploadHeap, placedTexture2D ), 
        nullptr );

请注意帮助程序结构的用法 CD3DX12_HEAP_PROPERTIESCD3DX12_TEXTURE_COPY_LOCATION,以及 CreateCommittedResourceCopyTextureRegion 的方法。

复制

D3D12 方法使应用程序能够替换 D3D11 UpdateSubresourceCopySubresourceRegion 和资源初始数据。 单个包含一定数量行主纹理数据的3D子资源可能位于缓冲区资源中。 CopyTextureRegion 可以将该纹理数据从缓冲区复制到具有未知纹理布局的纹理资源,反之亦然。 应用程序应首选这种类型的技术来填充经常访问的 GPU 资源,方法是在 UPLOAD 堆中创建大型缓冲区,同时在没有 CPU 访问权限的 DEFAULT 堆中创建经常访问的 GPU 资源。 这种技术有效地支持离散 GPU 及其大量 CPU 不可访问的内存,而不会经常损害 UMA 体系结构。

请注意以下两个常量:

const UINT D3D12_TEXTURE_DATA_PITCH_ALIGNMENT = 256;
const UINT D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT = 512;

除非 D3D12_FEATURE_DATA_D3D12_OPTIONS13::UnrestrictedBufferTextureCopyPitchSupported 为 TRUE,否则每个子资源的行间距必须与 D3D12_TEXTURE_DATA_PITCH_ALIGNMENT(256)的倍数对齐,并且每个子资源的起始偏移必须与 D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT(512)的倍数对齐。

如果行间距小于 D3D12_TEXTURE_DATA_PITCH_ALIGNMENT(256),这是纹理的最小 mip 层的典型情况,则行间距对齐后,D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT(512)的子资源偏移对齐可能会导致额外的填充,这超出了通过行间距对齐所添加的填充。

映射和取消映射

映射和取消映射可以安全地由多个线程调用。 对 Map 的第一次调用为资源分配 CPU 虚拟地址范围。 对 Unmap 的最后一次调用会解除分配 CPU 虚拟地址范围。 CPU 虚拟地址通常返回到应用程序。

每当通过读回堆中的资源在 CPU 和 GPU 之间传递数据时,必须使用MapUnmap,以支持所有支持 D3D12 的系统。 将范围保持得尽可能紧密可以提高需要范围的系统上的效率(请参阅 D3D12_RANGE)。

调试工具的性能受益于在所有 映射 / Unmap 调用中准确使用范围,以及当不再需要 CPU 修改时,应用程序主动取消映射资源。

D3D12 不支持使用 Map (具有 DISCARD 参数集)重命名资源的 D3D11 方法。 应用程序必须自行实现资源重命名。 所有 映射 调用都隐式NO_OVERWRITE和多线程调用。 应用程序负责确保在使用 CPU 访问数据之前完成命令列表中包含的任何相关 GPU 工作。 对 Map 的 D3D12 调用不会隐式刷新任何命令缓冲区,也不会阻止等待 GPU 完成工作。 因此,在某些情况下,映射取消映射 操作甚至可能被优化掉。

缓冲区对齐方式

缓冲区对齐限制:

  • 线性子资源复制必须与D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT(512)字节对齐(行间距与D3D12_TEXTURE_DATA_PITCH_ALIGNMENT(256)字节对齐)。
  • 常量数据读取必须从堆的起始位置开始,并且是 256 字节的倍数(即仅从与 256 字节对齐的地址读取)。
  • 索引数据的读取必须是索引数据类型大小的整数倍(即仅从自然对齐至该数据的地址进行读取)。
  • ID3D12GraphicsCommandList::ExecuteIndirect 数据必须来自 4 的倍数的偏移量(即仅来自 DWORD 对齐的地址)。