关键词:三角形冲孔网
标题:Salvia杂谈(一)——冲孔板ktv保序的三角形
来源:知乎
文章内容: 并行化
光栅化软件渲染器的基本理论和基础实现都非常简单,知乎上有很多人都做过,比如 @萧井陌 和 @韦易笑 。也因为它的趣味性,成为了很多大学的课程项目之一,如Assignment 1: Implementing a Parallel Sort-Middle Tiled Renderer : 15-869 Fall 2014。这个课程很好,几乎涵盖了所有写软件渲染器需要用到的资料。Chaos(知乎这破at的功能) 也曾在他的项目团队中,将光栅化渲染器作为新员工的培训项目,以此熟悉图形学。
如果不算一些以教学为目的的简洁实现,国内也有很多高水平的项目,比如冲孔板ktvGameKnife的 gameknife/SoftRenderer · GitHub ,和@Yong He的RasterRenderer。
这些项目只要不是以教学为目的,通常或多或少都会做一些优化措施,例如SIMD优化和并行化。SIMD化通常不影响计算结果,因此算是白捡的便宜,所以本文主要讨论并行的问题。
在整个渲染阶段中,*直观的并行化方式,要属针对三角形的并行化,即每个线程独立完成一个三角形的构建、顶点变换、裁切、光栅化和像素着色。
它的优点很明显:
自顶向下分解,代码简单;
在三角形大小均匀、相互遮盖较少的情况下并行度很高;
顶点变换、插值和纹冲孔板ktv理读取对Cache比较友善。
缺点也很明显:
三角形的绘制顺序不固定;
在frameBuffer上,可能会出现错误的写顺序,如果要加锁,也会有一定的性能损失;
并行粒度偏大,如果三角形数量少且大小很不均匀,并行度就会变得很差;
因为不同的三角形所覆盖的屏幕可能截然不同,因此frameBuffer的读写操作上,Cache命中率可能有问题。
另一种常见的并行化方案,解决了Cache和并行粒度的问题:
并行化的处理三角形;
将屏幕分冲孔板ktv成多个Tile,将三角形分配到块中;
每个块有一个线程(当然也有线程池),对块上的三角形光栅化。
这个方案的问题是,三角形在并行分块的时候,可能会有多个三角形加到同一个屏幕空间的Tile中,有同步的问题;另外,
三角形绘制顺序不固定
的问题也没有得到解决。
保序的意义
所谓保序,就是指三角形的绘制顺序必须和索引缓冲或顶点缓冲中的图元顺序一致。在列表前面的图元要先画,后面的图元,要后画。
那么,为什么这里要强调“三角形绘制顺序”呢?原因是,保证同样的渲染程序,多次绘制的结果是相同的。举个例子,比如有两个三角形A、B互相覆盖,且深度相同。这时,无论你的深度比较函数是什么,如果三角形绘制时不能保序,那么便可能会出现两种结果:
更惨的是,即便你使用相同的函数绘制同一帧,这两种结果都是随机出现的。这样你渲染出来的图像就是不停闪烁。
尽管在通常情况下(无混合,开启了Depth Test),这个问题不会大面积发生,但是作为图形标准来说是万万不能埋下这种定时炸弹的。
如何保序
软件保序的手段比较灵活:
三角形的生成和光栅化都是串行的;
三角形并行生成,并行光栅化,并行分配到块中,但是分配完了后,在渲染Tile之前,将所有Tile内的三角形排序;
认为渲染顺序不重要,不保序。
SALVIA的保序基本上就是第二种方法,靠排序来完成顺序化,这是SALVIA本身的设计目的所决定的,就是在管线上尽可能符合标准。下一篇会分析一下SALVIA的实现和潜在的性能问题。但是对于大多数软件渲染器而言,它们的目的并不是通过WHQL,也不用做成产品,因此多选择方案3,也就是干脆不保序。
对于硬件而言,由于排序本身是不太可能实现的,因此能选择的方案就少多了。但是硬件有一个法宝,那就是流水线化以增加吞吐量。所以硬件的做法就是,串行的生成三角形,但是每个三角形生成速度极快,理想状况下,平均每个三角形可以以接近1个cycle的速度生成冲孔板ktv。在现代GPU应用的典型场景下,每个三角形在像素着色上,动辄消耗几十、几百甚至数千个Cycle,三角形的生成本身,几乎不会作为瓶颈出现。
当然,并不是说三角形生成和光栅化就一定不会成为瓶颈。如果有大量细碎的小三角形,或者任意使用GS和Tessellation,仍然有可能会在Triangle Setup上吃瘪,这种问题,通常都要在Application层上解决了。
致谢
SALVIA现在的并行化方案是@叛逆者所创建的,我进行了一些重构和优化冲孔板ktv;
本系列受到了@Milo Yip的RapidJson系列文章的启发。