光追渲染器开发记录:基础渲染架构/线程池/泛型单例

目录

程序的主入口:

构建BVH:

渲染循环:

核心功能:

多线程加速:

泛型单例:

消息队列:

线程池:

线程同步:

学习资料:
​​​​​​​


上一篇记录:

光追渲染器开发记录:开发环境配置Cmake+Vcpkg进行集成_This is MX的博客-CSDN博客https://blog.csdn.net/m0_56399931/article/details/123835101下一篇记录:

光追渲染器开发记录:BVH加速结构构建与射线求交_This is MX的博客-CSDN博客icon-default.png?t=M276https://blog.csdn.net/m0_56399931/article/details/124145240

前记:个人水平有限,如果有改进的建议欢迎提出-------------------------------------------博主:mx

程序的主入口:

渲染器目前主入口长这样:

光追渲染器开发记录:基础渲染架构/线程池/泛型单例_第1张图片

后续添加修改内容肯定会进行更改。

我们的主入口主要做了以下几件事:

· 构建场景

· 初始化渲染器

· 建立空间切分

· 侦听输入

· 渲染tick

构建BVH:

截取了核心的一部分:

光追渲染器开发记录:基础渲染架构/线程池/泛型单例_第2张图片

这里我采用games101里面的递归构建方法,核心就是:获取到最大的轴,根据那个轴进行排序切分递归构建。

渲染循环:

先看图:

光追渲染器开发记录:基础渲染架构/线程池/泛型单例_第3张图片

核心功能:

渲染循环主要做两个事情:

· Path Tracing 获取屏幕的每个像素颜色

· PostProcess后处理

这一部分肯定需要进行多线程加速。

多线程加速:

为了使用多线程加速

这里多线程,采用了单例的线程池。

泛型单例:

这个比较简单,就是采用的懒汉版单理模式,然后c++11是要求编译器保证内部静态变量的线程安全性,所以这个单例也是线程安全的,并且泛型能够适配多种类。

光追渲染器开发记录:基础渲染架构/线程池/泛型单例_第4张图片

消息队列:

这个核心是把消息队列适配多种任务,并且需要是线程安全的,所以我做的是把原生的std::queue进行改造,对齐进行加锁,保证在调用的时候不会错误。

光追渲染器开发记录:基础渲染架构/线程池/泛型单例_第5张图片

线程池:

这个其实就比较难。主要是工作线程和消息封装这两个部分。

工作线程:

首先我们使用一个封装的工作线程来调用消息队列里面的消息,他会在线程池运行的时候,不断获取消息队列的任务进行执行,如果消息队列里面没有任务了,就会阻塞当前线程,直到有任务被唤醒。

光追渲染器开发记录:基础渲染架构/线程池/泛型单例_第6张图片

消息封装:

我们封装的函数应该做到以下两点:

  • 接收任何参数的任何函数。(普通函数,Lambda,成员函数……)
  • 立即返回任务结束的结果,避免阻塞主线程。。

这里我们涉及几个知识:

· typename... Args:这个是C++11引入的可变模版参数(variadic templates)

· decltype:decltype(表达式)能够根据表达式推断出类型

· 尾返回类型(trailing return type):例如这样的来自动推断返回类型:

template
auto add2(T x, U y) -> decltype(x+y){
return x + y;
}

· std::future :提供了一种用于访问异步操作结果的机制。我们可以使用std::future的wait()方法来设置屏障,阻塞线程,实现线程同步。并最终使用std::future的get()方法来获得执行结果。

· std::function :它是一种通用、多态的函数封装,它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作,它也是对 C++中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的),简而言之,std::function 就是函数的容器。

· std::bind :它可以将函数f和参数args绑定起来,留下一部分在真正调用时确定(当然,你也可以直接指定全部参数,在调用时不再指定。)。这个特性其实很适合做回调函数。

· std::forward:这个主要涉及万能引用、完美转发以及引用折叠。

· · 完美转发:

指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。

· · 万能引用(universal reference)

只有在发生类型推导的时候 “&&” 才代表 universal reference

如果用来初始化universal reference的表达式是一个左值,那么universal reference就变成lvalue reference。

如果用来初始化universal reference的表达式是一个右值,那么universal reference就变成rvalue reference。

· · 引用折叠

引用折叠只有两条规则:

一个 rvalue reference to an rvalue reference 会变成 (“折叠为”) 一个 rvalue reference.

所有其他种类的"引用的引用"(i.e., 组合当中含有lvalue reference) 都会折叠为 lvalue reference.

了解了这些之后我们再来看这个消息的封装:

光追渲染器开发记录:基础渲染架构/线程池/泛型单例_第7张图片

核心其实是:自动推断出返回的future,用这个future来进行结果的获取。函数体是主要先绑定参数,然后将其封装成一个void()类型的函数方便调用,然后将其提交到任务队列,并唤醒一个线程。

这里提一下我们的消息队列也将每日任务统一的封装成了std::function,对于返回值呢我们通过std::future来获取。

线程同步:

因为我希望用单例控制所有线程,这就要求,Path Tracing 获取完屏幕的所有像素颜色,再进行PostProcess后处理,对于我们的线程池来说就得要判断所有任务完成,主线程再继续进行。那就往线程池加这样一个Join的功能。

怎么做呢?

一个循环计数器message_num,每提交一个任务++,每个任务彻底运行完成--,主线程阻塞一下,message_num=0的时候再将其唤醒。

学习资料:

games101 光追渲染器

1.基于C++11实现线程池 - 知乎

2.C++中的单例模式_bob62856的博客-CSDN博客_c++ 单例模式

你可能感兴趣的:(渲染器开发笔记,图形学,图形渲染,游戏引擎,算法)