转载请注明:http://www.cnblogs.com/vertexshader/articles/5225675.html,欢迎入群54288273一起扯淡。
前序的什么环境配置等工作就不总结了,所有的资料和SDK都汇总在这个地址,按照要求安装和配置就可以了。
吐槽优先!
万事开头先吐槽,Vulkan SDK简直就是日了狗了我想说,函数的名称终于达到了又长又臭的地步,以至于Demo代码看起来是这样的:
1 err = vkEnumerateInstanceLayerProperties(&instance_layer_count, NULL);
一个函数的名称就达到了35个字符,这还是短的,还有更长的,加加参数一个函数就可以占几行了。
到底做了啥?
这次Vulkan不像OpenGL一样是无SDK的,它妥妥的出了一个叫做Vulkan SDK的东西,似乎我们只需要include这个头文件,link这些lib就接入了Vulkan的SDK。不过我在Demo里看到了一些函数仍然需要通过vkGetInstanceProcAddr的方式获取函数指针,这时候我就对这个SDK的行为产生了怀疑和兴趣。
回顾一下以前了解OpenGL的经验,OpenGL Runtime是由不同的Manufactory所实现的ICD(Installable Client Driver)来支持的。通过载入默认OpenGL库的方式,获取相应的函数指针,完成整个API的初始化的。这个不同于Direct3D,由于Direct3D的封闭性,其Runtime是在操作系统层面实现的。而Vulkan又是OpenGL的继承者,又是由一大堆的Manufactory在背后默默地支持,可以充分地怀疑其应该是通过和过去OpenGL类似的方式获取函数指针实现的。
还好Vulkan SDK是开源的,果然不假,搜索整个代码在vk_loader_platform.h发现了:
typedef HMODULE loader_platform_dl_handle; static loader_platform_dl_handle loader_platform_open_library(const char *libPath) { return LoadLibrary(libPath); }
打上断点,看看搞了毛:
loader_platform_open_library(const char * libPath) loader_scanned_icd_add(...) loader_icd_scan(...) vkCreateInstance(...) main()
而这时候libPath的值是“C:\\Windows\\System32\\nvoglv32.dll”,追溯上游,发现其在函数vkCreateInstance中调用了loader_icd_scan这个接口,而这个接口又调用了loader_get_manifest_files这个接口,
loader_get_manifest_files内部有一代码是:
loc = loader_get_registry_files(inst, loc);
这个loader_get_registry_files则是调用了RegOpenKeyEx这个接口,也就是从注册表中获得键值,而loc的值则是预定义的宏“SOFTWARE\\Khronos\\Vulkan\\Drivers”。打开注册表,看看这个值到底是个啥哇:
指向了一个json文件,打开这个json文件看看其中内容:
{ "file_format_version" : "1.0.0", "ICD": { "library_path": "nvoglv64.dll", "api_version" : "1.0.4" } }
原来被骗了!
之前的json文件原来指定了库的名称和API的版本,那么nv-vk64.dll和nvoglv64.dll到底有什么接口呢,可以直接打开dll查看其接口和偏移:
是不是有很多vk的接口?但是只有一个接口是核心的,就是vk_icdGetInstanceProcAddr这个接口,在后续的loader_scanned_icd_add调用中,代码从载入的库中获得了这个函数指针,然后干了一件事情:
fp_create_inst = (PFN_vkCreateInstance)fp_get_proc_addr(NULL, "vkCreateInstance");
原来是创建了入口的函数指针实例!说好的vkCreateInstance接口,也就是包在外面的一层,并不是真正的vkCreateInstance。那么也就是说整个流程就是,Vulkan Loader扫描注册表获得相应的json文件,载入这个json文件,载入dll,获得入口函数指针,载入相应函数指针。也就是说这套方法仍然和过去OpenGL的方法一致。而这个载入过程,通过检查函数调用,也会在vkEnumerateInstanceExtensionProperties等接口中调用。而SDK中的接口,都是对函数指针包了一层,例如vkCreateBuffer:
LOADER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateBuffer(VkDevice device, const VkBufferCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkBuffer *pBuffer) { const VkLayerDispatchTable *disp; disp = loader_get_dispatch(device); return disp->CreateBuffer(device, pCreateInfo, pAllocator, pBuffer); }
整个VkLayerDispatchTable就是函数指针的集合。
为啥这么干?
那么这么做的意义是什么呢?直接调用这些函数指针不就完事了吗?恰巧vulkan的一个思想就是,去除掉验证层,那么验证层能放在何处?也就是放在这个SDK里,类似各种Graphic Debugger的实现,Vulkan有很多Validation Layer,可以根据需求载入,在真正的函数调用之前,截取一些信息,获得一些信息,最后再调用真正的接口。也就是说整个SDK不仅仅是一个函数指针获取器,还是一个Graphic Debugger。