Filament引擎(三) ——引擎渲染流程

通過Filament引擎(二) ——引擎的調用及接口層核心對象的介紹我們知道,要在項目中使用filament,首先我們需要構建出filament的Engine的對象,然后通過filament::Engine對象實例,來構建其他對象,組裝渲染場景,執行渲染操作等。那么filament::Engine的構建過程具體發生了什么事情呢?我們組裝的渲染場景,又是如何被渲染渲染出來的?在本篇博客中,我們進一步去了解下Filament引擎的內部是如何工作的。

一、Engine構建

我們通過Engine::Builder().build()來創建filament::Engine對象,會調用到Engine* FEngine::create(Builder const& builder)函數來創建FEngine對象。

在編譯時,如果開啟FILAMENT_SINGLE_THREADED宏,則filament不會開啟單獨的渲染線程,否則Engine.create的時候,會構建單獨的DriverThread,渲染工作在此線程中執行。出于渲染效率的考慮,我們在使用filament時,一般都是會采用異步渲染的方式。

在異步渲染的模式下,filament渲染的核心對象Driver,會在渲染線程中根據調用者配置和當前運行環境進行構建。構建成功后,通知工作線程Driver構建成功,然后渲染線程會進入命令處理的循環中。

二、工作線程

我們將調用Filament命令的線程稱為工作線程。在工作線程中,我們調用filament的API,創建IndexBuffer、VertexBufer、Texture、Material之類的渲染對象,構建渲染場景,然后調用渲染器進行渲染,實際上并不會真正的調用OpenGL、Metal、Vulkan、DirectX這樣的底層渲染API。
在構建渲染對象時,實際上會調用我們封裝的渲染驅動(OpenGLDriver、MetalDriver、VulkanDriver等,具體調用取決于平臺及構建Engine時的配置),會先創建出filament抽象出的對應的RHI對象,如果需要調到底層渲染API,引擎會通過CommandStream在CircularBuffer實例中構建出對應的Command,由渲染線程進行執行。
在Filament引擎(一) ——渲染框架設計中異步渲染的實現這部分,對此部分實現,也進行了分析和說明。它用了一些C++開發中的小技巧,在此也不再贅述。

渲染驅動指令的構建

IndexBuffer::setBuffer的調用為例,其堆棧如下:
在這里插入圖片描述
CommandStream::updateIndexBuffer函數是通過DriverAPI.inc中的宏DECL_DRIVER_API_N聲明,DECL_DRIVER_API_N會展開為DECL_DRIVER_API,在CommandStream.h中引入DriverAPI.inc前對DECL_DRIVER_API進行了定義:

#define DECL_DRIVER_API(methodName, paramsDecl, params)                                         \inline void methodName(paramsDecl) {                                                        \DEBUG_COMMAND_BEGIN(methodName, false, params);                                         \using Cmd = COMMAND_TYPE(methodName);                                                   \void* const p = allocateCommand(CommandBase::align(sizeof(Cmd)));                       \new(p) Cmd(mDispatcher.methodName##_, APPLY(std::move, params));                        \DEBUG_COMMAND_END(methodName, false);                                                   \}

所以,實際上CommandStream::updateIndexBuffer的實現,宏展開后如下:

inline void updateIndexBuffer(filament::backend::Handle<filament::backend::HwIndexBuffer> ibh, filament::backend::BufferDescriptor && data, unsigned int byteOffset){                                                       mDriver.debugCommandBegin(this, false, "updateIndexBuffer");using Cmd = CommandType<decltype(&Driver::updateIndexBuffer)>::Command<&Driver::updateIndexBuffer>;void* const p = allocateCommand(CommandBase::align(sizeof(Cmd)));new(p) Cmd(mDispatcher.updateIndexBuffer_, std::move(ibh), std::move(data), std::move(byteOffset));mDriver.debugCommandEnd(this, false, "updateIndexBuffer");
}

filament以DriverAPI中的函數作為模板參數,通過CommandType模板類及其內部模板類Command,將DriverAPI函數及調用傳入的參數封裝成CommandBase的子類對象,對象存儲在CircularBuffer中。這樣,渲染線程就能不關注渲染的具體指令,而是按照統一的調用方式進行執行。

三、渲染線程

在這里插入圖片描述
渲染線程需要做的工作,只是不斷的從CircularBuffer中取出需要執行命令,讓Driver進行執行。

FEngine::execute:通過調用mCommandBufferQueue.waitForCommands,每次循環從引擎實例中的CommandBufferQueue實例內,取出當前的待執行的Commands(std::vector<CommandBufferQueue::Range>), 然后遍歷的將Range所指向的內存Buffer,傳遞給CommandStream.execute進行處理。

  • CommandBufferQueue::Range記錄的只有begin和end兩個void*指針,指向的是一組Commands的起止地址。CircularBuffer、Command及Range的關系示意如下:
    在這里插入圖片描述

  • mCommandBufferQueue.waitForCommands內部在命令隊列為空或者在暫停渲染時,進入等待狀態,阻塞渲染線程的工作。

CommandStream.execute: CommandStream封裝了代表渲染驅動的Driver,在execute方法中,會將FEngine.execute傳遞進來的buffer(CommandBufferQueue::Range) 轉換成Command(CommandBase*)進行執行。Command執行(CommandBase.execute)會返回下一個CommandBase對象的指針。

四、幀渲染

通過上面的分析,我們大致可以知道,在工作線程中,我們調用的filament的API,會直接或間接的轉換成渲染驅動命令,存儲在CircularBuffer中,由渲染線程進行消費。

對于VertexBuffer、IndexBuffer、Texture等對象的創建,filament會構建出對應渲染驅動命令。對于Material對象,則會在其實例化的時候(createInstance),構建對應的渲染驅動命令。渲染Entity的構建,會構建“創建渲染圖元”的驅動命令,這些調用路徑都相對比較簡單且直觀。

這種工作線程+渲染線程的方式,是現代渲染引擎比較通用的實現方案。其實對于一個渲染引擎來說,更關鍵的是,渲染場景的組織如何去設計,以及在工作線程中如何將組織好的渲染場景轉換成渲染線程可以“無腦”執行的渲染指令。

在filament中,當我們組織好場景后,會調用Renderer.beginFrameRenderer.render以及Renderer.endFrame進行場景的“渲染”。這個過程,實際上就是將我們組織的渲染場景,變成一系列的渲染驅動命令,發送到渲染線程執行。

beginFrame

在beginFrame中,會利用FrameSkipper去動態控制渲染幀的跳過策略,在渲染負載過高時返回false。我們在使用時,當beginFrame返回false時,就不在調用Renderer.render,避免因渲染延遲導致畫面卡頓。
除此之外,beginFrame中主要做的工作包括:

  1. SwapChain::makeCurrent: 交換鏈和渲染環境的上下文綁定,讓當前渲染線程知道要把圖畫到哪里,確保后續的渲染操作能正確顯示在指定的面上。
  2. CommandStream::tick: 發個指令讓渲染線程執行需要在渲染線程執行的周期性的任務。這些任務一般是因為渲染后端實現時需要控制某些渲染指令的調用時機而發出的任務。
  3. CommandStream::beginFrame: 發送beginFrame的渲染驅動命令。不同驅動(MetalDriver、OpenGLDriver、VulkanDriver等)處理不同。
  4. FrameInfoManager::beginFrame: 幀信息收集,FrameInfoManager用于管理幀級別的渲染元數據和性能監控信息。
  5. FEngine.prepare: 主要工作都是針對材質,包括將材質實例中的參數的修改同步到渲染管線,保證運行時修改的材質參數能被GPU使用。以及檢查著色器程序與材質參數一致性。

render

filament中每幀的渲染由View進行組織,場景(Scene)和相機(Camera)是其進行內容呈現的必要元素。render的核心調用棧為:FRenderer::render->FRenderer::renderInternal->FRenderer::renderJob

在render中,主要是對場景、相機的存在進行判斷,以及保證渲染前的flush操作。

在renderInternal中,會基于Engine中RenderPassArena構建一個RootArenaScope對象,在渲染構建幀圖的過程中,RenderPass會被存儲到RenderPassArean中。在renderInternal執行完成后,RootAreanScope自動析構,析構時會把新加入到RenderPassArean中的RenderPass使用的內存進行回收。此外,renderInternal中,會為JobSystem創建rootJob,并在函數執行完成前runAndWait(rootJob),以保證在此間所有的Job都在管控中切執行完成。

在renderJob中,最為核心的工作包括幀圖(FrameGraph)的構建、編譯和執行 以及 后處理(PostProcessManager)的設置,主要的流程大致為:

  1. engine.getPostProcessManager().setFrameUniforms(driver, view.getFrameUniforms()) 將后處理過程中需要用到的渲染統一變量進行同步。
  2. 獲取view中的各種配置項和功能狀態,包括抗鋸齒、防抖動、后處理等等各種配置和狀態。
  3. view.prepare進行View的準備工作,其入參中的cameraInfo由view.computeCameraInfo返回,并根據后處理、fass等設置,在必要時將渲染視口的尺寸調整為16的倍數,幫助優化內存分配和四邊形渲染。
    1. scene->prepare收集渲染此場景所需的所有信息。工作流程:
      1. 遍歷Entities,按照光源實體和渲染實體進行分類,存儲到不同的容器中。
      2. 根據光源實體和渲染實體的容器中實體個數,調整mLightDatamRenderableData的容器大小,他們分別是光照和渲染的SoA(Structure of Arrays)數據。
      3. 發起任務到JobSystem中執行,填充光照和渲染的SoA。定向光源(directional ligth)需要單獨處理。
    2. 當場景中設置了POINT、FOCUSED_SPOT及SPOT類型的光源,會在任務系統(JobSystem)中運行FView::prepareVisibleLights,對設置進來的這些光源進行準備工作。
      1. 進行必要的光照剔除,確定場景中那些光源是可見的。未被標記參與光照投影計算的(lightCaster)、與視椎不相交等情況下的光源,會比標記為不可見。可見光源會被排到SoA的前面來,便于渲染處理。
      2. 計算光源和相機之間的距離,按照離相機由近到遠對光源進行排序。按照源碼中注釋的解釋,這么做是未來方便后續構建光源樹。如果光源數量超過GPU緩沖區所能容納的數量,距離相機較遠的光源會被舍棄掉。
    3. 開啟了視椎裁切的時候,將不在視椎中的渲染對象進行剔除,標記為VISIBLE_RENDERABLE_BIT
    4. prepareVisibleLights執行完成后,判斷如果存在動態光源,就進行Froxel化。Froxel是filament中的光源在視椎空間下的體素化,結合Frustum和Voxel造的詞。光照效果的渲染,filament采用的是分簇前向渲染的方式,來平衡渲染效果和渲染效率。關于filament具體的光照實現,在另外一篇博客中再進一步分析。
    5. 進行陰影的準備工作,主要是根據光照信息進行ShadowMap的構建和更新。
    6. 按照渲染實體的可見性,將渲染的SoA進行分組,然后進行渲染對象的準備工作,更新渲染的SoA。分組包括:
      1. 主攝像機可見: 被主攝像機捕獲且需要直接渲染到屏幕的對象,會參與主渲染通道。
      2. 主攝像機可見且進行定向光陰影投射:主攝像機可見且需要為定向光投射陰影,在生成平行光陰影貼圖時渲染,同時也會參與主渲染通道。
      3. 進行定向光陰影投射:不可見于主攝像機,但需要進行定向光陰影投射,僅在生成平行光陰影貼圖時渲染。
      4. 潛在的點光源陰影投射:可能被點光源或聚光燈照射,但未被主攝像機直接看到,在生成點光源陰影貼圖時渲染。
      5. 明確不可見:完全不可見且無需參與任何渲染或陰影計算,會在渲染時被剔除。
    7. 進行光照信息的準備工作,更新光照的UBO,以及設置IBL(Indirect Light)等等。
  4. view.prepareUpscaler進行上采樣的設置。
  5. FrameGraph的構建和設置,這部分在renderJob中占據最大的篇幅。其主要流程如下:
    1. 以Renderer中的mResourceAllocator作為入參,構建FrameGraph實例。
    2. 獲取FrameGraph中的Blackboard,它主要是作為FrameGraph中的全局資源管理器,用于存儲和傳遞渲染過程中需要共享的虛擬資源(如紋理、渲染目標等)。在有陰影效果時,設置其陰影資源,陰影資源會用到view.prepare時構建的ShadowMap。
    3. 構建FrameGraph的RenderTarget資源,作為FrameGraph的渲染目標。
    4. 根據渲染對象的可見性進行圖元更新。
    5. 在此過程中,會解析各種設置和狀態,構建RenderPass,作為FrameGraph中的渲染單元。
  6. FrameGraph的“編譯”和執行。編譯階段,會分析渲染通道依賴關系,進行無效節點的剔除,并標記資源生命周期,以保證在資源不再被使用時及時銷毀或回收。執行階段就是根據編譯結果動態實例化GPU資源并將渲染指令提交到真正的渲染線程中。

關于renderJob中關鍵源碼的具體分析,后續進行進一步的展開,此處僅做簡單的流程性分析。

endFrame

一幀渲染完結,由endFrame中來進行必要的指令提交,資源回收等工作,主要包括:

  1. SwapChain::commit: 交換鏈提交,一般會刷新緩沖區,可以看做是將渲染管線的后緩沖區提交到渲染鏈前段,使渲染結果能從GPU到顯示設備。
  2. FrameInfoManager::endFrame: 幀信息收集完結,同beginFrame中的FrameInfoManager::beginFrame對應。
  3. CommandStream::tick: 發個指令讓渲染線程執行需要在渲染線程執行的周期性的任務。
  4. mResourceAllocator.gc幀資源回收

歡迎轉載,轉載請保留文章出處。求閑的博客[https://blog.csdn.net/junzia/article/details/149294146]


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/88603.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/88603.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/88603.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Oracle存儲過程導出數據到Excel:全面實現方案詳解

技術背景與需求分析 數據導出是企業級應用的核心功能,Oracle存儲過程因其高性能執行(減少網絡傳輸)、代碼復用性(封裝業務邏輯)和事務安全性(ACID保障)成為理想載體。Excel作為使用率$ \geq 95% $的辦公工具,其兼容性需求尤為突出。典型場景包括: 財務報表自動生成物…

解決el-table右下角被擋住部分

一部分展示不全&#xff0c;被遮擋&#xff0c;因為 最右邊加了fixed"right"<el-table-column fixed"right" label"操作" width"120">解決&#xff1a;1、去除fixed"right"或2、設置樣式單頁面<style lang"sc…

Waiting for server response 和 Content Download

在瀏覽器網絡調試&#xff08;如 Chrome DevTools 的 Network 面板&#xff09;中&#xff0c;Timing 選項卡下的 Waiting for server response 和 Content Download 是兩個關鍵性能指標&#xff0c;它們分別代表了 HTTP 請求生命周期的不同階段。以下是詳細解釋和優化方案&…

《Java Web程序設計》實驗報告五 Java Script學習匯報

目 錄 一、實驗目的 二、實驗環境 三、實驗步驟和內容 1、小組成員分工&#xff08;共計4人&#xff09; 2、實驗方案 3、實驗結果與分析 Ⅰ、簡述JavaScript的產生過程與Java的關系 Ⅱ、簡述JavaScript的特點有哪些 Ⅲ、簡述ECMAScript的歷史 Ⅳ、簡述ECMAScript與J…

C#與FX5U進行Socket通信

實現效果實現步驟&#xff1a;注意&#xff1a;詳細的參數這里就不說明了&#xff0c;自己網上搜即可&#xff1b;打開GX Works3 創建FX5U項目系統參數設置PLC的具體型號&#xff08;我有實物PLC&#xff09;設置IP及組態參數添加通訊設備&#xff08;這里PLC做客戶端&#xff…

ubuntu20.04基于tensorRT和c++跑yolo11

設備 系統&#xff1a;Ubuntu 20.04 顯卡&#xff1a;NVIDIA GeForce RTX 3050 顯卡驅動&#xff1a; Driver Version: 535.183.01 CUDA Version: 12.2 關鍵軟件版本總結 Cmake: 3.28.6 Cuda&#xff1a; 12.2.2 Cudnn: 8.9.7 TensorRT: 10.8.0.43 Python&#xff1a;3.10.1…

玖玖NFT數字藏品源碼(源碼下載)

玖玖NFT數字藏品源碼 這套還是很不錯的&#xff0c;前端uniapp&#xff0c;后端FastAdmin&#xff0c;對接匯元支付&#xff0c;富友支付&#xff0c;對接avata鏈&#xff0c;感興趣的自行下載研究 源碼下載&#xff1a;https://download.csdn.net/download/m0_66047725/9133…

【Redis-05】高可用方案-主從哨兵

1 概述 高可用&#xff08;High Availability&#xff09;指系統在部分節點故障時仍能持續提供服務的能力。Redis 作為核心緩存組件&#xff0c;主流的高可用方案有主從復制、哨兵模式、集群模式三種。本文介紹主從復制、哨兵模式兩種高可用方案。 2 主從復制 通過 “一主多從”…

焊接機器人智能節氣裝置

工業焊接作為現代制造業的重要組成部分&#xff0c;廣泛應用于汽車、航空航天、建筑、船舶等多個領域。隨著自動化技術的快速發展&#xff0c;焊接機器人已成為提升焊接效率和質量的關鍵裝備。在傳統焊接及部分自動化焊接過程中&#xff0c;氣體流失問題仍然普遍存在&#xff0…

【6.1.0 漫畫數據庫技術選型】

漫畫數據庫技術選型 &#x1f3af; 學習目標&#xff1a;掌握架構師核心技能——數據庫技術選型&#xff0c;針對不同業務場景選擇最合適的數據庫方案 &#x1f3db;? 第一章&#xff1a;關系型數據庫對比選型 &#x1f914; MySQL vs PostgreSQL vs TiDB 想象數據庫就像不同…

CVE-2022-4262/CVE-2022-3038

CVE-2022-4262&#xff08;Linux內核UAF漏洞&#xff09;漏洞原理CVE-2022-4262是Linux內核中RDS&#xff08;Reliable Datagram Sockets&#xff09;協議實現的一個UAF&#xff08;Use-After-Free&#xff0c;釋放后使用&#xff09;漏洞。具體來說&#xff1a;在rds_rdma_ext…

[Token]Token merging for Vision Generation

Token Compression for Vision Domain_Generation 文章目錄Image GenerationToken Merging for Fast Stable Diffusion, CVPRW 2023.Token Fusion: Bridging the Gap between Token Pruning and Token Merging, WACV 2024ToDo: Token Downsampling for Efficient Generation of…

React封裝過哪些組件-下拉選擇器和彈窗表單

背景&#xff08;S - Situation&#xff09;&#xff1a;在某活動管理系統中&#xff0c;前端頁面需要支持用戶選擇“要配置的當前活動”&#xff0c;并提供「新增」「編輯」功能&#xff0c;操作內容包括填寫活動名稱、ID、版本號等字段。原始實現邏輯分散、復用性差&#xff…

多租戶架構下的多線程處理實踐指南

在現代 SaaS 系統中&#xff0c;多租戶架構&#xff08;Multi-Tenant Architecture&#xff09;已成為主流。然而&#xff0c;隨著系統性能要求的提升和業務復雜度的增加&#xff0c;多線程成為不可避免的技術手段。但在多租戶環境下使用多線程&#xff0c;容易引發數據錯亂、租…

MyBatis插件機制揭秘:從攔截器開發到分頁插件實戰

一、攔截器體系架構解析 1.1 責任鏈模式在MyBatis中的實現 MyBatis通過動態代理技術構建攔截器鏈&#xff0c;每個插件相當于一個切面&#xff1a; // 攔截器鏈構建過程 public class InterceptorChain {private final List<Interceptor> interceptors new ArrayList<…

百度文心一言開源ERNIE-4.5深度測評報告:技術架構解讀與性能對比

目錄一、技術架構解讀1.1、ERNIE 4.5 系列模型概覽1.2、模型架構解讀1.2.1、異構MoE&#xff08;Heterogeneous MoE&#xff09;1.2.2、視覺編碼器&#xff08;Vision Encoder&#xff09;1.2.3、適配器&#xff08;Adapter&#xff09;1.2.4、多模態位置嵌入&#xff08;Multi…

Matplotlib 模塊入門

Python 中有個非常實用的可視化庫 ——Matplotlib。數據可視化是數據分析中不可或缺的環節&#xff0c;而 Matplotlib 作為 Python 的 2D 繪圖庫&#xff0c;能幫助我們生成高質量的圖表&#xff0c;讓數據更直觀、更有說服力。接下來&#xff0c;我們將從 Matplotlib 的概述、…

LeetCode 3169.無需開會的工作日:排序+一次遍歷——不需要正難則反,因為正著根本不難

【LetMeFly】3169.無需開會的工作日&#xff1a;排序一次遍歷——不需要正難則反&#xff0c;因為正著根本不難 力扣題目鏈接&#xff1a;https://leetcode.cn/problems/count-days-without-meetings/ 給你一個正整數 days&#xff0c;表示員工可工作的總天數&#xff08;從第…

VUE3 el-table 主子表 顯示

在Vue 3中&#xff0c;實現主子表&#xff08;主從表&#xff09;的顯示通常涉及到兩個組件&#xff1a;一個是主表&#xff08;Master Table&#xff09;&#xff0c;另一個是子表&#xff08;Detail Table&#xff09;。我們可以使用el-table組件來實現這一功能。這里&#x…

張量數值計算

一.前言前面我們介紹了一下pytorch還有張量的創建&#xff0c;而本章節我們就來介紹一下張量的計算&#xff0c;類型轉換以及操作&#xff0c;這個是十分重要的&#xff0c;我們的學習目標是&#xff1a;掌握張量基本運算、掌握阿達瑪積、點積運算 掌握PyTorch指定運算設備。Py…