【Vulkan学习记录-基础篇-5】多线程渲染

本文探讨现代图形API如Vulkan如何支持多线程渲染,以提高CPU性能。Vulkan通过Queue和CommandBuffer设计优化多线程提交,每个线程负责设置自己的CommandBuffer,最后统一提交。文中以多飞碟场景为例,展示每个飞碟颜色和位置更新的多线程处理过程,以及线程池的工作原理。
摘要由CSDN通过智能技术生成

现代的图形API都具备对多线程渲染友好的特性,所谓的多线程并不是指GPU端的多线程图像渲染,而是指在CPU提交DrawCall时所做的一系列工作可以并行化,也就是说多线程渲染其实是在CPU端提升程序的性能。

在使用D3D 11或者OpenGL的时候,每次提交DrawCall之前,都需要将相关的状态进行更新,将需要用的资源进行绑定,在提交DrawCall时,还要进行相关的参数检查等工作,这些看上去耗费的时间并没有太大的影响,而如果场景的几何体、材质种类非常多,用到的Shader数量比较多,每一帧的Pass比较多,就会导致有大量的DrawCall产生。那么每次在CPU端进行的这些操作的费时就很有可能会成为瓶颈。一种很自然的优化策略,就是将所有的CPU端的这些操作并行处理,即多线程地进行状态修改、参数检查等工作。但是传统的图形API对此并不友好,不管是D3D11还是OpenGL,它们都具有一个Context的概念,这个Context负责进行资源的绑定、状态修改、DrawCall调用,这样的模式对多线程十分不友好,如果想要实现多线程地提交,理论上是可以完成,但是非常麻烦,而且需要用到很多复杂同步原语,导致整体性能未必能达到理想的效果。

而现代的图形API则进行了一些模式上的更新,使得对多线程的支持更加友好。在Vulkan中的设计则主要体现在Queue和CommandBuffer上。在前几篇中有提到过,Vulkan中所有需要GPU执行的命令,只能通过CommandBuffer来完成,这些命令并不只包括DrawCall,对计算的调用,内存的操作,都需要用到CommandBuffer。而渲染所需要的所有状态(Shader和DescriptorSet等),都需要在CommandBuffer中进行绑定。每一个CommandBuffer,都有它独立的这些状态,在使用任意一个CommandBuffer时,都不可能避免这些操作,这与传统API中,如果不改变一个状态的话那么它将一直保持不变很不一样。而Queue则是在Vulkan中唯一一个可以向GPU提交命令的通道,而不是通过绑定在一个单一线程上的Context来完成。可以向Queue提交任务,而如果需要等待Queue中的某个任务结束的话,就需要手动的进行同步控制。用到上一篇所介绍的同步机制。

因此在Vulkan中的一种简单的多线程模式为:每个线程在每一帧都负责设置好自己的CommandBuffer,等待所有的线程将自己的CommandBuffer都设置好后,再将所有的CommandBuffer全部提交给Queue。

本文需要渲染的场景为:
在这里插入图片描述
这个场景由非常多的飞碟构成,观察到每个飞碟中间部分的颜色都不相同,也就是在渲染每一个飞碟时,都需要对渲染的状态进行更新。并且每个飞碟的位置在每一帧都需要进行更新。

下面就介绍这种多线程模式是如何具体实现的:
首先需要手动实现一下Thread:

class Thread
{
   
private:
	bool destroying = false;
	std::thread worker;
	std::queue<std::function<void()>> jobQueue;
	std::mutex queueMutex;
	std::condition_variable condition;

	// Loop through all remaining jobs
	void queueLoop()
	{
   
		while (true)
		{
   
			std::function<void()> job;
			{
   
				std::unique_lock<std::mutex> lock(queueMutex);
				condition.wait(lock, [this] {
    return !jobQueue.empty() || destroying; });
				if (destroying)
				{
   
					break;
				}
				job = jobQueue.front();
			}

			job();

			{
   
				std::lock_guard<std::mutex> lock(queueMutex);
				jobQueue.pop();
				condition.notify_one();
			}
		}
	}

public:
	Thread()
	{
   
		worker = std::thread(&Thread::queueLoop, this);
	}

	~Thread()
	{
   
		if (worker.joinable())
		{
   
			wait();
			queueMutex.lock();
			destroying = true;
			condition.notify_one();
			queueMutex.unlock();
			worker.join();
		}
	}

	// Add a new job to the thread's queue
	void addJob(std::function<void()> function)
	{
   
		std::lock_guard<std::mutex> lock(queueMutex);
		jobQueue.push(std::move(function));
		condition.notify_one();
	}

	// Wait until all work items have been finished
	void wait()
	{
   
		std::unique_lock<std::mutex> lock(queueMutex);
		condition.wait(lock, [this]() {
    return jobQueue.empty(); });
	}
}
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值