游戏引擎学习第240天:将渲染器移至第三层
这节又枯燥又无聊简直了
回顾并为今天的内容做铺垫
昨天我们说到,想对渲染器和平台层的集成方式做一些修改。我们之前简单讲了一下修改的目的:我们希望游戏本身不再直接调用 OpenGL 的渲染代码,而是只生成一组渲染指令缓冲区,然后 OpenGL 再去解析并执行这些指令,完成最终的绘制。我们现在的目标就是把这个结构搭建起来。
这其实不是一个复杂的目标,也不是个什么特别奇怪的需求,应该很容易理解,也希望大家能跟得上思路。
当前我们已经基本上通过 OpenGL 实现了渲染流程,但这个流程还不是我们想要的架构方式。除了架构问题,我们还有其他一些问题,比如现在贴图还没有正确处理,我们并没有按照理想方式上传纹理,还有很多 OpenGL 功能我们目前还没有实现,所以在后续我们还会继续完善 OpenGL 的支持。
但在那之前,我们先来完成架构方面的调整,然后再继续处理 OpenGL 的其它问题。现在就开始着手这个改动,看具体需要怎么做。
修改 game_platform.h:将 game_offscreen_buffer
替换为 game_render_commands
我们现在的目标是让同一个 RenderGroup
能够贯穿整个渲染流程,也就是从头到尾只使用一个渲染组来完成所有渲染操作。
目前在调试帧中可以看到,游戏逻辑和调试渲染部分是分别创建了两个不同的 RenderGroup
,也就是游戏用一个,调试用一个,而它们都是在游戏内部被创建和销毁的。这种做法让游戏本身依赖了 OpenGL 渲染调用,这是我们不想要的。
我们现在要做的是,把渲染组的控制权从游戏内部移出来,由平台层提供渲染组,传给游戏使用。这样游戏只负责往 RenderGroup
里写指令,真正的绘制动作由平台层完成。
首先需要修改 game_platform.h
,让平台层知道 RenderGroup
的存在,至少有一定程度的认知。我们先从简单的修改开始,比如把传入游戏的 GameOffscreenBuffer
移除,改成传入一个 RenderGroup
。这个修改本身不复杂,但涉及到的代码范围比较广,需要花时间调整。
GameOffscreenBuffer
是现在用于描述像素缓冲区的结构,而我们想要改成传入一个 RenderGroup
。这样游戏调用 GameUpdateAndRender
时就不再需要处理像素数据,而是只写入渲染命令,实际的图像生成由平台负责。
我们也将原来放在游戏内部的 RenderGroup
分配逻辑移到平台层,比如放到 win32_game.cpp
的 UpdateAndRender
调用流程中。平台先创建 RenderGroup
,然后把它传入游戏中使用。
接着我们发现,游戏中还涉及资源系统,比如调用纹理、字体等,这些都依赖某些结构,因此如果整个 RenderGroup
都挪到平台层,会导致资源访问变得困难,所以不能完全把整个渲染系统拉出来。
为了解决这个问题,我们决定只把“渲染指令缓冲区”(PushBuffer)作为桥梁结构放在平台层,而把资源管理仍然留在游戏中。这样游戏生成渲染指令,通过 PushBuffer 写入,而平台层只读取这个缓冲区并执行渲染操作。这样既避免了平台层对资源的依赖,又完成了渲染职责的拆分。
我们将这种新结构命名为 GameRenderCommands
,它包含画面宽度、高度以及实际的 PushBuffer。我们定义了 InitializeRenderCommands
函数,接收需要的信息并为 PushBuffer 分配内存,初始化结构体。
这个初始化过程由平台层完成,因此任何平台只要实现这个简单的初始化过程就能兼容整套渲染逻辑。后续游戏调用就只需要向这个结构写入渲染命令,无需知道底层是 OpenGL 还是其他渲染 API,实现了良好的解耦。
整体来说,我们正在把渲染模块从游戏逻辑中剥离出来,拆分成三层结构:
- 游戏逻辑层(只写渲染命令)
- 平台中介层(负责创建/管理指令缓冲区)
- 渲染执行层(比如 OpenGL,读取并执行命令)
这个架构改动的方向就是让渲染操作变成纯粹的数据驱动,游戏只输出描述渲染内容的指令,而具体怎么呈现完全交由平台决定。这样做既增强了灵活性,也便于移植和维护。
修改 game_platform.h:引入 InitializeRenderCommands
函数
我们现在处理的是 GameRenderCommands
的初始化细节部分。
我们已经设置好了一个渲染指令缓冲区(PushBuffer),这是整个渲染过程的核心数据结构。它用于记录游戏逻辑阶段生成的渲染命令,供后续平台层的渲染代码读取并执行。
我们也设定了画面的宽度和高度,还有其它一些相关参数,比如像素宽高比(Width/Height)和坐标系中的原点位置(OriginX / OriginY)等。这些参数用于设定渲染时的基础空间信息,确保绘制出来的内容具有正确的比例和位置。
我们把这些参数组合在一起构成了一个 RenderCommands
的结构体,它是这个初始化流程的输出,供游戏主循环使用。
此时,为了让这个结构易于调用,我们将其设计为一个 inline
函数。这样我们在平台层或者任意其他调用方就可以方便地初始化 RenderCommands
,不需要调用复杂的构造函数或处理繁琐的初始化逻辑。
通过将初始化逻辑封装为 inline
函数,我们避免了额外的函数开销,同时也让代码变得更加清晰和模块化。
我们可以在调用的时候直接传入需要的参数,比如:
- 渲染缓冲区大小
- 画面宽高
- 内存分配方式(如 VirtualAlloc)
然后这个函数就能自动初始化好一个完整的 GameRenderCommands
实例,并准备好供游戏逻辑填充渲染命令。
这种方式不仅提升了灵活性,也增强了渲染流程的可控性和可移植性,未来如果我们更换渲染 API(例如从 OpenGL 改为 Vulkan),只需要调整平台层的解析逻辑,而游戏逻辑部分完全不变。这就是我们现在要实现的三层架构中的重要一环。
修改 game_platform.h:指出 game_render_commands
中的 #inline
不起作用,接着将其改为 #define
我们现在遇到的一个问题是:如果继续使用 C 语言来实现这些渲染命令初始化逻辑,那么 inline
函数就不太适用了,因为 inline
在 C 中兼容性和表现不是特别理想,特别是跨平台或结构不同的编译器下。
因此我们考虑将初始化逻辑改为宏(#define
),来确保渲染命令结构体的初始化能够方便地完成,并且可以避免调用者遗漏某些重要字段。我们的目标是对 RenderGroup
的分配逻辑(也就是 AllocateRenderGroup
)进行提取和封装,而这个分配函数做了很多关键初始化操作,比如:
- 初始化 push buffer 的大小、内存基址等
- 设置渲染指令计数为零
- 设置排序缓冲区的指针
- 清除上一次渲染留下的状态
- 设定屏幕宽度和高度
- 分配 push buffer 空间
- 设置相关结构体字段的初值等
我们希望这些初始化步骤都被自动完成,而不是每次都手动设置。
为了实现这一点,我们提取出所有必要的字段,比如:
PushBufferSize
PushBufferBase
PushBufferElementCount
SortEntryAt
RenderWidth
RenderHeight
然后用一个宏来封装这些设置操作,可能长这样:
#define RenderCommandStruct(MaxPushBufferSize, PushBuffer, Width, Height) \{Width, Height, MaxPushBufferSize, 0, (uint8 *)PushBuffer, 0, MaxPushBufferSize};
这样我们就可以在任意地方快速、安全地初始化一个 RenderCommands
实例,只需要传入缓冲区大小、基址、宽高就可以了,所有其它字段都会自动设置为初始状态,防止忘记初始化某个字段。
另外还考虑到如果继续保留 ground chunk(地形块缓存)机制的话,这部分也可能需要额外的初始化逻辑,但我们已经意识到这部分机制从一开始就比较麻烦,意义不大,所以也在倾向于将其完全移除,从而让渲染逻辑更为纯粹和简洁。
总之,我们的目标是:
- 让渲染命令的初始化更可靠、更清晰
- 从游戏逻辑中分离平台细节
- 保持平台层对渲染资源的控制
- 提高整体渲染流程的可维护性和可扩展性
使用宏的方式能够在 C 语言下很好地完成这些目标。接下来只需在平台层统一调用这个宏,就可以快速、安全地完成渲染结构的初始化。
修改 win32_game.cpp:将 RenderCommands
传递给所有需要它的部分
现在我们进入了渲染命令使用的实际流程阶段。整个思路是,在执行 GameUpdateRender
的时候,我们不再直接操作某个后备缓冲区,而是传入一个 RenderCommands
结构体,也就是渲染命令缓冲区。游戏逻辑和调试系统分别向其中写入它们要渲染的内容,最后统一处理。
具体流程如下:
1. 传入渲染命令缓冲区
在游戏逻辑执行(GameUpdateRender
)时,我们会传入 RenderCommands
对象。这个对象包含了 push buffer、排序表、尺寸等所有与当前帧渲染相关的数据结构。
2. 调试渲染同样写入命令
调用调试绘制(比如 DebugFrame
)时,也不再直接写入屏幕或后备缓冲区,而是统一向 RenderCommands
中写入命令,保持与游戏逻辑一致的路径。
3. 最终统一渲染
当所有逻辑(包括调试和游戏)都执行完毕并填充好 RenderCommands
,我们就开始实际的渲染流程。这个渲染是在调用 DisplayBufferInWindow
的时候完成的:
DisplayBufferInWindow
不再处理具体的 back buffer。- 它改为接收
RenderCommands
,并直接基于这些命令执行渲染。
4. 处理渲染命令排序
在真正开始渲染前,我们需要对所有渲染命令进行排序(比如根据深度、图层等)。为了实现这个步骤:
- 在
RenderCommands
中预留一块临时内存(Temp Memory),而不是每次临时分配。 - 排序过程使用这块内存来构造排序缓冲区。
5. 调用具体渲染路径
排序完成后,根据平台类型执行相应渲染函数:
- 如果是硬件渲染路径(OpenGL 等),调用
RenderToOpenGL
。 - 如果是软件渲染路径,调用
TiledRenderGroupToOutput
。
这两个函数都从 RenderCommands
中读取数据,不需要平台层再进行状态配置或转换,完全解耦。
6. 总结结构优化的意义
- 统一接口:无论是游戏逻辑还是调试系统,都通过同一个
RenderCommands
接口写入命令。 - 去平台依赖:平台不再关心具体后备缓冲区管理,只要调用最终渲染函数即可。
- 排序流程合理:排序和渲染解耦,有临时内存支持。
- 更清晰的数据流:所有渲染数据都聚集在一个结构体中,方便调试与分析。
- 易于扩展和维护:不同渲染路径只需实现对应的最终渲染函数,不影响整体架构。
这个改动将渲染系统从平台层中抽离出来,转而作为游戏层、调试层的统一输出通道,使渲染逻辑更加模块化、可控且高效。
指出三层架构的出现
我们在很早之前就提到过三层架构的概念,现在这个结构逐渐开始显现出来。我们可以清晰地看到,代码分为三个层次:平台层、游戏逻辑层和渲染层。
平台层负责与操作系统或底层平台的交互,比如窗口管理、输入处理等;游戏逻辑层包含具体的游戏行为和规则;而中间的这一层——渲染层,就是现在开始拆分出来的部分。它专注于图形渲染的实现,例如使用 OpenGL 进行图形绘制,或者是基于软件的渲染方式。
渲染层的主要职责是将游戏逻辑层传来的渲染指令进行实际的图形绘制。通过将渲染的解析和执行放在这一独立层中,我们可以更灵活地替换渲染方式。例如,如果希望支持 DirectX,我们只需要在这一层增加对应的渲染逻辑,然后通过一个开关或判断语句来选择当前使用哪种渲染方式即可。这种结构带来的优势是清晰的模块划分,使得各层之间解耦,有利于后期的扩展和维护。
总之,三层结构中的第三层,也就是渲染层,现在开始独立出来,形成了一个可替换、可扩展的部分。其他层依旧保持原样,只是渲染的执行和细节处理交由这一层负责,从而实现了清晰的功能分离与灵活的架构设计。
修改 win32_game.cpp:根据条件选择通过硬件显示软件渲染结果,或使用 StretchBlt
我们目前的任务是对渲染输出部分进行整理和重构,以适应更清晰的渲染路径划分。我们已经明确知道需要进行排序处理等工作,因此第一步是将相关的旧代码裁剪掉,清除不再需要的部分。
接下来,我们实现了一个 TiledRenderGroupToOutput 的流程,用于执行瓦片化渲染。这个部分负责将渲染结果输出到指定目标。完成这一步之后,会紧接着处理图像在窗口上的拉伸显示,也就是 StretchDIBits
之类的操作,它将在这一阶段完成。
在更底层的硬件加速路径中,我们保留了对 OpenGL 的调用,也就是通过 RenderToOpenGL
的方式进行硬件渲染。这一部分仍然保留,继续发挥作用。而之前用于模拟显示、例如某些早期测试用途的代码已经不再需要,基本可以删除。虽然如此,我们也可以将部分代码保留下来,作为备用路径或者参考用途。
逻辑上,我们现在建立了一个分支判断结构:首先调用 TiledRenderGroupToOutput
,之后根据当前渲染方式的选择(软件渲染还是硬件渲染),决定具体的显示路径。如果是使用 OpenGL,那么会通过硬件路径进行渲染输出并调用 SwapBuffers
完成交换;如果是软件渲染路径,那么我们会调用软件渲染函数,然后将其显示,可以选择通过硬件拉伸或其他方式进行最终显示。
这个设计意味着渲染方式的选择是开放的,软件渲染和硬件渲染是两条互不干扰的路径。我们负责完成渲染过程,但具体如何最终将画面呈现在硬件上(比如窗口系统如何处理)就不是我们关心的范畴了。
最终,这次的主要变化其实就是对渲染路径做了清晰的拆分,其他的都是代码边缘的清理工作,例如删除旧代码、添加判断逻辑等。整体结构依然简单清晰,后续的主要工作就是确保新的路径能够正确编译并运行。以上就是我们当前的全部进展和结构调整目标。
修改 game_opengl.cpp:引入 DisplayBitmapViaOpenGL
函数
我们目前正在逐步完善 OpenGL 渲染路径的实现,并使其与游戏渲染命令系统无缝对接。在这个过程中,我们的核心目标是将原本混杂的渲染逻辑清晰地分层,进一步推动三层架构的实际落地和运作。
首先,我们尝试让 RenderToOpenGL
函数正确地运行。为此,我们考虑将相关的 OpenGL 显示函数整理为内联函数,比如定义 inline void DisplayBitmapViaOpenGL(...)
,这样可以将所有 OpenGL 的渲染代码封装得更清晰,使主逻辑更加易读。
我们不再使用早期渲染路径中的窗口参数或其他无关数据,而是只接收来自游戏层的渲染命令,这些命令结构中已经包含了诸如宽度、高度等基本信息,正是我们所需的关键数据。
我们检查了渲染指令中依赖的内容,发现基本上需要的所有东西都已经存在于命令本身,因此我们只需要简单替换原先的依赖,直接以渲染命令为输入即可。
为了让 OpenGL 能够正确渲染软件生成的图像,我们将 DisplayBitmapViaOpenGL
接收的参数限制为我们支持的格式,比如图像的 pitch
必须为 width * 4
(每像素 32 位),并添加断言确保这一限制被满足,避免混淆和潜在错误。
同时,为了简化 OpenGL 的配置,我们封装了屏幕空间矩阵设置逻辑为 OpenGLSetScreenSpace
函数,集中设置投影矩阵等参数,使主流程更清晰。之后在多个地方可以重复使用这段逻辑,减少冗余代码。
另外,像颜色填充矩形这类操作,我们也封装为更直观的调用,比如调用 OpenGLDrawRectangle
,以取代手动填写顶点数组等底层调用,这样大大简化了 OpenGL 渲染的调用路径。
在处理窗口 WM_PAINT
消息时,我们决定暂时不做处理,因为这时的 OpenGL 上下文可能尚未初始化或状态未知,未来如果有需要再引入备用的缓冲处理机制。
接下来,我们调整了软件渲染路径中的函数结构,比如 TiledRenderGroupToOutput
,准备将其移出原先的游戏渲染组模块,作为通用的、平台无关的渲染处理函数。同时将排序相关的逻辑也移出,与平台无关的功能集中在一个共享模块中,比如 GameRenderShared
,这样可以为不同平台或渲染路径共用。
通过这次重构,RenderQueue
等原本需要通过平台层中转的结构,现在可以直接在平台层完成,避免了平台与游戏层之间的来回数据流动,这样也便于多线程的任务分离,比如未来可能只让平台层负责渲染线程,而让游戏层专注于逻辑和模拟。
同时,我们在传递渲染命令时增加了窗口尺寸参数 WindowWidth
和 WindowHeight
,虽然当前还未完全使用,但为将来支持窗口尺寸与渲染分辨率不一致的处理(比如加黑边、居中显示、裁剪等)提前预留了接口。
最后,对整个游戏渲染模块进行重构,将所有真正执行渲染输出的函数,如排序、实际绘制、输出目标设置等,全部迁移到平台无关的共享模块中,只保留渲染组对象本身和它负责的资产需求等逻辑。这标志着渲染逻辑的第三层完全建立,真正实现平台独立、渲染路径可配置、渲染命令结构化的目标。
整体来看,这一阶段的改动显著提高了代码的结构清晰度和模块化程度,为未来扩展新的渲染路径、平台移植、多线程优化等打下了坚实基础。
黑板讲解:三层架构的结构
我们当前构建的整体架构正是我们早期构想中的“三层架构”的实际体现,并且现在已经逐步形成和投入使用。
整个架构分为三层:
第一层:平台层(Platform Layer)
这一层完全依赖于平台,是与操作系统和底层硬件直接交互的部分。它负责诸如窗口创建、OpenGL 上下文管理、输入获取、多线程管理、文件加载、时间控制等所有与平台相关的操作。它的特点是完全与平台绑定,因此这部分代码在不同系统上通常需要单独实现,比如 Windows 和 Linux 下是完全不同的实现方式。
第二层:游戏层(Game Layer)
这一层完全独立于平台,纯粹进行游戏逻辑的运算。它负责所有的游戏状态更新、实体逻辑处理、物理运算、AI 行为控制等。所有的数据在这里被生成并结构化,比如渲染命令队列就是由这层输出的。这部分代码理论上可以在任何平台上直接运行,无需修改,是最核心的部分。
第三层:渲染层(Render Layer)
渲染层是一个“混合层”,也就是说,它既包含平台相关的内容,也包含平台无关的部分。我们将这层分成两部分来看待:
- 平台相关的部分:例如 OpenGL、Vulkan、Direct3D 等图形 API 的具体调用,这些都是跟平台紧耦合的。
- 平台无关的部分:例如渲染命令的排序、合批、通用光栅化算法、软件光栅化器等,这些算法可以运行在任何平台上,不依赖于底层 API。
这一层作为桥梁连接游戏逻辑和实际的显示设备,它处理如何将游戏层给出的抽象渲染命令真正绘制到屏幕上,同时还可根据平台实现差异化的优化或渲染路径选择。
我们选择将这部分称作“混合层”,是因为它在架构上的地位既不像游戏层那样完全与平台无关,也不像平台层那样完全绑定底层,它具有一定的灵活性和可替换性,是整个系统中扩展性最强的一部分。
这种三层架构带来的最大优势在于模块清晰、职责明确,使得每一层都可以独立开发、测试、替换。例如:
- 更换图形 API 只需要替换渲染层中的平台相关部分;
- 移植到新平台只需重新实现平台层的部分;
- 更改游戏逻辑或添加新功能仅需在游戏层进行,无需触碰渲染或平台代码;
- 渲染优化或添加新特效时,可以在渲染层中自由扩展,而不影响核心逻辑。
最终,这种架构为我们建立一个清晰、可维护、可扩展、跨平台的游戏引擎打下了坚实的基础,也使我们能够更从容地应对未来引擎演进中出现的各种需求和挑战。
修改 game_render_group.cpp:处理编译错误
我们正在进行一项比较大的架构调整,主要目标是简化渲染流程和资源管理的逻辑,尤其是去除冗余的 BeginRender 和 EndRender 流程,同时清理与资源“(generation ID)”相关的机制,优化整个渲染管线的初始化与同步过程。
架构调整的主要方向
-
清理渲染流程的 BeginRender / EndRender 概念
发现当前架构中并没有实际频繁使用 BeginRender 和 EndRender,大多数情况下只是在游戏主循环和调试逻辑中被调用,甚至某些模块根本没有使用。由此推导出:这对函数在当前逻辑中并非必要,完全可以被移除。渲染流程将转向以 RenderGroup 为核心的方式:直接分配、使用并释放 RenderGroup,无需显式地开始和结束渲染阶段。 -
RenderGroup 的职责简化
新设计中,RenderGroup 不再负责资源分配或管理临时内存,也不需要与平台层紧耦合。它的唯一职责是将渲染命令组织好,提交给底层渲染系统即可。这意味着:- 不再需要传入 Arena 或 PushBufferSize;
- 只需要持有渲染命令结构体(GameRenderCommands);
- 只需要知道当前的 Generation ID(用于资源一致性验证);
- 其他临时状态如缺失资源数量可以附带,但不是必须。
-
Generation ID(资源)的简化与集中管理
资源系统中原本通过 Generation ID 追踪哪些资源是“在当前帧有效”的,避免多线程渲染中出现资源提前释放的问题。
现在准备将这部分逻辑从多个地方集中起来,统一在渲染系统初始化(每帧)时推进一次 Generation ID,即“AdvanceGeneration”,不再显式区分 BeginGeneration / EndGeneration。新的流程如下:- 在每帧游戏逻辑开始前,调用 AdvanceGeneration;
- 返回当前帧的 Generation ID;
- 赋值给该帧中所有使用资源的 RenderGroup;
- 在帧结束后进行统一清理。
如果未来不再支持例如“地块(Ground Chunk)”这种复杂的资源流动系统,甚至可以完全移除这套机制,进一步简化代码。
-
调试与字体系统资源访问简化
原先调试系统获取资源是通过外部引用进行的,现在可以直接使用统一传入的 GameAssets 实例,并由 RenderGroup 在创建时持有,因此所有使用者都可以通过 RenderGroup 获取一致的资源引用,无需额外逻辑。
接下来的具体调整
- 将原本的
AllocateRenderGroup
函数改造为BeginRenderGroup
; - 移除其中所有涉及 Arena、内存分配和初始化步骤;
- RenderGroup 将只保存指向 GameRenderCommands 的指针和 Generation ID;
- 所有的渲染操作都通过该结构体完成;
- 保留
EndRenderGroup
,但仅作为逻辑上的结尾,用于统计或后续清理,并非资源释放的必要操作; - 缺失资源统计仍可能保留接口,但不再贯穿各层代码;
- 调整原本依赖 PushBuffer 的接口,统一访问
RenderGroup->Commands
中的实际命令缓冲区。
总结
这次重构本质上是从一个历史遗留的、多层次、多接口的渲染和资源系统中,提炼出一个更为纯粹和现代化的逻辑结构。它让 RenderGroup 成为渲染流程中的核心容器,解耦平台逻辑和资源逻辑,使系统更加清晰、易维护,同时也为未来的性能优化、多线程渲染、调试系统独立等提供了更好的基础。
虽然当前还处于中间状态,很多代码尚未编译通过,但方向已经明确,后续将继续推动剩余部分重构完成。
反正就是改了一大堆东西懒得截图
https://gitee.com/mrxiao_com/2d_game_5/commit/f0d02fd4c8e743a7530885a19fb9da5634c06d86
“这些地面区块从头到尾都特别烦人…”β
我们决定彻底放弃“地块(ground chunks)”的机制。这个系统从一开始就是一个不断制造麻烦的存在,既不直观,也让资源系统变得异常复杂,而且并未带来实际价值。现在我们明确不再希望它出现在游戏中,计划将其彻底移除。这一改变也将带动相关的资源(generation ID)机制的简化和优化。
移除地块系统带来的变化
-
** 简化(main generation ID)**
原先我们用主 来协调资源在帧之间的生命周期,主要是为了解决地块加载、卸载过程中的资源一致性问题。随着地块机制的移除,这套复杂的处理流程也失去了存在的意义。- 将
main_generation_id
添加到transient_state
中; - 后续所有使用者直接从该结构中获取;
- 渲染初始化时(
BeginRenderGroup
)传入该 ID; - 无需在多个阶段显式调用 BeginGeneration / EndGeneration。
- 将
-
渲染流程的整合与清理
开始统一使用BeginRenderGroup
/EndRenderGroup
来标志渲染的生命周期,进一步简化调用层级,明确渲染的结构:- 原先通过
AllocateRenderGroup
、BeginRender
,EndRender
三段流程,现在仅保留前后两个函数; - 去除冗余的
ClearRenderValues
操作; - 渲染中间结构
RenderGroup
只需持有命令缓存(RenderCommands
)和; - 将命令缓存和 以参数方式传入构造函数,顺序统一整理;
- 所有需要渲染命令的地方都直接引用
RenderGroup->Commands
。
- 原先通过
-
取消不必要的内存分配和临时内存管理
在当前重构中发现,原本的临时内存区域(transient memory)在渲染流程中已无实际用途。曾经用于RenderGroup
的内存 Arena 和推送缓存(push buffer)逻辑已经不再使用:RenderGroup
初始化时不再传入 Arena;- 相关的内存参数从函数签名中删除;
- 渲染过程中不再动态申请内存;
- 计划在后续阶段完全移除相关的临时内存结构。
当前状态与后续计划
- 已将
BeginRenderGroup
替代旧的AllocateRenderGroup
和BeginRender
; EndRenderGroup
替代EndRender
;- 对渲染命令的引用统一调整;
- 临时内存和渲染资源清理机制待定,但大概率也将被精简甚至移除;
- 当前部分代码尚未完全编译通过,但逻辑框架已明确,计划下一阶段集中整理编译错误并完成全部迁移;
- 整体目标是减少架构复杂性,提高系统稳定性和可维护性,避免无谓的资源管理开销。
这次调整是一次深度的架构清理,主要清除不必要的复杂资源同步机制和渲染生命周期控制逻辑,为后续系统的可扩展性和可控性打下更稳固的基础。我们将在后续继续完善这一重构,并彻底剥离历史遗留的“地块”逻辑。
修改 game.cpp:继续清理编译错误
我们正在重构渲染系统的接口逻辑,目的是移除 DrawBuffer
的概念,并用更清晰、更合理的机制取而代之。在原有架构中,很多函数都接受一个 DrawBuffer
参数,但实际上大部分时候只是为了获取屏幕的宽度和高度,导致整体结构复杂却并不高效。
渲染目标区域替代 DrawBuffer
- 现阶段我们决定将
DrawBuffer
的使用移除,转而通过RenderCommands
直接获取屏幕的宽度与高度; - 我们引入了一个更明确的输出区域描述机制(例如
ScreenSpec
或类似结构),用来标明目标渲染区域; - 渲染函数不再需要
DrawBuffer
,只需知道输出尺寸,即可根据RenderCommands
中的信息设置视角和投影; - 此举可以减少冗余接口参数,提升模块的清晰度和可维护性。
RenderGroup
初始化与管理简化
- 重构后的渲染流程使用
BeginRenderGroup
和EndRenderGroup
包裹渲染过程; - 所有渲染命令集中在
RenderCommands
中,由RenderGroup
持有; - 初始化
RenderGroup
不再需要传入内存 arena,临时内存逻辑被彻底清除; - 清晰的代际 ID(
MainGenerationID
)依旧保留,以确保渲染资源状态正确同步; - 虽然我们暂时保留了
MainGenerationID
,但已经评估其带来的复杂性远超收益,未来计划逐步移除这一机制。
Debug 与 Game Render 的整合调整
- 调试功能模块(debug state)也迁移到了新的
RenderGroup
架构; - 调试系统中的渲染调用,例如
DebugStart
、DebugGameFrameEnd
,不再依赖GameOffscreenBuffer
,改为仅传入RenderCommands
; - 相应函数签名和调用点都进行了修改,确保渲染命令贯穿整个调试流程;
- 渲染命令由调试系统与游戏主体共同累积,最终统一由平台层处理输出。
平台层接口调整与资源同步
- 在平台层,游戏与调试的渲染命令会合并进入同一套接口;
- 不再需要为每个渲染目标单独维护 buffer;
- 所有渲染资源的准备阶段(资源是否准备齐全)可能转移到渲染前的检测流程中,而不再由具体渲染调用内部处理;
- 当前仅剩下部分平台层接口尚未完全迁移完毕,核心逻辑已就绪。
整体结果与后续任务
- 游戏已经能够成功编译,主要结构清理工作已经完成;
- 渲染命令收敛机制、调试逻辑和平台层接口之间的协作已初步理顺;
- 后续将继续完成尚未迁移的部分接口,将新的渲染命令流程全面替代原始的
DrawBuffer
流程; - 最终目标是实现一个简洁、高效、易维护的渲染架构,彻底摆脱早期设计遗留问题。
这次的重构为后续开发打下了坚实的基础,也让渲染架构更加直观、清晰,更贴合实际需求。后续还需完成资源准备与平台层渲染流程的整合,进一步提升整体的性能与灵活度。
Q&A
那渲染器的代码就不再支持热重载了吗?
目前我们已经不再为渲染代码保留热重载机制。虽然现在不支持,但如果未来有需要,依然可以实现热重载,这完全取决于是否需要该功能。
关于热重载的可行性与实现方式
- 如果希望重新启用热重载机制,只需要将相关模块(例如
GameOpenGL
和资源访问逻辑)从平台层迁移到游戏代码中; - 然后在平台层保留一个指向这些模块的指针,通过指针调用实现热重载;
- 这样的设计仍然保留热重载的灵活性,但不会影响当前的系统稳定性;
- 当前阶段不做这一改变,是因为暂时没有热重载的需求,因此也不增加额外的复杂性;
- 一旦决定启用热重载机制,只需做出小范围调整即可,主要是结构上的指针传递改动。
当前结构的选择原因
- 热重载虽然灵活,但也增加了接口复杂性与维护成本;
- 现阶段重构的核心目标是清晰、可控和稳定,因此选择先行简化架构;
- 资源如
GameOpenGL
暂时仍在平台层初始化,并不通过指针下发给游戏层; - 我们保留了将来调整的能力,但不会预先引入可能暂时不需要的特性。
总结来说,我们现在不使用渲染代码的热重载机制,但架构上完全可以在未来恢复它,只需要将相关资源交由游戏层管理,并将指针返回给平台层。这种设计既保留了未来的灵活性,也让当前系统更清晰、更稳定。
你对那些地面区块似乎有点怨念,是不是特别讨厌它们?
我们对“ground chunks”(地面区块)一直持保留态度,甚至可以说已经不太愿意继续保留它们。
不再需要地面区块的原因
- 引入了不必要的多线程复杂性:这些地面区块强制引入了大量多线程处理的问题,而如果没有它们,我们根本无需面对这些额外的线程管理与同步挑战;
- 没有实际收益:如果这些复杂性换来的是某些有意义的价值,比如可以在后台执行复合渲染、共享渲染路径等高性能功能,那倒是值得,但现在我们并没有获得这样的好处;
- 功能被闲置:目前来看,游戏本身似乎并没有真正使用这些地面区块。它们的存在仅仅是带来了系统结构上的额外负担;
- 教育意义已经达成:虽然一开始保留这些复杂结构是出于教学和学习的目的,为了展示多线程和资源管理技巧,但现在也已经充分了解了它们的处理方式;
- 性价比低:继续维护这些地面区块,需要花费大量精力去清理、简化它们带来的多线程问题,而这些投入并不能带来与之匹配的收益;
- 可以选择更有意义的方向投入开发时间:如果要花时间解决这些地面区块的问题,不如将时间用于更直接提升游戏功能或体验的模块上,毕竟开发资源和精力有限。
对未来的思考
- 虽然理论上我们可以继续清理并优化这些地面区块,使它们不再那么难以管理,也可能成为一个有趣的技术挑战;
- 但就实用性而言,与其在它们身上花费大量精力,不如彻底剔除,集中精力做真正有用的系统功能扩展;
- 因此目前的倾向是彻底抛弃这部分系统,以换取更简洁、可控、聚焦的代码结构。
总而言之,我们认为地面区块系统已经失去了存在的必要,不仅增加了复杂度,而且没有为项目带来实际好处。与其继续维护这块低价值的遗留代码,不如将时间用于更有意义的游戏开发任务。
你一开始设计地面区块的初衷是什么?后来有变化吗?如果删掉了它们会用什么替代?
我们最初设计 ground chunks(地面区块)的目标,是为了实现一个更加自由的“外部世界”场景。设想中,整个游戏的外部空间应该是开放式的,具有较强的自由度,类似“溅开的地面”、“流动的自然形态”那种不拘泥于规则格子的布局方式。
最初目标:
- 希望外部区域(overworld)能展现出自然、不规则、连续变化的视觉表现;
- 地面区块被设计成独立渲染单元,通过组合和动态加载方式表现复杂地形;
- 设想通过这种机制提升地图的视觉表现力和开放感。
当前的变化:
- 游戏设计重心转变:现在的设计思路发生了变化,游戏本身的玩法结构不再适合“自由拼接”的外部世界;
- 更偏向网格化结构:为了匹配玩法节奏、交互模式和系统设计,外部区域不再适合自由布局,而是应该类似“瓦片地图”的结构——并不是指引擎上的 tilemap 技术,而是指在设计上需要一种明确、规则的空间组织方式;
- 艺术表现需与玩法统一:既然玩法决定了空间结构的规律性,那么美术资源也必须配合,原先“溅开的地面”在新的体系下就显得格格不入;
- 性能与复杂度考量:即使我们还想保留这种视觉效果,其实也没必要通过复杂的地面区块机制来实现。现代硬件渲染路径性能充裕,即便是软件渲染路径,只要有合适的裁剪与组织逻辑,也能在运行时完成这些效果;
- 不再具备技术优势:相比过去,地面区块机制在现在的硬件环境下已经没有了技术上的必要性,反而显得繁琐、拖累系统结构。
替代方案:
- 运行时生成与裁剪:改用运行时构建可视区域的方式,通过渲染命令进行高效裁剪和绘制,直接根据 tile 数据决定哪些区域需要渲染;
- 更紧凑的瓦片系统:构建一种清晰的 tile-based 系统,确保逻辑、资源管理、渲染流程都围绕它展开,降低复杂度;
- 统一的资源调度方式:不再将地面划分成“chunks”异步加载,而是将资源绑定在 tile 层面,以线性或局部调度机制进行控制;
- 保持视觉灵活性:如果未来确实有必要实现某些特殊地形视觉效果,完全可以通过更轻量的纹理动画、shader 技术或局部粒子系统来模拟。
综上,我们放弃 ground chunks 的主要原因在于它的设计理念已与当前的游戏目标背道而驰,不仅无助于实际效果,还增添了系统负担。通过更现代化、灵活且高性能的替代手段,我们可以更好地支撑游戏整体架构,同时提升开发效率和可维护性。
你会不会整理一份“程序员应该知道的东西”列表?我想知道像“排序算法”这种初级必学内容
我们认为,整理一份程序员应当掌握的知识清单确实是一个非常好的想法,尤其是针对初学者,提供一些明确的学习方向和入门路径会非常有帮助。
这个建议的价值:
- 程序员需要掌握的基础知识非常广泛,包括但不限于算法、数据结构、计算机系统原理、编程范式、调试技巧等;
- 初学者常常缺乏明确的学习路线,这种清单可以在学习初期起到“导航”的作用;
- 如果能罗列出常见算法类型(例如排序、查找、图、动态规划等)和基础算法实现,就能帮助很多人建立起基础算法体系;
- 同时还可以涵盖基础数据结构(数组、链表、哈希表、栈、队列、树、图等),以及何时该使用它们;
- 除了理论知识,也可以包括一些实用技能,比如版本控制(如 Git)、调试方法、测试策略、代码风格、编译器工作原理、性能优化基础等;
- 最理想的形式是一个结构化的资源库,按难度或领域分类,便于逐步学习。
实际情况与困难:
- 整理这样一份资源清单本身是一项系统性工程,耗时巨大;
- 需要投入大量时间梳理、分类、验证,并附带解释说明与例子;
- 当前阶段时间和精力有限,短期内很难完成这样一个任务;
- 将来可能会有人去做,也可能某些组织或社区会整理出来这样一份优秀的资源清单。
总结:
这是一个非常值得做的项目,对初学者和自学者尤其有价值。我们非常支持这样的想法,只是当前可能没法亲自去完成它。未来如果有时间,或许会回过头来参与类似的整理项目。不过希望也能有人愿意接下这个任务,把它变成一个真正可用的公共资源。
相关文章:
游戏引擎学习第240天:将渲染器移至第三层
这节又枯燥又无聊简直了 回顾并为今天的内容做铺垫 昨天我们说到,想对渲染器和平台层的集成方式做一些修改。我们之前简单讲了一下修改的目的:我们希望游戏本身不再直接调用 OpenGL 的渲染代码,而是只生成一组渲染指令缓冲区,然…...
2025.04.23华为机考第三题-300分
📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 03. 时空旅行者的最优路径 问题描述 A先生是一名时空旅行者,他可以在不同的时空点之间穿梭。每次从一个时空点跳跃到另一个时空点需要消耗一个时间单位。在每个时空点,都有一些特…...
Kafka 保证多分区的全局顺序性的设计方案和具体实现
Kafka 本身无法直接保证多分区的全局顺序性,因为分区设计旨在并行处理以提升吞吐量。 要实现多分区的顺序性,可尝试通过以下方法在系统层面或业务逻辑上解决: 一、方案设计 单一分区路由(还是将消息发送到同一分区)&a…...
数据结构初阶:二叉树(四)
概述:本篇博客主要介绍链式结构二叉树的实现。 目录 1.实现链式结构二叉树 1.1 二叉树的头文件(tree.h) 1.2 创建二叉树 1.3 前中后序遍历 1.3.1 遍历规则 1.3.1.1 前序遍历代码实现 1.3.1.2 中序遍历代码实现 1.3.1.3 后序遍历代…...
华为开发岗暑期实习笔试(2025年4月16日)
刷题小记: 第一题怀疑测试样例不完整,贪心法不应该能够解决该题。第二题使用0-1BFS解决单源最短路径的问题,往往搭配双端队列实现。第三题是运用动态规划解决最大不重叠子区间个数的问题,难点在于满足3重判断规则,所需…...
第一篇:Django简介
第一篇:Django简介 文章目录 第一篇:Django简介一、纯手写一个简易版的web框架1、软件开发架构2、HTTP协议3、简易的socket服务端4、wsgiref模块5、动静态网页6、后端获取当前时间展示到html页面上7、字典数据传给html文件8、数据从数据库中获取的展示到…...
2025年渗透测试面试题总结-拷打题库13(题目+回答)
网络安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 2025年渗透测试面试题总结-拷打题库13 一、GitHub等三方敏感信息泄漏防御 二、业务逻辑漏洞技术规避 …...
(09)Vue脚手架的使用(Vite、vue-cli、create-vue)
本系列教程目录:Vue3Element Plus全套学习笔记-目录大纲 文章目录 第3章 Vue脚手架3.1 vite3.3.1 Vite使用1)创建Vite项目2)Vite项目打包 3.1.2 组件化开发3.1.4 Vite工程运行原理1)分析main.js2)自定义根组件 3.2 vue…...
Unity 将Excel表格中的数据导入到Mysql数据表中
1.Mysql数据表users如下: 2.即将导入的Excel表格如下: 3.代码如下: using System; using System.Data; using System.IO; using Excel; using MySql.Data.MySqlClient; using UnityEngine; using UnityEditor;public class ImportExcel {// …...
【QT】信号与槽中多个按钮(pushbutton)共用一个槽函数的两种实现方式
两种方法的对比 方法1:sender() 优点:代码简洁,无需额外参数 缺点:依赖运行时类型转换,安全性较低 适用场景:简单场景,少量按钮 方法2:Lambda (推荐) 优点:安全直观&…...
Python----深度学习(神经网络的过拟合解决方案)
一、正则化 1.1、正则化 正则化是一种用于控制模型复杂度的技术。它通过在损失函数中添加额外的项(正则 化项)来降低模型的复杂度,以防止过拟合。 在机器学习中,模型的目标是在训练数据上获得较好的拟合效果。然而,过…...
【金仓数据库征文】从 HTAP 到 AI 加速,KingbaseES 的未来之路
国产数据库早已实现 “可替代”,但要真正与国际头部厂商掰手腕,必须在 HTAP(Hybrid‑Transaction/Analytical Processing)与 AI 加速 两条技术赛道上实现跨越。KingbaseES 自 V8R3 调整为多进程架构后,历经 V8R6、KSOn…...
创建第一个Spring Boot项目
什么是Spring Boot 随着Spring的快速发展,项目中的XML文件越来越多,繁琐的配置以及,整合第三方框架的配置问题,导致大大增加了开发和部署的效率,使开发者无法专心于业务的开发。Spring Boot就相当于使Spring框架的脚手…...
Java—— 正则表达式 练习
需求: 请编写正则表达式验证用户输入的手机号码是否满足要求。 请编写正则表达式验证用户输入的邮箱号是否满足要求。 请编写正则表达式验证用户输入的电话号码是否满足要求。 验证手机号码 13112345678 13712345667 13945679027 139456790271 验证座机电话号码 02…...
Linux[指令与权限]
Linux指令与权限 Linux环境中,打包文件有多种 tar (打包/解包) 指令 tar -czvf 文件要打包到的位置 文件(打包并压缩到) tar -xzvf 文件(在当前目录下解压) tar选项 -c创建压缩文件 -z使用gzip属性压缩 -v展现压缩过程 -f后面使用新建文档名 -x不要新建,解压 -C 文件…...
MySQL数据库精研之旅第十期:打造高效联合查询的实战宝典
专栏:MySQL数据库成长记 个人主页:手握风云 目录 一、简介 1.1. 为什么要使用联合查询 1.2. 多表联合查询时的计算 1.3. 示例 二、内连接 2.1. 语法 2.2. 示例 三、外连接 4.1. 语法 4.2. 示例 一、简介 1.1. 为什么要使用联合查询 一次查询需…...
【Redis】集合类型Set 常用命令详解
1. sadd - 添加 语法:sadd key value > sadd testset A 1 > sadd testset B 1 > sadd testset C 1 > sadd testset C # set的值不能重复 0 > smembers set1 # 查询指定set的所有值,乱序 1) "B" 2) "A" 3) "C&qu…...
React 5 种组件提取思路与实践
在开发时,经常遇到一些高度重复但略有差异的 UI 模式,此时我们当然会把组件提取出去,但是组件提取的方式有很多,怎么根据不同场景选取合适的方式呢?尤其时在复杂的业务场景中,组件提取的思路影响着着代码的可维护性、可读性以及扩展性。本文将以一个[详情]组件为例,探讨…...
第十五届蓝桥杯 2024 C/C++组 合法密码
目录 题目: 题目描述: 题目链接: 思路: substr函数: 思路详解: 代码: 代码详解; 题目: 题目描述: 题目链接: P10906 [蓝桥杯 2024 国 B] 合法密码 -…...
云原生时代的双轮驱动
在当今数字化浪潮汹涌澎湃的时代,企业 IT 主管、CIO、CTO 们肩负着引领企业乘风破浪、实现数字化转型的重任。而主数据平台与数据中台,宛如企业数字化征程中的双引擎,为企业发展注入强劲动力。 一、主数据与数据中台:企业数据世界…...
GD32F407单片机开发入门(六)定时器TIMER详解及实战含源码
文章目录 一.概要二.通用定时器内部结构1.时基单元2.时钟源3.输入捕获4.输出比较 三.通用定时器内部特色四.TIME定时器1ms中断例程五.工程源代码下载六.小结 一.概要 定时器就是计数器,应用在我们生活的方方面面,比如有闹钟、计时器等。在GD32F407VET6定…...
时序数据库 TDengine 助力石油石化业务, 平滑接替 Oracle 数据库
小T导读:胜软科技在石油石化行业中选择使用 TDengine 处理时序数据,不仅显著降低了运维数据库的成本,也大幅减少了存储空间的占用,实现了从原有的 40 多套 Oracle 数据库向仅 9 套 TDengine集群的精简替换。在迁移过程中ÿ…...
【问题解决】本机navicat连接云服务器mysql
一般情况下,当你使用navicat等工具连接云服务器会因为mysql的安全机制,导致无法连接root用户,但是在测试环境中,不考虑安全性的前提条件下,可以通过修改MySQL的配置文件来连接云服务器mysql的root用户。 选择数据库&am…...
STM32F407 的通用定时器与串口配置深度解析
在 STM32F407 芯片的开发过程中,通用定时器和串口的配置与使用是极为关键的技能点。本文将结合提供的代码示例,深入剖析这两个模块的配置流程、工作原理以及实际应用,助力开发者更好地掌握相关技术。 一、通用定时器 (一&#x…...
深入探究Linux项目自动化构建工具:make与Makefile
目录 引言 一、make与Makefile概述 1.1 背景 1.2 理解 二、make工作原理 2.1 查找Makefile 2.2 确定目标文件 2.3 处理文件依赖 三、Makefile实例分析 3.1 简单C程序示例 3.2 项目清理机制 四、结合行缓冲区概念的有趣现象 五、结语 引言 在Linux软件开发的世界里…...
【Hive入门】Hive基础操作与SQL语法:DDL操作全面指南
目录 1 Hive DDL操作概述 2 数据库操作全流程 2.1 创建数据库 2.2 查看数据库 2.3 使用数据库 2.4 修改数据库 2.5 删除数据库 3 表操作全流程 3.1 创建表 3.2 查看表信息 3.3 修改表 3.4 删除表 4 分区与分桶操作 4.1 分区操作流程 4.2 分桶操作 5 最佳实践与…...
STM32F103 “BluePill” 上的 DMA 原理与实践
摘要:本文深入浅出地介绍什么是 DMA(直接存储器访问),它的核心原理、硬件架构,以及在 STM32F103(BluePill)上常见的几种使用场景(ADC、UART、内存拷贝等)。通过对比 CPU 轮询、中断、DMA 三种方式的数据搬运效率,结合寄存器级和 HAL 库示例代码,并附带性能测试与优化…...
软考软件设计师30天备考指南
文章目录 一、考情分析(一)综合知识(二)案例分析 二、30天学习规划(一)第1 - 5天:基础夯实(二)第6 - 10天:核心知识突破(三)第11 - 15…...
比较:AWS VPC peering与 AWS Transit Gateway
简述: VPC 对等连接和 Transit Gateway 用于连接多个 VPC。VPC 对等连接提供全网状架构,而 Transit Gateway 提供中心辐射型架构。Transit Gateway 提供大规模 VPC 连接,并简化了 VPC 间通信管理,相比 VPC 对等连接,支持大量 VPC 的 VPC 间通信管理。 VPC 对等连接 AWS V…...
【AI大模型】MCP:AI应用的“超级扩展坞”
一、什么是MCP MCP(Model Context Protocol,模型上下文协议)是一种新兴的开放协议,于2024年11月由Anthropic公司(Claude的开发者)开源。它的核心目标是建立一个类似USB-C的标准化协议,统一AI模…...
线程封装
目录 makefile Thread.hpp main.cc 以面向对象的方式造轮子 #ifndef _THREAD_HPP__ // 如果没有定义过 _THREAD_HPP__ #define _THREAD_HPP__ // 则定义 _THREAD_HPP__// 这里是头文件的实际内容(类、函数声明等)#endif // 结束条件…...
【Java后端】MyBatis 与 MyBatis-Plus 如何防止 SQL 注入?从原理到实战
在日常开发中,SQL 注入是一种常见但危害巨大的安全漏洞。如果你正在使用 MyBatis 或 MyBatis-Plus 进行数据库操作,这篇文章将带你系统了解:这两个框架是如何防止 SQL 注入的,我们又该如何写出安全的代码。 什么是 SQL 注入&#…...
智能穿戴的终极形态会是AR眼镜吗?
清晨的地铁里,戴着普通眼镜的小张正通过镜片查看实时导航路线,眼前的虚拟箭头精准指引换乘方向;手术室里,主刀医生透过镜片看到患者血管的3D投影,如同获得透视眼般精准避开危险区域;装修现场,设…...
ubantu18.04(Hadoop3.1.3)Hive3.1.2安装指南
说明:本文图片较多,耐心等待加载。(建议用电脑) 注意所有打开的文件都要记得保存。本文的操作均在Master主机下进行 第一步:准备工作 本文是在之前Hadoop搭建完集群环境后继续进行的,因此需要读者完成我之…...
Hive 多表查询案例
文章目录 前提条件Hive 多表查询案例JOIN案例JOIN查询数据准备1. 内连接(INNER JOIN)2. 左外连接(LEFT OUTER JOIN)3. 右外连接(RIGHT OUTER JOIN)4. 全外连接(FULL OUTER JOIN)5. 多…...
4.23刷题记录(栈与队列专题)
第一部分:基础知识 栈先进后出,队列先进先出栈用stack实现,主要函数有pop,push,top队列由queue或者deque实现,主要函数有front,back,push,pop,emplace&#…...
Python常用的第三方模块之【jieba库】支持三种分词模式:精确模式、全模式和搜索引擎模式(提高召回率)
Jieba 是一个流行的中文分词Python库,它提供了三种分词模式:精确模式、全模式和搜索引擎模式。精确模式尝试将句子最精确地切分,适合文本分析;全模式则扫描文本中所有可能的词语,速度快但存在冗余;搜索引擎…...
Redisson实战:分布式系统中的五大典型应用场景
引言 在分布式系统架构中,数据一致性、高并发控制和资源协调是开发者面临的核心挑战。Redisson作为基于Redis的Java客户端,不仅提供了丰富的分布式对象和服务,还简化了分布式场景下的编程模型。本文将通过实际代码示例,解析Redis…...
webrtc建立连接的过程
WebRTC 连接全过程:从零到视频通话的每一步 WebRTC 是个神奇的技术,让浏览器直接进行点对点(P2P)音视频通话或数据传输,不用每次都靠服务器中转。想知道 Alice 和 Bob 是怎么通过 WebRTC 建立视频通话的吗?…...
system verilog 语句 耗时规则
在 SystemVerilog 中,确实有一类语句是**不消耗仿真时间(zero simulation time)**的,我们一般叫它们: ✅ 零延迟语句(Zero-Time Statements) 🔹1. 什么是“不费时间”的语句? 这些语句在仿真时…...
【Docker】在Ubuntu平台上的安装部署
写在前面 docker作为一种部署项目的辅助工具,真是太好用了需要魔法,不然无法正常运行笔者环境:ubuntu22.04 具体步骤 更新系统包索引 sudo apt update安装必要依赖包 sudo apt install -y apt-transport-https ca-certificates curl softwa…...
2025年阅读论文的常用工具推荐
在快速发展的学术界,阅读和整理论文的能力对于研究者和学生来说至关重要。随着科技的进步,各种工具应运而生,帮助我们更高效地处理文献。本文将为您推荐一些2025年最常用的阅读论文工具,让您的学术之路更加顺畅。 1. SumiNote S…...
pod内部共享命名空间与k8s命名空间是一个东西吗?
文章目录 小知识-命名空间**下面着重介绍一下刚刚提到的内部命名空间**IPC NamespaceNetwork Namespace 本文摘自于我的免费专栏《Kubernetes从0到1(持续更新)》请多关注 小知识-命名空间 注意,首先我要强调一点,Kubernetes命名空…...
Linux笔记---进程间通信:匿名管道
1. 管道通信 1.1 管道的概念与分类 管道(Pipe) 是进程间通信(IPC)的一种基础机制,主要用于在具有亲缘关系的进程(如父子进程、兄弟进程)之间传递数据,其核心特性是通过内核缓冲区实…...
JAVA设计模式——(三)桥接模式
JAVA设计模式——(三)桥接模式(Bridge Pattern) 介绍理解实现武器抽象类武器实现类涂装颜色的行为接口具体颜色的行为实现让行为影响武器修改武器抽象类修改实现类 测试 适用性 介绍 将抽象和实现解耦,使两者可以独立…...
设计模式--工厂模式详解
工厂模式 作用: 实现了创建者与调用者的分离 详细分类 简单工厂模式 工厂方法模式 抽象工厂模式 OOP七大原则: 开闭原则:一个软件的实体应该对拓展开发,对修改关闭 依赖反转原则:要针对接口编程,不…...
每天五分钟深度学习PyTorch:图像的处理的上采样和下采样
本文重点 在pytorch中封装了上采样和下采样的方法,我们可以使用封装好的方法可以很方便的完成采样任务,采样分为上采样和下采样。 上采样和下采样 下采样(缩小图像)的主要目的有两个:1、使得图像符合显示区域的大小;2、生成对应图像的缩略图。 下采样( 放大图像)的…...
前端面试场景题
目录 1.项目第一次加载太慢优化 / vue 首屏加载过慢如何优化 2.说说了解的es6-es10的东西有哪些 ES6(ES2015)之后,JavaScript 新增了许多实用的数组和对象方法,下面为你详细介绍: 3.常见前端安全性问题 XSS&#…...
国际化不生效
经过我的重重检查 最终发现是 版本问题。 原本下载默认next版本cnpm install vue-i18nnext 下载 国际化插件 cnpm install vue-i18n^9.14.3 删除掉node_models,再重新加载包:cnpm install 这时候就可以正常显示了 国际化操作: en.js zh…...
新一代人工智能驱动医疗数智化:范式变革、实践方向及路径选择
人工智能(AI)正以前所未有的速度重构医疗健康行业的底层逻辑,从数据获取、知识建模到临床决策支持,AI不仅是“辅助工具”,更日益成为医疗生产力体系的核心引擎。随着大模型、计算平台和数智基础设施的迅猛发展,医疗数智化正进入从“点状创新”走向“系统重构”的深水区。…...