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

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

译者前言:

本文译自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

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传