《Practical Rendering & Computation with Direct3D11》读书总结 Chapter-2-Direct3D 11 Resources

这本书的第二章花了近80页的篇幅来具体介绍D3D11中的资源,并给出了一些资源创建使用的具体方法,各种资源有些非常相似而又有不同,因此很有必要做一个总结归纳。

资源总览

首先要明确一点,资源的本质就是内存块,这些内存块可以被GPU来读取和操控。
在第一章中已经提到了,D3D11中的资源无非就两类:Buffer和Texture,并且它们各自都有自己的子类,但不同种类的资源的本质都是相同的,它们的不同就在于各自的语义、格式、访问权限、使用方式。
资源的创建:资源的创建是由ID3D11Device来完成的,对于每种不同的资源都提供了不同的方法,但这些方法都遵循相同的模式:它需要接受三个参数,第一个是对资源的描述(resource description),第二个是D3D11_SUBRESOURCE_DATA结构体,它用来提供初始化资源的数据,第三个是一个指向指针的指针,它用来接受返回的最终生成的指向资源的指针。在这些参数中,决定了资源的独特性质的是对资源的描述,之后会详细讲述。

Usage Flags:Usage Flags是一个枚举类型,

enum D3D11_USAGE {
    D3D11_USAGE_DEFAULT,
    D3D11_USAGE_IMMUTABLE,
    D3D11_USA6E_DYNAMIC,
    D3D11JJSAGE_STAGING
}

这个类型代表的是CPU和GPU的访问权限。
DEFAULT:表示资源可以被GPU读写,CPU没有读写权限,代表资源有render texture、stream output vertex buffers
IMMUTABLE:表示资源只能被GPU读,不可以被GPU写或被CPU读写,代表资源有静态的顶点、索引数据,例如网格的顶点数据。
DYNAMIC:表示资源可以被GPU读和CPU写,代表资源有变换矩阵等。
STAGING:表示资源可以被GPU和CPU读写,这类资源用到的场合有很多。

CPU Access Flags:一个枚举类型,只有两种取值:

enum D3D11_CPU_ACCESS_FLA6 {
    D3D11_CPU_ACCESS_WRITE,
    D3D11_CPU_ACCESS_READ
}

表示CPU的访问权限,要与选定的Usage中的规则相对应,例如一个资源如果选定了Default Usage,那么它的CPU Access Flags只能置为0。如果要表示CPU可读写,就用或运算符将这两个枚举值连接点起来。

Bind Flags: Bind Flags表示资源可以绑定在流水线中的哪个位置,它也是一个枚举类型,有八种可能的取值,

enum D3D11_BIND_FLAG {
    D3D11_BIND_VERTEX_BUFFER,
    D3D11_BIND_INDEX_BUFFER,
    D3D11_BIND_C0NSTANT_BUFFER,
    D3D11_BIND_SHADER_RES0URCE,
    D3D11_BIND_STREAM_OUTPUT,
    D3D11_BIND_RENDER_TARGET,
    D3D11_BIND_DEPTH_STENCIL,
    D3D11_BIND_UNORDERED_ACCESS
}

这里写图片描述
结合流水线的图来看,这八种类型中,VERTEX_BUFFER和INDEX_BUFFER表示被绑定在输入装配阶段(Input Assembler),代表着资源的数据用作流水线的输入数据;RENDER_TARGET和DEPTH_STENCIL表示被绑定在输出归并阶段(Output Merger), 表示这些资源将用来获取光栅化渲染的最终图像的信息;STREAM_OUTPUT表示被绑定在流输出阶段(Stream Output),表示这些资源将用来获取流水线中间得到的几何信息;剩余的三种CONSTANT_BUFFER、SHADER_RESOURCE、UNORDERED_ACCESS表示资源被绑定在一些管线的一些可编程阶段,但它们之间略有不同,之后会详细讲述。

Miscellaneous Flags:用来表示资源的一些特殊性质,一般都是用在非传统的场合。这个在用到的时候再作具体解释。

enum D3D11_RES0URCE_MISC_FLAG {
    D3D11_RESOURCE_MISC_GENERATE_MIPS,
    D3D11_RESOURCE_MISC_SHARED,
    D3D11_RESOURCE_MISC_TEXTURECUBE,
    D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS,
    D3D11_RESOURCE_MISC_BUFFER_ALO0W_RAW_VIEWS,
    D3D11_RESOURCE_MISC_BUFFER_STRUCTURED,
    D3D11_RESOURCE_MISC_RES0URCE_CLAMP,
    D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX,
    D3D11_RESOURCE_MISC_GDI_C0MPATIBLE
}

Resource View

在Bind Flags中一共有八种取值,它们代表着资源要被绑定在哪里,其中有四种表示资源可以直接被绑定到流水线上,它们是Vertex Buffer、Index Buffer、Constant Buffer、Stream Output Buffer,而其他的四种则不能被直接绑定到流水线上,它们需要借助一种适配器——Resource View,Resource View是与Resource绑定在一起的,它表示了资源的一些性质,决定了这些资源可以被执行哪些操作,同时也可以代表一些资源的子集。

Resource View Types
Resource View一共有四种:Render Target View、Depth Stentil View、Shader Resource View、Unordered Access View,下面分别介绍它们的意义。

Render Target View:它用于绑定一个资源来获取流水线的输出,即这个资源是可以被流水线修改的,并且它也会被流水线读取来执行一些操作,一般来说,Render Target View所绑定的资源是一个二维的Texture,但也可以绑定其他类型的资源。

Depth Stencil View:它与Render Target View类似,也是绑定一个资源来获取流水线的输出,不同的是,它绑定的是一个Depth Stencil Buffer,而不是一个带有颜色的Render Target,被Depth Stencil View绑定的资源都是用来作深度测试和模板测试的,它的读写性质是依靠一个额外的Flag来决定的,因为一个Depth Stencil Buffer既有可能用来做深度测试及模板测试和一个Depth Stencil View绑定在一起,同时作为输入数据和一个Shader Resource View绑定在一起,在这种情况下,这两个View都要被设定为只读的。但是当它只用在传统的获取流水线的输出结果时,要将其设置为可写的。

Shader Resource View:提供流水线中可编程着色器阶段的读取权限,它可以被用在所有的可编程着色器阶段。

Unordered Access View:像Shader Resource View一样,它让资源可以被着色器读取获取信息,但同时它也允许资源被着色器修改,但是它绑定的资源只能被用在Pixel Shader和Compute Shader阶段。

Resource View Creation
Resource的创建都是由ID3D11Device完成的,同样Resource View也是由ID3D11Device完成的,下面是一个例子:

ID3D11ShaderResourceView* CreateShaderResourceView(ID3D11Resource * resource, D3D11_SHADER_RESOURCE_VIEW_DESC * desc)                                                                                                       
{
    ID3D11ShaderResourceView * view;
    HRESULT hr = m_Device->CreateShaderResourceView(resource, desc, &view);
    if (FAILED(hr))
    {
        return NULL;
    }
    return view;
}

可见创建一个Resource View,需要提供Resource、Resource View Desc、指向View指针的指针三个参数。各种View创建的不同的地方就在于Resource View Desc参数。

Render Target View Desc:
这里写图片描述

第一个参数是资源的数据格式,要与资源的Format对应,否则会创建失败!
第二个参数表示Resource View要与哪种类型的资源绑定在一起,只能从第三个参数中列举出来的那些类型中选。
第三个参数是一个联合类型,根据第二个参数所指示的类型,来填充对应的类型的成员就行了。具体的各种资源的成员的意义,之后会详述。

Depth Stencil View Desc:
这里写图片描述
其中Flags代表着读写权限,可用OR运算连接,下面是一个创建DSV Desc的例子:

    D3D11_DEPTH_STENCIL_VIEW_DESC depthdesc;
    ZeroMemory(&depthdesc, sizeof(depthdesc));
    depthdesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthdesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    depthdesc.Texture2D.MipSlice = 0;
    hr = m_Device->CreateDepthStencilView(m_DepthStencilBuffer, &depthdesc, &m_DepthStencilView);

Shader Resource View Desc:
这里写图片描述

Unordered Access View Desc:
这里写图片描述

接下来具体介绍各种资源,对于每种资源,需要关注它的创建、使用、对Resource View的要求

Buffer

关于Buffer,可以分为两个大类:可直接绑定到流水线即无需Resource View的Buffer;需要Resource View的Buffer。注意Buffer类型的资源都提供一个一维的线性内存块。

可直接绑定的Buffer——Vertex Buffer

如果写过简单的渲染网格模型的程序,肯定对Vertex、Index非常熟悉,Vertex Buffer就是存储Vertex数据的Buffer,最简单的Vertex Buffer的构造就是一个Vertex Structure数组,每个Vertex Structure存储了顶点重点内容的坐标、法线、纹理信息,如果要用到Instancing Rendering之类的技术,Vertex Buffer也可以有其他的构造。

Vertex Buffer的使用
常规的Vertex Buffer肯定就是提供Vertex数据到输入装配阶段,除了这个其实还有一种用法,它也可以被绑定到Stream Output阶段来接受pipeline的渲染结果。
Vertex Buffer的创建
Vertex Buffer的Bind Flag首先必须有D3D11_BIND_VERTEX_BUFFER,还可以加上D3D11_BIND_STREAM_OUTPUT。
还需要关注它的读写权限,Vertex Buffer是可以在CPU中作更新的,例如可以在CPU中对顶点作空间的变换,这个时候它的Usage应该是Dynamic,而如果Vertex Buffer是作为静态的地形数据之类的,那么它的Usage应该是Immutable,而如果Vertex Buffer是被绑定到STREAM_OUTPUT阶段,那么它的Usage应该是Default。因此,不难得出如下的创建代码:

ID3D11Buffer * Sy_Graphics::CreateVertexBuffer(UINT size, bool dynamic, bool output, D3D11_SUBRESOURCE_DATA * pSubresource)
{
    D3D11_BUFFER_DESC desc;
    desc.StructureByteStride = 0;
    desc.MiscFlags = 0;
    desc.ByteWidth = size;
    if (dynamic)
    {
        desc.Usage = D3D11_USAGE_DYNAMIC;
        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
        desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    }
    else if (output)
    {
        desc.Usage = D3D11_USAGE_DEFAULT;
        desc.CPUAccessFlags = 0;
        desc.BindFlags = D3D11_BIND_STREAM_OUTPUT | D3D11_BIND_VERTEX_BUFFER;
    }
    else {
        desc.Usage = D3D11_USAGE_IMMUTABLE;
        desc.CPUAccessFlags = 0;
        desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    }
    ID3D11Buffer * pbuffer = 0;
    HRESULT hr = m_Device->CreateBuffer(&desc, pSubresource, &pbuffer);
    if (FAILED(hr))
    {
        return NULL;
    }
    return pbuffer;
}

资源的创建一般都是基于根据用法来确定Bind Flag、Usage Flag、CPU Access Flag这样的模式。

可直接绑定的Buffer——Index Buffer

Index就是顶点的索引,比如一个正方体,原则上它只需要8个顶点就够了,但是如果不用索引缓冲,那么它就需要36个顶点,因为每个三角形的顶点必须都要独立定义,这显然是不够高效的一种手段,那么可以利用顶点缓冲,指定每个三角形的顶点的下标,有效地避免了重复定义顶点。由此可见,Index Buffer也是作为输入数据绑定到输入装配阶段的。
Index Buffer的使用:和Vertex Buffer不同的是,它只能被绑定到输入装配阶段,而不能绑定到Output Stream阶段。
Index Buffer的创建:它的Bind Flag只能是D3D11_BIND_INDEX_BUFFER,而它的Usage也只需要考虑是CPU还是GPU修改就行了。

ID3D11Buffer * Sy_Graphics::CreateIndexBuffer(UINT size, bool dynamic, D3D11_SUBRESOURCE_DATA * pSubresource)
{
    D3D11_BUFFER_DESC desc;
    desc.ByteWidth = size;
    desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    desc.MiscFlags = 0;
    desc.StructureByteStride = 0;
    if (dynamic)
    {
        desc.Usage = D3D11_USAGE_DYNAMIC;
        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    }
    else {
        desc.Usage = D3D11_USAGE_IMMUTABLE;
        desc.CPUAccessFlags = 0;
    }
    ID3D11Buffer * buffer = 0;
    HRESULT hr = m_Device->CreateBuffer(&desc, pSubresource, &buffer);
    if (FAILED(hr))
    {
        return NULL;
    }
    return buffer;
}

可直接绑定的Buffer——Constant Buffer

Constant Buffer是可以被绑定到可编程着色器阶段的资源,它可以被HLSL代码使用,之所以称它为Constant,是因为在整个draw、dispatch调用过程中,它的内容是不变的,但并不是说在整个程序运行过程中它始终要不变,在一次流水线工作完成后,它的内容是可以被改变的。Constant Buffer存储的数据类型和其他的有所不同,在之前的VertexBuffer和IndexBuffer中,资源的数据都是一个相同类型的数组,而Constant Buffer只是一个单独的数据类型,即一个单独的结构体,而不是一个数组。

Constant Buffer的使用
每一个可编程着色器都可以接受Constant Buffer,Constant Buffer可能会在CPU中被修改,并且在修改后必须重新装配到GPU中。假设现在一个Shader程序接受10个参数,其中有8个是固定不变的,有2个是需要改动的,如果将这10个参数全部存在一个Constant Buffer中,那么每一次都需要重新装配这10个参数,显然是非常低效的,因此可以考虑采用2个Constant Buffer,其中一个设定为只读的,存储8个不变的参数,另外一个设定为动态的,存储那两个变化的参数,这样就可以每次只重新装配这个动态的Buffer,更为高效。
还有一点是当Constant Buffer为只读的时候,它可以同时被绑定到多个Shader中,但如果它是可写的,就不行。

Constant Buffer的创建
Constant Buffer的Bind Flag一定是D3D11_BIND_CONSTANT_BUFFER,而它的Usage Flag可以是Dynamic、Immutable、Default,前两个很容易理解,那么为什么还可以设置为Default呢?有的时候需要复制一个 Append/Consume Buffer 中的元素个数到一个Constant Buffer中,这个时候需要用到DeviceContext中的CopyStructureCount()方法,这个时候就是在GPU上对Constant Buffer修改了,所以要用Default。

ID3D11Buffer * Sy_Graphics::CreateConstantBuffer(UINT size, bool dynamic, bool CPUUpdates, D3D11_SUBRESOURCE_DATA * pSubresource)
{
    D3D11_BUFFER_DESC desc;
    desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    desc.ByteWidth = size;
    desc.MiscFlags = 0;
    desc.StructureByteStride = 0;
    if (dynamic && CPUUpdates)
    {
        desc.Usage = D3D11_USAGE_DYNAMIC;
        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    }
    else if (dynamic && !CPUUpdates)
    {
        desc.Usage = D3D11_USAGE_DYNAMIC;
        desc.CPUAccessFlags = 0;
    }
    else {
        desc.Usage = D3D11_USAGE_IMMUTABLE;
        desc.CPUAccessFlags = 0;
    }
    ID3D11Buffer * pbuffer = 0;
    HRESULT hr = m_Device->CreateBuffer(&desc, pSubresource, &pbuffer);
    if (FAILED(hr))
    {
        return NULL;
    }
    return pbuffer;
}

对于Constant Buffer,它所存储的那个结构体的大小必须是16字节的整数倍

需要View的Buffer——Standard Buffer 和 Structured Buffer

Standard Buffer和Structured Buffer都是需要View的Buffer,它们都是数组形式的Buffer,这一点和Constant Buffer只是单个结构体不同。它们之间的不同之处在于,Standard Buffer所存储的数据类型都是内置类型,而Structured Buffer的数据类型是自定义的结构类型。

Standard Buffer和Structured Buffer的使用
这两种Buffer都是用在Shader中的,根据它们绑定的View的类型,可以实现被各种Shader读取(Shader Resource View)或者被Pixel、Compute Shader修改(Unordered Access View),可以说它们提供了一种在流水线不同的阶段之间的交流的一种方法。它们必须要和Shader Resource View、Unordered Access View中的一种或者同时绑定。和Constant Buffer一样,当它们为只读的时候,就可以同时绑定到流水线上的多个Shader中,而如果可写,就不行。

Standard Buffer和Structured Buffer的创建

ID3D11Buffer * Sy_Graphics::CreateStructuredBuffer(UINT count, UINT structsize, bool CPUWritable, bool GPUWritable, D3D11_SUBRESOURCE_DATA * pData)
{
    D3D11_BUFFER_DESC desc;
    desc.ByteWidth = count * structsize;
    desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
    desc.StructureByteStride = structsize;
    if (!CPUWritable && !GPUWritable)
    {
        desc.Usage = D3D11_USAGE_IMMUTABLE;
        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        desc.CPUAccessFlags = 0;
    }
    else if (CPUWritable && !GPUWritable)
    {
        desc.Usage = D3D11_USAGE_DYNAMIC;
        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    }
    else if (!CPUWritable && GPUWritable)
    {
        desc.Usage = D3D11_USAGE_DEFAULT;
        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;
        desc.CPUAccessFlags = 0;
    }
    else
    {
        //error!
    }
    ID3D11Buffer * pbuffer = 0;
    HRESULT hr = m_Device->CreateBuffer(&desc, pData, &pbuffer);
    return pbuffer;
}

上面是创建Structured Buffer的一个例子,注意这里使用了MISC Flag 表示这个资源要被作为Structured Buffer处理,其他的和前面的差不多,只有细微的区别。

需要View的Buffer—— Append/Consume Buffer

Append/Consume Buffer是Structured Buffer的一种特殊形式,它们都需要且只能和Unordered Access View 绑定,主要是为HLSL提供一种实现栈的手段,在创建Unordered View时,需要为他们提供特殊的Flag。在HLSL中,可以使用append()方法将元素Push入Buffer中,用consume()方法将元素Pop出来。
Append/Consume Buffer的使用:一个典型的应用就是粒子系统:在粒子系统中,一个Compute Shader Program每帧会从当前状态的粒子Buffer中用consume方法取出,然后经过一系列计算,再通过append()方法压入更新后的粒子Buffer中。
Append/Consume Buffer的创建:和Structured Buffer的创建方式类似,但是不需要考虑它是CPU还是GPU可写,因为它一定是GPU可写,并且Bind Flag一定是Unordered_Access和Shader_Resource。

ID3D11Buffer * Sy_Graphics::CreateAppendConsumeBuffer(UINT size, UINT structsize, D3D11_SUBRESOURCE_DATA * pdata)
{
    D3D11_BUFFER_DESC desc;
    desc.ByteWidth = size * structsize;
    desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
    desc.StructureByteStride = structsize;
    desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;
    desc.CPUAccessFlags = 0;
    desc.Usage = D3D11_USAGE_DEFAULT;
    ID3D11Buffer * buffer = 0;
    HRESULT hr = m_Device->CreateBuffer(&desc, pdata, &buffer);
    if (FAILED(hr))
    {
        return NULL;
    }
    return buffer;
}

注意,在创建UAV时,Format必须设置为DXGI_FORMAT_UNKNOWN,并且添加Flag:D3D11_BUFFER_UAV_FLAG_APPEND。

需要View的Buffer——Byte Address Buffer

Byte Address Buffer的目的是给HLSL程序提供更多的对内存操控的灵活度,它不采用以一个固定大小的结构体size来确定目标数据在资源中的下标,而是以通过一个偏移量来获取资源从开始位置经过这个偏移量后得到的四个字节的数据。
Byte Address Buffer的使用:使用Byte Address Buffer就不需要资源中的数据一定要是相同大小的,在HLSL程序中可以实现任何适用于在整个内存块内的数据结构,例如链表、二叉搜索树之类的,因此这种Buffer为HLSL程序提供了极大的灵活性。
Byte Address Buffer的创建:一个Byte Address Buffer必须使用MISC Flag :D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS,如果它只读,可以与Shader Resource View绑定,如果它要求可写,要与Unordered Access View绑定。

ID3D11Buffer * Sy_Graphics::CreateByteAddressBuffer(UINT size, bool GPUWritable, D3D11_SUBRESOURCE_DATA * pdata)
{
    D3D11_BUFFER_DESC desc;
    desc.ByteWidth = size;
    desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;
    desc.StructureByteStride = 0;
    desc.CPUAccessFlags = 0;

    if (GPUWritable)
    {
        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;
        desc.Usage = D3D11_USAGE_DEFAULT;
    }
    else {
        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        desc.Usage = D3D11_USAGE_IMMUTABLE;
    }
    ID3D11Buffer * buffer = 0;
    HRESULT hr = m_Device->CreateBuffer(&desc, pdata, &buffer);
    if (FAILED(hr))
    {
        return NULL;
    }
    return buffer;
}

不管绑定到哪种View,FORMAT必须设置为DXGI_FORMAT_TYPELESS,当UAV被使用的时候,要添加Flag:D3D11_BUFFER_UAV_FLAG_RAW。

Texture

只介绍一下一维纹理,纹理的内容不是很复杂,都是差不多的,简单的看看就行了。
一维纹理的使用:一维的Texture通常都是用来实现查找表,例如将一个特别复杂的函数替换为一个一维的查找纹理,那么就可以减少大量的计算。
一维纹理的创建

Struct D3D11_TEXTURE1D_DESC {
    UINT Width;
    UINT MipLevels;
    UINT ArraySize;
    DXGI_FORMAT Format;
    D3D11_USAGE Usage;
    UINT BindFlags;
    UINT CPUAccessFlags;
    UINT MiscFlags;
}

创建方法和创建Buffer的类似,都是提供三个参数,主要还是看DESC参数。
一维纹理的DESC中,Width表示纹理的大小,因为只有一维,就是那一维的大小;MipLevels指定纹理的在Mipmap产生的一系列纹理中的编号,如果为0就表示创建一个只有一个元素;Mipmap纹理链;ArraySize表示纹理的个数;其余的和Buffer中的类似。
一维纹理可以与四种View绑定。

之后的二维纹理、三维纹理其实都是差不多的,限于篇幅就不多说了。

猜你喜欢

转载自blog.csdn.net/yjr3426619/article/details/81130071