DirectX11 顶点缓存

2023-12-20 09:08
文章标签 缓存 顶点 directx11

本文主要是介绍DirectX11 顶点缓存,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

顶点缓存

1. 顶点缓存的作用?

为了让GPU访问顶点数组,我们必须把它放置在一个称为缓冲(buffer)的特殊资源容器中,该容器由ID3D11Buffer接口表示。

用于存储顶点的缓冲区称为顶点缓冲(vertex buffer)。Direct3D缓冲不仅可以存储数据,而且还说明了如何访问数据以及数据被绑定到图形管线的那个阶段。要创建一个顶点缓冲,我们必须执行以下步骤:

1.填写一个D3D11_BUFFER_DESC结构体,描述我们所要创建的缓冲区。

2.填写一个D3D11_SUBRESOURCE_DATA结构体,为缓冲区指定初始化数据。

3.调用ID3D11Device::CreateBuffer方法来创建缓冲区。

2. D3D11_BUFFER_DESC结构体如何填充?

D3D11_BUFFER_DESC结构体的定义如下:

typedef struct D3D11_BUFFER_DESC{UINT ByteWidth;D3D11_USAGE Usage;UINT BindFlags;UINT CPUAccessFlags;UINT MiscFlags;UINT StructureByteStride;
} D3D11_BUFFER_DESC;

1.ByteWidth:我们将要创建的顶点缓冲区的大小,单位为字节。

2.Usage:一个用于指定缓冲区用途的D3D11_USAGE枚举类型成员。有4个可选值:

(a)D3D10_USAGE_DEFAULT:表示GPU会对资源执行读写操作。在使用映射API(例如ID3D11DeviceContext::Map)时,CPU在使用映射API时不能读写这种资源,但它能使用ID3D11DeviceContext::UpdateSubresource。ID3D11DeviceContext::Map方法会在6.14节中介绍。

(b)D3D11_USAGE_IMMUTABLE:表示在创建资源后,资源中的内容不会改变。这样可以获得一些内部优化,因为GPU会以只读方式访问这种资源。除了在创建资源时CPU会写入初始化数据外,其他任何时候CPU都不会对这种资源执行任何读写操作,我们也无法映射或更新一个只读资源。

(c)D3D11_USAGE_DYNAMIC:表示应用程序(CPU)会频繁更新资源中的数据内容(例如,每帧更新一次)。GPU可以从这种资源中读取数据,使用映射API(ID3D11DeviceContext::Map)时,CPU可以向这种资源中写入数据。因为新的数据要从CPU内存(即系统RAM)传送到GPU内存(即显存),所以从CPU动态地更新GPU资源会有性能损失;若非必须,请勿使用D3D11_USAGE_DYNAMIC。

(d)D3D11_USAGE_STAGING:表示应用程序(CPU)会读取该资源的一个副本(即,该资源支持从显存到系统内存的数据复制操作)。显存到系统内存的复制是一个缓慢的操作,应尽量避免。使用ID3D11DeviceContext::CopyResource和ID3D11DeviceContext::CopySubresourceRegion方法可以复制资源,在12.3.5节会介绍一个复制资源的例子。

3.BindFlags:对于顶点缓冲区,该参数应设为D3D11_BIND_VERTEX_BUFFER。

4.CPUAccessFlags:指定CPU对资源的访问权限。设置为0则表示CPU无需读写缓冲。如果CPU需要向资源写入数据,则应指定D3D11_CPU_ACCESS_WRITE。具有写访问权限的资源的Usage参数应设为D3D11_USAGE_DYNAMIC或D3D11_USAGE_STAGING。如果CPU需要从资源读取数据,则应指定D3D11_CPU_ACCESS_READ。具有读访问权限的资源的Usage参数应设为D3D11_USAGE_STAGING。当指定这些标志值时,应按需而定。通常,CPU从Direct3D资源读取数据的速度较慢。CPU向资源写入数据的速度虽然较快,但是把内存副本传回显存的过程仍很耗时。所以,最好的做法是(如果可能的话)不指定任何标志值,让资源驻留在显存中,只用GPU来读写数据。

5.MiscFlags:我们不需要为顶点缓冲区指定任何杂项(miscellaneous)标志值,所以该参数设为0。有关D3D11_RESOURCE_MISC_FLAG枚举类型的详情请参阅SDK文档。

6.StructureByteStride:存储在结构化缓冲中的一个元素的大小,以字节为单位。这个属性只用于结构化缓冲,其他缓冲可以设置为0。所谓结构化缓冲,是指存储其中的元素大小都相等的缓冲。

2. D3D11_SUBRESOURCE_DATA结构体如何填充?

D3D11_SUBRESOURCE_DATA结构体的定义如下:

typedef struct D3D11_SUBRESOURCE_DATA {const void *pSysMem;UINT SysMemPitch;UINT SysMemSlicePitch;
} D3D11_SUBRESOURCE_DATA;

1.pSysMem:包含初始化数据的系统内存数组的指针。当缓冲区可以存储n个顶点时,对应的初始化数组也应至少包含n个顶点,从而使整个缓冲区得到初始化。

2.SysMemPitch:顶点缓冲区不使用该参数。

3.SysMemSlicePitch:顶点缓冲区不使用该参数。

下面的代码创建了一个只读的顶点缓冲区,并以中心在原点上的立方体的8顶点来初始化该缓冲区。之所以说该缓冲区是只读的,是因为当立方体创建后相关的几何数据从不改变——始终保持为一个立方体。另外,我们为每个顶点指定了不同的颜色;这些颜色将用于立方体着色,我们会在本章随后的小节中对此进行讲解。

//  定义在d3dUtil.h中的Colors命名空间
//
// #define XMGLOBALCONST extern CONST __declspec(selectany)
//   1. extern 保证只会有一个变量在当前项目中
//   2. __declspec(selectany)可以消除重定义引起的错误namespace Colors
{XMGLOBALCONST XMVECTORF32 White     = {1.0f, 1.0f, 1.0f, 1.0f};XMGLOBALCONST XMVECTORF32 Black     = {0.0f, 0.0f, 0.0f, 1.0f};XMGLOBALCONST XMVECTORF32 Red       = {1.0f, 0.0f, 0.0f, 1.0f};XMGLOBALCONST XMVECTORF32 Green     = {0.0f, 1.0f, 0.0f, 1.0f};XMGLOBALCONST XMVECTORF32 Blue      = {0.0f, 0.0f, 1.0f, 1.0f};XMGLOBALCONST XMVECTORF32 Yellow    = {1.0f, 1.0f, 0.0f, 1.0f};XMGLOBALCONST XMVECTORF32 Cyan      = {0.0f, 1.0f, 1.0f, 1.0f};XMGLOBALCONST XMVECTORF32 Magenta   = {1.0f, 0.0f, 1.0f, 1.0f};XMGLOBALCONST XMVECTORF32 Silver    = {0.75f, 0.75f, 0.75f, 1.0f};XMGLOBALCONST XMVECTORF32 LightSteelBlue = {0.69f, 0.77f, 0.87f, 1.0f};
}// 创建顶点缓冲
Vertex vertices[] =
{{ XMFLOAT3(-1.0f, -1.0f, -1.0f), (const float*)&Colors::White   },{ XMFLOAT3(-1.0f, +1.0f, -1.0f), (const float*)&Colors::Black   },{ XMFLOAT3(+1.0f, +1.0f, -1.0f), (const float*)&Colors::Red     },{ XMFLOAT3(+1.0f, -1.0f, -1.0f), (const float*)&Colors::Green   },{ XMFLOAT3(-1.0f, -1.0f, +1.0f), (const float*)&Colors::Blue    },{ XMFLOAT3(-1.0f, +1.0f, +1.0f), (const float*)&Colors::Yellow  },{ XMFLOAT3(+1.0f, +1.0f, +1.0f), (const float*)&Colors::Cyan    },{ XMFLOAT3(+1.0f, -1.0f, +1.0f), (const float*)&Colors::Magenta }
};D3D11_BUFFER_DESC vbd;
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(Vertex) * 8;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
vbd.MiscFlags = 0;
vbd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA vinitData;
vinitData.pSysMem = vertices;ID3D11Buffer * mVB;
HR(md3dDevice->CreateBuffer(&vbd, &vinitData, &mVB));

Vertex类型和颜色由以下结构定义:

struct Vertex
{XMFLOAT3 Pos;XMFLOAT4 Color;
};
3. 如何创建顶点缓存?

利用设备的CreateBuffer函数来创建顶点缓存。
第一个参数是设备描述对象,用于描述顶点缓存。
第二个参数是子资源数据,该结构的pSysMem成员是一个已经初始化内存的指针,在现在这种情况是我们发送给缓存填充的内存,也就是上面的顶点数组。
第三个参数保存创建的缓存对象指针。

4. 如何绑定顶点缓存?

在创建顶点缓冲区后, 我们必须把它绑定到设备的输入槽上,只有这样才能将顶点送入管线。这一工作使用如下方法完成:

void ID3D11DeviceContext::IASetVertexBuffers(UINT StartSlot,UINT NumBuffers,ID3D10Buffer *const *ppVertexBuffers,const UINT *pStrides,const UINT *pOffsets);

1.StartSlot:顶点缓冲区所要绑定的起始输入槽。一共有16个输入槽,索引依次为0到15。

2.NumBuffers:顶点缓冲区所要绑定的输入槽的数量,如果起始输入槽为索引k,我们绑定了n个缓冲,那么缓冲将绑定在索引为Ik,Ik+1……Ik+n-1的输入槽上。

3.ppVertexBuffers:指向顶点缓冲区数组的第一个元素的指针。

4.pStrides:指向步长数组的第一个元素的指针(该数组的每个元素对应一个顶点缓冲区,也就是,第i个步长对应于第i个顶点缓冲区)。这个步长是指顶点缓冲区中的元素的字节长度。

5.pOffsets:指向偏移数组的第一个元素的指针(该数组的每个元素对应一个顶点缓冲区,也就是,第i个偏移量对应于第i个顶点缓冲区)。这个偏移量是指从顶点缓冲区的起始位置开始,到输入装配阶段将要开始读取数据的位置之间的字节长度。当希望跳过顶点缓冲区前面的一部分数据时,可以使用该参数。

因为IASetVertexBuffers方法支持将一个顶点缓冲数组设置到不同的输入槽中,因此这个方法看起来有点复杂。但是,大多数情况下我们只使用一个输入槽。本章最后的练习部分你会遇到使用两个输入插槽的情况。

顶点缓冲区会一直绑定在输入槽上时。如果不改变输入槽的绑定对象,那么当前的顶点缓冲区会一直驻留在那里。所以,当使用多个顶点缓冲区时,你可以按照下面的形式组织代码:

ID3D11Buffer* mVB1; // stores vertices of type Vertex1
ID3D11Buffer* mVB2; // stores vertices of type Vertex2
/*...Create the vertex buffers...*/
UINT stride = sizeof(Vertex1);
UINT offset = 0;
md3dImmediateContext->IASetVertexBuffers(0, 1, &mVB1, &stride, &offset);
/* ...draw objects using vertex buffer 1... */
stride = sizeof(Vertex2);
offset = 0;
md3dImmediateContext->IASetVertexBuffers(0, 1, &mVB2, &stride, &offset);
/* ...draw objects using vertex buffer 2... */

把顶点缓冲区指定给输入槽并不能实现顶点的绘制;它只是绘制前的准备工作(准备把顶点传送到管线)。顶点的实际绘制工作由ID3D11DeviceContext::Draw方法完成:

void ID3D11DeviceContext::Draw(UINT VertexCount, UINT StartVertexLocation);

这两个参数定义了在顶点缓冲区中所要绘制的顶点的范围,如图6.2所示。
这里写图片描述
(图6.2 StartVertexLocation 指定了在顶点缓冲区中所要绘制的第一个顶点的索引(从0开始)。VertexCount指定了所要绘制的顶点的数量。)

这篇关于DirectX11 顶点缓存的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/515483

相关文章

Linux修改pip和conda缓存路径的几种方法

《Linux修改pip和conda缓存路径的几种方法》在Python生态中,pip和conda是两种常见的软件包管理工具,它们在安装、更新和卸载软件包时都会使用缓存来提高效率,适当地修改它们的缓存路径... 目录一、pip 和 conda 的缓存机制1. pip 的缓存机制默认缓存路径2. conda 的缓

Redis解决缓存击穿问题的两种方法

《Redis解决缓存击穿问题的两种方法》缓存击穿问题也叫热点Key问题,就是⼀个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击,本文给大家介绍了Re... 目录引言解决办法互斥锁(强一致,性能差)逻辑过期(高可用,性能优)设计逻辑过期时间引言缓存击穿:给

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

一文详解Nginx的强缓存和协商缓存

《一文详解Nginx的强缓存和协商缓存》这篇文章主要为大家详细介绍了Nginx中强缓存和协商缓存的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、强缓存(Strong Cache)1. 定义2. 响应头3. Nginx 配置示例4. 行为5. 适用场景二、协商缓存(协

Golang基于内存的键值存储缓存库go-cache

《Golang基于内存的键值存储缓存库go-cache》go-cache是一个内存中的key:valuestore/cache库,适用于单机应用程序,本文主要介绍了Golang基于内存的键值存储缓存库... 目录文档安装方法示例1示例2使用注意点优点缺点go-cache 和 Redis 缓存对比1)功能特性

MySQL8.0设置redo缓存大小的实现

《MySQL8.0设置redo缓存大小的实现》本文主要在MySQL8.0.30及之后版本中使用innodb_redo_log_capacity参数在线更改redo缓存文件大小,下面就来介绍一下,具有一... mysql 8.0.30及之后版本可以使用innodb_redo_log_capacity参数来更改

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1

Redis与缓存解读

《Redis与缓存解读》文章介绍了Redis作为缓存层的优势和缺点,并分析了六种缓存更新策略,包括超时剔除、先删缓存再更新数据库、旁路缓存、先更新数据库再删缓存、先更新数据库再更新缓存、读写穿透和异步... 目录缓存缓存优缺点缓存更新策略超时剔除先删缓存再更新数据库旁路缓存(先更新数据库,再删缓存)先更新数

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧