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

网站设计郑州如何注册个人工作室

网站设计郑州,如何注册个人工作室,商城网站免费模板,东莞网站建设怎么收费仓库:https://gitee.com/mrxiao_com/2d_game_3 运行游戏并识别我们的小问题 今天的工作重点是对游戏引擎进行架构优化#xff0c;特别是针对渲染和多线程的部分。目前#xff0c;我们的目标是让地面块在独立线程上进行渲染#xff0c;以提高性能。在此过程中#xff0c;我…仓库:https://gitee.com/mrxiao_com/2d_game_3 运行游戏并识别我们的小问题 今天的工作重点是对游戏引擎进行架构优化特别是针对渲染和多线程的部分。目前我们的目标是让地面块在独立线程上进行渲染以提高性能。在此过程中我们注意到虽然渲染速度已经变得非常快但在生成新的地面块时仍然会出现轻微的帧率波动。 这个问题虽然不算严重但为了进一步提升性能我们决定改进地面块生成的方式。目前我们生成地面块的方法过于简单涉及过多的图形工作这显然可以通过更高效的方式来实现。为了更好地处理这一问题我们考虑将地面块的生成和渲染过程移到后台线程中这样就可以在后台完成这些任务不影响主线程的渲染工作。 这样做不仅有助于解决当前的性能瓶颈而且为未来更复杂的游戏任务和引擎开发打下基础。通过实现多线程处理我们希望能够在游戏开发中实现更高效的资源加载和渲染避免由于主线程负载过重而导致的卡顿或延迟。 我们目前正在做一些基础的架构工作确保在将来能够顺利地进行游戏开发。这个过程虽然看似简单但实际上涉及到多个模块的协作包括渲染系统和后台任务调度。通过合理的线程管理和任务调度可以显著提高游戏的流畅度和响应速度。 总的来说今天的工作不仅是为了优化当前的性能问题还在为整个引擎架构的进一步完善做准备确保我们在开发过程中能够应对更多的挑战和需求。 为什么我们不能在单独的线程上生成地面块 目前的目标是将地面块渲染工作移到单独的线程上运行但在此之前我们需要进行一些清理工作因为目前的代码存在一些未处理的问题幸运的是代码在没有处理这些问题的情况下居然能够正常运行。 我们发现问题的根源在于内存对齐。当代码编译时如果路径没有正确配置系统会触发断言错误因为内存没有对齐导致渲染代码无法正常工作。事实上代码之所以能运行是因为内存恰好对齐了。我们之前确保了帧缓冲区是对齐的因此没有问题但当我们尝试渲染那些地面块时内存并没有正确对齐。 为了避免这个问题我们需要做的是确保内存对齐。如果没有正确的对齐模式我们会触发断言错误甚至崩溃。因此我们需要进行一些处理确保内存在渲染过程中得到正确的对齐。 如果我们取消了对齐检查虽然代码可能能够继续执行但在后续操作中肯定会崩溃因为没有对齐的加载操作会导致错误。例如如果内存加载指令没有对齐系统就会崩溃。同样的情况也会出现在存储操作中。为了避免这些问题我们必须确保所有加载和存储操作都使用对齐的指令而这也是我们选择使用对齐加载和存储的原因。 总的来说当前的工作是通过确保内存对齐来避免潜在的崩溃和错误确保渲染过程能够顺利进行。这为将来实现多线程渲染和其他优化奠定了基础。 game.h: 允许 PushSize_ 接受对齐方式 首先需要解决的问题是如何确保地面块的内存对齐。当前在创建这些地面块时我们已经有了一个暂时的内存区域transient_state但是我们并没有办法确保这些内存区域是对齐的。 为了确保对齐我们需要在内存分配过程中指定对齐要求。以 make_empty_bitmap 为例当我们创建地面块时可以在这里确保内存对齐。具体来说我们可以修改内存分配函数比如 PushSize_ 函数添加一个新的参数来指定内存的对齐方式。这个参数就是对齐要求表示希望分配的内存需要按照什么样的对齐方式进行对齐。 为了实现这一点我们需要确保对齐要求是2的幂次方例如常见的对齐方式是4字节对齐。我们可以设定一个默认值比如 4 字节对齐这样大多数情况下都能满足需求。如果需要其他的对齐方式则需要显式地指定对齐要求。 通过这种方法能够在内存分配时确保内存区域按需要的对齐方式进行分配从而避免因为内存不对齐导致的崩溃和性能问题。这是解决内存对齐问题的一种方案为地面块的正确渲染和多线程处理提供了基础。 引入 AlignmentMask 为了确保内存对齐首先需要检查当前内存地址是否符合所需的对齐要求。具体来说当分配内存时可能需要对内存地址进行调整确保它满足对齐的条件。对齐是指内存地址能够按特定的字节数如4字节、8字节等对齐以提高效率和避免崩溃。 已经有一些经验可以借鉴尤其是在之前处理图形裁剪时就涉及到了类似的对齐检查。在图形渲染中曾经使用过类似的对齐代码比如检查一个地址是否是4字节对齐。如果地址不是4字节对齐就需要进行调整通常会将地址向上调整到最近的4字节对齐边界因为我们不能将地址向下调整那样可能会覆盖已分配的内存。所以只能向上调整到下一个对齐边界。 具体的实现方式包括使用一个对齐掩码alignment mask。这个掩码用来检查内存地址是否符合对齐要求。如果内存地址与掩码按位与操作后不为0表示当前地址不对齐需要进行调整。 调整的过程大概是这样的 计算当前内存地址与对齐要求的掩码的“与”操作得到不对齐部分。如果不对齐就需要将内存地址向上调整确保它对齐到下一个对齐边界。 这种方式帮助确保内存始终按照正确的边界对齐避免了因内存不对齐而引发的性能问题或崩溃问题。 Blackboard: 将 2 的幂转化为掩码 为了确保内存地址的对齐可以使用位运算来判断一个值是否符合特定的对齐要求特别是当对齐要求是2的幂时。这里以4字节对齐为例详细过程如下 理解二进制与对齐要求 每个数字在二进制中由多个位组成比如8位、16位、32位、64位等。对于一个4字节对齐的需求我们只关心最低的两位即二进制的最后两位因为4字节对齐意味着这些最低的两位应该是0。比如如果一个数字是4二进制表示为0000 0100其中只有第四位是1其他位是0。 使用掩码来判断对齐 为了检查一个值是否满足4字节对齐可以使用掩码。这意味着我们需要检查该值的最低两位是否是0。如果是0则表示这个地址已经是4字节对齐的。 通过减去1来生成掩码 一种简便的方式是通过将对齐要求的值减去1来生成掩码。例如如果要求4字节对齐4减去1就得到3二进制表示为0000 0011这个掩码可以用来检查是否对齐。具体方法是减去1后较低的位会发生变化形成一个掩码。如果原值是4即二进制0000 0100减去1后得到3二进制0000 0011。通过与原值进行位运算检查是否满足对齐要求。 检查对齐 为了判断一个值是否对齐可以使用“与”运算。将原值与掩码进行“与”运算如果结果是0说明该值满足对齐要求如果结果不为0则说明该值没有对齐。 总结 通过这种方法我们可以非常高效地检查内存地址是否满足特定的对齐要求尤其是在对齐要求是2的幂时。只需要减去1生成掩码然后进行位运算即可判断对齐情况。 这种技巧在内存管理和优化中非常有用因为对齐不当会导致性能下降或者在某些平台上引发崩溃。 根据 AlignmentMask 设置 AlignmentOffset 为了确保内存对齐特别是在内存池中处理内存分配时我们需要进行一些对齐操作。这些操作的目的是确保返回的指针是按照指定的对齐要求进行调整的。以下是整个过程的详细说明 初始化内存和对齐偏移量 设定了一个内存池称为arena并使用一个基地址指针arena base来表示它的起始位置。此外arena used指示了已经使用的字节数。对齐要求是通过alignment mask来表示它包含了不能被设置的位。通过与内存地址进行位运算我们可以检查是否已经满足对齐要求。 检查对齐情况 在分配内存之前我们需要检查当前的基地址是否已经符合对齐要求。如果地址不对齐接下来就需要进行调整。如果地址不对齐我们需要计算出实际需要的对齐偏移量。比如如果当前地址已经偏离对齐要求那么就需要通过增加一个合适的偏移量来确保地址对齐。 调整对齐 计算对齐的方式是首先判断当前地址和对齐要求的关系。如果当前地址的低位没有符合对齐要求例如要求对齐到4字节但当前地址不是4的倍数就需要调整地址。通过计算出当前地址与对齐要求的差异决定需要加多少字节来满足对齐要求。通常通过减去当前地址的低位部分得到所需的偏移量。 调整内存块大小 除了调整地址还需要调整内存块的大小。由于内存的对齐所需的内存大小会被“扩展”一定的字节数。这样做是为了确保分配的内存块能够满足对齐要求。内存块大小需要根据对齐要求来进行增加这样内存的使用就能够按要求对齐从而避免访问时出现错误。 检查内存是否溢出 在调整内存大小后必须确认内存池中是否还有足够的空间来容纳新的内存块。因为在当前的实现中并不会动态分配新的内存而是直接使用现有的内存池。如果内存池已满分配会失败导致错误。 最终指针计算 最终计算出对齐后的内存块的指针。这个指针是基地址加上调整后的对齐偏移量。将得到的指针转换为void*类型确保它可以被正确使用。 通过这些步骤我们确保了分配的内存块是按照要求对齐的从而避免了因为内存不对齐而导致的崩溃或性能问题。 声明地方添加默认值好像定义的地方不能添加默认值 再次讨论 PushSize_ 在处理内存对齐时目标是确保内存地址能够符合指定的对齐要求以便更高效地使用内存并避免潜在的错误。为了完成这一目标以下是具体的步骤和过程 检查是否需要对齐 首先需要确定当前内存指针是否符合对齐要求。如果当前指针不符合要求则需要计算出调整的偏移量使其对齐到指定的字节边界。计算的方法是检查当前地址和对齐要求的关系判断是否存在“残余”部分。如果存在那么就需要增加相应的字节数直到指针对齐。 调整内存大小 在对齐时除了调整内存指针还需要增加内存块的大小。增加的大小是为了确保对齐后的地址能够完全满足要求这个增量是基于对齐要求来决定的。例如如果地址的低位部分不符合对齐要求那么就需要增加相应的字节数使得指针对齐。 内存池检查 调整内存大小后需要进行断言确保调整后的内存块能够在内存池中成功分配。如果调整后的内存大小超出了当前内存池的可用空间那么分配将失败并会触发错误。 更新内存池的已用空间 一旦内存分配成功需要更新内存池中已用空间的大小。这个值需要包含调整后的内存块大小以确保后续的分配能够正常进行。 返回对齐后的指针 最终返回调整后的内存指针这个指针已经根据指定的对齐要求进行了调整确保后续的内存访问不会出现错误。 通过这些步骤内存分配过程中的对齐问题得以解决同时保证了内存的高效使用。虽然目前的实现可能并非最为高效但它能够确保在当前的需求下正确地执行对齐操作。如果将来需要更高效的实现可以进一步优化这部分代码。 调试器: 步进执行 PushSize_ 在处理内存对齐时我们通过以下步骤确保内存地址正确对齐 初始化内存区域和指针 我们从内存池的基础指针base pointer开始并检查当前的内存区域的使用情况。在这个步骤中内存池的大小尚未被使用因此可以看到空闲区域的大小。 计算对齐掩码 接下来我们根据所需的对齐要求构建一个对齐掩码。这个掩码是用来判断当前内存是否已经符合对齐要求。对于四字节对齐掩码通常会检查低两位是否为零。如果这些低位不为零说明当前内存指针未对齐。 检查是否需要调整对齐 如果内存指针已经符合要求即低位为零则不需要做任何修改。如果当前的内存指针已经对齐到四字节边界则不需要增加内存的大小继续进行正常的内存分配。 调整内存大小 如果发现内存指针没有对齐我们会计算出需要增加的字节数以便将内存指针对齐到所需的边界。调整后的内存块大小会相应地增加。 最终内存指针 最终我们将返回调整后的内存指针result pointer它会指向正确的对齐地址。此时该内存地址符合所需的对齐要求且内存区域的大小已经调整好。 通过这些步骤我们确保了内存的对齐并且避免了因对齐问题导致的潜在错误。如果内存已经对齐那么无需做额外的调整分配过程可以顺利进行。 __VA__ARGS 在处理内存对齐的问题时首先需要确保能够指定内存对齐方式以便更好地处理不同类型的数据对齐需求。具体步骤如下 当前对齐方式 目前我们将所有内存默认对齐到4字节边界这对于大多数情况下是足够的。然而在处理某些类型的地面瓦片时需要将其对齐到16字节边界因此需要进一步调整。 调整MakeEmptyBitmap函数 在MakeEmptyBitmap函数中需要修改内存分配的方式以确保地面瓦片的内存是按照16字节边界对齐的。为此计划在调用precise函数时允许指定一个对齐参数而不仅仅是默认的4字节对齐。 使用宏和变参机制 通过修改宏定义允许在调用时传递额外的对齐参数。具体来说可以在宏的末尾添加指定对齐的参数这样可以灵活地控制对齐方式。这种方法需要特别处理一些编译器的差异例如MSVC和GCC之间的差异。 处理平台差异 不同的编译器和平台可能对宏和变参的处理方式不同。某些编译器可能需要使用特殊的语法来处理宏参数因此在实现时要特别注意这一点确保在不同的平台上都能正常工作。为了兼容各种平台使用了一种被广泛认可的语法虽然在一些平台上可能需要调整但当前这种实现方式应该可以在大多数情况下工作。 后续验证 在完成这些调整后需要依赖不同平台上的编译者来验证是否能够正常工作。特别是在编译器处理变参宏时可能会出现一些特殊情况因此需要广泛测试来确保代码的跨平台兼容性。 通过这种方式可以在内存分配时灵活控制对齐方式以确保不同类型的数据都能正确对齐从而避免由于对齐错误带来的潜在问题。 在 C 中##__VA_ARGS__ 是一种与可变参数宏variadic macro相关的语法通常用于处理宏定义中的参数拼接。它的核心作用是将可变参数__VA_ARGS__与前面的标记token通过 ## 运算符进行拼接从而生成合法的代码。要理解它的用法我们需要逐步拆解它的组成和场景。 背景知识 可变参数宏 C从 C99 开始引入C11 正式支持允许定义带有可变参数的宏使用 ... 表示可变参数__VA_ARGS__ 表示这些参数的实际内容。示例#define LOG(...) printf(__VA_ARGS__)调用 LOG(Hello, %d\n, 42) 会展开为 printf(Hello, %d\n, 42)。 ## 运算符 ## 是宏定义中的“token-pasting”标记拼接运算符用于将两个标记连接成一个新的标记。示例#define CONCAT(a, b) a ## b调用 CONCAT(foo, bar) 会展开为 foobar。 ##__VA_ARGS__ 当 ## 与 __VA_ARGS__ 结合时它将前一个标记与可变参数的内容拼接起来形成一个新的标记。 ##__VA_ARGS__ 的用法 ##__VA_ARGS__ 通常用于需要动态拼接可变参数的场景尤其是在模板元编程、调试工具或生成复杂代码时。它的关键点在于 __VA_ARGS__ 代表所有传入的可变参数可以是多个用逗号分隔。## 会尝试将前面的标记与 __VA_ARGS__ 的内容连接起来。 基本语法 #define MACRO(pre, ...) pre ## __VA_ARGS__pre 是前缀标记。__VA_ARGS__ 是可变参数。## 将 pre 与 __VA_ARGS__ 的内容拼接。 示例 1简单拼接 #include iostream#define GLUE(x, ...) x ## __VA_ARGS__int main() {int GLUE(var, 123) 42; // 展开为 int var123 42;std::cout var123 std::endl; // 输出 42return 0; }调用 GLUE(var, 123) 时x 是 var__VA_ARGS__ 是 123## 将它们拼接为 var123。结果是一个变量声明 int var123 42;。 注意事项 __VA_ARGS__ 不能为空早期实现 在 C99 和早期 C 实现中如果 __VA_ARGS__ 为空即没有参数传入##__VA_ARGS__ 会导致语法错误。示例#define GLUE(x, ...) x ## __VA_ARGS__ GLUE(foo, ) // 错误__VA_ARGS__ 为空解决方法现代编译器C11 及以上支持空 __VA_ARGS__但需要小心处理。 C11 改进空参数支持 C11 允许 __VA_ARGS__ 为空## 前面的逗号会被忽略。例如#define LOG(fmt, ...) printf(fmt, ##__VA_ARGS__) LOG(Hello\n); // 展开为 printf(Hello\n) LOG(Value: %d\n, 42); // 展开为 printf(Value: %d\n, 42)这里 ## 确保即使没有额外参数也能正确展开。 多参数时的行为 如果 __VA_ARGS__ 包含多个参数用逗号分隔## 会将前缀与整个 __VA_ARGS__ 的内容拼接这可能导致意外结果。示例#define GLUE(x, ...) x ## __VA_ARGS__ GLUE(var, 1, 2, 3) // 错误var1, 2, 3 不是合法标记这种情况下## 只能拼接单个标记多参数需要其他方式处理。 实用场景 示例 2调试宏 #include stdio.h#define TRACE(name, ...) printf(Tracing #name with args: #__VA_ARGS__ \n) #define TRACE_VAR(name, ...) name ## __VA_ARGS__int main() {int TRACE_VAR(x, 1) 10; // 展开为 int x1 10;TRACE(x1, 1, 2, 3); // 输出 Tracing x1 with args: 1, 2, 3printf(x1 %d\n, x1); // 输出 x1 10return 0; }TRACE_VAR 用 ##__VA_ARGS__ 生成变量名。TRACE 用普通 __VA_ARGS__ 输出参数。 示例 3生成函数名 #include iostream#define FUNC(name, ...) name ## __VA_ARGS__void FUNC(print, _int)(int x) {std::cout x std::endl; }int main() {FUNC(print, _int)(42); // 调用 print_int(42)return 0; }FUNC(print, _int) 展开为 print_int定义并调用一个函数 print_int。 高级用法结合模板 在模板元编程中##__VA_ARGS__ 可以用来生成复杂的标识符。例如 #include iostream#define MAKE_TYPE(T, ...) T ## __VA_ARGS__templatetypename T struct Wrapper {T value; };int main() {MAKE_TYPE(Wrap, per)int w{42}; // 展开为 Wrapperint w{42};std::cout w.value std::endl; // 输出 42return 0; }MAKE_TYPE(Wrap, per) 拼接为 Wrapper生成模板类型 Wrapperint。 常见问题与解决 问题多参数拼接失败 如果 __VA_ARGS__ 有多个参数直接用 ## 会出错。解决使用辅助宏逐个处理参数或者避免拼接多参数。#define FIRST_ARG(x, ...) x #define GLUE(x, ...) FIRST_ARG(__VA_ARGS__) ## x GLUE(foo, bar, baz) // 展开为 barfoo问题空参数处理 如果需要支持空 __VA_ARGS__可以用条件宏#define GLUE(x, ...) x BOOST_PP_IF(BOOST_PP_IS_EMPTY(__VA_ARGS__), , ##__VA_ARGS__)需要 Boost.Preprocessor 库支持 总结 ##__VA_ARGS__ 的作用将前缀与可变参数拼接为单一标记。用法常用于生成变量名、函数名或其他标识符。限制适用于单一标记拼接多参数或空参数需额外处理。场景调试、信息生成、模板元编程等。 如果你有具体的需求或代码示例可以告诉我我帮你进一步优化或解释 game.cpp: 传递 16 给 PushSize 调用并运行游戏 现在我们已经成功地把地面覆盖物ground covers对齐到16字节并且解决了之前崩溃的问题。经过调试后代码看起来运行正常虽然渲染这些地面覆盖物需要较长时间但这其实是一个有利的情况。因为这为我们接下来的多线程处理提供了机会。通过利用渲染时间较长的特点我们可以尝试将这些任务并行化来提高性能优化渲染过程。 具体来说尽管当前的代码运行不算非常高效但考虑到这个过程不是非常频繁地被调用暂时保持代码简单明了是一个不错的选择。我们不需要过多的优化反而能够通过这种简洁的方式清晰地看到每一个步骤的执行情况。 总的来说虽然我们可以进一步优化使得处理过程更加高效但目前的实现已经解决了问题并且能顺利进行调试和渲染。这也为后续的多线程改进打下了基础。因此现在的目标是尽量利用这些时间差通过并行处理来加速工作流程。 这种方式虽然简单但已经足够满足当前的需求。 考虑将所有 FillGroundChunk 放入队列 接下来需要解决的问题是我们在创建暂时状态transient state时并没有为用户提供临时缓冲区的概念。这个问题如果不解决会很快暴露出潜在的风险。举个例子假设我们要处理某个名为FillGroundChunk的内容我们希望能够调用一个函数在队列上执行工作。具体来说我们希望这个函数能处理整个流程将整个过程作为队列上的任务来执行。 为了实现这一目标实际上我们需要将这段处理逻辑放入队列中完成之后再处理。如果没有适当的临时缓冲区支持任务的队列可能会因为缺乏合适的内存分配而变得无法正常工作。因此考虑到这一点我们需要设计一个合适的机制来支持这个队列任务并且保证在执行过程中每个临时缓冲区的生命周期不会影响系统的其他部分。 现在暂时我打算将这段处理代码拆分为多个部分进行处理因为不确定我们是否需要将整个操作都放入队列。我想在一开始对这部分做一些分隔看看在队列中执行时是否有问题。主要考虑的是实际上将任务分配到队列并不会有太大问题因为每个队列中的任务会在系统空闲时被逐一执行不会造成资源冲突或阻塞。 但这也提出了一个新的问题如何保证任务分配的正确性以及每个临时缓冲区的生命周期与其他内存区域不冲突。这需要进一步的设计和实现以确保临时缓冲区能够与其他部分的内存管理系统无缝协作。 关闭 TiledRenderGroupToOutput 如果我们只是将所有的 pushback 映射排入队列然后根本不去渲染它们不做任何处理理论上我们就不会遇到任何暂停现象。这是我的假设。为了确认这一点我可以先将当前的状态恢复一下这样可以更确保我们不会遇到任何暂停或类似的问题。 game_render_group.cpp: 切换到玩家的相机并四处走动 我们恢复了渲染实体的基础部分并继续处理。经过测试后感觉没有出现什么问题编译也顺利进行。在运行时我们没有遇到任何长时间的停顿或卡顿所以一切都挺好的感觉目前的情况已经可以接受。 离开时我们的一部分操作是同步的 这意味着我们可以将部分操作保持异步这对于某些情况来说可能是有益的。例如在处理模拟时我们可能不希望锁定某些区域或者其他可能会干扰的操作。当前的做法是可以完全异步地进行的。但如果未来涉及到实体位置、绘制等操作这可能会导致锁定我们不想锁定的资源或者出现其他不可预见的问题。 因此我们希望能够让操作在同步模式下运行完成需要生成的内容并进行生成。具体来说就是将这一部分操作推送到单独的线程中执行这样就能避免阻塞主线程。 然而问题在于我们当前为渲染组创建了临时内存来存储命令并在渲染输出后立即结束了这个临时内存块。如果这些操作是重叠执行的且持续一段时间那就不能在任务运行时释放内存因为内存一旦被释放仍在运行的任务就会读取被覆盖的数据导致崩溃或读取垃圾数据。因此需要小心处理内存的释放时机。 提供临时内存“临时空间”以便后台任务在不被覆盖的情况下工作 为了解决这个问题我们需要一个临时存储区域可以将数据存放在这里以便后台任务能够进行处理而不会因为内存被覆盖而出错。为了实现这一点可能并不会非常复杂我们可以在现有的内存结构中进行一些调整。 在暂态状态transient_state中我们已经有了内存区域memory arena。接下来我打算引入子区域sub-arenas的概念也就是在一个大内存区域内再划分出多个小的内存区域。这样多个需要并行工作的任务可以在各自的内存块中进行操作而不会相互干扰。每个任务都会得到一个独立的内存区域且我知道它们不会产生冲突。 通过这种方式不仅可以避免使用锁locking减少同步上的潜在问题还能避免性能损失。只要事先估计好每个任务所需要的内存空间就可以将大内存区域预先划分成若干块任务各自占用一块且无论多少任务并发进行都不会影响其他任务的运行。这种方法不仅能够提高效率还能够避免因同步问题带来的潜在 bug。 game.h: 引入 GroundChunkArenas 想要引入的概念是使用多个内存区域memory arena这些内存区域主要用于地面块的填充ground chunk filling。这些内存区域可以称作“地面块内存区域”GroundChunkArenas。目标是为地面块分配一些内存区域并且能够循环使用这些区域。 具体来说希望通过引入一个“滚动计数器”来管理这些内存区域的使用情况。这个计数器将允许按顺序使用这些内存区域确保当一个内存区域的任务完成后能够标记它并重新使用其他区域。这种方式可以确保不同的内存区域不会发生冲突同时又能高效地分配内存给不同的任务。 目前对于如何实现这一点想法还不完全明确。例如可能会考虑让每个内存区域从它的开始位置开始使用或者采用其他方式来管理这些区域的分配和回收。不过已经决定将继续推进这个方向。 引入 task_with_memory 想要做的事情是引入一种新的概念称为“task_with_memory”task with memory。每个任务会有一个专用的内存区域arena用于执行任务时的内存分配。此外每个任务还会有一个“使用状态”being used用于表示该任务是否正在使用内存。 具体来说计划定义一个结构体来表示这个“task_with_memory”。这个结构体包含一个内存区域并有一个标识当前任务是否正在使用的字段。通过这种方式能够管理多个任务的内存使用情况。 想要实现一个任务池其中有几个“task_with_memory”实例。每当有任务需要执行时能够从池中选取一个空闲的任务并分配相应的内存区域。如果某个任务完成并且内存区域不再使用就可以将其标记为可用从而重新加入到池中以便后续使用。 这种方式使得可以灵活地管理内存资源并且可以根据需要创建更多的任务实例以适应不同的任务需求。 game.cpp: 创建一堆任务每个任务都创建一个 SubArena 在此过程中首先创建了一个新的任务结构task_with_memory。每个任务都需要关联一个内存区域并且设置一个标志来表示该任务是否正在使用内存。 具体做法是首先进行初始化工作。之后计划为每个任务分配一个内存区域并创建一个task with memory类型的结构。每个任务都包含一个标志being used来表示该任务是否正在使用内存区域同时每个任务将关联一个内存区域arena。 然后任务会使用一块内存区域并且可以根据需要在内存区域内分配一个子区域。比如每个任务可以从一个更大的内存池中划分出一定大小的内存区域假设任务需要1MB的栈空间那么就可以从这个内存池中切割出1MB的区域来供任务使用。这种子区域的分配通过对原本的内存区域进行子分配来实现。 这种方法的目的是在不锁定整个内存区域的情况下让多个任务能够并行使用各自的内存区域而不相互冲突同时也能有效地利用内存。 查看 FillGroundChunk 是如何工作的 当调用填充地面块FillGroundChunk时当前的这个例程实际上是一个占位符并不包含具体的功能实现。因此在这一阶段并不会对任务如何启动进行深入探讨。后续可以考虑如何更好地启动这些任务。 一旦调用了FillGroundChunk我们要做的第一件事是获取一个可用的任务task。获取到任务后我们希望将临时内存的开始和相关操作绑定到任务的执行过程中。具体来说我们希望这个临时内存的管理和任务的完成状态相互关联即任务完成时也会结束相应的内存操作。这样可以确保任务在执行过程中能够正确地分配和释放内存从而避免潜在的内存泄漏或未正确回收的问题。 game.h: 在 task_with_memory 中隐式表示内存是临时的 目标是使任务的内存管理变得更加隐式且自动化。具体来说每当任务开始时所有分配的内存都将被视为临时内存因为当任务结束时这些内存将会被自动清除或刷新。 在任务结构中临时内存将被包含为任务的一部分并且这个内存的状态会被标记为“已刷新”或类似的标识。这样通过任务的生命周期内存会被自动管理不需要额外的手动操作确保任务结束时相关的临时内存被正确释放。 game.cpp: 实现 BeginTaskWithMemory 和 EndTaskWithMemory 目标是通过任务管理来处理内存的分配和释放。在任务开始时会通过 begin task 来启动任务并初始化相关的临时内存。任务结束时内存将会自动刷新和释放。 具体来说begin task 将会在合适的地方调用可能是在程序的某个模块里而临时内存的初始化即 begin temporary memory也将被移到任务开始时处理。这样内存的分配和释放操作都与任务生命周期紧密绑定确保内存管理的简洁和高效。 在处理过程中执行实际渲染工作的部分会依赖于这些已管理好的内存区域在执行渲染时调用任务内存结构来处理临时内存确保内存不被错误覆盖或泄漏。 如果 BeginTaskWithMemory 为 true则执行 FillGroundChunk 在填充地面数据时首先使用 BeginTaskWithMemory 方法来检查是否可以开始一个新任务。通过使用条件判断如果返回 true才会执行实际的地面填充工作。这种做法的目的是控制任务的数量防止启动过多任务导致系统过载。这样任务的启动受到限制只在资源允许的情况下才会分配新的任务。 这种方法的好处是可以避免任务队列过度膨胀从而导致性能问题。通过这种方式可以更精细地控制后台任务的处理确保系统在负载过重时不会出现问题。 总体来说这种处理方式能帮助更好地管理任务队列避免无控制地增加任务负担保持任务的数量和执行的有效性。 编写 BeginTaskWithMemory 在这里系统将会遍历任务池中的任务检查是否有可用的任务。如果某个任务没有被使用则表示该任务可以被分配并使用。一旦找到可用任务就会将其标记为当前任务并为其分配内存。 具体操作包括 遍历任务列表检查每个任务的使用状态。如果任务未被使用则选择该任务并将其设为当前任务。为当前任务分配临时内存空间并开始使用该任务的内存。完成任务处理后刷新任务的内存释放资源。 如果没有找到可用任务函数会返回零表示没有可用的任务可供分配。这样就确保了任务的合理调度并在任务完成后清理内存以防止内存泄漏。 最终通过这种方式任务的分配和内存的管理能够高效地进行并确保在没有足够任务时不会浪费资源。 将 TiledRenderGroupToOutput 放到后台线程 在进行“TiledRenderGroupToOutput”操作时目标是将这个过程移到后台线程中执行。为了支持多线程渲染系统将提供一个选项来决定是否启用多线程。如果选择启用多线程渲染操作将被分配到多个线程上执行从而提高效率如果不启用多线程渲染仍然可以正常进行。 为了实现这一点考虑到是否需要多线程渲染将会有一个标志位来决定是否开启多线程。这样渲染过程可以根据需要选择不同的执行方式既支持多线程也支持单线程操作。 同时为了简化实现过程可能会创建一个单独的例程来处理这些不同的渲染方式。通过这种方式系统能够灵活地在多线程和单线程模式下运行渲染任务确保根据实际需要调整渲染策略。 game_render_group.cpp: 引入 RenderGroupToOutput 作为非瓦片版本 在实现“RenderGroupToOutput”的过程中如果不启用tile渲染即单线程渲染系统将依旧执行相同的操作但是不会进行分块渲染。具体来说系统将跳过与tile相关的操作而是直接在整个区域上进行渲染。 为了实现这一点首先需要确保在渲染前对内存区域进行验证例如验证上层内存的正确性和尺寸是否合适。此外还需要去掉与tile渲染相关的部分保留一些必要的代码来确保渲染区域正确。例如设置一个剪辑矩形ClipRect这个剪辑矩形覆盖整个渲染区域也就是整个地面缓冲区。 最终在此路径中系统将按照单线程渲染的方式工作假装进行tile渲染的操作但实际上并不会进行tile分块而是直接对整个区域进行渲染。 game.cpp: 调用 RenderGroupToOutput 在实现渲染时系统可以选择不使用渲染队列并且渲染队列可以被传入为零这样就不再需要使用渲染队列。通过这样的设置代码变得更加简洁且明确能够确保不依赖于渲染队列来执行渲染操作。 此外需要为这个任务添加适当的回调机制确保任务能够在完成时进行必要的操作包括内存刷新、标记任务完成以及设置相关标志如将任务的“正在使用”标记设置为“false”。为了确保任务执行的顺利完成还可以添加完成任务的操作确保数据的一致性。为了确保任务操作的正确性可能还需要加上一些同步机制比如读屏障以确保在任务执行过程中数据的顺序性和一致性。 如果将任务转换为内联执行因为它的执行代码较小这将进一步提高效率。虽然添加读屏障在某些情况下可能并不必要但考虑到多线程环境下的数据一致性问题加入读屏障仍然是一个有效的优化措施确保内存访问的顺序性。 另外在实现平台的相关功能时可以在平台的代码中定义一些基本的操作和数据结构确保任务的执行与平台的操作系统和硬件环境之间的一致性。 game_intrinsics.h: #define CompletePreviousWritesBeforeFutureWrites 在实现手写平台时首先可以使用编译器来定义一些平台特定的细节例如使用 MSCV 编译器或者对于不清楚的情况使用 else 分支进行错误处理但为了继续编译可以暂时保留不清楚的部分并在稍后再做处理。 接着为了确保数据一致性需要定义类似 GCC 的内存屏障指令确保先前的写操作在进行后续写操作前已经完成。这种屏障有助于确保数据访问的顺序性防止读取不一致的数据。 具体而言这里涉及的屏障操作是“写屏障”Write Barrier它确保在写入数据时所有前面的写操作已经完成并且不会被重排序从而保证数据的一致性。这对于多线程或并发执行的环境特别重要可以有效避免数据竞争和不一致的问题。 接下来还需要确保代码能够在不同平台上进行编译尤其是那些可能使用不同编译器的平台如 GCC 或其他编译器。同时在开发过程中需要时刻保持对代码的跟进和优化尽量避免过多的工作量以确保实现的高效性和稳定性。 .Clangd用来给编辑器显示高亮 game.cpp: 清理 目前正在进行渲染工作需要处理“分块渲染工作”。此时无法将参数转换为写入地址因为已经有相应的函数体因此需要确保定义正确。当前的工作实际上是在处理“填充块任务”并且任务的内存需要正确管理和清理。 基本上现在的任务是在准备开始执行这些任务。这意味着开始执行任务是下一步的关键目标。 从 Task-Arena 中分配 AllocateRenderGroup 在执行任务时当需要分配渲染组时之前是使用临时内存区域来分配内存。但现在打算改为使用任务内存区域task arena。因为目前没有其他的分配需求渲染组将会完全从任务内存区域中分配内存。 因此渲染组的内存分配将直接使用任务内存区域剩余的空间而不会再需要额外的内存。为了做到这一点任务内存区域将被用来完全分配给渲染组这样渲染组就能在这块内存中完成所有的渲染工作不需要额外的内存空间。 引入 fill_ground_chunk_work 在任务内部需要执行具体的工作因此必须定义一个“填充地面”工作结构fill_ground_chunk_work。该结构将包含所有任务执行所需的内容。已经事先列出了这些内容因此现在只需将它们正确地组织和插入结构中。 具体来说需要处理的内容包括渲染组render group、输出缓冲区output buffer等元素。渲染组的输出缓冲区可能是一个加载的位图缓冲区loaded bitmap buffer但实际上它就是一个缓冲区buffer用于存储渲染数据。这些元素将会作为结构的一部分来执行任务的工作。 将 fill_ground_chunk_work 放入 PLATFORM_WORK_QUEUE_CALLBACK 这些就是所需要的实际内容。如果将任务传递给“填充地面”工作时它将接收一个包含所有必要内容的结构体。这个结构体包含了任务需要的所有信息任务执行时会从中提取数据并进行处理。处理完毕后它会在最后进行清理。 需要注意的是这个清理过程必须是任务的最后一步因为它会销毁相关的资源意味着一旦清理完毕无法恢复。所以清理工作是任务执行的最终步骤确保所有资源都被妥善释放并处理完毕。 将 fill_ground_chunk_work 放入 FillGroundChunk 并在最后填充它 接下来我们要做的是从任务内存池中分配空间。首先从任务内存池task arena分配内存为每个“fill_ground_chunk_work”ground chunk work结构体创建一个实例。接着可以将需要的数据填充到这个结构体中。 当处理即将结束时将所有的数据添加到结构体中。具体来说这些数据将包括缓冲区、渲染组和任务等相关信息这样就能确保在任务执行过程中所有必要的资源都已正确地关联并准备好。 调用 PlatformAddEntry 接下来应该可以像在渲染组中一样进行操作启动任务。具体来说我们需要在平台上进行入口操作将任务传递到适当的队列中。这个队列用于处理那些在后台执行的任务。 在任务中包含了要填充的地面工作ground chunk work以及执行该任务所需的工作顺序work order。这些步骤将确保任务能够正确地获取地址并开始执行。 通过这种方式我们可以确保后台任务得到正确的安排和管理确保系统能够高效地处理并行任务。 game.h: 实现 GetArenaSizeRemaining 现在需要实现之前提到的、还没有实现的其他函数实际上它们相对简单。首先来看一下内存池arena的结构。我们有size和used两个字段。 为了实现“获取剩余内存”的功能并没有特别复杂的操作。我们可以直接通过计算得到剩余内存。具体来说remaining就等于总内存大小减去已经使用的内存量。 但有一点需要注意的是计算剩余内存时实际上还需要考虑对齐alignment的问题。因为内存对齐可能会导致一些内存空间的浪费所以需要在计算剩余内存时考虑对齐的影响。如果内存块需要特定的对齐必须在计算剩余内存时调整大小。 因此最终的代码应该在计算内存剩余时同时考虑对齐的因素并根据对齐要求调整空间的计算。这是为了确保内存分配时不会因为对齐问题导致错误或者浪费内存。 引入 GetAlignmentOffset 首先需要创建一个类似于“GetAlignmentOffset”这样的函数这个函数会帮助完成对齐计算的工作。它会在计算过程中返回正确的对齐偏移量。该函数的实现会包含对内存的对齐调整从而确保我们在进行内存操作时正确处理了对齐的需求。 在实现时函数会获取当前内存块的对齐偏移量并将其加到当前内存的大小中这样就能确保内存的分配和访问都符合对齐要求。这个过程实际上只是一个数学运算用来处理内存块的对齐问题。 通过这种方式我们可以保证每次进行内存操作时都会考虑到对齐问题避免因对齐不当导致的内存错误或浪费。接下来调用这个计算函数时就可以确保每次都能返回正确的对齐偏移量从而在分配内存时做出相应的调整。 最终计算完成后内存对齐偏移量会被返回这样我们就可以在需要时使用它来调整内存大小确保内存分配与访问操作都符合预期的对齐规则。 计算 GetArenaSizeRemaining 中的结果并清理 首先在进行内存分配时需要确保对齐的正确处理。为了计算剩余的内存大小首先需要从总的内存大小中减去已使用的内存量。接着需要加上任何为对齐所需的额外空间以确保内存的对齐要求得以满足。 在实现时应该初始化对齐偏移量为零之后根据实际的对齐要求调整计算出来的剩余空间。这样可以确保内存分配的对齐性。 接下来的步骤是处理数据类型转换特别是从 size_t 转换到 uint32_t 类型因为在某些情况下这可能会涉及到数据丢失的警告。在这里编译器会警告潜在的数据丢失问题因此需要特别注意。 这部分代码的关键在于正确地计算剩余内存空间并确保所有的内存操作都遵循正确的对齐规范同时解决可能发生的数据类型转换问题。 game.cpp: 在 FillGroundChunk 中将 GetArenaSizeRemaining 强制转换为 uint32 在处理内存分配时需要注意数据类型转换问题尤其是将较大范围的值从 uint64_t 转换到 uint32_t。由于 uint32_t 的最大值为 4GB当值超出这个范围时会发生数据丢失或溢出。 为了解决这个问题可以使用一个安全的转换方法在进行 uint64_t 到 uint32_t 的转换时首先检查值是否超出 uint32_t 的最大值。如果超出就需要采取适当的措施来避免溢出。可以使用一种安全的转换机制例如 safe_cast来保证转换过程中不会丢失数据。 在这种情况下选择使用 uint32_t 或 uint64_t 作为内存大小的表示类型需要谨慎。如果最大缓冲区大小较大超出了 uint32_t 的表示范围则可能需要使用 uint64_t 来避免潜在的溢出问题。 game.h: 实现 SubArena 在处理内存分配时创建子区域SubArena相对直接。首先需要一个内存区域memory arena来作为基础然后从中创建子区域。为此传递内存区域和结果大小信息就足够了。子区域的大小由传入的参数决定表示为某个特定的大小值。 此外内存分配还需要考虑对齐alignment。虽然有可能强制将所有子区域的对齐方式设置为特定值确保对齐正确但对于一些特殊情况还需考虑具体需求。如果对齐是固定的可以忽略不做特别处理否则需要根据实际情况来选择适当的对齐方式。 创建子区域时除了大小和对齐外还需要初始化其他必要的变量例如基础地址、内存索引、临时计数等。这些变量用于确保内存分配的正确性和完整性。子区域的创建过程可以视为一个子分配sub-allocation过程从内存区域中分配一块特定的空间。 创建完子区域后可能还需要进行一些额外的调整比如将某些数据转换为适合的格式确保内存指针指向正确的位置。例如确保内存指针是 uint8_t 指针类型表示字节级的地址。通过这种方式可以确保内存管理的灵活性和高效性。 总结起来创建子区域的过程涉及传递相关大小、内存区域和对齐信息同时也需要根据实际情况初始化必要的变量以确保分配的内存区域正确且高效地利用。 演示我们新增的功能 目前虽然我们接近完成一些工作但仍有许多内容没有完全覆盖特别是在如何整理和完成任务处理部分如任务队列和调度。但是从整体上看我们已经在逐步接近可以进行一般性工作的阶段而不仅仅是针对某些特定的渲染工作负载。 具体来说在填充工作时我们正在使用一个专门的内存区域来处理当前任务。这个内存区域仅限于当前要执行的任务将所有必要的内容都放入该区域中任务完成后会被释放。完成后的任务会通过标记“任务不再使用”来通知系统从而让系统可以清理该任务资源。 目前的实现虽然没有特别复杂但它确实为后续的调试和执行提供了一个基础帮助我们将任务分配到多个队列并允许它们并行运行直到任务完成。通过这种方式系统可以有效地管理多个任务并确保它们按需被执行。 这些步骤为我们提供了一个框架使得可以通过任务调度和内存管理来执行一般性工作而不仅限于某些特定的工作负载。接下来需要进一步完善和调试以确保这个系统能够按预期工作。 game.h:添加Size 将 Size 放到 PushSize_ 中正确的位置 遇到的问题是内存分配时应该保证所请求的内存大小大于或等于所需的大小。通过断言检查可以确保至少分配了请求的内存量。在调试时发现错误的根源是内存位置的推进被放置在了错误的位置。这导致了分配的问题尽管内存空间实际上是充足的但由于推进位置不正确内存没有按预期分配。 这个问题通过调整推进位置来解决确保了内存能够正确分配。这个错误的发现是通过断言验证来确认的。 在调试过程中发现需要考虑到结构体的对齐问题。在分配内存时除了需要处理最大内存大小外还需要确保考虑到结构体的对齐要求。因此决定在进行内存分配时加入结构体对齐的处理。这样可以确保内存的分配不仅满足最大内存大小要求还能正确对齐结构体避免潜在的内存问题。 game_render_group.cpp: 在 AllocateRenderGroup 中有条件地设置 MaxPushBufferSize 在处理 allocate render group 的时候遇到了需要考虑最大推送缓冲区大小MaxPushBufferSize的问题。当前的做法是如果最大推送缓冲区大小为零则会使用剩余的空间来分配内存。这种做法并不是最理想的但在目前的情况下它是一个可行的解决方案。虽然不太喜欢这种方式但没有看到其他更好的选择因此暂时采用了这种做法即在 MaxPushBufferSize 为零时使用剩余空间进行内存分配。 成功运行游戏 现在在后台和渲染过程中有了一些进展虽然我们没有给它足够的提前时间去始终命中但似乎已经解决了长时间停滞的问题这是好的。目前渲染的地块已经在后台运行这是我们想要的效果。 game_render_group.cpp: 重新启用调试相机 现在后台任务已经开始顺利运行我们可以在此基础上进一步改进。接下来可以调整一些细节比如将渲染基础设置为调试模式并扩大查询范围确保后台任务正常工作。 目前虽然已经解决了卡顿问题确保了平稳的帧率但仍有一些细节没有处理好。例如当前我们在渲染位图的同时可能存在未完成的任务问题。为了避免这种情况应该加入一个机制确保任务完成后再开始渲染否则会导致错误。因此需要在任务完成之前加个检查确保任务已完成才进行渲染。 总体来说已经解决了很多问题保持了良好的帧率整体运行良好。 如果一个 task_with_memory 调用先于前面的几个 task_with_memory 调用执行 EndTaskWithMemory前面的任务会出问题吗还是这种情况不可能发生我感觉我没完全理解某些东西 如果有一个任务使用了内存并且它前面有多个任务在使用内存当它调用并测试第一个任务时前面的任务会不会被弄乱这种情况是不可能发生的因为每个任务使用的是各自独立的内存区域而不是共享同一个内存区域。 具体来说每个任务都会使用自己专用的内存区域arena因此它们之间不会相互影响。即使一个任务开始执行它前面的任务的内存内容也不会被破坏因为每个任务的内存分配和操作是独立的。这样可以避免任务之间的相互干扰确保它们各自按计划进行不会互相影响。 在 Arena 中存放什么以及它们是如何工作的 在启动时分配了一个临时内存区域transient arena这个区域的大小是根据机器内存来决定的基本上是整个系统可用的内存量。这个内存区域一开始是一个大的内存块随后会被划分成多个小区域来使用。 首先这个内存区域会被用来存储一些数据比如说图像或其他一些内容。接着内存的不同部分会被用来存放不同的任务或数据。例如用来存储地面数据、位图数据等。然后根据需求将内存区域分割成几个独立的子区域分别用于不同的任务处理。 每个任务都会获得自己专用的内存区域称为子内存区域或arena。这些子区域是完全独立的因此多个任务可以异步执行互不干扰。每个任务只会在自己的内存区域内操作不需要和其他任务的内存进行同步或通信。这种设计确保了任务之间互不干扰各自可以独立进行计算。 当需要启动一个任务时任务的相关数据例如渲染组数据会被放入相应任务的内存区域。一旦任务开始执行就会一直运行到完成。当任务完成时它会通过设置一个标志位来表明自己已经完成。每个内存区域都有一个“正在使用”的标志位这样系统可以通过检查这些标志位来确定哪些任务还在进行哪些任务已经完成。一旦一个任务完成标志位会被设置为0表示该内存区域可以被其他任务使用。 这样每个任务的内存区域都是隔离的这意味着任务可以独立运行互不干扰系统不需要担心任务的执行顺序或同步问题。 我们已经为 GroundBuffers 预留了内存。为什么不能直接使用这些内存来处理线程任务 我们已经为地面缓冲区预留了内存但是不能将这些内存直接用于多线程任务处理。原因是地面缓冲区和渲染组使用的是不同的内存区域且它们的用途不同。地面缓冲区内存需要用来存放位图而渲染组则涉及到与渲染相关的数据因此不能将这两者的内存混用。 此外我们需要处理大量的地面数据例如有256个地面区域和128个地面块而这些地面块的内存会被用来存储位图数据。位图本身的大小是256x256的因此需要相当大的内存空间来存储这些位图。如果直接将渲染组数据放入地面缓冲区内存区域中渲染组数据可能会覆盖掉位图数据从而导致渲染错误。 另外如果我们将渲染组数据存放在地面缓冲区内存的开头部分虽然可以避免覆盖位图数据但这样会导致所有128个地面块的内存被扩大。即便我们只会同时使用其中的四个地面块其他不需要的内存也会被浪费掉。这显然是不合适的因为这样会浪费大量内存空间。 为了解决这个问题我们为每个任务单独分配了内存这样只有在任务生命周期内需要的内存才会被使用。任务处理完成后这些内存会被释放不会占用持久存储从而避免了浪费大量内存。通过这种方式可以有效地管理内存使用同时确保每个任务有足够的内存进行处理。 屏幕上的那些线条是什么 屏幕上的这些线条是调试模式下显示的用来帮助查看不同的区域。具体来说这些线条表示以下几个区域 相机视野范围表示相机能够看到的区域。模拟区域表示模拟的工作区域也就是游戏或应用的核心计算和处理区域。碰撞考虑的外边界表示碰撞检测的最大边界用来判断物体之间是否发生碰撞。 这些线条是调试时用于帮助开发人员了解各个区域的关系用户在实际使用时是看不到这些线条的。用户只会看到应用或游戏的正常界面而这些调试信息只是用来辅助开发和调试工作。例如黄色线条表示相机的边界它显示了相机能够捕捉到的区域范围。 win32_game.cpp: 在 Win32DisplayBufferInWindow 中暂时将 OffsetX 和 OffsetY 设置为 0 在调试过程中遇到了一个对齐问题。看起来调用路径可能有误导致出现了不希望出现的行为。具体来说发现窗口宽度和缓冲区宽度的倍数关系不对导致了某种错误的路径被执行。检查后发现这个问题与窗口大小的拉伸stretch有关而实际上不应该进行拉伸操作。 经过分析发现这个问题是由于代码的某个路径被错误地调用导致了不正确的行为。为了暂时解决问题决定注释掉一些代码行直到能够做出更智能的调整。 所以你基本上为地面块构建了一个任务池来在另一个线程上运行。你能否使用这个机制处理其他类型的任务 这个机制不仅仅是为了处理地面块的任务它的设计是可以用于其他类型的任务的。通过这种方式任何需要的临时存储和任务处理都可以通过这种机制来实现。对于渲染任务虽然它也通过相同的任务系统进行处理但它不需要临时存储因为它是一个即时的任务所以不需要额外的存储空间。 为什么不把临时 Arena 顶部的东西比如地面块位图存放到永久存储中 将暂时存储区顶部的内容放入永久存储区并不可行因为这些数据并不是永久性的它们可以随时重新创建。例如当操作系统进入低内存状态时这些数据可以被丢弃然后在需要时重新生成。因此使用暂时存储区是合理的因为这些数据可以根据需要被丢弃并重新创建。 为什么有时会闪烁 magenta是因为我们在工作没有完成时就开始绘制了吗 闪烁成品红色品红色是因为我们在没有完成工作的情况下就开始绘制了。正如之前提到的应该在绘制之前先进行锁定。虽然从技术上讲我们可以选择不进行锁定因为我们的目标是始终在这些区域被看到之前就将它们排队这样就不会进入这种情况。但是目前的问题是我们没有提前请求这些区域。 在 Windows 和 Linux 系统中堆Heap和栈Stack的内存地址增长方向是由操作系统和硬件架构共同决定的。以下是对两者在常见情况下的详细分析特别是在主流的 x86 和 x64 架构上。我们将分别讨论堆和栈的内存地址增长方向并解释其背后的机制。 栈Stack的内存地址增长方向 栈是用于存储函数调用上下文如局部变量、返回地址等的一种数据结构其内存地址的增长方向通常由 CPU 架构决定。 Windows 和 Linux 的共同点 在 x8632 位和 x6464 位架构上栈的地址增长方向是向下从高地址向低地址增长。原因 这是 Intel x86/x64 架构的硬件设计决定的。栈指针ESP 或 RSP在每次压栈push操作时减少指向更低的内存地址。例如调用函数时栈指针会减小以分配空间给局部变量返回时通过加回来释放pop。 具体行为 初始栈顶 操作系统会为每个线程分配一个栈空间默认大小 Windows 上通常是 1MBLinux 上通过 ulimit -s 查看常用 8MB。栈顶从高地址开始例如 0x7FFFFFFF32 位或 0x7FFFFFFFFFFF64 位具体地址因系统而异。 增长方向 每次压栈push或分配局部变量栈指针减小例如从 0x7FFFFFFF 到 0x7FFFFF00。示例void func() {int a 1; // 栈指针减少 4 字节32 位或 8 字节64 位int b 2; // 再次减少 }假设初始栈指针是 0x7FFFFFFF分配 a 后可能变为 0x7FFFFFFB再分配 b 后变为 0x7FFFFFF732 位系统未考虑对齐。 Windows 和 Linux 的细微差异 栈大小 Windows默认 1MB可通过链接器选项/STACK调整。Linux默认 8MB可通过 ulimit -s 修改。 地址空间 两者的虚拟地址范围不同但增长方向一致向下。Windows 64 位用户态栈地址通常在 0x7FFFFFFF0000 附近。Linux 64 位栈地址通常在 0x7FFFxxxxxxxx 范围内由内核随机化影响。 堆Heap的内存地址增长方向 堆是动态分配内存的区域如通过 malloc、new 等其地址增长方向由操作系统的内存管理器决定而非硬件直接控制。 Windows 和 Linux 的共同点 堆的地址增长方向通常是向上从低地址向高地址增长。原因 堆管理器从较低的地址开始分配内存块随着分配请求增加逐渐向更高的地址扩展。这样设计避免与栈从高地址向下增长发生冲突。 具体行为 初始堆地址 堆通常位于进程地址空间的较低部分高于代码段和数据段。示例 Windows 64 位可能从 0x00000000xxxxxxx 开始。Linux 64 位可能从 0x00000000xxxxxxx 开始受 ASLR 影响。 增长方向 每次分配内存如 malloc堆管理器从当前堆顶向高地址扩展。示例int* p1 new int; // 分配 4 字节假设地址 0x1000 int* p2 new int; // 下一个地址 0x1004忽略对齐和元数据Windows 的堆管理 实现Windows 使用 HeapAlloc基于 NT 堆管理器。增长 默认堆从低地址开始调用 HeapAlloc 时向高地址扩展。如果默认堆不够系统会创建额外的堆地址可能不连续。 地址示例 初始堆可能在 0x0000000140000000 附近后续分配递增。 Linux 的堆管理 实现Linux 使用 glibc 的 malloc底层通过 brk 或 mmap 分配。增长 小块内存通过 brk 扩展程序的数据段sbrk地址向上增长。大块内存通过 mmap 分配可能不连续但总体趋势是从低到高。 地址示例 初始堆可能在 0x0000555555600000 附近后续递增。 差异 ASLR地址空间布局随机化 Linux 默认启用 ASLR堆地址随机化程度更高每次运行可能不同。Windows 也支持 ASLR但默认堆地址相对稳定除非明确启用随机化。 扩展方式 Linux 的 mmap 可能导致堆地址跳跃而 Windows 的堆更倾向于连续增长。 内存地址空间布局 为了直观理解以下是典型 64 位进程的地址空间布局简化版 ------------------- 高地址 (0x7FFFFFFFFFFF) | 栈 (向下增长) | | | | ... | | | | 堆 (向上增长) | | ... | | 数据段 (.data) | | 代码段 (.text) | ------------------- 低地址 (0x000000000000)栈从高地址向下如 0x7FFFFFFFxxxx 到 0x7FFFFFxxxx。堆从低地址向上如 0x000055555xxxx 到 0x000055556xxxx。 验证方法 示例代码 #include iostream int main() {int a 1; // 栈上int* p new int(2); // 堆上std::cout Stack addr: a std::endl;std::cout Heap addr: p std::endl;delete p;return 0; }Windows 输出示例Stack addr: 0x000000F4A6EFFC4C (高地址) Heap addr: 0x000001F4A7206640 (低地址)Linux 输出示例Stack addr: 0x7FFF5E8B6CFC (高地址) Heap addr: 0x55555556B6B0 (低地址)观察增长 栈连续定义多个局部变量地址递减。堆多次 new地址递增。 总结 栈 Windows 和 Linux向下增长高地址 - 低地址。由 x86/x64 硬件设计决定。 堆 Windows 和 Linux向上增长低地址 - 高地址。由操作系统堆管理器实现Linux 受 ASLR 和 mmap 影响更大。 差异 默认栈大小和地址随机化策略不同但增长方向一致。 如果需要更具体的地址示例或调试方法可以告诉我你的系统和架构我可以进一步帮你验证
http://www.hkea.cn/news/14270149/

相关文章:

  • 微信公众平台官方网站大良网站建设价位
  • 如何在网站上木马网店运营推广1+x证书查询
  • 个人网站效果苏州高新区住建局官网
  • 织梦开发小说网站教程佛山网站优化公司排名
  • 湘潭网站建设速来磐石网络龙岗网站建设网站排名优化
  • 手机版企页网站案例wordpress最好的编辑器
  • 南通网站群建设公司宣传册制作
  • WordPress和ftp区别wordpress可以做seo吗
  • 网站建设自助建站云建站营销型网站建设就找山东艾乎建站
  • 学校html网站模板代码网站备案后要做什么
  • 建设网站东莞广州品牌设计公司50强
  • 网站建设第一步怎么弄wordpress 判断文章页
  • 开封做网站优化vi视觉设计案例
  • 鄂州网站开发环境建设公司属于什么企业
  • 电影网站源码access中英文网站怎么做的
  • 怎么让公司建设网站泸州市住房与城乡建设局网站
  • 网站内容与功能设计与实现的品牌专业群建设网站
  • wordpress做的网站效果wordpress 分类标题
  • 网站技术解决大型门户网站建设方案
  • 物流网站建设公司阿里云 装wordpress
  • 废品回收在哪个网站做效果好商城的网站设计
  • 网站建设推广费用sem营销推广
  • 网站访问量统计代码怎么做西安seo公司
  • 大学生兼职网站开发毕设论文上海做网站的月薪
  • 企业网站优秀案例wordpress用户关注
  • 色块布局网站首页模板西安网站维保公司
  • 政务网站队伍建设情况汇报兰州网站定制公司
  • 秒玩大型游戏的网站企业自适应网站制作
  • 建立网站兴田德润电话多少网站模板登录模块
  • 怎样能注册自己的网站有哪些行业需要做网站建设和推广