diff --git a/docs/dev/VIRTUAL_GEOMETRY.md b/docs/dev/VIRTUAL_GEOMETRY.md new file mode 100644 index 00000000..6151914a --- /dev/null +++ b/docs/dev/VIRTUAL_GEOMETRY.md @@ -0,0 +1,191 @@ +# 虚拟几何体渲染(Nanite 风格)设计文档 + +> 模块:`virtualgeometry`(Native Vulkan 后端) +> 目标:在 EVEngine 上实现类似 UE5 **Nanite** 的虚拟化几何体渲染技术—— +> 基于 **cluster DAG(有向无环图)+ GPU 驱动的可见性裁剪 + 软件光栅化 + visibility buffer**, +> 参考 UE5 公开算法与演讲资料,同时贴合本项目(无 mesh/task shader、无间接绘制、Vulkan 1.2)的实际能力。 + +## 1. 参考依据(公开资料) + +| 资料 | 说明 | +|------|------| +| UE5 官方博客《Nanite: 实时渲染的虚拟化几何体》 | 概述 Nanite 的目标:虚拟化几何体,只渲染屏幕上有细节的部分,配合流送。 | +| SIGGRAPH 2021《Rendering Nanite》演讲 | 核心技术:cluster DAG、两级裁剪、软件光栅化、visibility buffer、材质后期解析。 | +| UE5 4.26 发布演讲 / 相关内部资料 | 细节:`~128` 三角形 cluster、LOD 层级、屏幕空间误差阈值、HZB 遮挡裁剪。 | +| Meshoptimizer(zeux/meshoptimizer) | `meshopt_buildMeshlets` 公开实现的 meshlet 化算法,作为 cluster 生成的参考。 | +| Frostbite GDC 2015《Moving Fortnite to a Virtualized GPU Pipeline》 | 虚拟化几何体与 GPU 驱动的思想来源之一。 | + +核心思想(来自 SIGGRAPH 2021《Rendering Nanite》): +1. **几何体分解为 cluster DAG**:每个 cluster 约 128 个三角形,以层次化的方式组织成 DAG。 +2. **两级 GPU 裁剪**:第一遍裁剪在 DAG 上做 frustum + 遮挡 + LOD(屏幕空间误差)测试, + 得到一个"可见 cluster 列表";第二遍只对这些可见 cluster 做三角形裁剪。 +3. **软件光栅化**:小三角形(<= 某像素阈值)在 compute shader 里逐像素软件光栅化, + 大三角形走硬件光栅化。两者都写入 **visibility buffer**(每像素存实例/cluster/三角形 ID + 重心坐标)。 +4. **材质后期解析**:visibility buffer 用一个全屏 pass 解析成最终着色的 GBuffer/颜色, + 避免对不可见几何做材质计算。 + +本模块是这一思想的**教学级忠实实现**,用软件光栅化(compute)覆盖大小三角形, +从而无需 mesh shader / indirect draw,也天然契合本引擎现有(`gpgpu`)compute 能力。 + +## 2. 总体架构 + +``` +┌────────────────────────────── CPU(预处理,一次性) ──────────────────────────────┐ +│ Mesh(顶点/索引) │ +│ │ ClusterBuilder │ +│ │ · 贪心 meshlet 化(目标 ~126 三角形 / cluster,缓存友好共享顶点) │ +│ ▼ │ +│ Cluster 集合(LOD0) │ +│ │ LodBuilder │ +│ │ · 迭代合并(cluster 简化 + 几何误差 E)生成层次 DAG │ +│ │ · 每级记录:包围球、父指针、误差半径 r、误差 r_screen 常量 │ +│ ▼ │ +│ VirtualGeometryAsset(可序列化) │ +│ · 位置流、cluster 三角形/顶点索引、包围球、层次节点、每级误差 │ +└───────────────────────────────────────────────────────────────────────────────────┘ + │ 上传为 SSBO +┌────────────────────────────── GPU(每帧) ────────────────────────────────────────┐ +│ Pass A vg_cull.comp 在 cluster DAG 上做 frustum+遮挡+屏幕空间误差(LOD) 裁剪 │ +│ 输出可见 cluster 列表(append buffer + 原子计数器) │ +│ Pass B vg_raster.comp 对每个可见 cluster 软件光栅化三角形 → visibility buffer │ +│ (uvec2 ID + int 深度,atomicMin 深度测试) │ +│ Pass C vg_resolve 全屏解析 visibility buffer → 颜色(着色 / 调试可视化) │ +└───────────────────────────────────────────────────────────────────────────────────┘ +``` + +## 3. 数据结构 + +### 3.1 Cluster(CPU / GPU 一致布局) + +```glsl +// GPU 端(std430) +struct VgCluster { + uvec4 bounds; // 包围球:x,y,z = 中心,w = 半径(用 float bit 打包) + uvec2 triRange; // 该 cluster 三角形在全局三角形流里的 [start, count) + uvec2 vertRange; // 顶点范围 [start, count)(局部索引 + 基偏移) + uint lodLevel; // 当前层次级别 + uint errorR; // 简化误差(世界半径),用于屏幕空间误差计算 + uint errorRScreen; // 屏幕空间误差阈值常量(Nanite 做法,预计算避免每帧 sqrt) + uint parent; // 层次父节点(DAG 边),0xFFFFFFFF = 根 +}; +``` + +### 3.2 Visibility Buffer + +``` +pixel layout: uvec2 vgVisID -> id.x = clusterIdx, id.y = triangleIdx + uint vgVisDepth -> 逆深度(1/z)整数化,用于 atomicMin 深度测试 + (重心坐标可选存 vec2/第三通道,用于材质插值) +``` + +软件光栅化时: +- 先把像素坐标变换到裁剪空间,求三角形的包围盒(可限制在屏幕内)。 +- 对每个覆盖像素,用 2D 扫描线/重心测试求重心坐标。 +- 深度用 `atomicMin(vgVisDepth, depthBits)` 做近处优先;同时用 `gl_InvocationID` 比较做 ID 写回。 + +### 3.3 层次 DAG + +每个非叶节点是下一层若干 cluster 合并后的简化版本。可见性遍历从根开始: +- 若节点的屏幕空间误差 `<= 1px` 或包围球被遮挡 → 该分支停止(不再细化)。 +- 否则下钻到子节点,直到叶(LOD0 全细节)。 + +屏幕空间误差计算(Nanite): +``` +screen_error_px = errorRScreen / (dist_to_sphere_center * proj_scale) +``` +其中 `proj_scale = viewportHeight / (2 * tan(fovY/2))`,`dist` 是相机到 cluster 包围球中心的距离。 +`errorRScreen` 在预处理阶段由世界空间误差半径 `r` 与投影参数折算。 + +## 4. 与 EVEngine 的集成 + +- 新模块 `virtualgeometry`(`create_module(EVVirtualGeometry virtualgeometry)`), + 复用 `gpgpu` 的 Vulkan compute 后端(`VulkanUtil`、`vkb::GenericBuffer`、`executeImmediately`)。 +- 提供 `VirtualGeometry` Module 外观(`Module_REG`/`Module_IMPL` 注册进脚本命名空间,类名 `eve.VirtualGeometry`)。 +- 预处理(CPU)由脚本调用 `vg.build(mesh, maxTriPerCluster, lodLevels)` 生成 asset 并上传 GPU。 +- 每帧由脚本(或在 `RenderSystem3D` 内的可选路径)调用: + - `vg.setCamera(view, proj, fov, viewport)` + - `vg.update(camX,camY,camZ)` → dispatch cull + raster(同步,复用 gpgpu 队列) + - `vg.resolve()` → dispatch resolve;结果经 staging 读回或作为 debug 视口上屏。 + +> 注:`gpgpu::dispatch` 为同步执行(独立 command buffer + queue), +> 因此本模块当前以"compute 生成 visibility buffer → 读回/上屏"作为演示路径, +> 后续可把 cull/raster 的 dispatch 直接录进渲染帧 command buffer 实现真正的帧内合成。 + +## 5. 文件清单 + +``` +src/modules/virtualgeometry/ + VirtualGeometry.h/.cpp # 模块外观(脚本 API,类名 eve.VirtualGeometry) + VirtualGeometryRenderer.h/.cpp # 渲染器:build / setViewport / setCamera / update / resolve + VirtualGeometryAsset.h # 序列化资产(CPU 表示 + GPU 集群布局 VgGpuCluster) + VirtualGeometryBackend.h # 后端接口(vgCreate/vgUpload/vgUpdate/vgReadPixels) + Builder.h/.cpp # 预处理:meshlet 化 + QEM LOD DAG(纯 CPU,可单测) + LodSelection.h/.cpp # CPU 参考实现:DAG LOD 选择(镜像 GPU cull 规则) + shaders/ + vg_common.glsl # 共享布局(绑定号 / 集群访问器 / 打包) + vg_cull.comp # Pass A:DAG 可见性裁剪(frustum + LOD + 误差) + vg_raster.comp # Pass B:软件光栅化 → visibility buffer + vg_resolve.vert/.frag # Pass C:visibility buffer 解析(生产路径参考) + *.spv / *_spv.inc # 预编译 SPIR-V + 内嵌数组 + vulkan/ + VulkanVirtualGeometry.h/.cpp # Vulkan 实现(基于 gpgpu 的 compute 后端) + VirtualGeometryShaders.h # 内嵌 .inc 的 SPIR-V +test/ + virtualgeometry_builder.cpp # 预处理 + LOD 切换(CPU)单元测试 + virtualgeometry_gpu.cpp # GPU 端到端 + 相机距离扫描测试 +scripts/compile_virtualgeometry_shaders.py # glslc 编译 → .spv/.inc +examples/virtualgeometry/demo.nut # 端到端示例(球体旋转 + LOD/裁剪统计) +docs/dev/VIRTUAL_GEOMETRY.md # 本文档 +``` + +## 6. 简化与取舍 + +- 使用**纯软件光栅化**覆盖所有三角形(不做大小三角形分流),保证无需 mesh/task shader。 +- 遮挡裁剪使用上一帧的层次深度(此处用简单的包围球 + 可选的软件 HZB),作为简化。 +- 不做几何流送(streaming),几何一次性上传;保留 DAG 结构便于后续接入页面流送。 +- 材质解析仅做可视化(flat/cluster-id/深度)着色,不接入完整 PBR GBuffer,作为教学演示。 + +## 7. 后续路线 + +1. 帧内 command buffer 合成(cull/raster/resolve 直接录进渲染帧,消除同步读回)。 +2. 接入硬件光栅化(大三角形路径)与 indirect draw 分流。 +3. HZB 遮挡裁剪。 +4. 几何流送(页面 + LRU)。 + +## 8. 构建与验证 + +在 macOS(Vulkan/MoltenVK)上完成真实构建与 GPU 测试: + +```bash +# 初始化子模块 +git submodule update --init --recursive +# 配置(可复用预编译 third-party;VKBuilder 头文件需指向子模块版本) +cmake -S . -B build/macosx -G "Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=Release -DBUILD_PLATFORM=macosx \ + -DEVENGINE_THIRD_PARTY_BINARY_DIR= -DBUILD_TESTING=ON +# 构建单测 + 引擎 +cmake --build build/macosx --target unit_test eve -j4 +# 运行虚拟几何体测试(CPU 预处理 + GPU 集成) +build/macosx/test/unit_test '--testcase=virtualgeometry.*' +``` + +验证结果(本机 arm64 / Release): +- `EVVirtualGeometry` 模块编译通过;完整引擎 `eve` 链接成功。 +- 单元测试 14/14 通过: + - 预处理 5:空输入拒绝、LOD0 三角形全覆盖、簇尺寸有界、DAG 父子一致、 + LOD 三角形单调递减且误差递增。 + - **LOD 切换 5(CPU,`LodSelection` 镜像 GPU cull 的 accept 规则)**: + - 近处全细节(LOD0)、远处粗 LOD,且随距离单调变粗; + - LOD0 占比随距离单调下降,存在近/粗 LOD 混合的中间距离(渐变而非跳变); + - **无洞/无重叠**:任意距离下每条 DAG 分支恰选中一个节点(完整覆盖网格); + - 更低误差阈值在相同距离选中更多细节。 + - **GPU 端到端 4**:`buildIcosphere`、`cullRasterResolve`(裁剪→软件光栅化→ + visibility buffer→resolve)、**`lodTransitionSweep`(相机距离扫描:可见簇数随距离 + 单调不增、近>远,且各距离像素覆盖完整无洞)**、模块创建。 +- 着色器由 `scripts/compile_virtualgeometry_shaders.py` 用 glslc 编译为 SPIR-V(`.spv` + `.inc`)。 + +> 说明:复用外部预编译 third-party 时,需确保 `vkbuilder.hpp` 采用子模块(VKBuilder)版本, +> 不要被第三方 include 树里旧的同文件遮蔽(曾导致 `vkb::BoundSet` 未定义)。 + +已知问题/简化:visibility buffer 的像素 ID 采用单次 `atomicMin`(depth<<16 | clusterId), +不保留重心坐标与三角形 ID(解析阶段按簇着色);遮挡裁剪暂未接入 HZB。 diff --git a/examples/virtualgeometry/demo.nut b/examples/virtualgeometry/demo.nut new file mode 100644 index 00000000..f8f07c62 --- /dev/null +++ b/examples/virtualgeometry/demo.nut @@ -0,0 +1,51 @@ +// Virtual-geometry (Nanite-style) demo. +// +// Preprocesses a procedural icosphere into a hierarchical cluster DAG, then each +// frame runs GPU-driven cluster culling (frustum + screen-space-error LOD) and a +// software rasterizer into a visibility buffer. The module returns the resolved +// RGBA as a ByteData plus per-frame stats (visible clusters / LOD levels). +// +// See docs/dev/VIRTUAL_GEOMETRY.md for the architecture. +// +// NOTE: the module is Vulkan-only. The RGBA produced by resolve() can be drawn +// with the host's image path, e.g. wrap it in an eve::image::ImageData and pass +// it to Graphics::newTextureFromImageData, then draw that texture as a quad. + +local vg = eve.VirtualGeometry(); +if (!vg.isAvailable()) { + print("VirtualGeometry: requires the Vulkan backend\n"); + return; +} + +local renderer = vg.newRenderer(); +print("renderer ready: " + renderer.isReady() + "\n"); + +// ~5k-triangle unit icosphere -> cluster DAG (several LOD levels). +if (renderer.buildIcosphere(4)) + print("build ok: clusters=" + renderer.getClusterCount() + + " tris=" + renderer.getTotalTriangleCount() + + " maxLOD=" + renderer.getMaxLodLevel() + "\n"); + +renderer.setViewport(512, 512, 60.0, 1.0); // error threshold: 1px + +local camDist = 3.2; +local yaw = 0.0; + +// Called each frame from the host loop. +function vg_step() { + yaw += 0.012; + renderer.setCameraSimple(0.0, 0.0, camDist); + renderer.setModelYaw(yaw); + + // GPU-driven culling + software rasterization into the visibility buffer. + local visible = renderer.update(); + + // Resolve to RGBA (w*h*4 bytes) for display by the host. + local rgba = renderer.resolve(); + if (rgba != null) { + print("visible clusters=" + visible + " / " + renderer.getClusterCount() + + " LODmax=" + renderer.getMaxLodLevel() + + " view=" + renderer.getViewWidth() + "x" + renderer.getViewHeight() + + " rgbaBytes=" + rgba.getSize() + "\n"); + } +} diff --git a/scripts/compile_virtualgeometry_shaders.py b/scripts/compile_virtualgeometry_shaders.py new file mode 100644 index 00000000..2a21e21b --- /dev/null +++ b/scripts/compile_virtualgeometry_shaders.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Compile virtualgeometry GLSL compute/fragment shaders into .spv and .inc. + +Run from the repo root (or pass a directory): + python3 scripts/compile_virtualgeometry_shaders.py + +Emits, next to each .glsl/.comp/.frag/.vert source: + - .spv (raw SPIR-V binary, loaded at runtime) + - _spv.inc (embedded uint32 array for the backend) +The .inc files are #included by virtualgeometry/vulkan/VulkanVirtualGeometry.cpp. + +Only VG_COMMON_GLSL is #included by the passes; the include dir is added via -I. +""" +import os +import subprocess +import sys +import struct + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SHADERS = os.path.join(ROOT, "src", "modules", "virtualgeometry", "shaders") + +STAGES = {".comp": "compute", ".vert": "vertex", ".frag": "fragment"} + + +def find_compiler(): + for name in ("glslc", "glslangValidator"): + path = subprocess.run(["which", name], capture_output=True, text=True).stdout.strip() + if path: + return name, path + return None, None + + +def compile_one(compiler, src, out_spv, stage): + inc = os.path.join(SHADERS) + if compiler == "glslc": + cmd = ["glslc", "-I", inc, "-fshader-stage=" + stage, "-o", out_spv, src] + else: + cmd = ["glslangValidator", "-I", inc, "-S", stage, "-o", out_spv, src] + res = subprocess.run(cmd, capture_output=True, text=True) + if res.returncode != 0: + print("FAIL", src) + print(res.stdout) + print(res.stderr) + return False + return True + + +def emit_inc(spv, inc, symbol): + with open(spv, "rb") as f: + data = f.read() + assert len(data) % 4 == 0, "SPIR-V size must be word-aligned" + words = struct.unpack("<%dI" % (len(data) // 4), data) + lines = ["// Generated by compile_virtualgeometry_shaders.py. Do not edit."] + lines.append("#pragma once") + lines.append("static const uint32_t %s[] = {" % symbol) + for i in range(0, len(words), 6): + chunk = words[i:i + 6] + lines.append(" " + ", ".join("0x%08x" % w for w in chunk) + ",") + lines.append("};") + lines.append("static const size_t %s_count = sizeof(%s) / sizeof(%s[0]);" % (symbol, symbol, symbol)) + with open(inc, "w") as f: + f.write("\n".join(lines) + "\n") + + +def main(): + compiler, _ = find_compiler() + if not compiler: + print("No GLSL compiler (glslc / glslangValidator) found on PATH.") + return 1 + + base_dir = sys.argv[1] if len(sys.argv) > 1 else SHADERS + for root, _, files in os.walk(base_dir): + for name in sorted(files): + ext = os.path.splitext(name)[1] + if ext not in STAGES or name == "vg_common.glsl": + continue + stem = os.path.splitext(name)[0] + src = os.path.join(root, name) + # Suffix with the stage so vg_resolve.vert and vg_resolve.frag don't collide. + spv = os.path.join(root, "%s_%s.spv" % (stem, STAGES[ext])) + inc = os.path.join(root, "%s_%s_spv.inc" % (stem, STAGES[ext])) + if not compile_one(compiler, src, spv, STAGES[ext]): + return 1 + emit_inc(spv, inc, "%s_%s_spv" % (stem, STAGES[ext])) + print("compiled", os.path.relpath(spv, ROOT)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index 8c16d54d..f49b4cd7 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -200,6 +200,7 @@ list(APPEND EVELIBS EVParticles EVMap EVGpgpu + EVVirtualGeometry EVModel3D EVFont EVThread @@ -221,7 +222,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") foreach(_eve_trimmed IN ITEMS EVAnimation EVAudio EVAvatar EVBuilding EVDatabase EVFont EVInventory EVMap EVModel3D EVNetwork EVParticles EVPlugins EVRPG EVSound EVStylize EVVoxel - EVDialogue EVProcgen EVSceneLoader) + EVDialogue EVProcgen EVSceneLoader EVVirtualGeometry) list(REMOVE_ITEM EVELIBS ${_eve_trimmed}) endforeach() endif() diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 58c631da..617836ec 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -154,6 +154,11 @@ endif() create_module(EVGpgpu gpgpu) target_include_directories(EVGpgpu BEFORE PRIVATE ${CMAKE_SOURCE_DIR}/external/VKBuilder/include) +if(NOT EVENGINE_WEBGPU_TRIM) + create_module(EVVirtualGeometry virtualgeometry) + target_include_directories(EVVirtualGeometry BEFORE PRIVATE + ${CMAKE_SOURCE_DIR}/external/VKBuilder/include) +endif() if(NOT EVENGINE_WEBGPU_TRIM) create_module(EVModel3D model3d) create_module(EVFont font) diff --git a/src/modules/virtualgeometry/Builder.cpp b/src/modules/virtualgeometry/Builder.cpp new file mode 100644 index 00000000..aad525a4 --- /dev/null +++ b/src/modules/virtualgeometry/Builder.cpp @@ -0,0 +1,446 @@ +#include "virtualgeometry/Builder.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eve::virtualgeometry { +namespace { + +constexpr std::uint32_t kNoParent = 0xFFFFFFFFu; + +struct Vec3 { + float x = 0.f, y = 0.f, z = 0.f; +}; + +// --------------------------------------------------------------------------- +// LOD0 meshletization: greedily grow connected clusters from seed triangles +// via a BFS over vertex adjacency, capped by per-cluster vertex/triangle counts. +// --------------------------------------------------------------------------- +struct Meshlet { + std::vector triangles; // global corner indices, count % 3 == 0 + std::vector verts; // unique global vertex indices +}; + +bool hasVertex(const Meshlet &m, std::uint32_t v) { + return std::find(m.verts.begin(), m.verts.end(), v) != m.verts.end(); +} + +bool canAdd(const Meshlet &m, const std::uint32_t corner[3], int maxVerts, int maxTris) { + if (static_cast(m.triangles.size() / 3) >= maxTris) return false; + int newVerts = 0; + for (int k = 0; k < 3; ++k) + if (!hasVertex(m, corner[k])) ++newVerts; + return static_cast(m.verts.size()) + newVerts <= maxVerts; +} + +void addTriangle(Meshlet &m, const std::uint32_t corner[3]) { + for (int k = 0; k < 3; ++k) + if (!hasVertex(m, corner[k])) m.verts.push_back(corner[k]); + for (int k = 0; k < 3; ++k) m.triangles.push_back(corner[k]); +} + +void meshletize(const std::vector &indices, int vertexCount, + const VirtualGeometryBuilder::Options &opt, std::vector &out) { + const int triCount = static_cast(indices.size() / 3); + if (triCount <= 0) return; + + std::vector> vtxTris(static_cast(vertexCount)); + for (int t = 0; t < triCount; ++t) + for (int k = 0; k < 3; ++k) vtxTris[indices[3 * t + k]].push_back(t); + + std::vector assigned(static_cast(triCount), 0); + + for (int seed = 0; seed < triCount; ++seed) { + if (assigned[seed]) continue; + + Meshlet m; + std::queue frontier; + frontier.push(seed); + const std::uint32_t seedCorner[3] = {indices[3 * seed], indices[3 * seed + 1], + indices[3 * seed + 2]}; + addTriangle(m, seedCorner); + assigned[seed] = 1; + for (int k = 0; k < 3; ++k) + for (int nb : vtxTris[seedCorner[k]]) + if (!assigned[nb]) frontier.push(nb); + + while (!frontier.empty() && + static_cast(m.triangles.size() / 3) < opt.maxTrianglesPerCluster) { + int t = frontier.front(); + frontier.pop(); + if (assigned[t]) continue; // already added to this cluster (frontier dupes) + const std::uint32_t corner[3] = {indices[3 * t], indices[3 * t + 1], indices[3 * t + 2]}; + if (!canAdd(m, corner, opt.maxVerticesPerCluster, opt.maxTrianglesPerCluster)) continue; + addTriangle(m, corner); + assigned[t] = 1; + for (int k = 0; k < 3; ++k) + for (int nb : vtxTris[corner[k]]) + if (!assigned[nb]) frontier.push(nb); + } + out.push_back(std::move(m)); + } +} + +void computeSphere(const std::vector &positions, const std::vector &verts, + float &cx, float &cy, float &cz, float &r) { + if (verts.empty()) { + cx = cy = cz = r = 0.f; + return; + } + double sx = 0, sy = 0, sz = 0; + for (std::uint32_t v : verts) { + sx += positions[3 * v]; + sy += positions[3 * v + 1]; + sz += positions[3 * v + 2]; + } + float n = static_cast(verts.size()); + cx = static_cast(sx) / n; + cy = static_cast(sy) / n; + cz = static_cast(sz) / n; + r = 0.f; + for (std::uint32_t v : verts) { + float dx = positions[3 * v] - cx, dy = positions[3 * v + 1] - cy, dz = positions[3 * v + 2] - cz; + r = std::max(r, std::sqrt(dx * dx + dy * dy + dz * dz)); + } +} + +// --------------------------------------------------------------------------- +// QEM (Quadric Error Metric) simplifier. Works on a triangle list in GLOBAL +// vertex indices; collapses edges into an optimal point, avoiding flips. +// Reports the max geometric error radius (sqrt of max quadric cost). +// --------------------------------------------------------------------------- +class QuadricSimplifier { +public: + QuadricSimplifier(const std::vector &positions, + const std::vector &inTris, + const std::vector &verts, + std::vector &outTris, float &outError) + : srcPos_(positions), outTris_(outTris), outError_(outError), verts_(verts) { + tris_ = inTris; + pos_ = positions; // mutable working copy for flip tests + initPos_ = positions; // original positions for the error metric + } + + void run(std::size_t targetTriangles) { + build(); + for (std::uint32_t v : verts_) alive_[v] = 1; + + std::size_t aliveTri = countAliveTriangles(); + double maxCost = 0.0; + const float kInf = std::numeric_limits::infinity(); + std::unordered_map blocked; // rejected edges + + while (aliveTri > targetTriangles) { + float bestCost = kInf; + std::uint32_t bestA = 0, bestB = 0; + float bestP[3] = {0, 0, 0}; + for (const auto &e : edges_) { + std::uint32_t a = e.first, b = e.second; + if (!alive_[a] || !alive_[b]) continue; + std::uint64_t key = (std::uint64_t(a) << 32) | b; + if (blocked.count(key)) continue; + float cost, p[3]; + collapseCost(a, b, cost, p); + if (cost < bestCost) { + bestCost = cost; + bestA = a; + bestB = b; + bestP[0] = p[0]; + bestP[1] = p[1]; + bestP[2] = p[2]; + } + } + if (bestCost == kInf) break; + if (!collapse(bestA, bestB, bestP, maxCost)) { + blocked[(std::uint64_t(bestA) << 32) | bestB] = 1; + continue; // skip this edge, try the next-best + } + aliveTri = countAliveTriangles(); + } + + outTris_.clear(); + for (std::size_t i = 0; i + 2 < tris_.size(); i += 3) { + std::uint32_t a = tris_[i], b = tris_[i + 1], c = tris_[i + 2]; + if (a != b && b != c && a != c) { + outTris_.push_back(a); + outTris_.push_back(b); + outTris_.push_back(c); + } + } + outError_ = std::sqrt(maxCost); + } + +private: + const std::vector &srcPos_; + std::vector pos_; + std::vector verts_; + std::vector tris_; + std::vector &outTris_; + float &outError_; + std::uint32_t maxV_ = 0; + std::vector alive_; + std::vector quadric_; + std::vector> triIncident_; + std::vector> edges_; + std::vector initPos_; + + // Squared quadric error of vertex v evaluated at its ORIGINAL position. + double evalQuadricAtInit(std::uint32_t v) const { + const double *Q = &quadric_[static_cast(v) * 10]; + double x = initPos_[3 * v], y = initPos_[3 * v + 1], z = initPos_[3 * v + 2]; + return Q[0] * x * x + 2 * Q[1] * x * y + 2 * Q[2] * x * z + 2 * Q[6] * x + + Q[3] * y * y + 2 * Q[4] * y * z + 2 * Q[7] * y + + Q[5] * z * z + 2 * Q[8] * z + Q[9]; + } + + void build() { + maxV_ = 0; + for (std::uint32_t v : verts_) maxV_ = std::max(maxV_, v); + alive_.assign(static_cast(maxV_) + 1, 0); + triIncident_.assign(static_cast(maxV_) + 1, {}); + quadric_.assign(static_cast(maxV_ + 1) * 10, 0.0); + std::unordered_map seen; + + for (std::size_t i = 0; i + 2 < tris_.size(); i += 3) { + std::uint32_t t[3] = {tris_[i], tris_[i + 1], tris_[i + 2]}; + Vec3 a{pos_[3 * t[0]], pos_[3 * t[0] + 1], pos_[3 * t[0] + 2]}; + Vec3 b{pos_[3 * t[1]], pos_[3 * t[1] + 1], pos_[3 * t[1] + 2]}; + Vec3 c{pos_[3 * t[2]], pos_[3 * t[2] + 1], pos_[3 * t[2] + 2]}; + Vec3 n{(b.y - a.y) * (c.z - a.z) - (b.z - a.z) * (c.y - a.y), + (b.z - a.z) * (c.x - a.x) - (b.x - a.x) * (c.z - a.z), + (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)}; + float nl = std::sqrt(n.x * n.x + n.y * n.y + n.z * n.z); + if (nl < 1e-12f) continue; + n.x /= nl; n.y /= nl; n.z /= nl; + float d = -(n.x * a.x + n.y * a.y + n.z * a.z); + double q[10] = {double(n.x) * n.x, double(n.x) * n.y, double(n.x) * n.z, + double(n.y) * n.y, double(n.y) * n.z, double(n.z) * n.z, + double(n.x) * d, double(n.y) * d, double(n.z) * d, + double(d) * d}; + for (int k = 0; k < 3; ++k) { + double *qv = &quadric_[static_cast(t[k]) * 10]; + for (int j = 0; j < 10; ++j) qv[j] += q[j]; + } + for (int k = 0; k < 3; ++k) { + triIncident_[t[k]].push_back(i / 3); + std::uint32_t u = t[k], v = t[(k + 1) % 3]; + if (u == v) continue; + std::uint64_t key = (std::uint64_t(std::min(u, v)) << 32) | std::max(u, v); + if (seen.try_emplace(key, 1).second) edges_.emplace_back(std::min(u, v), std::max(u, v)); + } + } + } + + void collapseCost(std::uint32_t a, std::uint32_t b, float &cost, float p[3]) const { + const double *qa = &quadric_[static_cast(a) * 10]; + const double *qb = &quadric_[static_cast(b) * 10]; + double Q[10]; + for (int j = 0; j < 10; ++j) Q[j] = qa[j] + qb[j]; + double A[9] = {Q[0], Q[1], Q[2], Q[1], Q[3], Q[4], Q[2], Q[4], Q[5]}; + double g[3] = {Q[6], Q[7], Q[8]}; + double det = A[0] * (A[4] * A[8] - A[5] * A[7]) - + A[1] * (A[3] * A[8] - A[5] * A[6]) + + A[2] * (A[3] * A[7] - A[4] * A[6]); + double x[3]; + if (std::fabs(det) > 1e-12) { + double inv = 1.0 / det; + double b1[3] = {-g[0], -g[1], -g[2]}; + x[0] = inv * ((A[4] * A[8] - A[5] * A[7]) * b1[0] + + (A[2] * A[7] - A[1] * A[8]) * b1[1] + + (A[1] * A[5] - A[2] * A[4]) * b1[2]); + x[1] = inv * ((A[5] * A[6] - A[3] * A[8]) * b1[0] + + (A[0] * A[8] - A[2] * A[6]) * b1[1] + + (A[2] * A[3] - A[0] * A[5]) * b1[2]); + x[2] = inv * ((A[3] * A[7] - A[4] * A[6]) * b1[0] + + (A[1] * A[6] - A[0] * A[7]) * b1[1] + + (A[0] * A[4] - A[1] * A[3]) * b1[2]); + } else { + x[0] = 0.5 * (pos_[3 * a] + pos_[3 * b]); + x[1] = 0.5 * (pos_[3 * a + 1] + pos_[3 * b + 1]); + x[2] = 0.5 * (pos_[3 * a + 2] + pos_[3 * b + 2]); + } + double cv = Q[0] * x[0] * x[0] + 2 * Q[1] * x[0] * x[1] + 2 * Q[2] * x[0] * x[2] + + 2 * Q[6] * x[0] + Q[3] * x[1] * x[1] + 2 * Q[4] * x[1] * x[2] + + 2 * Q[7] * x[1] + Q[5] * x[2] * x[2] + 2 * Q[8] * x[2] + Q[9]; + cost = static_cast(std::max(0.0, cv)); + p[0] = static_cast(x[0]); + p[1] = static_cast(x[1]); + p[2] = static_cast(x[2]); + } + + float signedArea(std::uint32_t a, std::uint32_t b, std::uint32_t c) const { + Vec3 pa{pos_[3 * a], pos_[3 * a + 1], pos_[3 * a + 2]}; + Vec3 pb{pos_[3 * b], pos_[3 * b + 1], pos_[3 * b + 2]}; + Vec3 pc{pos_[3 * c], pos_[3 * c + 1], pos_[3 * c + 2]}; + Vec3 n{(pb.y - pa.y) * (pc.z - pa.z) - (pb.z - pa.z) * (pc.y - pa.y), + (pb.z - pa.z) * (pc.x - pa.x) - (pb.x - pa.x) * (pc.z - pa.z), + (pb.x - pa.x) * (pc.y - pa.y) - (pb.y - pa.y) * (pc.x - pa.x)}; + return n.x + n.y + n.z; + } + + bool flips(std::size_t ti, std::uint32_t a, std::uint32_t b) const { + std::uint32_t t0 = tris_[ti * 3 + 0], t1 = tris_[ti * 3 + 1], t2 = tris_[ti * 3 + 2]; + const bool usesA = (t0 == a || t1 == a || t2 == a); + const bool usesB = (t0 == b || t1 == b || t2 == b); + // Triangles on the collapsed edge become degenerate and are removed; + // they must not block the collapse. + if (usesA && usesB) return false; + std::uint32_t s0 = (t0 == b) ? a : t0, s1 = (t1 == b) ? a : t1, s2 = (t2 == b) ? a : t2; + if (s0 == s1 || s1 == s2 || s0 == s2) return true; + float before = signedArea(t0, t1, t2); + float after = signedArea(s0, s1, s2); + if (before == 0.f) return true; + return (after * before) < 0.f; + } + + bool collapse(std::uint32_t a, std::uint32_t b, const float p[3], double &maxError) { + float ax = pos_[3 * a], ay = pos_[3 * a + 1], az = pos_[3 * a + 2]; + pos_[3 * a] = p[0]; pos_[3 * a + 1] = p[1]; pos_[3 * a + 2] = p[2]; + bool ok = true; + for (std::size_t ti : triIncident_[a]) if (flips(ti, a, b)) { ok = false; break; } + if (ok) + for (std::size_t ti : triIncident_[b]) if (flips(ti, a, b)) { ok = false; break; } + if (ok) { + for (auto &idx : tris_) if (idx == b) idx = a; + alive_[b] = 0; + // Merge b's quadric into a, then measure cumulative geometric error + // (max quadric error over a and b at their original positions). + double *qa = &quadric_[static_cast(a) * 10]; + const double *qb = &quadric_[static_cast(b) * 10]; + for (int j = 0; j < 10; ++j) qa[j] += qb[j]; + maxError = std::max(maxError, std::max(evalQuadricAtInit(a), evalQuadricAtInit(b))); + } else { + // Restore original position on rejection. + pos_[3 * a] = ax; pos_[3 * a + 1] = ay; pos_[3 * a + 2] = az; + } + return ok; + } + + std::size_t countAliveTriangles() const { + std::size_t n = 0; + for (std::size_t i = 0; i + 2 < tris_.size(); i += 3) { + std::uint32_t a = tris_[i], b = tris_[i + 1], c = tris_[i + 2]; + if (a != b && b != c && a != c) ++n; + } + return n; + } +}; + +} // namespace + +bool VirtualGeometryBuilder::build(const MeshInput &in, const Options &opt, + VirtualGeometryAsset &out) { + if (in.vertexCount <= 0 || !in.positions || !in.indices || in.indexCount < 3) return false; + + out = VirtualGeometryAsset(); + out.vertexCount = in.vertexCount; + out.positions.assign(in.positions, in.positions + 3 * static_cast(in.vertexCount)); + if (in.normals) + out.normals.assign(in.normals, in.normals + 3 * static_cast(in.vertexCount)); + + std::vector indices(in.indices, + in.indices + static_cast(in.indexCount)); + + // ---- Level 0: meshletize ---- + std::vector lod0; + meshletize(indices, in.vertexCount, opt, lod0); + if (lod0.empty()) return false; + + std::vector clusters; + clusters.reserve(lod0.size()); + for (auto &m : lod0) { + VgCluster c; + c.triStart = static_cast(out.triangles.size() / 3); // triangle units + c.triCount = static_cast(m.triangles.size() / 3); + c.lodLevel = 0; + c.errorR = 0.f; + c.parent = kNoParent; + c.vertCount = static_cast(m.verts.size()); + computeSphere(out.positions, m.verts, c.cx, c.cy, c.cz, c.r); + out.triangles.insert(out.triangles.end(), m.triangles.begin(), m.triangles.end()); + clusters.push_back(c); + } + + // ---- Higher LOD levels: merge + QEM simplify (cluster DAG) ---- + std::vector current = clusters; + std::size_t level = 0; + int totalLevels = 1; + const std::size_t mergeF = static_cast(opt.mergeFactor); + while (current.size() > 1 && totalLevels < opt.minLodLevels) { + ++level; + std::vector parents; + std::vector> simplifiedList; + + for (std::size_t g = 0; g < current.size(); g += mergeF) { + std::vector unionTris; + std::vector unionVerts; + std::vector seenV(static_cast(out.vertexCount), 0); + const std::size_t hi = std::min(current.size(), g + mergeF); + for (std::size_t k = g; k < hi; ++k) { + const VgCluster &ch = current[k]; + for (std::uint32_t i = 0; i < ch.triCount * 3; ++i) { + std::uint32_t v = out.triangles[static_cast(ch.triStart) * 3 + i]; + unionTris.push_back(v); + if (!seenV[v]) { seenV[v] = 1; unionVerts.push_back(v); } + } + } + + VgCluster parent; + parent.lodLevel = static_cast(level); + parent.parent = kNoParent; + parent.childCount = static_cast(hi - g); + + std::vector simplified; + float error = 0.f; + std::size_t target = std::max( + 1, static_cast(unionTris.size() / 3 * opt.lodTargetRatio)); + QuadricSimplifier qs(out.positions, unionTris, unionVerts, simplified, error); + qs.run(target); + parent.triCount = static_cast(simplified.size() / 3); + parent.errorR = error; + + std::vector used(static_cast(out.vertexCount), 0); + std::vector pverts; + for (std::uint32_t v : simplified) + if (!used[v]) { used[v] = 1; pverts.push_back(v); } + computeSphere(out.positions, pverts, parent.cx, parent.cy, parent.cz, parent.r); + parent.vertCount = static_cast(pverts.size()); + + parents.push_back(parent); + simplifiedList.push_back(std::move(simplified)); + } + + // Append simplified triangles; assign real triStart (triangle units). + for (std::size_t i = 0; i < parents.size(); ++i) { + parents[i].triStart = static_cast(out.triangles.size() / 3); + out.triangles.insert(out.triangles.end(), simplifiedList[i].begin(), + simplifiedList[i].end()); + } + // Wire child <-> parent. `current` are the last current.size() clusters, + // so global id of current[k] = baseId + k (parents not yet inserted). + const std::uint32_t baseId = static_cast(clusters.size() - current.size()); + for (std::size_t g = 0; g < current.size(); g += mergeF) { + std::size_t groupIndex = g / mergeF; + std::uint32_t pid = static_cast(clusters.size() + groupIndex); + std::size_t hi = std::min(current.size(), g + mergeF); + for (std::size_t k = g; k < hi; ++k) { + current[k].parent = pid; + parents[groupIndex].children[k - g] = baseId + static_cast(k); + } + } + clusters.insert(clusters.end(), parents.begin(), parents.end()); + current = parents; + ++totalLevels; + } + + out.clusters = std::move(clusters); + return !out.clusters.empty(); +} + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/Builder.h b/src/modules/virtualgeometry/Builder.h new file mode 100644 index 00000000..0371fb47 --- /dev/null +++ b/src/modules/virtualgeometry/Builder.h @@ -0,0 +1,43 @@ +#pragma once + +#include "virtualgeometry/VirtualGeometryAsset.h" + +#include + +namespace eve::virtualgeometry { + +/** + * CPU preprocessor that builds a Nanite-style hierarchical cluster DAG from a + * triangle mesh: + * + * 1. Meshletization (LOD0): greedily partition the mesh into connected + * clusters of ~maxTrianglesPerCluster triangles (cache-friendly). + * 2. Hierarchical LOD: iteratively merge clusters into parents and simplify + * each parent with a Quadric Error Metric (QEM) edge-collapse, building a + * cluster DAG with per-cluster bounding spheres + geometric error. + * + * Pure CPU, dependency-free, unit-testable. + */ +class VirtualGeometryBuilder { +public: + struct MeshInput { + int vertexCount = 0; + const float *positions = nullptr; // xyz packed + const float *normals = nullptr; // optional xyz packed + const std::uint32_t *indices = nullptr; // triangle list (count % 3 == 0) + int indexCount = 0; + }; + + struct Options { + int maxVerticesPerCluster = 64; // ~Nanite 128 tris / 64-96 verts + int maxTrianglesPerCluster = 124; + int minLodLevels = 4; // build at least this many levels + int mergeFactor = 4; // clusters merged into one parent + float lodTargetRatio = 0.5f; // parent keeps this fraction of triangles + }; + + /** Returns false on empty/invalid input. */ + bool build(const MeshInput &in, const Options &opt, VirtualGeometryAsset &out); +}; + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/LodSelection.cpp b/src/modules/virtualgeometry/LodSelection.cpp new file mode 100644 index 00000000..059526ee --- /dev/null +++ b/src/modules/virtualgeometry/LodSelection.cpp @@ -0,0 +1,41 @@ +#include "virtualgeometry/LodSelection.h" + +#include +#include + +namespace eve::virtualgeometry { + +int selectClusters(const VirtualGeometryAsset &asset, float dist, float projScale, float errorPx, + std::vector &outSelected) { + outSelected.clear(); + const std::vector &clusters = asset.clusters; + if (clusters.empty() || dist <= 0.f) return 0; + + const float d = std::max(dist, 1e-4f); + for (std::size_t cid = 0; cid < clusters.size(); ++cid) { + const VgCluster &c = clusters[cid]; + const float screenC = c.errorR * projScale / d; + const bool isLeaf = c.childCount == 0; + + // Parent's screen error; +inf for roots so the root can be selected. + float screenP = 1e30f; + if (c.parent != 0xFFFFFFFFu && c.parent < clusters.size()) + screenP = clusters[c.parent].errorR * projScale / d; + + const bool accept = (screenC <= errorPx || isLeaf) && (screenP > errorPx); + if (accept) outSelected.push_back(static_cast(cid)); + } + return static_cast(outSelected.size()); +} + +void lodHistogram(const VirtualGeometryAsset &asset, const std::vector &selected, + std::vector &perLevelCount) { + int maxLod = 0; + for (const auto &c : asset.clusters) maxLod = std::max(maxLod, static_cast(c.lodLevel)); + perLevelCount.assign(static_cast(maxLod + 1), 0); + for (std::uint32_t cid : selected) + if (cid < asset.clusters.size()) + ++perLevelCount[asset.clusters[cid].lodLevel]; +} + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/LodSelection.h b/src/modules/virtualgeometry/LodSelection.h new file mode 100644 index 00000000..7a05eebd --- /dev/null +++ b/src/modules/virtualgeometry/LodSelection.h @@ -0,0 +1,29 @@ +#pragma once + +#include "virtualgeometry/VirtualGeometryAsset.h" + +#include +#include + +namespace eve::virtualgeometry { + +/** + * CPU reference implementation of the GPU cluster-DAG LOD selection + * (mirrors shaders/vg_cull.comp). + * + * A cluster is selected iff it is the *coarsest* ancestor whose screen-space + * geometric error is within `errorPx` (or a leaf when no ancestor is fine + * enough). Combined with `screenP > errorPx`, this emits exactly one cluster per + * DAG branch (no overlaps, no gaps), so as the camera moves the visible LOD + * transitions continuously from fine (near) to coarse (far). + * + * screenError(cluster) = errorR * projScale / dist + */ +int selectClusters(const VirtualGeometryAsset &asset, float dist, float projScale, float errorPx, + std::vector &outSelected); + +/** Per-LOD-level counts of the selected clusters (size = maxLod+1). */ +void lodHistogram(const VirtualGeometryAsset &asset, const std::vector &selected, + std::vector &perLevelCount); + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/VirtualGeometry.cpp b/src/modules/virtualgeometry/VirtualGeometry.cpp new file mode 100644 index 00000000..b4cdd9a5 --- /dev/null +++ b/src/modules/virtualgeometry/VirtualGeometry.cpp @@ -0,0 +1,77 @@ +#include "virtualgeometry/VirtualGeometry.h" +#include "virtualgeometry/VirtualGeometryRenderer.h" + +#include "common/Exception.h" +#include "common/Module.h" +#include "graphics/Graphics.h" + +#include + +#include +#include + +namespace eve::virtualgeometry { +namespace { + +std::string currentGraphicsBackend() { + auto *gfx = eve::ModuleManager::getInstance("Graphics"); + if (!gfx) gfx = eve::graphics::Graphics::create(); + if (!gfx) return {}; + return gfx->getBackendName(); +} + +} // namespace + +Module_IMPL(VirtualGeometry, new VirtualGeometry()); + +bool VirtualGeometry::isAvailable() const { +#ifdef EVENGINE_WEBGPU + return false; // virtual geometry currently requires the Vulkan backend. +#else + return currentGraphicsBackend() == "vulkan"; +#endif +} + +VirtualGeometryRenderer *VirtualGeometry::newRenderer() { +#ifdef EVENGINE_WEBGPU + throw Exception("VirtualGeometry.newRenderer: requires vulkan Graphics backend"); +#else + if (currentGraphicsBackend() != "vulkan") + throw Exception("VirtualGeometry.newRenderer: requires vulkan Graphics backend"); + return new VirtualGeometryRenderer(); +#endif +} + +void VirtualGeometry::expose(ssq::Table &table) { + auto cls = table.addClass(name, VirtualGeometry::create, false); + expose(cls); + + auto renderer = table.addClass( + "VirtualGeometryRenderer", + std::function([]() -> VirtualGeometryRenderer * { + return nullptr; + }), + true); + renderer.addFunc("buildIcosphere", &VirtualGeometryRenderer::buildIcosphere); + renderer.addFunc("setViewport", &VirtualGeometryRenderer::setViewport); + renderer.addFunc("setCameraSimple", &VirtualGeometryRenderer::setCameraSimple); + renderer.addFunc("setModelYaw", &VirtualGeometryRenderer::setModelYaw); + renderer.addFunc("update", &VirtualGeometryRenderer::update); + renderer.addFunc("resolve", &VirtualGeometryRenderer::resolveByteData); + renderer.addFunc("getViewWidth", &VirtualGeometryRenderer::getViewWidth); + renderer.addFunc("getViewHeight", &VirtualGeometryRenderer::getViewHeight); + renderer.addFunc("isReady", &VirtualGeometryRenderer::isReady); + renderer.addFunc("getClusterCount", &VirtualGeometryRenderer::getClusterCount); + renderer.addFunc("getVisibleCount", &VirtualGeometryRenderer::getVisibleCount); + renderer.addFunc("getTotalTriangleCount", &VirtualGeometryRenderer::getTotalTriangleCount); + renderer.addFunc("getLodLevel", &VirtualGeometryRenderer::getLodLevel); + renderer.addFunc("getMaxLodLevel", &VirtualGeometryRenderer::getMaxLodLevel); +} + +void VirtualGeometry::expose(ssq::Class &cls) { + cls.addFunc("getName", &VirtualGeometry::getName); + cls.addFunc("isAvailable", &VirtualGeometry::isAvailable); + cls.addFunc("newRenderer", &VirtualGeometry::newRenderer); +} + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/VirtualGeometry.h b/src/modules/virtualgeometry/VirtualGeometry.h new file mode 100644 index 00000000..8bc8b797 --- /dev/null +++ b/src/modules/virtualgeometry/VirtualGeometry.h @@ -0,0 +1,38 @@ +#pragma once + +#include "common/Module.h" + +#include + +namespace ssq { +class Class; +class Table; +} + +namespace eve::virtualgeometry { + +class VirtualGeometryRenderer; + +/** + * Virtual-geometry module — Nanite-style virtualized geometry rendering on top + * of the active Graphics backend (Vulkan). See docs/dev/VIRTUAL_GEOMETRY.md. + * + * Script: + * vg <- eve.VirtualGeometry() + * r <- vg.newRenderer() + * r.buildIcosphere(4) + */ +class VirtualGeometry : public Module { +public: + Module_REG(VirtualGeometry); + VirtualGeometry() = default; + ~VirtualGeometry() override = default; + + /** True when the active Graphics backend can run virtual geometry. */ + bool isAvailable() const; + + /** Create a virtual-geometry renderer (holds GPU buffers). */ + VirtualGeometryRenderer *newRenderer(); +}; + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/VirtualGeometryAsset.h b/src/modules/virtualgeometry/VirtualGeometryAsset.h new file mode 100644 index 00000000..c6b2bbce --- /dev/null +++ b/src/modules/virtualgeometry/VirtualGeometryAsset.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +namespace eve::virtualgeometry { + +/** + * GPU-mirrored cluster node layout (std430, matches shaders/vg_*.comp). + * Exactly 4 x uvec4 per cluster: + * + * [0] bounds = { f32x(cx), f32x(cy), f32x(cz), f32x(r) } + * [1] ranges = { triStart, triCount, lodLevel, parent } + * [2] err = { f32x(errorR), f32x(errorRScreen), childCount, 0 } + * [3] children = { child0, child1, child2, child3 } + */ +struct VgGpuCluster { + std::uint32_t u0[4]; + std::uint32_t u1[4]; + std::uint32_t u2[4]; + std::uint32_t u3[4]; +}; + +/** CPU-side processed cluster (before packing into VgGpuCluster). */ +struct VgCluster { + float cx = 0.f, cy = 0.f, cz = 0.f, r = 0.f; // bounding sphere + std::uint32_t triStart = 0, triCount = 0; // global triangle stream range + std::uint32_t vertStart = 0, vertCount = 0; // global position range used + std::uint32_t lodLevel = 0; + float errorR = 0.f; // world-space geometric error radius + float errorRScreen = 0.f; // screen-space error constant (precomputed) + std::uint32_t parent = 0xFFFFFFFFu; // 0xFFFFFFFF = root + std::uint32_t children[4] = {0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu}; + std::uint32_t childCount = 0; +}; + +/** + * Fully processed virtual-geometry asset (CPU representation). + * Produced by Builder from a Mesh; uploaded to GPU SSBOs by the backend. + */ +struct VirtualGeometryAsset { + int vertexCount = 0; + std::vector positions; // xyz packed, size = 3 * vertexCount + std::vector normals; // optional xyz packed + + /** Flat global triangle stream; each entry is an index into positions. */ + std::vector triangles; + + std::vector clusters; + + // Precomputed projection-dependent constant cache (recomputed on setCamera). + float lastErrorScale = 1.f; + + int totalTriangles() const { return static_cast(triangles.size() / 3); } +}; + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/VirtualGeometryBackend.h b/src/modules/virtualgeometry/VirtualGeometryBackend.h new file mode 100644 index 00000000..874b83c6 --- /dev/null +++ b/src/modules/virtualgeometry/VirtualGeometryBackend.h @@ -0,0 +1,42 @@ +#pragma once + +#include "virtualgeometry/VirtualGeometryAsset.h" + +#include +#include + +namespace eve::virtualgeometry { + +struct VgUniforms { + float viewProj[16]; + float model[16]; + float cameraPos[4]; + float params[4]; // x=viewW, y=viewH, z=projScale, w=errorPx + float frustum[24]; // 6 planes + float misc[4]; // x=clusterCount +}; + +/** + * Opaque backend state for one virtual-geometry renderer. Owned by + * VirtualGeometryRenderer; allocated/freed by the active backend. + */ +struct VgBackend { + void *state = nullptr; // vulkan::VgState* +}; + +// ---- backend interface (implemented in vulkan/) ---- +void vgCreate(VgBackend &be); +void vgDestroy(VgBackend &be); + +// Upload a built asset (positions/triangles/clusters) to the GPU. +void vgUpload(VgBackend &be, const VirtualGeometryAsset &asset); +void vgUploadUniforms(VgBackend &be, const VgUniforms &u); +void vgReset(VgBackend &be, int visibleCapacity); + +// Dispatch cull + raster for the given workgroup counts. Returns visible count. +int vgUpdate(VgBackend &be, int clusterCount, int visibleCapacity, int viewW, int viewH); + +// Read the visibility buffer back to CPU (packed depth<<16|clusterId). +bool vgReadPixels(VgBackend &be, std::vector &out); + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/VirtualGeometryRenderer.cpp b/src/modules/virtualgeometry/VirtualGeometryRenderer.cpp new file mode 100644 index 00000000..c8d23364 --- /dev/null +++ b/src/modules/virtualgeometry/VirtualGeometryRenderer.cpp @@ -0,0 +1,251 @@ +#include "virtualgeometry/VirtualGeometryRenderer.h" +#include "virtualgeometry/Builder.h" + +#include "common/Exception.h" +#include "data/ByteData.h" + +#include +#include +#include +#include + +namespace eve::virtualgeometry { +namespace { + +// Minimal column-major mat4 helpers (GLM is not linked into this module). +struct Mat4 { + float m[16]; + static Mat4 identity() { + Mat4 r{}; + r.m[0] = r.m[5] = r.m[10] = r.m[15] = 1.f; + return r; + } + static Mat4 mul(const Mat4 &a, const Mat4 &b) { + Mat4 r{}; + for (int c = 0; c < 4; ++c) + for (int row = 0; row < 4; ++row) { + float sum = 0; + for (int k = 0; k < 4; ++k) sum += a.m[k * 4 + row] * b.m[c * 4 + k]; + r.m[c * 4 + row] = sum; + } + return r; + } +}; + +} // namespace + +VirtualGeometryRenderer::VirtualGeometryRenderer() { vgCreate(backend_); } + +VirtualGeometryRenderer::~VirtualGeometryRenderer() { vgDestroy(backend_); } + +bool VirtualGeometryRenderer::build(const VirtualGeometryBuilder::MeshInput &in) { + VirtualGeometryBuilder builder; + bool ok = builder.build(in, builderOptions_, asset_); + if (!ok) return false; + vgReset(backend_, visibleCapacity_); // size visible/stats before upload + vgUpload(backend_, asset_); + return true; +} + +bool VirtualGeometryRenderer::build(const float *positions, int vertexCount, + const std::uint32_t *indices, int indexCount) { + VirtualGeometryBuilder::MeshInput in; + in.vertexCount = vertexCount; + in.positions = positions; + in.indices = indices; + in.indexCount = indexCount; + return build(in); +} + +namespace { +// Build a unit icosphere (triangulated) into CPU buffers. +void buildIco(std::vector &positions, std::vector &indices, int subdiv) { + positions.clear(); + indices.clear(); + struct F { int a, b, c; }; + auto addVtx = [&](float x, float y, float z) { + float l = std::sqrt(x * x + y * y + z * z); + positions.push_back(x / l); + positions.push_back(y / l); + positions.push_back(z / l); + return static_cast(positions.size() / 3) - 1; + }; + auto tri = [&](int a, int b, int c) { + indices.push_back(static_cast(a)); + indices.push_back(static_cast(b)); + indices.push_back(static_cast(c)); + }; + auto mid = [&](int i0, int i1) { + return addVtx(0.5f * (positions[3 * i0] + positions[3 * i1]), + 0.5f * (positions[3 * i0 + 1] + positions[3 * i1 + 1]), + 0.5f * (positions[3 * i0 + 2] + positions[3 * i1 + 2])); + }; + const float t = (1.f + std::sqrt(5.f)) * 0.5f; + std::vector> base = { + {-1, t, 0}, {1, t, 0}, {-1, -t, 0}, {1, -t, 0}, {0, -1, t}, {0, 1, t}, + {0, -1, -t}, {0, 1, -t}, {t, 0, -1}, {t, 0, 1}, {-t, 0, -1}, {-t, 0, 1}}; + int idx[12]; + for (int i = 0; i < 12; ++i) idx[i] = addVtx(base[i][0], base[i][1], base[i][2]); + int faces[20][3] = {{0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11}, + {1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8}, + {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9}, + {4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1}}; + std::vector cur; + for (auto &f : faces) cur.push_back({idx[f[0]], idx[f[1]], idx[f[2]]}); + for (int d = 0; d < subdiv; ++d) { + std::vector nf; + for (auto &x : cur) { + int ab = mid(x.a, x.b), ac = mid(x.a, x.c), bc = mid(x.b, x.c); + nf.push_back({x.a, ab, ac}); + nf.push_back({ab, x.b, bc}); + nf.push_back({ac, bc, x.c}); + nf.push_back({ab, bc, ac}); + } + cur.swap(nf); + } + for (auto &x : cur) tri(x.a, x.b, x.c); +} +} // namespace + +bool VirtualGeometryRenderer::buildIcosphere(int subdiv) { + std::vector pos; + std::vector idx; + buildIco(pos, idx, std::clamp(subdiv, 1, 6)); + return build(pos.data(), static_cast(pos.size() / 3), idx.data(), + static_cast(idx.size())); +} + +void VirtualGeometryRenderer::setViewport(int width, int height, float fovYDeg, float errorPx) { + width_ = std::max(1, width); + height_ = std::max(1, height); + uniforms_.params[0] = static_cast(width_); + uniforms_.params[1] = static_cast(height_); + uniforms_.params[2] = static_cast(height_) / + (2.f * std::tan(fovYDeg * 3.14159265358979323846f / 360.f)); + uniforms_.params[3] = errorPx; +} + +void VirtualGeometryRenderer::setCamera(const float view[16], const float proj[16], + const float model[16], const float camPos[3]) { + // Combined view-projection: clip = P * V * model * pos. The shader uses + // vgU.viewProj * (vgU.model * pos), so viewProj = P * V. + Mat4 p; + for (int i = 0; i < 16; ++i) p.m[i] = proj[i]; + Mat4 v; + for (int i = 0; i < 16; ++i) v.m[i] = view[i]; + Mat4 vp = Mat4::mul(p, v); + for (int i = 0; i < 16; ++i) uniforms_.viewProj[i] = vp.m[i]; + for (int i = 0; i < 16; ++i) uniforms_.model[i] = model ? model[i] : Mat4::identity().m[i]; + uniforms_.cameraPos[0] = camPos[0]; + uniforms_.cameraPos[1] = camPos[1]; + uniforms_.cameraPos[2] = camPos[2]; + uniforms_.cameraPos[3] = 0.f; + updateUniforms(); +} + +void VirtualGeometryRenderer::setCameraSimple(float camX, float camY, float camZ, float nearZ, + float farZ) { + const float fovY = (uniforms_.params[2] > 0.f) + ? 2.f * std::atan(static_cast(height_) / (2.f * uniforms_.params[2])) + : 60.f * 3.14159265358979323846f / 180.f; + // Identity view (looking down -Z). + Mat4 view = Mat4::identity(); + // Perspective projection (column-major, D3D/Vulkan clip z in [0,1]). + Mat4 proj{}; + const float a = 1.f / std::tan(fovY * 0.5f); + const float aspect = static_cast(width_) / static_cast(height_); + proj.m[0] = a / aspect; + proj.m[5] = a; + proj.m[10] = farZ / (nearZ - farZ); + proj.m[11] = -1.f; + proj.m[14] = (nearZ * farZ) / (nearZ - farZ); + const float camPos[3] = {camX, camY, camZ}; + setCamera(view.m, proj.m, nullptr, camPos); +} + +void VirtualGeometryRenderer::setModelYaw(float yaw) { modelYaw_ = yaw; } + +void VirtualGeometryRenderer::updateUniforms() { + uniforms_.misc[0] = static_cast(static_cast(asset_.clusters.size())); + if (modelYaw_ != 0.f) { + // Build a Y-rotation model matrix (column-major) and override. + float c = std::cos(modelYaw_), s = std::sin(modelYaw_); + Mat4 r = Mat4::identity(); + r.m[0] = c; r.m[2] = s; + r.m[8] = -s; r.m[10] = c; + for (int i = 0; i < 16; ++i) uniforms_.model[i] = r.m[i]; + } + // Recompute per-cluster screen-space error constants (errorR * projScale). + for (auto &c : asset_.clusters) c.errorRScreen = c.errorR * uniforms_.params[2]; + vgUploadUniforms(backend_, uniforms_); +} + +int VirtualGeometryRenderer::update() { + int clusters = static_cast(asset_.clusters.size()); + if (clusters <= 0 || !backend_.state) return 0; + lastVisible_ = vgUpdate(backend_, clusters, visibleCapacity_, width_, height_); + return lastVisible_; +} + +bool VirtualGeometryRenderer::resolve(unsigned char *outRgba, int &outW, int &outH) { + std::vector pix; + if (!vgReadPixels(backend_, pix)) return false; + outW = width_; + outH = height_; + const int n = outW * outH; + for (int i = 0; i < n; ++i) { + std::uint32_t packed = pix[i]; + std::uint32_t depth = packed >> 16; + std::uint32_t cid = packed & 0xFFFFu; + if (cid == 0u && depth == 0xFFFFu) { + outRgba[4 * i + 0] = 4; + outRgba[4 * i + 1] = 6; + outRgba[4 * i + 2] = 12; + outRgba[4 * i + 3] = 255; + continue; + } + // Stable per-cluster color hash. + std::uint32_t h = cid * 2654435761u; + float cr = (h >> 16) & 0xFFu, cg = (h >> 8) & 0xFFu, cb = h & 0xFFu; + float mx = std::max({cr, cg, cb, 1.f}); + float depthF = static_cast(depth) / 65535.f; + float shade = 0.35f + 0.65f * (1.f - depthF); + outRgba[4 * i + 0] = static_cast(cr / mx * 255.f * shade); + outRgba[4 * i + 1] = static_cast(cg / mx * 255.f * shade); + outRgba[4 * i + 2] = static_cast(cb / mx * 255.f * shade); + outRgba[4 * i + 3] = 255; + } + return true; +} + +eve::data::ByteData *VirtualGeometryRenderer::resolveByteData() { + int w = 1, h = 1; + auto *bd = new eve::data::ByteData(static_cast(width_) * height_ * 4); + if (!bd) return nullptr; + if (!resolve(static_cast(bd->getData()), w, h)) { + delete bd; + return nullptr; + } + return bd; +} + +int VirtualGeometryRenderer::getClusterCount() const { + return static_cast(asset_.clusters.size()); +} + +int VirtualGeometryRenderer::getTotalTriangleCount() const { + return asset_.totalTriangles(); +} + +int VirtualGeometryRenderer::getLodLevel(int clusterId) const { + if (clusterId < 0 || static_cast(clusterId) >= asset_.clusters.size()) return -1; + return static_cast(asset_.clusters[clusterId].lodLevel); +} + +int VirtualGeometryRenderer::getMaxLodLevel() const { + int m = 0; + for (const auto &c : asset_.clusters) m = std::max(m, static_cast(c.lodLevel)); + return m; +} + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/VirtualGeometryRenderer.h b/src/modules/virtualgeometry/VirtualGeometryRenderer.h new file mode 100644 index 00000000..bdb9b9a8 --- /dev/null +++ b/src/modules/virtualgeometry/VirtualGeometryRenderer.h @@ -0,0 +1,83 @@ +#pragma once + +#include "virtualgeometry/VirtualGeometryAsset.h" +#include "virtualgeometry/VirtualGeometryBackend.h" +#include "virtualgeometry/Builder.h" + +#include +#include + +namespace eve::data { +class ByteData; +} + +namespace eve::virtualgeometry { + +/** + * A virtual-geometry renderer: preprocesses a mesh into a cluster DAG, uploads + * it to the GPU, then each frame runs GPU-driven culling + a software rasterizer + * into a visibility buffer. + * + * Lifecycle (Squirrel): + * r <- vg.newRenderer() + * r.buildIcosphere(4) (or build(...) with raw arrays) + * r.setViewport(w, h, fovYDeg, errorPx) + * r.setCamera(view[16], proj[16], model[16], cam[3]) + * visible <- r.update() + * ok <- r.resolve(rgbaArray, w, h) // CPU readback of the visibility buffer + */ +class VirtualGeometryRenderer { +public: + VirtualGeometryRenderer(); + ~VirtualGeometryRenderer(); + + VirtualGeometryRenderer(const VirtualGeometryRenderer &) = delete; + VirtualGeometryRenderer &operator=(const VirtualGeometryRenderer &) = delete; + + bool isReady() const { return backend_.state != nullptr; } + + // ---- build (preprocess + upload) ---- + bool build(const float *positions, int vertexCount, const std::uint32_t *indices, int indexCount); + bool build(const VirtualGeometryBuilder::MeshInput &in); + /** Convenience: procedural unit icosphere, `subdiv` subdivision levels. */ + bool buildIcosphere(int subdiv); + + // ---- per-frame ---- + void setViewport(int width, int height, float fovYDeg, float errorPx = 1.0f); + void setCamera(const float view[16], const float proj[16], const float model[16], + const float camPos[3]); + /** Script-friendly: identity view (camera looking down -Z) + perspective. */ + void setCameraSimple(float camX, float camY, float camZ, float nearZ = 0.1f, float farZ = 100.f); + /** Spin the virtualized model about Y by `yaw` radians each frame. */ + void setModelYaw(float yaw); + /** Run cull + raster; returns the number of visible clusters. */ + int update(); + /** Resolve the visibility buffer to RGBA (w*h*4 bytes). */ + bool resolve(unsigned char *outRgba, int &outW, int &outH); + /** Resolve into a heap ByteData (RGBA) for scripting/display. */ + eve::data::ByteData *resolveByteData(); + int getViewWidth() const { return width_; } + int getViewHeight() const { return height_; } + + // ---- stats ---- + int getClusterCount() const; + int getVisibleCount() const { return lastVisible_; } + int getTotalTriangleCount() const; + int getLodLevel(int clusterId) const; + int getMaxLodLevel() const; + +private: + void buildIcosphereInternal(int subdiv); + void updateUniforms(); + + VgBackend backend_; + VirtualGeometryAsset asset_; + VirtualGeometryBuilder::Options builderOptions_{}; + VgUniforms uniforms_{}; + int width_ = 1, height_ = 1; + int lastVisible_ = 0; + int visibleCapacity_ = 4096; + float modelYaw_ = 0.f; +}; + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/shaders/vg_common.glsl b/src/modules/virtualgeometry/shaders/vg_common.glsl new file mode 100644 index 00000000..dfc5328a --- /dev/null +++ b/src/modules/virtualgeometry/shaders/vg_common.glsl @@ -0,0 +1,59 @@ +#ifndef VG_COMMON_GLSL +#define VG_COMMON_GLSL + +// Shared layout for the virtual-geometry compute passes. Every pass is a GLSL +// 4.50 compute shader bound through the gpgpu backend (set 0, storage buffers). +// Bindings must stay in sync with virtualgeometry/vulkan/VulkanVirtualGeometry.cpp. + +#define VG_BIND_POSITIONS 0u +#define VG_BIND_TRIANGLES 1u +#define VG_BIND_CLUSTERS 2u +#define VG_BIND_VISIBLE 3u +#define VG_BIND_PIXELS 4u +#define VG_BIND_UNIFORMS 5u +#define VG_BIND_STATS 6u + +// Max triangles any cluster may hold. Must match +// VirtualGeometryBuilder::Options::maxTrianglesPerCluster. +#define VG_MAX_TRI_PER_CLUSTER 124u + +// Pack the depth (high 16 bits, 0 = near) and cluster id (low 16 bits) into a +// single uint so a single atomicMin yields a consistent (depth, cluster) pair. +uint vg_pack(uint depthBits, uint clusterId) { + return (min(depthBits, 0xFFFFu) << 16) | (clusterId & 0xFFFFu); +} +uint vg_depthOf(uint packed) { return packed >> 16; } +uint vg_clusterOf(uint packed){ return packed & 0xFFFFu; } + +struct VgUniforms { + mat4 viewProj; // [0..15] + mat4 model; // [16..31] + vec4 cameraPos; // [32..35] xyz = camera, w = 0 + vec4 params; // [36..39] x=viewW, y=viewH, z=projScale, w=errorPx + vec4 frustum[6]; // [40..63] world-space planes (xyz normal, w distance) + vec4 misc; // [64..67] x = clusterCount +}; + +layout(set = 0, binding = VG_BIND_POSITIONS, std430) readonly buffer VgPositions { float vgPos[]; }; +layout(set = 0, binding = VG_BIND_TRIANGLES, std430) readonly buffer VgTriangles { uint vgTri[]; }; +layout(set = 0, binding = VG_BIND_CLUSTERS, std430) readonly buffer VgClusters { uvec4 vgCl[]; }; +layout(set = 0, binding = VG_BIND_VISIBLE, std430) buffer VgVisible { uint vgCounter; uint vgVis[]; }; +layout(set = 0, binding = VG_BIND_PIXELS, std430) buffer VgPixels { uint vgPix[]; }; +layout(set = 0, binding = VG_BIND_UNIFORMS, std430) readonly buffer VgUniformBlock{ VgUniforms vgU; }; +layout(set = 0, binding = VG_BIND_STATS, std430) buffer VgStats { uvec4 vgStat[]; }; + +vec3 vg_clusterCenter(uint cid) { + uint b = cid * 4u; + return vec3(uintBitsToFloat(vgCl[b + 0u].x), + uintBitsToFloat(vgCl[b + 0u].y), + uintBitsToFloat(vgCl[b + 0u].z)); +} +float vg_clusterRadius(uint cid) { return uintBitsToFloat(vgCl[cid * 4u + 0u].w); } +uint vg_clusterTriStart(uint cid) { return vgCl[cid * 4u + 1u].x; } +uint vg_clusterTriCount(uint cid) { return vgCl[cid * 4u + 1u].y; } +uint vg_clusterParent(uint cid) { return vgCl[cid * 4u + 1u].w; } +float vg_clusterErrorR(uint cid) { return uintBitsToFloat(vgCl[cid * 4u + 2u].x); } +float vg_clusterErrorRS(uint cid) { return uintBitsToFloat(vgCl[cid * 4u + 2u].y); } +uint vg_clusterChildCount(uint cid) { return vgCl[cid * 4u + 2u].z; } + +#endif diff --git a/src/modules/virtualgeometry/shaders/vg_cull.comp b/src/modules/virtualgeometry/shaders/vg_cull.comp new file mode 100644 index 00000000..3c1b8410 --- /dev/null +++ b/src/modules/virtualgeometry/shaders/vg_cull.comp @@ -0,0 +1,49 @@ +// Pass A — GPU-driven cluster-DAG visibility culling (Nanite two-pass style). +// +// One invocation per cluster. Emits a cluster into the visible list iff it is +// the coarsest ancestor that satisfies the screen-space error threshold (or a +// leaf), exactly reproducing a top-down DAG traversal with one thread/cluster: +// +// accept = (screenErr(cluster) <= threshold || isLeaf) && screenErr(parent) > threshold +// +// Combined with view-frustum culling against a world-space sphere. +#version 450 + +#include "vg_common.glsl" + +layout(local_size_x = 64) in; + +void main() { + uint cid = gl_GlobalInvocationID.x; + if (cid >= vgU.misc.x) return; // clusterCount + + vec3 center = vg_clusterCenter(cid); + float r = vg_clusterRadius(cid); + vec4 wc4 = vgU.model * vec4(center, 1.0); + vec3 wc = wc4.xyz / max(wc4.w, 1e-6); + vec3 toCam = wc - vgU.cameraPos.xyz; + float dist = max(length(toCam), 1e-4); + + // Frustum culling (sphere vs world-space planes). + for (int i = 0; i < 6; ++i) { + if (dot(vgU.frustum[i].xyz, wc) + vgU.frustum[i].w < -r) return; + } + + // Screen-space error (pre-scaled by projScale on the CPU). + float screenC = vg_clusterErrorRS(cid) / dist; + float screenP = 1e30; + uint parent = vg_clusterParent(cid); + if (parent != 0xFFFFFFFFu) { + vec4 pc = vgU.model * vec4(vg_clusterCenter(parent), 1.0); + float pd = max(length(pc.xyz - vgU.cameraPos.xyz), 1e-4); + screenP = vg_clusterErrorRS(parent) / pd; + } + + bool isLeaf = vg_clusterChildCount(cid) == 0u; + bool accept = (screenC <= vgU.params.w || isLeaf) && (screenP > vgU.params.w); + if (!accept) return; + + uint slot = atomicAdd(vgCounter, 1u); + vgVis[slot] = cid; + atomicAdd(vgStat[0].x, 1u); // visible cluster count +} diff --git a/src/modules/virtualgeometry/shaders/vg_cull_compute.spv b/src/modules/virtualgeometry/shaders/vg_cull_compute.spv new file mode 100644 index 00000000..10f83f2a Binary files /dev/null and b/src/modules/virtualgeometry/shaders/vg_cull_compute.spv differ diff --git a/src/modules/virtualgeometry/shaders/vg_cull_compute_spv.inc b/src/modules/virtualgeometry/shaders/vg_cull_compute_spv.inc new file mode 100644 index 00000000..158c0f42 --- /dev/null +++ b/src/modules/virtualgeometry/shaders/vg_cull_compute_spv.inc @@ -0,0 +1,325 @@ +// Generated by compile_virtualgeometry_shaders.py. Do not edit. +#pragma once +static const uint32_t vg_cull_compute_spv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x00000122, 0x00000000, 0x00020011, + 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, + 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0006000f, 0x00000005, + 0x00000004, 0x6e69616d, 0x00000000, 0x0000005f, 0x00060010, 0x00000004, + 0x00000011, 0x00000040, 0x00000001, 0x00000001, 0x00030003, 0x00000002, + 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, + 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004, + 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, + 0x00657669, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00080005, + 0x0000000c, 0x635f6776, 0x7473756c, 0x65437265, 0x7265746e, 0x3b317528, + 0x00000000, 0x00030005, 0x0000000b, 0x00646963, 0x00080005, 0x00000010, + 0x635f6776, 0x7473756c, 0x61527265, 0x73756964, 0x3b317528, 0x00000000, + 0x00030005, 0x0000000f, 0x00646963, 0x00080005, 0x00000014, 0x635f6776, + 0x7473756c, 0x61507265, 0x746e6572, 0x3b317528, 0x00000000, 0x00030005, + 0x00000013, 0x00646963, 0x00080005, 0x00000017, 0x635f6776, 0x7473756c, + 0x72457265, 0x52726f72, 0x31752853, 0x0000003b, 0x00030005, 0x00000016, + 0x00646963, 0x00090005, 0x0000001a, 0x635f6776, 0x7473756c, 0x68437265, + 0x43646c69, 0x746e756f, 0x3b317528, 0x00000000, 0x00030005, 0x00000019, + 0x00646963, 0x00030005, 0x0000001c, 0x00000062, 0x00050005, 0x00000022, + 0x6c436756, 0x65747375, 0x00007372, 0x00050006, 0x00000022, 0x00000000, + 0x6c436776, 0x00000000, 0x00030005, 0x00000024, 0x00000000, 0x00030005, + 0x0000005c, 0x00646963, 0x00080005, 0x0000005f, 0x475f6c67, 0x61626f6c, + 0x766e496c, 0x7461636f, 0x496e6f69, 0x00000044, 0x00050005, 0x00000069, + 0x6e556756, 0x726f6669, 0x0000736d, 0x00060006, 0x00000069, 0x00000000, + 0x77656976, 0x6a6f7250, 0x00000000, 0x00050006, 0x00000069, 0x00000001, + 0x65646f6d, 0x0000006c, 0x00060006, 0x00000069, 0x00000002, 0x656d6163, + 0x6f506172, 0x00000073, 0x00050006, 0x00000069, 0x00000003, 0x61726170, + 0x0000736d, 0x00050006, 0x00000069, 0x00000004, 0x73757266, 0x006d7574, + 0x00050006, 0x00000069, 0x00000005, 0x6373696d, 0x00000000, 0x00060005, + 0x0000006a, 0x6e556756, 0x726f6669, 0x6f6c426d, 0x00006b63, 0x00040006, + 0x0000006a, 0x00000000, 0x00556776, 0x00030005, 0x0000006c, 0x00000000, + 0x00040005, 0x00000077, 0x746e6563, 0x00007265, 0x00040005, 0x00000078, + 0x61726170, 0x0000006d, 0x00030005, 0x0000007c, 0x00000072, 0x00040005, + 0x0000007d, 0x61726170, 0x0000006d, 0x00030005, 0x00000081, 0x00346377, + 0x00030005, 0x0000008d, 0x00006377, 0x00040005, 0x00000096, 0x61436f74, + 0x0000006d, 0x00040005, 0x0000009e, 0x74736964, 0x00000000, 0x00030005, + 0x000000a4, 0x00000069, 0x00040005, 0x000000c0, 0x65726373, 0x00436e65, + 0x00040005, 0x000000c1, 0x61726170, 0x0000006d, 0x00040005, 0x000000c6, + 0x65726373, 0x00506e65, 0x00040005, 0x000000c8, 0x65726170, 0x0000746e, + 0x00040005, 0x000000c9, 0x61726170, 0x0000006d, 0x00030005, 0x000000d1, + 0x00006370, 0x00040005, 0x000000d4, 0x61726170, 0x0000006d, 0x00030005, + 0x000000dc, 0x00006470, 0x00040005, 0x000000e5, 0x61726170, 0x0000006d, + 0x00040005, 0x000000eb, 0x654c7369, 0x00006661, 0x00040005, 0x000000ec, + 0x61726170, 0x0000006d, 0x00040005, 0x000000f0, 0x65636361, 0x00007470, + 0x00040005, 0x00000104, 0x746f6c73, 0x00000000, 0x00050005, 0x00000106, + 0x69566756, 0x6c626973, 0x00000065, 0x00060006, 0x00000106, 0x00000000, + 0x6f436776, 0x65746e75, 0x00000072, 0x00050006, 0x00000106, 0x00000001, + 0x69566776, 0x00000073, 0x00030005, 0x00000108, 0x00000000, 0x00040005, + 0x0000010f, 0x74536756, 0x00737461, 0x00050006, 0x0000010f, 0x00000000, + 0x74536776, 0x00007461, 0x00030005, 0x00000111, 0x00000000, 0x00050005, + 0x00000115, 0x6f506756, 0x69746973, 0x00736e6f, 0x00050006, 0x00000115, + 0x00000000, 0x6f506776, 0x00000073, 0x00030005, 0x00000117, 0x00000000, + 0x00050005, 0x00000119, 0x72546756, 0x676e6169, 0x0073656c, 0x00050006, + 0x00000119, 0x00000000, 0x72546776, 0x00000069, 0x00030005, 0x0000011b, + 0x00000000, 0x00050005, 0x0000011d, 0x69506756, 0x736c6578, 0x00000000, + 0x00050006, 0x0000011d, 0x00000000, 0x69506776, 0x00000078, 0x00030005, + 0x0000011f, 0x00000000, 0x00040047, 0x00000021, 0x00000006, 0x00000010, + 0x00030047, 0x00000022, 0x00000003, 0x00040048, 0x00000022, 0x00000000, + 0x00000018, 0x00050048, 0x00000022, 0x00000000, 0x00000023, 0x00000000, + 0x00030047, 0x00000024, 0x00000018, 0x00040047, 0x00000024, 0x00000021, + 0x00000002, 0x00040047, 0x00000024, 0x00000022, 0x00000000, 0x00040047, + 0x0000005f, 0x0000000b, 0x0000001c, 0x00040047, 0x00000068, 0x00000006, + 0x00000010, 0x00040048, 0x00000069, 0x00000000, 0x00000005, 0x00050048, + 0x00000069, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000069, + 0x00000000, 0x00000023, 0x00000000, 0x00040048, 0x00000069, 0x00000001, + 0x00000005, 0x00050048, 0x00000069, 0x00000001, 0x00000007, 0x00000010, + 0x00050048, 0x00000069, 0x00000001, 0x00000023, 0x00000040, 0x00050048, + 0x00000069, 0x00000002, 0x00000023, 0x00000080, 0x00050048, 0x00000069, + 0x00000003, 0x00000023, 0x00000090, 0x00050048, 0x00000069, 0x00000004, + 0x00000023, 0x000000a0, 0x00050048, 0x00000069, 0x00000005, 0x00000023, + 0x00000100, 0x00030047, 0x0000006a, 0x00000003, 0x00040048, 0x0000006a, + 0x00000000, 0x00000018, 0x00050048, 0x0000006a, 0x00000000, 0x00000023, + 0x00000000, 0x00030047, 0x0000006c, 0x00000018, 0x00040047, 0x0000006c, + 0x00000021, 0x00000005, 0x00040047, 0x0000006c, 0x00000022, 0x00000000, + 0x00040047, 0x00000105, 0x00000006, 0x00000004, 0x00030047, 0x00000106, + 0x00000003, 0x00050048, 0x00000106, 0x00000000, 0x00000023, 0x00000000, + 0x00050048, 0x00000106, 0x00000001, 0x00000023, 0x00000004, 0x00040047, + 0x00000108, 0x00000021, 0x00000003, 0x00040047, 0x00000108, 0x00000022, + 0x00000000, 0x00040047, 0x0000010e, 0x00000006, 0x00000010, 0x00030047, + 0x0000010f, 0x00000003, 0x00050048, 0x0000010f, 0x00000000, 0x00000023, + 0x00000000, 0x00040047, 0x00000111, 0x00000021, 0x00000006, 0x00040047, + 0x00000111, 0x00000022, 0x00000000, 0x00040047, 0x00000114, 0x00000006, + 0x00000004, 0x00030047, 0x00000115, 0x00000003, 0x00040048, 0x00000115, + 0x00000000, 0x00000018, 0x00050048, 0x00000115, 0x00000000, 0x00000023, + 0x00000000, 0x00030047, 0x00000117, 0x00000018, 0x00040047, 0x00000117, + 0x00000021, 0x00000000, 0x00040047, 0x00000117, 0x00000022, 0x00000000, + 0x00040047, 0x00000118, 0x00000006, 0x00000004, 0x00030047, 0x00000119, + 0x00000003, 0x00040048, 0x00000119, 0x00000000, 0x00000018, 0x00050048, + 0x00000119, 0x00000000, 0x00000023, 0x00000000, 0x00030047, 0x0000011b, + 0x00000018, 0x00040047, 0x0000011b, 0x00000021, 0x00000001, 0x00040047, + 0x0000011b, 0x00000022, 0x00000000, 0x00040047, 0x0000011c, 0x00000006, + 0x00000004, 0x00030047, 0x0000011d, 0x00000003, 0x00050048, 0x0000011d, + 0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x0000011f, 0x00000021, + 0x00000004, 0x00040047, 0x0000011f, 0x00000022, 0x00000000, 0x00040047, + 0x00000121, 0x0000000b, 0x00000019, 0x00020013, 0x00000002, 0x00030021, + 0x00000003, 0x00000002, 0x00040015, 0x00000006, 0x00000020, 0x00000000, + 0x00040020, 0x00000007, 0x00000007, 0x00000006, 0x00030016, 0x00000008, + 0x00000020, 0x00040017, 0x00000009, 0x00000008, 0x00000003, 0x00040021, + 0x0000000a, 0x00000009, 0x00000007, 0x00040021, 0x0000000e, 0x00000008, + 0x00000007, 0x00040021, 0x00000012, 0x00000006, 0x00000007, 0x0004002b, + 0x00000006, 0x0000001e, 0x00000004, 0x00040017, 0x00000020, 0x00000006, + 0x00000004, 0x0003001d, 0x00000021, 0x00000020, 0x0003001e, 0x00000022, + 0x00000021, 0x00040020, 0x00000023, 0x00000002, 0x00000022, 0x0004003b, + 0x00000023, 0x00000024, 0x00000002, 0x00040015, 0x00000025, 0x00000020, + 0x00000001, 0x0004002b, 0x00000025, 0x00000026, 0x00000000, 0x0004002b, + 0x00000006, 0x00000028, 0x00000000, 0x00040020, 0x0000002a, 0x00000002, + 0x00000006, 0x0004002b, 0x00000006, 0x00000030, 0x00000001, 0x0004002b, + 0x00000006, 0x00000036, 0x00000002, 0x0004002b, 0x00000006, 0x00000040, + 0x00000003, 0x00040017, 0x0000005d, 0x00000006, 0x00000003, 0x00040020, + 0x0000005e, 0x00000001, 0x0000005d, 0x0004003b, 0x0000005e, 0x0000005f, + 0x00000001, 0x00040020, 0x00000060, 0x00000001, 0x00000006, 0x00040017, + 0x00000065, 0x00000008, 0x00000004, 0x00040018, 0x00000066, 0x00000065, + 0x00000004, 0x0004002b, 0x00000006, 0x00000067, 0x00000006, 0x0004001c, + 0x00000068, 0x00000065, 0x00000067, 0x0008001e, 0x00000069, 0x00000066, + 0x00000066, 0x00000065, 0x00000065, 0x00000068, 0x00000065, 0x0003001e, + 0x0000006a, 0x00000069, 0x00040020, 0x0000006b, 0x00000002, 0x0000006a, + 0x0004003b, 0x0000006b, 0x0000006c, 0x00000002, 0x0004002b, 0x00000025, + 0x0000006d, 0x00000005, 0x00040020, 0x0000006e, 0x00000002, 0x00000008, + 0x00020014, 0x00000071, 0x00040020, 0x00000076, 0x00000007, 0x00000009, + 0x00040020, 0x0000007b, 0x00000007, 0x00000008, 0x00040020, 0x00000080, + 0x00000007, 0x00000065, 0x0004002b, 0x00000025, 0x00000082, 0x00000001, + 0x00040020, 0x00000083, 0x00000002, 0x00000066, 0x0004002b, 0x00000008, + 0x00000087, 0x3f800000, 0x0004002b, 0x00000008, 0x00000092, 0x358637bd, + 0x0004002b, 0x00000025, 0x00000098, 0x00000002, 0x00040020, 0x00000099, + 0x00000002, 0x00000065, 0x0004002b, 0x00000008, 0x000000a1, 0x38d1b717, + 0x00040020, 0x000000a3, 0x00000007, 0x00000025, 0x0004002b, 0x00000025, + 0x000000ab, 0x00000006, 0x0004002b, 0x00000025, 0x000000ad, 0x00000004, + 0x0004002b, 0x00000008, 0x000000c7, 0x7149f2ca, 0x0004002b, 0x00000006, + 0x000000cd, 0xffffffff, 0x00040020, 0x000000ea, 0x00000007, 0x00000071, + 0x0004002b, 0x00000025, 0x000000f2, 0x00000003, 0x0003001d, 0x00000105, + 0x00000006, 0x0004001e, 0x00000106, 0x00000006, 0x00000105, 0x00040020, + 0x00000107, 0x00000002, 0x00000106, 0x0004003b, 0x00000107, 0x00000108, + 0x00000002, 0x0003001d, 0x0000010e, 0x00000020, 0x0003001e, 0x0000010f, + 0x0000010e, 0x00040020, 0x00000110, 0x00000002, 0x0000010f, 0x0004003b, + 0x00000110, 0x00000111, 0x00000002, 0x0003001d, 0x00000114, 0x00000008, + 0x0003001e, 0x00000115, 0x00000114, 0x00040020, 0x00000116, 0x00000002, + 0x00000115, 0x0004003b, 0x00000116, 0x00000117, 0x00000002, 0x0003001d, + 0x00000118, 0x00000006, 0x0003001e, 0x00000119, 0x00000118, 0x00040020, + 0x0000011a, 0x00000002, 0x00000119, 0x0004003b, 0x0000011a, 0x0000011b, + 0x00000002, 0x0003001d, 0x0000011c, 0x00000006, 0x0003001e, 0x0000011d, + 0x0000011c, 0x00040020, 0x0000011e, 0x00000002, 0x0000011d, 0x0004003b, + 0x0000011e, 0x0000011f, 0x00000002, 0x0004002b, 0x00000006, 0x00000120, + 0x00000040, 0x0006002c, 0x0000005d, 0x00000121, 0x00000120, 0x00000030, + 0x00000030, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, + 0x000200f8, 0x00000005, 0x0004003b, 0x00000007, 0x0000005c, 0x00000007, + 0x0004003b, 0x00000076, 0x00000077, 0x00000007, 0x0004003b, 0x00000007, + 0x00000078, 0x00000007, 0x0004003b, 0x0000007b, 0x0000007c, 0x00000007, + 0x0004003b, 0x00000007, 0x0000007d, 0x00000007, 0x0004003b, 0x00000080, + 0x00000081, 0x00000007, 0x0004003b, 0x00000076, 0x0000008d, 0x00000007, + 0x0004003b, 0x00000076, 0x00000096, 0x00000007, 0x0004003b, 0x0000007b, + 0x0000009e, 0x00000007, 0x0004003b, 0x000000a3, 0x000000a4, 0x00000007, + 0x0004003b, 0x0000007b, 0x000000c0, 0x00000007, 0x0004003b, 0x00000007, + 0x000000c1, 0x00000007, 0x0004003b, 0x0000007b, 0x000000c6, 0x00000007, + 0x0004003b, 0x00000007, 0x000000c8, 0x00000007, 0x0004003b, 0x00000007, + 0x000000c9, 0x00000007, 0x0004003b, 0x00000080, 0x000000d1, 0x00000007, + 0x0004003b, 0x00000007, 0x000000d4, 0x00000007, 0x0004003b, 0x0000007b, + 0x000000dc, 0x00000007, 0x0004003b, 0x00000007, 0x000000e5, 0x00000007, + 0x0004003b, 0x000000ea, 0x000000eb, 0x00000007, 0x0004003b, 0x00000007, + 0x000000ec, 0x00000007, 0x0004003b, 0x000000ea, 0x000000f0, 0x00000007, + 0x0004003b, 0x00000007, 0x00000104, 0x00000007, 0x00050041, 0x00000060, + 0x00000061, 0x0000005f, 0x00000028, 0x0004003d, 0x00000006, 0x00000062, + 0x00000061, 0x0003003e, 0x0000005c, 0x00000062, 0x0004003d, 0x00000006, + 0x00000063, 0x0000005c, 0x00040070, 0x00000008, 0x00000064, 0x00000063, + 0x00070041, 0x0000006e, 0x0000006f, 0x0000006c, 0x00000026, 0x0000006d, + 0x00000028, 0x0004003d, 0x00000008, 0x00000070, 0x0000006f, 0x000500be, + 0x00000071, 0x00000072, 0x00000064, 0x00000070, 0x000300f7, 0x00000074, + 0x00000000, 0x000400fa, 0x00000072, 0x00000073, 0x00000074, 0x000200f8, + 0x00000073, 0x000100fd, 0x000200f8, 0x00000074, 0x0004003d, 0x00000006, + 0x00000079, 0x0000005c, 0x0003003e, 0x00000078, 0x00000079, 0x00050039, + 0x00000009, 0x0000007a, 0x0000000c, 0x00000078, 0x0003003e, 0x00000077, + 0x0000007a, 0x0004003d, 0x00000006, 0x0000007e, 0x0000005c, 0x0003003e, + 0x0000007d, 0x0000007e, 0x00050039, 0x00000008, 0x0000007f, 0x00000010, + 0x0000007d, 0x0003003e, 0x0000007c, 0x0000007f, 0x00060041, 0x00000083, + 0x00000084, 0x0000006c, 0x00000026, 0x00000082, 0x0004003d, 0x00000066, + 0x00000085, 0x00000084, 0x0004003d, 0x00000009, 0x00000086, 0x00000077, + 0x00050051, 0x00000008, 0x00000088, 0x00000086, 0x00000000, 0x00050051, + 0x00000008, 0x00000089, 0x00000086, 0x00000001, 0x00050051, 0x00000008, + 0x0000008a, 0x00000086, 0x00000002, 0x00070050, 0x00000065, 0x0000008b, + 0x00000088, 0x00000089, 0x0000008a, 0x00000087, 0x00050091, 0x00000065, + 0x0000008c, 0x00000085, 0x0000008b, 0x0003003e, 0x00000081, 0x0000008c, + 0x0004003d, 0x00000065, 0x0000008e, 0x00000081, 0x0008004f, 0x00000009, + 0x0000008f, 0x0000008e, 0x0000008e, 0x00000000, 0x00000001, 0x00000002, + 0x00050041, 0x0000007b, 0x00000090, 0x00000081, 0x00000040, 0x0004003d, + 0x00000008, 0x00000091, 0x00000090, 0x0007000c, 0x00000008, 0x00000093, + 0x00000001, 0x00000028, 0x00000091, 0x00000092, 0x00060050, 0x00000009, + 0x00000094, 0x00000093, 0x00000093, 0x00000093, 0x00050088, 0x00000009, + 0x00000095, 0x0000008f, 0x00000094, 0x0003003e, 0x0000008d, 0x00000095, + 0x0004003d, 0x00000009, 0x00000097, 0x0000008d, 0x00060041, 0x00000099, + 0x0000009a, 0x0000006c, 0x00000026, 0x00000098, 0x0004003d, 0x00000065, + 0x0000009b, 0x0000009a, 0x0008004f, 0x00000009, 0x0000009c, 0x0000009b, + 0x0000009b, 0x00000000, 0x00000001, 0x00000002, 0x00050083, 0x00000009, + 0x0000009d, 0x00000097, 0x0000009c, 0x0003003e, 0x00000096, 0x0000009d, + 0x0004003d, 0x00000009, 0x0000009f, 0x00000096, 0x0006000c, 0x00000008, + 0x000000a0, 0x00000001, 0x00000042, 0x0000009f, 0x0007000c, 0x00000008, + 0x000000a2, 0x00000001, 0x00000028, 0x000000a0, 0x000000a1, 0x0003003e, + 0x0000009e, 0x000000a2, 0x0003003e, 0x000000a4, 0x00000026, 0x000200f9, + 0x000000a5, 0x000200f8, 0x000000a5, 0x000400f6, 0x000000a7, 0x000000a8, + 0x00000000, 0x000200f9, 0x000000a9, 0x000200f8, 0x000000a9, 0x0004003d, + 0x00000025, 0x000000aa, 0x000000a4, 0x000500b1, 0x00000071, 0x000000ac, + 0x000000aa, 0x000000ab, 0x000400fa, 0x000000ac, 0x000000a6, 0x000000a7, + 0x000200f8, 0x000000a6, 0x0004003d, 0x00000025, 0x000000ae, 0x000000a4, + 0x00070041, 0x00000099, 0x000000af, 0x0000006c, 0x00000026, 0x000000ad, + 0x000000ae, 0x0004003d, 0x00000065, 0x000000b0, 0x000000af, 0x0008004f, + 0x00000009, 0x000000b1, 0x000000b0, 0x000000b0, 0x00000000, 0x00000001, + 0x00000002, 0x0004003d, 0x00000009, 0x000000b2, 0x0000008d, 0x00050094, + 0x00000008, 0x000000b3, 0x000000b1, 0x000000b2, 0x0004003d, 0x00000025, + 0x000000b4, 0x000000a4, 0x00080041, 0x0000006e, 0x000000b5, 0x0000006c, + 0x00000026, 0x000000ad, 0x000000b4, 0x00000040, 0x0004003d, 0x00000008, + 0x000000b6, 0x000000b5, 0x00050081, 0x00000008, 0x000000b7, 0x000000b3, + 0x000000b6, 0x0004003d, 0x00000008, 0x000000b8, 0x0000007c, 0x0004007f, + 0x00000008, 0x000000b9, 0x000000b8, 0x000500b8, 0x00000071, 0x000000ba, + 0x000000b7, 0x000000b9, 0x000300f7, 0x000000bc, 0x00000000, 0x000400fa, + 0x000000ba, 0x000000bb, 0x000000bc, 0x000200f8, 0x000000bb, 0x000100fd, + 0x000200f8, 0x000000bc, 0x000200f9, 0x000000a8, 0x000200f8, 0x000000a8, + 0x0004003d, 0x00000025, 0x000000be, 0x000000a4, 0x00050080, 0x00000025, + 0x000000bf, 0x000000be, 0x00000082, 0x0003003e, 0x000000a4, 0x000000bf, + 0x000200f9, 0x000000a5, 0x000200f8, 0x000000a7, 0x0004003d, 0x00000006, + 0x000000c2, 0x0000005c, 0x0003003e, 0x000000c1, 0x000000c2, 0x00050039, + 0x00000008, 0x000000c3, 0x00000017, 0x000000c1, 0x0004003d, 0x00000008, + 0x000000c4, 0x0000009e, 0x00050088, 0x00000008, 0x000000c5, 0x000000c3, + 0x000000c4, 0x0003003e, 0x000000c0, 0x000000c5, 0x0003003e, 0x000000c6, + 0x000000c7, 0x0004003d, 0x00000006, 0x000000ca, 0x0000005c, 0x0003003e, + 0x000000c9, 0x000000ca, 0x00050039, 0x00000006, 0x000000cb, 0x00000014, + 0x000000c9, 0x0003003e, 0x000000c8, 0x000000cb, 0x0004003d, 0x00000006, + 0x000000cc, 0x000000c8, 0x000500ab, 0x00000071, 0x000000ce, 0x000000cc, + 0x000000cd, 0x000300f7, 0x000000d0, 0x00000000, 0x000400fa, 0x000000ce, + 0x000000cf, 0x000000d0, 0x000200f8, 0x000000cf, 0x00060041, 0x00000083, + 0x000000d2, 0x0000006c, 0x00000026, 0x00000082, 0x0004003d, 0x00000066, + 0x000000d3, 0x000000d2, 0x0004003d, 0x00000006, 0x000000d5, 0x000000c8, + 0x0003003e, 0x000000d4, 0x000000d5, 0x00050039, 0x00000009, 0x000000d6, + 0x0000000c, 0x000000d4, 0x00050051, 0x00000008, 0x000000d7, 0x000000d6, + 0x00000000, 0x00050051, 0x00000008, 0x000000d8, 0x000000d6, 0x00000001, + 0x00050051, 0x00000008, 0x000000d9, 0x000000d6, 0x00000002, 0x00070050, + 0x00000065, 0x000000da, 0x000000d7, 0x000000d8, 0x000000d9, 0x00000087, + 0x00050091, 0x00000065, 0x000000db, 0x000000d3, 0x000000da, 0x0003003e, + 0x000000d1, 0x000000db, 0x0004003d, 0x00000065, 0x000000dd, 0x000000d1, + 0x0008004f, 0x00000009, 0x000000de, 0x000000dd, 0x000000dd, 0x00000000, + 0x00000001, 0x00000002, 0x00060041, 0x00000099, 0x000000df, 0x0000006c, + 0x00000026, 0x00000098, 0x0004003d, 0x00000065, 0x000000e0, 0x000000df, + 0x0008004f, 0x00000009, 0x000000e1, 0x000000e0, 0x000000e0, 0x00000000, + 0x00000001, 0x00000002, 0x00050083, 0x00000009, 0x000000e2, 0x000000de, + 0x000000e1, 0x0006000c, 0x00000008, 0x000000e3, 0x00000001, 0x00000042, + 0x000000e2, 0x0007000c, 0x00000008, 0x000000e4, 0x00000001, 0x00000028, + 0x000000e3, 0x000000a1, 0x0003003e, 0x000000dc, 0x000000e4, 0x0004003d, + 0x00000006, 0x000000e6, 0x000000c8, 0x0003003e, 0x000000e5, 0x000000e6, + 0x00050039, 0x00000008, 0x000000e7, 0x00000017, 0x000000e5, 0x0004003d, + 0x00000008, 0x000000e8, 0x000000dc, 0x00050088, 0x00000008, 0x000000e9, + 0x000000e7, 0x000000e8, 0x0003003e, 0x000000c6, 0x000000e9, 0x000200f9, + 0x000000d0, 0x000200f8, 0x000000d0, 0x0004003d, 0x00000006, 0x000000ed, + 0x0000005c, 0x0003003e, 0x000000ec, 0x000000ed, 0x00050039, 0x00000006, + 0x000000ee, 0x0000001a, 0x000000ec, 0x000500aa, 0x00000071, 0x000000ef, + 0x000000ee, 0x00000028, 0x0003003e, 0x000000eb, 0x000000ef, 0x0004003d, + 0x00000008, 0x000000f1, 0x000000c0, 0x00070041, 0x0000006e, 0x000000f3, + 0x0000006c, 0x00000026, 0x000000f2, 0x00000040, 0x0004003d, 0x00000008, + 0x000000f4, 0x000000f3, 0x000500bc, 0x00000071, 0x000000f5, 0x000000f1, + 0x000000f4, 0x0004003d, 0x00000071, 0x000000f6, 0x000000eb, 0x000500a6, + 0x00000071, 0x000000f7, 0x000000f5, 0x000000f6, 0x000300f7, 0x000000f9, + 0x00000000, 0x000400fa, 0x000000f7, 0x000000f8, 0x000000f9, 0x000200f8, + 0x000000f8, 0x0004003d, 0x00000008, 0x000000fa, 0x000000c6, 0x00070041, + 0x0000006e, 0x000000fb, 0x0000006c, 0x00000026, 0x000000f2, 0x00000040, + 0x0004003d, 0x00000008, 0x000000fc, 0x000000fb, 0x000500ba, 0x00000071, + 0x000000fd, 0x000000fa, 0x000000fc, 0x000200f9, 0x000000f9, 0x000200f8, + 0x000000f9, 0x000700f5, 0x00000071, 0x000000fe, 0x000000f7, 0x000000d0, + 0x000000fd, 0x000000f8, 0x0003003e, 0x000000f0, 0x000000fe, 0x0004003d, + 0x00000071, 0x000000ff, 0x000000f0, 0x000400a8, 0x00000071, 0x00000100, + 0x000000ff, 0x000300f7, 0x00000102, 0x00000000, 0x000400fa, 0x00000100, + 0x00000101, 0x00000102, 0x000200f8, 0x00000101, 0x000100fd, 0x000200f8, + 0x00000102, 0x00050041, 0x0000002a, 0x00000109, 0x00000108, 0x00000026, + 0x000700ea, 0x00000006, 0x0000010a, 0x00000109, 0x00000030, 0x00000028, + 0x00000030, 0x0003003e, 0x00000104, 0x0000010a, 0x0004003d, 0x00000006, + 0x0000010b, 0x00000104, 0x0004003d, 0x00000006, 0x0000010c, 0x0000005c, + 0x00060041, 0x0000002a, 0x0000010d, 0x00000108, 0x00000082, 0x0000010b, + 0x0003003e, 0x0000010d, 0x0000010c, 0x00070041, 0x0000002a, 0x00000112, + 0x00000111, 0x00000026, 0x00000026, 0x00000028, 0x000700ea, 0x00000006, + 0x00000113, 0x00000112, 0x00000030, 0x00000028, 0x00000030, 0x000100fd, + 0x00010038, 0x00050036, 0x00000009, 0x0000000c, 0x00000000, 0x0000000a, + 0x00030037, 0x00000007, 0x0000000b, 0x000200f8, 0x0000000d, 0x0004003b, + 0x00000007, 0x0000001c, 0x00000007, 0x0004003d, 0x00000006, 0x0000001d, + 0x0000000b, 0x00050084, 0x00000006, 0x0000001f, 0x0000001d, 0x0000001e, + 0x0003003e, 0x0000001c, 0x0000001f, 0x0004003d, 0x00000006, 0x00000027, + 0x0000001c, 0x00050080, 0x00000006, 0x00000029, 0x00000027, 0x00000028, + 0x00070041, 0x0000002a, 0x0000002b, 0x00000024, 0x00000026, 0x00000029, + 0x00000028, 0x0004003d, 0x00000006, 0x0000002c, 0x0000002b, 0x0004007c, + 0x00000008, 0x0000002d, 0x0000002c, 0x0004003d, 0x00000006, 0x0000002e, + 0x0000001c, 0x00050080, 0x00000006, 0x0000002f, 0x0000002e, 0x00000028, + 0x00070041, 0x0000002a, 0x00000031, 0x00000024, 0x00000026, 0x0000002f, + 0x00000030, 0x0004003d, 0x00000006, 0x00000032, 0x00000031, 0x0004007c, + 0x00000008, 0x00000033, 0x00000032, 0x0004003d, 0x00000006, 0x00000034, + 0x0000001c, 0x00050080, 0x00000006, 0x00000035, 0x00000034, 0x00000028, + 0x00070041, 0x0000002a, 0x00000037, 0x00000024, 0x00000026, 0x00000035, + 0x00000036, 0x0004003d, 0x00000006, 0x00000038, 0x00000037, 0x0004007c, + 0x00000008, 0x00000039, 0x00000038, 0x00060050, 0x00000009, 0x0000003a, + 0x0000002d, 0x00000033, 0x00000039, 0x000200fe, 0x0000003a, 0x00010038, + 0x00050036, 0x00000008, 0x00000010, 0x00000000, 0x0000000e, 0x00030037, + 0x00000007, 0x0000000f, 0x000200f8, 0x00000011, 0x0004003d, 0x00000006, + 0x0000003d, 0x0000000f, 0x00050084, 0x00000006, 0x0000003e, 0x0000003d, + 0x0000001e, 0x00050080, 0x00000006, 0x0000003f, 0x0000003e, 0x00000028, + 0x00070041, 0x0000002a, 0x00000041, 0x00000024, 0x00000026, 0x0000003f, + 0x00000040, 0x0004003d, 0x00000006, 0x00000042, 0x00000041, 0x0004007c, + 0x00000008, 0x00000043, 0x00000042, 0x000200fe, 0x00000043, 0x00010038, + 0x00050036, 0x00000006, 0x00000014, 0x00000000, 0x00000012, 0x00030037, + 0x00000007, 0x00000013, 0x000200f8, 0x00000015, 0x0004003d, 0x00000006, + 0x00000046, 0x00000013, 0x00050084, 0x00000006, 0x00000047, 0x00000046, + 0x0000001e, 0x00050080, 0x00000006, 0x00000048, 0x00000047, 0x00000030, + 0x00070041, 0x0000002a, 0x00000049, 0x00000024, 0x00000026, 0x00000048, + 0x00000040, 0x0004003d, 0x00000006, 0x0000004a, 0x00000049, 0x000200fe, + 0x0000004a, 0x00010038, 0x00050036, 0x00000008, 0x00000017, 0x00000000, + 0x0000000e, 0x00030037, 0x00000007, 0x00000016, 0x000200f8, 0x00000018, + 0x0004003d, 0x00000006, 0x0000004d, 0x00000016, 0x00050084, 0x00000006, + 0x0000004e, 0x0000004d, 0x0000001e, 0x00050080, 0x00000006, 0x0000004f, + 0x0000004e, 0x00000036, 0x00070041, 0x0000002a, 0x00000050, 0x00000024, + 0x00000026, 0x0000004f, 0x00000030, 0x0004003d, 0x00000006, 0x00000051, + 0x00000050, 0x0004007c, 0x00000008, 0x00000052, 0x00000051, 0x000200fe, + 0x00000052, 0x00010038, 0x00050036, 0x00000006, 0x0000001a, 0x00000000, + 0x00000012, 0x00030037, 0x00000007, 0x00000019, 0x000200f8, 0x0000001b, + 0x0004003d, 0x00000006, 0x00000055, 0x00000019, 0x00050084, 0x00000006, + 0x00000056, 0x00000055, 0x0000001e, 0x00050080, 0x00000006, 0x00000057, + 0x00000056, 0x00000036, 0x00070041, 0x0000002a, 0x00000058, 0x00000024, + 0x00000026, 0x00000057, 0x00000036, 0x0004003d, 0x00000006, 0x00000059, + 0x00000058, 0x000200fe, 0x00000059, 0x00010038, +}; +static const size_t vg_cull_compute_spv_count = sizeof(vg_cull_compute_spv) / sizeof(vg_cull_compute_spv[0]); diff --git a/src/modules/virtualgeometry/shaders/vg_raster.comp b/src/modules/virtualgeometry/shaders/vg_raster.comp new file mode 100644 index 00000000..872758d3 --- /dev/null +++ b/src/modules/virtualgeometry/shaders/vg_raster.comp @@ -0,0 +1,76 @@ +// Pass B — software rasterizer that fills the visibility buffer. +// +// Each invocation handles one triangle of one visible cluster: +// slot = globalId / VG_MAX_TRI_PER_CLUSTER (index into the visible list) +// tri = globalId % VG_MAX_TRI_PER_CLUSTER (triangle within the cluster) +// +// The triangle is transformed, backface-culled, and rasterized with edge +// functions. Per pixel we atomically write a packed (depth, cluster) value +// (see vg_pack) so the closest visible cluster wins at every pixel. +#version 450 + +#include "vg_common.glsl" + +layout(local_size_x = 128) in; + +void main() { + uint slot = gl_GlobalInvocationID.x / VG_MAX_TRI_PER_CLUSTER; + uint t = gl_GlobalInvocationID.x % VG_MAX_TRI_PER_CLUSTER; + uint nVisible = vgCounter; + if (slot >= nVisible) return; + + uint cid = vgVis[slot]; + uint triStart = vg_clusterTriStart(cid); + uint triCount = vg_clusterTriCount(cid); + if (t >= triCount) return; + + // Fetch triangle corners (global vertex indices). + uint i0 = vgTri[triStart * 3u + t * 3u + 0u]; + uint i1 = vgTri[triStart * 3u + t * 3u + 1u]; + uint i2 = vgTri[triStart * 3u + t * 3u + 2u]; + + vec3 p0 = vec3(vgPos[3u * i0 + 0u], vgPos[3u * i0 + 1u], vgPos[3u * i0 + 2u]); + vec3 p1 = vec3(vgPos[3u * i1 + 0u], vgPos[3u * i1 + 1u], vgPos[3u * i1 + 2u]); + vec3 p2 = vec3(vgPos[3u * i2 + 0u], vgPos[3u * i2 + 1u], vgPos[3u * i2 + 2u]); + + vec4 c0 = vgU.viewProj * (vgU.model * vec4(p0, 1.0)); + vec4 c1 = vgU.viewProj * (vgU.model * vec4(p1, 1.0)); + vec4 c2 = vgU.viewProj * (vgU.model * vec4(p2, 1.0)); + + vec2 n0 = c0.xy / c0.w, n1 = c1.xy / c1.w, n2 = c2.xy / c2.w; + vec2 s0 = (n0 * 0.5 + 0.5) * vgU.params.xy; + vec2 s1 = (n1 * 0.5 + 0.5) * vgU.params.xy; + vec2 s2 = (n2 * 0.5 + 0.5) * vgU.params.xy; + + // Backface cull (CCW in screen space, using signed area). + float area = (s1.x - s0.x) * (s2.y - s0.y) - (s1.y - s0.y) * (s2.x - s0.x); + if (area <= 0.0) return; + + // 2D bounding box, clamped to the viewport. + ivec2 vmin = ivec2(floor(min(min(s0, s1), s2))); + ivec2 vmax = ivec2(ceil(max(max(s0, s1), s2))); + ivec2 view = ivec2(vgU.params.xy); + vmin = clamp(vmin, ivec2(0), view - ivec2(1)); + vmax = clamp(vmax, ivec2(0), view - ivec2(1)); + + float invArea = 1.0 / area; + int W = view.x; + for (int y = vmin.y; y <= vmax.y; ++y) { + for (int x = vmin.x; x <= vmax.x; ++x) { + vec2 p = vec2(float(x) + 0.5, float(y) + 0.5); + // Edge functions -> barycentrics. + float e0 = (s1.x - s0.x) * (p.y - s0.y) - (s1.y - s0.y) * (p.x - s0.x); + float e1 = (s2.x - s1.x) * (p.y - s1.y) - (s2.y - s1.y) * (p.x - s1.x); + float e2 = (s0.x - s2.x) * (p.y - s2.y) - (s0.y - s2.y) * (p.x - s2.x); + if (e0 < 0.0 || e1 < 0.0 || e2 < 0.0) continue; + + float l0 = e1 * invArea, l1 = e2 * invArea, l2 = e0 * invArea; + // Interpolate NDC depth (near = 0). + float ndcZ = l0 * (c0.z / c0.w) + l1 * (c1.z / c1.w) + l2 * (c2.z / c2.w); + uint depth = uint(clamp(ndcZ, 0.0, 1.0) * 65535.0); + uint packed = vg_pack(depth, cid); + atomicMin(vgPix[uint(y) * uint(W) + uint(x)], packed); + } + } + atomicAdd(vgStat[1].x, 1u); // rasterized triangle count +} diff --git a/src/modules/virtualgeometry/shaders/vg_raster_compute.spv b/src/modules/virtualgeometry/shaders/vg_raster_compute.spv new file mode 100644 index 00000000..ceae1a02 Binary files /dev/null and b/src/modules/virtualgeometry/shaders/vg_raster_compute.spv differ diff --git a/src/modules/virtualgeometry/shaders/vg_raster_compute_spv.inc b/src/modules/virtualgeometry/shaders/vg_raster_compute_spv.inc new file mode 100644 index 00000000..0b6a3a17 --- /dev/null +++ b/src/modules/virtualgeometry/shaders/vg_raster_compute_spv.inc @@ -0,0 +1,548 @@ +// Generated by compile_virtualgeometry_shaders.py. Do not edit. +#pragma once +static const uint32_t vg_raster_compute_spv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x0000022a, 0x00000000, 0x00020011, + 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, + 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0006000f, 0x00000005, + 0x00000004, 0x6e69616d, 0x00000000, 0x0000003a, 0x00060010, 0x00000004, + 0x00000011, 0x00000080, 0x00000001, 0x00000001, 0x00030003, 0x00000002, + 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, + 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004, + 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, + 0x00657669, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00060005, + 0x0000000b, 0x705f6776, 0x286b6361, 0x753b3175, 0x00003b31, 0x00050005, + 0x00000009, 0x74706564, 0x74694268, 0x00000073, 0x00050005, 0x0000000a, + 0x73756c63, 0x49726574, 0x00000064, 0x00080005, 0x0000000f, 0x635f6776, + 0x7473756c, 0x72547265, 0x61745369, 0x75287472, 0x00003b31, 0x00030005, + 0x0000000e, 0x00646963, 0x00080005, 0x00000012, 0x635f6776, 0x7473756c, + 0x72547265, 0x756f4369, 0x7528746e, 0x00003b31, 0x00030005, 0x00000011, + 0x00646963, 0x00050005, 0x00000021, 0x6c436756, 0x65747375, 0x00007372, + 0x00050006, 0x00000021, 0x00000000, 0x6c436776, 0x00000000, 0x00030005, + 0x00000023, 0x00000000, 0x00040005, 0x00000037, 0x746f6c73, 0x00000000, + 0x00080005, 0x0000003a, 0x475f6c67, 0x61626f6c, 0x766e496c, 0x7461636f, + 0x496e6f69, 0x00000044, 0x00030005, 0x00000040, 0x00000074, 0x00050005, + 0x00000044, 0x7369566e, 0x656c6269, 0x00000000, 0x00050005, 0x00000046, + 0x69566756, 0x6c626973, 0x00000065, 0x00060006, 0x00000046, 0x00000000, + 0x6f436776, 0x65746e75, 0x00000072, 0x00050006, 0x00000046, 0x00000001, + 0x69566776, 0x00000073, 0x00030005, 0x00000048, 0x00000000, 0x00030005, + 0x00000052, 0x00646963, 0x00050005, 0x00000057, 0x53697274, 0x74726174, + 0x00000000, 0x00040005, 0x00000058, 0x61726170, 0x0000006d, 0x00050005, + 0x0000005b, 0x43697274, 0x746e756f, 0x00000000, 0x00040005, 0x0000005c, + 0x61726170, 0x0000006d, 0x00030005, 0x00000065, 0x00003069, 0x00050005, + 0x00000067, 0x72546756, 0x676e6169, 0x0073656c, 0x00050006, 0x00000067, + 0x00000000, 0x72546776, 0x00000069, 0x00030005, 0x00000069, 0x00000000, + 0x00030005, 0x00000073, 0x00003169, 0x00030005, 0x0000007c, 0x00003269, + 0x00030005, 0x00000089, 0x00003070, 0x00050005, 0x0000008b, 0x6f506756, + 0x69746973, 0x00736e6f, 0x00050006, 0x0000008b, 0x00000000, 0x6f506776, + 0x00000073, 0x00030005, 0x0000008d, 0x00000000, 0x00030005, 0x0000009f, + 0x00003170, 0x00030005, 0x000000b0, 0x00003270, 0x00030005, 0x000000c3, + 0x00003063, 0x00050005, 0x000000c7, 0x6e556756, 0x726f6669, 0x0000736d, + 0x00060006, 0x000000c7, 0x00000000, 0x77656976, 0x6a6f7250, 0x00000000, + 0x00050006, 0x000000c7, 0x00000001, 0x65646f6d, 0x0000006c, 0x00060006, + 0x000000c7, 0x00000002, 0x656d6163, 0x6f506172, 0x00000073, 0x00050006, + 0x000000c7, 0x00000003, 0x61726170, 0x0000736d, 0x00050006, 0x000000c7, + 0x00000004, 0x73757266, 0x006d7574, 0x00050006, 0x000000c7, 0x00000005, + 0x6373696d, 0x00000000, 0x00060005, 0x000000c8, 0x6e556756, 0x726f6669, + 0x6f6c426d, 0x00006b63, 0x00040006, 0x000000c8, 0x00000000, 0x00556776, + 0x00030005, 0x000000ca, 0x00000000, 0x00030005, 0x000000d8, 0x00003163, + 0x00030005, 0x000000e4, 0x00003263, 0x00030005, 0x000000f2, 0x0000306e, + 0x00030005, 0x000000fa, 0x0000316e, 0x00030005, 0x00000101, 0x0000326e, + 0x00030005, 0x00000108, 0x00003073, 0x00030005, 0x00000114, 0x00003173, + 0x00030005, 0x0000011d, 0x00003273, 0x00040005, 0x00000126, 0x61657261, + 0x00000000, 0x00040005, 0x00000146, 0x6e696d76, 0x00000000, 0x00040005, + 0x0000014e, 0x78616d76, 0x00000000, 0x00040005, 0x00000156, 0x77656976, + 0x00000000, 0x00040005, 0x00000165, 0x41766e69, 0x00616572, 0x00030005, + 0x00000169, 0x00000057, 0x00030005, 0x0000016c, 0x00000079, 0x00030005, + 0x00000178, 0x00000078, 0x00030005, 0x00000184, 0x00000070, 0x00030005, + 0x0000018c, 0x00003065, 0x00030005, 0x000001a4, 0x00003165, 0x00030005, + 0x000001bc, 0x00003265, 0x00030005, 0x000001df, 0x0000306c, 0x00030005, + 0x000001e3, 0x0000316c, 0x00030005, 0x000001e7, 0x0000326c, 0x00040005, + 0x000001eb, 0x5a63646e, 0x00000000, 0x00040005, 0x00000203, 0x74706564, + 0x00000068, 0x00040005, 0x00000209, 0x6b636170, 0x00006465, 0x00040005, + 0x0000020a, 0x61726170, 0x0000006d, 0x00040005, 0x0000020c, 0x61726170, + 0x0000006d, 0x00050005, 0x00000210, 0x69506756, 0x736c6578, 0x00000000, + 0x00050006, 0x00000210, 0x00000000, 0x69506776, 0x00000078, 0x00030005, + 0x00000212, 0x00000000, 0x00040005, 0x00000223, 0x74536756, 0x00737461, + 0x00050006, 0x00000223, 0x00000000, 0x74536776, 0x00007461, 0x00030005, + 0x00000225, 0x00000000, 0x00040047, 0x00000020, 0x00000006, 0x00000010, + 0x00030047, 0x00000021, 0x00000003, 0x00040048, 0x00000021, 0x00000000, + 0x00000018, 0x00050048, 0x00000021, 0x00000000, 0x00000023, 0x00000000, + 0x00030047, 0x00000023, 0x00000018, 0x00040047, 0x00000023, 0x00000021, + 0x00000002, 0x00040047, 0x00000023, 0x00000022, 0x00000000, 0x00040047, + 0x0000003a, 0x0000000b, 0x0000001c, 0x00040047, 0x00000045, 0x00000006, + 0x00000004, 0x00030047, 0x00000046, 0x00000003, 0x00050048, 0x00000046, + 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000046, 0x00000001, + 0x00000023, 0x00000004, 0x00040047, 0x00000048, 0x00000021, 0x00000003, + 0x00040047, 0x00000048, 0x00000022, 0x00000000, 0x00040047, 0x00000066, + 0x00000006, 0x00000004, 0x00030047, 0x00000067, 0x00000003, 0x00040048, + 0x00000067, 0x00000000, 0x00000018, 0x00050048, 0x00000067, 0x00000000, + 0x00000023, 0x00000000, 0x00030047, 0x00000069, 0x00000018, 0x00040047, + 0x00000069, 0x00000021, 0x00000001, 0x00040047, 0x00000069, 0x00000022, + 0x00000000, 0x00040047, 0x0000008a, 0x00000006, 0x00000004, 0x00030047, + 0x0000008b, 0x00000003, 0x00040048, 0x0000008b, 0x00000000, 0x00000018, + 0x00050048, 0x0000008b, 0x00000000, 0x00000023, 0x00000000, 0x00030047, + 0x0000008d, 0x00000018, 0x00040047, 0x0000008d, 0x00000021, 0x00000000, + 0x00040047, 0x0000008d, 0x00000022, 0x00000000, 0x00040047, 0x000000c6, + 0x00000006, 0x00000010, 0x00040048, 0x000000c7, 0x00000000, 0x00000005, + 0x00050048, 0x000000c7, 0x00000000, 0x00000007, 0x00000010, 0x00050048, + 0x000000c7, 0x00000000, 0x00000023, 0x00000000, 0x00040048, 0x000000c7, + 0x00000001, 0x00000005, 0x00050048, 0x000000c7, 0x00000001, 0x00000007, + 0x00000010, 0x00050048, 0x000000c7, 0x00000001, 0x00000023, 0x00000040, + 0x00050048, 0x000000c7, 0x00000002, 0x00000023, 0x00000080, 0x00050048, + 0x000000c7, 0x00000003, 0x00000023, 0x00000090, 0x00050048, 0x000000c7, + 0x00000004, 0x00000023, 0x000000a0, 0x00050048, 0x000000c7, 0x00000005, + 0x00000023, 0x00000100, 0x00030047, 0x000000c8, 0x00000003, 0x00040048, + 0x000000c8, 0x00000000, 0x00000018, 0x00050048, 0x000000c8, 0x00000000, + 0x00000023, 0x00000000, 0x00030047, 0x000000ca, 0x00000018, 0x00040047, + 0x000000ca, 0x00000021, 0x00000005, 0x00040047, 0x000000ca, 0x00000022, + 0x00000000, 0x00040047, 0x0000020f, 0x00000006, 0x00000004, 0x00030047, + 0x00000210, 0x00000003, 0x00050048, 0x00000210, 0x00000000, 0x00000023, + 0x00000000, 0x00040047, 0x00000212, 0x00000021, 0x00000004, 0x00040047, + 0x00000212, 0x00000022, 0x00000000, 0x00040047, 0x00000222, 0x00000006, + 0x00000010, 0x00030047, 0x00000223, 0x00000003, 0x00050048, 0x00000223, + 0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x00000225, 0x00000021, + 0x00000006, 0x00040047, 0x00000225, 0x00000022, 0x00000000, 0x00040047, + 0x00000229, 0x0000000b, 0x00000019, 0x00020013, 0x00000002, 0x00030021, + 0x00000003, 0x00000002, 0x00040015, 0x00000006, 0x00000020, 0x00000000, + 0x00040020, 0x00000007, 0x00000007, 0x00000006, 0x00050021, 0x00000008, + 0x00000006, 0x00000007, 0x00000007, 0x00040021, 0x0000000d, 0x00000006, + 0x00000007, 0x0004002b, 0x00000006, 0x00000015, 0x0000ffff, 0x00040015, + 0x00000017, 0x00000020, 0x00000001, 0x0004002b, 0x00000017, 0x00000018, + 0x00000010, 0x00040017, 0x0000001f, 0x00000006, 0x00000004, 0x0003001d, + 0x00000020, 0x0000001f, 0x0003001e, 0x00000021, 0x00000020, 0x00040020, + 0x00000022, 0x00000002, 0x00000021, 0x0004003b, 0x00000022, 0x00000023, + 0x00000002, 0x0004002b, 0x00000017, 0x00000024, 0x00000000, 0x0004002b, + 0x00000006, 0x00000026, 0x00000004, 0x0004002b, 0x00000006, 0x00000028, + 0x00000001, 0x0004002b, 0x00000006, 0x0000002a, 0x00000000, 0x00040020, + 0x0000002b, 0x00000002, 0x00000006, 0x00040017, 0x00000038, 0x00000006, + 0x00000003, 0x00040020, 0x00000039, 0x00000001, 0x00000038, 0x0004003b, + 0x00000039, 0x0000003a, 0x00000001, 0x00040020, 0x0000003b, 0x00000001, + 0x00000006, 0x0004002b, 0x00000006, 0x0000003e, 0x0000007c, 0x0003001d, + 0x00000045, 0x00000006, 0x0004001e, 0x00000046, 0x00000006, 0x00000045, + 0x00040020, 0x00000047, 0x00000002, 0x00000046, 0x0004003b, 0x00000047, + 0x00000048, 0x00000002, 0x00020014, 0x0000004d, 0x0004002b, 0x00000017, + 0x00000053, 0x00000001, 0x0003001d, 0x00000066, 0x00000006, 0x0003001e, + 0x00000067, 0x00000066, 0x00040020, 0x00000068, 0x00000002, 0x00000067, + 0x0004003b, 0x00000068, 0x00000069, 0x00000002, 0x0004002b, 0x00000006, + 0x0000006b, 0x00000003, 0x0004002b, 0x00000006, 0x00000082, 0x00000002, + 0x00030016, 0x00000086, 0x00000020, 0x00040017, 0x00000087, 0x00000086, + 0x00000003, 0x00040020, 0x00000088, 0x00000007, 0x00000087, 0x0003001d, + 0x0000008a, 0x00000086, 0x0003001e, 0x0000008b, 0x0000008a, 0x00040020, + 0x0000008c, 0x00000002, 0x0000008b, 0x0004003b, 0x0000008c, 0x0000008d, + 0x00000002, 0x00040020, 0x00000091, 0x00000002, 0x00000086, 0x00040017, + 0x000000c1, 0x00000086, 0x00000004, 0x00040020, 0x000000c2, 0x00000007, + 0x000000c1, 0x00040018, 0x000000c4, 0x000000c1, 0x00000004, 0x0004002b, + 0x00000006, 0x000000c5, 0x00000006, 0x0004001c, 0x000000c6, 0x000000c1, + 0x000000c5, 0x0008001e, 0x000000c7, 0x000000c4, 0x000000c4, 0x000000c1, + 0x000000c1, 0x000000c6, 0x000000c1, 0x0003001e, 0x000000c8, 0x000000c7, + 0x00040020, 0x000000c9, 0x00000002, 0x000000c8, 0x0004003b, 0x000000c9, + 0x000000ca, 0x00000002, 0x00040020, 0x000000cb, 0x00000002, 0x000000c4, + 0x0004002b, 0x00000086, 0x000000d1, 0x3f800000, 0x00040017, 0x000000f0, + 0x00000086, 0x00000002, 0x00040020, 0x000000f1, 0x00000007, 0x000000f0, + 0x00040020, 0x000000f5, 0x00000007, 0x00000086, 0x0004002b, 0x00000086, + 0x0000010a, 0x3f000000, 0x0004002b, 0x00000017, 0x0000010e, 0x00000003, + 0x00040020, 0x0000010f, 0x00000002, 0x000000c1, 0x0004002b, 0x00000086, + 0x0000013f, 0x00000000, 0x00040017, 0x00000144, 0x00000017, 0x00000002, + 0x00040020, 0x00000145, 0x00000007, 0x00000144, 0x0005002c, 0x00000144, + 0x0000015c, 0x00000024, 0x00000024, 0x0005002c, 0x00000144, 0x0000015e, + 0x00000053, 0x00000053, 0x00040020, 0x00000168, 0x00000007, 0x00000017, + 0x0004002b, 0x00000086, 0x00000206, 0x477fff00, 0x0003001d, 0x0000020f, + 0x00000006, 0x0003001e, 0x00000210, 0x0000020f, 0x00040020, 0x00000211, + 0x00000002, 0x00000210, 0x0004003b, 0x00000211, 0x00000212, 0x00000002, + 0x0003001d, 0x00000222, 0x0000001f, 0x0003001e, 0x00000223, 0x00000222, + 0x00040020, 0x00000224, 0x00000002, 0x00000223, 0x0004003b, 0x00000224, + 0x00000225, 0x00000002, 0x0004002b, 0x00000006, 0x00000228, 0x00000080, + 0x0006002c, 0x00000038, 0x00000229, 0x00000228, 0x00000028, 0x00000028, + 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, + 0x00000005, 0x0004003b, 0x00000007, 0x00000037, 0x00000007, 0x0004003b, + 0x00000007, 0x00000040, 0x00000007, 0x0004003b, 0x00000007, 0x00000044, + 0x00000007, 0x0004003b, 0x00000007, 0x00000052, 0x00000007, 0x0004003b, + 0x00000007, 0x00000057, 0x00000007, 0x0004003b, 0x00000007, 0x00000058, + 0x00000007, 0x0004003b, 0x00000007, 0x0000005b, 0x00000007, 0x0004003b, + 0x00000007, 0x0000005c, 0x00000007, 0x0004003b, 0x00000007, 0x00000065, + 0x00000007, 0x0004003b, 0x00000007, 0x00000073, 0x00000007, 0x0004003b, + 0x00000007, 0x0000007c, 0x00000007, 0x0004003b, 0x00000088, 0x00000089, + 0x00000007, 0x0004003b, 0x00000088, 0x0000009f, 0x00000007, 0x0004003b, + 0x00000088, 0x000000b0, 0x00000007, 0x0004003b, 0x000000c2, 0x000000c3, + 0x00000007, 0x0004003b, 0x000000c2, 0x000000d8, 0x00000007, 0x0004003b, + 0x000000c2, 0x000000e4, 0x00000007, 0x0004003b, 0x000000f1, 0x000000f2, + 0x00000007, 0x0004003b, 0x000000f1, 0x000000fa, 0x00000007, 0x0004003b, + 0x000000f1, 0x00000101, 0x00000007, 0x0004003b, 0x000000f1, 0x00000108, + 0x00000007, 0x0004003b, 0x000000f1, 0x00000114, 0x00000007, 0x0004003b, + 0x000000f1, 0x0000011d, 0x00000007, 0x0004003b, 0x000000f5, 0x00000126, + 0x00000007, 0x0004003b, 0x00000145, 0x00000146, 0x00000007, 0x0004003b, + 0x00000145, 0x0000014e, 0x00000007, 0x0004003b, 0x00000145, 0x00000156, + 0x00000007, 0x0004003b, 0x000000f5, 0x00000165, 0x00000007, 0x0004003b, + 0x00000168, 0x00000169, 0x00000007, 0x0004003b, 0x00000168, 0x0000016c, + 0x00000007, 0x0004003b, 0x00000168, 0x00000178, 0x00000007, 0x0004003b, + 0x000000f1, 0x00000184, 0x00000007, 0x0004003b, 0x000000f5, 0x0000018c, + 0x00000007, 0x0004003b, 0x000000f5, 0x000001a4, 0x00000007, 0x0004003b, + 0x000000f5, 0x000001bc, 0x00000007, 0x0004003b, 0x000000f5, 0x000001df, + 0x00000007, 0x0004003b, 0x000000f5, 0x000001e3, 0x00000007, 0x0004003b, + 0x000000f5, 0x000001e7, 0x00000007, 0x0004003b, 0x000000f5, 0x000001eb, + 0x00000007, 0x0004003b, 0x00000007, 0x00000203, 0x00000007, 0x0004003b, + 0x00000007, 0x00000209, 0x00000007, 0x0004003b, 0x00000007, 0x0000020a, + 0x00000007, 0x0004003b, 0x00000007, 0x0000020c, 0x00000007, 0x00050041, + 0x0000003b, 0x0000003c, 0x0000003a, 0x0000002a, 0x0004003d, 0x00000006, + 0x0000003d, 0x0000003c, 0x00050086, 0x00000006, 0x0000003f, 0x0000003d, + 0x0000003e, 0x0003003e, 0x00000037, 0x0000003f, 0x00050041, 0x0000003b, + 0x00000041, 0x0000003a, 0x0000002a, 0x0004003d, 0x00000006, 0x00000042, + 0x00000041, 0x00050089, 0x00000006, 0x00000043, 0x00000042, 0x0000003e, + 0x0003003e, 0x00000040, 0x00000043, 0x00050041, 0x0000002b, 0x00000049, + 0x00000048, 0x00000024, 0x0004003d, 0x00000006, 0x0000004a, 0x00000049, + 0x0003003e, 0x00000044, 0x0000004a, 0x0004003d, 0x00000006, 0x0000004b, + 0x00000037, 0x0004003d, 0x00000006, 0x0000004c, 0x00000044, 0x000500ae, + 0x0000004d, 0x0000004e, 0x0000004b, 0x0000004c, 0x000300f7, 0x00000050, + 0x00000000, 0x000400fa, 0x0000004e, 0x0000004f, 0x00000050, 0x000200f8, + 0x0000004f, 0x000100fd, 0x000200f8, 0x00000050, 0x0004003d, 0x00000006, + 0x00000054, 0x00000037, 0x00060041, 0x0000002b, 0x00000055, 0x00000048, + 0x00000053, 0x00000054, 0x0004003d, 0x00000006, 0x00000056, 0x00000055, + 0x0003003e, 0x00000052, 0x00000056, 0x0004003d, 0x00000006, 0x00000059, + 0x00000052, 0x0003003e, 0x00000058, 0x00000059, 0x00050039, 0x00000006, + 0x0000005a, 0x0000000f, 0x00000058, 0x0003003e, 0x00000057, 0x0000005a, + 0x0004003d, 0x00000006, 0x0000005d, 0x00000052, 0x0003003e, 0x0000005c, + 0x0000005d, 0x00050039, 0x00000006, 0x0000005e, 0x00000012, 0x0000005c, + 0x0003003e, 0x0000005b, 0x0000005e, 0x0004003d, 0x00000006, 0x0000005f, + 0x00000040, 0x0004003d, 0x00000006, 0x00000060, 0x0000005b, 0x000500ae, + 0x0000004d, 0x00000061, 0x0000005f, 0x00000060, 0x000300f7, 0x00000063, + 0x00000000, 0x000400fa, 0x00000061, 0x00000062, 0x00000063, 0x000200f8, + 0x00000062, 0x000100fd, 0x000200f8, 0x00000063, 0x0004003d, 0x00000006, + 0x0000006a, 0x00000057, 0x00050084, 0x00000006, 0x0000006c, 0x0000006a, + 0x0000006b, 0x0004003d, 0x00000006, 0x0000006d, 0x00000040, 0x00050084, + 0x00000006, 0x0000006e, 0x0000006d, 0x0000006b, 0x00050080, 0x00000006, + 0x0000006f, 0x0000006c, 0x0000006e, 0x00050080, 0x00000006, 0x00000070, + 0x0000006f, 0x0000002a, 0x00060041, 0x0000002b, 0x00000071, 0x00000069, + 0x00000024, 0x00000070, 0x0004003d, 0x00000006, 0x00000072, 0x00000071, + 0x0003003e, 0x00000065, 0x00000072, 0x0004003d, 0x00000006, 0x00000074, + 0x00000057, 0x00050084, 0x00000006, 0x00000075, 0x00000074, 0x0000006b, + 0x0004003d, 0x00000006, 0x00000076, 0x00000040, 0x00050084, 0x00000006, + 0x00000077, 0x00000076, 0x0000006b, 0x00050080, 0x00000006, 0x00000078, + 0x00000075, 0x00000077, 0x00050080, 0x00000006, 0x00000079, 0x00000078, + 0x00000028, 0x00060041, 0x0000002b, 0x0000007a, 0x00000069, 0x00000024, + 0x00000079, 0x0004003d, 0x00000006, 0x0000007b, 0x0000007a, 0x0003003e, + 0x00000073, 0x0000007b, 0x0004003d, 0x00000006, 0x0000007d, 0x00000057, + 0x00050084, 0x00000006, 0x0000007e, 0x0000007d, 0x0000006b, 0x0004003d, + 0x00000006, 0x0000007f, 0x00000040, 0x00050084, 0x00000006, 0x00000080, + 0x0000007f, 0x0000006b, 0x00050080, 0x00000006, 0x00000081, 0x0000007e, + 0x00000080, 0x00050080, 0x00000006, 0x00000083, 0x00000081, 0x00000082, + 0x00060041, 0x0000002b, 0x00000084, 0x00000069, 0x00000024, 0x00000083, + 0x0004003d, 0x00000006, 0x00000085, 0x00000084, 0x0003003e, 0x0000007c, + 0x00000085, 0x0004003d, 0x00000006, 0x0000008e, 0x00000065, 0x00050084, + 0x00000006, 0x0000008f, 0x0000006b, 0x0000008e, 0x00050080, 0x00000006, + 0x00000090, 0x0000008f, 0x0000002a, 0x00060041, 0x00000091, 0x00000092, + 0x0000008d, 0x00000024, 0x00000090, 0x0004003d, 0x00000086, 0x00000093, + 0x00000092, 0x0004003d, 0x00000006, 0x00000094, 0x00000065, 0x00050084, + 0x00000006, 0x00000095, 0x0000006b, 0x00000094, 0x00050080, 0x00000006, + 0x00000096, 0x00000095, 0x00000028, 0x00060041, 0x00000091, 0x00000097, + 0x0000008d, 0x00000024, 0x00000096, 0x0004003d, 0x00000086, 0x00000098, + 0x00000097, 0x0004003d, 0x00000006, 0x00000099, 0x00000065, 0x00050084, + 0x00000006, 0x0000009a, 0x0000006b, 0x00000099, 0x00050080, 0x00000006, + 0x0000009b, 0x0000009a, 0x00000082, 0x00060041, 0x00000091, 0x0000009c, + 0x0000008d, 0x00000024, 0x0000009b, 0x0004003d, 0x00000086, 0x0000009d, + 0x0000009c, 0x00060050, 0x00000087, 0x0000009e, 0x00000093, 0x00000098, + 0x0000009d, 0x0003003e, 0x00000089, 0x0000009e, 0x0004003d, 0x00000006, + 0x000000a0, 0x00000073, 0x00050084, 0x00000006, 0x000000a1, 0x0000006b, + 0x000000a0, 0x00050080, 0x00000006, 0x000000a2, 0x000000a1, 0x0000002a, + 0x00060041, 0x00000091, 0x000000a3, 0x0000008d, 0x00000024, 0x000000a2, + 0x0004003d, 0x00000086, 0x000000a4, 0x000000a3, 0x0004003d, 0x00000006, + 0x000000a5, 0x00000073, 0x00050084, 0x00000006, 0x000000a6, 0x0000006b, + 0x000000a5, 0x00050080, 0x00000006, 0x000000a7, 0x000000a6, 0x00000028, + 0x00060041, 0x00000091, 0x000000a8, 0x0000008d, 0x00000024, 0x000000a7, + 0x0004003d, 0x00000086, 0x000000a9, 0x000000a8, 0x0004003d, 0x00000006, + 0x000000aa, 0x00000073, 0x00050084, 0x00000006, 0x000000ab, 0x0000006b, + 0x000000aa, 0x00050080, 0x00000006, 0x000000ac, 0x000000ab, 0x00000082, + 0x00060041, 0x00000091, 0x000000ad, 0x0000008d, 0x00000024, 0x000000ac, + 0x0004003d, 0x00000086, 0x000000ae, 0x000000ad, 0x00060050, 0x00000087, + 0x000000af, 0x000000a4, 0x000000a9, 0x000000ae, 0x0003003e, 0x0000009f, + 0x000000af, 0x0004003d, 0x00000006, 0x000000b1, 0x0000007c, 0x00050084, + 0x00000006, 0x000000b2, 0x0000006b, 0x000000b1, 0x00050080, 0x00000006, + 0x000000b3, 0x000000b2, 0x0000002a, 0x00060041, 0x00000091, 0x000000b4, + 0x0000008d, 0x00000024, 0x000000b3, 0x0004003d, 0x00000086, 0x000000b5, + 0x000000b4, 0x0004003d, 0x00000006, 0x000000b6, 0x0000007c, 0x00050084, + 0x00000006, 0x000000b7, 0x0000006b, 0x000000b6, 0x00050080, 0x00000006, + 0x000000b8, 0x000000b7, 0x00000028, 0x00060041, 0x00000091, 0x000000b9, + 0x0000008d, 0x00000024, 0x000000b8, 0x0004003d, 0x00000086, 0x000000ba, + 0x000000b9, 0x0004003d, 0x00000006, 0x000000bb, 0x0000007c, 0x00050084, + 0x00000006, 0x000000bc, 0x0000006b, 0x000000bb, 0x00050080, 0x00000006, + 0x000000bd, 0x000000bc, 0x00000082, 0x00060041, 0x00000091, 0x000000be, + 0x0000008d, 0x00000024, 0x000000bd, 0x0004003d, 0x00000086, 0x000000bf, + 0x000000be, 0x00060050, 0x00000087, 0x000000c0, 0x000000b5, 0x000000ba, + 0x000000bf, 0x0003003e, 0x000000b0, 0x000000c0, 0x00060041, 0x000000cb, + 0x000000cc, 0x000000ca, 0x00000024, 0x00000024, 0x0004003d, 0x000000c4, + 0x000000cd, 0x000000cc, 0x00060041, 0x000000cb, 0x000000ce, 0x000000ca, + 0x00000024, 0x00000053, 0x0004003d, 0x000000c4, 0x000000cf, 0x000000ce, + 0x0004003d, 0x00000087, 0x000000d0, 0x00000089, 0x00050051, 0x00000086, + 0x000000d2, 0x000000d0, 0x00000000, 0x00050051, 0x00000086, 0x000000d3, + 0x000000d0, 0x00000001, 0x00050051, 0x00000086, 0x000000d4, 0x000000d0, + 0x00000002, 0x00070050, 0x000000c1, 0x000000d5, 0x000000d2, 0x000000d3, + 0x000000d4, 0x000000d1, 0x00050091, 0x000000c1, 0x000000d6, 0x000000cf, + 0x000000d5, 0x00050091, 0x000000c1, 0x000000d7, 0x000000cd, 0x000000d6, + 0x0003003e, 0x000000c3, 0x000000d7, 0x00060041, 0x000000cb, 0x000000d9, + 0x000000ca, 0x00000024, 0x00000024, 0x0004003d, 0x000000c4, 0x000000da, + 0x000000d9, 0x00060041, 0x000000cb, 0x000000db, 0x000000ca, 0x00000024, + 0x00000053, 0x0004003d, 0x000000c4, 0x000000dc, 0x000000db, 0x0004003d, + 0x00000087, 0x000000dd, 0x0000009f, 0x00050051, 0x00000086, 0x000000de, + 0x000000dd, 0x00000000, 0x00050051, 0x00000086, 0x000000df, 0x000000dd, + 0x00000001, 0x00050051, 0x00000086, 0x000000e0, 0x000000dd, 0x00000002, + 0x00070050, 0x000000c1, 0x000000e1, 0x000000de, 0x000000df, 0x000000e0, + 0x000000d1, 0x00050091, 0x000000c1, 0x000000e2, 0x000000dc, 0x000000e1, + 0x00050091, 0x000000c1, 0x000000e3, 0x000000da, 0x000000e2, 0x0003003e, + 0x000000d8, 0x000000e3, 0x00060041, 0x000000cb, 0x000000e5, 0x000000ca, + 0x00000024, 0x00000024, 0x0004003d, 0x000000c4, 0x000000e6, 0x000000e5, + 0x00060041, 0x000000cb, 0x000000e7, 0x000000ca, 0x00000024, 0x00000053, + 0x0004003d, 0x000000c4, 0x000000e8, 0x000000e7, 0x0004003d, 0x00000087, + 0x000000e9, 0x000000b0, 0x00050051, 0x00000086, 0x000000ea, 0x000000e9, + 0x00000000, 0x00050051, 0x00000086, 0x000000eb, 0x000000e9, 0x00000001, + 0x00050051, 0x00000086, 0x000000ec, 0x000000e9, 0x00000002, 0x00070050, + 0x000000c1, 0x000000ed, 0x000000ea, 0x000000eb, 0x000000ec, 0x000000d1, + 0x00050091, 0x000000c1, 0x000000ee, 0x000000e8, 0x000000ed, 0x00050091, + 0x000000c1, 0x000000ef, 0x000000e6, 0x000000ee, 0x0003003e, 0x000000e4, + 0x000000ef, 0x0004003d, 0x000000c1, 0x000000f3, 0x000000c3, 0x0007004f, + 0x000000f0, 0x000000f4, 0x000000f3, 0x000000f3, 0x00000000, 0x00000001, + 0x00050041, 0x000000f5, 0x000000f6, 0x000000c3, 0x0000006b, 0x0004003d, + 0x00000086, 0x000000f7, 0x000000f6, 0x00050050, 0x000000f0, 0x000000f8, + 0x000000f7, 0x000000f7, 0x00050088, 0x000000f0, 0x000000f9, 0x000000f4, + 0x000000f8, 0x0003003e, 0x000000f2, 0x000000f9, 0x0004003d, 0x000000c1, + 0x000000fb, 0x000000d8, 0x0007004f, 0x000000f0, 0x000000fc, 0x000000fb, + 0x000000fb, 0x00000000, 0x00000001, 0x00050041, 0x000000f5, 0x000000fd, + 0x000000d8, 0x0000006b, 0x0004003d, 0x00000086, 0x000000fe, 0x000000fd, + 0x00050050, 0x000000f0, 0x000000ff, 0x000000fe, 0x000000fe, 0x00050088, + 0x000000f0, 0x00000100, 0x000000fc, 0x000000ff, 0x0003003e, 0x000000fa, + 0x00000100, 0x0004003d, 0x000000c1, 0x00000102, 0x000000e4, 0x0007004f, + 0x000000f0, 0x00000103, 0x00000102, 0x00000102, 0x00000000, 0x00000001, + 0x00050041, 0x000000f5, 0x00000104, 0x000000e4, 0x0000006b, 0x0004003d, + 0x00000086, 0x00000105, 0x00000104, 0x00050050, 0x000000f0, 0x00000106, + 0x00000105, 0x00000105, 0x00050088, 0x000000f0, 0x00000107, 0x00000103, + 0x00000106, 0x0003003e, 0x00000101, 0x00000107, 0x0004003d, 0x000000f0, + 0x00000109, 0x000000f2, 0x0005008e, 0x000000f0, 0x0000010b, 0x00000109, + 0x0000010a, 0x00050050, 0x000000f0, 0x0000010c, 0x0000010a, 0x0000010a, + 0x00050081, 0x000000f0, 0x0000010d, 0x0000010b, 0x0000010c, 0x00060041, + 0x0000010f, 0x00000110, 0x000000ca, 0x00000024, 0x0000010e, 0x0004003d, + 0x000000c1, 0x00000111, 0x00000110, 0x0007004f, 0x000000f0, 0x00000112, + 0x00000111, 0x00000111, 0x00000000, 0x00000001, 0x00050085, 0x000000f0, + 0x00000113, 0x0000010d, 0x00000112, 0x0003003e, 0x00000108, 0x00000113, + 0x0004003d, 0x000000f0, 0x00000115, 0x000000fa, 0x0005008e, 0x000000f0, + 0x00000116, 0x00000115, 0x0000010a, 0x00050050, 0x000000f0, 0x00000117, + 0x0000010a, 0x0000010a, 0x00050081, 0x000000f0, 0x00000118, 0x00000116, + 0x00000117, 0x00060041, 0x0000010f, 0x00000119, 0x000000ca, 0x00000024, + 0x0000010e, 0x0004003d, 0x000000c1, 0x0000011a, 0x00000119, 0x0007004f, + 0x000000f0, 0x0000011b, 0x0000011a, 0x0000011a, 0x00000000, 0x00000001, + 0x00050085, 0x000000f0, 0x0000011c, 0x00000118, 0x0000011b, 0x0003003e, + 0x00000114, 0x0000011c, 0x0004003d, 0x000000f0, 0x0000011e, 0x00000101, + 0x0005008e, 0x000000f0, 0x0000011f, 0x0000011e, 0x0000010a, 0x00050050, + 0x000000f0, 0x00000120, 0x0000010a, 0x0000010a, 0x00050081, 0x000000f0, + 0x00000121, 0x0000011f, 0x00000120, 0x00060041, 0x0000010f, 0x00000122, + 0x000000ca, 0x00000024, 0x0000010e, 0x0004003d, 0x000000c1, 0x00000123, + 0x00000122, 0x0007004f, 0x000000f0, 0x00000124, 0x00000123, 0x00000123, + 0x00000000, 0x00000001, 0x00050085, 0x000000f0, 0x00000125, 0x00000121, + 0x00000124, 0x0003003e, 0x0000011d, 0x00000125, 0x00050041, 0x000000f5, + 0x00000127, 0x00000114, 0x0000002a, 0x0004003d, 0x00000086, 0x00000128, + 0x00000127, 0x00050041, 0x000000f5, 0x00000129, 0x00000108, 0x0000002a, + 0x0004003d, 0x00000086, 0x0000012a, 0x00000129, 0x00050083, 0x00000086, + 0x0000012b, 0x00000128, 0x0000012a, 0x00050041, 0x000000f5, 0x0000012c, + 0x0000011d, 0x00000028, 0x0004003d, 0x00000086, 0x0000012d, 0x0000012c, + 0x00050041, 0x000000f5, 0x0000012e, 0x00000108, 0x00000028, 0x0004003d, + 0x00000086, 0x0000012f, 0x0000012e, 0x00050083, 0x00000086, 0x00000130, + 0x0000012d, 0x0000012f, 0x00050085, 0x00000086, 0x00000131, 0x0000012b, + 0x00000130, 0x00050041, 0x000000f5, 0x00000132, 0x00000114, 0x00000028, + 0x0004003d, 0x00000086, 0x00000133, 0x00000132, 0x00050041, 0x000000f5, + 0x00000134, 0x00000108, 0x00000028, 0x0004003d, 0x00000086, 0x00000135, + 0x00000134, 0x00050083, 0x00000086, 0x00000136, 0x00000133, 0x00000135, + 0x00050041, 0x000000f5, 0x00000137, 0x0000011d, 0x0000002a, 0x0004003d, + 0x00000086, 0x00000138, 0x00000137, 0x00050041, 0x000000f5, 0x00000139, + 0x00000108, 0x0000002a, 0x0004003d, 0x00000086, 0x0000013a, 0x00000139, + 0x00050083, 0x00000086, 0x0000013b, 0x00000138, 0x0000013a, 0x00050085, + 0x00000086, 0x0000013c, 0x00000136, 0x0000013b, 0x00050083, 0x00000086, + 0x0000013d, 0x00000131, 0x0000013c, 0x0003003e, 0x00000126, 0x0000013d, + 0x0004003d, 0x00000086, 0x0000013e, 0x00000126, 0x000500bc, 0x0000004d, + 0x00000140, 0x0000013e, 0x0000013f, 0x000300f7, 0x00000142, 0x00000000, + 0x000400fa, 0x00000140, 0x00000141, 0x00000142, 0x000200f8, 0x00000141, + 0x000100fd, 0x000200f8, 0x00000142, 0x0004003d, 0x000000f0, 0x00000147, + 0x00000108, 0x0004003d, 0x000000f0, 0x00000148, 0x00000114, 0x0007000c, + 0x000000f0, 0x00000149, 0x00000001, 0x00000025, 0x00000147, 0x00000148, + 0x0004003d, 0x000000f0, 0x0000014a, 0x0000011d, 0x0007000c, 0x000000f0, + 0x0000014b, 0x00000001, 0x00000025, 0x00000149, 0x0000014a, 0x0006000c, + 0x000000f0, 0x0000014c, 0x00000001, 0x00000008, 0x0000014b, 0x0004006e, + 0x00000144, 0x0000014d, 0x0000014c, 0x0003003e, 0x00000146, 0x0000014d, + 0x0004003d, 0x000000f0, 0x0000014f, 0x00000108, 0x0004003d, 0x000000f0, + 0x00000150, 0x00000114, 0x0007000c, 0x000000f0, 0x00000151, 0x00000001, + 0x00000028, 0x0000014f, 0x00000150, 0x0004003d, 0x000000f0, 0x00000152, + 0x0000011d, 0x0007000c, 0x000000f0, 0x00000153, 0x00000001, 0x00000028, + 0x00000151, 0x00000152, 0x0006000c, 0x000000f0, 0x00000154, 0x00000001, + 0x00000009, 0x00000153, 0x0004006e, 0x00000144, 0x00000155, 0x00000154, + 0x0003003e, 0x0000014e, 0x00000155, 0x00060041, 0x0000010f, 0x00000157, + 0x000000ca, 0x00000024, 0x0000010e, 0x0004003d, 0x000000c1, 0x00000158, + 0x00000157, 0x0007004f, 0x000000f0, 0x00000159, 0x00000158, 0x00000158, + 0x00000000, 0x00000001, 0x0004006e, 0x00000144, 0x0000015a, 0x00000159, + 0x0003003e, 0x00000156, 0x0000015a, 0x0004003d, 0x00000144, 0x0000015b, + 0x00000146, 0x0004003d, 0x00000144, 0x0000015d, 0x00000156, 0x00050082, + 0x00000144, 0x0000015f, 0x0000015d, 0x0000015e, 0x0008000c, 0x00000144, + 0x00000160, 0x00000001, 0x0000002d, 0x0000015b, 0x0000015c, 0x0000015f, + 0x0003003e, 0x00000146, 0x00000160, 0x0004003d, 0x00000144, 0x00000161, + 0x0000014e, 0x0004003d, 0x00000144, 0x00000162, 0x00000156, 0x00050082, + 0x00000144, 0x00000163, 0x00000162, 0x0000015e, 0x0008000c, 0x00000144, + 0x00000164, 0x00000001, 0x0000002d, 0x00000161, 0x0000015c, 0x00000163, + 0x0003003e, 0x0000014e, 0x00000164, 0x0004003d, 0x00000086, 0x00000166, + 0x00000126, 0x00050088, 0x00000086, 0x00000167, 0x000000d1, 0x00000166, + 0x0003003e, 0x00000165, 0x00000167, 0x00050041, 0x00000168, 0x0000016a, + 0x00000156, 0x0000002a, 0x0004003d, 0x00000017, 0x0000016b, 0x0000016a, + 0x0003003e, 0x00000169, 0x0000016b, 0x00050041, 0x00000168, 0x0000016d, + 0x00000146, 0x00000028, 0x0004003d, 0x00000017, 0x0000016e, 0x0000016d, + 0x0003003e, 0x0000016c, 0x0000016e, 0x000200f9, 0x0000016f, 0x000200f8, + 0x0000016f, 0x000400f6, 0x00000171, 0x00000172, 0x00000000, 0x000200f9, + 0x00000173, 0x000200f8, 0x00000173, 0x0004003d, 0x00000017, 0x00000174, + 0x0000016c, 0x00050041, 0x00000168, 0x00000175, 0x0000014e, 0x00000028, + 0x0004003d, 0x00000017, 0x00000176, 0x00000175, 0x000500b3, 0x0000004d, + 0x00000177, 0x00000174, 0x00000176, 0x000400fa, 0x00000177, 0x00000170, + 0x00000171, 0x000200f8, 0x00000170, 0x00050041, 0x00000168, 0x00000179, + 0x00000146, 0x0000002a, 0x0004003d, 0x00000017, 0x0000017a, 0x00000179, + 0x0003003e, 0x00000178, 0x0000017a, 0x000200f9, 0x0000017b, 0x000200f8, + 0x0000017b, 0x000400f6, 0x0000017d, 0x0000017e, 0x00000000, 0x000200f9, + 0x0000017f, 0x000200f8, 0x0000017f, 0x0004003d, 0x00000017, 0x00000180, + 0x00000178, 0x00050041, 0x00000168, 0x00000181, 0x0000014e, 0x0000002a, + 0x0004003d, 0x00000017, 0x00000182, 0x00000181, 0x000500b3, 0x0000004d, + 0x00000183, 0x00000180, 0x00000182, 0x000400fa, 0x00000183, 0x0000017c, + 0x0000017d, 0x000200f8, 0x0000017c, 0x0004003d, 0x00000017, 0x00000185, + 0x00000178, 0x0004006f, 0x00000086, 0x00000186, 0x00000185, 0x00050081, + 0x00000086, 0x00000187, 0x00000186, 0x0000010a, 0x0004003d, 0x00000017, + 0x00000188, 0x0000016c, 0x0004006f, 0x00000086, 0x00000189, 0x00000188, + 0x00050081, 0x00000086, 0x0000018a, 0x00000189, 0x0000010a, 0x00050050, + 0x000000f0, 0x0000018b, 0x00000187, 0x0000018a, 0x0003003e, 0x00000184, + 0x0000018b, 0x00050041, 0x000000f5, 0x0000018d, 0x00000114, 0x0000002a, + 0x0004003d, 0x00000086, 0x0000018e, 0x0000018d, 0x00050041, 0x000000f5, + 0x0000018f, 0x00000108, 0x0000002a, 0x0004003d, 0x00000086, 0x00000190, + 0x0000018f, 0x00050083, 0x00000086, 0x00000191, 0x0000018e, 0x00000190, + 0x00050041, 0x000000f5, 0x00000192, 0x00000184, 0x00000028, 0x0004003d, + 0x00000086, 0x00000193, 0x00000192, 0x00050041, 0x000000f5, 0x00000194, + 0x00000108, 0x00000028, 0x0004003d, 0x00000086, 0x00000195, 0x00000194, + 0x00050083, 0x00000086, 0x00000196, 0x00000193, 0x00000195, 0x00050085, + 0x00000086, 0x00000197, 0x00000191, 0x00000196, 0x00050041, 0x000000f5, + 0x00000198, 0x00000114, 0x00000028, 0x0004003d, 0x00000086, 0x00000199, + 0x00000198, 0x00050041, 0x000000f5, 0x0000019a, 0x00000108, 0x00000028, + 0x0004003d, 0x00000086, 0x0000019b, 0x0000019a, 0x00050083, 0x00000086, + 0x0000019c, 0x00000199, 0x0000019b, 0x00050041, 0x000000f5, 0x0000019d, + 0x00000184, 0x0000002a, 0x0004003d, 0x00000086, 0x0000019e, 0x0000019d, + 0x00050041, 0x000000f5, 0x0000019f, 0x00000108, 0x0000002a, 0x0004003d, + 0x00000086, 0x000001a0, 0x0000019f, 0x00050083, 0x00000086, 0x000001a1, + 0x0000019e, 0x000001a0, 0x00050085, 0x00000086, 0x000001a2, 0x0000019c, + 0x000001a1, 0x00050083, 0x00000086, 0x000001a3, 0x00000197, 0x000001a2, + 0x0003003e, 0x0000018c, 0x000001a3, 0x00050041, 0x000000f5, 0x000001a5, + 0x0000011d, 0x0000002a, 0x0004003d, 0x00000086, 0x000001a6, 0x000001a5, + 0x00050041, 0x000000f5, 0x000001a7, 0x00000114, 0x0000002a, 0x0004003d, + 0x00000086, 0x000001a8, 0x000001a7, 0x00050083, 0x00000086, 0x000001a9, + 0x000001a6, 0x000001a8, 0x00050041, 0x000000f5, 0x000001aa, 0x00000184, + 0x00000028, 0x0004003d, 0x00000086, 0x000001ab, 0x000001aa, 0x00050041, + 0x000000f5, 0x000001ac, 0x00000114, 0x00000028, 0x0004003d, 0x00000086, + 0x000001ad, 0x000001ac, 0x00050083, 0x00000086, 0x000001ae, 0x000001ab, + 0x000001ad, 0x00050085, 0x00000086, 0x000001af, 0x000001a9, 0x000001ae, + 0x00050041, 0x000000f5, 0x000001b0, 0x0000011d, 0x00000028, 0x0004003d, + 0x00000086, 0x000001b1, 0x000001b0, 0x00050041, 0x000000f5, 0x000001b2, + 0x00000114, 0x00000028, 0x0004003d, 0x00000086, 0x000001b3, 0x000001b2, + 0x00050083, 0x00000086, 0x000001b4, 0x000001b1, 0x000001b3, 0x00050041, + 0x000000f5, 0x000001b5, 0x00000184, 0x0000002a, 0x0004003d, 0x00000086, + 0x000001b6, 0x000001b5, 0x00050041, 0x000000f5, 0x000001b7, 0x00000114, + 0x0000002a, 0x0004003d, 0x00000086, 0x000001b8, 0x000001b7, 0x00050083, + 0x00000086, 0x000001b9, 0x000001b6, 0x000001b8, 0x00050085, 0x00000086, + 0x000001ba, 0x000001b4, 0x000001b9, 0x00050083, 0x00000086, 0x000001bb, + 0x000001af, 0x000001ba, 0x0003003e, 0x000001a4, 0x000001bb, 0x00050041, + 0x000000f5, 0x000001bd, 0x00000108, 0x0000002a, 0x0004003d, 0x00000086, + 0x000001be, 0x000001bd, 0x00050041, 0x000000f5, 0x000001bf, 0x0000011d, + 0x0000002a, 0x0004003d, 0x00000086, 0x000001c0, 0x000001bf, 0x00050083, + 0x00000086, 0x000001c1, 0x000001be, 0x000001c0, 0x00050041, 0x000000f5, + 0x000001c2, 0x00000184, 0x00000028, 0x0004003d, 0x00000086, 0x000001c3, + 0x000001c2, 0x00050041, 0x000000f5, 0x000001c4, 0x0000011d, 0x00000028, + 0x0004003d, 0x00000086, 0x000001c5, 0x000001c4, 0x00050083, 0x00000086, + 0x000001c6, 0x000001c3, 0x000001c5, 0x00050085, 0x00000086, 0x000001c7, + 0x000001c1, 0x000001c6, 0x00050041, 0x000000f5, 0x000001c8, 0x00000108, + 0x00000028, 0x0004003d, 0x00000086, 0x000001c9, 0x000001c8, 0x00050041, + 0x000000f5, 0x000001ca, 0x0000011d, 0x00000028, 0x0004003d, 0x00000086, + 0x000001cb, 0x000001ca, 0x00050083, 0x00000086, 0x000001cc, 0x000001c9, + 0x000001cb, 0x00050041, 0x000000f5, 0x000001cd, 0x00000184, 0x0000002a, + 0x0004003d, 0x00000086, 0x000001ce, 0x000001cd, 0x00050041, 0x000000f5, + 0x000001cf, 0x0000011d, 0x0000002a, 0x0004003d, 0x00000086, 0x000001d0, + 0x000001cf, 0x00050083, 0x00000086, 0x000001d1, 0x000001ce, 0x000001d0, + 0x00050085, 0x00000086, 0x000001d2, 0x000001cc, 0x000001d1, 0x00050083, + 0x00000086, 0x000001d3, 0x000001c7, 0x000001d2, 0x0003003e, 0x000001bc, + 0x000001d3, 0x0004003d, 0x00000086, 0x000001d4, 0x0000018c, 0x000500b8, + 0x0000004d, 0x000001d5, 0x000001d4, 0x0000013f, 0x0004003d, 0x00000086, + 0x000001d6, 0x000001a4, 0x000500b8, 0x0000004d, 0x000001d7, 0x000001d6, + 0x0000013f, 0x000500a6, 0x0000004d, 0x000001d8, 0x000001d5, 0x000001d7, + 0x0004003d, 0x00000086, 0x000001d9, 0x000001bc, 0x000500b8, 0x0000004d, + 0x000001da, 0x000001d9, 0x0000013f, 0x000500a6, 0x0000004d, 0x000001db, + 0x000001d8, 0x000001da, 0x000300f7, 0x000001dd, 0x00000000, 0x000400fa, + 0x000001db, 0x000001dc, 0x000001dd, 0x000200f8, 0x000001dc, 0x000200f9, + 0x0000017e, 0x000200f8, 0x000001dd, 0x0004003d, 0x00000086, 0x000001e0, + 0x000001a4, 0x0004003d, 0x00000086, 0x000001e1, 0x00000165, 0x00050085, + 0x00000086, 0x000001e2, 0x000001e0, 0x000001e1, 0x0003003e, 0x000001df, + 0x000001e2, 0x0004003d, 0x00000086, 0x000001e4, 0x000001bc, 0x0004003d, + 0x00000086, 0x000001e5, 0x00000165, 0x00050085, 0x00000086, 0x000001e6, + 0x000001e4, 0x000001e5, 0x0003003e, 0x000001e3, 0x000001e6, 0x0004003d, + 0x00000086, 0x000001e8, 0x0000018c, 0x0004003d, 0x00000086, 0x000001e9, + 0x00000165, 0x00050085, 0x00000086, 0x000001ea, 0x000001e8, 0x000001e9, + 0x0003003e, 0x000001e7, 0x000001ea, 0x0004003d, 0x00000086, 0x000001ec, + 0x000001df, 0x00050041, 0x000000f5, 0x000001ed, 0x000000c3, 0x00000082, + 0x0004003d, 0x00000086, 0x000001ee, 0x000001ed, 0x00050041, 0x000000f5, + 0x000001ef, 0x000000c3, 0x0000006b, 0x0004003d, 0x00000086, 0x000001f0, + 0x000001ef, 0x00050088, 0x00000086, 0x000001f1, 0x000001ee, 0x000001f0, + 0x00050085, 0x00000086, 0x000001f2, 0x000001ec, 0x000001f1, 0x0004003d, + 0x00000086, 0x000001f3, 0x000001e3, 0x00050041, 0x000000f5, 0x000001f4, + 0x000000d8, 0x00000082, 0x0004003d, 0x00000086, 0x000001f5, 0x000001f4, + 0x00050041, 0x000000f5, 0x000001f6, 0x000000d8, 0x0000006b, 0x0004003d, + 0x00000086, 0x000001f7, 0x000001f6, 0x00050088, 0x00000086, 0x000001f8, + 0x000001f5, 0x000001f7, 0x00050085, 0x00000086, 0x000001f9, 0x000001f3, + 0x000001f8, 0x00050081, 0x00000086, 0x000001fa, 0x000001f2, 0x000001f9, + 0x0004003d, 0x00000086, 0x000001fb, 0x000001e7, 0x00050041, 0x000000f5, + 0x000001fc, 0x000000e4, 0x00000082, 0x0004003d, 0x00000086, 0x000001fd, + 0x000001fc, 0x00050041, 0x000000f5, 0x000001fe, 0x000000e4, 0x0000006b, + 0x0004003d, 0x00000086, 0x000001ff, 0x000001fe, 0x00050088, 0x00000086, + 0x00000200, 0x000001fd, 0x000001ff, 0x00050085, 0x00000086, 0x00000201, + 0x000001fb, 0x00000200, 0x00050081, 0x00000086, 0x00000202, 0x000001fa, + 0x00000201, 0x0003003e, 0x000001eb, 0x00000202, 0x0004003d, 0x00000086, + 0x00000204, 0x000001eb, 0x0008000c, 0x00000086, 0x00000205, 0x00000001, + 0x0000002b, 0x00000204, 0x0000013f, 0x000000d1, 0x00050085, 0x00000086, + 0x00000207, 0x00000205, 0x00000206, 0x0004006d, 0x00000006, 0x00000208, + 0x00000207, 0x0003003e, 0x00000203, 0x00000208, 0x0004003d, 0x00000006, + 0x0000020b, 0x00000203, 0x0003003e, 0x0000020a, 0x0000020b, 0x0004003d, + 0x00000006, 0x0000020d, 0x00000052, 0x0003003e, 0x0000020c, 0x0000020d, + 0x00060039, 0x00000006, 0x0000020e, 0x0000000b, 0x0000020a, 0x0000020c, + 0x0003003e, 0x00000209, 0x0000020e, 0x0004003d, 0x00000017, 0x00000213, + 0x0000016c, 0x0004007c, 0x00000006, 0x00000214, 0x00000213, 0x0004003d, + 0x00000017, 0x00000215, 0x00000169, 0x0004007c, 0x00000006, 0x00000216, + 0x00000215, 0x00050084, 0x00000006, 0x00000217, 0x00000214, 0x00000216, + 0x0004003d, 0x00000017, 0x00000218, 0x00000178, 0x0004007c, 0x00000006, + 0x00000219, 0x00000218, 0x00050080, 0x00000006, 0x0000021a, 0x00000217, + 0x00000219, 0x00060041, 0x0000002b, 0x0000021b, 0x00000212, 0x00000024, + 0x0000021a, 0x0004003d, 0x00000006, 0x0000021c, 0x00000209, 0x000700ed, + 0x00000006, 0x0000021d, 0x0000021b, 0x00000028, 0x0000002a, 0x0000021c, + 0x000200f9, 0x0000017e, 0x000200f8, 0x0000017e, 0x0004003d, 0x00000017, + 0x0000021e, 0x00000178, 0x00050080, 0x00000017, 0x0000021f, 0x0000021e, + 0x00000053, 0x0003003e, 0x00000178, 0x0000021f, 0x000200f9, 0x0000017b, + 0x000200f8, 0x0000017d, 0x000200f9, 0x00000172, 0x000200f8, 0x00000172, + 0x0004003d, 0x00000017, 0x00000220, 0x0000016c, 0x00050080, 0x00000017, + 0x00000221, 0x00000220, 0x00000053, 0x0003003e, 0x0000016c, 0x00000221, + 0x000200f9, 0x0000016f, 0x000200f8, 0x00000171, 0x00070041, 0x0000002b, + 0x00000226, 0x00000225, 0x00000024, 0x00000053, 0x0000002a, 0x000700ea, + 0x00000006, 0x00000227, 0x00000226, 0x00000028, 0x0000002a, 0x00000028, + 0x000100fd, 0x00010038, 0x00050036, 0x00000006, 0x0000000b, 0x00000000, + 0x00000008, 0x00030037, 0x00000007, 0x00000009, 0x00030037, 0x00000007, + 0x0000000a, 0x000200f8, 0x0000000c, 0x0004003d, 0x00000006, 0x00000014, + 0x00000009, 0x0007000c, 0x00000006, 0x00000016, 0x00000001, 0x00000026, + 0x00000014, 0x00000015, 0x000500c4, 0x00000006, 0x00000019, 0x00000016, + 0x00000018, 0x0004003d, 0x00000006, 0x0000001a, 0x0000000a, 0x000500c7, + 0x00000006, 0x0000001b, 0x0000001a, 0x00000015, 0x000500c5, 0x00000006, + 0x0000001c, 0x00000019, 0x0000001b, 0x000200fe, 0x0000001c, 0x00010038, + 0x00050036, 0x00000006, 0x0000000f, 0x00000000, 0x0000000d, 0x00030037, + 0x00000007, 0x0000000e, 0x000200f8, 0x00000010, 0x0004003d, 0x00000006, + 0x00000025, 0x0000000e, 0x00050084, 0x00000006, 0x00000027, 0x00000025, + 0x00000026, 0x00050080, 0x00000006, 0x00000029, 0x00000027, 0x00000028, + 0x00070041, 0x0000002b, 0x0000002c, 0x00000023, 0x00000024, 0x00000029, + 0x0000002a, 0x0004003d, 0x00000006, 0x0000002d, 0x0000002c, 0x000200fe, + 0x0000002d, 0x00010038, 0x00050036, 0x00000006, 0x00000012, 0x00000000, + 0x0000000d, 0x00030037, 0x00000007, 0x00000011, 0x000200f8, 0x00000013, + 0x0004003d, 0x00000006, 0x00000030, 0x00000011, 0x00050084, 0x00000006, + 0x00000031, 0x00000030, 0x00000026, 0x00050080, 0x00000006, 0x00000032, + 0x00000031, 0x00000028, 0x00070041, 0x0000002b, 0x00000033, 0x00000023, + 0x00000024, 0x00000032, 0x00000028, 0x0004003d, 0x00000006, 0x00000034, + 0x00000033, 0x000200fe, 0x00000034, 0x00010038, +}; +static const size_t vg_raster_compute_spv_count = sizeof(vg_raster_compute_spv) / sizeof(vg_raster_compute_spv[0]); diff --git a/src/modules/virtualgeometry/shaders/vg_resolve.frag b/src/modules/virtualgeometry/shaders/vg_resolve.frag new file mode 100644 index 00000000..5e337c2e --- /dev/null +++ b/src/modules/virtualgeometry/shaders/vg_resolve.frag @@ -0,0 +1,26 @@ +// Resolve: sample the visibility buffer (packed depth<<16 | clusterId) and emit +// a shaded color. Cluster id -> stable color via a small hash; depth used for a +// subtle gradient. Optionally visualize LOD level via the misc uniform (x). +#version 450 + +#include "vg_common.glsl" + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 color; + +void main() { + ivec2 px = ivec2(uv * vgU.params.xy); + uint packed = vgPix[uint(px.y) * uint(vgU.params.x) + uint(px.x)]; + uint cid = vg_clusterOf(packed); + uint depth = vg_depthOf(packed); + if (cid == 0u && depth == 0u) { + color = vec4(0.02, 0.02, 0.04, 1.0); // empty + return; + } + // Stable per-cluster color hash. + uint h = cid * 2654435761u; + vec3 tint = vec3(float((h >> 16) & 0xFFu), float((h >> 8) & 0xFFu), float(h & 0xFFu)) / 255.0; + tint = normalize(max(tint, vec3(0.05))); + float depthF = float(depth) / 65535.0; + color = vec4(tint * (0.35 + 0.65 * (1.0 - depthF)), 1.0); +} diff --git a/src/modules/virtualgeometry/shaders/vg_resolve.vert b/src/modules/virtualgeometry/shaders/vg_resolve.vert new file mode 100644 index 00000000..4b00d85b --- /dev/null +++ b/src/modules/virtualgeometry/shaders/vg_resolve.vert @@ -0,0 +1,13 @@ +// Resolve: fullscreen triangle producing a shading pass over the visibility +// buffer. Provided as the production path for compositing into the render frame; +// the module's demo path resolves on the CPU after reading the buffer back. +#version 450 + +layout(location = 0) out vec2 uv; + +void main() { + vec2 verts[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0)); + vec2 pos = verts[gl_VertexIndex]; + uv = pos * 0.5 + 0.5; + gl_Position = vec4(pos, 0.0, 1.0); +} diff --git a/src/modules/virtualgeometry/shaders/vg_resolve_fragment.spv b/src/modules/virtualgeometry/shaders/vg_resolve_fragment.spv new file mode 100644 index 00000000..c1cdc0f1 Binary files /dev/null and b/src/modules/virtualgeometry/shaders/vg_resolve_fragment.spv differ diff --git a/src/modules/virtualgeometry/shaders/vg_resolve_fragment_spv.inc b/src/modules/virtualgeometry/shaders/vg_resolve_fragment_spv.inc new file mode 100644 index 00000000..8ed9c941 --- /dev/null +++ b/src/modules/virtualgeometry/shaders/vg_resolve_fragment_spv.inc @@ -0,0 +1,203 @@ +// Generated by compile_virtualgeometry_shaders.py. Do not edit. +#pragma once +static const uint32_t vg_resolve_fragment_spv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x000000a4, 0x00000000, 0x00020011, + 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, + 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000004, + 0x00000004, 0x6e69616d, 0x00000000, 0x00000020, 0x0000005a, 0x00030010, + 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, + 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, + 0x7269645f, 0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f, + 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005, + 0x00000004, 0x6e69616d, 0x00000000, 0x00060005, 0x0000000a, 0x645f6776, + 0x68747065, 0x7528664f, 0x00003b31, 0x00040005, 0x00000009, 0x6b636170, + 0x00006465, 0x00070005, 0x0000000d, 0x635f6776, 0x7473756c, 0x664f7265, + 0x3b317528, 0x00000000, 0x00040005, 0x0000000c, 0x6b636170, 0x00006465, + 0x00030005, 0x0000001c, 0x00007870, 0x00030005, 0x00000020, 0x00007675, + 0x00050005, 0x00000026, 0x6e556756, 0x726f6669, 0x0000736d, 0x00060006, + 0x00000026, 0x00000000, 0x77656976, 0x6a6f7250, 0x00000000, 0x00050006, + 0x00000026, 0x00000001, 0x65646f6d, 0x0000006c, 0x00060006, 0x00000026, + 0x00000002, 0x656d6163, 0x6f506172, 0x00000073, 0x00050006, 0x00000026, + 0x00000003, 0x61726170, 0x0000736d, 0x00050006, 0x00000026, 0x00000004, + 0x73757266, 0x006d7574, 0x00050006, 0x00000026, 0x00000005, 0x6373696d, + 0x00000000, 0x00060005, 0x00000027, 0x6e556756, 0x726f6669, 0x6f6c426d, + 0x00006b63, 0x00040006, 0x00000027, 0x00000000, 0x00556776, 0x00030005, + 0x00000029, 0x00000000, 0x00040005, 0x00000032, 0x6b636170, 0x00006465, + 0x00050005, 0x00000034, 0x69506756, 0x736c6578, 0x00000000, 0x00050006, + 0x00000034, 0x00000000, 0x69506776, 0x00000078, 0x00030005, 0x00000036, + 0x00000000, 0x00030005, 0x00000049, 0x00646963, 0x00040005, 0x0000004a, + 0x61726170, 0x0000006d, 0x00040005, 0x0000004d, 0x74706564, 0x00000068, + 0x00040005, 0x0000004e, 0x61726170, 0x0000006d, 0x00040005, 0x0000005a, + 0x6f6c6f63, 0x00000072, 0x00030005, 0x00000060, 0x00000068, 0x00040005, + 0x00000066, 0x746e6974, 0x00000000, 0x00040005, 0x0000007e, 0x74706564, + 0x00004668, 0x00050005, 0x00000090, 0x6f506756, 0x69746973, 0x00736e6f, + 0x00050006, 0x00000090, 0x00000000, 0x6f506776, 0x00000073, 0x00030005, + 0x00000092, 0x00000000, 0x00050005, 0x00000094, 0x72546756, 0x676e6169, + 0x0073656c, 0x00050006, 0x00000094, 0x00000000, 0x72546776, 0x00000069, + 0x00030005, 0x00000096, 0x00000000, 0x00050005, 0x00000099, 0x6c436756, + 0x65747375, 0x00007372, 0x00050006, 0x00000099, 0x00000000, 0x6c436776, + 0x00000000, 0x00030005, 0x0000009b, 0x00000000, 0x00050005, 0x0000009d, + 0x69566756, 0x6c626973, 0x00000065, 0x00060006, 0x0000009d, 0x00000000, + 0x6f436776, 0x65746e75, 0x00000072, 0x00050006, 0x0000009d, 0x00000001, + 0x69566776, 0x00000073, 0x00030005, 0x0000009f, 0x00000000, 0x00040005, + 0x000000a1, 0x74536756, 0x00737461, 0x00050006, 0x000000a1, 0x00000000, + 0x74536776, 0x00007461, 0x00030005, 0x000000a3, 0x00000000, 0x00040047, + 0x00000020, 0x0000001e, 0x00000000, 0x00040047, 0x00000025, 0x00000006, + 0x00000010, 0x00040048, 0x00000026, 0x00000000, 0x00000005, 0x00050048, + 0x00000026, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000026, + 0x00000000, 0x00000023, 0x00000000, 0x00040048, 0x00000026, 0x00000001, + 0x00000005, 0x00050048, 0x00000026, 0x00000001, 0x00000007, 0x00000010, + 0x00050048, 0x00000026, 0x00000001, 0x00000023, 0x00000040, 0x00050048, + 0x00000026, 0x00000002, 0x00000023, 0x00000080, 0x00050048, 0x00000026, + 0x00000003, 0x00000023, 0x00000090, 0x00050048, 0x00000026, 0x00000004, + 0x00000023, 0x000000a0, 0x00050048, 0x00000026, 0x00000005, 0x00000023, + 0x00000100, 0x00030047, 0x00000027, 0x00000003, 0x00040048, 0x00000027, + 0x00000000, 0x00000018, 0x00050048, 0x00000027, 0x00000000, 0x00000023, + 0x00000000, 0x00030047, 0x00000029, 0x00000018, 0x00040047, 0x00000029, + 0x00000021, 0x00000005, 0x00040047, 0x00000029, 0x00000022, 0x00000000, + 0x00040047, 0x00000033, 0x00000006, 0x00000004, 0x00030047, 0x00000034, + 0x00000003, 0x00050048, 0x00000034, 0x00000000, 0x00000023, 0x00000000, + 0x00040047, 0x00000036, 0x00000021, 0x00000004, 0x00040047, 0x00000036, + 0x00000022, 0x00000000, 0x00040047, 0x0000005a, 0x0000001e, 0x00000000, + 0x00040047, 0x0000008f, 0x00000006, 0x00000004, 0x00030047, 0x00000090, + 0x00000003, 0x00040048, 0x00000090, 0x00000000, 0x00000018, 0x00050048, + 0x00000090, 0x00000000, 0x00000023, 0x00000000, 0x00030047, 0x00000092, + 0x00000018, 0x00040047, 0x00000092, 0x00000021, 0x00000000, 0x00040047, + 0x00000092, 0x00000022, 0x00000000, 0x00040047, 0x00000093, 0x00000006, + 0x00000004, 0x00030047, 0x00000094, 0x00000003, 0x00040048, 0x00000094, + 0x00000000, 0x00000018, 0x00050048, 0x00000094, 0x00000000, 0x00000023, + 0x00000000, 0x00030047, 0x00000096, 0x00000018, 0x00040047, 0x00000096, + 0x00000021, 0x00000001, 0x00040047, 0x00000096, 0x00000022, 0x00000000, + 0x00040047, 0x00000098, 0x00000006, 0x00000010, 0x00030047, 0x00000099, + 0x00000003, 0x00040048, 0x00000099, 0x00000000, 0x00000018, 0x00050048, + 0x00000099, 0x00000000, 0x00000023, 0x00000000, 0x00030047, 0x0000009b, + 0x00000018, 0x00040047, 0x0000009b, 0x00000021, 0x00000002, 0x00040047, + 0x0000009b, 0x00000022, 0x00000000, 0x00040047, 0x0000009c, 0x00000006, + 0x00000004, 0x00030047, 0x0000009d, 0x00000003, 0x00050048, 0x0000009d, + 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x0000009d, 0x00000001, + 0x00000023, 0x00000004, 0x00040047, 0x0000009f, 0x00000021, 0x00000003, + 0x00040047, 0x0000009f, 0x00000022, 0x00000000, 0x00040047, 0x000000a0, + 0x00000006, 0x00000010, 0x00030047, 0x000000a1, 0x00000003, 0x00050048, + 0x000000a1, 0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x000000a3, + 0x00000021, 0x00000006, 0x00040047, 0x000000a3, 0x00000022, 0x00000000, + 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00040015, + 0x00000006, 0x00000020, 0x00000000, 0x00040020, 0x00000007, 0x00000007, + 0x00000006, 0x00040021, 0x00000008, 0x00000006, 0x00000007, 0x00040015, + 0x00000010, 0x00000020, 0x00000001, 0x0004002b, 0x00000010, 0x00000011, + 0x00000010, 0x0004002b, 0x00000006, 0x00000016, 0x0000ffff, 0x00040017, + 0x0000001a, 0x00000010, 0x00000002, 0x00040020, 0x0000001b, 0x00000007, + 0x0000001a, 0x00030016, 0x0000001d, 0x00000020, 0x00040017, 0x0000001e, + 0x0000001d, 0x00000002, 0x00040020, 0x0000001f, 0x00000001, 0x0000001e, + 0x0004003b, 0x0000001f, 0x00000020, 0x00000001, 0x00040017, 0x00000022, + 0x0000001d, 0x00000004, 0x00040018, 0x00000023, 0x00000022, 0x00000004, + 0x0004002b, 0x00000006, 0x00000024, 0x00000006, 0x0004001c, 0x00000025, + 0x00000022, 0x00000024, 0x0008001e, 0x00000026, 0x00000023, 0x00000023, + 0x00000022, 0x00000022, 0x00000025, 0x00000022, 0x0003001e, 0x00000027, + 0x00000026, 0x00040020, 0x00000028, 0x00000002, 0x00000027, 0x0004003b, + 0x00000028, 0x00000029, 0x00000002, 0x0004002b, 0x00000010, 0x0000002a, + 0x00000000, 0x0004002b, 0x00000010, 0x0000002b, 0x00000003, 0x00040020, + 0x0000002c, 0x00000002, 0x00000022, 0x0003001d, 0x00000033, 0x00000006, + 0x0003001e, 0x00000034, 0x00000033, 0x00040020, 0x00000035, 0x00000002, + 0x00000034, 0x0004003b, 0x00000035, 0x00000036, 0x00000002, 0x0004002b, + 0x00000006, 0x00000037, 0x00000001, 0x00040020, 0x00000038, 0x00000007, + 0x00000010, 0x0004002b, 0x00000006, 0x0000003c, 0x00000000, 0x00040020, + 0x0000003d, 0x00000002, 0x0000001d, 0x00040020, 0x00000046, 0x00000002, + 0x00000006, 0x00020014, 0x00000052, 0x00040020, 0x00000059, 0x00000003, + 0x00000022, 0x0004003b, 0x00000059, 0x0000005a, 0x00000003, 0x0004002b, + 0x0000001d, 0x0000005b, 0x3ca3d70a, 0x0004002b, 0x0000001d, 0x0000005c, + 0x3d23d70a, 0x0004002b, 0x0000001d, 0x0000005d, 0x3f800000, 0x0007002c, + 0x00000022, 0x0000005e, 0x0000005b, 0x0000005b, 0x0000005c, 0x0000005d, + 0x0004002b, 0x00000006, 0x00000062, 0x9e3779b1, 0x00040017, 0x00000064, + 0x0000001d, 0x00000003, 0x00040020, 0x00000065, 0x00000007, 0x00000064, + 0x0004002b, 0x00000006, 0x00000069, 0x000000ff, 0x0004002b, 0x00000010, + 0x0000006d, 0x00000008, 0x0004002b, 0x0000001d, 0x00000075, 0x437f0000, + 0x0004002b, 0x0000001d, 0x00000079, 0x3d4ccccd, 0x0006002c, 0x00000064, + 0x0000007a, 0x00000079, 0x00000079, 0x00000079, 0x00040020, 0x0000007d, + 0x00000007, 0x0000001d, 0x0004002b, 0x0000001d, 0x00000081, 0x477fff00, + 0x0004002b, 0x0000001d, 0x00000084, 0x3eb33333, 0x0004002b, 0x0000001d, + 0x00000085, 0x3f266666, 0x0003001d, 0x0000008f, 0x0000001d, 0x0003001e, + 0x00000090, 0x0000008f, 0x00040020, 0x00000091, 0x00000002, 0x00000090, + 0x0004003b, 0x00000091, 0x00000092, 0x00000002, 0x0003001d, 0x00000093, + 0x00000006, 0x0003001e, 0x00000094, 0x00000093, 0x00040020, 0x00000095, + 0x00000002, 0x00000094, 0x0004003b, 0x00000095, 0x00000096, 0x00000002, + 0x00040017, 0x00000097, 0x00000006, 0x00000004, 0x0003001d, 0x00000098, + 0x00000097, 0x0003001e, 0x00000099, 0x00000098, 0x00040020, 0x0000009a, + 0x00000002, 0x00000099, 0x0004003b, 0x0000009a, 0x0000009b, 0x00000002, + 0x0003001d, 0x0000009c, 0x00000006, 0x0004001e, 0x0000009d, 0x00000006, + 0x0000009c, 0x00040020, 0x0000009e, 0x00000002, 0x0000009d, 0x0004003b, + 0x0000009e, 0x0000009f, 0x00000002, 0x0003001d, 0x000000a0, 0x00000097, + 0x0003001e, 0x000000a1, 0x000000a0, 0x00040020, 0x000000a2, 0x00000002, + 0x000000a1, 0x0004003b, 0x000000a2, 0x000000a3, 0x00000002, 0x00050036, + 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, + 0x0004003b, 0x0000001b, 0x0000001c, 0x00000007, 0x0004003b, 0x00000007, + 0x00000032, 0x00000007, 0x0004003b, 0x00000007, 0x00000049, 0x00000007, + 0x0004003b, 0x00000007, 0x0000004a, 0x00000007, 0x0004003b, 0x00000007, + 0x0000004d, 0x00000007, 0x0004003b, 0x00000007, 0x0000004e, 0x00000007, + 0x0004003b, 0x00000007, 0x00000060, 0x00000007, 0x0004003b, 0x00000065, + 0x00000066, 0x00000007, 0x0004003b, 0x0000007d, 0x0000007e, 0x00000007, + 0x0004003d, 0x0000001e, 0x00000021, 0x00000020, 0x00060041, 0x0000002c, + 0x0000002d, 0x00000029, 0x0000002a, 0x0000002b, 0x0004003d, 0x00000022, + 0x0000002e, 0x0000002d, 0x0007004f, 0x0000001e, 0x0000002f, 0x0000002e, + 0x0000002e, 0x00000000, 0x00000001, 0x00050085, 0x0000001e, 0x00000030, + 0x00000021, 0x0000002f, 0x0004006e, 0x0000001a, 0x00000031, 0x00000030, + 0x0003003e, 0x0000001c, 0x00000031, 0x00050041, 0x00000038, 0x00000039, + 0x0000001c, 0x00000037, 0x0004003d, 0x00000010, 0x0000003a, 0x00000039, + 0x0004007c, 0x00000006, 0x0000003b, 0x0000003a, 0x00070041, 0x0000003d, + 0x0000003e, 0x00000029, 0x0000002a, 0x0000002b, 0x0000003c, 0x0004003d, + 0x0000001d, 0x0000003f, 0x0000003e, 0x0004006d, 0x00000006, 0x00000040, + 0x0000003f, 0x00050084, 0x00000006, 0x00000041, 0x0000003b, 0x00000040, + 0x00050041, 0x00000038, 0x00000042, 0x0000001c, 0x0000003c, 0x0004003d, + 0x00000010, 0x00000043, 0x00000042, 0x0004007c, 0x00000006, 0x00000044, + 0x00000043, 0x00050080, 0x00000006, 0x00000045, 0x00000041, 0x00000044, + 0x00060041, 0x00000046, 0x00000047, 0x00000036, 0x0000002a, 0x00000045, + 0x0004003d, 0x00000006, 0x00000048, 0x00000047, 0x0003003e, 0x00000032, + 0x00000048, 0x0004003d, 0x00000006, 0x0000004b, 0x00000032, 0x0003003e, + 0x0000004a, 0x0000004b, 0x00050039, 0x00000006, 0x0000004c, 0x0000000d, + 0x0000004a, 0x0003003e, 0x00000049, 0x0000004c, 0x0004003d, 0x00000006, + 0x0000004f, 0x00000032, 0x0003003e, 0x0000004e, 0x0000004f, 0x00050039, + 0x00000006, 0x00000050, 0x0000000a, 0x0000004e, 0x0003003e, 0x0000004d, + 0x00000050, 0x0004003d, 0x00000006, 0x00000051, 0x00000049, 0x000500aa, + 0x00000052, 0x00000053, 0x00000051, 0x0000003c, 0x0004003d, 0x00000006, + 0x00000054, 0x0000004d, 0x000500aa, 0x00000052, 0x00000055, 0x00000054, + 0x0000003c, 0x000500a7, 0x00000052, 0x00000056, 0x00000053, 0x00000055, + 0x000300f7, 0x00000058, 0x00000000, 0x000400fa, 0x00000056, 0x00000057, + 0x00000058, 0x000200f8, 0x00000057, 0x0003003e, 0x0000005a, 0x0000005e, + 0x000100fd, 0x000200f8, 0x00000058, 0x0004003d, 0x00000006, 0x00000061, + 0x00000049, 0x00050084, 0x00000006, 0x00000063, 0x00000061, 0x00000062, + 0x0003003e, 0x00000060, 0x00000063, 0x0004003d, 0x00000006, 0x00000067, + 0x00000060, 0x000500c2, 0x00000006, 0x00000068, 0x00000067, 0x00000011, + 0x000500c7, 0x00000006, 0x0000006a, 0x00000068, 0x00000069, 0x00040070, + 0x0000001d, 0x0000006b, 0x0000006a, 0x0004003d, 0x00000006, 0x0000006c, + 0x00000060, 0x000500c2, 0x00000006, 0x0000006e, 0x0000006c, 0x0000006d, + 0x000500c7, 0x00000006, 0x0000006f, 0x0000006e, 0x00000069, 0x00040070, + 0x0000001d, 0x00000070, 0x0000006f, 0x0004003d, 0x00000006, 0x00000071, + 0x00000060, 0x000500c7, 0x00000006, 0x00000072, 0x00000071, 0x00000069, + 0x00040070, 0x0000001d, 0x00000073, 0x00000072, 0x00060050, 0x00000064, + 0x00000074, 0x0000006b, 0x00000070, 0x00000073, 0x00060050, 0x00000064, + 0x00000076, 0x00000075, 0x00000075, 0x00000075, 0x00050088, 0x00000064, + 0x00000077, 0x00000074, 0x00000076, 0x0003003e, 0x00000066, 0x00000077, + 0x0004003d, 0x00000064, 0x00000078, 0x00000066, 0x0007000c, 0x00000064, + 0x0000007b, 0x00000001, 0x00000028, 0x00000078, 0x0000007a, 0x0006000c, + 0x00000064, 0x0000007c, 0x00000001, 0x00000045, 0x0000007b, 0x0003003e, + 0x00000066, 0x0000007c, 0x0004003d, 0x00000006, 0x0000007f, 0x0000004d, + 0x00040070, 0x0000001d, 0x00000080, 0x0000007f, 0x00050088, 0x0000001d, + 0x00000082, 0x00000080, 0x00000081, 0x0003003e, 0x0000007e, 0x00000082, + 0x0004003d, 0x00000064, 0x00000083, 0x00000066, 0x0004003d, 0x0000001d, + 0x00000086, 0x0000007e, 0x00050083, 0x0000001d, 0x00000087, 0x0000005d, + 0x00000086, 0x00050085, 0x0000001d, 0x00000088, 0x00000085, 0x00000087, + 0x00050081, 0x0000001d, 0x00000089, 0x00000084, 0x00000088, 0x0005008e, + 0x00000064, 0x0000008a, 0x00000083, 0x00000089, 0x00050051, 0x0000001d, + 0x0000008b, 0x0000008a, 0x00000000, 0x00050051, 0x0000001d, 0x0000008c, + 0x0000008a, 0x00000001, 0x00050051, 0x0000001d, 0x0000008d, 0x0000008a, + 0x00000002, 0x00070050, 0x00000022, 0x0000008e, 0x0000008b, 0x0000008c, + 0x0000008d, 0x0000005d, 0x0003003e, 0x0000005a, 0x0000008e, 0x000100fd, + 0x00010038, 0x00050036, 0x00000006, 0x0000000a, 0x00000000, 0x00000008, + 0x00030037, 0x00000007, 0x00000009, 0x000200f8, 0x0000000b, 0x0004003d, + 0x00000006, 0x0000000f, 0x00000009, 0x000500c2, 0x00000006, 0x00000012, + 0x0000000f, 0x00000011, 0x000200fe, 0x00000012, 0x00010038, 0x00050036, + 0x00000006, 0x0000000d, 0x00000000, 0x00000008, 0x00030037, 0x00000007, + 0x0000000c, 0x000200f8, 0x0000000e, 0x0004003d, 0x00000006, 0x00000015, + 0x0000000c, 0x000500c7, 0x00000006, 0x00000017, 0x00000015, 0x00000016, + 0x000200fe, 0x00000017, 0x00010038, +}; +static const size_t vg_resolve_fragment_spv_count = sizeof(vg_resolve_fragment_spv) / sizeof(vg_resolve_fragment_spv[0]); diff --git a/src/modules/virtualgeometry/shaders/vg_resolve_vertex.spv b/src/modules/virtualgeometry/shaders/vg_resolve_vertex.spv new file mode 100644 index 00000000..0ea3047f Binary files /dev/null and b/src/modules/virtualgeometry/shaders/vg_resolve_vertex.spv differ diff --git a/src/modules/virtualgeometry/shaders/vg_resolve_vertex_spv.inc b/src/modules/virtualgeometry/shaders/vg_resolve_vertex_spv.inc new file mode 100644 index 00000000..0f437c2d --- /dev/null +++ b/src/modules/virtualgeometry/shaders/vg_resolve_vertex_spv.inc @@ -0,0 +1,63 @@ +// Generated by compile_virtualgeometry_shaders.py. Do not edit. +#pragma once +static const uint32_t vg_resolve_vertex_spv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x00000031, 0x00000000, 0x00020011, + 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, + 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0008000f, 0x00000000, + 0x00000004, 0x6e69616d, 0x00000000, 0x00000017, 0x0000001c, 0x00000027, + 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, + 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, + 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, + 0x69645f65, 0x74636572, 0x00657669, 0x00040005, 0x00000004, 0x6e69616d, + 0x00000000, 0x00040005, 0x0000000c, 0x74726576, 0x00000073, 0x00030005, + 0x00000014, 0x00736f70, 0x00060005, 0x00000017, 0x565f6c67, 0x65747265, + 0x646e4978, 0x00007865, 0x00030005, 0x0000001c, 0x00007675, 0x00060005, + 0x00000025, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, 0x00060006, + 0x00000025, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, + 0x00000025, 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, + 0x00070006, 0x00000025, 0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, + 0x0065636e, 0x00070006, 0x00000025, 0x00000003, 0x435f6c67, 0x446c6c75, + 0x61747369, 0x0065636e, 0x00030005, 0x00000027, 0x00000000, 0x00040047, + 0x00000017, 0x0000000b, 0x0000002a, 0x00040047, 0x0000001c, 0x0000001e, + 0x00000000, 0x00030047, 0x00000025, 0x00000002, 0x00050048, 0x00000025, + 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x00000025, 0x00000001, + 0x0000000b, 0x00000001, 0x00050048, 0x00000025, 0x00000002, 0x0000000b, + 0x00000003, 0x00050048, 0x00000025, 0x00000003, 0x0000000b, 0x00000004, + 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, + 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000002, + 0x00040015, 0x00000008, 0x00000020, 0x00000000, 0x0004002b, 0x00000008, + 0x00000009, 0x00000003, 0x0004001c, 0x0000000a, 0x00000007, 0x00000009, + 0x00040020, 0x0000000b, 0x00000007, 0x0000000a, 0x0004002b, 0x00000006, + 0x0000000d, 0xbf800000, 0x0005002c, 0x00000007, 0x0000000e, 0x0000000d, + 0x0000000d, 0x0004002b, 0x00000006, 0x0000000f, 0x40400000, 0x0005002c, + 0x00000007, 0x00000010, 0x0000000f, 0x0000000d, 0x0005002c, 0x00000007, + 0x00000011, 0x0000000d, 0x0000000f, 0x0006002c, 0x0000000a, 0x00000012, + 0x0000000e, 0x00000010, 0x00000011, 0x00040020, 0x00000013, 0x00000007, + 0x00000007, 0x00040015, 0x00000015, 0x00000020, 0x00000001, 0x00040020, + 0x00000016, 0x00000001, 0x00000015, 0x0004003b, 0x00000016, 0x00000017, + 0x00000001, 0x00040020, 0x0000001b, 0x00000003, 0x00000007, 0x0004003b, + 0x0000001b, 0x0000001c, 0x00000003, 0x0004002b, 0x00000006, 0x0000001e, + 0x3f000000, 0x00040017, 0x00000022, 0x00000006, 0x00000004, 0x0004002b, + 0x00000008, 0x00000023, 0x00000001, 0x0004001c, 0x00000024, 0x00000006, + 0x00000023, 0x0006001e, 0x00000025, 0x00000022, 0x00000006, 0x00000024, + 0x00000024, 0x00040020, 0x00000026, 0x00000003, 0x00000025, 0x0004003b, + 0x00000026, 0x00000027, 0x00000003, 0x0004002b, 0x00000015, 0x00000028, + 0x00000000, 0x0004002b, 0x00000006, 0x0000002a, 0x00000000, 0x0004002b, + 0x00000006, 0x0000002b, 0x3f800000, 0x00040020, 0x0000002f, 0x00000003, + 0x00000022, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, + 0x000200f8, 0x00000005, 0x0004003b, 0x0000000b, 0x0000000c, 0x00000007, + 0x0004003b, 0x00000013, 0x00000014, 0x00000007, 0x0003003e, 0x0000000c, + 0x00000012, 0x0004003d, 0x00000015, 0x00000018, 0x00000017, 0x00050041, + 0x00000013, 0x00000019, 0x0000000c, 0x00000018, 0x0004003d, 0x00000007, + 0x0000001a, 0x00000019, 0x0003003e, 0x00000014, 0x0000001a, 0x0004003d, + 0x00000007, 0x0000001d, 0x00000014, 0x0005008e, 0x00000007, 0x0000001f, + 0x0000001d, 0x0000001e, 0x00050050, 0x00000007, 0x00000020, 0x0000001e, + 0x0000001e, 0x00050081, 0x00000007, 0x00000021, 0x0000001f, 0x00000020, + 0x0003003e, 0x0000001c, 0x00000021, 0x0004003d, 0x00000007, 0x00000029, + 0x00000014, 0x00050051, 0x00000006, 0x0000002c, 0x00000029, 0x00000000, + 0x00050051, 0x00000006, 0x0000002d, 0x00000029, 0x00000001, 0x00070050, + 0x00000022, 0x0000002e, 0x0000002c, 0x0000002d, 0x0000002a, 0x0000002b, + 0x00050041, 0x0000002f, 0x00000030, 0x00000027, 0x00000028, 0x0003003e, + 0x00000030, 0x0000002e, 0x000100fd, 0x00010038, +}; +static const size_t vg_resolve_vertex_spv_count = sizeof(vg_resolve_vertex_spv) / sizeof(vg_resolve_vertex_spv[0]); diff --git a/src/modules/virtualgeometry/vulkan/VirtualGeometryShaders.h b/src/modules/virtualgeometry/vulkan/VirtualGeometryShaders.h new file mode 100644 index 00000000..3e7598f9 --- /dev/null +++ b/src/modules/virtualgeometry/vulkan/VirtualGeometryShaders.h @@ -0,0 +1,7 @@ +#pragma once +// Embedded precompiled SPIR-V for the virtual-geometry compute passes. +// Regenerate with: python3 scripts/compile_virtualgeometry_shaders.py +#include +#include +#include "virtualgeometry/shaders/vg_cull_compute_spv.inc" +#include "virtualgeometry/shaders/vg_raster_compute_spv.inc" diff --git a/src/modules/virtualgeometry/vulkan/VulkanVirtualGeometry.cpp b/src/modules/virtualgeometry/vulkan/VulkanVirtualGeometry.cpp new file mode 100644 index 00000000..3ef48726 --- /dev/null +++ b/src/modules/virtualgeometry/vulkan/VulkanVirtualGeometry.cpp @@ -0,0 +1,206 @@ +#include "virtualgeometry/vulkan/VulkanVirtualGeometry.h" +#include "virtualgeometry/vulkan/VirtualGeometryShaders.h" +#include "virtualgeometry/VirtualGeometryAsset.h" +#include "virtualgeometry/VirtualGeometryBackend.h" + +#include "gpgpu/ComputeShader.h" +#include "gpgpu/GpuBuffer.h" +#include "gpgpu/vulkan/VulkanGpgpu.h" +#include "gpgpu/vulkan/VulkanUtil.h" + +#include +#include + +namespace eve::virtualgeometry { + +namespace vulkan { + +struct VgState { + gpgpu::ComputeShader *cull = nullptr; + gpgpu::ComputeShader *raster = nullptr; + gpgpu::GpuBuffer *positions = nullptr; + gpgpu::GpuBuffer *triangles = nullptr; + gpgpu::GpuBuffer *clusters = nullptr; + gpgpu::GpuBuffer *visible = nullptr; + gpgpu::GpuBuffer *pixels = nullptr; + gpgpu::GpuBuffer *uniforms = nullptr; + gpgpu::GpuBuffer *stats = nullptr; + + int viewW = 1, viewH = 1; + int pixelCapacity = 0; + int visibleCapacity = 0; + int lastVisible = 0; +}; + +namespace { + +gpgpu::ComputeShader *makeShader(const std::vector &spv) { + return gpgpu::vulkanNewShaderFromSpirv(spv); +} + +void bindStandard(gpgpu::ComputeShader *s, VgState *st) { + if (st->positions) s->bindBuffer(0, st->positions); + if (st->triangles) s->bindBuffer(1, st->triangles); + if (st->clusters) s->bindBuffer(2, st->clusters); + if (st->visible) s->bindBuffer(3, st->visible); + if (st->pixels) s->bindBuffer(4, st->pixels); + if (st->uniforms) s->bindBuffer(5, st->uniforms); + if (st->stats) s->bindBuffer(6, st->stats); +} + +void ensurePixels(VgState *st, int w, int h) { + int need = w * h; + if (need > st->pixelCapacity) { + delete st->pixels; + st->pixels = gpgpu::vulkanNewBuffer(need * (int)sizeof(uint32_t), "storage"); + st->pixelCapacity = need; + bindStandard(st->cull, st); + bindStandard(st->raster, st); + } +} + +} // namespace + +} // namespace vulkan + +// ---- eve::virtualgeometry backend interface ---- + +void vgCreate(VgBackend &be) { + auto *st = new vulkan::VgState(); + be.state = st; + try { + st->cull = vulkan::makeShader( + std::vector(vg_cull_compute_spv, vg_cull_compute_spv + vg_cull_compute_spv_count)); + st->raster = vulkan::makeShader( + std::vector(vg_raster_compute_spv, vg_raster_compute_spv + vg_raster_compute_spv_count)); + } catch (...) { + delete st; + be.state = nullptr; + throw; + } +} + +void vgDestroy(VgBackend &be) { + auto *st = static_cast(be.state); + if (!st) return; + delete st->cull; + delete st->raster; + delete st->positions; + delete st->triangles; + delete st->clusters; + delete st->visible; + delete st->pixels; + delete st->uniforms; + delete st->stats; + delete st; + be.state = nullptr; +} + +void vgUpload(VgBackend &be, const VirtualGeometryAsset &asset) { + auto *st = static_cast(be.state); + if (!st) return; + + delete st->positions; + delete st->triangles; + delete st->clusters; + + st->positions = gpgpu::vulkanNewBuffer((int)(asset.positions.size() * sizeof(float)), "storage"); + st->positions->uploadBytes(asset.positions.data(), asset.positions.size() * sizeof(float)); + + st->triangles = gpgpu::vulkanNewBuffer((int)(asset.triangles.size() * sizeof(uint32_t)), "storage"); + st->triangles->uploadBytes(asset.triangles.data(), asset.triangles.size() * sizeof(uint32_t)); + + // Cluster table: 4 uvec4 per cluster (see VgGpuCluster layout). + std::vector packed; + packed.reserve(asset.clusters.size() * 16); + auto fb = [](float f) { + union { float f; uint32_t u; } x; + x.f = f; + return x.u; + }; + for (const auto &c : asset.clusters) { + packed.push_back(fb(c.cx)); packed.push_back(fb(c.cy)); + packed.push_back(fb(c.cz)); packed.push_back(fb(c.r)); + packed.push_back(c.triStart); packed.push_back(c.triCount); + packed.push_back(c.lodLevel); packed.push_back(c.parent); + packed.push_back(fb(c.errorR)); packed.push_back(fb(c.errorRScreen)); + packed.push_back(c.childCount); packed.push_back(0); + for (int k = 0; k < 4; ++k) packed.push_back(c.children[k]); + } + st->clusters = gpgpu::vulkanNewBuffer((int)(packed.size() * sizeof(uint32_t)), "storage"); + st->clusters->uploadBytes(packed.data(), packed.size() * sizeof(uint32_t)); + + // Per-frame buffers (uniforms / visible / stats / pixels) must exist before + // we bind the standard layout so no binding is left null. + if (!st->uniforms) + st->uniforms = gpgpu::vulkanNewBuffer((int)sizeof(VgUniforms), "storage"); + if (!st->visible) + st->visible = gpgpu::vulkanNewBuffer((st->visibleCapacity + 1) * (int)sizeof(uint32_t), + "storage"); + if (!st->stats) + st->stats = gpgpu::vulkanNewBuffer(4 * (int)sizeof(uint32_t), "storage"); + vulkan::ensurePixels(st, st->viewW, st->viewH); + + vulkan::bindStandard(st->cull, st); + vulkan::bindStandard(st->raster, st); +} + +void vgUploadUniforms(VgBackend &be, const VgUniforms &u) { + auto *st = static_cast(be.state); + if (!st) return; + if (!st->uniforms) + st->uniforms = gpgpu::vulkanNewBuffer((int)sizeof(VgUniforms), "storage"); + st->uniforms->uploadBytes(&u, sizeof(VgUniforms)); + st->cull->bindBuffer(5, st->uniforms); + st->raster->bindBuffer(5, st->uniforms); +} + +void vgReset(VgBackend &be, int visibleCapacity) { + auto *st = static_cast(be.state); + if (!st || visibleCapacity <= 0) return; + if (st->visibleCapacity == visibleCapacity && st->visible && st->stats) return; + st->visibleCapacity = visibleCapacity; + delete st->visible; + delete st->stats; + // [counter, data...] + st->visible = gpgpu::vulkanNewBuffer((visibleCapacity + 1) * (int)sizeof(uint32_t), "storage"); + st->stats = gpgpu::vulkanNewBuffer(4 * (int)sizeof(uint32_t), "storage"); + st->cull->bindBuffer(3, st->visible); + st->cull->bindBuffer(6, st->stats); + st->raster->bindBuffer(3, st->visible); + st->raster->bindBuffer(6, st->stats); +} + +int vgUpdate(VgBackend &be, int clusterCount, int visibleCapacity, int viewW, int viewH) { + auto *st = static_cast(be.state); + if (!st || !st->cull || !st->raster || !st->visible) return 0; + st->visible->fillFloat32(0.f); + st->stats->fillFloat32(0.f); + if (viewW * viewH > 0) vulkan::ensurePixels(st, viewW, viewH); + st->viewW = viewW; + st->viewH = viewH; + float allOnes = 0.f; + std::memcpy(&allOnes, "\xff\xff\xff\xff", 4); + st->pixels->fillFloat32(allOnes); + int cullGroups = (clusterCount + 63) / 64; + gpgpu::vulkanDispatch(st->cull, cullGroups, 1, 1); + int rasterThreads = visibleCapacity * 124; + int rasterGroups = (rasterThreads + 127) / 128; + gpgpu::vulkanDispatch(st->raster, rasterGroups, 1, 1); + + uint32_t counter = 0; + st->visible->downloadBytes(&counter, sizeof(counter), 0); + st->lastVisible = (int)counter; + return st->lastVisible; +} + +bool vgReadPixels(VgBackend &be, std::vector &out) { + auto *st = static_cast(be.state); + if (!st || !st->pixels || st->viewW <= 0 || st->viewH <= 0) return false; + size_t n = (size_t)st->viewW * (size_t)st->viewH; + out.resize(n); + st->pixels->downloadBytes(out.data(), n * sizeof(uint32_t), 0); + return true; +} + +} // namespace eve::virtualgeometry diff --git a/src/modules/virtualgeometry/vulkan/VulkanVirtualGeometry.h b/src/modules/virtualgeometry/vulkan/VulkanVirtualGeometry.h new file mode 100644 index 00000000..84dc77b5 --- /dev/null +++ b/src/modules/virtualgeometry/vulkan/VulkanVirtualGeometry.h @@ -0,0 +1,8 @@ +#pragma once + +namespace eve::virtualgeometry::vulkan { + +// Opaque per-renderer GPU state, owned by VulkanVirtualGeometry.cpp. +struct VgState; + +} // namespace eve::virtualgeometry::vulkan diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7cdc4124..0fceadd7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -67,6 +67,8 @@ file(GLOB all_test_cpp Xray.cpp voxel.cpp voxel_render.cpp + virtualgeometry_builder.cpp + virtualgeometry_gpu.cpp gpgpu.cpp box2d.cpp box3d.cpp @@ -280,6 +282,7 @@ set(EVELIBS EVPhysics EVMap EVGpgpu + EVVirtualGeometry EVModel3D EVFont EVThread diff --git a/test/virtualgeometry_builder.cpp b/test/virtualgeometry_builder.cpp new file mode 100644 index 00000000..23ac0c5f --- /dev/null +++ b/test/virtualgeometry_builder.cpp @@ -0,0 +1,282 @@ +#include "zeroerr/assert.h" +#include "zeroerr/unittest.h" + +#include "virtualgeometry/Builder.h" +#include "virtualgeometry/LodSelection.h" + +#include +#include +#include + +using namespace eve::virtualgeometry; + +namespace { + +// Build a unit icosphere with `subdiv` levels (3 => 1280 tris, 4 => 5120). +struct IcoMesh { + std::vector positions; + std::vector indices; + + int addVtx(float x, float y, float z) { + float l = std::sqrt(x * x + y * y + z * z); + positions.push_back(x / l); + positions.push_back(y / l); + positions.push_back(z / l); + return static_cast(positions.size() / 3) - 1; + } + void tri(int a, int b, int c) { + indices.push_back(static_cast(a)); + indices.push_back(static_cast(b)); + indices.push_back(static_cast(c)); + } + int mid(int i0, int i1) { + float x = (positions[3 * i0] + positions[3 * i1]) * 0.5f; + float y = (positions[3 * i0 + 1] + positions[3 * i1 + 1]) * 0.5f; + float z = (positions[3 * i0 + 2] + positions[3 * i1 + 2]) * 0.5f; + return addVtx(x, y, z); + } + void build(int subdiv) { + const float t = (1.f + std::sqrt(5.f)) * 0.5f; + std::vector> base = {{-1, t, 0}, {1, t, 0}, {-1, -t, 0}, {1, -t, 0}, + {0, -1, t}, {0, 1, t}, {0, -1, -t}, {0, 1, -t}, + {t, 0, -1}, {t, 0, 1}, {-t, 0, -1}, {-t, 0, 1}}; + int idx[12]; + for (int i = 0; i < 12; ++i) idx[i] = addVtx(base[i][0], base[i][1], base[i][2]); + int faces[20][3] = {{0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11}, + {1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8}, + {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9}, + {4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1}}; + struct FT { int a, b, c; }; + std::vector cur; + for (auto &f : faces) cur.push_back({idx[f[0]], idx[f[1]], idx[f[2]]}); + for (int d = 0; d < subdiv; ++d) { + std::vector nf; + for (auto &x : cur) { + int ab = mid(x.a, x.b), ac = mid(x.a, x.c), bc = mid(x.b, x.c); + nf.push_back({x.a, ab, ac}); + nf.push_back({ab, x.b, bc}); + nf.push_back({ac, bc, x.c}); + nf.push_back({ab, bc, ac}); + } + cur.swap(nf); + } + indices.clear(); + for (auto &x : cur) tri(x.a, x.b, x.c); + } +}; + +VirtualGeometryAsset buildIco(int subdiv) { + IcoMesh m; + m.build(subdiv); + VirtualGeometryBuilder b; + VirtualGeometryBuilder::MeshInput in; + in.vertexCount = static_cast(m.positions.size() / 3); + in.positions = m.positions.data(); + in.indices = m.indices.data(); + in.indexCount = static_cast(m.indices.size()); + VirtualGeometryBuilder::Options opt; + opt.minLodLevels = 5; + opt.mergeFactor = 4; + opt.maxTrianglesPerCluster = 124; + opt.maxVerticesPerCluster = 64; + VirtualGeometryAsset a; + REQUIRE(b.build(in, opt, a)); + return a; +} + +} // namespace + +TEST_CASE("virtualgeometry.builder.rejectsEmptyInput") { + VirtualGeometryBuilder b; + VirtualGeometryBuilder::MeshInput in; // all null / zero + VirtualGeometryAsset a; + CHECK(!b.build(in, VirtualGeometryBuilder::Options(), a)); +} + +TEST_CASE("virtualgeometry.builder.icosphereCoversAllTriangles") { + auto a = buildIco(4); // 5120 triangles + long l0 = 0; + for (const auto &c : a.clusters) + if (c.lodLevel == 0) l0 += static_cast(c.triCount); + // Every LOD0 triangle is covered exactly once. + CHECK_EQ(l0, 5120); + // All triangle indices are in range. + bool inRange = true; + for (std::uint32_t t : a.triangles) + if (t >= static_cast(a.vertexCount)) inRange = false; + CHECK(inRange); +} + +TEST_CASE("virtualgeometry.builder.clusterSizeBounds") { + auto a = buildIco(4); + for (const auto &c : a.clusters) + if (c.lodLevel == 0) CHECK(c.triCount <= 124); +} + +TEST_CASE("virtualgeometry.builder.dagParentChildConsistent") { + auto a = buildIco(4); + int roots = 0; + for (const auto &c : a.clusters) { + if (c.parent == 0xFFFFFFFFu) { + ++roots; + } else { + const VgCluster &p = a.clusters[c.parent]; + bool isChild = false; + for (std::uint32_t k = 0; k < p.childCount; ++k) + if (p.children[k] != 0xFFFFFFFFu && a.clusters.size() > p.children[k] && + &a.clusters[p.children[k]] == &c) + isChild = true; + CHECK(isChild); + } + } + CHECK(roots >= 1); +} + +TEST_CASE("virtualgeometry.builder.lodMonotonicReduction") { + auto a = buildIco(4); + int maxLod = 0; + for (const auto &c : a.clusters) maxLod = std::max(maxLod, static_cast(c.lodLevel)); + long prev = 1L << 30; + for (int l = 0; l <= maxLod; ++l) { + long tris = 0; + float err = 0.f; + for (const auto &c : a.clusters) + if (static_cast(c.lodLevel) == l) { + tris += static_cast(c.triCount); + err = std::max(err, c.errorR); + } + if (l > 0) { + CHECK(tris < prev); // each level is coarser + CHECK(err > 0.f); // and carries positive geometric error + } + prev = tris; + } +} + +// --------------------------------------------------------------------------- +// LOD transition tests. Sweep camera distance and drive the CPU reference of +// the GPU cull accept-rule (LodSelection::selectClusters) to verify that the +// visible LOD changes continuously: fine LOD0 near, coarse LOD far, with a +// gradual mix in between and no gaps/overlaps (a full, hole-free cover). +// --------------------------------------------------------------------------- + +namespace { + +constexpr float kProjScale = 540.f; // e.g. 1080px / (2*tan(30deg)) +constexpr float kErrorPx = 1.0f; + +struct SelectionStats { + int selected = 0; + std::vector hist; // per-LOD counts +}; + +SelectionStats selectAt(const VirtualGeometryAsset &a, float dist) { + std::vector sel; + selectClusters(a, dist, kProjScale, kErrorPx, sel); + SelectionStats s; + s.selected = static_cast(sel.size()); + lodHistogram(a, sel, s.hist); + return s; +} + +// Highest LOD level that has any selected cluster (0..maxLod); -1 if none. +int maxSelectedLod(const SelectionStats &s) { + for (int l = static_cast(s.hist.size()) - 1; l >= 0; --l) + if (s.hist[l] > 0) return l; + return -1; +} + +} // namespace + +TEST_CASE("virtualgeometry.lod.selectionFineNearCoarseFar") { + auto a = buildIco(4); // 5 LOD levels + // Very near: needs full detail -> mostly LOD0. + auto near_ = selectAt(a, 0.9f); + CHECK(maxSelectedLod(near_) == 0); // only finest LOD selected + // Very far: only coarse detail -> only top LOD. + auto far = selectAt(a, 200.f); + CHECK(maxSelectedLod(far) > 0); // some coarse level + // The sphere is on-screen at all these distances (frustum cull disabled). + CHECK(near_.selected >= far.selected); // near sees at least as many +} + +TEST_CASE("virtualgeometry.lod.monotonicCoarsening") { + auto a = buildIco(4); + std::vector countPerDist; + std::vector maxLodPerDist; + for (int d = 1; d <= 200; d += 2) { + auto s = selectAt(a, static_cast(d)); + countPerDist.push_back(s.selected); + maxLodPerDist.push_back(maxSelectedLod(s)); + } + // Selected count never increases with distance (LOD only gets coarser). + for (std::size_t i = 1; i < countPerDist.size(); ++i) + CHECK(countPerDist[i] <= countPerDist[i - 1]); + // Max LOD never decreases with distance. + for (std::size_t i = 1; i < maxLodPerDist.size(); ++i) + CHECK(maxLodPerDist[i] >= maxLodPerDist[i - 1]); +} + +TEST_CASE("virtualgeometry.lod.gradualTransitionNoAbruptJump") { + auto a = buildIco(4); + // A fine sweep over the transition range: the selected-LOD histogram should + // evolve gradually, not flip wholesale. Track the LOD-0 share per step. + int prevLod0 = -1; + for (int d = 1; d <= 120; ++d) { + auto s = selectAt(a, static_cast(d)); + int lod0 = s.hist.empty() ? 0 : s.hist[0]; + if (prevLod0 >= 0) { + // LOD0 count decreases monotonically as we move away. + CHECK(lod0 <= prevLod0); + } + prevLod0 = lod0; + } + // Some intermediate distance must contain BOTH LOD0 and a coarser LOD + // (a genuine in-transition mixture, not an on/off switch). + bool foundMix = false; + for (int d = 1; d <= 120; ++d) { + auto s = selectAt(a, static_cast(d)); + int nonLod0 = 0; + for (std::size_t l = 1; l < s.hist.size(); ++l) nonLod0 += s.hist[l]; + if (s.hist.size() >= 2 && s.hist[0] > 0 && nonLod0 > 0) foundMix = true; + } + CHECK(foundMix); +} + +TEST_CASE("virtualgeometry.lod.coverHasNoGapsOrOverlaps") { + auto a = buildIco(4); + // At every distance, the selected clusters must exactly cover the mesh: + // every cluster is either selected or has a selected ancestor (no gaps), + // and no cluster has both itself and an ancestor selected (no overlap). + for (int d = 1; d <= 200; d += 3) { + std::vector sel; + selectClusters(a, static_cast(d), kProjScale, kErrorPx, sel); + std::vector isSelected(a.clusters.size(), 0); + for (std::uint32_t c : sel) isSelected[c] = 1; + + for (std::size_t c = 0; c < a.clusters.size(); ++c) { + bool covered = isSelected[c]; + // Walk up the DAG: covered if a selected ancestor exists. + std::uint32_t p = a.clusters[c].parent; + while (p != 0xFFFFFFFFu && !covered) { + covered = isSelected[p]; + p = a.clusters[p].parent; + } + CHECK(covered); // every branch has exactly one selected node + + // Overlap check: a selected cluster must not have a selected parent. + if (isSelected[c] && a.clusters[c].parent != 0xFFFFFFFFu) + CHECK(!isSelected[a.clusters[c].parent]); + } + } +} + +TEST_CASE("virtualgeometry.lod.errorThresholdSelectsMoreDetailCloser") { + auto a = buildIco(4); + // Lower errorPx demands finer detail -> more clusters selected at the same + // distance. + std::vector strictSel, looseSel; + selectClusters(a, 5.f, kProjScale, 0.5f, strictSel); + selectClusters(a, 5.f, kProjScale, 2.0f, looseSel); + CHECK(strictSel.size() >= looseSel.size()); +} diff --git a/test/virtualgeometry_gpu.cpp b/test/virtualgeometry_gpu.cpp new file mode 100644 index 00000000..8c27dd8a --- /dev/null +++ b/test/virtualgeometry_gpu.cpp @@ -0,0 +1,146 @@ +#include "zeroerr/assert.h" +#include "zeroerr/unittest.h" + +#include "virtualgeometry/VirtualGeometry.h" +#include "virtualgeometry/VirtualGeometryRenderer.h" +#include "graphics/Graphics.h" +#include "window/Window.h" +#include "data/ByteData.h" + +#include +#include +#include + +using namespace eve::virtualgeometry; + +namespace { + +bool tryInitGpuWindow() { + auto *win = eve::window::Window::create(); + auto *gfx = eve::graphics::Graphics::create(); + if (!win || !gfx) return false; + win->setGraphics(gfx); + eve::window::WindowSettings s; + s.width = 320; + s.height = 240; + s.centered = true; + return win->setWindowSettings(s); +} + +} // namespace + +TEST_CASE("virtualgeometry.module.create") { + auto *mod = VirtualGeometry::create(); + REQUIRE(mod != nullptr); + CHECK_EQ(mod->getName(), std::string("VirtualGeometry")); +} + +TEST_CASE("virtualgeometry.gpu.buildIcosphere") { + if (!tryInitGpuWindow()) return; // headless: skip + + auto *mod = VirtualGeometry::create(); + if (!mod->isAvailable()) return; + auto *r = mod->newRenderer(); + REQUIRE(r != nullptr); + REQUIRE(r->isReady()); + + bool built = r->buildIcosphere(3); // 1280-triangle unit sphere + CHECK(built); + CHECK(r->getClusterCount() > 0); + CHECK(r->getMaxLodLevel() >= 2); + CHECK(r->getTotalTriangleCount() > 0); + CHECK(r->getLodLevel(0) >= 0); + + delete r; +} + +TEST_CASE("virtualgeometry.gpu.cullRasterResolve") { + if (!tryInitGpuWindow()) return; // headless: skip + + auto *mod = VirtualGeometry::create(); + if (!mod->isAvailable()) return; + auto *r = mod->newRenderer(); + REQUIRE(r != nullptr); + REQUIRE(r->isReady()); + bool built = r->buildIcosphere(3); + REQUIRE(built); + + r->setViewport(128, 128, 60.f, 1.0f); + r->setCameraSimple(0.f, 0.f, 3.2f); + r->setModelYaw(0.3f); + + // GPU-driven culling + software rasterization into the visibility buffer. + int visible = r->update(); + CHECK(visible > 0); // some clusters are visible + CHECK(visible <= r->getClusterCount()); + + // Resolve the visibility buffer to RGBA. + auto *rgba = r->resolveByteData(); + REQUIRE(rgba != nullptr); + CHECK_EQ(r->getViewWidth(), 128); + CHECK_EQ(r->getViewHeight(), 128); + CHECK_EQ(rgba->getSize(), std::size_t(128 * 128 * 4)); + + // Most of the projected sphere's pixels should be covered (non-empty color). + const unsigned char *px = static_cast(rgba->getData()); + int covered = 0; + for (std::size_t i = 0; i < rgba->getSize(); i += 4) { + bool empty = px[i] == 4 && px[i + 1] == 6 && px[i + 2] == 12; + if (!empty) ++covered; + } + CHECK(covered > 128 * 128 / 4); // >25% coverage of the viewport + + delete rgba; + delete r; +} + +// Sweep the camera distance through the real GPU pipeline and verify LOD +// transitions are smooth: fine detail up close, coarse detail far away, with a +// continuous, hole-free cover the whole time. +TEST_CASE("virtualgeometry.gpu.lodTransitionSweep") { + if (!tryInitGpuWindow()) return; // headless: skip + + auto *mod = VirtualGeometry::create(); + if (!mod->isAvailable()) return; + auto *r = mod->newRenderer(); + REQUIRE(r != nullptr); + REQUIRE(r->isReady()); + bool built = r->buildIcosphere(4); // 5 LOD levels + REQUIRE(built); + r->setViewport(128, 128, 60.f, 1.0f); + + const float distances[] = {1.6f, 2.2f, 3.2f, 5.0f, 9.0f}; + std::vector visibleCounts; + std::vector coveredPixels; + for (float d : distances) { + r->setCameraSimple(0.f, 0.f, d); + r->setModelYaw(0.2f); + int visible = r->update(); + CHECK(visible > 0); + visibleCounts.push_back(visible); + + auto *rgba = r->resolveByteData(); + REQUIRE(rgba != nullptr); + const unsigned char *px = static_cast(rgba->getData()); + int covered = 0; + for (std::size_t i = 0; i < rgba->getSize(); i += 4) { + bool empty = px[i] == 4 && px[i + 1] == 6 && px[i + 2] == 12; + if (!empty) ++covered; + } + coveredPixels.push_back(covered); + delete rgba; + } + + // Fine detail (many clusters) up close, coarse (few) far away. + CHECK(visibleCounts[0] > visibleCounts.back()); + // Monotonic non-increase of visible clusters with distance. + for (std::size_t i = 1; i < visibleCounts.size(); ++i) + CHECK(visibleCounts[i] <= visibleCounts[i - 1]); + // The sphere stays fully rendered at every distance (no holes). + for (std::size_t i = 1; i < coveredPixels.size(); ++i) { + CHECK(coveredPixels[i] > 0); // still visible far away + CHECK(coveredPixels[i] <= coveredPixels[i - 1]); // smaller when farther + } + + delete r; +}