[软件渲染器入门]四-额外章节,使用技巧和并行处理来提高性能

本文主要是介绍[软件渲染器入门]四-额外章节,使用技巧和并行处理来提高性能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

译者前言:

本文译自MSDN,原作者为David Rousset,文章中如果有我的额外说明,我会加上【译者注:】。

 

正文开始:

在之前的教程中,我们学习了如何通过C#、TypeScript、JavaScript使用光栅化和深度缓冲来填充我们的三角形。由于我们的3D软件渲染引擎使用的是CPU运算,因此它将耗费大量的CPU处理时间。不过倒是有一个好消息,那就是CPU大多是多核心的。那么,我们可以想象一下使用并行处理来提高引擎性能。不过我们只能在C#中这么做,至于为什么Html5不可以,我将会稍后做出解释。我们在此篇章中可以学到一些简单的技巧,以此达到优化渲染性能的目的。而且实际上,我们也得到了从5 FPS提升到50 FPS的结果,这可是10倍的性能提升!

 

本章教程是以下系列的一部分:

1 – 编写相机、网格和设备对象的核心逻辑

2 – 绘制线段和三角形来获得线框渲染效果

3 – 加载通过Blender扩展导出JSON格式的网格

4 –填充光栅化的三角形并使用深度缓冲

4b – 额外章节:使用技巧和并行处理来提高性能(本文)

5 – 使用平面着色和高氏着色处理光  

6 – 应用纹理、背面剔除以及一些WebGL相关

 

计算FPS

首先第一步我们需要知道FPS,这是性能体现的一个重要指标。我们不论在C#、TypeScript或JavaScript中都可以实现FPS的计算。

 

我们需要知道渲染两帧之间的时间间隔。然后我们只需要捕获当前时间,然后绘制一个新帧(HTML5中的requestAnimationFrame,在XAML中是Composition.Rendering),然后再次捕获当前时间并将其与之前捕获的时间进行比较。然后你将得到以毫秒为单位的结果。要得到FPS值只需要将这个结果除以1000,例如,如果他是16.66毫秒,即最佳的增量时间,那么就是60帧每秒。

 

你可以在每一帧渲染后都进行这样的操作,以获得非常精确的FPS值。David和我已经通过基准测试专题探讨了这个话题。

 

综上所诉,在C#中,添加一个新的XAML TextBlock元素,将其命名为"fps",用于显示当前FPS值,通过以下代码来计算FPS值:

 

DateTime previousDate;void CompositionTarget_Rendering(object sender, object e)
{// Fpsvar now = DateTime.Now;var currentFps = 1000.0 / (now - previousDate).TotalMilliseconds;previousDate = now;fps.Text = string.Format("{0:0.00} fps", currentFps);// Rendering loopdevice.Clear(0, 0, 0, 255);foreach (var mesh in meshes){mesh.Rotation = new Vector3(mesh.Rotation.X, mesh.Rotation.Y + 0.01f, mesh.Rotation.Z);device.Render(mera, mesh);}device.Present();
}


我在我的联想 Carbon X1 Touch(分辨率为1600x900)电脑上运行前面的C#版示例,平均每秒5 FPS。我的联想电脑CPU为Intel Core I7-3667U(4个逻辑处理器)以及一个HD4000核心显卡。

 

 

优化和并行处理策略

WinRT应用程序使用.Net Framework4.5版本,其中默认(TPL)包含了任务并行库(Task Parallel Library)。如果你留心写算法的方式,并且你的算法可以并行处理,多亏了TPL使并行化处理将变得非常简单。如果你不了解这个概念,可以先看看并行处理的.Net Framework4: 入门

 

避免接触UI控件

多线程/多任务的第一条规则是没有一点关于UI控件代码存在。只有UI线程可以接触/操控图形控件。但在我们的例子中,有一段代码访问到bmp.PixelWidth和bmp.PixelHeight。bmp是WriteableBitmap类型,它被认为是一个UI元素,而不是线程安全的。这就是为什么,我们需要先修改这些代码才能使他们“并行”。在之前的教程中,我们就已经开始这么做了,那就是将bmp中的PixelWidth和PixelHeight的引用全部更改为renderWidth和renderHeight就可以了。

 

顺便说一句,这个规则不仅是并行处理所必须的,也是一般的性能优化。因此,通过简单的缓存机制把WriteableBitmap转移到我们的变量中便可以达到从5 FPS提升到45 FPS

 

在Html5中这个规则也是非常重要的。你应该避免直接访问DOM元素的属性,因为DOM操作非常慢。所以,它并不以16毫秒每帧的运行速度在运行。我们最好始终缓存接下来要使用到的值。不过我们已经在之前的教程中这么做了。

 

自给自足

第二个原则是,代码将在多个可用核心中自给自足。你的代码不能等待很长时间,否则将抵消并行处理的优势。不过幸运的是,在我们的例子中,一直遵循着这个规则。

 

你可能已经看到,我们有几个地方用Parallel.For替换了经典的循环以此来并行处理。

 

第一个例子是DrawTriangle方法中。我们将用并行处理用几条线画三角形。在此,你可以很容易的将2个正常循环替换为Parallel.For循环:

 

if (dP1P2 > dP1P3)
{Parallel.For((int)p1.Y, (int)p3.Y + 1, y =>{if (y < p2.Y){ProcessScanLine(y, p1, p3, p1, p2, color);}else{ProcessScanLine(y, p1, p3, p2, p3, color);}});
}
else
{Parallel.For((int)p1.Y, (int)p3.Y + 1, y =>{if (y < p2.Y){ProcessScanLine(y, p1, p2, p1, p3, color);}else{ProcessScanLine(y, p2, p3, p1, p3, color);}});
}


但是,在我的情况下,输出的结果有一点点出乎意料。我的性能被降低了,竟然从45 FPS跌到了40 FPS!那么,是什么原因导致了这个问题呢?

 

 

那么,出现这样的情况,显然不是使用并行处理就够了。我们需要花更多的时间用于上下文切换,也就是从一个核心移动到另一个核心而不是真正并行处理。你可以使用Visual Studio 2012的嵌入式分析工具:Visual Studio 2012并行可视化来查看。

 

下面是第一种并行方式的核心利用图:

第一种并行方式核心利用图

各种颜色都与工作线程有关,这真的没有效率可言。下面再看看非并行版本的核心利用图:

非并行版本核心利用图

我们只有一个线程在工作(绿色的),所显示的是由操作系统派出的一些核心。即使我们不明确使用并行,CPU也会自动使用多核心处理。显然我们的第一种并行方案上下文切换太频繁了。

 

互斥锁和适当循环的并行处理

嗯,我猜你和我得出了同样的结论。并行化的drawTriangle循环方案似乎并没有成为一个很好的选择。我们需要另外的东西来让线程切换更加高效。我们要并行处理多个三角形,而不是绘制一个三角形。总之,每一个核心都要绘制一个完整的三角形。

 

这种方法的问题将会在PutPixel方法中出现。现在我们要并行处理几个面,我们可能会陷入2个内核/线程试图并行访问同一个像素的情况。这时,我们只需要保护要访问的像素就可以了。事实上,如果我们用更多时间去保护正在工作中的数据,并行化将会非常有用。

 

解决的办法是使用一个锁:

 

private object[] lockBuffer;public Device(WriteableBitmap bmp)
{this.bmp = bmp;renderWidth = bmp.PixelWidth;renderHeight = bmp.PixelHeight;// 后台缓冲区大小值是要绘制的像素  // 屏幕(width*height) * 4 (R,G,B & Alpha值)  backBuffer = new byte[renderWidth * renderHeight * 4];depthBuffer = new float[renderWidth * renderHeight];lockBuffer = new object[renderWidth * renderHeight];for (var i = 0; i < lockBuffer.Length; i++){lockBuffer[i] = new object();}
}// 调用此方法把一个像素绘制到指定的X, Y坐标上
public void PutPixel(int x, int y, float z, Color4 color)
{// 我们的后台缓冲区是一维数组  // 这里我们简单计算,将X和Y对应到此一维数组中  var index = (x + y * renderWidth);var index4 = index * 4;// 使用锁来保护缓冲区不被并行处理扰乱lock (lockBuffer[index]){if (depthBuffer[index] < z){return; // 深度测试不通过 }depthBuffer[index] = z;backBuffer[index4] = (byte)(color.Blue * 255);backBuffer[index4 + 1] = (byte)(color.Green * 255);backBuffer[index4 + 2] = (byte)(color.Red * 255);backBuffer[index4 + 3] = (byte)(color.Alpha * 255);}
}

 

使用第二种方法,从45 FPS提升到了53 FPS。你可能会认为这点性能提升并不能让人印象深刻。但是在接下来的教程中,drawTriangle方法将更加复杂,比如处理阴影和照明之类的,并行处理几乎可以得到两倍的性能提升

 

 

我们还可以看看这个并行处理方法分析的核心利用图:

新版并行处理核心利用图

比较以前的核心图,你就会明白为什么它更加高效了。

 

你可以在这里下载含有这种优化的C#版本解决方案

- C#:SoftEngineCSharpPart4Bonus.zip

 

那么,在Html5/JavaScript中为什么不能应用这种方法呢?

Html5在JavaScript中为开发人员提供了一个新的API用于处理类似的情况。它叫Web Workers,使用它可以处理使用多核心的情况。

 

David Catuhe和我已经在这三篇话题中讨论过这个东西:

 

- 介绍Html5的Web Workers:JavaScript的多线程方法:如果你还不知道Web Workers,那么你应该先阅读这篇文章

- 使用Web Workers,用于提高图像处理性能:一篇很有意思的文章,我们使用Web Workers来获得像素操作的性能提升

- 教程系列:在Windows 8中使用WinJS和WinRT构建一个有趣的Html5摄像头应用程序:在这个教程中使用Web Workers来进行摄像头拍摄的图像效果处理

 

我们使用message与workers进行沟通。这意味着大多数时间被用于从UI线程给workers线程传输拷贝数据。我们只有很少部分的类型可以使用。事实上,随着转换对象,当转移到workers时原始对象将从呼叫者上下文(UI线程)中清除。

 

但是在我们加速Html5的3D软件渲染引擎时,这并不是主要的问题。在发送数据的时候将有memcpy()进行操作,这是一个非常快速的过程。但真正的问题是,当workers完成了处理后,需要将结果发送回主线程而且这个主线程还需要将数据重新填充回像素数组中。这个操作将很直接的抵消掉所有Web Workers带来的性能增益。

 

所以最后,我还没有找到一个有效的并行处理方法来实现Html5中3D软件渲染引擎的性能提升。不过,可能我有一些东西还不知道。如果您可以解决当前Web Workers的局限,获得到一个显著的性能提升的话,我非常乐意接受建议! :)

 

在接下来的教程中,我们将讨论平面着色和高氏着色。我们的实现将开始真正耀眼! :)

这篇关于[软件渲染器入门]四-额外章节,使用技巧和并行处理来提高性能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/732215

相关文章

详解Vue如何使用xlsx库导出Excel文件

《详解Vue如何使用xlsx库导出Excel文件》第三方库xlsx提供了强大的功能来处理Excel文件,它可以简化导出Excel文件这个过程,本文将为大家详细介绍一下它的具体使用,需要的小伙伴可以了解... 目录1. 安装依赖2. 创建vue组件3. 解释代码在Vue.js项目中导出Excel文件,使用第三

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

python管理工具之conda安装部署及使用详解

《python管理工具之conda安装部署及使用详解》这篇文章详细介绍了如何安装和使用conda来管理Python环境,它涵盖了从安装部署、镜像源配置到具体的conda使用方法,包括创建、激活、安装包... 目录pytpshheraerUhon管理工具:conda部署+使用一、安装部署1、 下载2、 安装3

Mysql虚拟列的使用场景

《Mysql虚拟列的使用场景》MySQL虚拟列是一种在查询时动态生成的特殊列,它不占用存储空间,可以提高查询效率和数据处理便利性,本文给大家介绍Mysql虚拟列的相关知识,感兴趣的朋友一起看看吧... 目录1. 介绍mysql虚拟列1.1 定义和作用1.2 虚拟列与普通列的区别2. MySQL虚拟列的类型2

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

关于@MapperScan和@ComponentScan的使用问题

《关于@MapperScan和@ComponentScan的使用问题》文章介绍了在使用`@MapperScan`和`@ComponentScan`时可能会遇到的包扫描冲突问题,并提供了解决方法,同时,... 目录@MapperScan和@ComponentScan的使用问题报错如下原因解决办法课外拓展总结@

mysql数据库分区的使用

《mysql数据库分区的使用》MySQL分区技术通过将大表分割成多个较小片段,提高查询性能、管理效率和数据存储效率,本文就来介绍一下mysql数据库分区的使用,感兴趣的可以了解一下... 目录【一】分区的基本概念【1】物理存储与逻辑分割【2】查询性能提升【3】数据管理与维护【4】扩展性与并行处理【二】分区的

使用Python实现在Word中添加或删除超链接

《使用Python实现在Word中添加或删除超链接》在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能,本文将为大家介绍一下Python如何实现在Word中添加或... 在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能。通过添加超

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存