|
如之前的文章所说,下面三个部分是blender中最核心的代码,第一次记录会比较零散。
source/blender
source/creator
source/tools
在阅读源码过程中,有大量的宏定义。这一些在代码预编译阶段就做了展开,给我们debug增加了难度,但是便于参数调节、增加运行效率等等。这一些优势,反正我开始读的时候是没有感觉到的,希望之后可以感觉到。
简单介绍一下,最常用的宏定义的一些内容。
1. 简单的可以分为带参数、不带参数的宏定义两种。对于后者,比如
#define PI 3.1415926
2. 我们把PI作为3.1415926的替代对象。对于前者,比如
#define B(x,y) x##y
3. 定义了B(x,y),提供类似函数的功能,但是在预编译时而不是运行时,就做到把x和y链接起来。常用的有四种特殊符号,#、##、#@、\
1. #: 把#之后的内容变成字符串;
2. ##:将前后两个对象拼接在一起,变成一个;
3. #@:将后面一个内容,变成单个字符,类似于加上单引号;
4. \: 一行不够,把下面一行内容也作为#define的内容
第一次读代码,目前就想到哪里,就写到哪里了;
我在使用中感觉到,需要Visual Studio 和Visual Studio Code 配合,看源码+调试会更方便。
从入口函数所在模块开始 source/creator 里面内容很少,从Visual Studio的解决方案中也可以看到。
其中,buildinfo.c 这个文件,是在CMakeLists阶段生成的。具体内容如下所示,通过VSCode在整个项目中搜索关键字可以找到,在 build_files\cmake 中,将hash、date等内容写入到这个特殊c文件中。在编译阶段,这个c文件中的内容就被读取了。
下图是cmake中的部分代码
主入口函数在creator.c 中,这个文件不大,所以我们从头开始看。
blender在各个文件都,都做了一些自己操作的封装,比如像这种存粹是输出字符串内容到标准错误通道的例子;
fflush是为了清buffer,保证之前内容先输出完。否则会出现多次输出顺序与调用顺序不一致的问题。类似的一些函数,我第一次遇到的就介绍一下,相似的就不做记录了。
随后遇到的新面孔是一个在blender中非常常见的结构:一个函数指针,或者一个宏定义,加上一个函数指针或者宏定义作为参数。没有做宏展开或者具体看指针指向的函数要干嘛之前,这种结构真的是让源码读者很无奈。这边我第一次遇到,就展开介绍下。
MEM_set_error_callback 是一个函数指针,指向一个返回值为void,入参为函数指针的函数。显然在blender中,这个指针直接指向了 MEM_lockfree_set_error_callback 。 而MEM_lockfree_set_error_callback很明显是在某个private的地方定义的函数。
搜了一圈看到,MEM_lockfree_set_error_callback 的唯一作用,是把入参的函数指针,传给一个内部定义的函数指针。所以转了一圈,经过3个调用函数,main_callback_setup的作用,就是使得在creator.c中定义的函数 callback_mem_error, 传给内部的error_callback。
乍一看,是肯定会疑惑就这么一个操作为什么需要这么多次的函数调用呢?可能的理由是,这种多模块之间的接口调用,都需要通过模块之间相互暴露接口、通过接口访问来实现,所以中间就有这一些步骤,很合理。
到了主函数中,抛开变量的定义,首先做的是注册在blender还没有加载完全时就退出这种情况下的资源释放函数。从我的角度看,BKE_blender_atexit_register 就是做这件事情了。
随后通过 CommandLineToArgvW(GetCommandLineW, &argc) 来获取命令行参数,并提供给argv。
这是WIN32的获取命令行参数的方式,在其他OS中直接通过argv就可以拿到参数了。随后有一些为了特殊模式做的特殊初始化,比如对于debug模式要做从无锁分配器到完全保护分配器的切换(具体意义是什么并不清楚)。对于日志信息的初始化,也在这部分做,日志信息是目前看到在blender的context初始化之前,最后一个初始化的内容。这个很合理,毕竟日志模块需要记录blender的上下文以及其他模块的各种日志信息。
在blender中,有几个特殊的全局变量,上下文context是其中一个。在整个源码中,都用大写字母C来表示,确实是很随意。而在做context内存分配时候,则使用了blender内建的MEM_callocN。这个分配函数所在的模块为 blender\intern\guardedalloc 。blender\intern 与 blender\source 同级。在blender中这种自定义的内存分配随处可见,目的在于做类似单例的实现。以MEM_callocN为例,入参分别为待分配字节数量,以及一个静态字符串。被分配的内存与这个静态字符串绑定。
在上下文创建之后,准备开始做各个子模块的初始化了。看起来比较有意思,比如最开始初始化,就首先找到当前自己所在完整路径。这一系列初始化,都使用了blender自定义的内存分配函数,构建了一堆“内存空间”-“静态字符串” 的映射。这部分内容相对比较繁杂,也没有深入看的意义。可以认为,几乎所有与启动相关的、子系统相关的初始化,都在这边执行了。需要注意的是,如果是子模块的初始化函数的话,子模块内部几乎都还有一堆初始化要做。这块我们先跳过。
在大量初始化之后,需要注册程序退出时调用的回调函数,以及在main最后做了一次是否为background执行的判断。一般的,对于background执行方式,所有计算,都已经在上述初始化之内,以及CPython中执行完毕了,此时会直接退出。之所以能直接退出,是因为所有必要模块初始化完成之后,background执行的python脚本任务,已经在同步的CPython的执行逻辑中,执行完毕了。对于非后台任务模式,需要以Editor模式来运行(Editor模式相关的源码,几乎全部集中在 /source/blender/editors 目录下,46+个模块)。此时会进入WM_Main(C)。 这个定义在 source\blender\windowmanager\intern\wm.c 中的函数,才是正常我们看到的blender页面的渲染循环的入口。
下图是WM_Main的事件、渲染循环代码,其中的逻辑,主要是为了做UI操作的监听,以及监听之后的事件触发,以及事件通知,渲染绘制更新。Blender的代码严格遵循MVC模式。具体的,Model为Blender中实现的各种功能,View就是与用户交互的界面,Controller就是接受用户的交互并将需要的操作告诉Model,以及把Model计算结果反馈给View来展示给用户。从循环中也可以看出来,Model的数据更新在前,View的更新在最后。
到此处为止,我们已经把blender入口以及整个运行tick部分的地方找到了,下面针对性查询感兴趣的模块的源码就行。
通过官方提供的文件夹结构与说明,记录以下我比较感兴趣的一些模块地址:
- Object级别的修改器;
- /source/blender/modifiers/
- /intern/dualcon/
- 渲染模块
- 底层渲染
- /source/blender/draw/
- /source/blender/render/
- /intern/cycles/
- 渲染在editor部分的节点交互
- /source/blender/editors/space_node/
- /source/blender/nodes/
- 渲染与editor的交互
- UV Editing部分
- Mesh级别的Edit操作
来源:http://www.yidianzixun.com/article/0pvfgT5f
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|