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类,内部使用RenderPass
、FrameGraph
等来组织backend提供的RHI进行渲染,承担最重要的“RenderSystem”的工作。
渲染后端RHI设计
Filament的渲染后端RHI非常轻量,并没有什么复杂的设计,使用起来也相对比较简单。不过它在异步渲染的实现上有一些不太好看但是实用的“奇淫巧技”。
Filament定义了一个RHI空对象基类HwBase,然后派生了一系列的RHI对象,包括HwVertexBuffer
、HwIndexBuffer
、HwProgram
、HwTexture
、HwRenderTarget
等等。基本上和其他诸多渲染框架采用的是类似的概念,然后不同的后端渲染(OpenGL/Vulkan/Metal)继承这些对象,进行了不同后端的实现,如基于HwProgram
派生了OpenGLProgram
、VulkanProgram
以及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。RenderTarget
、Program
、PipelineState
、Primitive
等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_API
、DECL_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对象。
在使用上,CommandStream
和Driver
具有基本一致的函数,利用Filament的backend去实现异步渲染时,相对同步渲染,只需要以下几步:
- 创建
CommandBufferQueue
,然后利用Driver
实例和CommandBufferQueue
中的CircularBuffer
去构造一个CommandStream
。 - 创建一个渲染线程,在渲染线程中循环等待渲染指令,然后执行渲染指令。在必要的时候进行退出。
- 在另外的线程里面像使用
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();
}
其主要使用流程为:
- beginFrame开启一帧渲染。beginFrame的时候,需要指定一个SwapChain,决定了最终渲染输出的位置。
- render执行一帧渲染。render执行一帧渲染的时候,不是直接就进行了渲染,而是构建FrameGraph,并进行compile,然后execute。
- endFrame结束一帧渲染
Renderer的每一次渲染,都是执行一次renderJob(这个函数比较复杂,理论上应该拆一下)。它进行的工作,主要就是构建一个FrameGraph,通过对其进行“编译”,再执行,以达到优化渲染的目的。FrameGraph的构建看起来非常复杂,主要进行的工作是将外部设置的信息,转换成FrameGraph中的ResourceNode
、PassNode
、VirtualResource
等列表,并在此过程中,构建DependencyGraph
,以明确PassNode
、ResourceNode
的依赖关系。
FrameGraph的“编译”(compile
),主要做了以下事情:
- 遍历它所包含的所有的
PassNode
。对于每个PassNode
, 会通过DependencyGraph
,去获取它所依赖的资源,并把这些资源信息注册到PassNode
当中。RendererTarget
的数据的更新在资源注册后,通过调用PassNode.resolve
进行。在资源信息注册到PassNode
时,每个资源会记录并更新最早使用它的节点和最晚使用它的节点, 以便后续根据这个信息,来进行真实资源的创建和销毁。 - 而后,资源列表会被遍历,然后借助资源中记录的最早和最晚使用它的节点信息,来把资源直接挂载到相应节点的资源构造(
Resource.devirtualize
)和资源析构列表(Resource.destroy
),这样就后续就可以方便合理的进行资源的按需创建和销毁。 - 遍历所有的资源节点,解析它们的用途(
Resource.usage
),后续资源进行真实资源创建时(Resource.devirtualize
),需要用到。
FrameGraph的“执行”(execute
),也是遍历它所包含的所有PassNode,针对每个PassNode进行一下工作:
- 资源准备,
VirtualResource.devirtualize
- RenderPassNode执行
- 根据RenderPassData列表,进行必要的RenderTarget的构造
- mPassBase->execute,将指令进行部分解析,转换成RHI指令数据,加入到Engine下的CommandStream中,由CircularBuffer进行存储和管理。
- 析构前面构造的RenderTarget
- 资源析构,
VirtualResource.destroy
需要注意的是,其中的资源构造和资源析构,并不一定是在一次循环里对同一个资源进行。而是根据需要,在资源不再被后续的PassNode使用时,才会被析构。
在异步模式下,filament的FEngine
构建时候,会启动一个渲染线程,用来通过CommandStream
对应的CommandBuffferQueue
,来循环从CircularBuffer
中获取指令列表,让真正的Driver
来进行执行.执行完成后CircularBuffer
中相应的指令空间就会被回收,用来接受CommandStream
给的新指令。
ECS的实现
前面说到,Filament上层是一个ECS的设计。我们将其拆开来理解。引擎中的Entity结构非常简单,它在内存中实际上就是一个unit32_t,代表的Entity的索引,它本身并不持有Component的数据,相对来说非常简单,重点在于Component
和System
。
Filament中的Component更多的是一个概念,每类Component都有一个对应的Manager,Manager包含了:
- Component的数据结构
- 向Entity中增删此Component。实际上Component的数据实例,是由Manager持有和管理,并记录和Entity的关联。
- 查询某个Entity,是否具备Component。
- 更新Entity中对应Component的数据
Filament中的ComponentManager包括CameraManager
、LightManager
、RenderableManager
、TransformManager
。这些Manager的实现大同小异,都是依赖utils::SingleInstanceComponentManager
、utils::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]
相关文章:
Filament引擎(一) ——渲染框架设计
filament是谷歌开源的一个基于物理渲染(PBR)的轻量级、高性能的实时渲染框架,其框架架构设计并不复杂,后端RHI的设计也比较简单。重点其实在于项目中材质、光照模型背后的方程式和理论,以及对它们的实现。相关的信息,可以参考官方…...
区间带边权并查集,XY4060泄露的测试点
目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 码蹄集 二、解题报告 1、思路分析 关于带边权并查集:并查集&…...
虚幻引擎5-Unreal Engine笔记之Pawn与胶囊体的关系
虚幻引擎5-Unreal Engine笔记之Pawn与胶囊体的关系 code review! 文章目录 虚幻引擎5-Unreal Engine笔记之Pawn与胶囊体的关系1. 什么是Pawn?2. 什么是胶囊体(Capsule Component)?3. Pawn与胶囊体的具体关系(1&#x…...
USB学习【11】STM32 USB初始化过程详解
1.USB HAL库里面的结构体 为了管理USB,HAL首先构建了一下几个结构体 1.1 USBD设备结构体 USB用到的全局变量,保存了USB生命周期的全部信息。 1.2 USBD PCD底层硬件操作相关结构体 1.3 USB 配置结构体 USB速度、PHY接口类型、端点0参数等 1.4 端点配置…...
Estimation(估算):业务分析师的“不确定性对抗术”
在变化中给出最靠谱的预判。 当面对项目排期模糊、资源计划混乱、老板催问“多久能搞定”的时候, 我总会说:“别着急,我们先做个 Estimation。” 因为,没有靠谱的估算,承诺和资源分配就是空中楼阁。 什么是 Estimati…...
【MyBatis-11】MyBatis批处理:提升数据操作性能的利器
1. 批处理概述 在数据密集型应用中,频繁的单条数据操作会导致严重的性能问题。MyBatis批处理技术通过将多个SQL语句组合成一个批处理单元,显著减少与数据库的交互次数,从而大幅提升数据操作效率。 1.1 为什么需要批处理? 减少网…...
MyBatis 核心技术详解:从连接池到多表查询
一、MyBatis 连接池:提升数据库访问效率 1. 连接池的本质与作用 本质:连接池是存储数据库连接的 “容器”,负责创建、管理连接,避免频繁创建 / 销毁连接带来的性能损耗。核心问题:若无连接池,每次执行 SQ…...
2025.05.17得物机考笔试真题第一题
📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 01. 魔法浮石逃生记 问题描述 LYA 不慎闯入了一片禁忌湖泊,现在她需要踩着湖中的魔法浮石迅速逃离。湖中有 n n n...
时序数据库、实时数据库与实时数仓:如何为实时数据场景选择最佳解决方案?
随着物联网、金融交易、在线游戏等场景对实时数据处理需求的增长,市场上涌现出多种专门针对实时数据处理的数据库解决方案。然而,面对时序数据库、实时数据库和实时数据仓库这三种看似相似的技术,许多技术决策者常常感到困惑:它们…...
构建一个“湖仓一体”(Data Lakehouse)系统
构建一个“湖仓一体”(Data Lakehouse)系统,关键是融合数据湖(Data Lake)的灵活性与数据仓库(Data Warehouse)的高性能分析能力。下面是构建流程的核心步骤: 一、总体架构设计 分层架…...
【C++】尾置返回类型(Trailing Return Type)总结
尾置返回类型(Trailing Return Type)是 C11 引入的一种函数返回类型声明方式,允许将返回类型放在函数参数列表之后,使用 -> 符号指定。这种语法在模板编程、Lambda 表达式和复杂类型推导时特别有用。 1. 基本语法 auto func(参…...
[人月神话_6] 另外一面 | 一页流程图 | 没有银弹
另外一面(The other face) 计算机程序是人类向机器传递信息的一种方式,为了确保意图能够被无言的机器准确理解,程序采用了严格的语法和精确的定义。(这就需要 我们有严密的逻辑思维) 然而,除了…...
GO学习指南
GO学习指南 主题一 go语言基础知识讲解 go语言面向对象编程 go语言接口详解 go语言协程 后续内容请大家持续关注,每月一主题,让各位读者能零基础、零成本学习go语言...
【机器学习】逻辑回归
文章目录 一、逻辑回归概述1.定义2.原理 二、Sigmoid函数三、梯度上升算法四、实验1.代码2.运行结果3.实验小结 一、逻辑回归概述 1.定义 Logistic回归是一种广义线性回归(generalized linear model),因此与多重线性回归分析有很多相同之处…...
Nginx配置与命令
Nginx 配置文件基础 全局块(Main Context):配置影响全局的参数,如用户、进程数、日志路径等。 user nginx; # 运行Nginx的用户和组 worker_processes auto; # 工作进程数(通常设为CPU核心数&…...
测试--测试分类 (白盒 黑盒 单元 集成)
一、按照测试目标分类(测试目的是什么) 主类别细分说明1. 界面测试UI内容完整性、一致性、准确性、友好性,布局排版合理性,控件可用性等2. 功能测试检查软件功能是否符合需求说明书,常用黑盒方法:边界值、…...
工作流介绍
了解工作流对大模型进行高质量工作的辅助意义学会复现吴恩达博士的翻译工作流开源项目了解构成大模型工作流系统的关键元素学会搭建一个更复杂的业务场景工作流 一、为什么我们需要工作流? ❓ 什么样的职场打工人是合格的打工人? 反应快,理…...
学习黑客Active Directory 入门指南(五)
Active Directory 入门指南(五):管理工具、安全基础与学习路径 🛠️🛡️📚 大家好!欢迎来到 “Active Directory 入门指南” 系列的最后一篇。在前四篇中,我们已经全面探讨了Active…...
【第三篇】 SpringBoot项目中的属性配置
简介 SpringBoot支持多种配置文件格式,包括application.properties、yml和yaml等。本文章将详细介绍这三种配置文件的内容格式和详细用法,以及在程序中如何对配置文件中的属性进行读取。文章内容若存在错误或需改进的地方,欢迎大家指正&#…...
处理金融数据,特别是股票指数数据,以计算和分析RSRS(相对强度指数)
Python脚本,用于处理金融数据,特别是股票指数数据,以计算和分析RSRS(相对强度指数)指标。以下是代码的逐部分解释: 1. **导入库**: - `pandas`:用于数据处理和CSV文件操作。 - `numpy`:用于数值计算。 - `ElasticNet`:来自`sklearn.linear_model`,用于线性…...
C++面试2——C与C++的关系
C与C++的关系及核心区别的解析 一、哲学与编程范式:代码组织的革命 过程式 vs 多范式混合 C语言是过程式编程的典范,以算法流程为中心,强调“怎么做”(How)。例如,实现链表操作需手动管理节点指针和内存。 C++则是多范式语言,支持面向对象(OOP)、泛型编程(模板)、函…...
Linux云计算训练营笔记day10(MySQL数据库)
Linux云计算训练营笔记day10(MySQL数据库) 目录 Linux云计算训练营笔记day10(MySQL数据库)ifnull别名聚合函数group byHAVING 子查询关联查询 ifnull 在DQL语句中可以使用函数或表达式 函数 IFNULL(arg1,arg2) 如果arg1为NULL,函…...
深度解析:AWS NLB 与 ALB 在 EKS 集群中的最佳选择
前言 AWS 提供多种弹性负载均衡器,包括应用程序负载均衡器 (ALB)、网络负载均衡器 (NLB)、网关负载均衡器 (GWLB) 和经典负载均衡器 (CLB)。本文重点介绍 ALB 和 NLB,因为它们是 EKS 集群最相关的选项。 在确定合适的负载均衡器类型时,需要…...
nginx模块使用、过滤器模块以及handler模块
一、如何使用nginx的模块 1.ngx_code.c: #include "ngx_config.h" #include "ngx_conf_file.h" #include "nginx.h" #include "ngx_core.h" #include "ngx_string.h" #include "ngx_palloc.h" #include "n…...
基于PageHelper的分页查询
基于PageHelper的分页查询 ‘PageHelper是基于java的一个开源框架,用于在MyBatis等持久层框架中方便地进行分页查询操作。它提供了一组简单易用的API和拦截器机制,可以帮助开发者快速集成和使用分页功能。 PageHelper的主要功能包括: 分页…...
Linux518 YUM源仓库回顾(需查)ssh 服务配置回顾 特定任务配置回顾
计划配仓库YUM源 为什么我在/soft文件夹下 使用yum install --downloadonly --downloaddir /soft samba 为什么文件夹下看不到samba文件 exiting because “Download Only” specified 计划过 计划配SSH 参考 ok了 计划配置特定任务解决方案 code: 两端先配好网络 测试好s…...
AI 制作游戏美术素材流程分享(程序员方向粗糙版)
AI 制作游戏美术素材分享(程序员方向粗糙版) 视频讲解: 抖音:https://www.douyin.com/user/self?from_tab_namemain&modal_id7505691614690561295&showTabpost Bilibili: https://www.bilibili.com/video/BV1ojJGzZEve/ 写在最前面: 本方法比较粗糙,只对对美术风…...
山东大学计算机图形学期末复习12——CG13下
CG13下 BSP树 BSP (Binary Space Partition)表示二叉空间分割。 使用这种方法可以使我们在运行时使用一个预先计算好的树来得到多边形从后向前的列表,它的复杂度为O(n)。 它的基本思想是基于这样一个事实:任何平面都可以将空间分…...
Muduo网络库大总结
Muduo网络库大总结 目录 目的知识储备IO模型 阻塞与非阻塞五种IO模型 epoll原理 select/poll的缺点epoll的优势LT与ET模式 Reactor模型muduo核心模块扩展功能 目的 理解阻塞、非阻塞、同步、异步的概念掌握Unix/Linux五种IO模型深入理解epoll原理及优势掌握Reactor模型设计学…...
LLMs:《POE报告:2025年春季人工智能模型使用趋势》解读
LLMs:《POE报告:2025年春季人工智能模型使用趋势》解读 导读:2025年5月13日,该报告基于 Poe 平台的用户数据,分析了 2025 年春季人工智能模型的使用趋势。报告指出,人工智能格局快速演变,通用文…...
机器学习(13)——LGBM(2)
一、LightGBM算法简介 (一)背景 机器学习中的树模型 在机器学习领域,基于树的模型(如决策树、随机森林、梯度提升树等)是非常重要的算法类别。它们具有很强的可解释性,能够很好地处理非线性关系ÿ…...
翻到了一段2005年写的关于需求的文字
那时的俺还很稚嫩,很多东西都不懂。 另外 hfghfghfg其实是俺的一个马甲,早年间在delphibbs时用的。 来自:hfghfghfg, 时间:2005-01-20 13:16, ID:2971188我到客户那里的情况 一边要和他聊天 一边改报表。 一张报表 …...
MCP - Cline 接入 高德地图 Server
文章目录 一、准备1、注册、认证高德开放平台账号2、创建应用、获取 Key3、用量管理2、Cline 配置模型 二、接入三、测试官方测试 - 出行规划专属地图 四、关于 高德 MCP Server - AI时代的出行服务中台1、产品定位2、技术架构亮点3、核心API能力矩阵4、开发者优势5、典型应用场…...
Linux的MySQL头文件和找不到头文件问题解决
头文件 #include <iostream> #include <mysql_driver.h> #include <mysql_connection.h> #include <cppconn/statement.h> #include <cppconn/resultset.h> #include <cppconn/prepared_statement.h> #include <cppconn/exception.h&g…...
进程和线程的区别和联系
二者概念 进程 运行起来一个程序就会在操作系统产生一个或多个进程 进程属于软件资源。 进程是操作系统中资源分配的基本单位。 每个进程拥有独立的 内存空间、文件描述符、系统资源。 进程之间相互隔离,一个进程崩溃不会直接影响其他进程。 操作系统管理进程…...
SHAP分析图的含义
1. 训练集预测结果对比图 表征含义: 展示模型在训练集上的预测值(红色曲线)与真实值(灰色曲线)的对比。通过曲线重合度可直观判断模型的拟合效果。标题中显示的RMSE(均方根误差)量化了预测值与…...
PointNet++:点云处理的升级版算法
在三维计算机视觉和机器学习领域,点云数据的处理一直是一个关键问题。点云是由一系列三维坐标点组成的集合,这些点可以描述物体的形状和结构。然而,由于点云的无序性和不规则性,传统的处理方法往往难以直接应用。PointNet算法的出…...
PostGIS实现矢量数据转栅格数据【ST_AsRaster】
ST_AsRaster函数应用详解:将矢量数据转换为栅格数据 [文章目录] 一、函数概述 二、函数参数与分组说明 三、核心特性与注意事项 四、示例代码 五、应用场景 六、版本依赖 七、总结 一、函数概述 ST_AsRaster是PostGIS中用于将几何对象(如点、线…...
【PyQt5实战】五大对话框控件详解:从文件选择到消息弹窗
对话框是人机交互的重要组件,PyQt5提供了一系列标准对话框满足不同场景需求。本文将深入解析QDialog及其子类的使用方法,助你快速掌握GUI开发核心交互功能。 对话框基础:QDialog QDialog是所有对话框的基类,支持模态/非模态两种…...
机器学习-人与机器生数据的区分模型测试 - 模型选择与微调
内容继续机器学习-人与机器生数据的区分模型测试 整体模型的准确率 X_train_scaled pd.DataFrame(X_train_scaled,columns X_train.columns ) X_test_scaled pd.DataFrame(X_test_scaled,columns X_test.columns)from ngboost.distns import Bernoulli # 模型训练和评估 m…...
学习黑客Active Directory 入门指南(四)
Active Directory 入门指南(四):组策略的威力与操作主机角色 📜👑 大家好!欢迎来到 “Active Directory 入门指南” 系列的第四篇。在前几篇中,我们已经构建了对AD逻辑结构、物理组件、关键服务…...
十一、STM32入门学习之FREERTOS移植
目录 一、FreeRTOS1、源码下载:2、解压源码 二、移植步骤一:在需要移植的项目中新建myFreeRTOS的文件夹,用于存放FREERTOS的相关源码步骤二:keil中包含相关文件夹和文件引用路径步骤三:修改FreeRTOSConfig.h文件的相关…...
Spring ioc和Aop
IOC 在传统Java当中,我们的对象都需要new关键字来生成,这在面对很多对象的情况产生了不必要的麻烦,因为我不需要在一个项目中一直做重复的事情,那怎么办把,自然而然的一些好的框架就诞生了,避免我们去做这…...
动态内存管理2+柔性数组
一、动态内存经典笔试题分析 分析错误并改正 题目1 void GetMemory(char *p) {p (char *)malloc(100); } void Test(void) {char *str NULL;GetMemory(str);strcpy(str, "hello world");printf(str); } int main() {Test();return 0; }错误的原因: …...
USB传输速率 和 RS-232/RS-485串口协议速率 的倍数关系
一、技术背景 RS-232:传统串口标准,典型速率 115.2 kbps(最高约 1 Mbps)。RS-485:工业串口标准,典型速率 10 Mbps(理论最高可达 50 Mbps)。USB:不同版本差异巨大&#x…...
多线程代码案例-4 线程池
1、引入 池是一个非常重要的概念,我们有常量池,数据库连接池,线程池,进程池,内存池…… 池的作用: 1、提前把要用的对象准备好 2、用完的对象也不立即释放,先留着以备下次使用,提…...
JSON Schema 高效校验 JSON 数据格式
在数据交换和API开发中,JSON 已成为最流行的数据格式之一。但你是否遇到过这些困扰? 接收的JSON字段缺失关键数据?数值类型意外变成了字符串?嵌套结构不符合预期? JSON Schema 正是解决这些问题的利器。本文将带你全…...
机器学习09-正规方程
机器学习笔记:正规方程(Normal Equation) 概述 正规方程是线性回归中求解参数的一种解析方法。它基于最小化损失函数(如最小二乘法)来直接计算出参数的最优值。在机器学习中,这种方法尤其适用于特征数量不…...
Java大师成长计划之第26天:Spring生态与微服务架构之消息驱动的微服务
📢 友情提示: 本文由银河易创AI(https://ai.eaigx.com)平台gpt-4-turbo模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。 在现代微服务架构中,服务…...
Linux 文件(1)
1. 文件 1.1 文件是什么 一个文件,是由其文件属性与文件内容构成的。文件属性又称为一个文件的元数据,因此如果一个文件,内容为空,这个文件依然要占据磁盘空间。 1.2 文件在哪里 一个文件,如果没有被打开ÿ…...