转发请注明出处

Doom3的渲染管线分为两个阶段,一个是前端渲染,一个是后端渲染。其中前端

渲染负责绘制简单的2D UI和提供解析场景树并将需要绘制的物体打包且用“渲染命令”
进行序列化,并排序然后送往后端进行渲染。后端渲染是真正进行渲染的地方,它接受
前端传来的“渲染命令”链表,然后逐一解析并调用OPENGL API进行渲染。
Doom3每帧都会调用session的UpdateScreen函数,该函数如下:

void idSessionLocal::UpdateScreen( bool outOfSequence ) 
{
...
renderSystem->BeginFrame( renderSystem->GetScreenWidth(), renderSystem->GetScreenHeight() );

// draw everything
Draw();

if ( com_speeds.GetBool() ) {
renderSystem->EndFrame( &time_frontend, &time_backend );
} else {
renderSystem->EndFrame( NULL, NULL );
}

insideUpdateScreen = false;
}

其中 renderSystem->BeginFrame 是往渲染的命令队列中加入“选择缓冲区命令”,然后
是调用 idSessionLocal自身的 Draw()函数,该函数是解析整个场景树,并将对应的命令放入
后端渲染命令队列中。然后调用 renderSystem->EndFrame 该函数,是运行所有在后端命令队列
中的命令,进行真正的渲染。下面开始讨论支持这种渲染机制的一些基础“构件”。

高效率的帧间临时内存分配器:

由于在渲染过程中,需要生成大量的临时性结构体与数据缓冲区,若这些临时性结构体都由
系统提供的new,delete,malloc,free 来在“帧”中分配,然后在帧结尾处释放的话会很没效率并且
会造成大量的内存碎片的产生(因为这些临时性结构体都比较小)。为了解决这个问题,Doom3提供
了一种帧分配内存器。该分配器是一个全局frameData_t的变量。该结构体声明如下:
typedef struct {
frameMemoryBlock_t *memory;
frameMemoryBlock_t *alloc;
srfTriangles_t * firstDeferredFreeTriSurf;
srfTriangles_t * lastDeferredFreeTriSurf;
int memoryHighwater; // max used on any frame
emptyCommand_t *cmdHead, *cmdTail; // may be of other command type based on commandId
} frameData_t;
其中与帧临时性内存分配有关的结构体还有:

typedef struct frameMemoryBlock_s {
struct frameMemoryBlock_s *next;
int size;
int used;
int poop; // so that base is 16 byte aligned
byte base[4]; // dynamically allocated as [size]
} frameMemoryBlock_t;
frameMemoryBlock_t这个结构真正持有,分配的内存块,其中size是为该节点分配的内存总大小一般为
1MB , used是表明该内存块的使用量。frameData_t的职责是维护了一个内存分配链表与后端渲染消息队列
frameData_t中的memory是内存分配的链表头,alloc指向了正在被分配的内存块节点。该分配器的策略是
分配一个内存块链表供每帧重复性使用,在每帧的结尾处调用R_ToggleSmpFrame该函数并不真正释放该链
表内存,而是将分配的内存块链表中的各各节点的used变量重置为0,然后更新全局frameData_t结构的
memoryHighwater 的变量,该变量标明内存最大的使用“高度”,也就是每帧中该内存块链表分配节点的实际
使用大小的总和。

该分配器的初始化是通过调用 R_InitFrameData()完成的。该函数会在堆中分配出全局frameData_t
变量,然后为该分配器初始化一个frameMemoryBlock内存块,并让alloc指针与memory指针指向它。
在每一帧中渲染函数都会调用R_FrameAlloc来分配一些临时空间用来存放用于渲染的临时结构体。该函数会
先检查alloc所指向的当前正在分配的内存块是否满足分配空间的要求,若满足则直接改写内存节点中的used
变量,然后返回分配的内存指针,若当前块大小不满足要求则先看该块下一个节点是否存在(注意这一点!
可能有人会问若当前alloc是当前正在分配的内存块,那哪来的下一个节点?block->next的指针难道不应该
为空吗?! 但别忘了为了实现快速分配,该内存块仅仅在帧结尾处调用R_ToggleSmpFrame进行“释放”但该
释放并不是真正意义上的在堆中释放,而是便利之前分配过的内存节点列表然后把所有内存块的used重置为0
这样也就实现了一次堆分配多次使用的功能,不用每帧都真正的在堆中分配或释放。)若存在则直接在下一个节点中
分配,若还不满足要求,那说明当前分配的内存大于整个块的最大值直接报错。若当前alloc没有后续节点
则分配之。然后更新全局frameData_t结构的alloc指向当前分配块,更新当前分配块的used成员变量。

该分配器的真实释放是通过调用R_ShutdownFrameData(),该函数是真正的释放函数。调用该函数会逐一
释放掉分配的内存块节点,然后释放掉全局的frameData_t结构。该R_ShutdownFrameData()函数会在系统结束
时才调用。这样这些分配出去的内存块可以在游戏中一直存在以备重复使用。

高效率的状态转换

OpenGL是个状态机,在渲染的过程中需要频繁切换各种渲染状态,这就造成了在函数调用时的效率低下。
关键一点不在于状态的变换,而是在于有可能当前状态就是想要变换到的状态而重复的调用函数。Doom3引擎
维护了一个名为glstate_t的结构体。该结构体里有个记录当前状态的数据成员 int glStateBits 该整型值
每一位都代表了一种状态。这样当某位置为时代表当前状态为开。例如 GLS_DEPTHFUNC_ALWAYS 为 0x00020000
也就是第18位置位代表 glDepthFunc(GL_ALWAYS)设置了。OpenGL的状态统一由GL_State(int stateBits),该
函数首先利用传入的stateBits与当前的状态glStateBits进行异或操作(XOR)得到一个diff 32位的值,这样
只要diff中某位为 1 则说明该位有变更,直接根据该位的指引变更相应的状态即可,这样就实现了真正变更
需要变更的状态,节省了效率。源码(Tr_backend.cpp 239行)。

渲染命令队列

Doom3维护着一个渲染命令队列,该队列每帧都会被重新分配。命令队列是单链表,他被安插在了frameData_t
结构体中:
typedef struct {
...
...
emptyCommand_t *cmdHead, *cmdTail; // may be of other command type based on commandId
} frameData_t;
由于该队列被每帧不断的重新分配,所以分配效率是个关键。同样该队列也不是用new ,malloc , delete , free分配
的,该队列的节点是通过上面讲到的帧分配器分配的。要往后端渲染器发布命令需要调用R_GetCommandBuffer(int bytes)函数
获得分配好的 渲染命令对象,该函数会先在帧分配器中分配一个渲染命令对象节点,然后自动将其挂接在命令队列队尾。
由于渲染命令对象是在帧分配器上分配的,故该对象同样无需释放。因为每帧帧分配器会自动“清理”分配出的所有内存。

Doom3 引擎渲染管线分析相关推荐

  1. 沈志勇-百度大数据引擎与分析预测

    2019独角兽企业重金招聘Python工程师标准>>> 沈志勇-百度大数据引擎与分析预测 大数据是目前全球关注的一个热点技术领域.百度大数据实验室作为百度大数据引擎的重要组成部分,以 ...

  2. osgEarth的Rex引擎原理分析(一二六)rex瓦片组织方式

    目标:(一二五)中问题212 通过如下确定瓦片的组织方式 ,核心是profile osgEarth/Map.cpp void Map::calculateProfile() {// collect t ...

  3. 目前主流游戏引擎的分析报告

    前言 游戏引擎之争就像编程语言之争一样,在游戏开发圈永远是一个火爆的话题,目前市面上主流的一些游戏引擎,我们来给他们做一些比较,了解他们的历史,特点, 为了严谨,备注一下写这个文章的时间编写时间是20 ...

  4. Java三大主流开源工作流引擎技术分析

    首先,这个评论是我从网上,书中,搜索和整理出来的,也许有技术点上的错误点,也许理解没那么深入.但是我是秉着学习的态度加以评论,学习,希望对大家有用,进入正题! 三大主流工作流引擎:Shark,oswo ...

  5. JQuery - Sizzle选择器引擎原理分析

    说明:14年学习的jquery源码,搬到这里供大家交流.原文地址:https://segmentfault.com/a/1190000003933990 一.前言 Sizzle原来是jQuery里面的 ...

  6. 三大主流开源工作流引擎技术分析与市场预测

    1.从<功夫>说起 时下的新新人类看到我,一定会认为在下是个十足的老古董,这不,<功夫>这样的片子我到今年2月底才看.不过看过<功夫>,我想的一定比一般的人多:周星 ...

  7. 网狐棋牌(三) 调度引擎初步分析

    相关UML: CAttempterEngine实现了两个接口:IQueueServiceSink.IAttemperEngine; 通过前面的分析,偶们了解到,IQueueServiceSink这个接 ...

  8. 微软SQLServer官方示例项目部署-数据引擎和分析服务部分

    微软SQLServer每个版本都会带有相应的示例项目,从2000时的Foodmart到2005之后的Adventure Works,里面的设计方法和规范都有很多我们值得学习的地方.不仅是做普通的开发, ...

  9. mysql源码分析——InnoDB引擎启动分析

    一.InnoDB启动 在MySql中,InnoDB的启动流程其实是很重要的.一些更细节的问题,就藏在了这其中.在前面分析过整个数据库启动的流程,本篇就具体分析一下InnoDB引擎启动所做的各种动作.在 ...

  10. osgEarth的Rex引擎原理分析(二十五)地形瓦片大小尺寸和LOD的关系

    目标:(十八)中的问题55 osgEarth::TerrainOption中_tileSize默认大小为17,LOD的默认范围为0-23,这两个值的关系是什么? 还有瓦片的像素尺寸_tilePixel ...

最新文章

  1. Xcode快捷键及代码块
  2. 计算各种形钢的重量用什么软件_造价常用工具不会用,30个常用工程算量工具免费送,速来领取收藏...
  3. 囊括三大视觉顶会,行人重识别新基准方法AGW!已被TPAMI录用
  4. python 的import m.a.b 和 from m.a import b的区别
  5. SessionAttributes介绍
  6. wpf 大数据界面_24小时删!WPF 界面开发可视化数据源500行代码分享
  7. 前端学习(1884)vue之电商管理系统电商系统之实现侧边栏的折叠和展开
  8. python-Day03
  9. Android传感器应用——重力传感器实现滚动的弹球
  10. JS调试的时候遇到无限debugger怎么办?
  11. j2se--Socket沟通
  12. sqlserver一个循环圆的算法
  13. Map2Shp地理格式转换器——专业mapgis格式无损批量转换工具_map2shp_新浪博客
  14. OpenGL红宝书正序解读(二)
  15. 【webGoat】Path traversal
  16. 黑马SQL入门到精通笔记 —— 进阶篇
  17. 一个屌丝程序猿的人生(五十七)
  18. CCF论文列表(2022拟定)大更新!NAACL升B!ICLR继续陪跑...MICCAI空降B!PRCV空降C!
  19. 数据结构整理笔记(提纲) (数据结构 C语言版 第二版 严蔚敏)
  20. post请求路径出错NET:ERR_CONNECTION_RESET

热门文章

  1. Win10任务栏图标变成空白方块解决办法
  2. XOM版本1.2.5
  3. three.js 场景编辑器 源码解析(二)
  4. 日志报错:WARNING: An illegal reflective access operation has occurred
  5. 股票涨或跌为什么?看懂本质,才能顺势而为!
  6. 【做小游戏在Godot中遇到的问题第一篇】
  7. python统计文章中高频词汇并生成词云
  8. Thief-Book v1.0.1免费版
  9. 数学之美笔录(3):隐含马尔可夫模型(详解)
  10. POJ 3422 - Kaka's Matrix Travels(最小费用流)