当前位置: 首页 > news >正文

游戏引擎学习第167天

回顾和今天的计划

我们不使用引擎,也不依赖库,只有我们自己和我们的小手指在敲击代码。

今天我们会继续进行一些工作。首先,我们会清理昨天留下的一些问题,这些问题我们当时没有深入探讨。除了这些,我觉得我们在资产系统方面的工作差不多可以告一段落了。虽然我们在处理资产时遇到了一些小bug,但今天的主要目标是继续实现字体相关的功能。

之前我们已经完成了字体字形的获取部分,但我们还没有开始处理字体的度量(metrics)问题,也就是如何计算字母的尺寸、如何对齐字母等。今天我们会集中精力做这些工作。

在正式开始之前,我希望先清理一下昨天问答环节中留下的一些问题。我们当时编写了一些代码,但并没有仔细检查,所以今天我想确保这些问题都得到解决。

首先,解决上次问答中代码中提出的两个小问题

我们讨论了一个状态自旋锁的问题。当时的代码逻辑大致是,如果尝试加载某个资产时,发现自己不是第一个尝试加载它的(即如果这个加载操作是即时的),那么就应该使用自旋锁。

在回顾这段代码时,我发现了几个问题。首先,我没有使用 volatile 版本的变量,而这是我最初的意图。这个疏忽是个小错误,但需要修正。

其次,更重要的问题是,这个自旋锁应该仅在我们尝试进行即时加载时使用。如果我们并不要求即时加载,那么就不应当等待这个资产的加载。也就是说,自旋锁的使用条件并不正确,这与我们的初衷不符。

因此,我打算修改代码,移除这个不恰当的自旋锁逻辑,确保只有在需要即时加载时才使用它。这样,代码就能按照预期更合理地运行。
在这里插入图片描述

在这里插入图片描述

回到字体部分

回到字体的部分,可以看到我们之前调试时,字体的图形上出现了黑色背景,而不是像现在这样干净的背景。我们当时的目标是去除这些背景,以便更清晰地查看字体本身。如果要继续做这项工作,接下来想要做的是打开测试资产构建器,这是我们一直在做资产文件规范的工具。

在这个资产构建器中,我们有一个预处理指令,可以选择是否在构建过程中使用库。你可以选择是否在资产构建器中完全不使用任何库,设置为1。如果你想使用stb truetype,也可以设置为0并将其编译进来。这是之前我展示过如何使用这个库的方式。
在这里插入图片描述

在这里插入图片描述

获取Windows下的抗锯齿字体

在 Windows 版本中,我们之前没有完全完成字体的抗锯齿处理。如果仔细看会发现字体并没有启用抗锯齿,这也是我们在直播中停下的地方。所以现在值得花点时间来修复 Windows 版本的抗锯齿问题,因为这应该不会太难。

当我们使用 Windows 字体时,路径是这样的。其工作原理是,当我们需要加载字体时,系统会通过一定的方式处理字体的渲染,我们需要检查是否能够在这个过程中启用抗锯齿效果。

也许创建一个颜色兼容的DC能让我们得到抗锯齿效果

在处理字体抗锯齿的问题时,创建了一个设备上下文(DC),并使用了兼容的位图。有人在论坛上猜测,可能由于使用了某种单色位图,导致没有启用抗锯齿。具体来说,CreateCompatibleDC(0)可能和GetDC(0)不同,后者会返回屏幕的设备上下文,而前者可能会有默认的行为,导致创建了一个没有颜色信息的位图,因此无法进行抗锯齿处理。

查看字体渲染效果时,字体确实没有抗锯齿效果,这是我们停留的地方。为了测试这个问题,重新编译并运行测试资产构建工具,这会重新提取字体数据,但观察渲染效果时,似乎没有看到抗锯齿有任何改进,显示效果与之前相同。

因此,似乎需要做一些额外的工作,确保位图设置合理,才能真正启用抗锯齿。不过,具体的解决方法尚不清楚,可能需要进一步的调试和测试。

在这里插入图片描述

使用CreateDIBSection来更好地控制我们创建的位图

为了能够更好地控制位图,决定创建一个能够直接读取的位图。目标是通过使用 CreateDIBSection 来创建一个位图,这样可以直接访问位图的像素数据,而不是依赖每次获取像素的操作(GetPixel),因为后者非常慢,每次都需要进行操作系统调用。

CreateDIBSection 会使用一个 BITMAPINFO 结构,结构中的内容可以与之前加载位图时使用的结构相同。具体来说,我们可以设置图像的宽度、高度、位深度等参数,确保生成的位图符合要求。这样,我们就可以验证是否正确设置了位图,且能直接访问其数据。

CreateDIBSection 中,我们需要传入设备上下文(DC)句柄,它会是之前创建的兼容设备上下文。我们还需要传入 BITMAPINFO 结构体以及一个指向位图数据的指针,使用该指针我们可以直接访问位图的像素数据。

通过这种方式,我们不仅可以绕过不高效的 GetPixel,还可以为进一步的抗锯齿渲染提供基础。总体来说,目标是确保位图能够正确创建,并且可以有效地读取位图数据,避免不必要的性能损耗。
在这里插入图片描述

初始化传递给CreateDIBSection的BITMAPINFO

为了正确创建 DIBSection,需要初始化 BITMAPINFO 结构,并确保其中的字段设置合理。BITMAPINFO 结构由 BITMAPINFOHEADER 组成,因此我们必须正确填充这些字段,以便后续使用它创建位图。

首先,我们需要设置 BITMAPINFOHEADER 结构的大小,即 biSize,通常可以直接使用 sizeof(BITMAPINFOHEADER)。然后,必须确定位图的宽度 (biWidth) 和高度 (biHeight)。由于当前代码尚未明确宽度和高度的定义,因此暂时使用一个最大尺寸(如 1024)作为 MaxDim,以确保能够适应任何字体字符的渲染。

接着,biPlanes 必须设为 1,这是 Windows GDI 规定的标准值。biBitCount 需要设为 32,因为希望使用 32 位位图格式(每个像素 4 字节,包含 Alpha 透明通道),这样可以更方便地进行像素操作。biCompression 设为 BI_RGB(即 0),表示不进行任何压缩,这样可以直接访问原始像素数据。

对于 biSizeImage,如果 biCompression 设为 BI_RGB,则可以直接设为 0,系统会自动计算。biXPelsPerMeterbiYPelsPerMeter 用于指示像素密度,但在当前情况下不太重要,因此可以设为 0。biClrUsedbiClrImportant 主要用于调色板索引,但我们不使用调色板,因此也设为 0。

完成 BITMAPINFO 结构的初始化后,接下来会调用 CreateDIBSection 来创建位图,并返回一个可直接访问像素数据的指针。这样,我们可以绕过 GetPixel 这种低效的像素读取方法,直接操作内存,提高渲染性能。不过,目前还无法确定是否所有设置都是正确的,因此需要运行测试来验证是否能够正确创建和访问位图。
在这里插入图片描述

最终,我们成功地实现了抗锯齿的字体渲染。现在,字体显示已经变得平滑,没有了之前的锯齿感,这表明问题出在原始位图的创建方式上。

之前的问题可能是由于 Windows 认为位图的每像素位数不足,导致无法正确执行抗锯齿。而通过手动创建 DIBSection,确保位图具有足够的位深度(如 32 位),Windows 便能够正确地进行抗锯齿处理,使字体呈现更清晰和顺滑的效果。

目前,字体的渲染效果已经明显改善,说明新的位图创建方法是正确的,接下来可以继续推进字体的其他相关功能。

直接获取位图的内容,而不是为每个像素发出操作系统调用

我们尝试优化位图数据的访问方式,使其更加高效,避免调用 GetPixel 这样的低效 API。理论上,既然已经创建了 DIBSection,那么应该可以直接访问 bits 指针中的数据,而无需逐个调用 GetPixel

主要调整:

  1. 存储指针
    • 通过 DIBSection 获取 bits 指针,并在外部存储它,以便可以直接访问位图数据。
  2. 遍历像素数据
    • 遍历 bits 指针时,按照行扫描方式读取像素。
    • 由于位图是 32bpp(每个像素 4 字节),所以可以假设每行的步幅(stride)是 width * 4
    • 这样可以避免调用 GetPixel,直接通过指针访问像素数据。
  3. 确保步幅正确
    • 由于 DIBSection 没有显式提供 stride,但 Windows 设备上下文通常会进行对齐,所以需要检查是否有额外的字节填充(通常是 DWORD 对齐)。
  4. 调试问题
    • 发现 star pixelref pixel 的数据不匹配,可能是 bits 指针的数据未正确初始化,或者 DIBSection 没有正确地存储像素数据。
    • 通过断点和 assert 进行检查,确认 X=11, Y=26 处的数据不符合预期,可能是 bits 数据未正确映射或 stride 计算有误。

下一步行动:

  • 验证 bits 指针的数据:检查 bits 是否正确指向 DIBSection 内存。
  • 检查 stride 计算:确保每行数据的步幅计算正确,没有额外填充字节。
  • 确认 SelectObject 是否生效:确保 DIBSection 作为当前位图被正确选入 DC
  • 调试 assert 失败的点:逐步检查 X=11, Y=26 处的数据,看看 ref pixelstar pixel 的具体值。

整体来说,这一步的优化是为了提升性能,使得位图数据可以被直接访问,避免 GetPixel 造成的性能开销。目前仍需进一步调试,以确保 DIBSection 被正确初始化,并且 bits 指针能正确映射像素数据。
在这里插入图片描述

我们的像素指针显示的全是零,为什么会这样?

在调试过程中,我们尝试通过 CreateDIBSection 创建一个位图,并希望直接访问其中的像素数据。然而,当前获取到的像素数据全部为零,导致位图显示为空白。我们怀疑 bits 指针并未正确指向有效数据,因此进行了多种排查。

首先,我们确认 bits 指针是否真的无效,尝试遍历像素数据,但发现所有像素值始终为零。接着,我们考虑了数据是否可能是上下颠倒的,但进一步检查后发现并非如此。我们还尝试在遍历像素时添加 assert 断言,以检查像素值是否真的被正确写入,但依然未能找到问题所在。

我们回顾了 CreateDIBSection 的官方文档,并查阅了一些示例代码,发现我们的代码和其他成功示例基本一致,例如:

  • 调用 CreateDIBSection 创建位图。
  • 使用 SelectObject 将位图选入设备上下文(DC)。
  • 执行 TextOut 绘制文本到该 DC。
  • 通过 bits 指针访问像素数据。

然而,尽管我们确认 GetPixel 方法可以正确获取绘制的内容,但直接访问 bits 指针时却仍然只得到全零数据。这让我们怀疑 TextOut 是否确实影响了 CreateDIBSection 所创建的位图,或者是否有其他未正确初始化的地方。

进一步检查代码逻辑:

  1. 位图的创建CreateDIBSection 的调用方式看起来是正确的,bits 指针也成功返回了一个地址。
  2. 位图的选入SelectObject 也被正确调用,使 DC 绑定到了我们创建的位图。
  3. 绘制文本TextOut 运行后,我们可以通过 GetPixel 正确获取像素值,说明 DC 的内容是正确的。
  4. 像素数据访问:遍历 bits 指针时,所有数据均为零,可能说明 CreateDIBSection 返回的 bits 缓冲区未实际存储 DC 渲染的内容。

由于 CreateDIBSection 允许直接访问像素数据,我们推测:

  • 可能 DC 并未真正渲染到 CreateDIBSection 所提供的位图内存,而是仍然写入了默认的设备表面。
  • 可能需要使用 BitBltGetDIBits 等 API 从 DC 复制像素数据到 CreateDIBSection 的内存。

为了验证这一点,我们考虑缩小位图尺寸,以便更容易观察数据的变化。此外,我们希望找出 CreateDIBSection 内部是否有额外的初始化要求,以确保数据能够正确写入 bits 指针指向的缓冲区。

在代码逻辑和 API 方面,我们仍然在寻找可能的疏漏,以确定为什么 bits 指针中的数据不会随着 TextOut 而更新。
在这里插入图片描述

我们想要理解我们的代码

在调试 CreateDIBSection 生成的位图时,我们发现 GetPixel 方法可以正确获取像素数据,但直接访问 CreateDIBSection 返回的 bits 指针时,数据却始终为零。虽然 GetPixel 可以用于 Asset Builder,但这种做法并不符合代码的理解原则,我们需要真正弄清楚 CreateDIBSection 为什么无法正确提供数据,而不是仅仅接受 GetPixel 作为一个可行的替代方案。

从目前的分析来看:

  1. CreateDIBSection 似乎正确创建了位图

    • 代码调用了 CreateDIBSection,并成功返回了 bits 指针。
    • 设备上下文(DC)也成功地将该位图选入,说明位图确实与 DC 相关联。
    • TextOut 成功执行,且 GetPixel 也可以获取到正确的像素值,这表明 DC 里确实有正确的图像数据。
  2. 问题出现在 bits 指针访问数据时

    • bits 指针本应指向 CreateDIBSection 分配的像素数据区域。
    • 但在遍历 bits 指针时,所有像素值均为零。
    • 这说明 TextOut 绘制的内容似乎并未写入 CreateDIBSection 分配的内存,或者 bits 指针指向的区域并未正确更新。
  3. 可能的原因分析

    • CreateDIBSection 分配的内存可能未被 TextOut 正确写入。
    • 设备上下文(DC)可能仍然在写入默认的设备表面,而不是 CreateDIBSection 的位图内存。
    • 可能需要显式使用 BitBltGetDIBits 来确保 bits 指针所指向的内存正确更新。

为了更进一步确认问题,我们可以尝试:

  • 检查 CreateDIBSection 生成的位图是否正确
    例如,使用 GetObject 检查 DIBSECTION 结构,看看 dsBm.bmBits 是否与 bits 指针一致。
  • 尝试强制刷新位图数据
    例如,在 TextOut 之后,使用 GdiFlushBitBlt 强制将 DC 内容写入 bits 所指向的内存。
  • 缩小位图尺寸,以便更容易检查数据的变化
    通过创建更小尺寸的位图,可以更快验证 bits 指针是否正确返回了数据。

我们需要深入理解 CreateDIBSection 的行为,而不是简单接受 GetPixel 的可行性。下一步的重点是验证 bits 指针的有效性,并检查 DC 是否真的将数据写入了 CreateDIBSection 分配的内存区域。

擦除位图,看看这种变化是否通过我们的像素指针可见

在调试 CreateDIBSection 生成的位图时,为了确认 bits 指针是否真的指向有效的像素数据区域,我们尝试在位图初始化时使用 memset 将其填充为全白 (0xFFFFFFFF),然后再让 Windows 进行绘制,观察绘制后的结果。

实验过程如下:

  1. bits 指针指向的内存全部填充为白色

    • 使用 memsetbits 指针指向的整个位图区域填充为 0xFFFFFFFF
    • 这样做的目的是确保初始状态下,位图中的所有像素都是白色,以便观察 Windows 是否会正确修改这些像素。
  2. 执行 TextOut 进行文本绘制

    • 代码调用 TextOut 进行绘制,理论上 Windows 应该在 bits 指针指向的位图区域中写入文本的像素数据。
    • 期望结果是 bits 指向的内存中应该出现文本的像素数据,而不是全白。
  3. 检查 bits 指针的内容

    • 运行代码后,检查 bits 指针指向的内存,发现内容仍然保持全白 (0xFFFFFFFF),完全没有被修改。
    • 这表明 TextOut 并没有实际写入 CreateDIBSection 返回的内存区域。

结论:

  • 这次实验明确证明,TextOut 并未修改 CreateDIBSection 返回的 bits 指针所指向的内存。
  • 可能的原因包括:
    • CreateDIBSection 生成的位图未正确绑定到 DC,导致 TextOut 仍然在绘制到默认 DC,而不是 DIB 位图。
    • TextOut 绘制的内容仍然停留在 DC,但并未同步到 bits 指针所指向的内存。

下一步可能的尝试:

  • 使用 GetDIBits 读取 DC 中的像素数据,看看 Windows 是否实际上已经绘制了文本,只是没有正确同步到 bits 指针。
  • 尝试 BitBlt 复制 DC 的内容CreateDIBSection 的位图,确保数据能够正确传递到 bits 指针指向的内存区域。
  • 检查 SelectObject 是否正确CreateDIBSection 生成的位图选入了 DC,如果 SelectObject 失败,可能意味着 DC 仍然在绘制到默认目标,而不是 CreateDIBSection 的位图。

这个实验帮助我们进一步缩小了问题范围,接下来的关键是弄清楚 TextOut 为什么没有修改 bits 指针的内容,并找到正确的方法将 DC 的数据同步到 CreateDIBSection 的位图内存中。

位图是从下往上存储的!

在调试过程中,发现位图的指针可能处于错误的位置,导致图像的绘制方向不符合预期。推测位图的渲染顺序可能是从下到上,而我们期望的是从上到下绘制,这造成了显示问题。

问题分析

  1. 绘制方向错误
    位图的指针可能是从位图的底部开始,而不是从顶部开始,这导致图像呈现出来的方向与预期相反。位图在渲染过程中没有按照期望的顺序更新。

  2. 解决方案猜测
    为了修正这个问题,考虑到绘制顺序的不同,可以通过调整位图指针的处理方式来模拟正确的绘制顺序。例如,尝试反向处理位图的行数据,逐行从下往上进行绘制,这样可以匹配底部到顶部的绘制顺序。

直接访问像素,而不是通过GetPixel

在解决位图绘制问题时,首先尝试了调整位图处理的逻辑。通过设置正确的行指针,并确保逐行正确处理位图的像素数据。具体做法是将指针从位图的底部开始,并通过对像素行的操作来确保渲染顺序正确。

问题发现与处理

  1. 行指针调整
    最初在处理位图时,行指针的步进方向不一致。为了处理这种情况,尝试将源行指针向上移动,但在某些步骤中,发现了一个bug,即 xy 值没有按照预期的方式调整,这影响了图像渲染的正确性。

  2. 修正代码
    通过进一步调整位图中的最小 xy 值的预处理(称为 “pre-step”),确保在读取像素数据时可以准确地对齐并正确处理每个像素。这一步是解决渲染错误的关键。

  3. 逐步调整并验证
    在解决了前面的问题后,继续进行调试,确保在位图渲染过程中没有遗漏任何重要的步骤。随着程序的修正,逐渐恢复了正常的绘制流程。

最终结果

  • 在做完这些修改后,成功解决了位图渲染的方向问题,位图从正确的方向开始绘制,并且字符能够正确显示。
  • 最终通过在程序中调整源指针、步进方向以及相应的像素数据处理,达到了预期的渲染效果,确保图像准确地显示出来。

此时,经过这些修改,位图绘制问题得到解决,程序也开始按照预期正常运行,显示出了正确的字符和图像内容。

在这里插入图片描述

移除调试中的黑色背景

在处理图像和图形渲染时,之前我们选择将 alpha 通道与背景分开处理,这样可以在背景上加上黑色以便更清晰地显示图像。然而,这种做法并不是最理想的,现在我们已经采用了更为准确的处理方式,不再需要这样单独处理 alpha 通道。

通过使用正确的 alpha 渲染方法,图像的透明部分可以更加自然地与背景融合,而不再显示黑色背景。这种方法能够确保渲染出的图像具有更好的视觉效果,背景也会更加清晰和真实。因此,重新构建图像资产时,应该使用更新的 alpha 渲染方法,这样我们可以得到更理想的透明效果。

经过这种改进,现在图像看起来更加专业,透明效果得到了优化,背景不再干扰显示,整体效果更加自然。
在这里插入图片描述

重新加入字符周围的1像素边框

目前还需要给图像添加一个边框(围裙),但是由于之前编写位图处理时,我们默认每个图像四周都有一个1像素的透明边框,以保证图像的采样能够正确进行。因此,我们仍然需要在现有的图像处理中加入这个1像素的透明边框,确保渲染效果。

为了做到这一点,可以在处理位图的宽度和高度时考虑到这一点。具体来说,处理时需要在图像四周加上一个1像素的白色边框。在拷贝图像的过程中,可以先清空整个区域,然后确保在实际写入像素时留出空间,具体做法是将目标位置预先偏移1像素。通过这种方法,能够正确处理图像边缘的透明度,避免图像出现被截断的现象。

在代码实现上,尝试将两个相似的代码路径合并,这样可以简化代码,并提高维护性。虽然目前合并可能有些急躁,但可以计划在未来的时间里进行优化。通过调整代码,可以在图像操作时,确保每次拷贝都不会覆盖边缘区域,从而保留预留的1像素边框。

另外,在调试时也发现了一些问题,导致程序崩溃。问题出在尝试预偏移1像素时,导致了内存溢出或访问错误。通过进一步检查,发现是预步进(pre-stepping)过程中,对内存地址的处理不当导致了崩溃,尤其是在处理图像高度时的操作不符合预期。因此,修复这个问题的关键是在处理高度时,确保正确地使用和计算图像的高度,而不是错误地使用错误的值。

解决这个问题后,预期图像渲染应该可以正确地显示带有透明边框的效果。
在这里插入图片描述

测试今天的更改

目前看起来,字体的渲染效果已经有所改善,尤其是字母“O”看起来更加平滑了。然而,对于字形(glyph)是否仍然有些微的截断问题,仍然不确定,因此建议查看实际的字形,确认它们是否按预期显示。

虽然目前看起来已经有了一些进展,但仍然无法完全确定所有字形的对齐是否完美。比如,字母“G”是否仍然被截断,可能需要进一步检查,以确保所有字形都被正确地渲染和对齐。

总的来说,现阶段可以认为已经完成了大部分工作,字体的平滑度和准确性得到了提升,虽然可能还有一些细节需要进一步调整。至于字体的种类,如果确认是 Arial 字体,接下来可能需要对比实际效果,进一步确认字形的显示是否完全符合预期。

将我们的结果与Wordpad的字体渲染进行比较

在检查字体效果时,发现字体显示并不是预期的 Arial 字体,而是被意外切换到了 Times New Roman 或 Courier New 字体。通过对比,可以发现字母“G”在 Courier New 字体中显示得比较平坦,这与预期中的 Arial 字体有所不同。不过,这个问题看起来并不严重,因为 Courier New 的“G”本身就是这样的形状。

虽然字形的显示在某些情况下可能和预期不完全一致,但目前来看,这个问题并不会对整体效果造成太大影响。然而,最终还是建议进一步检查字体的准确性,以确保未来在使用不同字体时,能够完全符合预期的显示效果。

你应该将GetDC(0)传递给CreateCompatibleBitmap,而不是CreateCompatibleDC。 但是没关系,你仍然需要使用另一种方法来有效访问位图数据

提到创建兼容位图时,实际上并不是指使用 CreateCompatibleBitmap 函数,而是应该使用一个不同的方法来提高效率。这里讨论的是如果不关心像素获取(GetPixel),那么就不需要使用 CreateDIBSection,可以直接使用 CreateCompatibleBitmap 来创建一个兼容的位图。

如果选择不使用 GetPixel,则可以避免需要像 CreateDIBSection 这样的方法,而是通过 CreateCompatibleBitmap 来直接获得所需的位图。这样的方法可能会更加高效,但会依赖于不同的访问方式。

为什么字形位图需要任意的填充? 获取实际字形大小的方法是正确的吗?

在处理字形位图时,通常需要一些“任意填充”。这个填充是为了确保每个字形在位图中能正确存储和显示。所谓“任意填充”是指在字形位图周围或字形之间加入一定的空白区域,以便进行正确的采样和绘制,尤其是在显示时防止像素错误或重叠的情况。这种填充并不是字形本身的一部分,而是为了适应图像处理、显示和对齐等需要。

关于“实际字形大小”的问题,获取字形大小时需要使用适当的方法,比如从字体或位图中提取字形的边界框。若使用的方法不正确,可能无法准确地提取字形的实际大小,导致显示和存储上的问题。

总的来说,任意填充是为了确保在渲染或处理字形时,能够避免边界问题和对齐错误,从而提高图像处理的准确性。

“任意” = 宽度/高度额外加2

在讨论字形位图时,所谓的“任意填充”其实并不是任意的,而是指根据规范所要求的额外宽度和高度。这个填充是为了确保图像能够正确地进行线性采样,避免在渲染时出现问题。填充的目的是为了保证位图在进行双线性采样时,不会因为边界问题导致采样不准确,特别是在处理图像边界和内存对齐时,填充使得图像能够更高效地进行处理。

在实现渲染器时,提到了使用一个“围裙”(apron)来帮助进行无缝的双线性平铺。这种方法让图像采样变得更加高效,因为它避免了每次处理边界时需要做额外的判断。通过这种填充,图像能够正确地平铺并渲染,而无需在每次处理边界时都增加额外的逻辑判断。

总结来说,这个“填充”是规范要求的,并不是随意的,它的目的是提高图像渲染效率,特别是在双线性采样和处理边界时。

我应该停止使用模板来做容器,而永远使用void*吗?

关于是否应该停止使用模板容器而改用 void*,讨论的关键在于如何高效地管理容器。在某些情况下,比如在特定的游戏引擎开发中,可以采用元编程的方式来处理容器。这种方式比使用模板和 void* 更具优势,因为它避免了 C++ 中模板和 void* 的复杂性和限制。

在这个环境下,元编程被认为是一种更高效、可控的做法。通过元编程,可以生成专门为特定任务定制的代码,而不是依赖通用的模板或者 void*,这些往往带来不必要的复杂性和性能问题。对于普通的游戏开发,虽然可能不需要使用元编程,但如果能够掌握元编程技巧,能够为未来更复杂的任务打下基础。

然而,在一些较为简单的开发环境下,直接使用 void* 可能会比复杂的模板方案更容易实现,特别是在需要处理大量不同类型数据时。对于游戏开发的核心技术,可以通过自定义编程方式,避免依赖模板和 void*,通过减少不必要的抽象和复杂性来提高效率。

总体来说,模板和 void* 的使用都被认为是有局限性的,而元编程作为更高层次的技术手段,能够带来更好的控制和效率,因此如果要进行更复杂的开发工作,掌握元编程技术是必不可少的。

相关文章:

游戏引擎学习第167天

回顾和今天的计划 我们不使用引擎,也不依赖库,只有我们自己和我们的小手指在敲击代码。 今天我们会继续进行一些工作。首先,我们会清理昨天留下的一些问题,这些问题我们当时没有深入探讨。除了这些,我觉得我们在资产…...

阿里云服务器环境部署 三 Minio文件服务集群的搭建

Minio文件服务集群的搭建 一 准备工作 1、三台机器 配置 vim /etc/hosts 172.16.108.44 minio4 172.16.108.43 minio3 172.16.108.42 minio2 [rootbigdata41 es]# docker --version Docker version 26.1.4, build 5650f9b [rootbigdata43 minio]# docker-compose -v -bash…...

让人感到疑惑的const

const 关键字在不同的编程语言中有着不同的含义和限制,但通常它被用来声明一个常量或只读变量。然而,在 JavaScript 中,const 的行为有时可能会让人感到困惑,因为它并不总是意味着“不可变”(immutable)。让…...

网易云信架构升级实践,故障恢复时间缩至8秒

一、项目背景 网易云信是网易旗下集IM与音视频技术于一体的PaaS服务平台,为全球提供融合通信与视频的核心功能和组件,包括IM即时通讯、短信、信令等通信服务,以及RTC、直播、点播、互动直播、互动白板等音视频服务,此外&#xf…...

算法刷题记录——LeetCode篇(3) [第201~300题](持续更新)

(优先整理热门100及面试150,不定期持续更新,欢迎关注) 207. 课程表 你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequ…...

navicat导出文件密码解密

文章目录 一、概念二、导出文件1、创建的数据库连接信息2、导出带密码的连接信息3、查看导出后的文件 三、Python代码解析四、参考地址 一、概念 Navicat中导出的带密码的文件后缀是.ncx结尾的,里面是xml格式的文件,存储了数据库的连接,方便…...

uniapp vue3项目定义全局变量,切换底部babar时根据条件刷新页面

前言 uniapp项目中,每个tabbar页面来回点时候,不会触发页面更新。但是有时页面上有数据发生改变需要更新模版时,就得能及时的通知到页面。如果在onshow生命周期里每次都调用异步请求更新数据,有些不合理,况且页面有时…...

Linux上的`i2c-tools`工具集的详细介绍;并利用它操作IMX6ULL的I2C控制器进而控制芯片AP3216C读取光照值和距离值

IC-Tools 工具集介绍 i2c-tools 是 Linux 下用于 IC 设备调试 的用户空间工具集(你也可以把它看成是一个库,类似于之前自己用过的触摸屏库tslib库、FreeType矢量字符库),它提供了一系列命令行工具,可以扫描、读取、写入 IC 设备,…...

## DeepSeek写射击手机小游戏

DeepSeek写射击手机小游戏 提问 根据提的要求,让DeepSeek整理的需求,进行提问,内容如下: 请生成一个包含以下功能的可运行移动端射击小游戏H5文件: 要求 可以重新开始游戏 可以暂停游戏 射击位置在底部中间&#xff…...

奇安信全流量(天眼)面试题

一、全流量设备(天眼)的部署架构 天眼系统采用旁路部署模式,通过流量镜像实现非侵入式监测,核心组件包括流量传感器、分析平台和文件威胁鉴定器,具体部署架构如下: 传感器部署 关键节点覆盖:在…...

计算机四级 - 数据库原理(操作系统部分)- 第2章「操作系统运行机制」

系统调用是应用程序请求操作系统核心完成某一特定功能的一种过程调用,与一般调用的最大区别就是调用程序运行在用户态,而被调用程序则运行在系统态寄存器类型: 用户不可见寄存器:程序计数器、指令寄存器、程序状态字(P…...

【css酷炫效果】纯CSS实现虫洞穿越效果

【css酷炫效果】纯CSS实现穿越效果 缘创作背景html结构css样式完整代码基础版进阶版(虫洞穿越) 效果图 想直接拿走的老板,链接放在这里:https://download.csdn.net/download/u011561335/90491973 缘 创作随缘,不定时…...

火山引擎(豆包大模型)(抖音平台)之火山方舟的Prompt的使用测试

前言 在大模型的使用过程当中,Prompt的使用非常的关键。原来,我对Prompt的理解不深,觉得Prompt的产生并不是很有必要。但是,自从使用了火山方舟中的“Prompt优解”之后,感受加深了,觉得Prompt是我们和大模型…...

多线程(四)----线程安全

线程安全问题的万恶之源就是多线程的抢占式执行所带来的随机性. 有了多线程, 此时抢占式执行下, 代码执行的顺序, 会出现更多的变数, 代码执行顺序的可能性就从一种情况变成了无数种情况. 只要有一种情况使得代码结果不正确, 都是视为bug, 线程不安全. 有线程安全的代码 以下…...

跨系统投屏:Realme手机(远程)投屏到Linux系统的简单方法

家里长辈年纪上来了,有点老花眼,平常看手机总是觉得字体不够大,还一个劲儿地将手机拿很远。其实那台手机的字体已经调到最大了。 为了让长辈刷手机的时候可以轻松快乐一点,我们帮他将手机投屏到电脑上。毕竟电脑屏幕比手机大多了&…...

【eNSP基础使用教程-1】

座右铭: 纵有疾风起,人生不言弃。 文章目录 前言一、更改设备名称指令1、双击路由器进入2、 进入系统视图3、更改设备名称为R14、使用同样的办法修改路由器R2、R3 二、配置路由物理接口的IP 地址1、查看R1路由器当前接口IP 地址配置与路由表2、查看路由器上的路由表…...

android开发:组件事件汇总

在 Android 开发中,Java 文件中有许多组件事件可以处理用户交互。以下是一些常见的组件事件及其用途和示例: 1. 点击事件 (Click) 用于处理用户点击控件的操作。 示例代码: Button button findViewById(R.id.button); button.setOnClickL…...

C++|向函数传递对象

在 C 里,对象作为函数的参数和返回值,有值传递、指针传递和引用传递这三种传递方式,下面为你详细介绍。 1.值传递 在值传递时,把实参对象的值复制给形参对象,函数会接收实参的一个副本,而非实参本身。函数…...

网络爬虫【爬虫库urllib】

我叫不三不四,很高兴见到大家,欢迎一起学习交流和进步 今天来讲一讲爬虫 urllib介绍 Urllib是Python自带的标准库,无须安装,直接引用即可。 Urllib是一个收集几个模块来使用URL的软件包,大致具备以下功能。 ● urlli…...

【一起来学kubernetes】17、Configmap使用详解

前言概述核心特性创建 ConfigMap使用 ConfigMap1. **环境变量**2. **Volume 挂载**3. **命令行参数** 更新与热重载Docker容器中Java服务使用Configmap**一、通过环境变量注入****步骤说明****示例配置** **二、通过 Volume 挂载配置文件****步骤说明****示例配置** **三、动态…...

QT程序双击可执行文件运行方法

1、qt编译选择release模式 在pro文件添加:QMAKE_LFLAGS -no-pie 2、cmake编译qt界面程序 在CMakeLists.txt文件中添加: set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -no-pie") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -no-pie")注意 …...

【css酷炫效果】实现鱼群游动动态效果

【css酷炫效果】实现小鱼游动动态效果 缘创作背景css代码创建div容器引入jquery引入鱼群js完整代码效果图成品资源下载链接:点击下载 缘 在开发系统功能的时候,无意间看到了小鱼游动特效,感觉很有意思,就在网上找了相关教程,分享给大家。 创作背景 刚看到csdn出活动了…...

【GNN】GAT

消息传递 层数越多,聚合更多的消息...

Prims region.Views 为null

原因: 导航未完成或异步问题 解决方式:使用回调确认导航完成后再操作视图 _regionManager.RequestNavigate("MonitorRegion", "MonitorView", nps, navigationResult > {if (navigationResult.Result true){var region _regio…...

在windows10系统上安装docker,然后在容器中运行GPU版本的Pytorch,并使用vscode连接该容器

一 . 安装Docker Desktop 首先打开网址https://docs.docker.com/desktop/install/windows-install/ 下载完后,双击下面的exe文件进行安装,默认情况下,Docker Desktop 安装在C:\Program Files\Docker\Docker 出现提示时,请确保…...

WPS 搭配 Zotero 插件使用

安装Zotero后,Word自动引入了插件,但WPS却没有,做为WPS的重度用户,这是不行的。 解决方案: 1.找到 Zotero.dotm 一般在安装目录下, 2.然后复制到WPS的startup下 我的目录是:C:\Users\lianq…...

卷积神经网络 - 卷积层(具体例子)

为了更一步学习卷积神经网络之卷积层,本文我们来通过几个个例子来加深理解。 一、灰度图像和彩色图像的关于特征映射的例子 下面我们通过2个例子来形象说明卷积层中“特征映射”的概念,一个针对灰度图像,一个针对彩色图像。 例子 1&#x…...

新造车不再比拼排名,恰是曲终人散时,剩者为王

据称新能源汽车周销量不再发布,这可能也预示着新造车终于到了给出答案的时候了,新造车企业前三强已基本确立,其余那些落后的车企已很难有突围的机会,而特斯拉无疑是其中的最大赢家。 3月份第一周的数据显示,销量最高的…...

学有所得-Deepin linux操作系统在安装nvidia显卡驱动后的问题修复

目标: 装有deepin V20.9的移动硬盘在系统启动后无法进入图形化界面,修复系统。 背景: 为了方便随时随地开发研究,又不破坏笔记本电脑原装的正版操作系统,在一个朗科(容量50&…...

【QT:网络编程】

网络编程的本质就是在编写应用层代码。需要传输层支持。而传输层的协议有UDP、TCP等 使用QT网络编程的API,需要在.pro文件中添加network模块,而QT中的控件和其他内容都是包含在QtCore模块中的(默认添加) QT为什么要划分模块&…...

基于srpingboot高校智慧校园教学管理服务平台的设计与实现(源码+文档+部署讲解)

技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...

分布式事务3PC解决了2PC哪些问题?

三阶段提交(3PC,Three-Phase Commit) 是对 二阶段提交(2PC,Two-Phase Commit) 的改进,旨在解决 2PC 的一些固有缺陷,特别是在分布式系统中的容错性和性能问题。以下是 3PC 比 2PC 更好的原因及其优势的详细分析: 1. 二阶段提交(2PC)的问题 2PC 是一种经典的分布式事…...

Dify 使用 - 创建 翻译 工作流

文章目录 1、选择 模板2、设置 和 基本使用3、运行应用 1、选择 模板 2、设置 和 基本使用 翻译模板 自带了系统提示词,你也可以修改 3、运行应用 右上角 点击 发布 – 更新,运行应用,就可以在新的对话界面中使用此功能 2025-03-18&#x…...

集成学习(上):Bagging集成方法

一、什么是集成学习? 在机器学习的世界里,没有哪个模型是完美无缺的。就像古希腊神话中的"盲人摸象",单个模型往往只能捕捉到数据特征的某个侧面。但当我们把多个模型的智慧集合起来,就能像拼图一样还原出完整的真相&a…...

c盘清理宝藏小工具

引言 在数字化时代,电脑的存储空间和系统性能直接影响着我们的工作效率和用户体验。C盘作为系统盘,常常因为文件堆积、缓存冗余等问题变得臃肿不堪,导致电脑运行缓慢。为了解决这一问题,我最近试用了一款名为“小番茄C盘清理”的…...

QT多媒体播放器类:QMediaPlayer

QMediaPlayer 是 Qt Multimedia 模块中的核心类,用于播放音频和视频媒体文件。它支持本地文件、网络流媒体以及实时数据源,具备播放控制、状态管理、元数据访问等功能。QMediaPlayer的基本用法可能包括设置媒体源、控制播放(播放、暂停、停止…...

Java动态代理模式深度解析

1. 动态代理基础 1.1 核心组件 Proxy 类:动态生成代理对象的工厂类,核心方法为 newProxyInstance()。 InvocationHandler 接口:代理逻辑的处理器,所有方法调用会转发到其 invoke() 方法。 1.2 实现步骤 定义接口:代…...

【WRF模拟】垂直层设置/与观测数据对比

【WRF模拟】垂直层设置/与观测数据对比 WRF 中 有关垂直层的namelist变量1. 主要垂直层设置参数2. 详细解释3. 典型设置示例WRF 输出的垂直剖面数据与观测数据进行比较WRF 采用 地形跟随坐标(terrain-following coordinate)WRF 输出的垂直剖面数据与观测数据进行比较参考WRF …...

植物知识分享论坛毕设

1.这四个文件直接是什么关系?各自都是什么作用?他们之间是如何联系的? 关系与联系 UserController.java 负责接收外部请求,调用 UserService.java 里的方法来处理业务, 而 UserService.java 又会调用 UserMapper.jav…...

可视化图解算法:链表中倒数(最后)k个结点

1. 题目 描述 输入一个长度为 n 的链表,设链表中的元素的值为ai ,返回该链表中倒数第k个节点。 如果该链表长度小于k,请返回一个长度为 0 的链表。 数据范围:0≤n≤105,0 ≤ai≤109,0 ≤k≤109 要求&am…...

qt下载和安装教程国内源下载地址

qt不断在更新中,目前qt6日渐成熟,先前我们到官方下载或者国内镜像直接可以下载到exe文件安装,但是最近几年qt官方似乎在逐渐关闭旧版本下载通道,列为不推荐下载。但是qt5以其广泛使用和稳定性,以及积累大量代码使得qt5…...

html5表格实战-跨行跨列

效果如图 代码如图...

使用OBS进行webRTC推流参考

参考腾讯云官方文档: 云直播 OBS WebRTC 推流_腾讯云 说明非常详细,分为通过WHIP和OBS插件的形式进行推流。 注意:通过OBS插件的形式进行推流需要使用较低的版本,文档里有说明,需要仔细阅读。...

Rockchip --- 图像时延优化

通过配置wait-line,即图像采集多少行后提前输出buffer给ISP,而无需等待图像全部采集完毕。一般设置为图像采集一半后提前输出buffer给ISP (一)VICAP提前输出 Video Input CAPture是用于图像采集和处理的子系统 1. 通过dts配置 …...

微软 LIDA 库:基于大模型的自动化数据分析与可视化

微软 LIDA 库:基于大模型的自动化数据分析与可视化 一、核心架构与 LLM 交互流程 #mermaid-svg-UzSwZNKPlgrJUpej {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-UzSwZNKPlgrJUpej .error-icon{fill:#5…...

java-正则表达式-集合-泛型

正则表达式 正则表达式到底是什么东西? 在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。 http://tool.oschina.net/r…...

力扣刷题994. 腐烂的橘子

994. 腐烂的橘子 - 力扣(LeetCode) 使用bfs,先遍历所有的橘子,统计所有的橘子数量,然后把腐烂的橘子放入队列当中,然后进行bfs遍历,套用bfs的模版,但是每一次出队的橘子&#xff08…...

Kubernetes的Service详解

一、Service介绍 在 kubernetes 中, pod 是应用程序的载体,我们可以通过 pod 的 ip 来访问应用程序,但是 pod 的 ip 地址不是固定的,这也就意味着不方便直接采用pod 的 ip 对服务进行访问。 为了解决这个问题,kuberne…...

Linux目录理解

前言 最近在复习linux,发现有些目录总是忘记内容,发现有些还是得从原义和实际例子去理解会记忆深刻些。以下是个人的一些理解 Linux目录 常见的Linux下的目录如下: 1. 根目录 / (Root Directory) 英文含义:/ 是文件系统的根…...

vue中js简单创建一个事件中心/中间件/eventBus

vue中js简单创建一个事件中心/中间件/eventBus 目录结构如下: eventBus.js class eventBus {constructor() {this.events {};}// 监听事件on(event, callback) {if (!this.events[event]) {this.events[event] [];}this.events[event].push(callback);}// 发射…...