You are on page 1of 19

Vulkan

Loader

Loader 位于 应用程序与驱动之间。负责各种 layer 的工作。Vulkan 函数最终可能会调用一


组不同的模块:loader、layer 和 ICDs. Loader 负责派发 vulkan 函数到对应的
layers 和 ICDs。 Vulkan object 模型允许 loader 在 ICD 被调用之前插入 layers
调用链中来优先处理 vulkan 函数。

Layer

可以从应用程序到硬件影响 vulkan 函数执行。与功能无关的要的版本中需要禁用。

Installable Client Drivers

缩写:ICDs。每一个支持一个或者多个设备(VkPhysicalDevice ).loader 负责找出可用的

该设备。即 ICDs 可用表示当前设备锁支持的驱动。

DiligentEngine 分析:

Windows 平台下:人口函数 WinMain 函数。


1 初始化 app 类对象然后处理命令行参数:
通过 if (!(Arg = GetArgument(pos, "mode")).empty())
判断当前使用的 渲染 api 保存到 app 对象成员 m_DeviceType 中。

2 注册创建窗口。

3 动态加载渲染 api :
合并出动态 dll
sprintf_s(LibName, StringBufferSize, "%s%s%s.dll", EngineName, Arch, Conf);
hModuel = LoadLibraryA(LibName); //windows 32 加载 dll 库
GetFactoryFunc = GetProcAddress(hModuel ,GetFactoryFuncName)
4 创建 比较重要的类 渲染 API 工厂

5 创建 contetx 和 运行设备
运行时候检查错误:
#define VERIFY (EXPr,Message,...)
If(!EXPr)
ASSERTION_FAILED(Message, ##_VA__ARGS__);

创建内存分配器
SetRawAllocator(EngineCI.pRawMemAllocator);

//创建 shader

RefCntAutoPtr<IShaderSourceInputStreamFactory> pShaderSourceFactory;
m_pEngineFactory->CreateDefaultShaderSourceStreamFactory(nullptr,
&pShaderSourceFactory);
ShaderCI.pShaderSourceStreamFactory = pShaderSourceFactory;
通过创建 IShaderSourceInputStreamFactory 实现跨平台访问 shader 文件。

SRB
Shader resouces binding 必须创建一个用来绑定 shader 资源。Mutable 资源通过 SRB 进行设
置。SRB 包含了一个物体所有的资源。

Shader Resouce binding

Dx12 中,最主要的设计目标是实现可以频繁的改变的资源并且对 cpu 不会产生负荷。在 dx11 中


shader 资源被直接的绑定到 shader 寄存器,实现的 api 是
PSSetShaderResources(),PSsetConstantBuffers(),PSsetSamplers 和
CSSetUnorderedAccessVies ().

1pCtx->PSSetShaderResources(0, NumSRVs, ppSRVs);
2pCtx->PSSetConstantBuffers(0, NumBuffers, ppCBs);
3pCtx->PSSetSamplers(0, NumSamplers, ppSamplers);
4pCtx->CSSetUnorderedAccessViews(0, NumUAVs, ppUAVs, nullptr);

但是这个模型没有考虑到帧与帧之间资源的关联性。比如某一帧 N 使用了 8 个贴图和 2 个 constant


bufers。很有可能下一帧也会用吧同样的贴图和 constant buffers。Dx12 中会缓存这些资源的绑
定因此如果下帧需要这些资源会立即被设置绑定成功而不是像 dx11 中那样再需要一个个绑定。那么
dx12 是如果解决的呢?Dx12 通过一个叫做 descriptors 的东西来引用这些资源,并存储到叫一个
叫做 Descirptor 堆的东西上面。Descriptor 是 GPU 上一个相对比较小的数据块,虽然小但是也能
描述出一个资源。而 Descirptor 堆本质上是一个 descriptor 数组。每一个渲染管线状态都包含一
个叫做根签名的东西,它定义了 shader 寄存器如何映射到 descriptor 堆中。因此资源绑定分为
两个阶段:
1 shader 寄存器如何通过一个根签名映射到 descriptor 堆中。
2 这个 desriptor(可能是 SRV,UAV,CBV,Sampler 等)然后引用 GPU 内存资源。下面的图描述
了 dx12 中资源绑定模型。
值得注意的是实际上每一个 shader 阶段引用的是一系列的在 descirptor 堆中叫做 descriptor
tables 的东西。仅需要切换 descriptor table 就可以改变绑定的资源,这个操作一般很方便而且
性能高。但是在某些硬件上可能会引起 GPU 停顿一下来刷新所有的依赖当前 descriptor 堆的所有
进行的工作。Dx12 中有两个 descriptor 堆类型能够被 shader 引用:
* D3D12_SRV_UAV_CBV_DESCRIPTOR_HEAP can hold Shader Resource Views, Constant Buffer
Views, and Unordered Access Views
* D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER can only store sampler descriptors
对比 dx11,dx12 需要引用程序负责资源的生命周期管理和驻留管理。引用程序必须保证在绘制或者
计算命令完成之前,所有引用到 descriptor 堆中的所有资源必须是有效的。

杂项:待整理

...

進行到
判断數量計算錯誤。

来做库平台区分:

Hlsl 转 glsl 用

Hlsl 转 vulkan 着色器用


c++ 拾遗

1 左值 右值 将亡值 存右值
//https://stackoverflow.com/questions/25312225/c-why-decltype-this-returns-a-
reference decltype(x) 如果 x 是左值,会返回&x 左值引用,如果 x 是 xvalue,就会返回
&&x 可移动。 左值就是驻留在内存中的,可以取出地址的表达式。
//xvlue 是将亡值,可以被移动的表达式 ,prvalue 纯右值。
所以 Allocator 是一个左值,会返回一个引用,因此当类型使用时候需要解除引用。
Diligent 引擎中创建一个 object 时候用法。
#define NEW_RC_OBJ(Allocator, Desc, Type, ...) MakeNewRCObj<Type, typename
std::remove_reference<decltype(Allocator)>::type>(Allocator, Desc, __FILE__,
__LINE__, ##__VA_ARGS__)

} // namespace Diligent

1)++i 是左值,i++是右值。
      前者,对 i 加 1 后再赋给 i,最终的返回值就是 i,所以,++i 的结果是具名的,名字就
是 i;而对于 i++而言,是先对 i 进行一次拷贝,将得到的副本作为返回结果,然后再对 i
加 1,由于 i++的结果是对 i 加 1 前 i 的一份拷贝,所以它是不具名的。假设自增前 i 的值是
6,那么,++i 得到的结果是 7,这个 7 有个名字,就是 i;而 i++得到的结果是 6,这个 6
是 i 加 1 前的一个副本,它没有名字,i 不是它的名字,i 的值此时也是 7。可见,++i 和 i+
+都达到了使 i 加 1 的目的,但两个表达式的结果不同。
2)所以++i 效率更高、
2 lambda 表达式作模板参数,不需要再函数做声明,会自动把 lamda 看作为模板参数:
SPirv 生成:

主要方法:CompileShaderInternal
经过一系列处理后真正解析的位置:
Spirv 添加逻辑

前面 5 个字符表示
// Header, before first instructions:
out.push_back(MagicNumber);
out.push_back(spvVersion);
out.push_back(builderNumber);
out.push_back(uniqueId + 1);
out.push_back(0);
Spirv 解析:

先存储着色器代码为指令 Instruction 有 op count offset length 等变量,思想是:记录偏移和


当前占用的数量。
偏移地址从 5 开始:先通过偏移值找到当前指令,值不能超过 65535
然后通过右移动 16 位刚好值是 65535,记录当前存储的指令总长度。

接下来解析指令:

Stream 的 作 用 是 返 回 当 前 指 令 开 始 的 地 址 。 并 保 证 指 令 数 不 会 越 界 , 否 则 抛 出
std::runtimeError。

看到这里发现,指令集中的 op 对应着一个 Op 枚举类,最多有 6035 个有效指令。


以 OpExtension:枚举为例

String result;
从当前指令的偏移地址算起:直到整个指令结束为止:
取出当前指令
For( j=0; j<4; ++j, 当前指令值右移 8 位 )
{
Char 指令字符 = 当前指令 & 255
如果 指令字符为空 返回 result
否则 累加指令支付到字符串 result
}
经过两层循环,外层取出指令,内层循环对当前指令做四次字符取值,这样直到返回空格
该次解析终止。

如果想查看生成的 spirv 二进制数据内容,需要执行反编译:


命令:spirv-dis fileName

代码解析

具体执行位置:

解析完毕后执行反射
具体参考 https://github.com/KhronosGroup/SPIRV-Cross/wiki/Reflection-API-user-guide

Spirv:调试判断
Spirv 如果想调试成功必须开启:device 必须开启 SPV_KHR_non_semantic_info 扩展
开启成功后编译的 spirv 代码:里面有黄色的内容字样。 hello 是我打印的字符串。

先会统一执行:

然后 走判断是否保护字符
接着把

实际在使用过程中会进行各种优化:如果发现灭有打印成功,那么就得检查一下是否开启
了优化:
最终打印成功:

创建新的控制台窗口:

AllocConsole(); //create console


SetConsoleTitle(L"SHMRenderDebugConsole"); //set console title
FILE* tempFile = nullptr;
// freopen_s(&tempFile, "conin$", "r+t", stdin); //reopen the stdin, we can
use std::cout.
//重定向输出流
freopen_s(&tempFile, "conout$", "w+t", stdout);
std::cout << "创建控制台窗口成功!!!!" << std::endl;
最终版本:
https://stackoverflow.com/questions/311955/redirecting-cout-to-a-console-
in-windows
Spirv :生成的可阅读的性的代码,形如:

语法类似与 LLVM IR。


中间表达式 IR( Intermediate Representation),或者叫 IL(Intermediate Language),

如 DX 中间编译语言 DXIL

其中最重要的是:

Error:
检查内存泄露创建 runtime 类

通过 NEW_RC_OBJ 创建出需要的类对象,传递进去要创建的对象类型,以及一些参数。
RenderDeviceVkImpl:要创建目标类
RawMemAllocator:内存分配器对象。

NEW_RC_OBJ:

代码为
//https://stackoverflow.com/questions/25312225/c-why-decltype-this-returns-a-reference
decltype(x) 如果 x 是左值,会返回 &x 左值引用,如果 x 是 xvalue,就会返回&&x 可移动。 左值就是驻留在
内存中的,可以取出地址的表达式。
//xvlue 是将亡值,可以被移动的表达式 ,prvalue 纯右值。

#defineNEW_RC_OBJ(Allocator,Desc,Type,...)MakeNewRCObj<Type,typename
std::remove_reference<decltype(Allocator)>::type>(Allocator,Desc,__FILE__,__LINE__, ##__VA_ARGS__)

MakeNewRCObj:接受传递的 type 和内存分配器类,


m_dvpDescription:类描述
m_dvpFileName:文件名称
m_dvpLineNumber:调用改方法的文件的行号。

由于该 MakeNewRCObj 的()方法被重写了,检查内存分配器创建的话就通过 operator new 来构建


出改类,否则直接 new 创建出来。然后把参数,这里指:

这些参数。


根据 operator new 方法中会自动计算出当前对象的 size,也可以可以用来打印当前对象的
大小。

class car
{
string name;
int num;
public:
car(string a, int n)
{
cout << "Constructor called" << endl;
this->name = a;
this->num = n;
}
void display()
{
cout << "Name: " << name << endl;
cout << "Num: " << num << endl;
}
void* operator new(size_t size)
{
cout << "new operator overloaded" << endl;
void* p = malloc(size);
return p;
}
void operator delete(void* ptr)
{
cout << "delete operator overloaded" << endl;
free(ptr);
}
};
ar* p = new car("HYUNDAI", 2012);
修改向 operator new 添加参数 int
void* operator new(size_t size,int id)
{
cout << "new operator overloaded" << endl;
void* p = malloc(size);
return p;
}
调用方式在 new char 中间添加参数即可 car* p = new (1)car("HYUNDAI", 2012);

UE4 资源注册方式:
声明全局的资源
TGlobalResource<FFilterVertexDeclaration> GFilterVertexDeclaration;
InitResouce 里 面
把资源添加到
static

TArray<FRenderResource*> RenderResourceList; 里面。

You might also like