回顾并为今天的内容做好铺垫
今天,我们将对游戏的分析器进行升级。在之前的修复中,我们解决了分析器的一些敏感问题,例如它无法跨代码重新加载进行分析,以及一些复杂的小问题。现在,我们的分析器看起来已经很稳定了。然而,当前分析器的显示界面还不太好,虽然它能够提供一些基本的分析数据,但还不足以方便地展示详细信息,也没有提供有效的交互方式。
因此,今天我们需要重点改善分析器的渲染部分。我们要让它能够展示更多详细信息,并且提供一种更直观、更易于操作的交互方式。为了达到这个目标,我们将对分析器的显示部分进行升级和优化。
运行游戏并演示我们如何开发性能分析器
目前,我们的分析器已经有了基本的框架,但它还存在很多问题,需要进行改进。当前分析器的显示界面非常简单,实际上它并没有提供有效的用户界面。我们只能看到顶层的信息以及已经观察到的线程,但目前没有加载到其他线程的数据,所以它们在分析器中没有显示任何时间消耗。
即使在游戏的发布模式下,分析器的显示也非常简单,并没有很多工作被处理。如果我们切换到调试模式,就会发现其中一些块占用了更多的时间。比如,游戏更新的时间在调试模式下显著增加,而在发布模式下则非常小。这个现象引发了一个问题:究竟是编译器优化导致的性能差异,还是在调试模式下我们做了某些检查,导致了大量的时间消耗?我们无法从目前的分析器界面中获得这些信息,所以需要做进一步改进,使得分析器可以提供交互式的功能,允许我们深入了解这些数据。
此外,目前的分析器显示存在一些问题,比如如果鼠标悬停在某些部分时,界面下方的元素仍然可以被选择,这是不应该出现的。我们需要解决这个问题,使得分析器在交互时更加直观。
当切换到软件渲染模式时,分析器的显示也出现了异常,界面变成了粉色,这让我们无法准确看到分析数据的渲染效果。虽然我们可以看到一些调试信息,但在软件渲染模式下,分析器的表现显然存在问题,可能是与多线程渲染相关的BUG导致的,尤其是在非软件渲染模式下,其他线程的数据几乎不被使用,因此我们看不到多线程的渲染效果。
这些问题都表明,当前的分析器界面仍有大量的工作需要做。接下来,我们会着手解决这些问题,首先从改进分析器的绘制函数开始,确保它能够正确渲染并提供详细的调试信息。
软件渲染
硬件渲染
game_debug.cpp:查看性能分析器的代码
在分析器的多线程渲染部分,观察到一个问题。首先,背景矩形的绘制是正确的,它的作用是为了让分析器窗口背景更暗一些,看起来更清晰。然后,在计算每个“Lane”的高度时,首先会计算出总共有多少个“Lane”,然后把总高度除以这个值,得到每个“Lane”的高度。这个部分在硬件渲染模式下是正常的,因为只有三个活动线程:两个资产加载线程和主游戏线程,所以显示出的结果是将总高度均匀地分成了三个部分,看起来是正确的。
当切换到软件渲染模式时,分析器开始显示更多的线程信息,之前只有三个线程,但现在有更多线程被显示,导致每个部分变得更小。这说明线程数量的计算是正确的,因为它根据活动线程的数量自动调整了“Lane”的大小。
接下来,查看绘制线程信息的部分。这里,我们正在遍历所有根节点的直接子节点,然后绘制这些节点。为了绘制,每个线程的颜色是基于一些指针计算的,虽然这里没有特别的逻辑,只是为了获取一个颜色。对于绘制的X轴坐标的最小值和最大值,似乎计算是正确的,显示出来的结果也没有问题。因此,X坐标的计算没有问题。
然而,在Y轴的计算中,发现一个问题。在计算每个线程绘制的位置时,使用了“Lane索引”来决定该线程应该绘制在哪个“Lane”。问题在于,当前并没有正确设置“Lane索引”,这导致所有的线程都被绘制到第一个“Lane”上。换句话说,无论线程属于哪个“Lane”,它们都被绘制到同一个位置,这显然是一个错误。
总的来说,问题出在“Lane索引”没有被正确设置,因此所有的线程都被错误地绘制到了第一个“Lane”上。这个问题需要解决,以确保每个线程能够被正确地分配到相应的“Lane”。
game_debug.cpp:使用 ThreadOrdinal 来设置 LaneIndex
在处理分析节点时,首先,已经完成了确定每个节点应该在哪个“Lane”的工作。这个过程是在整理数据时进行的,因此可以认为,线程的序号(thread ordinal)就是我们用来确定“Lane索引”的信息。现在,可以直接将“Lane索引”设置为线程的序号,这样做更合理。
在计算每个“Lane”的Y轴坐标时,使用的是“最大Y值”开始,并向上移动。具体来说,Y轴的最小值将是“LaneIndex + 1”乘以“行高”,而最大值则是“LaneIndex”本身。这种计算方法是合理的,因为它表示从当前“Lane”位置的下一行开始,再向上计算。
需要注意的是,Y轴的坐标是以数学坐标系为基础的,其中Y值是随着向上增加的,而在屏幕坐标系中,Y值通常是向下增加的。所以这里的Y值处理方式是正确的。
接下来,使用了“push rect”调用来推送我们创建的矩形,这个过程也看起来是合理的。然后,检查鼠标是否位于这个矩形内。根据当前的实现,节点的交互部分还没有完全处理,应该将打印输出部分改为交互式的,或者至少增加一个交互功能,允许点击等操作。
总结来说,当前的实现已经正确地处理了每个分析节点的位置和大小问题,接下来需要做的是将其改为交互式的,使得用户能够点击和操作这些分析节点。
运行游戏,查看在 LANES 中的性能分析信息
在进行对“Lane”绘制的调整后,可以看到软件渲染器下的行为发生了一些变化。现在,“Lane”正确绘制,且能够看到在加载资源时的一些噪声,这表明资产系统正在加载。具体来说,可以看到主游戏线程和两个资产加载线程的状态,虽然资产加载线程此时没有进行任何操作,但它们依然被显示了出来。同时,还能看到子渲染工作线程的情况,它们的执行时间大致相同,并根据各自的工作量结束执行。
此外,还能够看到鼠标悬停效果现在正常工作,这表明交互功能部分已经得到了修复。尽管如此,填充矩形的背景仍然存在问题,具体表现为背景填充似乎在某些情况下失效。为了进一步排查这个问题,计划关闭背景填充,看看是否能够找出可能存在的奇怪行为。
目前,软件分析结果显示了正确的线程信息,但对于背景清除仅在某些阶段有效的情况仍然感到困惑,尚不清楚为什么背景填充会在某些渲染阶段无法正常显示。这一现象需要进一步调查和调试。
切换到软件渲染是这样的,为什么硬件渲染的线程很少呢奇怪
game_debug.cpp:调查为什么软件渲染器中的背景颜色不正确
我的软件渲染没问题
问题可能出在没有实现透明度混合(alpha blending)处理,这可能是导致矩形绘制异常的原因。假设背景填充的透明度处理存在问题,理论上透明度混合未启用可能导致矩形的背景填充效果不正常。不过,另一个让人困惑的问题是为什么绘制出来的背景是纯紫色,并且紫色覆盖在其他内容上。如果只是背景色绘制问题,且矩形绘制没有正确显示,应该是渲染过程中出错,而不仅仅是颜色显示问题。
另一个疑问是,为什么只有在软件渲染器中出现这种情况,而硬件渲染器却没有类似的问题。理论上,硬件渲染器和软件渲染器应该有相同的排序逻辑,但似乎软件渲染器的排序方式存在异常。可能是绘制时的顺序处理不正确,尤其是在使用背后变换(backing transform)和绘制变换(drawing transform)时,可能需要添加额外的Z轴层级来正确处理不同层级的绘制。换句话说,可能需要更多的Z轴层级来处理图形的绘制顺序,从而避免不同渲染层次之间的混乱。
然而,问题依然不完全清楚,特别是为什么硬件渲染器没有显示出类似的问题,这也让人感到困惑。总体来说,当前的问题涉及绘制顺序和透明度混合的处理,仍需进一步排查和修复。
切换到软件渲染,之后切回到硬件渲染就正确了
先不管
game_debug.h:引入 UITransform 层
我们尝试将绘制的内容移动到另一个中间层,目的是看看这样做是否会改变显示效果。目前我们已经知道当前的绘制层级系统可能过于粗糙,不够细致,可能导致图形覆盖顺序出现问题。虽然这并不是一种理想或长远的解决方式,但暂时我们还是采用这种方法进行测试。
我们在当前的背后变换层和前方的绘制层之间,插入了一个新的图形变换层,并将相关绘制内容放置到这个新层级上,希望通过这种方式观察绘制顺序是否发生改变。通过这样做,可以更清楚地判断现有的Z轴层级管理是否存在缺陷,是否需要更精细的控制手段来管理绘制优先级。
我们接下来会观察插入这个中间层之后,轮廓信息(profiler)显示是否发生了变化,如果有,那说明绘制顺序确实是导致当前显示异常的主要原因。这个实验将帮助我们进一步确认Z轴层级排序机制是否有效,也为后续可能的系统优化提供参考。
运行游戏,查看性能分析并调查为什么它是粉色的
我们观察到调整图形绘制到中间图层之后,确实修复了图形排序的问题。平台控件一旦开启,排序就恢复正常,这说明图层顺序(Z层级)确实对渲染结果起到了关键作用,说明排序机制本身在一定程度上是有效的。
但是,即使修复了排序的问题,仍然存在一个奇怪的现象:绘制出来的背景矩形颜色是紫色的,而我们本来期望的是黑色,并带有一定的透明度。我们显式传入的是黑色(R=0,G=0,B=0)和 alpha 值为 0.25,但却意外显示为紫色,这显然是个问题。
为了解释这个问题,我们查阅了用于绘制矩形的相关渲染代码,发现当前的 DrawRectangle
函数实际上并不支持 alpha 混合操作。也就是说,即使我们传入了 alpha 值,该函数也并不会以半透明的方式进行绘制,它会直接忽略 alpha 值,使用当前颜色进行不透明绘制。
这就解释了背景看起来不透明的问题,但并没有解释为什么颜色变成了紫色。因为传入的是黑色,我们按理说应当看到的是纯黑或接近黑色的区域。紫色的出现可能表明在某个环节中颜色值被错误解释或混合了其他颜色。也可能是因为当前像素缓冲区初始值或颜色格式处理不正确,例如默认颜色偏向于红蓝通道而非RGB全0。
总结当前问题:
- 图形排序问题已通过添加中间图层解决;
- 矩形颜色异常(紫色)源于
DrawRectangle
不支持 alpha blending; - 接下来应改进
DrawRectangle
实现,使其支持 alpha blending; - 同时检查颜色通道数据是否被错误处理,确保颜色值与预期一致。
后续步骤建议:
- 实现或修复
DrawRectangle
中的 alpha 混合逻辑; - 添加颜色分量和 alpha 的 debug 可视化工具,帮助更快定位问题;
- 验证颜色值是否正确传入并被 GPU/软件渲染路径正确处理。
这些修改将有助于我们更准确地调试 UI 渲染问题并提升工具可用性。
没啥问题
game_render.cpp:使 DrawRectangle 支持 SIMD,并进行 alpha 混合
我们计划修复当前矩形绘制不支持 alpha 混合的问题,并决定通过实现一个新的 DrawRectangle
函数版本来完成这个目标,同时保留对性能的优化考虑。以下是我们逐步推进的详细过程和逻辑:
目标
- 重新实现一个只绘制矩形的新版本
DrawRectangle
,并支持 alpha blending。 - 该版本不处理贴图、纹理等复杂操作,仅用于绘制屏幕对齐的填充矩形。
- 需要尽量保留已有优化路径,如 SIMD 向量化处理。
操作步骤详解
-
剥离纹理处理逻辑:
- 原函数中包含大量用于处理纹理(Texel fetch、U/V采样、双线性插值)的代码。
- 在新的矩形绘制逻辑中,这些内容都无用,因此我们全部移除。
-
保留绘制区域的裁剪逻辑:
- 保留
Min/Max X
的边界计算,确保矩形仅在视口范围内进行绘制。 - 裁剪区域依旧通过 clip region 与 fill region 的交集实现。
- 保留
-
移除旋转与方向支持:
- 由于绘制的是屏幕对齐矩形,不需要任何旋转或向量方向处理。
- 去除 access vectors 和旋转计算逻辑。
-
构建填充逻辑:
- 使用传入颜色(已预乘 alpha 值)进行填充。
- alpha 混合逻辑将当前 framebuffer 中的目标像素颜色与目标填充色进行混合。
- 目前尚未明确使用 gamma 校正是否必要,暂时保留。
-
去除无关变量:
- 删除与纹理、像素采样相关的变量和计算。
- 像素包含测试逻辑也删除,因为我们是在像素块内直接绘制。
-
统一 mask 运算:
- 将 clip mask 和右边界遮罩处理统一,只需一次位操作。
-
变量重构与提升作用域:
- 为了减少重复声明,对一些变量作用域进行了上提,便于多处共用。
-
完成填充代码初稿并集成测试:
- 最终形成一个可以执行 alpha blending 填充的绘制函数。
- 准备测试是否在平台层构建后能正常执行。
初步测试结果
- 新增的
DrawRectangle
版本似乎没有生效,初步判断可能是因为当前渲染系统还在平台层中构建,而我们没有为这个新渲染路径添加额外的独立渲染层。 - 需要后续创建一个专用渲染层用于该绘制逻辑,确保其能被正确编译并调用。
总结与后续计划
-
当前已经完成了一个简化的
DrawRectangle
实现,支持 alpha 混合,剥离了所有与纹理、贴图相关的逻辑。 -
后续工作包括:
- 创建并注册一个新的渲染层,确保这个新绘制路径能被调用。
- 验证 alpha blending 实际混合效果是否正确。
- 若需要进一步优化,可考虑用 SIMD 实现高性能像素处理。
- 检查并决定是否保留或调整 gamma 校正相关处理逻辑。
该过程使绘制路径更清晰、高效,并为后续构建更完整的 UI 渲染基础设施奠定良好基础。
clangd 不知道为什么宏拼接报错误先不管能编译过就行
调试器:在 FillRect 上断点,发现它和 ClipRect 从未交集
颜色混合的看不懂,不管
以下是上述内容的中文详细总结,不涉及开发者或叙述者,仅以第一视角客观描述调试过程和发现的问题:
在运行代码时,进入了相关渲染逻辑的执行路径,本应能够看到绘制出来的矩形,但实际上却完全没有显示出任何矩形,这是一个明显的问题。接下来进行了调试。
尝试对线程调试进行简化,原先希望能够以单线程方式查看渲染过程,但调试工具没有提供方便的逐线程执行选项,于是采用了一种变通方法:人为将高优先级渲染队列中的任务数设为 0,从而迫使只使用主线程进行渲染。
在主线程中观察渲染时,发现绘制的矩形“面积为零”,这显然不对。为了找出原因,进一步检查了“填充矩形(fill rect)”与“剪裁矩形(clip rect)”的交集情况,结果发现两者根本没有任何交集。
因为没有交集,所以整个渲染过程中的矩形绘制代码实际上从未被执行,这就是为什么屏幕上完全看不到任何矩形的原因。
这一发现说明问题的核心不是渲染函数逻辑错误,而是矩形参数(包括填充区域与剪裁区域)本身设置就有问题,导致了所有绘制操作都被跳过。因此需要进一步调查为什么这两个矩形区域没有交集,以修正根本问题。
软件硬件渲染来回切换会有问题
game_render.cpp:提前设置 FillRect
当前的渲染逻辑代码在多个地方使用的是相同的实现,因此暂时没有改动它。问题仍然是为什么始终没有任何矩形被渲染出来。接下来尝试检查是否从一开始就正确生成了矩形。
进行了一些调整之后,成功地生成出了一个明显可见的矩形。这表明之前可能是根本没有生成足够的可绘制矩形,导致看起来什么都没画出来。现在可以进一步检查具体参数来找出问题所在。
设置了一些变量和状态,打开了调试器的观察窗口(watch window)来检查当前变量值。通过查看 ymax
等变量的具体值,可以开始分析矩形坐标是否在期望范围之内,以此来进一步确认绘制失败的具体原因。
当前的初步结论是:可能并非渲染代码本身有严重问题,而是传入的矩形坐标参数在逻辑上出了问题,导致矩形被裁剪或判定为无效,从而未被绘制。后续需要继续分析坐标计算与裁剪逻辑是否匹配。
调试器:逐步执行并检查矩形值
我们继续调试矩形填充逻辑,进一步排查为什么屏幕上始终看不到预期的渲染效果。
首先确认了一些基本参数,比如 ymin
、xmax
、xmin
的值,发现这些值范围正常,显然有足够的区域应该被填充。然而观察发现,右侧遮罩 right mask
和裁剪遮罩 clip mask
是相同的,说明我们并没有屏蔽掉任何像素,所有像素都应该被写入。这从 right mask
是全开启的状态也得到了确认。
接着检查了目标像素的读取和颜色的设置,Texel 颜色确实是全黑(全为 0),这是预期的。进行混合时,使用的是 alpha 值为 0.25 的预乘颜色。程序中通过平方操作计算颜色分量的绝对值,然后进行 alpha 混合。理论上,当前的操作应该使目标像素被填充为 75% 原像素颜色 + 25% 新颜色的混合值。
进一步检查 Texel 的 alpha 值是 0.25,对应的反向 alpha 值为 0.75,都是符合预期的,数值也转换到了 $$0, 1] 区间中。也就是说,数学逻辑上看不出什么问题。
问题在于,尽管看起来流程正确,我们实际观察到屏幕像素值始终没有变化。通过检查输出像素值,发现写入的像素颜色始终没有发生变化,无论怎么执行,这个值都不变。说明混合过程虽然运行,但结果是恒定的。
因此目前怀疑的核心点在于 alpha 混合逻辑可能有某处没有生效。尤其需要重点排查的是目标像素和源像素之间混合计算时,可能因为某个计算路径不正确,导致始终写入相同的颜色,或者写入了一个错误的颜色值。
接下来的计划是进一步验证 alpha 混合公式的实现是否符合预期,特别是 Texel alpha 和目标颜色值在混合时是否被正确转换与使用。下一步可能会尝试手动计算几个示例值与实际输出做比对,找出问题根源。
game_render.cpp:使用结构化艺术来帮助调试
为了更清晰地跟踪颜色通道的渲染情况,我们决定采用结构化的调试方式,方便观察和验证各个颜色通道的效果是否正确。
具体操作是:不再使用实际预期的颜色值进行填充,而是人为设置某些颜色通道为明显的测试值。比如我们将红色通道(R)设为 1.0,绿色通道(G)设为 0.0,蓝色通道(B)也设为 1.0,alpha 保持不变或者设为测试值。通过这种设定,可以在渲染结果中直观地区分不同通道是否生效。
例如,只启用红蓝通道而关闭绿色通道,这样如果颜色看起来有绿色,就说明混合或填充过程中绿色值被错误地引入了;如果渲染结果准确反映红蓝混合的颜色,那就说明当前颜色值设置与传递是有效的,问题可能出现在别处。
通过这种方式,我们希望可以一步步排查是颜色值设置的问题、混合计算的问题,还是写入目标像素的问题。这种结构化的调试策略可以更有条理地定位颜色通道混合异常的根源。
调试器:检查混合后的值
在调试过程中,首先查看了纹理的颜色值(Texel),这些值是按预期显示为 1 的,意味着纹理的颜色通道是完全饱和的。然后,观察到目标值(destination value)为 9,这有点让人困惑,因为通常情况下目标像素的值应该在 0 到 255 之间。
接下来,进行了混合操作(blending),并发现最终的结果是 3,这引起了疑问:为什么混合后的结果会比预期的值小得多?
通过这种观察,怀疑可能对混合的颜色空间理解存在问题。通常情况下,传入的颜色值是在 0 到 1 之间的浮动范围,而目标像素的值则是在 0 到 255 之间的整数值。然而,看起来在进行颜色调制(modulation)时,这些值的空间处理可能存在一些不一致,导致结果和预期不同。也有可能是混合过程中的一些其他因素,导致计算过程中出现了不同的结果。
需要更仔细地检查色彩值的转换与混合操作,确保在不同颜色空间之间的转换与计算都是正确的。
game_render.cpp:将颜色乘以 255,以便将其转换到正确的颜色空间
在调试过程中,发现输入的颜色值(incoming color)是基于 0 到 1 的浮动范围,而纹理的颜色值(Texel)仍然处于 0 到 255 之间,这就是为什么最终结果中出现了 255 的原因。因此,需要首先对 RGB 进行重新编码,使其适应 0 到 255 的范围。
为了让颜色值正确地与目标值匹配,必须将输入颜色转换为 0 到 255 的范围,因为不再加载纹理,因此需要调整输入值的范围,才能确保颜色计算正确。
这段代码的目的是尽量减少改动,因此用最小的修改来调试填充矩形的过程。
运行游戏,发现颜色不正确并调查原因
问题出在颜色值的处理上,虽然 alpha 值看起来正确,但是颜色通道的处理却不对。首先,检查了 Texel 的 alpha 值,它是正确的,但颜色值似乎没有按预期传递。
在代码中,对颜色值进行了预乘操作,理论上这应该是正确的,但结果仍然是颜色值没有正确显示。特别是 RGB 值被统一处理,似乎没有区分不同的颜色通道。进一步检查代码后发现,确实在处理颜色时,对每个颜色通道都做了预乘操作,但似乎还没有完全考虑到 alpha 值的预乘应该如何在每个颜色通道上进行。
根据代码逻辑,颜色值应该先乘以其倒数平方根,然后再进行进一步的处理。而在调试时,发现颜色通道的值似乎没有分开处理,导致颜色显示异常。
进一步检查发现,问题可能出在“最大颜色值”处理上,这可能导致颜色值的范围出现问题。
game_render.cpp:提前平方 ColorValues
问题出在颜色值的处理方式上,特别是在进行颜色混合时,发现加载的颜色值是以 255 为基础的,并且使用的是平方空间。由于预期的颜色应该在一个线性空间内工作,所以需要先将颜色值平方才能适应正确的空间。
在当前的处理方式下,RGB 颜色值需要先进行平方操作,而 Alpha 值不需要平方。这个处理逻辑也跟 sRGB 转换和颜色通道的处理有关,虽然 Alpha 值在 sRGB 空间中并不需要平方,但 RGB 值确实需要。
进一步检查时发现,Alpha 值保持在 0 到 1 之间,并且在此过程中没有进行平方处理。这个问题可能是因为 Alpha 值并不需要转换到 sRGB 空间,因此不进行平方。而 RGB 通道则是需要经过平方处理的,这就是导致颜色值显示不正确的根本原因。
运行游戏,看到颜色现在是正确的
目前的问题可能出在硬件渲染器上,它在处理颜色时没有正确应用 Alpha 值,特别是在进行固态颜色填充时。硬件渲染器不会使用 sRGB 空间来处理输入的颜色,这可能导致了颜色值的失真。尽管软件渲染部分看起来已经接近正确,但硬件渲染器可能在 Alpha 处理上存在不准确的地方。
总的来说,虽然颜色值的处理逻辑上已经接近正确,但依然有细微的问题。这个问题可能源自硬件渲染器,而不是软件渲染部分的颜色处理。
我记得这个应该是内存对齐导致的
在使用 __m128i
类型进行 memcpy
或存储操作时,_mm_store_si128
是一个非常高效的 SSE 指令,用于将 128 位的寄存器数据存储到内存。然而,它对内存地址有严格的对齐要求:目标内存地址必须是 16 字节对齐的,否则会导致“内存对齐段错误”(Segmentation Fault)。
错误原因:
_mm_store_si128
需要将数据存储到一个 16 字节对齐的内存地址。如果目标地址没有正确对齐,处理器将无法有效地进行内存访问,从而导致段错误。
解决方案:
-
确保目标内存地址对齐:
确保Pixel
指向的内存是 16 字节对齐的。可以使用aligned_alloc
(在 C11 中)或者posix_memalign
来分配 16 字节对齐的内存。示例如下:#include <stdlib.h>// 分配一个 16 字节对齐的内存 int* Pixel = (int*)aligned_alloc(16, sizeof(int) * 4); // 为了保证 16 字节对齐
或者使用
posix_memalign
,它允许指定对齐字节:#include <stdlib.h>int* Pixel; posix_memalign((void**)&Pixel, 16, sizeof(int) * 4);
上面的代码将确保
Pixel
内存是 16 字节对齐的。 -
手动调整指针的对齐:
如果你有一个已经分配的内存,并且你不能更改内存分配的方式(例如,它来自于某个第三方库),你可以手动调整指针使其满足 16 字节对齐要求。不过这种做法复杂且容易出错,因此不推荐。 -
检查内存对齐:
如果你不确定内存是否对齐,可以在使用_mm_store_si128
之前使用reinterpret_cast
或uintptr_t
来检查地址是否是 16 字节对齐的:if ((uintptr_t)Pixel % 16 != 0) {// 处理未对齐的情况// 例如使用对齐的内存分配器分配内存或报告错误 }
-
使用其他存储方法(如果内存对齐不是必须的):
如果你不需要高效的 SIMD 存储操作,且内存对齐不是必需的,你可以考虑使用其他存储函数,如memcpy
,虽然这样会牺牲一部分性能,但能够避免内存对齐问题:memcpy(Pixel, &MaskedOut, sizeof(MaskedOut));
总结:
要避免“内存对齐段错误”,你必须确保目标内存地址是 16 字节对齐的。最常用的方式是使用 aligned_alloc
或 posix_memalign
来分配内存。在没有正确对齐的情况下,使用 _mm_store_si128
将导致程序崩溃。
跟对齐没关系
忘记取交集了导致图片太大
出现另外的一个问题得清一下背景
win32_game.cpp:重新启用线程并查看性能分析器
通过重新启用线程,问题似乎得到了改善。现在,矩形的渲染结果与之前相似,但在颜色处理上仍然存在一些不确定性。具体来说,虽然矩形的形状和渲染看起来正确,但在颜色方面可能存在一些偏差。总的来说,代码做出了调整,但颜色的正确性仍需进一步确认和修正。
game_opengl.cpp:在 OpenGLRectangle 中考虑 1 像素的边框
在渲染过程中,切换到硬件渲染器后,字体和整体渲染效果有些不对,导致了一些视觉偏差。一个论坛上的用户提醒了一个重要细节,那就是在之前的渲染方法中,为了避免在双线性插值时从纹理外部获取数据,加入了一个像素的“边缘”区域(texel apron)。实际上,纹理的实际使用区域是纹理内部区域,而外围的这一区域并不被使用。为了修复这一问题,代码中需要进行UV坐标的修正。
硬件渲染器没有进行这种修正,因此渲染的结果可能会错位。如果在硬件渲染器中加入UV修正,就有可能改善纹理的对齐问题。具体的做法是,当调用OpenGL进行矩形渲染时,需要传入修正后的纹理坐标,而不是使用标准的OpenGL矩形。
为了实现这一点,可以提前设定正确的UV坐标范围(min UV、max UV),然后在渲染时传递这些修正后的坐标。这样,之前没有进行修正的渲染就会使用默认的参数,而修正后的渲染会使用正确的纹理坐标。
通过这一改动,期望渲染的效果能够更接近实际图像,而不是像之前那样出现错位或不正确的纹理渲染。
game_opengl.cpp:将修正后的坐标传递给 OpenGLRectangle 在 OpenGLRenderCommands 中
在调用OpenGL矩形渲染时,计划传递最小和最大UV坐标。为了实现这一点,需要计算纹理的texel偏移量,方法与软件渲染器中的计算方式相同。具体来说,需要从一个texel开始,并在结束时退回一个texel。因此,需要进行相应的计算,确保在U和V方向上的texel偏移量正确。
由于U和V的范围是0到1,而位图的宽度和高度不同,U和V的texel大小也不同,因此需要分别计算在U和V方向上一个texel的大小。这可以通过已知宽度和高度的信息来完成。
为避免除以零的情况,代码中会在计算texel偏移量之前,确保位图的宽度和高度有效,防止位图的宽度或高度为0。若发现无效的情况,就不渲染任何内容,而不是让程序崩溃,这样处理起来更安全。
总的来说,关键是确保UV坐标和texel计算的正确性,尤其是在不同的位图宽度和高度情况下,避免出现因无效数据导致的渲染错误或程序崩溃。
运行游戏,看到字体看起来更正确
现在字体看起来明显更加正确了,已经更接近我们期望的显示效果。进行渲染器切换测试后,可以看到两者之间的差别几乎看不出来了,说明渲染精度已经非常接近了。这是一个很大的进步。
虽然仍然可以在字体上稍微看出些许差异,但问题已经非常微小。推测原因可能是软件渲染器在处理像素中心位置时,未能正确进行预偏移(pre-step),导致像素对齐略有偏差。这种偏差可能使得字符略微模糊或偏位一小点。
除此之外,其它渲染部分几乎完全对齐了,现在软件渲染器和 OpenGL 渲染结果高度一致。这说明之前在纹理坐标校正、UV 修复、颜色空间一致性等方面所做的调整都已经奏效。
总结:
- 字体显示更准确,整体效果非常接近;
- 剩下的微小差异可能来自像素中心位置的处理不一致;
- 软件渲染器和硬件渲染器在视觉上几乎一致;
- 整体目标基本达成,渲染对齐问题得到有效解决;
- 项目这一阶段可以顺利收尾。
我们在 GitHub 上建立了一个 Bug 列表,用来记录一些待修复的问题。这些问题包括我们在开发过程中发现但还未修复的,也有来自社区论坛用户反馈的,比如在不同编译器环境下的编译支持问题等等。
趁现在空闲时间,我们也可以顺便修复其中一些问题。例如其中一个问题是 sRGB 检测相关的拼写错误。之前在 win32_game
文件中修改了与 sRGB 相关的代码逻辑,但当时修改后没有保存,导致修复未生效。
我们重新检查该部分代码时,很快就定位到了错误所在,sRGB 相关的变量拼写错误存在于检测流程中,现在我们已经重新打开该文件准备修复。问题位置在代码中较为明显,很容易修改完成。
总结:
- 创建了 Bug 跟踪列表用于集中管理未修复问题;
- sRGB 检测流程中存在拼写错误,之前修改未生效;
- 当前已重新定位到错误位置,准备完成修复;
- 利用零碎时间修复一些已知小问题,提升系统整体稳定性。
win32_game.cpp:在 Win32SetPixelFormat 中检查 OpenGLSupportsSRGBFramebuffer
我们在查看一段关于 sRGB 帧缓冲支持的代码时,发现之前有一处逻辑理解错误。
具体地说,这里判断的条件不应该是 OpenGL 默认的内部纹理格式(internal texture format),而是应该检查当前环境是否支持 sRGB 帧缓冲扩展。这个扩展决定了是否能够在渲染输出时自动执行 gamma 校正操作。如果没有这个扩展的支持,我们就不应该启用相关的 sRGB 渲染路径。
具体的逻辑如下:
- 原始意图 是通过一个条件判断是否应该设置纹理格式为
sRGB
。 - 实际上,这个条件应该是:只有当显卡支持 sRGB 帧缓冲时,才设置纹理格式为 sRGB;否则应完全绕开该路径。
原因在于渲染流程中的 gamma 处理:
- 我们的软件渲染器是手动执行 gamma 解码和编码的(linear space → gamma space)。
- 如果我们在没有 sRGB 帧缓冲支持的情况下,仍然将纹理从 gamma 空间解码为 linear space(即“解 gamma”),但又无法在输出时重新进行“加 gamma”(因为没有自动支持),那么图像输出就会显得发灰、偏淡、对比度不足。
- 所以,为了保证输出图像正确,如果帧缓冲不支持 sRGB,我们应该完全绕过 gamma 解码过程,让所有处理都在 gamma 空间中完成,这样反而更接近正确结果。
总结如下:
- 判断逻辑应基于是否支持
sRGB framebuffer
扩展,而不是纹理格式本身; - 如果不支持 sRGB framebuffer,就不应该设置纹理为 sRGB 格式;
- 否则图像会出现 gamma 曲线应用不对称,导致颜色失真;
- 这在实现手动 gamma 操作的软件渲染路径时尤为关键;
- 当前我们已理解错误来源,并计划修复相关条件判断逻辑。
game_opengl.cpp:使 OpenGLInit 处理 FramebufferSupportsSRGB
我们希望在这里做一些调整,以提高对sRGB的支持,并确保在OpenGL环境中更好地兼容不同的纹理格式。
具体来说,我们需要判断当前环境是否支持sRGB扩展以及相关功能。如果帧缓冲支持sRGB,并且我们具备对应的扩展功能,那么我们应该启用它。这是因为仅在部分环节开启sRGB会导致图像显示错误,比如仅启用sRGB纹理或仅启用sRGB帧缓冲,都会使图像失真,结果非常糟糕。
因此,正确的做法是在满足所有前提条件的情况下——包括具备所有必要的OpenGL扩展、底层系统确实支持sRGB流程——我们才会启用sRGB功能。否则,就必须完全关闭,以避免错误的颜色转换。
另外,在执行OpenGL初始化时,需要确认相关设置是否已正确配置。我们此前已经完成了对交换间隔(swap interval)的查询和设置,这部分是无障碍通过的,但仍需仔细核对,确保整个sRGB支持链路在逻辑上和技术上都是完整一致的。这样才能在所有平台上获得正确且一致的渲染结果。
居然没有命中
运行游戏,看到字体看起来更正确
目前已经可以明显看出,字体渲染效果更接近预期,显示也更加准确。切换对比后,几乎看不出太大差别,说明渲染输出已经非常接近。虽然可能仍存在一些微小偏差,但这些差别非常难以察觉。
从字体渲染的细节来看,可以判断当前软件渲染器在处理像素中心时仍有轻微偏移,具体来说,我们并没有完全预处理到正确的像素中心位置,因此导致了这一点点偏差。不过整体上看,现在的渲染结果已经与 OpenGL 渲染非常一致,对齐效果几乎完美。
这说明整体渲染管线、位置精度和图像输出已经达到了很高的一致性,是一次非常成功的改进和验证。接下来可以将该部分流程收尾,准备进行下一步工作。
win32_game.cpp:在 Win32SetPixelFormat 中检查 OpenGLSupportsSRGBFramebuffer
我们这里要特别注意的一点是,判断的重点并不是 OpenGL 默认的内部纹理格式是否是 sRGB,而是系统是否支持相应的扩展,也就是是否支持 sRGB 帧缓冲(framebuffer)的相关能力。这才是我们真正需要依据的条件。
如果系统不支持 sRGB 帧缓冲,那么我们就不应该启用相关设置。不能只是单方面设置纹理为 sRGB 格式而忽略帧缓冲的支持情况。如果帧缓冲不支持 sRGB,那我们也不应该去使用 sRGB 纹理格式。
这是因为在进行 sRGB 渲染时,我们的流程是这样的:从纹理中读取 texel,然后对其进行解 γ(gamma correction),在线性空间中执行图像处理运算,最后在输出到帧缓冲前再进行一次 γ 校正(重新编码)。
如果帧缓冲本身不支持自动 re-gamma 校正,那这条流程就会断裂。我们解了 γ、在线性空间做了运算,结果却没有被正确 re-gamma 编码,最后显示出来的图像就会非常偏差,通常是严重的灰白、偏淡,整体画面会被“洗掉”一样,看起来非常不自然。
因此,如果检测到没有 sRGB 帧缓冲的支持,那么最合理的策略就是全程保留在 gamma 空间,不进行 γ 解码和重新编码,这样反而能够获得更接近真实的视觉效果。避免只处理一边而另一边没有对应的转换,否则图像结果将完全错误。
综上,我们在设置纹理格式和帧缓冲时,必须同时检查系统的 sRGB 支持情况,只有在两个方向都支持的情况下,才开启这项功能,确保图像色彩正确。
game_opengl.cpp:使 OpenGLInit 处理 FramebufferSupportsSRGB
为了让渲染流程更符合规范并增强兼容性,特别是在某些 OpenGL 实现中不支持特定纹理格式的情况下,我们需要在初始化阶段做出更周全的判断和设置。
具体来说,我们应该检测帧缓冲是否支持 sRGB,并结合当前是否拥有相关的扩展功能(例如 GL_EXT_framebuffer_sRGB
等)。如果都支持,我们就启用 sRGB 功能。这里不仅仅是纹理格式的问题,还包括是否支持将最终颜色结果输出到 sRGB 帧缓冲中,这两者必须一起判断和处理。
核心逻辑是:只有当所有相关扩展和底层系统都支持 sRGB 渲染流程时,才启用该功能。
否则,如果我们仅在某一侧启用了 sRGB(例如只设置了纹理格式为 sRGB,但帧缓冲不支持),就会导致颜色空间处理不一致,图像最终结果会完全错误,严重时会出现明显的色彩偏差或图像偏灰、偏淡等问题。
换句话说,sRGB 功能的正确使用必须是“两端对齐”:
- 读取端(纹理)使用 sRGB 并进行正确的 γ 解码;
- 输出端(帧缓冲)也必须支持 sRGB 并自动进行 γ 编码。
一旦只开启其中一端,就会因为处理链不闭合导致颜色失真。
目前的初始化流程中,我们已经完成了诸如交换间隔(swap interval)等信息的查询和设置,因此这里的流程是直接通过的。但我们仍然需要再次确认,在设置 OpenGL 环境参数时,是否确实正确配置了所有与 sRGB 相关的条件。最终目标是在不破坏兼容性的前提下,尽可能精确地启用 sRGB 渲染路径,从而保证图像输出的准确性与一致性。
抵抗回去修正的冲动
我们已经解决了一个相关的 bug,并且修正了拼写错误,现在对扩展名称的检查已经更加准确,不再总是错误地匹配同一个名字。这个问题已经关闭并处理完毕。
同时,还有一个建议需要考虑:有反馈指出 GL_ARB_framebuffer_sRGB
扩展有时也会用于同样的目的,即判断是否支持 sRGB 帧缓冲输出。因此,我们可能也需要加入对这个扩展的检测。
这意味着除了当前已经检查的扩展之外,我们还应增加对 GL_ARB_framebuffer_sRGB
的判断逻辑。这样在不同的实现环境下,系统就可以更全面地识别是否具备 sRGB 输出能力,从而进一步增强兼容性和鲁棒性。
接下来的工作可以是实现这个新扩展的检测支持,并测试其对现有功能的影响,看看是否能够改善在某些设备或驱动上的表现。这个扩展的处理属于与之前 bug 修复相关的增强项,有助于未来进一步完善整体的渲染路径判断逻辑。
game_opengl.cpp:支持我们的扩展的 ARB 版本
目前的想法是,无论是哪种扩展版本,我们都有对应的实现,因此可以直接支持 ARB 版本的扩展。虽然具体的扩展名称一时不太记得了,但可以查一下确认。比如 GL_ARB_framebuffer_sRGB
是其中一个明确存在的扩展,这是我们知道可以用来判断帧缓冲是否支持 sRGB 输出的。
因此我们可以在现有基础上补充对 GL_ARB_framebuffer_sRGB
的支持。这是与之前已有的扩展相对应的 ARB 版本,意义是一样的,只是名称和发布方式略有不同。通过这种方式可以增强兼容性,适配不同 OpenGL 实现中对扩展命名的差异。
接着还尝试去查找对应的纹理端扩展,也就是类似于 texture_srgb_ARB
这样的命名,但目前似乎没有明确找到对应的纹理版本扩展。只查到了与帧缓冲相关的 ARB 扩展,而没有发现针对纹理 sRGB 的 ARB 扩展。
也考虑过直接查找当前使用的扩展名称,看它的支持状态是怎么判断的,但似乎没有明确的方式得知它是否被支持。可能的解释是,从 OpenGL 4.x 开始,该功能就变成默认的一部分了,而在 3.x 中则需要通过扩展显式启用。由于文档上没有给出清晰的指引,目前这一点还不是很确定。
总体来说,现阶段可以明确做的是加入对 GL_ARB_framebuffer_sRGB
的支持判断,其余纹理方面的扩展需要进一步查证是否存在独立的 ARB 扩展或是否默认集成在核心版本中。同时也要考虑不同 OpenGL 版本之间的差异,保证兼容老版本的实现。
调试器:进入 OpenGLGetInfo 并查看该 GPU 支持的扩展
目前查看了一些相关内容,确认了 framebuffer_sRGB
扩展的存在,并且系统中确实包含了对它的支持。此外也尝试进一步查找是否存在与之对应的 ARB 扩展形式,尤其是关于纹理方向的 sRGB 扩展,例如 ARB_texture_sRGB
之类的,但最终并没有发现明确的对应项。也就是说,在当前的环境和资料中,并没有发现 sRGB 纹理方面的 ARB 扩展,只有帧缓冲部分是明确存在的。
在尝试查找过程中,也意识到当前工具中缺乏临时缓冲区的方式来处理查找内容,但最终还是通过简单方式快速定位到了相关条目。
总结目前的结论是:
GL_ARB_framebuffer_sRGB
是有效且已知的扩展,应该纳入支持判断;- 并未发现 ARB 形式的 sRGB 纹理扩展,说明可能这一部分已经集成进 OpenGL 核心标准;
- 当前位置与处理流程已基本就绪,功能支持情况已明晰;
- 后续可能只需在支持判断中加入对
GL_ARB_framebuffer_sRGB
的检测即可,不需要额外处理纹理方向的 ARB 扩展。
至此,sRGB 支持逻辑基本清晰,后续可以进入下一阶段。准备进行一次简短的总结和问答环节,进一步理清相关细节或回应可能的问题。
WGL_EXT_depth_float WGL_ARB_buffer_region WGL_ARB_extensions_string WGL_ARB_make_current_read WGL_ARB_pixel_format WGL_ARB_pbuffer WGL_EXT_extensions_string WGL_EXT_swap_control WGL_ARB_multisample WGL_ARB_pixel_format_float WGL_ARB_framebuffer_sRGB WGL_ARB_create_context WGL_ARB_create_context_profile WGL_EXT_pixel_format_packed_float WGL_EXT_create_context_es_profile WGL_EXT_create_context_es2_profile WGL_NV_DX_interop WGL_NV_DX_interop2 WGL_ARB_robustness_application_isolation WGL_ARB_robustness_share_group_isolation WGL_ARB_create_context_robustness WGL_ARB_context_flush_control
问答环节
软件渲染性能怎么回事?几个月前运行在 30 FPS,甚至没有 -O2 时也相当不错
曾经在没有启用 O2(优化模块或特定加速路径)的情况下,软件渲染器在“森林”这一关卡中也能跑到约 30 帧,表现还算可以,但对这一点是否准确存在疑问。
实际上,在处理那些复杂堆叠的过场动画时,渲染器从来无法流畅运行,尤其在没有 Od- 的情况下,性能表现始终不理想。而 Od- 模块在软件渲染器中一直是默认开启的,这是因为我们手工优化了很多关键路径,确保 Od- 始终处于启用状态,特别是在动画或图像堆叠密集的场景中,这些优化能显著提升性能。
因此回顾来看,那些之前看似“无优化”下的流畅运行,其实背后早就依赖了默认开启的 Od- 优化路径。如果没有这部分优化,渲染性能在复杂场景中是无法达到接受水平的。
build.bat:切换到 -O2 并观察性能分析器
我们在评估软件渲染器性能时,不能把不同条件下的结果简单对比。比如关闭优化和开启优化的性能表现自然是不同的,如果我们重新开启优化,性能就会恢复到一个比较合理的状态,这也是刚刚实验中的实际效果。
不过即便如此,在某些特别复杂的过场动画场景中,渲染器始终存在性能瓶颈。尤其是像“堆叠极深”的动画镜头,它们存在大量的图层叠加和遮挡,这就导致了严重的过度绘制(overdraw)问题。渲染器需要重复绘制许多看不到的像素,处理起来相当耗时。如果要针对这些场景进一步优化,就必须削减过度绘制的开销。
在图层较少的场景中,渲染器表现自然会更好。但在高图层密度的复杂场景里,就会出现明显的帧率下降。此外,调试工具自身的开销也会占用大量处理时间,这一点在分析时容易被忽略。
实际上,我们通过调试工具可以很清楚地观察性能瓶颈所在:软件渲染器部分已经完成渲染,性能开销主要来自于 OpenGL 的纹理上传(texture download)过程。也就是说,帧率下降并不是渲染本身的问题,而是把渲染结果传输到 GPU 的开销导致了瓶颈。
这个观察再次强调了调试工具的重要性,它能快速准确地指出性能瓶颈所在。如果没有这些工具,我们根本无法察觉到问题的根源,误以为是渲染本身造成的低性能。
因此,当前性能问题的本质是纹理上传导致的瓶颈,而不是软件渲染器计算阶段本身。这一发现为后续优化提供了明确方向。
win32_game.cpp:切换到 Win32RenderType_RenderSoftware_DisplayGDI 并再次观察性能分析器
为了在渲染类型之间切换,我们增加了一个三路开关,允许在不同的渲染模式下进行选择。具体来说,如果我们设置启动时通过 GDI 渲染,那么就可以看到不同的性能表现。
经过切换后,性能得到了显著提升,看起来要比之前快了至少两倍。这个改善的部分原因也与我们之前在流媒体处理上做的一些特殊处理有关,这些处理方式导致了 OpenGL 渲染上的一些问题,因此不能归咎于 OpenGL 本身,而是流媒体处理的方式导致了性能瓶颈。
目前,运行在 60Hz 的显示器上,整体效果相当不错。不过,当场景的图层数增加到大约 4 层时,帧率就稍微达不到 30 帧,这可能是由于多层渲染时计算负担加重导致的。虽然帧率稍微低于 30,但基本保持在一个稳定的水平。
另外,还存在一个与调试相关的问题——调试的合并功能(debug collation)可能是影响性能的因素之一。如果关闭调试合并功能,可能会进一步提高帧率,但这样我们就无法继续进行性能分析和剖析。这个问题需要在性能与调试之间做出权衡。
修复几个段错误的问题
还有段错误吗
++位置不对
还是段错误
挨着对OldestFrame 赋值的地方打断点
最开始初始化为0
第二次初始化
第三次初始化 内存不够开始释放老的frame
空间不够一直取释放
不知道什么原因直接256 兆吧
内存溢出段错误不知道什么原因
所以让我理解一下,现代的 PCI-E 连接有几个 GB/s 的带宽,为什么上传一个纹理到后缓冲区却需要几帧?
现代的高速连接虽然具备每秒数 GB 的带宽,但上传一个纹理到后备缓冲区(back buffer)却依然需要多个帧的时间,这背后原因远比表面看起来复杂。
纹理传输过程涉及大量流程和底层机制,不仅仅是简单的带宽问题。在将纹理传输至显卡的过程中,必须完成格式转换(swizzling)、内存映射(mapping to the aperture)、以及在 GPU 使用前的各种管线处理。当前我们使用的上传方式效率并不高。
为了提升 OpenGL 的图像吞吐能力,理想的做法是使用像素缓冲对象(Pixel Buffer Object, PBO)来进行纹理传输。具体做法是设置两个 PBO 并交替使用(ping-pong),以此消除一次不必要的内存拷贝操作,从而提高效率。然而目前的 OpenGL 设置并不支持这一优化流程,而是刻意处于一种特殊模式,以兼容现有的流媒体系统。这种模式并非为性能最优设计,而是为了确保流式处理顺利进行。
此外,如果开启双缓冲(double buffer)标志,也可能显著提升性能,这部分尚未启用,因此仍有优化空间。
另一个小问题是,目前软件渲染器并没有执行清屏(clear)操作。虽然对于当前的渲染逻辑来说这并不是必须的,因为后续会覆盖整张画面,但这一点还是值得记录,可能未来某些情况下会有影响。
总的来说,目前系统在纹理传输方面存在明显的优化空间,后续如能合理利用 PBO 并开启双缓冲机制,可望大幅提升性能表现。