三、D3D12学习笔记——顶点&索引数据传入GPU

0.关键代码

几何数据需要按照一定的布局从CPU传入GPU:

std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;

mInputLayout =
    {
        //语义,语义索引,格式,槽,偏移值,数据类型,是否实例化
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    };

传递数据通过以下代码实现:

Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
//将数据传递到VertexBufferGPU 
VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),mCommandList.Get(), vertices.data(), vbByteSize, VertexBufferUploader);

//绘图时使用
D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
vbv.StrideInBytes = VertexByteStride;
vbv.SizeInBytes = VertexBufferByteSize;

cmdList->IASetVertexBuffers(0, 1, &vbv);

将CPU定义好的顶点数据vertices.data(),通过cmdList->IASetVertexBuffers(0, 1, &vbv);这把钥匙放进了GPU,本身IA开头的函数就意味着输入装配InputAssemble。

1.资源定义

Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;

ID3D12Resource这个类代表了GPU资源,RTV和DSV,也是这个类型,D3D12对这个进行了统一,其实在DX11中还是区分什么ID3D11Buffer和ID3D11Texture2D这样的,那么DX12既然统一了,那怎么来区分呢?接着往下看。

2.数据传输的渠道

VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), vertices.data(), vbByteSize,VertexBufferUploader);

对应上边的资源定义,一个是左值,另一个是最后一个参数,对应默认堆和上传堆,VertexBufferGPU实际是个默认堆,而VertexBufferUploader实际是个上传堆: 

先看一下整体代码:
 

Microsoft::WRL::ComPtr<ID3D12Resource> d3dUtil::CreateDefaultBuffer(
    ID3D12Device* device,
    ID3D12GraphicsCommandList* cmdList,
    const void* initData,
    UINT64 byteSize,
    Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer)
{
    ComPtr<ID3D12Resource> defaultBuffer;

    // Create the actual default buffer resource.
    ThrowIfFailed(device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(byteSize),
		D3D12_RESOURCE_STATE_COMMON,
        nullptr,
        IID_PPV_ARGS(defaultBuffer.GetAddressOf())));

    // In order to copy CPU memory data into our default buffer, we need to create
    // an intermediate upload heap. 
    ThrowIfFailed(device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
		D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(byteSize),
		D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(uploadBuffer.GetAddressOf())));


    // Describe the data we want to copy into the default buffer.
    D3D12_SUBRESOURCE_DATA subResourceData = {};
    subResourceData.pData = initData;
    subResourceData.RowPitch = byteSize;
    subResourceData.SlicePitch = subResourceData.RowPitch;

    // Schedule to copy the data to the default buffer resource.  At a high level, the helper function UpdateSubresources
    // will copy the CPU memory into the intermediate upload heap.  Then, using ID3D12CommandList::CopySubresourceRegion,
    // the intermediate upload heap data will be copied to mBuffer.
	cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(), 
		D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST));
    UpdateSubresources<1>(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);
	cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),
		D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ));

    // Note: uploadBuffer has to be kept alive after the above function calls because
    // the command list has not been executed yet that performs the actual copy.
    // The caller can Release the uploadBuffer after it knows the copy has been executed.


    return defaultBuffer;
}
// Create the actual default buffer resource.
ThrowIfFailed(device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(byteSize),
        D3D12_RESOURCE_STATE_COMMON,
        nullptr,
        IID_PPV_ARGS(defaultBuffer.GetAddressOf())));

// In order to copy CPU memory data into our default buffer, we need to create
// an intermediate upload heap. 
ThrowIfFailed(device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(byteSize),
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(uploadBuffer.GetAddressOf())));

函数CreateCommittedResource,创建一个堆和一个资源,根据类型的不同就是默认堆和上传堆。注意到默认堆创建的时候的访问权限Common,我们现在是要往默认堆上传数据,也就是写入,因此进行资源转换:

cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),  D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST));

准备数据:

D3D12_SUBRESOURCE_DATA subResourceData = {};
subResourceData.pData = initData;//数据本身
subResourceData.RowPitch = byteSize;//描述数据的大小,占用内存
subResourceData.SlicePitch = subResourceData.RowPitch;//描述数据的大小,占用内存

万事俱备,只欠东风:

//核心函数,将数据subResourceData从CPU传导上传堆,再到默认堆
// <1>是模板参数,表示Subresource的数量
UpdateSubresources<1>(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);

注意cmdlist,表明实际是将传数据放在命令列表,交由命令队列来做的,这里有数据及其描述,也有中间过渡的上传堆uploadBuffer,还有传入终点defaultBuffer。

现在的默认堆是支持写入的,后边获取数据实际是读出:

cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ));

现在数据已经被我们送到GPU了,但是还没通知到输入装配。

3.通知输入装配

通知时大概的对话是这样的:

我们:货已经到了,放在了XXXX,它大概这么大,每多少个是一个基本单元。所以我们列出了以下关键词:

D3D12_VERTEX_BUFFER_VIEW vbv;
        vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();//获得GPU存储位置地址
        vbv.SizeInBytes = VertexBufferByteSize;
        vbv.StrideInBytes = VertexByteStride;

然后,将我们的这个命令又安排进队列等待执行:

cmdList->IASetVertexBuffers(0, 1, &vbv);

注意这个函数的定义:

IASetVertexBuffers( 
            _In_  UINT StartSlot,//起始槽编号,0~15
            _In_  UINT NumViews,//顶点缓冲区数量,描述第三个参数数组有几个顶点缓冲,要注意一个槽对应一个缓冲区,所以也就是说第三个参数最多不能超过16
            _In_reads_opt_(NumViews)  const D3D12_VERTEX_BUFFER_VIEW *pViews)

看到这里,我们就把CPU的数据送到了GPU的存储位置上,并且告知了输入装配阶段,老规矩,注意事项:

实际上如果有多个顶点缓冲区View1和View2,我们可以用下边的操作:

cmdList->IASetVertexBuffers(0, 1, &View1);

cmdList->IASetVertexBuffers(0, 1, &View2);

也就是说一个槽上边可以放多个信息,只要你不一次性放进去(意味着通知当时拿出当时数据,下次通知会更改数据)。

一般而言我们绘图都是根据索引来的,类似顶点缓冲区传递数据。

4.索引缓冲区

Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;

IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, IndexBufferUploader);

D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();//获得GPU存储位置地址
ibv.Format = IndexFormat;//描述格式,因为sizeof就能得到间隔了,其实和上边原理一样
ibv.SizeInBytes = IndexBufferByteSize;

cmdList->IASetIndexBuffer(&ibv);

准备好了顶点,索引数据,剩下就是告知以何种方式组装:

cmdList->IASetPrimitiveTopology(PrimitiveType);

都准备好了,DrawCall!!!

5.DrawCall

这里我们根据顶点缓冲和索引缓冲数据使用函数:

cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);

同样是加入命令队列,首先告诉他我们每IndexCount个索引画一个Mesh,从索引堆里边第StartIndexLocation开始看,从顶点堆里第BaseVertexLocation开始看,最后一个桉树表示是否使用实例化。

至此,驱动单个像素生成的流水线就启动了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是使用DirectX 12实现图片渲染纹理数据的示例代码: ```cpp // 初始化纹理资源 ID3D12Resource* textureResource = nullptr; D3D12_RESOURCE_DESC textureDesc = {}; textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; textureDesc.Width = textureWidth; textureDesc.Height = textureHeight; textureDesc.DepthOrArraySize = 1; textureDesc.MipLevels = 1; textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; textureDesc.SampleDesc.Count = 1; textureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ThrowIfFailed(device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE, &textureDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&textureResource))); // 创建上传堆 ID3D12Resource* textureUploadHeap = nullptr; ThrowIfFailed(device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE, &CD3DX12_RESOURCE_DESC::Buffer(GetRequiredIntermediateSize(textureResource, 0, 1)), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&textureUploadHeap))); // 将纹理数据复制到上传堆 D3D12_SUBRESOURCE_DATA textureData = {}; textureData.pData = imageData; // 图像数据指针 textureData.RowPitch = textureWidth * 4; // 每行字节数 textureData.SlicePitch = textureData.RowPitch * textureHeight; UpdateSubresources(commandList, textureResource, textureUploadHeap, 0, 0, 1, &textureData); // 将纹理资源从复制目标状态转换为常量状态 CD3DX12_RESOURCE_BARRIER textureBarrier = CD3DX12_RESOURCE_BARRIER::Transition( textureResource, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); commandList->ResourceBarrier(1, &textureBarrier); // 创建纹理描述符堆 CD3DX12_DESCRIPTOR_HEAP_DESC textureHeapDesc(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 1, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE); ID3D12DescriptorHeap* textureDescriptorHeap = nullptr; ThrowIfFailed(device->CreateDescriptorHeap(&textureHeapDesc, IID_PPV_ARGS(&textureDescriptorHeap))); // 创建纹理资源视图 D3D12_SHADER_RESOURCE_VIEW_DESC textureViewDesc = {}; textureViewDesc.Format = textureDesc.Format; textureViewDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; textureViewDesc.Texture2D.MipLevels = 1; device->CreateShaderResourceView(textureResource, &textureViewDesc, textureDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); ``` 这个示例代码包括以下步骤: 1. 创建纹理资源,使用R8G8B8A8_UNORM格式存储像素数据。 2. 创建上传堆,将像素数据复制到上传堆中。 3. 使用`UpdateSubresources`函数将上传堆中的数据复制到纹理资源中。 4. 使用`CD3DX12_RESOURCE_BARRIER`将纹理资源从复制目标状态转换为常量状态。 5. 创建纹理描述符堆和纹理资源视图,使其在着色器中可见。 请注意,这只是一个示例代码,并且需要根据您的具体需要进行修改。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值