Filament引擎(一) ——渲染框架設計

filament是谷歌開源的一個基于物理渲染(PBR)的輕量級、高性能的實時渲染框架,其框架架構設計并不復雜,后端RHI的設計也比較簡單。重點其實在于項目中材質、光照模型背后的方程式和理論,以及對它們的實現。相關的信息,可以參考官方給出的文檔。filament比較注重運行效率,在實現上也使用了一些抽象和技巧,這些也是比較有意思的代碼,可以學習和借鑒。

框架的整體設計

按照github項目中的說明,filament是適用主流平臺的實時物理渲染引擎,設計的目的是期望在Android上小而快。

Filament is a real-time physically based rendering engine for Android, iOS, Linux, macOS, Windows, and WebGL. It is designed to be as small as possible and as efficient as possible on Android.

在其設計中,使用Entity、components下各Manager中用來表示Component的數據結構及Manager本身,再加上Renderer,形成了ECS(Entity-Component-System)架構。

另外filament中存在一個backend的子工程,是一套自定義的RHI(Render Hardware Interface),封裝了諸如OpenGL、Vulkan、Metal的后端渲染API(PS:沒有DirectX,Windows平臺,官方默認使用Vulkan)。

在“ECS”和“RHI”之間,filament通過Renderer類,內部使用RenderPassFrameGraph等來組織backend提供的RHI進行渲染,承擔最重要的“RenderSystem”的工作。

渲染后端RHI設計

Filament的渲染后端RHI非常輕量,并沒有什么復雜的設計,使用起來也相對比較簡單。不過它在異步渲染的實現上有一些不太好看但是實用的“奇淫巧技”。

Filament定義了一個RHI空對象基類HwBase,然后派生了一系列的RHI對象,包括HwVertexBufferHwIndexBufferHwProgramHwTextureHwRenderTarget等等。基本上和其他諸多渲染框架采用的是類似的概念,然后不同的后端渲染(OpenGL/Vulkan/Metal)繼承這些對象,進行了不同后端的實現,如基于HwProgram派生了OpenGLProgramVulkanProgram以及MetalProgram。其他各類對象也是類似。

另外Filament有一個Driver的基類,基于這個Driver基類,派生了各后端渲染的Driver,包括VulkanDriver/MetalDriver/OpenGLDriver/NoopDriver,沒有DirectXDriver,Windows平臺,官方使用Vulkan來進行渲染。Driver作為渲染的主要入口,所有RHI對象的創建銷毀及更新,都經由Driver來進行調用。
請添加圖片描述

RHI的使用流程

Filament的RHI抽象和封裝度并不復雜,所以在使用上,如果有用過OpenGL、Vulkan、Metal的API,那么理解Filament的后端渲染也比較簡單。主要的使用流程參考以下代碼:

// 1. 初始化RHI的Driver
auto backend = filament::Backend::METAL;
auto platform = PlatformFactory::create(&backend);
Platform::DriverConfig const driverConfig;
auto driver = platform->createDriver(nullptr, driverConfig);
auto api = driver;
// 2. 創建SwapChainHandle,作為輸出。如果需要輸出到Window上,需要利用Window指針來進行創建
// api.createSwapChain(view.ptr, 0)
auto swapChain = api.createSwapChainHeadless(256, 256, 0);
api.makeCurrent(swapChain, swapChain);
// 3. 創建ProgramHandle,后續用來進行渲染,依賴Program對象,注意,Program和ProgramHandle不是同一個東西,Program就是用來創建ProgramHandle的一個參數集合
Program progCfg; // 進行相關配置
progCfg.shaderLanguage(ShaderLanguage::MSL);
projCfg.shader(ShaderStage::VERTEX, mVertexBlob.data(), mVertexBlob.size());
projCfg.shader(ShaderStage::FRAGMENT, mFragmentBlob.data(), mFragmentBlob.size());
projCfg.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
auto program = api.createProgram(progCfg);
// 4. 創建渲染需要的紋理,更新紋理數據
auto tex = api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 512, 512, 4, usage);
PixelBufferDescriptor descriptor = createImage();
api.update3DImage(tex, 1, 0, 0, 0, 512, 512, 1, std::move(descriptor));
// 5. 設置采樣方式
SamplerGroup samplers(1);
SamplerParams sparams = {};
sparams.filterMag = SamplerMagFilter::LINEAR;
sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
samplers.setSampler(0, { tex, sparams });
auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test"));
api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api));
api.bindSamplers(0, sgroup);
// 6. 開始一幀渲染,一個完整的渲染周期,可能由很多次渲染組成的一個渲染幀
api.beginFrame(0, 0, 0);
// 7. 創建RenderTarget,配置RenderPassParams,進而開啟RenderPass。RenderPass一般表示的是在一個渲染目標上進行的一系列渲染。
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 0.f, 1.f, 1.f};
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = 512;
params.viewport.width = 512;
auto renderTarget = api.createDefaultRenderTarget(0);
api.beginRenderPass(renderTarget, params);
// 8. 配置PipelineState
PipelineState state;
state.program = program;
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
// 9. 構建Primitive,Primitive主要包括頂點位置、頂點索引等相關數據
auto vertexBufferObj = api.createBufferObject(size, BufferObjectBinding::VERTEX, BufferUsage::STATIC);
auto vertexBufferInfo = api.createVertexBufferInfo(bufferCount, attributeCount, attributes);
auto vertexBuffer = api.createVertexBuffer(vertexBufferObj, vertexBufferInfo);
auto indexBuffer = api.createIndexBuffer(elementType, mIndexCount, BufferUsage::STATIC);
api.updateIndexBuffer(indexBuffer, std::move(indexBufferDesc), 0);
auto primitive = api.createRenderPrimitive( vertexBuffer, indexBuffer, PrimitiveType::TRIANGLES);
// 10. 渲染指令
api.draw(state, primitive, 0, mIndexCount, 1);
// 11. 終止RenderPass。
api.endRenderPass();
// 12. 一幀內要進行的所有渲染指令調用完后,提交并結束一幀渲染
api.commit(swapChain);
api.endFrame()
// 13. 如果當前的渲染任務結束了,不會再執行了,銷毀所有資源。
api.destroySamplerGroup(sgroup);
api.destroyProgram(program);
api.destroySwapChain(swapChain);
api.destroyRenderTarget(renderTarget);
// 14. 如果所有渲染結束,要終止渲染了,結束渲染
api.finish();
driver->purge();
driver->terminate();

以上是簡化的一個渲染流程中,在實際的渲染過程中,我們一般會進行多幀循環渲染。在一幀渲染過程中,我們也可能會有多個renderpass,甚至有subrenderpass。RenderTargetProgramPipelineStatePrimitive等RHI對象的創建,我們也不是一定要按照上面的順序來進行,只需要在被使用前創建好既可。

Driver中提供了startCapture來進行CPU和GPU的監測,我們一般也并不需要用到。而且在實現上也只有MetalDriver調用了Metal的API進行了實現。需要使用時,在需要進行監測的區間,調用startCapture/stopCapture即可。

異步渲染的實現

以上的調用,主要是用來說明Filament的使用流程,但是在實際的應用中,考慮到渲染效率和對渲染幀率的要求,我們往往需要進行異步渲染,把CPU和G的操作盡量并行起來。這時候,所有Filament的渲染指令的調用,都需要被發送到另外的線程中進行執行。我們不能再直接使用Driver對象去進行相關渲染指令的調用,而應該用CommandStream

在Filament中有一個DriverAPI.inc的頭文件,采用宏定義的方式,定義了一系列的渲染API,宏的具體實現又由引入者來進行。
最終呈現的效果,就是Driver定義了一系列的Driver的API,各后端實現對其進行了繼承,并實現。
CommandStream沒有繼承Driver,但是通過引入DriverAPI.inc,實現了一套和Driver的API一一對應,可以將Driver的各命令提交到隊列中的方法。

DriverAPI.inc使用宏調用的方式定義了一系列的函數,宏的定義又并不是在DriverAPI.inc中,幾個宏DECL_DRIVER_APIDECL_DRIVER_API_SYNCHRONOUS以及DECL_DRIVER_API_RETURN都是留給引用者定義,并在文件后undef這三個宏避免污染。每次引用DriverAPI.inc前,必須定義這三個宏,否則會編譯報錯。
Vulkan/Metal/OpenGL等各后端對于DECL_DRIVER_API的定義并沒什么差別,就是直接聲明對應名稱的函數。只有NoopDriver,在定義DECL_DRIVER_API_RETURN進行了空實現。

CommandStream/Dispatcher也都引入了DriverAPI.inc。
Dispatcher中DECL_DRIVER_API的定義就是一個function,那么引入DriverAPI.inc實際上,就是定義了一堆的Function,這么做主要是為了幫助CommandStream實現DriverAPI.inc中的方法時,來進行Command的構建。
CommandStream中的DECL_DRIVER_API的定義,是根據方法名(methodName),利用Dispatcher,構建出一個調用Driver.methodName的Command。這種實現方式雖然閱讀起來稍微麻煩一點,但是需要進行Driver函數的擴展會可以減少很多工作。

CommandStream中需要的Command是通過模板的方式進行定義的,參考CommandType和Command模板,由Command自己存儲所有指令執行時所需要的參數信息。CommandStream中有一個CircularBuffer,用來存儲所有的Command,Cammand一般都是在CommandStream調用DriverAPI.inc聲明的相關API時進行創建。創建過程是先根據Command對象需要的大小來申請內存,然后在這塊內存上構建(也可以說是初始化)Command對象。

在使用上,CommandStreamDriver具有基本一致的函數,利用Filament的backend去實現異步渲染時,相對同步渲染,只需要以下幾步:

  1. 創建CommandBufferQueue,然后利用Driver實例和CommandBufferQueue中的CircularBuffer去構造一個CommandStream
  2. 創建一個渲染線程,在渲染線程中循環等待渲染指令,然后執行渲染指令。在必要的時候進行退出。
  3. 在另外的線程里面像使用Driver一樣,使用CommandStream去執行相關命令接口。
    當然,這是簡化的說明,在實際使用中肯定還需要做一些額外的處理工作。Filament在使用其backend時,基于backend的api進行了進一步的封裝,以簡化調用。

渲染系統的實現

在filament中,實際上是由Renderer類承擔了渲染系統的職責,其執行渲染工作的核心代碼比較簡單:

if (renderer->beginFrame(window->getSwapChain())) {for (filament::View* offscreenView: mOffscreenViews) {renderer->render(offscreenView);}for (auto const& view: window->mViews) {renderer->render(view->getView());}if (postRender) {postRender(mEngine, window->mViews[0]->getView(), mScene, renderer);}renderer->endFrame();
}

其主要使用流程為:

  1. beginFrame開啟一幀渲染。beginFrame的時候,需要指定一個SwapChain,決定了最終渲染輸出的位置。
  2. render執行一幀渲染。render執行一幀渲染的時候,不是直接就進行了渲染,而是構建FrameGraph,并進行compile,然后execute。
  3. endFrame結束一幀渲染

Renderer的每一次渲染,都是執行一次renderJob(這個函數比較復雜,理論上應該拆一下)。它進行的工作,主要就是構建一個FrameGraph,通過對其進行“編譯”,再執行,以達到優化渲染的目的。FrameGraph的構建看起來非常復雜,主要進行的工作是將外部設置的信息,轉換成FrameGraph中的ResourceNodePassNodeVirtualResource等列表,并在此過程中,構建DependencyGraph,以明確PassNodeResourceNode的依賴關系。

FrameGraph的“編譯”(compile),主要做了以下事情:

  1. 遍歷它所包含的所有的PassNode。對于每個PassNode, 會通過DependencyGraph,去獲取它所依賴的資源,并把這些資源信息注冊到PassNode當中。RendererTarget的數據的更新在資源注冊后,通過調用PassNode.resolve進行。在資源信息注冊到PassNode時,每個資源會記錄并更新最早使用它的節點和最晚使用它的節點, 以便后續根據這個信息,來進行真實資源的創建和銷毀。
  2. 而后,資源列表會被遍歷,然后借助資源中記錄的最早和最晚使用它的節點信息,來把資源直接掛載到相應節點的資源構造(Resource.devirtualize)和資源析構列表(Resource.destroy),這樣就后續就可以方便合理的進行資源的按需創建和銷毀。
  3. 遍歷所有的資源節點,解析它們的用途(Resource.usage),后續資源進行真實資源創建時(Resource.devirtualize),需要用到。

FrameGraph的“執行”(execute),也是遍歷它所包含的所有PassNode,針對每個PassNode進行一下工作:

  1. 資源準備,VirtualResource.devirtualize
  2. RenderPassNode執行
    • 根據RenderPassData列表,進行必要的RenderTarget的構造
    • mPassBase->execute,將指令進行部分解析,轉換成RHI指令數據,加入到Engine下的CommandStream中,由CircularBuffer進行存儲和管理。
    • 析構前面構造的RenderTarget
  3. 資源析構,VirtualResource.destroy
    需要注意的是,其中的資源構造和資源析構,并不一定是在一次循環里對同一個資源進行。而是根據需要,在資源不再被后續的PassNode使用時,才會被析構。

在異步模式下,filament的FEngine構建時候,會啟動一個渲染線程,用來通過CommandStream對應的CommandBuffferQueue,來循環從CircularBuffer中獲取指令列表,讓真正的Driver來進行執行.執行完成后CircularBuffer中相應的指令空間就會被回收,用來接受CommandStream給的新指令。

ECS的實現

前面說到,Filament上層是一個ECS的設計。我們將其拆開來理解。引擎中的Entity結構非常簡單,它在內存中實際上就是一個unit32_t,代表的Entity的索引,它本身并不持有Component的數據,相對來說非常簡單,重點在于ComponentSystem

Filament中的Component更多的是一個概念,每類Component都有一個對應的Manager,Manager包含了:

  1. Component的數據結構
  2. 向Entity中增刪此Component。實際上Component的數據實例,是由Manager持有和管理,并記錄和Entity的關聯。
  3. 查詢某個Entity,是否具備Component。
  4. 更新Entity中對應Component的數據

Filament中的ComponentManager包括CameraManagerLightManagerRenderableManagerTransformManager。這些Manager的實現大同小異,都是依賴utils::SingleInstanceComponentManagerutils::EntityInstance這些模板類來做的實現。

以RenderableManager為例,其內部存在一個mManager成員,對象類型繼承自utils::SingleInstanceComponentManager模板類,如下代碼所示。

using Base = utils::SingleInstanceComponentManager<Box,                             // AABBuint8_t,                         // LAYERSMorphWeights,                    // MORPH_WEIGHTSuint8_t,                         // CHANNELSInstancesInfo,                   // INSTANCESVisibility,                      // VISIBILITYutils::Slice<FRenderPrimitive>,  // PRIMITIVESBones,                           // BONESFMorphTargetBuffer*              // MORPHTARGET_BUFFER
>;struct Sim : public Base {using Base::gc;using Base::swap;struct Proxy {// all of this gets inlinedUTILS_ALWAYS_INLINEProxy(Base& sim, utils::EntityInstanceBase::Type i) noexcept: aabb{ sim, i } { }union {// this specific usage of union is permitted. All fields are identicalField<AABB>                 aabb;Field<LAYERS>               layers;Field<MORPH_WEIGHTS>        morphWeights;Field<CHANNELS>             channels;Field<INSTANCES>            instances;Field<VISIBILITY>           visibility;Field<PRIMITIVES>           primitives;Field<BONES>                bones;Field<MORPHTARGET_BUFFER>   morphTargetBuffer;};};UTILS_ALWAYS_INLINE Proxy operator[](Instance i) noexcept {return { *this, i };}UTILS_ALWAYS_INLINE const Proxy operator[](Instance i) const noexcept {return { const_cast<Sim&>(*this), i };}
};Sim mManager;

RenderableManager對外的函數,最后基本上是對mManager的包裝。mManager用一個StructureOfArrays的模板類對象實例mData,來管理著當前Manager對應類別的所有Component數據集。上面SingleInstanceComponentManager模板傳入的參數,實際就構成了Component的數據結構。Sim定義了下標運算符,使mManager可以像數組一樣通過索引取得Component的代理對象,訪問Component數據。

但是,**在內存中實際存儲時,并不是按照Component的數據結構來存儲的,而是把相同的屬性的數據放到了一起。**StructureOfArrays中,mArray是一個std::tuple,存儲了所有屬性數據的起始地址。以FRenderableManager中mManager的數據存儲為例,圖示如下:
請添加圖片描述

在Filament的ECS中,S其實主要就一個,渲染系統Renderer,上面已對其渲染執行的過程進行了簡單的分析。其構建FrameGraph的過程比較復雜,涉及到諸多信息的處理,另外還包含一些View的操作,后面有時間再對其構建過程進行解讀。


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


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

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

相關文章

洛谷B3876—— [信息與未來 2015] 中間值

見&#xff1a;B3876 [信息與未來 2015] 中間值 - 洛谷 題目描述 給出一個正整數 n&#xff0c;生成長度為 n 的數列 a&#xff0c;其中 ai?i(1≤i≤n)。 若 n 為奇數&#xff0c;則輸出 a 的中間數&#xff08;位于 a 正中位置的數&#xff09;&#xff1b;若 n 為偶數&am…

Java 后端基礎 Maven

Maven 1.什么是Maven 2.Maven的作用 Maven核心 Maven概述 IDEA集成Maven 1.創建Maven項目 點擊設置里的 Project Structure 將jdk和編譯語言進行設置 隨后點擊apply點擊ok 2.Maven坐標 3.導入Maven項目 將文件夾復制到當前項目的目錄下 在這個目錄下&#xff0c;在磁盤中…

qtcreater配置opencv

我配置opencv不管是按照網上的教程還是deep seek發現都有些問題&#xff0c;下面是我的配置方法以及實踐成功的心得 電腦環境 windows平臺qt6 下載 我這里直接提供官網下載地址&#xff1a;https://opencv.org/releases/ 我下載的是最新版&#xff0c;下載后是一個.exe文件…

單片機-STM32部分:15、直流電機與步進電機 PWM/IO

飛書文檔https://x509p6c8to.feishu.cn/wiki/InUfwEeJNimqctkyW1mcImianLh 一、步進電機與直流電機&#xff1a; 1-1、什么是直流電機&#xff1f; 直流電機是最常見的電機類型。直流電動機通常只有兩個引線&#xff0c;一個正極和一個負極。直流電機的轉速控制主要依靠改變輸…

「佰傲再生醫學」攜手企企通,解鎖企業采購供應鏈數字化新體驗

健康&#xff0c;是人類美好生活的基石。隨著“健康中國2030”規劃的深入推進&#xff0c;生物醫藥和再生醫學等前沿技術快速崛起&#xff0c;已成為促進全民健康、提升生命質量的重要支撐&#xff0c;為健康事業注入了新的希望和動力。 一、佰傲再生醫學&#xff0c;讓每個人…

PyTorch Geometric(PyG):基于PyTorch的圖神經網絡(GNN)開發框架

PyTorch Geometric&#xff08;PyG&#xff09;&#xff1a;基于PyTorch的圖神經網絡&#xff08;GNN&#xff09;開發框架 一、PyG核心功能全景圖 PyTorch Geometric&#xff08;PyG&#xff09;是基于PyTorch的圖神經網絡&#xff08;GNN&#xff09;開發框架&#xff0c;專…

亮相戛納電影節、北京電影節的影星

?17日&#xff0c;由高圓圓、古天樂主演的《風林火山》劇組&#xff0c;在第78屆戛納影展上走紅毯亮相&#xff0c;記者爭相拍照&#xff0c;風光無限。 值得關注的是&#xff0c;導演麥浚龍以一身黑色晚禮服踏上紅毯&#xff0c;微笑間顯得躊躇滿志&#xff1b;古天樂則以白色…

Django框架的前端部分使用Ajax請求一

Ajax請求 目錄 1.ajax請求使用 2.增加任務列表功能(只有查看和新增) 3.代碼展示集合 這篇文章, 要開始講關于ajax請求的內容了。這個和以前文章中寫道的Vue框架里面的axios請求, 很相似。后端代碼, 會有一些細節點, 跟前幾節文章寫的有些區別。 一、ajax請求使用 我們先…

IP地址代理公司:服務模式與行業應用探析

隨著數據驅動型經濟的快速發展和互聯網應用的普及&#xff0c;IP地址代理服務逐漸成為支持多種網絡業務的重要組成部分。近年來&#xff0c;提供代理IP服務的公司遍地開花&#xff0c;這一市場強調供給的技術深度和服務靈活性&#xff0c;而代理IP公司本身也逐步從單一的技術供…

C語言練手磨時間

167. 兩數之和 II - 輸入有序數組 給你一個下標從 1 開始的整數數組 numbers &#xff0c;該數組已按 非遞減順序排列 &#xff0c;請你從數組中找出滿足相加之和等于目標數 target 的兩個數。如果設這兩個數分別是 numbers[index1] 和 numbers[index2] &#xff0c;則 1 <…

本地部署Firecrawl+Dify調用踩坑記錄

最近自己研究Dify&#xff0c;使用到Firecrawl這個比較好用的工具。用Firecrawl官網的不知道為什么總是卡住得不到結果&#xff0c;于是我打算自己去本地部署一個。好家伙真給我人搞麻了&#xff0c;太多問題了。 我是在京東云上面租的一臺服務器。 首先就是docker的安裝&…

iOS SwiftUI的具體運用實例(SwiftUI庫的運用)

最近接觸到一個 SwiftUI的第三方框架&#xff0c;它非常的好用。以下是 具體運用實例&#xff0c;結合其核心功能與開發場景&#xff0c;分多個維度進行詳細解析&#xff1a; 一、基礎 UI 組件開發 登錄界面 SwiftUI 的 VStack、TextField 和 Button 可快速構建用戶登錄表單。例…

【C++】模板上(泛型編程) —— 函數模板與類模板

文章目錄 一、啥是泛型編程二、函數模板2.1、函數模板的概念2.2、函數模板的格式2.3、函數模板的原理2.4、函數模板的實例化2.4.1、隱式實例化&#xff1a;讓編譯器根據實參推演模板參數的實際類型2.4.2、顯示實例化&#xff1a;在函數名后的<>中指定模板參數的實際類型 …

語音識別-2

目錄 1.藍牙優化 1.打開sco 2.外放時的藍牙的不同版本適配 2.微軟文本轉語音優化 1.異步文本轉語音 2.語音的個性化 上一篇關于語音識別, 雖然能用,但在系統適配,機器適配方面,速度,性能等還是有優化的地方.所以這篇是關于這些的. 1.藍牙優化 A2DP:是一種單向的高品質音…

【springcloud學習(dalston.sr1)】服務消費者通過restTemplate來訪問服務提供者(含源代碼)(五)

該系列項目整體介紹及源代碼請參照前面寫的一篇文章??????【springcloud學習(dalston.sr1)】項目整體介紹&#xff08;含源代碼&#xff09;&#xff08;一&#xff09; springcloud學習&#xff08;dalston.sr1&#xff09;系統文章匯總如下&#xff1a; 【springcloud…

小白學編程之——數據庫如何性能優化

小白學編程之——數據庫性能優化指南 數據庫如同一個大型倉庫&#xff0c;性能優化就是幫助倉庫管理員&#xff08;數據庫&#xff09;更高效地存取貨物&#xff08;數據&#xff09;。本文將以通俗易懂的方式&#xff0c;帶你避開常見誤區&#xff0c;讓數據庫運行得更快更穩…

SQLMesh信號機制詳解:如何精準控制模型評估時機

SQLMesh的信號機制為數據工程師提供了更精細的模型評估控制能力。本文深入解析信號機制的工作原理&#xff0c;通過簡單和高級示例展示如何自定義信號&#xff0c;并提供實用的使用技巧和測試方法&#xff0c;幫助讀者優化數據管道的調度效率。 一、為什么需要信號機制&#xf…

FreeSWITCH 簡單圖形化界面43 - 使用百度的unimrcp搞個智能話務臺,用的在線的ASR和TTS

FreeSWITCH 簡單圖形化界面43 - 使用百度的unimrcp搞個智能話務臺 0、一個fs的web配置界面預覽1、安裝unimrcp模塊2、安裝完成后&#xff0c;配置FreeSWITCH。2.1 有界面的配置2.1.1 mod_unimrcp模塊配置2.1.2 mod_unimrcp客戶端配置 2.2 無界面的配置 3、呼叫規則4、編寫流程4…

【架構】RUP統一軟件過程:企業級軟件開發的全面指南

一、RUP概述 RUP(Rational Unified Process&#xff0c;統一軟件過程)是由Rational Software公司(后被IBM收購)開發的一種迭代式軟件開發過程框架。它結合了傳統瀑布模型的系統性和敏捷方法的靈活性&#xff0c;為中大型軟件項目提供了全面的開發方法論。 RUP不僅僅是一種過程…

DeepSeek賦能電商,智能客服機器人破解大型活動人力困境

1. DeepSeek 與電商客服結合的背景 1.1 電商行業客服需求特點 電商行業具有獨特的客服需求特點&#xff0c;這些特點決定了智能客服機器人在該行業的必要性和重要性。 高并發性&#xff1a;電商平臺的用戶數量龐大&#xff0c;尤其是在促銷活動期間&#xff0c;用戶咨詢量會…