FlatBuffers C++ 如何直接访问 struct 内存并对接 OpenGL 顶点数组? 📅 发布时间:2026/9/13 18:42:12 👁 浏览次数: FlatBuffers C 如何直接访问 struct 内存并对接 OpenGL 顶点数组【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers如果你的 C 程序把顶点数据比如Vec3这类定长结构序列化进 FlatBuffers并想在渲染时跳过解包步骤、直接把缓冲区内的 struct 数组指针交给 OpenGL 的glVertexAttribPointer那么需要满足 FlatBuffers 官方文档给出的几个前提struct 的内存布局在所有平台上确定且一致且你运行在小端机器上。本文基于仓库内 C 语言指南 的 Direct memory access 一节、schema 文档 的 struct 说明以及 monster.fbs 这个随仓库自带的示例 schema给出从建 schema 到取指针传给 OpenGL 的完整路径。前提schema 中用 struct 描述顶点而不是 tableFlatBuffers 中 table 的字段按偏移访问、布局对用户不透明不能直接取成员内存而 struct 可以。根据 schema 文档struct 只能包含标量或其他 struct字段全部必填、没有默认值struct 永远内联存储inline在父对象中不使用虚表比 table 占内存少、访问更快struct 一旦定义就不能加字段或弃用字段——所以只适合你确信永远不会改的数据顶点坐标正好符合这一条。仓库示例 monster.fbs 里的Vec3就是典型的顶点 struct并且Monster表同时演示了两种持有方式单个内联字段pos:Vec3和 struct 向量path:[Vec3]// 来自 samples/monster.fbs struct Vec3 { x:float; y:float; z:float; } table Monster { pos:Vec3; ... path:[Vec3]; } root_type Monster;另外struct 还支持定长数组的简写语法当前只有 struct 支持数组struct Vec3 { v:[float:3]; }与上面三个 float 字段的写法二进制等价。顶点数据如果带法线、UV 等多组分量可以用这种写法合并。用 flatc 生成 C 头文件例如flatc -c monster.fbs会生成monster_generated.h仓库里已包含 monster_generated.h 可直接对照。生成的Vec3长这样// 摘自 samples/monster_generated.h FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Vec3 FLATBUFFERS_FINAL_CLASS { private: float x_; float y_; float z_; public: Vec3() : x_(0), y_(0), z_(0) {} Vec3(float _x, float _y, float _z) : x_(::flatbuffers::EndianScalar(_x)), y_(::flatbuffers::EndianScalar(_y)), z_(::flatbuffers::EndianScalar(_z)) {} float x() const { return ::flatbuffers::EndianScalar(x_); } // y(), z() 同理 }; FLATBUFFERS_STRUCT_END(Vec3, 12);FLATBUFFERS_MANUALLY_ALIGNED_STRUCT会关掉编译器自行添加的填充、按 FlatBuffers 规则强制对齐保证该 struct 布局与编译器和平台无关访问器里的EndianScalar负责标量字节序转换。struct 的整体大小固定为 12 字节3 个 float。直接访问内存的官方规则与硬性限制C 语言指南 Direct memory access 一节的原意是由于所有东西在 wire 格式上都是小端、且普通 table 布局对用户不透明所以默认必须走生成的访问器但 struct 是例外——struct 的布局是确定的deterministic并且在各平台保证一致标量按自身大小对齐struct 整体按最大成员对齐官方明确允许对 struct 指针包括 struct 数组直接sizeof()和memcpy要计算 struct 内部子元素的偏移时文档建议让子元素本身也是 struct这样可以直接用取地址的指针相减得到偏移不需要硬编码数值。这正是用 arrays of structs 对接glVertexAttribPointer之类的 OpenGL 调用的用法文档原文点名了这个场景限制struct 内存永远是按小端存储的。只有在你能确定不会在大端机器上运行时才可用这种技巧文档建议加一条断言assert(FLATBUFFERS_LITTLEENDIAN)。FLATBUFFERS_LITTLEENDIAN由 include/flatbuffers/base.h 在编译期按编译器宏推导GCC/Clang 下看__BYTE_ORDER__MSVC 下_M_PPC定义为 0否则为 1。注意另一条隐含前提读到的根对象指针GetMonster返回的指针指向的是缓冲区内部不是独立拷贝。把 struct 数组指针交给 OpenGL 之前必须保证源缓冲区你new char[length]出来的那块内存或文件映射仍然存活文档中对 root 指针与buffer_pointer的区分就是在这个意义上强调的。构造包含 struct 数组的 FlatBuffer用 monster.fbs 作为 schema 时C 侧构造path字段struct 向量的代码见 tutorial#include flatbuffers/flatbuffers.h #include monster_generated.h using namespace MyGame::Sample; FlatBufferBuilder builder(1024); // 构造一个 Vec3 数组 Vec3 points[] { Vec3(1.0f, 2.0f, 3.0f), Vec3(4.0f, 5.0f, 6.0f) }; // 序列化为 struct 向量 flatbuffers::Offsetflatbuffers::VectorVec3 path builder.CreateVectorOfStructs(points, 2); // 继续创建 Monsterpos 直接传 Vec3 指针内联存储Finish 得到根偏移 auto monster CreateMonster(builder, points[0], // pos:Vec3 150, 100, // mana, hp builder.CreateString(Orc), flatbuffers::Offsetflatbuffers::Vectoruint8_t(), 0, flatbuffers::Offsetflatbuffers::VectorWeapon(), Equipment_NONE, path); builder.Finish(monster);如果数据来自已有的二进制文件例如由flatc -b monster.fbs monsterdata.json生成或仓库自带的 monsterdata_test.mon读取方式同样来自 C 语言指南std::ifstream infile(monsterdata_test.mon, std::ios::binary | std::ios::in); infile.seekg(0, std::ios::end); int length infile.tellg(); infile.seekg(0, std::ios::beg); char *data new char[length]; // 注意此内存要活到 OpenGL 使用完 struct 数组为止 infile.read(data, length); infile.close(); Monster *monster GetMonster(data);pos是 struct 字段时monster-pos()返回const Vec3 *本身就指向缓冲区内的 12 字节内联数据path是向量字段时monster-path()返回const VectorVec3 *其元素连续排布就是一个标准的 arrays of structs。取 struct 数组指针并对接 glVertexAttribPointer从缓冲区拿到 struct 数组后直接访问不再经过访问器逐字段读取const flatbuffers::VectorVec3 *path monster-path(); if (path) { // path-data() 即第一个 Vec3 的内存(float*)path-data() 就是顶点坐标数组 const float *verts reinterpret_castconst float *(path-data()); uint32_t count path-size(); // 需要按成员取偏移如分离式属性、法线数组时 // 用指针相减代替硬编码偏移量 uint8_t *base const_castuint8_t *(path-data()); size_t off_y (uintptr_t)(const_castVec3 *(path-data())-y()) - (uintptr_t)base; glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vec3), verts); }各值的来源与替换方式verts、count、off_y都来自上面读到的缓冲区无外部输入sizeof(Vec3)为 12由生成的FLATBUFFERS_STRUCT_END(Vec3, 12)保证等价于文档所说对 struct 指针使用sizeof()off_y展示了文档建议的用指针计算子元素偏移的做法如果你的 struct 成员更多法线、UV 都是独立字段或独立 struct对每个字段照此取址即可不要手写 0/4/8 这类魔数glVertexAttribPointer调用本身属于 OpenGL 侧接口FlatBuffers 文档只点明了这种对接方式具体location、GL_FLOAT等参数按你的渲染需求填写使用前加assert(FLATBUFFERS_LITTLEENDIAN)确认运行在小端平台。一个更完整的端到端参照实现是仓库的 sample_binary.cpp它演示了加载.mon文件、GetMonster之后逐字段读取的完整流程你只需把读取pos的部分换成上面对path数组的处理。验证与边界验证读取是否成功调用生成的访问器本身如monster-path()非空且monster-path()-size()与写入的 struct 数量一致上面例子为 2对不受信任来源的缓冲区可先跑Verifier verifier(buf, len); VerifyMonsterBuffer(verifier);ok为真才继续访问这是 C 语言指南给出的标准验证入口。边界一这条直接取指针的路径只对 struct 成立。inventory、name、weapons这类 table/string/vector 字段仍必须通过访问器读取不能把monster指针当普通 C 结构体用。边界二struct 字段不可增删。将来顶点格式要改加切线、改精度只能换 schema 重新生成数据需要演进的数据结构应留在 table 层。边界三大端平台不可用FLATBUFFERS_LITTLEENDIAN 0时assert会触发这与 base.h 中该宏的推导一致。延伸阅读C 语言指南 中 Direct memory access 与 Access of untrusted buffers 两节前者是本文依据后者说明 verifier 的用法与开销。tutorial 的 Vectors 小节CreateVectorOfStructs的完整多语言对照。internals展示生成头文件中Vec3与Monster的完整形态理解FLATBUFFERS_MANUALLY_ALIGNED_STRUCT如何关闭编译器填充、手动加对齐。【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考