diff --git a/docs/模块设计.md b/docs/模块设计.md index 4165640a..043a26a0 100644 --- a/docs/模块设计.md +++ b/docs/模块设计.md @@ -36,8 +36,8 @@ 处理多线程信号同步、回调,以及来自硬件的输入信号。输入设备拆成独立子模块。 -- [ ] 事件队列、派发与回调注册 -- [ ] 与多线程 / `thread` 的信号整合 +- [x] 事件队列、派发与回调注册(`push`/`poll`/`pushData`;脚本 `async_dispatch_event`) +- [x] 与多线程 / `thread` 的信号整合(`postMain` / `submitPost`) - [x] 鼠标输入 — `mouse` - [x] 键盘输入 — `keyboard` - [x] 触屏输入 — `touch` @@ -66,7 +66,7 @@ Love2D 风格即时模式仅作 C++/DevTools 内部逃生舱。 - [ ] ECS 整合([ECS.hpp](https://github.com/sunxfancy/ECS.hpp) 子模块)与渲染组件(`Transform2D` / `Sprite`) - [x] `RenderSystem`:每帧 View 收集并绘制(脚本不 per-sprite draw) -- [ ] 纹理 / Quad 作为 Sprite 资源(image 解码 → GPU Texture) +- [x] 纹理 / Quad 作为 Sprite 资源(image 解码 → GPU Texture) - [ ] Vulkan 后端基础管线(VKBuilder Present / Pipeline / Texture) - [ ] `clear` / `present`、与 SDL Window Surface 对接 - [x] 离屏 Canvas(组件或内部目标,P1) @@ -77,9 +77,14 @@ Love2D 风格即时模式仅作 C++/DevTools 内部逃生舱。 - [ ] 精灵表现扩展(帧/分层等) - [ ] 场景 / 调试用世界过滤 - [x] 地图类接口:TileLayer + tilemap -- [ ] 摄像机:2D 光照与法线贴图 +- [x] 摄像机:2D 光照与法线贴图 - [ ] 低像素优化:小图旋转等 - [x] 简单 3D model 支持(`Model3D` 加载 + `Graphics::newMeshFromAssimp` / `RenderSystem3D`) +- [x] 3D 多光源 + PBR(`Light3D` / metallic-roughness / 法线贴图前向) +- [x] 3D Clustered Forward 多光源(CPU 分簇 + SSBO,>8 灯自动切换) +- [x] 3D IBL / Environment 反射(`Camera3D` env cubemap + specular Fresnel) +- [x] 3D Directional CSM 阴影(单方向光 3 cascade + PCF) +- [ ] (可选)Planar Reflection / Point Shadow / SSR ### 5. 数据抽象 — `data` @@ -139,18 +144,20 @@ Love2D 风格即时模式仅作 C++/DevTools 内部逃生舱。 - [x] 线程池 / Task 提交(`eve.Thread` → `ThreadPool` / `Task`) - [x] Channel / 跨线程消息(`ThreadChannel`;命名 `getChannel`) -- [ ] 与 `event` 的跨线程投递 +- [x] 与 `event` 的跨线程投递(`postMain` / `submitPost`;Event 队列已加锁) +- [x] Squirrel 异步运行时(`async.nut`:Promise / nextTick / setTimeout / `async_pump`) - [ ] 独立脚本线程(Squirrel VM 非线程安全,暂不支持) -代码:[`src/modules/thread/`](../src/modules/thread/) -脚本:`thread <- eve.Thread()`;见 `src/scripts/load.nut`。 -注意:worker 内不要调用脚本 / graphics;主线程 `poll` Channel 收结果。 -### 11. GPU 计算系统 — `compute` +代码:[`src/modules/thread/`](../src/modules/thread/)、[`src/scripts/async.nut`](../src/scripts/async.nut) +脚本:`thread <- eve.Thread()`;帧循环调用 `async_pump()`(见 `load.nut`)。 +注意:worker 内不要调用脚本 / graphics;用 Channel 或 `postMain` 回主线程,再 `Promise.then`(`.fail` 代替 JS `.catch`)。 +### 11. GPU 计算系统 — `gpgpu` -并行与异构计算,封装 compute shader;计算结果 buffer 尽量直接交给 `graphics`。 +并行与异构计算,封装 compute shader;计算结果 buffer 尽量直接交给 `graphics`。脚本:`gpgpu <- eve.Gpgpu()`。复用 Vulkan `Graphics` 已建好的 device/queue(无独立 headless 计算设备路径,需先创建窗口)。 -- [ ] Compute shader 封装 -- [ ] Buffer 与 `graphics` 共享 / 直传 +- [x] Compute shader 封装(`Gpgpu::newShader` glslc 编译 / `newShaderFromSpvFile`;固定 8 个 SSBO binding + 32 float push constant) +- [x] `GpuBuffer`(storage / staging)+ `writeData`/`readData`/`writeFloat32`/`readFloat32`;与 `graphics` 共用同一 `vkb::Device` +- [x] 供 `tensor` 消费:`CompiledFunction` 图执行 + 大张量 reduce(见 17b) - [ ] 与 `thread` 任务调度协调(可选) ### 12. 粒子系统 — `particles` @@ -226,13 +233,31 @@ Love2D 风格即时模式仅作 C++/DevTools 内部逃生舱。 ### 17. 数学库 — `math` -矩阵、向量;导出部分 glm 操作,并实现 noise、贝塞尔曲线等。 +矩阵、向量;导出部分 glm 操作,并实现 noise、贝塞尔曲线等。脚本:`math <- eve.Math()`。 -- [ ] 向量 / 矩阵基础导出 -- [ ] 常用几何与变换工具 -- [ ] Noise -- [ ] 贝塞尔曲线 -- [ ] 随机数工具 +- [x] 向量 / 矩阵基础导出(`Vec2` / `Vec3` / `Mat4`) +- [x] 常用几何与变换工具(length/distance/dot/normalize/pointIn* / rotate / polar) +- [x] Noise(simplex/perlin + `fbm` / `ridged` / `turbulence` / `voronoi` / `warpNoise`) +- [x] 贝塞尔曲线(二次 / 三次,1D 与 2D) +- [x] 随机数工具(可种子 `mt19937` + Gaussian) +- [x] 程序化生成辅助(`hash` / `ease` / `bias`/`gain` / `snap` / `bilinear`) + +代码:[`src/modules/math/`](../src/modules/math/) + +### 17b. 张量计算 — `tensor` / `TF` + +接近 TensorFlow 2:命名空间 `tf <- eve.TF()`,值类型 `Tensor`。默认 eager;`tf.func()`(对应 `tf.function`)追踪计算图后 `compile` / `run`。 + +- [x] Phase 1 CPU eager:`zeros`/`ones`/`fill`/`eye`/`arange`/`randomUniform` 等 +- [x] 模块级 op:`add`/`multiply`/`matmul`/`relu`/`reduceSum`/`where`/`reshape*`… +- [x] `tf.func()` → `Func`:`input*` / `setOutput` / `compile` → `CompiledFunction.run*` +- [x] Symbolic `Tensor` 禁止读值;eager 常量在追踪中自动捕获为 `Const` +- [x] CPU 拓扑执行 + 逐元素 unary 链融合;`getDevice()` → `"cpu"` +- [x] Phase 2:`CompiledFunction` 经 `gpgpu` compute shader 执行(逐元素二元/一元、`matmul`、`transpose`、`where`;`Reshape`/`Flatten` 走 buffer 别名);编译时探测 Vulkan/`glslc` 可用性,不可用则回退 CPU,`getDevice()` → `"gpu"`(见 `src/modules/tensor/GpuBackend.cpp`) +- [x] 大张量 `reduceSum`/`reduceMean`/`reduceMin`/`reduceMax`(eager)在可用时走 GPU 并行规约,小张量仍用 CPU(阈值 16384 元素) +- [ ] 图内 dispatch 批量录制(目前每节点单独同步提交,尚未合并为单次 command buffer + barrier) + +代码:[`src/modules/tensor/`](../src/modules/tensor/) ### 18. 实体系统 — `entity` @@ -278,13 +303,14 @@ Love2D 风格即时模式仅作 C++/DevTools 内部逃生舱。 | 系统 | `system` | [x] 可用(OS/CPU/RAM/墙钟/休眠/剪贴板/电量/GPU) | | 网络 | `network` | [ ] | | 线程 | `thread` | [x] 可用(ThreadPool / Task / ThreadChannel) | -| GPU 计算 | `compute` | [ ] | +| GPU 计算 | `gpgpu` | [x] 可用(Compute shader + storage buffer;需 Vulkan 窗口已建) | | 粒子 | `particles` | [x] 可用(ECS + 配置热更 + Camera/Canvas) | | 物理 | `box2d` | [x] 可用(World/Body/Fixture + event 碰撞 + debug draw) | | 动画 | `animation` | [ ] | | 地图 | `map` | [ ] | | 数据库 | `database` | [ ] | -| 数学 | `math` | [ ] | +| 数学 | `math` | [x] 可用(Vec2/Vec3/Mat4 + noise + bezier + random) | +| 张量 | `tensor` / `TF` | [x] TF2 风格(eager + `func` 图;`CompiledFunction` / 大张量 reduce 经 `gpgpu` 走 GPU,回退 CPU) | | 实体 | `entity` | [x] ECS.hpp 已接入(脚本绑定待做) | | 声明式 GUI | `ui` | [x] A+B(List/Component/Theme;脚本 Component 类待续) | | 内置演示 | `demo` | [x] 可选(`EVENGINE_BUILD_DEMO`;程序化音效/行星贴图 + 默认 demo.nut) | diff --git a/docs/测试覆盖.md b/docs/测试覆盖.md index 5086a28f..fc32d8d4 100644 --- a/docs/测试覆盖.md +++ b/docs/测试覆盖.md @@ -130,7 +130,8 @@ | animation | — | 未实现 | N/A | - | | | map | — | 未实现 | N/A | - | | | database | — | 未实现 | N/A | - | | -| math | — | 未实现 | N/A | - | | +| math | `src/modules/math/` | 可用 | 已展开 | Vec/Mat + fbm/voronoi/hash/ease + bezier/random | `test/math.cpp`(9 用例) | +| tensor | `src/modules/tensor/` | 可用 | 已展开 | TF2:`eve.TF` + `Tensor`;eager + `func`/compile/run | `test/tensor.cpp`(8 用例) | | entity | `src/modules/entity/EntityModule.h` / ECS | 可用 | 待展开 | - | `test/ECS.cpp` 已有基础用例 | | timer | `src/modules/timer/Timer.h` | 可用 | 已展开 | **3/3 已覆盖** | `test/timer_cpp.cpp`;`.nut` 不计入 | diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index b3dea43a..4bdca35f 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -117,9 +117,9 @@ else() endif() if(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android") OR CMAKE_SYSTEM_NAME STREQUAL "iOS" OR EVENGINE_IOS) - set(EVELIBS EVWindow EVMouse EVKeyboard EVJoystick EVEvent EVTimer EVSystem EVTouch EVCmdLine EVScripts EVCommon EVGraphics EVImage EVFileSystem EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles EVMap EVGpgpu EVModel3D EVFont EVThread EVPlugins EVPhysics) + set(EVELIBS EVWindow EVMouse EVKeyboard EVJoystick EVEvent EVTimer EVSystem EVMath EVTensor EVTouch EVCmdLine EVScripts EVCommon EVGraphics EVImage EVFileSystem EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles EVMap EVGpgpu EVModel3D EVFont EVThread EVPlugins EVPhysics) else() - set(EVELIBS EVWindow EVMouse EVKeyboard EVJoystick EVEvent EVTimer EVSystem EVTouch EVCmdLine EVDevTools EVScripts EVCommon EVGraphics EVImage EVFileSystem EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles EVMap EVGpgpu EVModel3D EVFont EVThread EVPlugins EVPhysics) + set(EVELIBS EVWindow EVMouse EVKeyboard EVJoystick EVEvent EVTimer EVSystem EVMath EVTensor EVTouch EVCmdLine EVDevTools EVScripts EVCommon EVGraphics EVImage EVFileSystem EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles EVMap EVGpgpu EVModel3D EVFont EVThread EVPlugins EVPhysics) endif() if(EVENGINE_BUILD_DEMO) diff --git a/src/engine/cmdline/Run.cpp b/src/engine/cmdline/Run.cpp index f203e2be..f9c80a92 100644 --- a/src/engine/cmdline/Run.cpp +++ b/src/engine/cmdline/Run.cpp @@ -92,6 +92,7 @@ int Cmdline::Run(std::string path, std::string root) { { ssq::Table eve(vm.find("eve")); eve.set("demoScript", std::string(demo_content ? demo_content : "")); + eve.set("asyncScript", std::string(async_content ? async_content : "")); } ssq::Script script = vm.compileSource(root.c_str()); vm.run(script); diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 45b81813..d435a035 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -6,6 +6,8 @@ create_module(EVWindow window) create_module(EVEvent event) create_module(EVTimer timer) create_module(EVSystem system) +create_module(EVMath math) +create_module(EVTensor tensor) create_module(EVMouse mouse) create_module(EVKeyboard keyboard) create_module(EVJoystick joystick) diff --git a/src/modules/Readme.md b/src/modules/Readme.md index 0af1115e..bffbba25 100644 --- a/src/modules/Readme.md +++ b/src/modules/Readme.md @@ -48,6 +48,15 @@ `getWallTime` / `sleepMilliseconds` / 剪贴板 / 电量 / GPU 查询) 帧计时仍用 `Timer`;GPU 信息在 Graphics init 后可用 +6b. 数学库 - math(`eve.Math`) + Vec2 / Vec3 / Mat4(glm);标量与几何工具;noise1/2/3;贝塞尔;可种子随机数 + 代码:`src/modules/math/` + +6c. 张量计算 - tensor(`eve.TF` / 类型 `Tensor`) + TF2 风格:默认 eager;`tf.func()` 建图 → `compile` / `run*`(对应 `tf.function`) + 模块级 `add`/`multiply`/`matmul`/`relu`/`reduceSum`/`where`;`getDevice()` → `"cpu"` + 代码:`src/modules/tensor/` + 7. 资源管理系统 - image sound video font(`font`/`Font`) 3dmodel(`model3d`/`Model3D`) animation 负责加载图形、声音、视频、字体、3d模型、动画等不同格式数据 3dmodel:`Model3D::newModelData` / `newModelDataFromFile` → `ModelData`(medialoader Assimp) @@ -58,8 +67,10 @@ 9. 线程系统 - thread(`eve.Thread`) 负责线程池、异步 Task、跨线程 ThreadChannel(字符串消息) - `getPool` / `newThreadPool` / `getChannel` / `newChannel` - `ThreadPool::submitSleep` / `submitPush` / `waitAll`;worker 勿碰 Squirrel VM + `getPool` / `newThreadPool` / `getChannel` / `newChannel` / `postMain` + `ThreadPool::submitSleep` / `submitPush` / `submitPost` / `waitAll`;worker 勿碰 Squirrel VM + 脚本异步:`src/scripts/async.nut`(Promise / nextTick / setTimeout / asyncSleep / asyncDelay) + 帧循环 `async_pump()`;`.fail` 代替 JS 的 `.catch`(Squirrel 关键字) 10. 粒子系统 - particles ECS `ParticleEmitter` + Sim/Render/Config System;JSON 热更;Camera2D/Canvas;发射区域与径向/切向力。 diff --git a/src/modules/audio/Source.cpp b/src/modules/audio/Source.cpp index fcca38e0..e7d111e8 100644 --- a/src/modules/audio/Source.cpp +++ b/src/modules/audio/Source.cpp @@ -62,9 +62,17 @@ Source::~Source() { alDeleteBuffers(streamBufferCount, streamBuffers); streamBufferCount = 0; } - if (ownsDecoder) { - delete decoder; - decoder = nullptr; + { + // audio->unregisterSource() above (under Audio::mutex) guarantees no *new* + // call to fillPendingFromDecoder() will be dispatched for this Source, but + // one may already be in flight on the worker thread using a snapshot taken + // before the unregister. Taking decoderMutex here blocks until that call + // (which also holds decoderMutex) has finished before we free the decoder. + std::lock_guard lock(decoderMutex); + if (ownsDecoder) { + delete decoder; + decoder = nullptr; + } } } @@ -73,9 +81,12 @@ ALenum Source::alFormat() const { if (staticData) { ch = staticData->getChannelCount(); bits = staticData->getBitDepth(); - } else if (decoder) { - ch = decoder->getChannelCount(); - bits = decoder->getBitDepth(); + } else { + std::lock_guard lock(decoderMutex); + if (decoder) { + ch = decoder->getChannelCount(); + bits = decoder->getBitDepth(); + } } if (ch == 1 && bits == 8) return AL_FORMAT_MONO8; if (ch == 1 && bits == 16) return AL_FORMAT_MONO16; @@ -84,6 +95,11 @@ ALenum Source::alFormat() const { throw eve::Exception("Unsupported PCM format for OpenAL"); } +int Source::decoderSampleRateOr(int fallback) const { + std::lock_guard lock(decoderMutex); + return decoder ? decoder->getSampleRate() : fallback; +} + void Source::ensureStaticBuffer() { if (alBuffer || !staticData) return; @@ -138,11 +154,16 @@ void Source::stop() { ALuint b = 0; alSourceUnqueueBuffers(alSource, 1, &b); } - std::lock_guard lock(pendingMutex); - while (!pending.empty()) - pending.pop(); - if (decoder) - decoder->rewind(); + { + std::lock_guard lock(pendingMutex); + while (!pending.empty()) + pending.pop(); + } + { + std::lock_guard lock(decoderMutex); + if (decoder) + decoder->rewind(); + } wantsData = true; } else { alSourceRewind(alSource); @@ -176,11 +197,18 @@ bool Source::isLooping() const { return looping; } bool Source::seek(double seconds) { if (streaming) { - if (!decoder || !decoder->isSeekable()) - return false; + { + std::lock_guard lock(decoderMutex); + if (!decoder || !decoder->isSeekable()) + return false; + } + // stop() takes decoderMutex itself (for rewind), so it must not be held here. stop(); - if (!decoder->seek(seconds)) - return false; + { + std::lock_guard lock(decoderMutex); + if (!decoder || !decoder->seek(seconds)) + return false; + } wantsData = true; if (audio) audio->notifyWorker(); @@ -199,6 +227,7 @@ double Source::tell() const { double Source::getDuration() const { if (staticData) return staticData->getDuration(); + std::lock_guard lock(decoderMutex); if (decoder) return decoder->getDuration(); return -1.0; @@ -217,27 +246,37 @@ void Source::setAttenuationDistances(float ref, float max) { } void Source::fillPendingFromDecoder() { - if (!streaming || !decoder || !wantsData.load()) + if (!streaming || !wantsData.load()) return; { std::lock_guard lock(pendingMutex); if (pending.size() >= kMaxPendingChunks) return; } - int n = decoder->decode(); - if (n <= 0) { - if (looping && decoder->rewind()) { - n = decoder->decode(); - } else { - wantsData = false; - return; - } - } - if (n <= 0) - return; + + // Guard the whole decode step: `decoder` may be deleted concurrently by + // Source::~Source() running on another thread (e.g. main/script thread + // destroying this Source while the Audio worker is mid-decode here). PcmChunk chunk; - auto *buf = static_cast(decoder->getBuffer()); - chunk.bytes.assign(buf, buf + n); + { + std::lock_guard decoderLock(decoderMutex); + if (!decoder) + return; + int n = decoder->decode(); + if (n <= 0) { + if (looping && decoder->rewind()) { + n = decoder->decode(); + } else { + wantsData = false; + return; + } + } + if (n <= 0) + return; + auto *buf = static_cast(decoder->getBuffer()); + chunk.bytes.assign(buf, buf + n); + } + std::lock_guard lock(pendingMutex); pending.push(std::move(chunk)); } @@ -263,7 +302,7 @@ void Source::pump() { // buffer; we must not lose buffer id. Queue a tiny silence. std::vector silence(256, 0); alBufferData(b, alFormat(), silence.data(), static_cast(silence.size()), - decoder ? decoder->getSampleRate() : 44100); + decoderSampleRateOr(44100)); alSourceQueueBuffers(alSource, 1, &b); continue; } @@ -271,7 +310,7 @@ void Source::pump() { pending.pop(); } alBufferData(b, alFormat(), chunk.bytes.data(), static_cast(chunk.bytes.size()), - decoder->getSampleRate()); + decoderSampleRateOr(44100)); alSourceQueueBuffers(alSource, 1, &b); } @@ -292,7 +331,7 @@ void Source::pump() { // Use streamBuffers[queued] style ALuint buf = streamBuffers[queued]; alBufferData(buf, alFormat(), chunk.bytes.data(), static_cast(chunk.bytes.size()), - decoder->getSampleRate()); + decoderSampleRateOr(44100)); alSourceQueueBuffers(alSource, 1, &buf); alGetSourcei(alSource, AL_BUFFERS_QUEUED, &queued); } diff --git a/src/modules/audio/Source.h b/src/modules/audio/Source.h index 7904e050..9e6fa9e2 100644 --- a/src/modules/audio/Source.h +++ b/src/modules/audio/Source.h @@ -51,7 +51,9 @@ public: bool isStreaming() const { return streaming; } void pump(); // main thread: queue/unqueue AL buffers for stream - // Called by Audio worker under Audio mutex. + // Called by the Audio worker thread. Internally synchronized against + // concurrent decoder teardown (see decoderMutex) — safe to call even if + // another thread is stopping/seeking/destroying this Source. void fillPendingFromDecoder(); private: @@ -60,6 +62,7 @@ private: void applyGainPitch(); ALenum alFormat() const; void ensureStaticBuffer(); + int decoderSampleRateOr(int fallback) const; Audio *audio = nullptr; sound::SoundData *staticData = nullptr; @@ -76,6 +79,11 @@ private: float pitch = 1.f; bool looping = false; + // Guards all access to `decoder` (including its lifetime): the Audio worker + // thread reads/decodes via fillPendingFromDecoder() while other threads may + // seek/stop/destroy the Source (and delete its Decoder) concurrently. + mutable std::mutex decoderMutex; + std::mutex pendingMutex; std::queue pending; std::atomic playing{false}; diff --git a/src/modules/event/Event.cpp b/src/modules/event/Event.cpp index 4ea58ca6..94c19130 100644 --- a/src/modules/event/Event.cpp +++ b/src/modules/event/Event.cpp @@ -19,16 +19,31 @@ void Event::expose(ssq::Class& cls) { cls.addFunc("getName", &Event::getName); cls.addFunc("pump", &Event::pump); cls.addFunc("poll", &Event::pollName); + cls.addFunc("pollData", &Event::pollData); + cls.addFunc("getLastData", &Event::getLastData); + cls.addFunc("pushData", &Event::pushData); } Event::~Event() {} void Event::push(Message* msg) { + if (!msg) + return; + std::lock_guard lock(queueMu_); queue.push(msg); } +void Event::pushData(std::string name, std::string data) { + std::vector args; + if (!data.empty()) + args.push_back(Variant::makeString(std::move(data))); + push(new Message(std::move(name), args)); +} + Message* Event::poll() { - if (queue.empty()) return nullptr; + std::lock_guard lock(queueMu_); + if (queue.empty()) + return nullptr; auto msg = queue.front(); queue.pop(); return msg; @@ -36,13 +51,41 @@ Message* Event::poll() { std::string Event::pollName() { Message* msg = poll(); - if (!msg) return {}; + lastData_.clear(); + if (!msg) + return {}; + for (const auto& a : msg->args) { + if (a.type == Variant::Type::String) { + lastData_ = a.s; + break; + } + } std::string n = msg->name; delete msg; return n; } +std::string Event::getLastData() const { return lastData_; } + +std::string Event::pollData() { + Message* msg = poll(); + lastData_.clear(); + if (!msg) + return {}; + std::string data; + for (const auto& a : msg->args) { + if (a.type == Variant::Type::String) { + data = a.s; + break; + } + } + lastData_ = data; + delete msg; + return data; +} + void Event::clear() { + std::lock_guard lock(queueMu_); while (!queue.empty()) { delete queue.front(); queue.pop(); diff --git a/src/modules/event/Event.h b/src/modules/event/Event.h index 99b7c416..779d8a00 100644 --- a/src/modules/event/Event.h +++ b/src/modules/event/Event.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -57,17 +58,29 @@ public: Module_REG(Event); virtual ~Event(); + /** Thread-safe: workers may push completions for the main loop to poll. */ void push(Message* msg); + /** Script helper: push an event with an optional string payload. */ + void pushData(std::string name, std::string data = ""); Message* poll(); /** Script helper: pop one message; return name or empty if none. */ std::string pollName(); + /** First string arg from the message most recently returned by pollName/pollData. */ + std::string getLastData() const; + /** + * Pop one message; return its first string arg (or ""). + * Name is discarded — use pollName when you need the type. + */ + std::string pollData(); virtual void clear(); virtual void pump() = 0; virtual Message* wait() = 0; protected: + std::mutex queueMu_; std::queue queue; + std::string lastData_; }; } // namespace event diff --git a/src/modules/graphics/ClusteredLight.cpp b/src/modules/graphics/ClusteredLight.cpp new file mode 100644 index 00000000..9fc0edb5 --- /dev/null +++ b/src/modules/graphics/ClusteredLight.cpp @@ -0,0 +1,120 @@ +#include "graphics/ClusteredLight.h" + +#include +#include +#include + +namespace eve::graphics { + +ClusteredLightingUpload buildClusteredLighting(const std::vector &points, + const std::vector &dirs, + const glm::mat4 &view, float nearZ, float farZ, + int screenW, int screenH, float fovYRad, + const glm::vec4 &ambient) { + ClusteredLightingUpload out{}; + out.ambient = ambient; + out.view = view; + out.clipInfo = glm::vec4(nearZ, farZ, float(std::max(screenW, 1)), float(std::max(screenH, 1))); + out.gridInfo = glm::vec4(float(ClusteredLightConfig::kTilesX), float(ClusteredLightConfig::kTilesY), + float(ClusteredLightConfig::kSlices), 0.f); + + if (!dirs.empty()) { + glm::vec3 d(dirs[0].posRadius); + if (glm::length(d) < 1e-6f) d = glm::vec3(0.f, 1.f, 0.f); + else d = glm::normalize(d); + out.primaryDir = glm::vec4(d, 1.f); + out.primaryColor = dirs[0].color; + } else { + out.primaryDir = glm::vec4(0.f, 1.f, 0.f, 0.f); + out.primaryColor = glm::vec4(0.f); + } + + const int maxPts = std::min(int(points.size()), ClusteredLightConfig::kMaxLights); + out.lights.assign(points.begin(), points.begin() + maxPts); + out.gridInfo.w = float(out.lights.size()); + + const int C = ClusteredLightConfig::kClusterCount; + out.clusterTable.assign(size_t(C), ClusterTableEntry{}); + auto perCluster = std::vector>(size_t(C)); + for (auto &v : perCluster) v.reserve(4); + + const float nx = nearZ; + const float fx = std::max(farZ, nearZ + 1e-3f); + const float tanHalfFov = std::tan(std::max(fovYRad, 1e-3f) * 0.5f); + const float aspect = out.clipInfo.z / std::max(out.clipInfo.w, 1.f); + + for (uint32_t li = 0; li < uint32_t(out.lights.size()); ++li) { + const auto &L = out.lights[li]; + const glm::vec3 worldPos(L.posRadius); + const float radius = std::max(L.posRadius.w, 0.01f); + const glm::vec4 vp = view * glm::vec4(worldPos, 1.f); + const glm::vec3 viewPos(vp); + const float zCenter = -viewPos.z; // positive depth in front of camera (RH) + const float z0 = zCenter - radius; + const float z1 = zCenter + radius; + if (z1 < nx || z0 > fx) continue; + + const float zMin = std::max(z0, nx); + const float zMax = std::min(z1, fx); + const int slice0 = std::clamp( + int(std::floor((zMin - nx) / (fx - nx) * float(ClusteredLightConfig::kSlices))), 0, + ClusteredLightConfig::kSlices - 1); + const int slice1 = std::clamp( + int(std::floor((zMax - nx) / (fx - nx) * float(ClusteredLightConfig::kSlices))), 0, + ClusteredLightConfig::kSlices - 1); + + const float depth = std::max(zCenter, nx); + const float halfH = depth * tanHalfFov; + const float halfW = halfH * aspect; + + auto toTileX = [&](float x) { + float u = (x + halfW) / std::max(2.f * halfW, 1e-3f); + return std::clamp(int(std::floor(u * float(ClusteredLightConfig::kTilesX))), 0, + ClusteredLightConfig::kTilesX - 1); + }; + auto toTileY = [&](float y) { + float v = 1.f - (y + halfH) / std::max(2.f * halfH, 1e-3f); + return std::clamp(int(std::floor(v * float(ClusteredLightConfig::kTilesY))), 0, + ClusteredLightConfig::kTilesY - 1); + }; + + const int tx0 = toTileX(viewPos.x - radius); + const int tx1 = toTileX(viewPos.x + radius); + const int tyA = toTileY(viewPos.y - radius); + const int tyB = toTileY(viewPos.y + radius); + const int ty0 = std::min(tyA, tyB); + const int ty1 = std::max(tyA, tyB); + + for (int sz = slice0; sz <= slice1; ++sz) { + for (int ty = ty0; ty <= ty1; ++ty) { + for (int tx = tx0; tx <= tx1; ++tx) { + const int cid = + (sz * ClusteredLightConfig::kTilesY + ty) * ClusteredLightConfig::kTilesX + tx; + perCluster[size_t(cid)].push_back(li); + } + } + } + } + + out.lightIndices.clear(); + out.lightIndices.reserve(size_t(C) * 4); + for (int ci = 0; ci < C; ++ci) { + auto &list = perCluster[size_t(ci)]; + if (list.size() > size_t(ClusteredLightConfig::kMaxLightsPerCluster)) { + std::stable_sort(list.begin(), list.end(), [&](uint32_t a, uint32_t b) { + const float ia = glm::length(glm::vec3(out.lights[a].color)); + const float ib = glm::length(glm::vec3(out.lights[b].color)); + return ia > ib; + }); + list.resize(size_t(ClusteredLightConfig::kMaxLightsPerCluster)); + } + out.clusterTable[size_t(ci)].offset = uint32_t(out.lightIndices.size()); + out.clusterTable[size_t(ci)].count = uint32_t(list.size()); + out.lightIndices.insert(out.lightIndices.end(), list.begin(), list.end()); + } + if (out.lightIndices.empty()) out.lightIndices.push_back(0); + out.active = true; + return out; +} + +} // namespace eve::graphics diff --git a/src/modules/graphics/ClusteredLight.h b/src/modules/graphics/ClusteredLight.h new file mode 100644 index 00000000..609ff397 --- /dev/null +++ b/src/modules/graphics/ClusteredLight.h @@ -0,0 +1,57 @@ +#pragma once + +#include "graphics/Light.h" + +#include +#include +#include + +namespace eve::graphics { + +/** Clustered forward grid (CPU assign, GPU consume). */ +struct ClusteredLightConfig { + static constexpr int kTilesX = 16; + static constexpr int kTilesY = 9; + static constexpr int kSlices = 24; + static constexpr int kClusterCount = kTilesX * kTilesY * kSlices; + static constexpr int kMaxLights = 256; + static constexpr int kMaxLightsPerCluster = 32; +}; + +using ClusteredLightGpu = Light3DGpu; + +struct ClusterTableEntry { + uint32_t offset = 0; + uint32_t count = 0; +}; + +/** + * CPU-built clustered lighting upload for one frame/camera. + * Point lights are clustered; directional lights become the primary dir (first by intensity). + */ +struct ClusteredLightingUpload { + glm::vec4 ambient{0.12f, 0.12f, 0.14f, 0.f}; + glm::vec4 gridInfo{float(ClusteredLightConfig::kTilesX), float(ClusteredLightConfig::kTilesY), + float(ClusteredLightConfig::kSlices), 0.f}; // w = point light count + glm::vec4 clipInfo{0.1f, 100.f, 1.f, 1.f}; // near, far, screenW, screenH + glm::mat4 view{1.f}; + glm::vec4 primaryDir{0.4f, 1.f, 0.3f, 0.f}; // toward surface; w=1 if valid + glm::vec4 primaryColor{1.f, 1.f, 1.f, 1.f}; + std::vector lights; // point lights only (world space) + std::vector clusterTable; // size = kClusterCount + std::vector lightIndices; + bool active = false; +}; + +/** + * Build clustered tables for point lights in view space. + * @param points world-space point lights (posRadius.w = radius > 0) + * @param dirs world-space directional lights (optional; first becomes primary) + */ +ClusteredLightingUpload buildClusteredLighting(const std::vector &points, + const std::vector &dirs, + const glm::mat4 &view, float nearZ, float farZ, + int screenW, int screenH, float fovYRad, + const glm::vec4 &ambient); + +} // namespace eve::graphics diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index e7f95530..6ee9491d 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -1,8 +1,11 @@ #include "graphics/Graphics.h" #include "graphics/vulkan/Graphics.h" #include "graphics/RenderSystem3D.h" +#include "graphics/RenderSystem.h" +#include "graphics/Light.h" #include "graphics/Mesh.h" #include "graphics/Texture.h" +#include "graphics/Quad.h" #include @@ -26,6 +29,14 @@ void Graphics::expose(ssq::Table &table) { table.addClass("Texture", std::function([]() -> Texture * { return nullptr; }), true); + auto quad = table.addClass( + "Quad", std::function([]() -> Quad * { return nullptr; }), true); + quad.addFunc("setViewport", &Quad::setViewport); + quad.addFunc("getX", &Quad::getX); + quad.addFunc("getY", &Quad::getY); + quad.addFunc("getWidth", &Quad::getWidth); + quad.addFunc("getHeight", &Quad::getHeight); + auto shader = table.addClass( "Shader", std::function([]() -> Shader * { return nullptr; }), true); shader.addFunc("declareFloat", &Shader::declareFloat); @@ -40,6 +51,27 @@ void Graphics::expose(ssq::Table &table) { shader.addFunc("hasUniform", &Shader::hasUniform); shader.addFunc("getUniformIndex", &Shader::getUniformIndex); + auto cam2d = table.addClass( + "Camera2D", std::function([]() { return Camera2D::createCamera(); }), true); + cam2d.addFunc("setAmbient", &Camera2D::setAmbient); + + auto light = table.addClass( + "Light2D", std::function([]() { return Light2D::createLight("point"); }), true); + light.addFunc("setType", &Light2D::setType); + light.addFunc("getType", &Light2D::getType); + light.addFunc("setPosition", &Light2D::setPosition); + light.addFunc("getX", &Light2D::getX); + light.addFunc("getY", &Light2D::getY); + light.addFunc("setDirection", &Light2D::setDirection); + light.addFunc("getDirX", &Light2D::getDirX); + light.addFunc("getDirY", &Light2D::getDirY); + light.addFunc("setColor", &Light2D::setColor); + light.addFunc("setRadius", &Light2D::setRadius); + light.addFunc("getRadius", &Light2D::getRadius); + light.addFunc("setEnabled", &Light2D::setEnabled); + light.addFunc("isEnabled", &Light2D::isEnabled); + light.addFunc("setCanvas", &Light2D::setCanvas); + auto cam = table.addClass( "Camera3D", std::function([]() { return Camera3D::createCamera(); }), true); cam.addFunc("setEye", &Camera3D::setEye); @@ -47,6 +79,33 @@ void Graphics::expose(ssq::Table &table) { cam.addFunc("setUp", &Camera3D::setUp); cam.addFunc("setFov", &Camera3D::setFov); cam.addFunc("setActive", &Camera3D::setActive); + cam.addFunc("setAmbient", &Camera3D::setAmbient); + cam.addFunc("setEnvMap", &Camera3D::setEnvMap); + cam.addFunc("setEnvIntensity", &Camera3D::setEnvIntensity); + + auto light3d = table.addClass( + "Light3D", std::function([]() { return Light3D::createLight("point"); }), true); + light3d.addFunc("setType", &Light3D::setType); + light3d.addFunc("getType", &Light3D::getType); + light3d.addFunc("setPosition", &Light3D::setPosition); + light3d.addFunc("getX", &Light3D::getX); + light3d.addFunc("getY", &Light3D::getY); + light3d.addFunc("getZ", &Light3D::getZ); + light3d.addFunc("setDirection", &Light3D::setDirection); + light3d.addFunc("getDirX", &Light3D::getDirX); + light3d.addFunc("getDirY", &Light3D::getDirY); + light3d.addFunc("getDirZ", &Light3D::getDirZ); + light3d.addFunc("setColor", &Light3D::setColor); + light3d.addFunc("setRadius", &Light3D::setRadius); + light3d.addFunc("getRadius", &Light3D::getRadius); + light3d.addFunc("setEnabled", &Light3D::setEnabled); + light3d.addFunc("isEnabled", &Light3D::isEnabled); + light3d.addFunc("setCastShadow", &Light3D::setCastShadow); + light3d.addFunc("getCastShadow", &Light3D::getCastShadow); + light3d.addFunc("setShadowBias", &Light3D::setShadowBias); + light3d.addFunc("getShadowBias", &Light3D::getShadowBias); + light3d.addFunc("setShadowStrength", &Light3D::setShadowStrength); + light3d.addFunc("getShadowStrength", &Light3D::getShadowStrength); auto ent = table.addClass( "Renderable3D", std::function([]() { return Renderable3D::create(); }), true); @@ -57,9 +116,15 @@ void Graphics::expose(ssq::Table &table) { ent.addFunc("setScale", &Renderable3D::setScale); ent.addFunc("setMesh", &Renderable3D::setMesh); ent.addFunc("setTexture", &Renderable3D::setTexture); + ent.addFunc("setNormalTexture", &Renderable3D::setNormalTexture); ent.addFunc("setShader", &Renderable3D::setShader); ent.addFunc("setTint", &Renderable3D::setTint); + ent.addFunc("setMetallic", &Renderable3D::setMetallic); + ent.addFunc("setRoughness", &Renderable3D::setRoughness); ent.addFunc("setVisible", &Renderable3D::setVisible); + ent.addFunc("setReceiveLight", &Renderable3D::setReceiveLight); + ent.addFunc("setCastShadow", &Renderable3D::setCastShadow); + ent.addFunc("setReceiveShadow", &Renderable3D::setReceiveShadow); ent.addFunc("setCamera", &Renderable3D::setCamera); } @@ -81,6 +146,7 @@ void Graphics::expose(ssq::Class &cls) { cls.addFunc("getShader", &Graphics::getShader); cls.addFunc("render3D", &Graphics::render3D); cls.addFunc("setDirectionalLight", &Graphics::setDirectionalLight); + cls.addFunc("newQuad", &Graphics::newQuad); } void Graphics::reset() { currentShader = nullptr; } @@ -102,4 +168,6 @@ void Graphics::drawSolidRectRGBA(float x, float y, float w, float h, float r, fl drawSolidRect(x, y, w, h, Color(r, g, b, a)); } +Quad *Graphics::newQuad(int x, int y, int w, int h) { return new Quad(x, y, w, h); } + } // namespace eve::graphics diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index da43b90f..9c8cdb22 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -6,6 +6,10 @@ #include "graphics/Canvas.h" #include "graphics/Texture.h" #include "graphics/Mesh.h" +#include "graphics/Quad.h" +#include "graphics/Light.h" +#include "graphics/ClusteredLight.h" +#include "graphics/Shadow.h" #include #include #include @@ -62,6 +66,12 @@ public: virtual Texture *newTexture(int width, int height, const uint8_t *rgba, bool repeatU = false, bool repeatV = false) = 0; + /** + * Create an RGBA8 cubemap from 6 faces packed as +X,-X,+Y,-Y,+Z,-Z + * (each faceSize×faceSize, total bytes = faceSize²×4×6). Owned by Graphics. + */ + virtual Texture *newCubemap(int faceSize, const uint8_t *rgbaFaces) = 0; + /** Create texture from ImageData (RGBA8 required for now). */ virtual Texture *newTexture(image::ImageData *data) = 0; @@ -90,6 +100,20 @@ public: float w, float h, float u0, float v0, float u1, float v1, const Color &color) = 0; + /** + * Lit 2D draw (albedo + normal map). Uses Lighting2DUBO from setLighting2D. + * normal may be null → treated as flat (0.5,0.5,1) only if a default normal tex exists. + */ + virtual void drawTexturedRectLitUV(Texture *albedo, Texture *normal, float x, float y, float w, + float h, float u0, float v0, float u1, float v1, + const Color &color) = 0; + + /** Upload per-frame / per-canvas 2D lighting constants for subsequent lit draws. */ + virtual void setLighting2D(const Lighting2DUBO &ubo) = 0; + + /** Pixel-space atlas rect. Squirrel owns the Quad*. */ + Quad *newQuad(int x, int y, int w, int h); + /** Upload triangulated mesh from Assimp (pos/normal/uv + indices). Owned by Graphics. */ virtual Mesh *newMeshFromAssimp(const ::aiMesh &mesh) = 0; @@ -117,16 +141,52 @@ virtual void begin3DFrame() = 0; /** Draw one mesh with model matrix. Requires begin3DFrame() (or an open swapchain pass). */ virtual void drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint) = 0; - /** Draw mesh with an explicit Mesh3D Shader (nullptr = default lit pipeline). */ + /** Draw mesh with an explicit Mesh3D Shader (nullptr = default PBR pipeline). */ virtual void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint, Shader *shader) = 0; + /** Optional normal map for the next drawMesh / drawMeshShader (nullptr = flat). */ + virtual void setMesh3DNormalTexture(Texture *normal) = 0; + + /** Metallic (0..1) and roughness (0..1) for the next default mesh draw. */ + virtual void setMesh3DMaterial(float metallic, float roughness) = 0; + + /** Per-frame ambient + up to 8 lights packed into Mesh3DUBO. */ + virtual void setMesh3DLighting(const Lighting3DPack &pack) = 0; + + /** + * Enable clustered forward path for subsequent default mesh draws (SSBO light lists). + * Pass upload.active=false (or empty) to disable and return to the ≤8 UBO path. + */ + virtual void setMesh3DClusteredLighting(const ClusteredLightingUpload &upload) = 0; + /** Directional light for subsequent drawMesh calls (world-space direction toward surface). */ virtual void setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) = 0; /** Camera eye used by mesh shaders that need view/rim (stored in Mesh3DUBO). */ virtual void setMesh3DCameraPos(const glm::vec3 &eye) = 0; + /** + * Specular IBL environment for subsequent default mesh draws. + * cube must be from newCubemap (or nullptr → black / intensity 0). + * intensity is packed into Mesh3DUBO lightColor.w. + */ + virtual void setMesh3DEnv(Texture *cube, float intensity) = 0; + + /** Upload CSM constants for subsequent default mesh draws (active=false disables). */ + virtual void setMesh3DShadows(const ShadowUpload &upload) = 0; + + /** Per-draw: when false, shadow sampling is forced off for the next mesh draw. */ + virtual void setMesh3DShadowReceive(bool receive) = 0; + + /** + * Depth-only shadow pass for one cascade layer (0..2). Uses a one-shot submit; + * call before begin3DFrame. + */ + virtual void beginShadowPass(int cascadeIndex) = 0; + virtual void drawMeshShadow(Mesh *mesh, const glm::mat4 &lightMVP) = 0; + virtual void endShadowPass() = 0; + /** True after begin3DFrame until present completes. */ bool consumeFrameHad3D() { bool v = frameHad3D; diff --git a/src/modules/graphics/Light.cpp b/src/modules/graphics/Light.cpp new file mode 100644 index 00000000..cc8a29ed --- /dev/null +++ b/src/modules/graphics/Light.cpp @@ -0,0 +1,120 @@ +#include "graphics/Light.h" + +namespace eve::graphics { + +Light2D *Light2D::createLight(const std::string &type) { + Light2D *l = Light2D::create(); + ASSERT(l != nullptr); + l->data()->entity = l; + l->setType(type); + return l; +} + +void Light2D::setType(const std::string &type) { + auto d = data(); + if (type == "dir" || type == "point") + d->type = type; + else + d->type = "point"; +} + +std::string Light2D::getType() { return data()->type; } + +void Light2D::setPosition(float x, float y) { + data()->x = x; + data()->y = y; +} + +float Light2D::getX() { return data()->x; } +float Light2D::getY() { return data()->y; } + +void Light2D::setDirection(float dx, float dy) { + data()->dx = dx; + data()->dy = dy; +} + +float Light2D::getDirX() { return data()->dx; } +float Light2D::getDirY() { return data()->dy; } + +void Light2D::setColor(float r, float g, float b, float intensity) { + auto d = data(); + d->r = r; + d->g = g; + d->b = b; + d->intensity = intensity; +} + +void Light2D::setRadius(float radius) { data()->radius = radius > 0.f ? radius : 0.f; } +float Light2D::getRadius() { return data()->radius; } + +void Light2D::setEnabled(bool enabled) { data()->enabled = enabled; } +bool Light2D::isEnabled() { return data()->enabled; } + +void Light2D::setCanvas(Canvas *canvas) { data()->canvas = canvas; } + +Light3D *Light3D::createLight(const std::string &type) { + Light3D *l = Light3D::create(); + ASSERT(l != nullptr); + l->data()->entity = l; + l->setType(type); + return l; +} + +void Light3D::setType(const std::string &type) { + auto d = data(); + if (type == "dir" || type == "point") + d->type = type; + else + d->type = "point"; +} + +std::string Light3D::getType() { return data()->type; } + +void Light3D::setPosition(float x, float y, float z) { + auto d = data(); + d->x = x; + d->y = y; + d->z = z; +} + +float Light3D::getX() { return data()->x; } +float Light3D::getY() { return data()->y; } +float Light3D::getZ() { return data()->z; } + +void Light3D::setDirection(float dx, float dy, float dz) { + auto d = data(); + d->dx = dx; + d->dy = dy; + d->dz = dz; +} + +float Light3D::getDirX() { return data()->dx; } +float Light3D::getDirY() { return data()->dy; } +float Light3D::getDirZ() { return data()->dz; } + +void Light3D::setColor(float r, float g, float b, float intensity) { + auto d = data(); + d->r = r; + d->g = g; + d->b = b; + d->intensity = intensity; +} + +void Light3D::setRadius(float radius) { data()->radius = radius > 0.f ? radius : 0.f; } +float Light3D::getRadius() { return data()->radius; } + +void Light3D::setEnabled(bool enabled) { data()->enabled = enabled; } +bool Light3D::isEnabled() { return data()->enabled; } + +void Light3D::setCastShadow(bool cast) { data()->castShadow = cast; } +bool Light3D::getCastShadow() { return data()->castShadow; } + +void Light3D::setShadowBias(float bias) { data()->shadowBias = bias < 0.f ? 0.f : bias; } +float Light3D::getShadowBias() { return data()->shadowBias; } + +void Light3D::setShadowStrength(float strength) { + data()->shadowStrength = strength < 0.f ? 0.f : (strength > 1.f ? 1.f : strength); +} +float Light3D::getShadowStrength() { return data()->shadowStrength; } + +} // namespace eve::graphics diff --git a/src/modules/graphics/Light.h b/src/modules/graphics/Light.h new file mode 100644 index 00000000..a2c6c6f0 --- /dev/null +++ b/src/modules/graphics/Light.h @@ -0,0 +1,145 @@ +#pragma once + +#include "common/ECS.h" +#include "zeroerr/assert.h" + +#include +#include +#include + +namespace eve::graphics { + +class Canvas; + +/** GPU light packing for lit2d (std140-friendly). */ +struct Light2DGpu { + glm::vec4 posRadius{0.f}; // xy = point pos or direction; w = radius (0 => directional) + glm::vec4 color{0.f}; // rgb * intensity +}; + +struct Lighting2DUBO { + static constexpr int kMaxLights = 8; + glm::vec4 ambient{0.15f, 0.15f, 0.18f, 0.f}; + glm::vec4 meta{0.f}; // x = count, y = viewW, z = viewH + Light2DGpu lights[kMaxLights]{}; +}; + +/** + * Declarative 2D light. Collected by RenderSystem (max 8 per canvas/frame). + * type: "point" | "dir" (≤15 chars). + */ +class Light2D : public ecs::Entity { +public: + ENTITY(Light2D, ecs::Entity) + + void release() override {} + + struct Data { + std::string type = "point"; + float x = 0.f; + float y = 0.f; + float dx = 0.f; + float dy = -1.f; + float r = 1.f, g = 1.f, b = 1.f; + float intensity = 1.f; + float radius = 200.f; + bool enabled = true; + Canvas *canvas = nullptr; + Light2D *entity = nullptr; + }; + + COMPONENT(Data, data) + + static Light2D *createLight(const std::string &type = "point"); + + void setType(const std::string &type); + std::string getType(); + + void setPosition(float x, float y); + float getX(); + float getY(); + + void setDirection(float dx, float dy); + float getDirX(); + float getDirY(); + + void setColor(float r, float g, float b, float intensity = 1.f); + void setRadius(float radius); + float getRadius(); + + void setEnabled(bool enabled); + bool isEnabled(); + + void setCanvas(Canvas *canvas); +}; + +/** GPU light packing for mesh3d / PBR (std140-friendly). */ +struct Light3DGpu { + glm::vec4 posRadius{0.f}; // xyz = point pos or direction; w = radius (0 => directional) + glm::vec4 color{0.f}; // rgb * intensity +}; + +struct Lighting3DPack { + static constexpr int kMaxLights = 8; + glm::vec4 ambient{0.12f, 0.12f, 0.14f, 0.f}; + Light3DGpu lights[kMaxLights]{}; + int count = 0; +}; + +/** + * Declarative 3D light. Collected by RenderSystem3D (max 8 per frame). + * type: "point" | "dir" (≤15 chars). + */ +class Light3D : public ecs::Entity { +public: + ENTITY(Light3D, ecs::Entity) + + void release() override {} + + struct Data { + std::string type = "point"; + float x = 0.f, y = 0.f, z = 0.f; + float dx = 0.4f, dy = 1.f, dz = 0.3f; + float r = 1.f, g = 1.f, b = 1.f; + float intensity = 1.f; + float radius = 8.f; + bool enabled = true; + bool castShadow = false; // only dir lights; at most one active caster per frame + float shadowBias = 0.002f; + float shadowStrength = 1.f; + Light3D *entity = nullptr; + }; + + COMPONENT(Data, data) + + static Light3D *createLight(const std::string &type = "point"); + + void setType(const std::string &type); + std::string getType(); + + void setPosition(float x, float y, float z); + float getX(); + float getY(); + float getZ(); + + void setDirection(float dx, float dy, float dz); + float getDirX(); + float getDirY(); + float getDirZ(); + + void setColor(float r, float g, float b, float intensity = 1.f); + void setRadius(float radius); + float getRadius(); + + void setEnabled(bool enabled); + bool isEnabled(); + + void setCastShadow(bool cast); + bool getCastShadow(); + void setShadowBias(float bias); + float getShadowBias(); + void setShadowStrength(float strength); + float getShadowStrength(); +}; + +} // namespace eve::graphics diff --git a/src/modules/graphics/Quad.cpp b/src/modules/graphics/Quad.cpp new file mode 100644 index 00000000..a72a5991 --- /dev/null +++ b/src/modules/graphics/Quad.cpp @@ -0,0 +1,34 @@ +#include "graphics/Quad.h" + +namespace eve::graphics { + +Quad::Quad() = default; + +Quad::Quad(int x_, int y_, int w_, int h_) { setViewport(x_, y_, w_, h_); } + +Quad::~Quad() = default; + +void Quad::setViewport(int x_, int y_, int w_, int h_) { + x = x_; + y = y_; + w = w_ > 0 ? w_ : 0; + h = h_ > 0 ? h_ : 0; +} + +void Quad::getUV(int texW, int texH, float &u0, float &v0, float &u1, float &v1) const { + if (texW <= 0 || texH <= 0 || w <= 0 || h <= 0) { + u0 = 0.f; + v0 = 0.f; + u1 = 1.f; + v1 = 1.f; + return; + } + const float tw = float(texW); + const float th = float(texH); + u0 = float(x) / tw; + v0 = float(y) / th; + u1 = float(x + w) / tw; + v1 = float(y + h) / th; +} + +} // namespace eve::graphics diff --git a/src/modules/graphics/Quad.h b/src/modules/graphics/Quad.h index 33bd5d40..c5e9b43a 100644 --- a/src/modules/graphics/Quad.h +++ b/src/modules/graphics/Quad.h @@ -1,20 +1,30 @@ #pragma once -namespace eve::graphics -{ +namespace eve::graphics { - -class Quad -{ +/** + * Pixel-space sub-rectangle of a Texture (atlas cell / sprite frame). + * UV conversion uses the texture's logical width/height. + */ +class Quad { public: Quad(); + Quad(int x, int y, int w, int h); ~Quad(); - - int x; - int y; - int w; - int h; + + void setViewport(int x, int y, int w, int h); + int getX() const { return x; } + int getY() const { return y; } + int getWidth() const { return w; } + int getHeight() const { return h; } + + /** Convert pixel rect to normalized UVs for a texture of size texW×texH. */ + void getUV(int texW, int texH, float &u0, float &v0, float &u1, float &v1) const; + + int x = 0; + int y = 0; + int w = 0; + int h = 0; }; -} // namespace eve::graphics - +} // namespace eve::graphics diff --git a/src/modules/graphics/RenderSystem.cpp b/src/modules/graphics/RenderSystem.cpp index 5083f317..d6c00307 100644 --- a/src/modules/graphics/RenderSystem.cpp +++ b/src/modules/graphics/RenderSystem.cpp @@ -1,15 +1,25 @@ #include "graphics/RenderSystem.h" #include "graphics/Graphics.h" #include "graphics/Canvas.h" +#include "graphics/Light.h" +#include "graphics/Quad.h" #include "zeroerr/assert.h" #include +#include #include #include #include namespace eve::graphics { +void Camera2D::setAmbient(float r, float g, float b) { + auto d = data(); + d->ambientR = r; + d->ambientG = g; + d->ambientB = b; +} + namespace { float clampZoom(float z) { return z <= 0.f ? 1e-4f : z; } @@ -20,6 +30,7 @@ struct ViewCam { float zoom = 1.f; bool valid = false; Color clearColor{0.1f, 0.1f, 0.12f, 1.f}; + float ambientR = 0.15f, ambientG = 0.15f, ambientB = 0.18f; }; ViewCam fromEntity(Camera2D *ent) { @@ -31,6 +42,9 @@ ViewCam fromEntity(Camera2D *ent) { v.y = d->y; v.zoom = d->zoom; v.clearColor = Color(d->r, d->g, d->b, d->a); + v.ambientR = d->ambientR; + v.ambientG = d->ambientG; + v.ambientB = d->ambientB; return v; } @@ -53,11 +67,94 @@ void applyCamera(float wx, float wy, float ww, float wh, const ViewCam &cam, int sh = wh * z; } +struct PackedLight { + Light2D::Data *data = nullptr; + bool isPoint = true; +}; + +void collectLights(Canvas *canvasKey, std::vector &out) { + out.clear(); + if (ecs::ComponentManager::inst().registy == nullptr) return; + auto view = ecs::View(); + for (auto it = view.begin(); it != view.end(); ++it) { + auto [d] = *it; + if (!d->enabled) continue; + if (d->canvas != canvasKey) continue; + PackedLight pl; + pl.data = d; + pl.isPoint = (d->type != "dir"); + out.push_back(pl); + } + std::stable_sort(out.begin(), out.end(), [](const PackedLight &a, const PackedLight &b) { + if (a.isPoint != b.isPoint) return a.isPoint && !b.isPoint; + return a.data->intensity > b.data->intensity; + }); + if (out.size() > size_t(Lighting2DUBO::kMaxLights)) + out.resize(size_t(Lighting2DUBO::kMaxLights)); +} + +Lighting2DUBO packLights(const std::vector &lights, const ViewCam &cam, int viewW, + int viewH) { + Lighting2DUBO ubo{}; + ubo.ambient = glm::vec4(cam.ambientR, cam.ambientG, cam.ambientB, 0.f); + ubo.meta = glm::vec4(float(lights.size()), float(viewW), float(viewH), 0.f); + for (size_t i = 0; i < lights.size(); ++i) { + const auto *d = lights[i].data; + Light2DGpu &g = ubo.lights[i]; + g.color = glm::vec4(d->r * d->intensity, d->g * d->intensity, d->b * d->intensity, 1.f); + if (lights[i].isPoint) { + float lx = d->x, ly = d->y; + float lr = d->radius; + if (cam.valid) { + const float z = clampZoom(cam.zoom); + lx = (d->x - cam.x) * z + float(viewW) * 0.5f; + ly = (d->y - cam.y) * z + float(viewH) * 0.5f; + lr = d->radius * z; + } + g.posRadius = glm::vec4(lx, ly, 0.f, lr); + } else { + float dx = d->dx, dy = d->dy; + const float len = std::sqrt(dx * dx + dy * dy); + if (len > 1e-6f) { + dx /= len; + dy /= len; + } else { + dx = 0.f; + dy = -1.f; + } + g.posRadius = glm::vec4(dx, dy, 0.f, 0.f); + } + } + return ubo; +} + +Color modulateUnlit(Color base, float cx, float cy, bool receiveLight, const Lighting2DUBO &ubo) { + if (!receiveLight) return base; + glm::vec3 lit(ubo.ambient.r, ubo.ambient.g, ubo.ambient.b); + const int count = int(ubo.meta.x + 0.5f); + for (int i = 0; i < count; ++i) { + const auto &L = ubo.lights[i]; + glm::vec3 col(L.color.r, L.color.g, L.color.b); + if (L.posRadius.w <= 0.f) { + // Directional: approximate as constant contribution for unlit sprites. + lit += col * 0.65f; + } else { + const float dx = L.posRadius.x - cx; + const float dy = L.posRadius.y - cy; + const float dist = std::sqrt(dx * dx + dy * dy); + float atten = 1.f - dist / std::max(L.posRadius.w, 1.f); + if (atten < 0.f) atten = 0.f; + atten *= atten; + lit += col * atten; + } + } + return Color(base.r * lit.r, base.g * lit.g, base.b * lit.b, base.a); +} + } // namespace void RenderSystem::render(Graphics &gfx) { std::unordered_map defaultCam; - // View() calls ensure_space(registy) and crashes if no Camera2D was ever created. if (ecs::ComponentManager::inst().registy != nullptr) { auto camView = ecs::View(); for (auto it = camView.begin(); it != camView.end(); ++it) { @@ -75,9 +172,13 @@ void RenderSystem::render(Graphics &gfx) { Color color; int layer; Texture *texture; + Texture *normal; + Quad *quad; Shader *shader; Canvas *canvas; ViewCam cam; + bool receiveLight; + bool litPath; }; std::vector items; @@ -100,9 +201,14 @@ void RenderSystem::render(Graphics &gfx) { item.color = Color(sp->r, sp->g, sp->b, sp->a); item.layer = sp->layer; item.texture = sp->texture; + item.normal = sp->normalTexture; + item.quad = sp->quad; item.shader = sp->shader; item.canvas = sp->canvas; item.cam = fromEntity(camEnt); + item.receiveLight = sp->receiveLight; + item.litPath = sp->receiveLight && sp->normalTexture != nullptr && sp->texture != nullptr && + sp->shader == nullptr; items.push_back(item); } @@ -112,23 +218,35 @@ void RenderSystem::render(Graphics &gfx) { if (aOff != bOff) return aOff && !bOff; if (a.canvas != b.canvas) return a.canvas < b.canvas; if (a.layer != b.layer) return a.layer < b.layer; + if (a.litPath != b.litPath) return !a.litPath && b.litPath; // unlit first if (a.shader != b.shader) return a.shader < b.shader; - return a.texture < b.texture; + if (a.texture != b.texture) return a.texture < b.texture; + return a.normal < b.normal; }); - // Per canvas group: setCanvas → clear → draw. No trailing empty screen group - // when the frame only touched offscreen targets. Canvas *current = reinterpret_cast(static_cast(1)); + Lighting2DUBO currentLights{}; for (size_t i = 0; i < items.size(); ++i) { Canvas *next = items[i].canvas; if (i == 0 || next != current) { Color clearCol = gfx.getBackgroundColor(); + ViewCam groupCam{}; auto defIt = defaultCam.find(next); - if (defIt != defaultCam.end()) clearCol = fromEntity(defIt->second).clearColor; + if (defIt != defaultCam.end()) { + groupCam = fromEntity(defIt->second); + clearCol = groupCam.clearColor; + } gfx.setCanvas(next); gfx.clear(clearCol, std::nullopt, std::nullopt); current = next; + + int viewW = next ? next->getWidth() : gfx.getWidth(); + int viewH = next ? next->getHeight() : gfx.getHeight(); + std::vector packed; + collectLights(next, packed); + currentLights = packLights(packed, groupCam, viewW, viewH); + gfx.setLighting2D(currentLights); } const auto &it = items[i]; @@ -136,10 +254,25 @@ void RenderSystem::render(Graphics &gfx) { int viewH = it.canvas ? it.canvas->getHeight() : gfx.getHeight(); float sx, sy, sw, sh; applyCamera(it.x, it.y, it.w, it.h, it.cam, viewW, viewH, sx, sy, sw, sh); - if (it.texture) - gfx.drawTexturedRectShader(it.texture, it.shader, sx, sy, sw, sh, it.color); - else - gfx.drawSolidRect(sx, sy, sw, sh, it.color); + + float u0 = 0.f, v0 = 0.f, u1 = 1.f, v1 = 1.f; + if (it.quad && it.texture) + it.quad->getUV(it.texture->getWidth(), it.texture->getHeight(), u0, v0, u1, v1); + + if (it.litPath) { + gfx.drawTexturedRectLitUV(it.texture, it.normal, sx, sy, sw, sh, u0, v0, u1, v1, + it.color); + } else if (it.texture) { + Color c = it.color; + if (it.receiveLight && !it.normal) + c = modulateUnlit(c, sx + sw * 0.5f, sy + sh * 0.5f, true, currentLights); + gfx.drawTexturedRectShaderUV(it.texture, it.shader, sx, sy, sw, sh, u0, v0, u1, v1, c); + } else { + Color c = it.color; + if (it.receiveLight) + c = modulateUnlit(c, sx + sw * 0.5f, sy + sh * 0.5f, true, currentLights); + gfx.drawSolidRect(sx, sy, sw, sh, c); + } } gfx.setCanvas(); diff --git a/src/modules/graphics/RenderSystem.h b/src/modules/graphics/RenderSystem.h index 3e67c550..a27431f3 100644 --- a/src/modules/graphics/RenderSystem.h +++ b/src/modules/graphics/RenderSystem.h @@ -1,6 +1,7 @@ #pragma once #include "common/ECS.h" +#include "graphics/Quad.h" #include "graphics/Shader.h" #include "graphics/Texture.h" #include "zeroerr/assert.h" @@ -22,6 +23,7 @@ public: float y = 0.f; float zoom = 1.f; float r = 0.1f, g = 0.1f, b = 0.12f, a = 1.f; + float ambientR = 0.15f, ambientG = 0.15f, ambientB = 0.18f; bool active = false; // true only after createCamera() / explicit enable Canvas *canvas = nullptr; Camera2D *entity = nullptr; // self; set by createCamera() @@ -37,6 +39,8 @@ public: ASSERT(c->data()->entity == c); return c; } + + void setAmbient(float r, float g, float b); }; /** Default renderable entity for declarative 2D sprites / solid quads. */ @@ -61,9 +65,12 @@ public: int layer = 0; bool visible = true; Texture *texture = nullptr; - Shader *shader = nullptr; // nullptr → default textured / solid pipeline - Canvas *canvas = nullptr; // nullptr → screen - Camera2D *camera = nullptr; // nullptr → default active camera for canvas + Texture *normalTexture = nullptr; // non-null → GPU lit2d path + Quad *quad = nullptr; // nullptr → full UV 0..1 + Shader *shader = nullptr; // nullptr → default textured / solid pipeline + Canvas *canvas = nullptr; // nullptr → screen + Camera2D *camera = nullptr; // nullptr → default active camera for canvas + bool receiveLight = true; // false → force unlit (ignore lights) }; COMPONENT(Transform2D, transform) diff --git a/src/modules/graphics/RenderSystem3D.cpp b/src/modules/graphics/RenderSystem3D.cpp index 01d37d53..6a1d5f89 100644 --- a/src/modules/graphics/RenderSystem3D.cpp +++ b/src/modules/graphics/RenderSystem3D.cpp @@ -1,8 +1,13 @@ #include "graphics/RenderSystem3D.h" #include "graphics/Graphics.h" +#include "graphics/Light.h" +#include "graphics/ClusteredLight.h" +#include "graphics/Shadow.h" -#include +#include #include +#include +#include namespace eve::graphics { @@ -32,6 +37,80 @@ Camera3D *findDefaultCamera3D() { return nullptr; } +struct PackedLight3D { + Light3D::Data *data = nullptr; + bool isPoint = true; +}; + +void collectLights3D(std::vector &out, size_t maxCount) { + out.clear(); + if (ecs::ComponentManager::inst().registy == nullptr) return; + auto view = ecs::View(); + for (auto it = view.begin(); it != view.end(); ++it) { + auto [d] = *it; + if (!d->enabled) continue; + PackedLight3D pl; + pl.data = d; + pl.isPoint = (d->type != "dir"); + out.push_back(pl); + } + std::stable_sort(out.begin(), out.end(), [](const PackedLight3D &a, const PackedLight3D &b) { + if (a.isPoint != b.isPoint) return a.isPoint && !b.isPoint; + return a.data->intensity > b.data->intensity; + }); + if (out.size() > maxCount) out.resize(maxCount); +} + +Lighting3DPack packLights3D(const std::vector &lights, const Camera3D::Data *cam) { + Lighting3DPack pack{}; + if (cam) { + pack.ambient = glm::vec4(cam->ambientR, cam->ambientG, cam->ambientB, 0.f); + } + if (lights.empty()) { + pack.count = 1; + pack.lights[0].posRadius = glm::vec4(gLightDir, 0.f); + pack.lights[0].color = glm::vec4(gLightColor, 1.f); + return pack; + } + const int n = std::min(int(lights.size()), Lighting3DPack::kMaxLights); + pack.count = n; + for (int i = 0; i < n; ++i) { + const auto *d = lights[size_t(i)].data; + Light3DGpu &g = pack.lights[i]; + g.color = glm::vec4(d->r * d->intensity, d->g * d->intensity, d->b * d->intensity, 1.f); + if (lights[size_t(i)].isPoint) { + g.posRadius = glm::vec4(d->x, d->y, d->z, d->radius); + } else { + glm::vec3 dir(d->dx, d->dy, d->dz); + if (glm::length(dir) < 1e-6f) dir = glm::vec3(0.f, 1.f, 0.f); + else dir = glm::normalize(dir); + g.posRadius = glm::vec4(dir, 0.f); + } + } + return pack; +} + +void splitLights(const std::vector &packed, std::vector &points, + std::vector &dirs) { + points.clear(); + dirs.clear(); + for (const auto &pl : packed) { + const auto *d = pl.data; + ClusteredLightGpu g{}; + g.color = glm::vec4(d->r * d->intensity, d->g * d->intensity, d->b * d->intensity, 1.f); + if (pl.isPoint) { + g.posRadius = glm::vec4(d->x, d->y, d->z, d->radius); + points.push_back(g); + } else { + glm::vec3 dir(d->dx, d->dy, d->dz); + if (glm::length(dir) < 1e-6f) dir = glm::vec3(0.f, 1.f, 0.f); + else dir = glm::normalize(dir); + g.posRadius = glm::vec4(dir, 0.f); + dirs.push_back(g); + } + } +} + } // namespace void Camera3D::setEye(float x, float y, float z) { @@ -59,6 +138,19 @@ void Camera3D::setFov(float fovYDeg) { data()->fovYDeg = fovYDeg; } void Camera3D::setActive(bool active) { data()->active = active; } +void Camera3D::setAmbient(float r, float g, float b) { + auto d = data(); + d->ambientR = r; + d->ambientG = g; + d->ambientB = b; +} + +void Camera3D::setEnvMap(Texture *cube) { data()->envMap = cube; } + +void Camera3D::setEnvIntensity(float intensity) { + data()->envIntensity = intensity < 0.f ? 0.f : intensity; +} + void Renderable3D::setPosition(float x, float y, float z) { auto t = transform(); t->x = x; @@ -88,6 +180,8 @@ void Renderable3D::setMesh(Mesh *mesh) { meshRenderer()->mesh = mesh; } void Renderable3D::setTexture(Texture *texture) { meshRenderer()->texture = texture; } +void Renderable3D::setNormalTexture(Texture *texture) { meshRenderer()->normalTexture = texture; } + void Renderable3D::setShader(Shader *shader) { meshRenderer()->shader = shader; } void Renderable3D::setTint(float r, float g, float b, float a) { @@ -98,8 +192,18 @@ void Renderable3D::setTint(float r, float g, float b, float a) { mr->a = a; } +void Renderable3D::setMetallic(float metallic) { meshRenderer()->metallic = metallic; } + +void Renderable3D::setRoughness(float roughness) { meshRenderer()->roughness = roughness; } + void Renderable3D::setVisible(bool visible) { meshRenderer()->visible = visible; } +void Renderable3D::setReceiveLight(bool receive) { meshRenderer()->receiveLight = receive; } + +void Renderable3D::setCastShadow(bool cast) { meshRenderer()->castShadow = cast; } + +void Renderable3D::setReceiveShadow(bool receive) { meshRenderer()->receiveShadow = receive; } + void Renderable3D::setCamera(Camera3D *camera) { meshRenderer()->camera = camera; } void RenderSystem3D::setDirectionalLight(float dx, float dy, float dz, float r, float g, float b) { @@ -109,19 +213,89 @@ void RenderSystem3D::setDirectionalLight(float dx, float dy, float dz, float r, gLightColor = glm::vec3(r, g, b); } +namespace { + +Light3D::Data *findShadowCasterDir(const std::vector &packed) { + Light3D::Data *best = nullptr; + float bestI = -1.f; + for (const auto &pl : packed) { + if (pl.isPoint || !pl.data || !pl.data->castShadow) continue; + if (pl.data->intensity > bestI) { + bestI = pl.data->intensity; + best = pl.data; + } + } + return best; +} + +void prioritizeShadowCaster(std::vector &packed, Light3D::Data *caster) { + if (!caster || packed.empty()) return; + for (size_t i = 0; i < packed.size(); ++i) { + if (packed[i].data != caster) continue; + if (i != 0) std::swap(packed[0], packed[i]); + return; + } +} + +} // namespace + void RenderSystem3D::render(Graphics &gfx) { Camera3D *defaultCam = findDefaultCamera3D(); - gfx.setMesh3DLight(gLightDir, gLightColor); + std::vector packed; + collectLights3D(packed, size_t(ClusteredLightConfig::kMaxLights)); + Light3D::Data *shadowCaster = findShadowCasterDir(packed); + prioritizeShadowCaster(packed, shadowCaster); + + const float aspect = + (gfx.getHeight() > 0) ? float(gfx.getWidth()) / float(gfx.getHeight()) : 1.f; + + ShadowUpload shadowUpload{}; + shadowUpload.active = false; + if (shadowCaster && defaultCam) { + auto cd = defaultCam->data(); + glm::vec3 dir(shadowCaster->dx, shadowCaster->dy, shadowCaster->dz); + if (glm::length(dir) < 1e-6f) dir = glm::vec3(0.f, 1.f, 0.f); + else dir = glm::normalize(dir); + const float fovRad = cd->fovYDeg * 0.017453292519943295f; + shadowUpload = + buildDirectionalCSM(dir, glm::vec3(cd->eyeX, cd->eyeY, cd->eyeZ), + glm::vec3(cd->targetX, cd->targetY, cd->targetZ), + glm::vec3(cd->upX, cd->upY, cd->upZ), fovRad, aspect, cd->nearZ, + cd->farZ, shadowCaster->shadowBias, shadowCaster->shadowStrength); + + if (ecs::ComponentManager::inst().registy != nullptr) { + auto casterView = + ecs::View(); + for (int c = 0; c < ShadowConfig::kCascades; ++c) { + gfx.beginShadowPass(c); + for (auto it = casterView.begin(); it != casterView.end(); ++it) { + auto [xf, mr] = *it; + if (!mr->visible || !mr->mesh || !mr->castShadow) continue; + const glm::mat4 model = modelFromTransform(*xf); + gfx.drawMeshShadow(mr->mesh, shadowUpload.ubo.lightVP[c] * model); + } + gfx.endShadowPass(); + } + } + } + gfx.setMesh3DShadows(shadowUpload); + + const bool useClustered = packed.size() > size_t(Lighting3DPack::kMaxLights); + if (!useClustered) { + ClusteredLightingUpload off{}; + off.active = false; + gfx.setMesh3DClusteredLighting(off); + const Camera3D::Data *ambientCam = defaultCam ? defaultCam->data().operator->() : nullptr; + gfx.setMesh3DLighting(packLights3D(packed, ambientCam)); + } + gfx.begin3DFrame(); if (!gfx.had3DThisFrame()) return; if (ecs::ComponentManager::inst().registy == nullptr) return; - const float aspect = - (gfx.getHeight() > 0) ? float(gfx.getWidth()) / float(gfx.getHeight()) : 1.f; - auto view = ecs::View(); for (auto it = view.begin(); it != view.end(); ++it) { auto [xf, mr] = *it; @@ -131,7 +305,6 @@ void RenderSystem3D::render(Graphics &gfx) { if (!camEnt) continue; auto cd = camEnt->data(); - // Right-handed view, Y-up; Vulkan depth via perspectiveRH_ZO. const glm::vec3 eye(cd->eyeX, cd->eyeY, cd->eyeZ); const glm::vec3 target(cd->targetX, cd->targetY, cd->targetZ); const glm::vec3 up(cd->upX, cd->upY, cd->upZ); @@ -140,6 +313,51 @@ void RenderSystem3D::render(Graphics &gfx) { const glm::mat4 projM = glm::perspectiveRH_ZO(fovRad, aspect, cd->nearZ, cd->farZ); gfx.setMesh3DViewProj(projM * viewM); gfx.setMesh3DCameraPos(eye); + gfx.setMesh3DMaterial(mr->metallic, mr->roughness); + gfx.setMesh3DNormalTexture(mr->normalTexture); + gfx.setMesh3DEnv(cd->envMap, cd->envIntensity); + gfx.setMesh3DShadowReceive(mr->receiveShadow); + + if (!mr->receiveLight) { + ClusteredLightingUpload off{}; + off.active = false; + gfx.setMesh3DClusteredLighting(off); + Lighting3DPack none{}; + none.count = 0; + none.ambient = glm::vec4(1.f, 1.f, 1.f, 0.f); + gfx.setMesh3DLighting(none); + } else if (useClustered && !mr->shader) { + std::vector points, dirs; + splitLights(packed, points, dirs); + if (dirs.empty()) { + ClusteredLightGpu d{}; + d.posRadius = glm::vec4(gLightDir, 0.f); + d.color = glm::vec4(gLightColor, 1.f); + dirs.push_back(d); + } + // packed[0] is shadow caster when present — match its direction as primary. + if (shadowCaster && !dirs.empty()) { + glm::vec3 dir(shadowCaster->dx, shadowCaster->dy, shadowCaster->dz); + if (glm::length(dir) < 1e-6f) dir = glm::vec3(0.f, 1.f, 0.f); + else dir = glm::normalize(dir); + for (size_t i = 0; i < dirs.size(); ++i) { + if (glm::length(glm::vec3(dirs[i].posRadius) - dir) < 1e-3f) { + if (i != 0) std::swap(dirs[0], dirs[i]); + break; + } + } + } + glm::vec4 ambient(cd->ambientR, cd->ambientG, cd->ambientB, 0.f); + auto upload = buildClusteredLighting(points, dirs, viewM, cd->nearZ, cd->farZ, + gfx.getWidth(), gfx.getHeight(), fovRad, ambient); + gfx.setMesh3DClusteredLighting(upload); + gfx.setMesh3DLighting(packLights3D(packed, cd.operator->())); + } else { + ClusteredLightingUpload off{}; + off.active = false; + gfx.setMesh3DClusteredLighting(off); + gfx.setMesh3DLighting(packLights3D(packed, cd.operator->())); + } const glm::mat4 model = modelFromTransform(*xf); const Color tint(mr->r, mr->g, mr->b, mr->a); diff --git a/src/modules/graphics/RenderSystem3D.h b/src/modules/graphics/RenderSystem3D.h index 95d4110c..78f20afc 100644 --- a/src/modules/graphics/RenderSystem3D.h +++ b/src/modules/graphics/RenderSystem3D.h @@ -21,6 +21,9 @@ public: float targetX = 0.f, targetY = 0.f, targetZ = 0.f; float upX = 0.f, upY = 1.f, upZ = 0.f; float fovYDeg = 60.f, nearZ = 0.1f, farZ = 100.f; + float ambientR = 0.12f, ambientG = 0.12f, ambientB = 0.14f; + Texture *envMap = nullptr; // cubemap; nullptr → no IBL + float envIntensity = 1.f; bool active = false; Camera3D *entity = nullptr; }; @@ -40,6 +43,10 @@ public: void setUp(float x, float y, float z); void setFov(float fovYDeg); void setActive(bool active); + void setAmbient(float r, float g, float b); + /** Specular IBL cubemap (Graphics::newCubemap). nullptr disables IBL. */ + void setEnvMap(Texture *cube); + void setEnvIntensity(float intensity); }; class Renderable3D : public ecs::Entity { @@ -57,9 +64,15 @@ public: struct MeshRenderer { Mesh *mesh = nullptr; Texture *texture = nullptr; - Shader *shader = nullptr; // nullptr → default mesh3d lit pipeline + Texture *normalTexture = nullptr; // nullptr → flat normal in default PBR path + Shader *shader = nullptr; // nullptr → default mesh3d PBR pipeline float r = 1, g = 1, b = 1, a = 1; + float metallic = 0.f; + float roughness = 0.45f; bool visible = true; + bool receiveLight = true; + bool castShadow = true; + bool receiveShadow = true; Camera3D *camera = nullptr; }; @@ -73,15 +86,22 @@ public: void setScale(float sx, float sy, float sz); void setMesh(Mesh *mesh); void setTexture(Texture *texture); + void setNormalTexture(Texture *texture); void setShader(Shader *shader); void setTint(float r, float g, float b, float a = 1.f); + void setMetallic(float metallic); + void setRoughness(float roughness); void setVisible(bool visible); + void setReceiveLight(bool receive); + void setCastShadow(bool cast); + void setReceiveShadow(bool receive); void setCamera(Camera3D *camera); }; class RenderSystem3D { public: static void render(Graphics &gfx); + /** Legacy single directional light used when no enabled Light3D exists. */ static void setDirectionalLight(float dx, float dy, float dz, float r, float g, float b); }; diff --git a/src/modules/graphics/Shadow.cpp b/src/modules/graphics/Shadow.cpp new file mode 100644 index 00000000..a36ec3d7 --- /dev/null +++ b/src/modules/graphics/Shadow.cpp @@ -0,0 +1,90 @@ +#include "graphics/Shadow.h" + +#include +#include +#include + +namespace eve::graphics { + +namespace { + +glm::mat4 cascadeVP(const glm::vec3 &lightDir, const glm::vec3 &eye, const glm::vec3 &target, + const glm::vec3 &up, float fovYRad, float aspect, float nearZ, float farZ) { + const glm::mat4 view = glm::lookAtRH(eye, target, up); + const glm::mat4 proj = glm::perspectiveRH_ZO(fovYRad, aspect, nearZ, farZ); + const glm::mat4 inv = glm::inverse(proj * view); + + // NDC cube corners → world + glm::vec3 corners[8]; + int idx = 0; + for (int z = 0; z < 2; ++z) + for (int y = 0; y < 2; ++y) + for (int x = 0; x < 2; ++x) { + const glm::vec4 ndc(x ? 1.f : -1.f, y ? 1.f : -1.f, z ? 1.f : 0.f, 1.f); + glm::vec4 w = inv * ndc; + w /= w.w; + corners[idx++] = glm::vec3(w); + } + + glm::vec3 center(0.f); + for (int i = 0; i < 8; ++i) center += corners[i]; + center *= 0.125f; + + glm::vec3 L = glm::normalize(lightDir); + if (glm::length(L) < 1e-6f) L = glm::vec3(0.f, 1.f, 0.f); + // Light looks along -L (from light toward scene). Place eye along +L from center. + const float radius = [&]() { + float r = 0.f; + for (int i = 0; i < 8; ++i) r = std::max(r, glm::length(corners[i] - center)); + return std::max(r, 0.5f); + }(); + + glm::vec3 lightUp(0.f, 1.f, 0.f); + if (std::abs(glm::dot(L, lightUp)) > 0.95f) lightUp = glm::vec3(0.f, 0.f, 1.f); + const glm::mat4 lightView = glm::lookAtRH(center + L * radius, center, lightUp); + + glm::vec3 minB(1e9f), maxB(-1e9f); + for (int i = 0; i < 8; ++i) { + const glm::vec3 lp = glm::vec3(lightView * glm::vec4(corners[i], 1.f)); + minB = glm::min(minB, lp); + maxB = glm::max(maxB, lp); + } + // Extend depth a bit so casters slightly outside the slice still contribute. + const float zPad = (maxB.z - minB.z) * 0.25f + 1.f; + minB.z -= zPad; + maxB.z += zPad; + + const glm::mat4 lightProj = + glm::orthoRH_ZO(minB.x, maxB.x, minB.y, maxB.y, -maxB.z, -minB.z); + return lightProj * lightView; +} + +} // namespace + +ShadowUpload buildDirectionalCSM(const glm::vec3 &lightDirTowardSurface, const glm::vec3 &eye, + const glm::vec3 &target, const glm::vec3 &up, float fovYRad, + float aspect, float nearZ, float farZ, float bias, float strength) { + ShadowUpload out{}; + out.active = true; + const float n = std::max(nearZ, 1e-3f); + const float f = std::max(farZ, n + 1e-2f); + const float ratio = f / n; + float splits[ShadowConfig::kCascades + 1]; + splits[0] = n; + for (int i = 1; i <= ShadowConfig::kCascades; ++i) { + const float p = float(i) / float(ShadowConfig::kCascades); + const float logS = n * std::pow(ratio, p); + const float uniS = n + (f - n) * p; + splits[i] = ShadowConfig::kSplitLambda * logS + (1.f - ShadowConfig::kSplitLambda) * uniS; + } + + for (int i = 0; i < ShadowConfig::kCascades; ++i) { + out.ubo.lightVP[i] = + cascadeVP(lightDirTowardSurface, eye, target, up, fovYRad, aspect, splits[i], splits[i + 1]); + } + out.ubo.splits = glm::vec4(splits[1], splits[2], splits[3], std::max(0.f, strength)); + out.ubo.bias = glm::vec4(std::max(0.f, bias), 1.f, 1.f, 0.f); + return out; +} + +} // namespace eve::graphics diff --git a/src/modules/graphics/Shadow.h b/src/modules/graphics/Shadow.h new file mode 100644 index 00000000..0bbe7c1a --- /dev/null +++ b/src/modules/graphics/Shadow.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace eve::graphics { + +struct ShadowConfig { + static constexpr int kCascades = 3; + static constexpr int kMapSize = 1024; + static constexpr float kSplitLambda = 0.7f; +}; + +/** Per-frame / per-draw CSM constants (std140). Binding separate from Mesh3D Frame UBO. */ +struct ShadowUBO { + glm::mat4 lightVP[ShadowConfig::kCascades]{}; + glm::vec4 splits{0.f}; // xyz = view-space split ends; w = strength + glm::vec4 bias{0.002f, 0.f, 1.f, 0.f}; // x=bias, y=enabled, z=receive, w unused +}; + +struct ShadowUpload { + ShadowUBO ubo{}; + bool active = false; +}; + +/** + * Build 3 cascade light view-proj matrices for a directional light. + * @param lightDirTowardSurface world-space direction toward the surface (same as Light3DGpu) + * @param eye / target / up camera basis + * @param fovYRad / aspect / nearZ / farZ camera clip + */ +ShadowUpload buildDirectionalCSM(const glm::vec3 &lightDirTowardSurface, const glm::vec3 &eye, + const glm::vec3 &target, const glm::vec3 &up, float fovYRad, + float aspect, float nearZ, float farZ, float bias, float strength); + +} // namespace eve::graphics diff --git a/src/modules/graphics/shaders/lit2d.frag b/src/modules/graphics/shaders/lit2d.frag new file mode 100644 index 00000000..fee5c646 --- /dev/null +++ b/src/modules/graphics/shaders/lit2d.frag @@ -0,0 +1,54 @@ +#version 450 + +layout(location = 0) in vec4 fragColor; +layout(location = 1) in vec2 fragUV; +layout(location = 2) in vec2 fragNdc; + +layout(set = 0, binding = 0) uniform sampler2D albedoSampler; +layout(set = 0, binding = 1) uniform sampler2D normalSampler; + +struct Light2D { + vec4 posRadius; // xy = point pos OR direction; w = radius (0 => directional) + vec4 color; // rgb * intensity +}; + +layout(set = 0, binding = 2) uniform Lighting2D { + vec4 ambient; // rgb + vec4 meta; // x = lightCount, y = viewW, z = viewH + Light2D lights[8]; +} lighting; + +layout(location = 0) out vec4 outColor; + +void main() { + vec4 base = texture(albedoSampler, fragUV) * fragColor; + vec3 nSample = texture(normalSampler, fragUV).xyz * 2.0 - 1.0; + // 2D convention: tangent ≈ screen X/Y, Z out of screen. + vec3 N = normalize(vec3(nSample.xy, max(nSample.z, 0.05))); + + vec2 logical = (fragNdc * 0.5 + 0.5) * lighting.meta.yz; + + vec3 lit = lighting.ambient.rgb; + int count = int(lighting.meta.x + 0.5); + for (int i = 0; i < 8; ++i) { + if (i >= count) break; + Light2D L = lighting.lights[i]; + vec3 lightCol = L.color.rgb; + float contrib = 0.0; + if (L.posRadius.w <= 0.0) { + // Directional: xy is light direction toward the surface (or from light). + vec3 Ld = normalize(vec3(L.posRadius.xy, 0.35)); + contrib = max(dot(N, Ld), 0.0); + } else { + vec2 toL = L.posRadius.xy - logical; + float dist = length(toL); + float atten = clamp(1.0 - dist / max(L.posRadius.w, 1.0), 0.0, 1.0); + atten *= atten; + vec3 Ld = normalize(vec3(toL, L.posRadius.w * 0.35)); + contrib = max(dot(N, Ld), 0.0) * atten; + } + lit += lightCol * contrib; + } + + outColor = vec4(base.rgb * lit, base.a); +} diff --git a/src/modules/graphics/shaders/lit2d.frag.spv b/src/modules/graphics/shaders/lit2d.frag.spv new file mode 100644 index 00000000..dcf9b0ed Binary files /dev/null and b/src/modules/graphics/shaders/lit2d.frag.spv differ diff --git a/src/modules/graphics/shaders/lit2d.vert b/src/modules/graphics/shaders/lit2d.vert new file mode 100644 index 00000000..bba1035f --- /dev/null +++ b/src/modules/graphics/shaders/lit2d.vert @@ -0,0 +1,15 @@ +#version 450 +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec4 inColor; +layout(location = 2) in vec2 inUV; + +layout(location = 0) out vec4 fragColor; +layout(location = 1) out vec2 fragUV; +layout(location = 2) out vec2 fragNdc; + +void main() { + gl_Position = vec4(inPosition, 0.0, 1.0); + fragColor = inColor; + fragUV = inUV; + fragNdc = inPosition; +} diff --git a/src/modules/graphics/shaders/lit2d.vert.spv b/src/modules/graphics/shaders/lit2d.vert.spv new file mode 100644 index 00000000..cac89bbe Binary files /dev/null and b/src/modules/graphics/shaders/lit2d.vert.spv differ diff --git a/src/modules/graphics/shaders/lit2d_frag_spv.inc b/src/modules/graphics/shaders/lit2d_frag_spv.inc new file mode 100644 index 00000000..df80f5a1 --- /dev/null +++ b/src/modules/graphics/shaders/lit2d_frag_spv.inc @@ -0,0 +1,156 @@ +static const uint32_t lit2d_frag_spv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x000000c1, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0009000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000011, 0x00000015, 0x00000035, + 0x000000b6, 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, 0x00040005, 0x00000009, 0x65736162, + 0x00000000, 0x00060005, 0x0000000d, 0x65626c61, 0x61536f64, 0x656c706d, 0x00000072, 0x00040005, + 0x00000011, 0x67617266, 0x00005655, 0x00050005, 0x00000015, 0x67617266, 0x6f6c6f43, 0x00000072, + 0x00040005, 0x0000001a, 0x6d61536e, 0x00656c70, 0x00060005, 0x0000001b, 0x6d726f6e, 0x61536c61, + 0x656c706d, 0x00000072, 0x00030005, 0x00000025, 0x0000004e, 0x00040005, 0x00000034, 0x69676f6c, + 0x006c6163, 0x00040005, 0x00000035, 0x67617266, 0x0063644e, 0x00040005, 0x0000003b, 0x6867694c, + 0x00443274, 0x00060006, 0x0000003b, 0x00000000, 0x52736f70, 0x75696461, 0x00000073, 0x00050006, + 0x0000003b, 0x00000001, 0x6f6c6f63, 0x00000072, 0x00050005, 0x0000003e, 0x6867694c, 0x676e6974, + 0x00004432, 0x00050006, 0x0000003e, 0x00000000, 0x69626d61, 0x00746e65, 0x00050006, 0x0000003e, + 0x00000001, 0x6174656d, 0x00000000, 0x00050006, 0x0000003e, 0x00000002, 0x6867696c, 0x00007374, + 0x00050005, 0x00000040, 0x6867696c, 0x676e6974, 0x00000000, 0x00030005, 0x00000048, 0x0074696c, + 0x00040005, 0x0000004e, 0x6e756f63, 0x00000074, 0x00030005, 0x00000055, 0x00000069, 0x00040005, + 0x00000065, 0x6867694c, 0x00443274, 0x00060006, 0x00000065, 0x00000000, 0x52736f70, 0x75696461, + 0x00000073, 0x00050006, 0x00000065, 0x00000001, 0x6f6c6f63, 0x00000072, 0x00030005, 0x00000067, + 0x0000004c, 0x00050005, 0x00000071, 0x6867696c, 0x6c6f4374, 0x00000000, 0x00040005, 0x00000075, + 0x746e6f63, 0x00626972, 0x00030005, 0x0000007d, 0x0000644c, 0x00030005, 0x0000008b, 0x004c6f74, + 0x00040005, 0x00000091, 0x74736964, 0x00000000, 0x00040005, 0x00000094, 0x65747461, 0x0000006e, + 0x00030005, 0x0000009f, 0x0000644c, 0x00050005, 0x000000b6, 0x4374756f, 0x726f6c6f, 0x00000000, + 0x00040047, 0x0000000d, 0x00000021, 0x00000000, 0x00040047, 0x0000000d, 0x00000022, 0x00000000, + 0x00040047, 0x00000011, 0x0000001e, 0x00000001, 0x00040047, 0x00000015, 0x0000001e, 0x00000000, + 0x00040047, 0x0000001b, 0x00000021, 0x00000001, 0x00040047, 0x0000001b, 0x00000022, 0x00000000, + 0x00040047, 0x00000035, 0x0000001e, 0x00000002, 0x00050048, 0x0000003b, 0x00000000, 0x00000023, + 0x00000000, 0x00050048, 0x0000003b, 0x00000001, 0x00000023, 0x00000010, 0x00040047, 0x0000003d, + 0x00000006, 0x00000020, 0x00030047, 0x0000003e, 0x00000002, 0x00050048, 0x0000003e, 0x00000000, + 0x00000023, 0x00000000, 0x00050048, 0x0000003e, 0x00000001, 0x00000023, 0x00000010, 0x00050048, + 0x0000003e, 0x00000002, 0x00000023, 0x00000020, 0x00040047, 0x00000040, 0x00000021, 0x00000002, + 0x00040047, 0x00000040, 0x00000022, 0x00000000, 0x00040047, 0x000000b6, 0x0000001e, 0x00000000, + 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, + 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000007, 0x00000007, + 0x00090019, 0x0000000a, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000000, 0x0003001b, 0x0000000b, 0x0000000a, 0x00040020, 0x0000000c, 0x00000000, 0x0000000b, + 0x0004003b, 0x0000000c, 0x0000000d, 0x00000000, 0x00040017, 0x0000000f, 0x00000006, 0x00000002, + 0x00040020, 0x00000010, 0x00000001, 0x0000000f, 0x0004003b, 0x00000010, 0x00000011, 0x00000001, + 0x00040020, 0x00000014, 0x00000001, 0x00000007, 0x0004003b, 0x00000014, 0x00000015, 0x00000001, + 0x00040017, 0x00000018, 0x00000006, 0x00000003, 0x00040020, 0x00000019, 0x00000007, 0x00000018, + 0x0004003b, 0x0000000c, 0x0000001b, 0x00000000, 0x0004002b, 0x00000006, 0x00000020, 0x40000000, + 0x0004002b, 0x00000006, 0x00000022, 0x3f800000, 0x00040015, 0x00000028, 0x00000020, 0x00000000, + 0x0004002b, 0x00000028, 0x00000029, 0x00000002, 0x00040020, 0x0000002a, 0x00000007, 0x00000006, + 0x0004002b, 0x00000006, 0x0000002d, 0x3d4ccccd, 0x00040020, 0x00000033, 0x00000007, 0x0000000f, + 0x0004003b, 0x00000010, 0x00000035, 0x00000001, 0x0004002b, 0x00000006, 0x00000037, 0x3f000000, + 0x0004001e, 0x0000003b, 0x00000007, 0x00000007, 0x0004002b, 0x00000028, 0x0000003c, 0x00000008, + 0x0004001c, 0x0000003d, 0x0000003b, 0x0000003c, 0x0005001e, 0x0000003e, 0x00000007, 0x00000007, + 0x0000003d, 0x00040020, 0x0000003f, 0x00000002, 0x0000003e, 0x0004003b, 0x0000003f, 0x00000040, + 0x00000002, 0x00040015, 0x00000041, 0x00000020, 0x00000001, 0x0004002b, 0x00000041, 0x00000042, + 0x00000001, 0x00040020, 0x00000043, 0x00000002, 0x00000007, 0x0004002b, 0x00000041, 0x00000049, + 0x00000000, 0x00040020, 0x0000004d, 0x00000007, 0x00000041, 0x0004002b, 0x00000028, 0x0000004f, + 0x00000000, 0x00040020, 0x00000050, 0x00000002, 0x00000006, 0x0004002b, 0x00000041, 0x0000005c, + 0x00000008, 0x00020014, 0x0000005d, 0x0004001e, 0x00000065, 0x00000007, 0x00000007, 0x00040020, + 0x00000066, 0x00000007, 0x00000065, 0x0004002b, 0x00000041, 0x00000068, 0x00000002, 0x00040020, + 0x0000006a, 0x00000002, 0x0000003b, 0x0004002b, 0x00000006, 0x00000076, 0x00000000, 0x0004002b, + 0x00000028, 0x00000077, 0x00000003, 0x0004002b, 0x00000006, 0x00000081, 0x3eb33333, 0x00040020, + 0x000000b5, 0x00000003, 0x00000007, 0x0004003b, 0x000000b5, 0x000000b6, 0x00000003, 0x00050036, + 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b, 0x00000008, + 0x00000009, 0x00000007, 0x0004003b, 0x00000019, 0x0000001a, 0x00000007, 0x0004003b, 0x00000019, + 0x00000025, 0x00000007, 0x0004003b, 0x00000033, 0x00000034, 0x00000007, 0x0004003b, 0x00000019, + 0x00000048, 0x00000007, 0x0004003b, 0x0000004d, 0x0000004e, 0x00000007, 0x0004003b, 0x0000004d, + 0x00000055, 0x00000007, 0x0004003b, 0x00000066, 0x00000067, 0x00000007, 0x0004003b, 0x00000019, + 0x00000071, 0x00000007, 0x0004003b, 0x0000002a, 0x00000075, 0x00000007, 0x0004003b, 0x00000019, + 0x0000007d, 0x00000007, 0x0004003b, 0x00000033, 0x0000008b, 0x00000007, 0x0004003b, 0x0000002a, + 0x00000091, 0x00000007, 0x0004003b, 0x0000002a, 0x00000094, 0x00000007, 0x0004003b, 0x00000019, + 0x0000009f, 0x00000007, 0x0004003d, 0x0000000b, 0x0000000e, 0x0000000d, 0x0004003d, 0x0000000f, + 0x00000012, 0x00000011, 0x00050057, 0x00000007, 0x00000013, 0x0000000e, 0x00000012, 0x0004003d, + 0x00000007, 0x00000016, 0x00000015, 0x00050085, 0x00000007, 0x00000017, 0x00000013, 0x00000016, + 0x0003003e, 0x00000009, 0x00000017, 0x0004003d, 0x0000000b, 0x0000001c, 0x0000001b, 0x0004003d, + 0x0000000f, 0x0000001d, 0x00000011, 0x00050057, 0x00000007, 0x0000001e, 0x0000001c, 0x0000001d, + 0x0008004f, 0x00000018, 0x0000001f, 0x0000001e, 0x0000001e, 0x00000000, 0x00000001, 0x00000002, + 0x0005008e, 0x00000018, 0x00000021, 0x0000001f, 0x00000020, 0x00060050, 0x00000018, 0x00000023, + 0x00000022, 0x00000022, 0x00000022, 0x00050083, 0x00000018, 0x00000024, 0x00000021, 0x00000023, + 0x0003003e, 0x0000001a, 0x00000024, 0x0004003d, 0x00000018, 0x00000026, 0x0000001a, 0x0007004f, + 0x0000000f, 0x00000027, 0x00000026, 0x00000026, 0x00000000, 0x00000001, 0x00050041, 0x0000002a, + 0x0000002b, 0x0000001a, 0x00000029, 0x0004003d, 0x00000006, 0x0000002c, 0x0000002b, 0x0007000c, + 0x00000006, 0x0000002e, 0x00000001, 0x00000028, 0x0000002c, 0x0000002d, 0x00050051, 0x00000006, + 0x0000002f, 0x00000027, 0x00000000, 0x00050051, 0x00000006, 0x00000030, 0x00000027, 0x00000001, + 0x00060050, 0x00000018, 0x00000031, 0x0000002f, 0x00000030, 0x0000002e, 0x0006000c, 0x00000018, + 0x00000032, 0x00000001, 0x00000045, 0x00000031, 0x0003003e, 0x00000025, 0x00000032, 0x0004003d, + 0x0000000f, 0x00000036, 0x00000035, 0x0005008e, 0x0000000f, 0x00000038, 0x00000036, 0x00000037, + 0x00050050, 0x0000000f, 0x00000039, 0x00000037, 0x00000037, 0x00050081, 0x0000000f, 0x0000003a, + 0x00000038, 0x00000039, 0x00050041, 0x00000043, 0x00000044, 0x00000040, 0x00000042, 0x0004003d, + 0x00000007, 0x00000045, 0x00000044, 0x0007004f, 0x0000000f, 0x00000046, 0x00000045, 0x00000045, + 0x00000001, 0x00000002, 0x00050085, 0x0000000f, 0x00000047, 0x0000003a, 0x00000046, 0x0003003e, + 0x00000034, 0x00000047, 0x00050041, 0x00000043, 0x0000004a, 0x00000040, 0x00000049, 0x0004003d, + 0x00000007, 0x0000004b, 0x0000004a, 0x0008004f, 0x00000018, 0x0000004c, 0x0000004b, 0x0000004b, + 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x00000048, 0x0000004c, 0x00060041, 0x00000050, + 0x00000051, 0x00000040, 0x00000042, 0x0000004f, 0x0004003d, 0x00000006, 0x00000052, 0x00000051, + 0x00050081, 0x00000006, 0x00000053, 0x00000052, 0x00000037, 0x0004006e, 0x00000041, 0x00000054, + 0x00000053, 0x0003003e, 0x0000004e, 0x00000054, 0x0003003e, 0x00000055, 0x00000049, 0x000200f9, + 0x00000056, 0x000200f8, 0x00000056, 0x000400f6, 0x00000058, 0x00000059, 0x00000000, 0x000200f9, + 0x0000005a, 0x000200f8, 0x0000005a, 0x0004003d, 0x00000041, 0x0000005b, 0x00000055, 0x000500b1, + 0x0000005d, 0x0000005e, 0x0000005b, 0x0000005c, 0x000400fa, 0x0000005e, 0x00000057, 0x00000058, + 0x000200f8, 0x00000057, 0x0004003d, 0x00000041, 0x0000005f, 0x00000055, 0x0004003d, 0x00000041, + 0x00000060, 0x0000004e, 0x000500af, 0x0000005d, 0x00000061, 0x0000005f, 0x00000060, 0x000300f7, + 0x00000063, 0x00000000, 0x000400fa, 0x00000061, 0x00000062, 0x00000063, 0x000200f8, 0x00000062, + 0x000200f9, 0x00000058, 0x000200f8, 0x00000063, 0x0004003d, 0x00000041, 0x00000069, 0x00000055, + 0x00060041, 0x0000006a, 0x0000006b, 0x00000040, 0x00000068, 0x00000069, 0x0004003d, 0x0000003b, + 0x0000006c, 0x0000006b, 0x00050051, 0x00000007, 0x0000006d, 0x0000006c, 0x00000000, 0x00050041, + 0x00000008, 0x0000006e, 0x00000067, 0x00000049, 0x0003003e, 0x0000006e, 0x0000006d, 0x00050051, + 0x00000007, 0x0000006f, 0x0000006c, 0x00000001, 0x00050041, 0x00000008, 0x00000070, 0x00000067, + 0x00000042, 0x0003003e, 0x00000070, 0x0000006f, 0x00050041, 0x00000008, 0x00000072, 0x00000067, + 0x00000042, 0x0004003d, 0x00000007, 0x00000073, 0x00000072, 0x0008004f, 0x00000018, 0x00000074, + 0x00000073, 0x00000073, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x00000071, 0x00000074, + 0x0003003e, 0x00000075, 0x00000076, 0x00060041, 0x0000002a, 0x00000078, 0x00000067, 0x00000049, + 0x00000077, 0x0004003d, 0x00000006, 0x00000079, 0x00000078, 0x000500bc, 0x0000005d, 0x0000007a, + 0x00000079, 0x00000076, 0x000300f7, 0x0000007c, 0x00000000, 0x000400fa, 0x0000007a, 0x0000007b, + 0x0000008a, 0x000200f8, 0x0000007b, 0x00050041, 0x00000008, 0x0000007e, 0x00000067, 0x00000049, + 0x0004003d, 0x00000007, 0x0000007f, 0x0000007e, 0x0007004f, 0x0000000f, 0x00000080, 0x0000007f, + 0x0000007f, 0x00000000, 0x00000001, 0x00050051, 0x00000006, 0x00000082, 0x00000080, 0x00000000, + 0x00050051, 0x00000006, 0x00000083, 0x00000080, 0x00000001, 0x00060050, 0x00000018, 0x00000084, + 0x00000082, 0x00000083, 0x00000081, 0x0006000c, 0x00000018, 0x00000085, 0x00000001, 0x00000045, + 0x00000084, 0x0003003e, 0x0000007d, 0x00000085, 0x0004003d, 0x00000018, 0x00000086, 0x00000025, + 0x0004003d, 0x00000018, 0x00000087, 0x0000007d, 0x00050094, 0x00000006, 0x00000088, 0x00000086, + 0x00000087, 0x0007000c, 0x00000006, 0x00000089, 0x00000001, 0x00000028, 0x00000088, 0x00000076, + 0x0003003e, 0x00000075, 0x00000089, 0x000200f9, 0x0000007c, 0x000200f8, 0x0000008a, 0x00050041, + 0x00000008, 0x0000008c, 0x00000067, 0x00000049, 0x0004003d, 0x00000007, 0x0000008d, 0x0000008c, + 0x0007004f, 0x0000000f, 0x0000008e, 0x0000008d, 0x0000008d, 0x00000000, 0x00000001, 0x0004003d, + 0x0000000f, 0x0000008f, 0x00000034, 0x00050083, 0x0000000f, 0x00000090, 0x0000008e, 0x0000008f, + 0x0003003e, 0x0000008b, 0x00000090, 0x0004003d, 0x0000000f, 0x00000092, 0x0000008b, 0x0006000c, + 0x00000006, 0x00000093, 0x00000001, 0x00000042, 0x00000092, 0x0003003e, 0x00000091, 0x00000093, + 0x0004003d, 0x00000006, 0x00000095, 0x00000091, 0x00060041, 0x0000002a, 0x00000096, 0x00000067, + 0x00000049, 0x00000077, 0x0004003d, 0x00000006, 0x00000097, 0x00000096, 0x0007000c, 0x00000006, + 0x00000098, 0x00000001, 0x00000028, 0x00000097, 0x00000022, 0x00050088, 0x00000006, 0x00000099, + 0x00000095, 0x00000098, 0x00050083, 0x00000006, 0x0000009a, 0x00000022, 0x00000099, 0x0008000c, + 0x00000006, 0x0000009b, 0x00000001, 0x0000002b, 0x0000009a, 0x00000076, 0x00000022, 0x0003003e, + 0x00000094, 0x0000009b, 0x0004003d, 0x00000006, 0x0000009c, 0x00000094, 0x0004003d, 0x00000006, + 0x0000009d, 0x00000094, 0x00050085, 0x00000006, 0x0000009e, 0x0000009d, 0x0000009c, 0x0003003e, + 0x00000094, 0x0000009e, 0x0004003d, 0x0000000f, 0x000000a0, 0x0000008b, 0x00060041, 0x0000002a, + 0x000000a1, 0x00000067, 0x00000049, 0x00000077, 0x0004003d, 0x00000006, 0x000000a2, 0x000000a1, + 0x00050085, 0x00000006, 0x000000a3, 0x000000a2, 0x00000081, 0x00050051, 0x00000006, 0x000000a4, + 0x000000a0, 0x00000000, 0x00050051, 0x00000006, 0x000000a5, 0x000000a0, 0x00000001, 0x00060050, + 0x00000018, 0x000000a6, 0x000000a4, 0x000000a5, 0x000000a3, 0x0006000c, 0x00000018, 0x000000a7, + 0x00000001, 0x00000045, 0x000000a6, 0x0003003e, 0x0000009f, 0x000000a7, 0x0004003d, 0x00000018, + 0x000000a8, 0x00000025, 0x0004003d, 0x00000018, 0x000000a9, 0x0000009f, 0x00050094, 0x00000006, + 0x000000aa, 0x000000a8, 0x000000a9, 0x0007000c, 0x00000006, 0x000000ab, 0x00000001, 0x00000028, + 0x000000aa, 0x00000076, 0x0004003d, 0x00000006, 0x000000ac, 0x00000094, 0x00050085, 0x00000006, + 0x000000ad, 0x000000ab, 0x000000ac, 0x0003003e, 0x00000075, 0x000000ad, 0x000200f9, 0x0000007c, + 0x000200f8, 0x0000007c, 0x0004003d, 0x00000018, 0x000000ae, 0x00000071, 0x0004003d, 0x00000006, + 0x000000af, 0x00000075, 0x0005008e, 0x00000018, 0x000000b0, 0x000000ae, 0x000000af, 0x0004003d, + 0x00000018, 0x000000b1, 0x00000048, 0x00050081, 0x00000018, 0x000000b2, 0x000000b1, 0x000000b0, + 0x0003003e, 0x00000048, 0x000000b2, 0x000200f9, 0x00000059, 0x000200f8, 0x00000059, 0x0004003d, + 0x00000041, 0x000000b3, 0x00000055, 0x00050080, 0x00000041, 0x000000b4, 0x000000b3, 0x00000042, + 0x0003003e, 0x00000055, 0x000000b4, 0x000200f9, 0x00000056, 0x000200f8, 0x00000058, 0x0004003d, + 0x00000007, 0x000000b7, 0x00000009, 0x0008004f, 0x00000018, 0x000000b8, 0x000000b7, 0x000000b7, + 0x00000000, 0x00000001, 0x00000002, 0x0004003d, 0x00000018, 0x000000b9, 0x00000048, 0x00050085, + 0x00000018, 0x000000ba, 0x000000b8, 0x000000b9, 0x00050041, 0x0000002a, 0x000000bb, 0x00000009, + 0x00000077, 0x0004003d, 0x00000006, 0x000000bc, 0x000000bb, 0x00050051, 0x00000006, 0x000000bd, + 0x000000ba, 0x00000000, 0x00050051, 0x00000006, 0x000000be, 0x000000ba, 0x00000001, 0x00050051, + 0x00000006, 0x000000bf, 0x000000ba, 0x00000002, 0x00070050, 0x00000007, 0x000000c0, 0x000000bd, + 0x000000be, 0x000000bf, 0x000000bc, 0x0003003e, 0x000000b6, 0x000000c0, 0x000100fd, 0x00010038, +}; +static const size_t lit2d_frag_spv_count = 1224; diff --git a/src/modules/graphics/shaders/lit2d_vert_spv.inc b/src/modules/graphics/shaders/lit2d_vert_spv.inc new file mode 100644 index 00000000..971b7e1a --- /dev/null +++ b/src/modules/graphics/shaders/lit2d_vert_spv.inc @@ -0,0 +1,43 @@ +static const uint32_t lit2d_vert_spv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x00000025, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x000c000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000012, 0x0000001b, + 0x0000001d, 0x00000020, 0x00000021, 0x00000023, 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, 0x505f6c67, + 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x0000000b, 0x00000000, 0x505f6c67, 0x7469736f, + 0x006e6f69, 0x00070006, 0x0000000b, 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, + 0x00070006, 0x0000000b, 0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00070006, + 0x0000000b, 0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, 0x00030005, 0x0000000d, + 0x00000000, 0x00050005, 0x00000012, 0x6f506e69, 0x69746973, 0x00006e6f, 0x00050005, 0x0000001b, + 0x67617266, 0x6f6c6f43, 0x00000072, 0x00040005, 0x0000001d, 0x6f436e69, 0x00726f6c, 0x00040005, + 0x00000020, 0x67617266, 0x00005655, 0x00040005, 0x00000021, 0x56556e69, 0x00000000, 0x00040005, + 0x00000023, 0x67617266, 0x0063644e, 0x00030047, 0x0000000b, 0x00000002, 0x00050048, 0x0000000b, + 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x0000000b, 0x00000001, 0x0000000b, 0x00000001, + 0x00050048, 0x0000000b, 0x00000002, 0x0000000b, 0x00000003, 0x00050048, 0x0000000b, 0x00000003, + 0x0000000b, 0x00000004, 0x00040047, 0x00000012, 0x0000001e, 0x00000000, 0x00040047, 0x0000001b, + 0x0000001e, 0x00000000, 0x00040047, 0x0000001d, 0x0000001e, 0x00000001, 0x00040047, 0x00000020, + 0x0000001e, 0x00000001, 0x00040047, 0x00000021, 0x0000001e, 0x00000002, 0x00040047, 0x00000023, + 0x0000001e, 0x00000002, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, + 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040015, 0x00000008, + 0x00000020, 0x00000000, 0x0004002b, 0x00000008, 0x00000009, 0x00000001, 0x0004001c, 0x0000000a, + 0x00000006, 0x00000009, 0x0006001e, 0x0000000b, 0x00000007, 0x00000006, 0x0000000a, 0x0000000a, + 0x00040020, 0x0000000c, 0x00000003, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000003, + 0x00040015, 0x0000000e, 0x00000020, 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, + 0x00040017, 0x00000010, 0x00000006, 0x00000002, 0x00040020, 0x00000011, 0x00000001, 0x00000010, + 0x0004003b, 0x00000011, 0x00000012, 0x00000001, 0x0004002b, 0x00000006, 0x00000014, 0x00000000, + 0x0004002b, 0x00000006, 0x00000015, 0x3f800000, 0x00040020, 0x00000019, 0x00000003, 0x00000007, + 0x0004003b, 0x00000019, 0x0000001b, 0x00000003, 0x00040020, 0x0000001c, 0x00000001, 0x00000007, + 0x0004003b, 0x0000001c, 0x0000001d, 0x00000001, 0x00040020, 0x0000001f, 0x00000003, 0x00000010, + 0x0004003b, 0x0000001f, 0x00000020, 0x00000003, 0x0004003b, 0x00000011, 0x00000021, 0x00000001, + 0x0004003b, 0x0000001f, 0x00000023, 0x00000003, 0x00050036, 0x00000002, 0x00000004, 0x00000000, + 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x00000010, 0x00000013, 0x00000012, 0x00050051, + 0x00000006, 0x00000016, 0x00000013, 0x00000000, 0x00050051, 0x00000006, 0x00000017, 0x00000013, + 0x00000001, 0x00070050, 0x00000007, 0x00000018, 0x00000016, 0x00000017, 0x00000014, 0x00000015, + 0x00050041, 0x00000019, 0x0000001a, 0x0000000d, 0x0000000f, 0x0003003e, 0x0000001a, 0x00000018, + 0x0004003d, 0x00000007, 0x0000001e, 0x0000001d, 0x0003003e, 0x0000001b, 0x0000001e, 0x0004003d, + 0x00000010, 0x00000022, 0x00000021, 0x0003003e, 0x00000020, 0x00000022, 0x0004003d, 0x00000010, + 0x00000024, 0x00000012, 0x0003003e, 0x00000023, 0x00000024, 0x000100fd, 0x00010038, +}; +static const size_t lit2d_vert_spv_count = 319; diff --git a/src/modules/graphics/shaders/mesh3d.frag b/src/modules/graphics/shaders/mesh3d.frag index 53a8381c..54337a0d 100644 --- a/src/modules/graphics/shaders/mesh3d.frag +++ b/src/modules/graphics/shaders/mesh3d.frag @@ -3,21 +3,188 @@ layout(location = 0) in vec3 vNormal; layout(location = 1) in vec2 vUV; layout(location = 2) in vec4 vTint; -layout(location = 3) in vec3 vLightDir; -layout(location = 4) in vec3 vLightColor; +layout(location = 3) in vec3 vWorldPos; +layout(location = 4) in vec3 vCameraPos; -layout(set = 0, binding = 1) uniform sampler2D albedo; +struct Light3D { + vec4 posRadius; // xyz = point OR dir; w = radius (0 => directional) + vec4 color; +}; + +layout(set = 0, binding = 0, std140) uniform Frame { + mat4 mvp; + mat4 model; + vec4 lightDirIntensity; // xyz = primary dir (toward surface); w = lightCount + vec4 lightColor; // rgb = primary radiance + vec4 tint; + vec4 cameraPos; // xyz = eye; w = roughness + vec4 ambient; // rgb = ambient; w = metallic + Light3D lights[8]; +} ubo; + +layout(set = 0, binding = 1) uniform sampler2D albedoSampler; +layout(set = 0, binding = 2) uniform sampler2D normalSampler; +layout(set = 0, binding = 3) uniform samplerCube envSampler; + +layout(set = 0, binding = 4, std140) uniform ShadowFrame { + mat4 lightVP[3]; + vec4 splits; // xyz = cascade ends (view-space +Z depth), w = strength + vec4 bias; // x = depth bias, y = enabled, z = receive, w unused +} shadow; + +layout(set = 0, binding = 5) uniform sampler2DArray shadowMap; layout(location = 0) out vec4 outColor; -void main() { - vec4 base = texture(albedo, vUV) * vTint; - vec3 N = normalize(vNormal); - vec3 L = normalize(vLightDir); - float ndotl = max(dot(N, L), 0.0); - // Soften just a little so silhouette isn't pure black, but keep contrast. - float diffuse = mix(ndotl, ndotl * 0.5 + 0.5, 0.25); - float ambient = 0.22; - vec3 lit = base.rgb * (ambient + diffuse * vLightColor); - outColor = vec4(lit, base.a); +const float PI = 3.14159265359; + +float DistributionGGX(vec3 N, vec3 H, float roughness) { + float a = max(roughness * roughness, 0.002); + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0); + float denom = (NdotH * NdotH * (a2 - 1.0) + 1.0); + return a2 / max(PI * denom * denom, 1e-4); +} + +float GeometrySchlickGGX(float NdotV, float roughness) { + float r = roughness + 1.0; + float k = (r * r) / 8.0; + return NdotV / max(NdotV * (1.0 - k) + k, 1e-4); +} + +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { + return GeometrySchlickGGX(max(dot(N, V), 0.0), roughness) * + GeometrySchlickGGX(max(dot(N, L), 0.0), roughness); +} + +vec3 fresnelSchlick(float cosTheta, vec3 F0) { + return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + +vec3 applyNormalMap(vec3 N, vec3 mapSample, vec3 worldPos, vec2 uv) { + vec3 mapN = mapSample * 2.0 - 1.0; + if (abs(mapN.x) < 0.1 && abs(mapN.y) < 0.1 && mapN.z > 0.85) + return normalize(N); + vec3 dp1 = dFdx(worldPos); + vec3 dp2 = dFdy(worldPos); + vec2 duv1 = dFdx(uv); + vec2 duv2 = dFdy(uv); + if (abs(duv1.x * duv2.y - duv2.x * duv1.y) < 1e-8) + return normalize(N); + vec3 T = normalize(cross(dp2, N) * duv1.x + cross(N, dp1) * duv2.x); + vec3 B = normalize(cross(dp2, N) * duv1.y + cross(N, dp1) * duv2.y); + if (length(T) < 1e-4 || length(B) < 1e-4) + return normalize(N); + return normalize(mat3(T, B, normalize(N)) * mapN); +} + +vec3 shadeLight(vec3 N, vec3 V, vec3 albedo, float metallic, float roughness, vec3 L, vec3 radiance) { + float NdotL = max(dot(N, L), 0.0); + // Lambert wrap (legacy mesh3d) for dielectrics; metals rely on specular. + float diffuse = mix(NdotL, NdotL * 0.5 + 0.5, 0.25); + + vec3 H = normalize(V + L); + vec3 F0 = mix(vec3(0.04), albedo, metallic); + float NDF = DistributionGGX(N, H, roughness); + float G = GeometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + vec3 specular = (NDF * G * F) / max(4.0 * max(dot(N, V), 0.0) * max(NdotL, 0.001), 1e-3); + + vec3 kD = (vec3(1.0) - F) * (1.0 - metallic); + return (kD * albedo * diffuse + specular * NdotL) * radiance; +} + +float sampleShadowPCF(vec3 worldPos, float viewDepth) { + if (shadow.bias.y < 0.5 || shadow.bias.z < 0.5 || shadow.splits.w < 1e-4) + return 1.0; + + int cascade = 2; + if (viewDepth < shadow.splits.x) cascade = 0; + else if (viewDepth < shadow.splits.y) cascade = 1; + + vec4 lightClip = shadow.lightVP[cascade] * vec4(worldPos, 1.0); + vec3 ndc = lightClip.xyz / max(lightClip.w, 1e-6); + vec2 uv = ndc.xy * 0.5 + 0.5; + float depth = ndc.z; + if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 || depth < 0.0 || depth > 1.0) + return 1.0; + + float bias = shadow.bias.x; + vec2 texel = 1.0 / vec2(textureSize(shadowMap, 0).xy); + float sum = 0.0; + for (int y = -1; y <= 1; ++y) { + for (int x = -1; x <= 1; ++x) { + float closest = texture(shadowMap, vec3(uv + vec2(float(x), float(y)) * texel, float(cascade))).r; + sum += (depth - bias > closest) ? 0.0 : 1.0; + } + } + float vis = sum / 9.0; + return mix(1.0, vis, clamp(shadow.splits.w, 0.0, 1.0)); +} + +void main() { + vec4 base = texture(albedoSampler, vUV) * vTint; + vec3 albedo = base.rgb; + float metallic = clamp(ubo.ambient.w, 0.0, 1.0); + float roughness = clamp(ubo.cameraPos.w, 0.04, 1.0); + int count = int(ubo.lightDirIntensity.w + 0.5); + + vec3 N = normalize(vNormal); + vec3 nSample = texture(normalSampler, vUV).xyz; + if (length(nSample - vec3(0.5, 0.5, 1.0)) > 0.04) + N = applyNormalMap(N, nSample, vWorldPos, vUV); + + vec3 V = normalize(vCameraPos - vWorldPos); + vec3 Lo = vec3(0.0); + float viewDepth = length(vCameraPos - vWorldPos); + float shadowVis = sampleShadowPCF(vWorldPos, viewDepth); + + // Primary directional light (legacy slot) — receives CSM. + Lo += shadeLight(N, V, albedo, metallic, roughness, + normalize(ubo.lightDirIntensity.xyz), ubo.lightColor.rgb) * shadowVis; + + for (int i = 0; i < 8; ++i) { + if (i >= count) break; + Light3D Lgt = ubo.lights[i]; + vec3 radiance = Lgt.color.rgb; + vec3 L; + if (Lgt.posRadius.w <= 0.0) { + L = normalize(Lgt.posRadius.xyz); + if (length(L - normalize(ubo.lightDirIntensity.xyz)) < 1e-3) + continue; + // Additional dir lights are unshadowed. + } else { + vec3 toL = Lgt.posRadius.xyz - vWorldPos; + float dist = length(toL); + L = toL / max(dist, 1e-4); + float atten = clamp(1.0 - dist / max(Lgt.posRadius.w, 1e-3), 0.0, 1.0); + atten *= atten; + radiance *= atten; + } + Lo += shadeLight(N, V, albedo, metallic, roughness, L, radiance); + } + + // Keep a Lambert-like floor so dielectrics stay close to the old brightness. + float amb = max(max(ubo.ambient.r, ubo.ambient.g), ubo.ambient.b); + if (amb < 0.05) + amb = 0.22; + else + amb = max(amb, 0.12); + vec3 color = albedo * amb * (1.0 - metallic) + Lo; + + // Specular IBL (envIntensity packed in lightColor.w). No BRDF LUT — Fresnel * env. + float envIntensity = ubo.lightColor.w; + if (envIntensity > 1e-4) { + vec3 R = reflect(-V, N); + float lod = roughness * 5.0; + vec3 envSpec = textureLod(envSampler, R, lod).rgb * envIntensity; + vec3 F0 = mix(vec3(0.04), albedo, metallic); + vec3 F = fresnelSchlick(max(dot(N, V), 0.0), F0); + color += envSpec * F; + // Cheap diffuse IBL for dielectrics (sample along N at a blurry lod). + vec3 irr = textureLod(envSampler, N, 5.0).rgb * envIntensity; + color += albedo * irr * (1.0 - metallic) * (1.0 - F) * 0.35; + } + + outColor = vec4(color, base.a); } diff --git a/src/modules/graphics/shaders/mesh3d.frag.spv b/src/modules/graphics/shaders/mesh3d.frag.spv index 7a8daaca..2c6e3b60 100644 Binary files a/src/modules/graphics/shaders/mesh3d.frag.spv and b/src/modules/graphics/shaders/mesh3d.frag.spv differ diff --git a/src/modules/graphics/shaders/mesh3d.vert b/src/modules/graphics/shaders/mesh3d.vert index 0e277217..0b4ade0c 100644 --- a/src/modules/graphics/shaders/mesh3d.vert +++ b/src/modules/graphics/shaders/mesh3d.vert @@ -4,26 +4,35 @@ layout(location = 0) in vec3 inPos; layout(location = 1) in vec3 inNormal; layout(location = 2) in vec2 inUV; -layout(set = 0, binding = 0) uniform Frame { +struct Light3D { + vec4 posRadius; + vec4 color; +}; + +layout(set = 0, binding = 0, std140) uniform Frame { mat4 mvp; mat4 model; - vec4 lightDirIntensity; // xyz direction toward surface - vec4 lightColor; + vec4 lightDirIntensity; // xyz = primary dir; w = lightCount + vec4 lightColor; // rgb = primary; w = metallic vec4 tint; + vec4 cameraPos; // xyz = eye; w = roughness + vec4 ambient; + Light3D lights[8]; } ubo; layout(location = 0) out vec3 vNormal; layout(location = 1) out vec2 vUV; layout(location = 2) out vec4 vTint; -layout(location = 3) out vec3 vLightDir; -layout(location = 4) out vec3 vLightColor; +layout(location = 3) out vec3 vWorldPos; +layout(location = 4) out vec3 vCameraPos; void main() { gl_Position = ubo.mvp * vec4(inPos, 1.0); + vec4 world = ubo.model * vec4(inPos, 1.0); + vWorldPos = world.xyz; mat3 normalMat = mat3(ubo.model); vNormal = normalize(normalMat * inNormal); vUV = inUV; vTint = ubo.tint; - vLightDir = normalize(ubo.lightDirIntensity.xyz); - vLightColor = ubo.lightColor.rgb; + vCameraPos = ubo.cameraPos.xyz; } diff --git a/src/modules/graphics/shaders/mesh3d.vert.spv b/src/modules/graphics/shaders/mesh3d.vert.spv index 98433858..d5da69b3 100644 Binary files a/src/modules/graphics/shaders/mesh3d.vert.spv and b/src/modules/graphics/shaders/mesh3d.vert.spv differ diff --git a/src/modules/graphics/shaders/mesh3d_clustered.frag b/src/modules/graphics/shaders/mesh3d_clustered.frag new file mode 100644 index 00000000..19edbd8e --- /dev/null +++ b/src/modules/graphics/shaders/mesh3d_clustered.frag @@ -0,0 +1,207 @@ +#version 450 + +layout(location = 0) in vec3 vNormal; +layout(location = 1) in vec2 vUV; +layout(location = 2) in vec4 vTint; +layout(location = 3) in vec3 vWorldPos; +layout(location = 4) in vec3 vCameraPos; +layout(location = 5) in vec3 vViewPos; + +layout(set = 0, binding = 0, std140) uniform Frame { + mat4 mvp; + mat4 model; + mat4 view; + vec4 lightDir; // xyz = primary dir; w = 1 if enabled + vec4 lightColor; + vec4 tint; + vec4 cameraPos; // xyz = eye; w = roughness + vec4 ambient; // rgb; w = metallic + vec4 gridInfo; // tilesX, tilesY, slices, pointCount + vec4 clipInfo; // near, far, screenW, screenH +} ubo; + +struct Light3D { + vec4 posRadius; + vec4 color; +}; + +layout(set = 0, binding = 1) uniform sampler2D albedoSampler; +layout(set = 0, binding = 2) uniform sampler2D normalSampler; +layout(set = 0, binding = 3) uniform samplerCube envSampler; + +layout(std430, set = 0, binding = 4) readonly buffer LightBuffer { + Light3D lights[]; +}; + +layout(std430, set = 0, binding = 5) readonly buffer ClusterTable { + uvec2 clusters[]; // x=offset y=count +}; + +layout(std430, set = 0, binding = 6) readonly buffer LightIndexBuffer { + uint lightIndices[]; +}; + +layout(set = 0, binding = 7, std140) uniform ShadowFrame { + mat4 lightVP[3]; + vec4 splits; + vec4 bias; +} shadow; + +layout(set = 0, binding = 8) uniform sampler2DArray shadowMap; + +layout(location = 0) out vec4 outColor; + +const float PI = 3.14159265359; + +float DistributionGGX(vec3 N, vec3 H, float roughness) { + float a = max(roughness * roughness, 0.002); + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0); + float denom = (NdotH * NdotH * (a2 - 1.0) + 1.0); + return a2 / max(PI * denom * denom, 1e-4); +} + +float GeometrySchlickGGX(float NdotV, float roughness) { + float r = roughness + 1.0; + float k = (r * r) / 8.0; + return NdotV / max(NdotV * (1.0 - k) + k, 1e-4); +} + +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { + return GeometrySchlickGGX(max(dot(N, V), 0.0), roughness) * + GeometrySchlickGGX(max(dot(N, L), 0.0), roughness); +} + +vec3 fresnelSchlick(float cosTheta, vec3 F0) { + return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + +vec3 applyNormalMap(vec3 N, vec3 mapSample, vec3 worldPos, vec2 uv) { + vec3 mapN = mapSample * 2.0 - 1.0; + if (abs(mapN.x) < 0.1 && abs(mapN.y) < 0.1 && mapN.z > 0.85) + return normalize(N); + vec3 dp1 = dFdx(worldPos); + vec3 dp2 = dFdy(worldPos); + vec2 duv1 = dFdx(uv); + vec2 duv2 = dFdy(uv); + if (abs(duv1.x * duv2.y - duv2.x * duv1.y) < 1e-8) + return normalize(N); + vec3 T = normalize(cross(dp2, N) * duv1.x + cross(N, dp1) * duv2.x); + vec3 B = normalize(cross(dp2, N) * duv1.y + cross(N, dp1) * duv2.y); + if (length(T) < 1e-4 || length(B) < 1e-4) + return normalize(N); + return normalize(mat3(T, B, normalize(N)) * mapN); +} + +vec3 shadeLight(vec3 N, vec3 V, vec3 albedo, float metallic, float roughness, vec3 L, vec3 radiance) { + float NdotL = max(dot(N, L), 0.0); + float diffuse = mix(NdotL, NdotL * 0.5 + 0.5, 0.25); + vec3 H = normalize(V + L); + vec3 F0 = mix(vec3(0.04), albedo, metallic); + float NDF = DistributionGGX(N, H, roughness); + float G = GeometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + vec3 specular = (NDF * G * F) / max(4.0 * max(dot(N, V), 0.0) * max(NdotL, 0.001), 1e-3); + vec3 kD = (vec3(1.0) - F) * (1.0 - metallic); + return (kD * albedo * diffuse + specular * NdotL) * radiance; +} + +float sampleShadowPCF(vec3 worldPos, float viewDepth) { + if (shadow.bias.y < 0.5 || shadow.bias.z < 0.5 || shadow.splits.w < 1e-4) + return 1.0; + int cascade = 2; + if (viewDepth < shadow.splits.x) cascade = 0; + else if (viewDepth < shadow.splits.y) cascade = 1; + vec4 lightClip = shadow.lightVP[cascade] * vec4(worldPos, 1.0); + vec3 ndc = lightClip.xyz / max(lightClip.w, 1e-6); + vec2 uv = ndc.xy * 0.5 + 0.5; + float depth = ndc.z; + if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 || depth < 0.0 || depth > 1.0) + return 1.0; + float bias = shadow.bias.x; + vec2 texel = 1.0 / vec2(textureSize(shadowMap, 0).xy); + float sum = 0.0; + for (int y = -1; y <= 1; ++y) { + for (int x = -1; x <= 1; ++x) { + float closest = texture(shadowMap, vec3(uv + vec2(float(x), float(y)) * texel, float(cascade))).r; + sum += (depth - bias > closest) ? 0.0 : 1.0; + } + } + return mix(1.0, sum / 9.0, clamp(shadow.splits.w, 0.0, 1.0)); +} + +uint clusterIndex() { + int tilesX = int(ubo.gridInfo.x + 0.5); + int tilesY = int(ubo.gridInfo.y + 0.5); + int slices = int(ubo.gridInfo.z + 0.5); + float nearZ = ubo.clipInfo.x; + float farZ = max(ubo.clipInfo.y, nearZ + 1e-3); + float screenW = max(ubo.clipInfo.z, 1.0); + float screenH = max(ubo.clipInfo.w, 1.0); + + int tx = clamp(int(floor(gl_FragCoord.x / screenW * float(tilesX))), 0, tilesX - 1); + int ty = clamp(int(floor(gl_FragCoord.y / screenH * float(tilesY))), 0, tilesY - 1); + + float depth = max(-vViewPos.z, nearZ); + int sz = clamp(int(floor((depth - nearZ) / (farZ - nearZ) * float(slices))), 0, slices - 1); + return uint((sz * tilesY + ty) * tilesX + tx); +} + +void main() { + vec4 base = texture(albedoSampler, vUV) * vTint; + vec3 albedo = base.rgb; + float metallic = clamp(ubo.ambient.w, 0.0, 1.0); + float roughness = clamp(ubo.cameraPos.w, 0.04, 1.0); + + vec3 N = normalize(vNormal); + vec3 nSample = texture(normalSampler, vUV).xyz; + if (length(nSample - vec3(0.5, 0.5, 1.0)) > 0.04) + N = applyNormalMap(N, nSample, vWorldPos, vUV); + + vec3 V = normalize(vCameraPos - vWorldPos); + vec3 Lo = vec3(0.0); + float shadowVis = sampleShadowPCF(vWorldPos, max(-vViewPos.z, 0.0)); + + if (ubo.lightDir.w > 0.5) { + Lo += shadeLight(N, V, albedo, metallic, roughness, + normalize(ubo.lightDir.xyz), ubo.lightColor.rgb) * shadowVis; + } + + uint cid = clusterIndex(); + uvec2 entry = clusters[cid]; + uint offset = entry.x; + uint count = min(entry.y, 32u); + for (uint i = 0u; i < count; ++i) { + uint li = lightIndices[offset + i]; + Light3D Lgt = lights[li]; + vec3 radiance = Lgt.color.rgb; + vec3 toL = Lgt.posRadius.xyz - vWorldPos; + float dist = length(toL); + vec3 L = toL / max(dist, 1e-4); + float atten = clamp(1.0 - dist / max(Lgt.posRadius.w, 1e-3), 0.0, 1.0); + atten *= atten; + radiance *= atten; + Lo += shadeLight(N, V, albedo, metallic, roughness, L, radiance); + } + + float amb = max(max(ubo.ambient.r, ubo.ambient.g), ubo.ambient.b); + if (amb < 0.05) + amb = 0.22; + else + amb = max(amb, 0.12); + vec3 color = albedo * amb * (1.0 - metallic) + Lo; + + float envIntensity = ubo.lightColor.w; + if (envIntensity > 1e-4) { + vec3 R = reflect(-V, N); + float lod = roughness * 5.0; + vec3 envSpec = textureLod(envSampler, R, lod).rgb * envIntensity; + vec3 F0 = mix(vec3(0.04), albedo, metallic); + vec3 F = fresnelSchlick(max(dot(N, V), 0.0), F0); + color += envSpec * F; + vec3 irr = textureLod(envSampler, N, 5.0).rgb * envIntensity; + color += albedo * irr * (1.0 - metallic) * (1.0 - F) * 0.35; + } + + outColor = vec4(color, base.a); +} diff --git a/src/modules/graphics/shaders/mesh3d_clustered.frag.spv b/src/modules/graphics/shaders/mesh3d_clustered.frag.spv new file mode 100644 index 00000000..c32ad10c Binary files /dev/null and b/src/modules/graphics/shaders/mesh3d_clustered.frag.spv differ diff --git a/src/modules/graphics/shaders/mesh3d_clustered.vert b/src/modules/graphics/shaders/mesh3d_clustered.vert new file mode 100644 index 00000000..d536a4a0 --- /dev/null +++ b/src/modules/graphics/shaders/mesh3d_clustered.vert @@ -0,0 +1,37 @@ +#version 450 + +layout(location = 0) in vec3 inPos; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec2 inUV; + +layout(set = 0, binding = 0, std140) uniform Frame { + mat4 mvp; + mat4 model; + mat4 view; + vec4 lightDir; // xyz = primary dir toward surface; w = 1 if enabled + vec4 lightColor; + vec4 tint; + vec4 cameraPos; // xyz = eye; w = roughness + vec4 ambient; // rgb; w = metallic + vec4 gridInfo; // tilesX, tilesY, slices, pointCount + vec4 clipInfo; // near, far, screenW, screenH +} ubo; + +layout(location = 0) out vec3 vNormal; +layout(location = 1) out vec2 vUV; +layout(location = 2) out vec4 vTint; +layout(location = 3) out vec3 vWorldPos; +layout(location = 4) out vec3 vCameraPos; +layout(location = 5) out vec3 vViewPos; + +void main() { + gl_Position = ubo.mvp * vec4(inPos, 1.0); + vec4 world = ubo.model * vec4(inPos, 1.0); + vWorldPos = world.xyz; + vViewPos = (ubo.view * world).xyz; + mat3 normalMat = mat3(ubo.model); + vNormal = normalize(normalMat * inNormal); + vUV = inUV; + vTint = ubo.tint; + vCameraPos = ubo.cameraPos.xyz; +} diff --git a/src/modules/graphics/shaders/mesh3d_clustered.vert.spv b/src/modules/graphics/shaders/mesh3d_clustered.vert.spv new file mode 100644 index 00000000..fdf3b5bb Binary files /dev/null and b/src/modules/graphics/shaders/mesh3d_clustered.vert.spv differ diff --git a/src/modules/graphics/shaders/mesh3d_clustered_frag_spv.inc b/src/modules/graphics/shaders/mesh3d_clustered_frag_spv.inc new file mode 100644 index 00000000..cd43590a --- /dev/null +++ b/src/modules/graphics/shaders/mesh3d_clustered_frag_spv.inc @@ -0,0 +1,758 @@ +static const uint32_t mesh3d_clustered_frag_spv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x000003cf, 0x00000000, 0x00020011, 0x00000001, 0x00020011, + 0x00000032, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, + 0x00000000, 0x00000001, 0x000d000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000254, + 0x00000271, 0x0000029a, 0x0000029d, 0x000002ae, 0x000002be, 0x000002c9, 0x000003c7, 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, 0x00090005, 0x0000000e, 0x74736944, 0x75626972, 0x6e6f6974, + 0x28584747, 0x3b336676, 0x3b336676, 0x003b3166, 0x00030005, 0x0000000b, 0x0000004e, 0x00030005, + 0x0000000c, 0x00000048, 0x00050005, 0x0000000d, 0x67756f72, 0x73656e68, 0x00000073, 0x00090005, + 0x00000013, 0x6d6f6547, 0x79727465, 0x6c686353, 0x476b6369, 0x66285847, 0x31663b31, 0x0000003b, + 0x00040005, 0x00000011, 0x746f644e, 0x00000056, 0x00050005, 0x00000012, 0x67756f72, 0x73656e68, + 0x00000073, 0x000a0005, 0x0000001a, 0x6d6f6547, 0x79727465, 0x74696d53, 0x66762868, 0x66763b33, + 0x66763b33, 0x31663b33, 0x0000003b, 0x00030005, 0x00000016, 0x0000004e, 0x00030005, 0x00000017, + 0x00000056, 0x00030005, 0x00000018, 0x0000004c, 0x00050005, 0x00000019, 0x67756f72, 0x73656e68, + 0x00000073, 0x00080005, 0x0000001f, 0x73657266, 0x536c656e, 0x696c6863, 0x66286b63, 0x66763b31, + 0x00003b33, 0x00050005, 0x0000001d, 0x54736f63, 0x61746568, 0x00000000, 0x00030005, 0x0000001e, + 0x00003046, 0x000a0005, 0x00000028, 0x6c707061, 0x726f4e79, 0x4d6c616d, 0x76287061, 0x763b3366, + 0x763b3366, 0x763b3366, 0x003b3266, 0x00030005, 0x00000024, 0x0000004e, 0x00050005, 0x00000025, + 0x5370616d, 0x6c706d61, 0x00000065, 0x00050005, 0x00000026, 0x6c726f77, 0x736f5064, 0x00000000, + 0x00030005, 0x00000027, 0x00007675, 0x000c0005, 0x00000032, 0x64616873, 0x67694c65, 0x76287468, + 0x763b3366, 0x763b3366, 0x663b3366, 0x31663b31, 0x3366763b, 0x3366763b, 0x0000003b, 0x00030005, + 0x0000002b, 0x0000004e, 0x00030005, 0x0000002c, 0x00000056, 0x00040005, 0x0000002d, 0x65626c61, + 0x00006f64, 0x00050005, 0x0000002e, 0x6174656d, 0x63696c6c, 0x00000000, 0x00050005, 0x0000002f, + 0x67756f72, 0x73656e68, 0x00000073, 0x00030005, 0x00000030, 0x0000004c, 0x00050005, 0x00000031, + 0x69646172, 0x65636e61, 0x00000000, 0x00080005, 0x00000037, 0x706d6173, 0x6853656c, 0x776f6461, + 0x28464350, 0x3b336676, 0x003b3166, 0x00050005, 0x00000035, 0x6c726f77, 0x736f5064, 0x00000000, + 0x00050005, 0x00000036, 0x77656976, 0x74706544, 0x00000068, 0x00060005, 0x0000003b, 0x73756c63, + 0x49726574, 0x7865646e, 0x00000028, 0x00030005, 0x0000003d, 0x00000061, 0x00030005, 0x00000043, + 0x00003261, 0x00040005, 0x00000047, 0x746f644e, 0x00000048, 0x00040005, 0x0000004d, 0x6f6e6564, + 0x0000006d, 0x00030005, 0x00000061, 0x00000072, 0x00030005, 0x00000064, 0x0000006b, 0x00040005, + 0x00000079, 0x61726170, 0x0000006d, 0x00040005, 0x0000007a, 0x61726170, 0x0000006d, 0x00040005, + 0x00000081, 0x61726170, 0x0000006d, 0x00040005, 0x00000082, 0x61726170, 0x0000006d, 0x00040005, + 0x00000095, 0x4e70616d, 0x00000000, 0x00030005, 0x000000b7, 0x00317064, 0x00030005, 0x000000ba, + 0x00327064, 0x00040005, 0x000000bd, 0x31767564, 0x00000000, 0x00040005, 0x000000c0, 0x32767564, + 0x00000000, 0x00030005, 0x000000d6, 0x00000054, 0x00030005, 0x000000e5, 0x00000042, 0x00040005, + 0x0000011a, 0x746f644e, 0x0000004c, 0x00040005, 0x0000011f, 0x66666964, 0x00657375, 0x00030005, + 0x00000127, 0x00000048, 0x00030005, 0x0000012c, 0x00003046, 0x00030005, 0x00000133, 0x0046444e, + 0x00040005, 0x00000134, 0x61726170, 0x0000006d, 0x00040005, 0x00000136, 0x61726170, 0x0000006d, + 0x00040005, 0x00000138, 0x61726170, 0x0000006d, 0x00030005, 0x0000013b, 0x00000047, 0x00040005, + 0x0000013c, 0x61726170, 0x0000006d, 0x00040005, 0x0000013e, 0x61726170, 0x0000006d, 0x00040005, + 0x00000140, 0x61726170, 0x0000006d, 0x00040005, 0x00000142, 0x61726170, 0x0000006d, 0x00030005, + 0x00000145, 0x00000046, 0x00040005, 0x0000014a, 0x61726170, 0x0000006d, 0x00040005, 0x0000014b, + 0x61726170, 0x0000006d, 0x00050005, 0x0000014e, 0x63657073, 0x72616c75, 0x00000000, 0x00030005, + 0x00000161, 0x0000446b, 0x00050005, 0x00000179, 0x64616853, 0x7246776f, 0x00656d61, 0x00050006, + 0x00000179, 0x00000000, 0x6867696c, 0x00505674, 0x00050006, 0x00000179, 0x00000001, 0x696c7073, + 0x00007374, 0x00050006, 0x00000179, 0x00000002, 0x73616962, 0x00000000, 0x00040005, 0x0000017b, + 0x64616873, 0x0000776f, 0x00040005, 0x00000195, 0x63736163, 0x00656461, 0x00050005, 0x000001a5, + 0x6867696c, 0x696c4374, 0x00000070, 0x00030005, 0x000001b0, 0x0063646e, 0x00030005, 0x000001b9, + 0x00007675, 0x00040005, 0x000001bf, 0x74706564, 0x00000068, 0x00040005, 0x000001e3, 0x73616962, + 0x00000000, 0x00040005, 0x000001e6, 0x65786574, 0x0000006c, 0x00050005, 0x000001ea, 0x64616873, + 0x614d776f, 0x00000070, 0x00030005, 0x000001f4, 0x006d7573, 0x00030005, 0x000001f5, 0x00000079, + 0x00030005, 0x000001fe, 0x00000078, 0x00040005, 0x00000206, 0x736f6c63, 0x00747365, 0x00040005, + 0x0000022d, 0x656c6974, 0x00005873, 0x00040005, 0x0000022e, 0x6d617246, 0x00000065, 0x00040006, + 0x0000022e, 0x00000000, 0x0070766d, 0x00050006, 0x0000022e, 0x00000001, 0x65646f6d, 0x0000006c, + 0x00050006, 0x0000022e, 0x00000002, 0x77656976, 0x00000000, 0x00060006, 0x0000022e, 0x00000003, + 0x6867696c, 0x72694474, 0x00000000, 0x00060006, 0x0000022e, 0x00000004, 0x6867696c, 0x6c6f4374, + 0x0000726f, 0x00050006, 0x0000022e, 0x00000005, 0x746e6974, 0x00000000, 0x00060006, 0x0000022e, + 0x00000006, 0x656d6163, 0x6f506172, 0x00000073, 0x00050006, 0x0000022e, 0x00000007, 0x69626d61, + 0x00746e65, 0x00060006, 0x0000022e, 0x00000008, 0x64697267, 0x6f666e49, 0x00000000, 0x00060006, + 0x0000022e, 0x00000009, 0x70696c63, 0x6f666e49, 0x00000000, 0x00030005, 0x00000230, 0x006f6275, + 0x00040005, 0x00000236, 0x656c6974, 0x00005973, 0x00040005, 0x0000023b, 0x63696c73, 0x00007365, + 0x00040005, 0x00000240, 0x7261656e, 0x0000005a, 0x00040005, 0x00000244, 0x5a726166, 0x00000000, + 0x00040005, 0x0000024a, 0x65726373, 0x00576e65, 0x00040005, 0x0000024e, 0x65726373, 0x00486e65, + 0x00030005, 0x00000252, 0x00007874, 0x00060005, 0x00000254, 0x465f6c67, 0x43676172, 0x64726f6f, + 0x00000000, 0x00030005, 0x00000262, 0x00007974, 0x00040005, 0x0000026f, 0x74706564, 0x00000068, + 0x00050005, 0x00000271, 0x65695676, 0x736f5077, 0x00000000, 0x00030005, 0x00000277, 0x00007a73, + 0x00040005, 0x00000293, 0x65736162, 0x00000000, 0x00060005, 0x00000297, 0x65626c61, 0x61536f64, + 0x656c706d, 0x00000072, 0x00030005, 0x0000029a, 0x00565576, 0x00040005, 0x0000029d, 0x6e695476, + 0x00000074, 0x00040005, 0x000002a0, 0x65626c61, 0x00006f64, 0x00050005, 0x000002a3, 0x6174656d, + 0x63696c6c, 0x00000000, 0x00050005, 0x000002a8, 0x67756f72, 0x73656e68, 0x00000073, 0x00030005, + 0x000002ad, 0x0000004e, 0x00040005, 0x000002ae, 0x726f4e76, 0x006c616d, 0x00040005, 0x000002b1, + 0x6d61536e, 0x00656c70, 0x00060005, 0x000002b2, 0x6d726f6e, 0x61536c61, 0x656c706d, 0x00000072, + 0x00050005, 0x000002be, 0x726f5776, 0x6f50646c, 0x00000073, 0x00040005, 0x000002bf, 0x61726170, + 0x0000006d, 0x00040005, 0x000002c1, 0x61726170, 0x0000006d, 0x00040005, 0x000002c3, 0x61726170, + 0x0000006d, 0x00040005, 0x000002c5, 0x61726170, 0x0000006d, 0x00030005, 0x000002c8, 0x00000056, + 0x00050005, 0x000002c9, 0x6d614376, 0x50617265, 0x0000736f, 0x00030005, 0x000002ce, 0x00006f4c, + 0x00050005, 0x000002d0, 0x64616873, 0x6956776f, 0x00000073, 0x00040005, 0x000002d5, 0x61726170, + 0x0000006d, 0x00040005, 0x000002d7, 0x61726170, 0x0000006d, 0x00040005, 0x000002e5, 0x61726170, + 0x0000006d, 0x00040005, 0x000002e7, 0x61726170, 0x0000006d, 0x00040005, 0x000002e9, 0x61726170, + 0x0000006d, 0x00040005, 0x000002eb, 0x61726170, 0x0000006d, 0x00040005, 0x000002ed, 0x61726170, + 0x0000006d, 0x00040005, 0x000002ef, 0x61726170, 0x0000006d, 0x00040005, 0x000002f0, 0x61726170, + 0x0000006d, 0x00030005, 0x000002fa, 0x00646963, 0x00040005, 0x000002fe, 0x72746e65, 0x00000079, + 0x00060005, 0x00000300, 0x73756c43, 0x54726574, 0x656c6261, 0x00000000, 0x00060006, 0x00000300, + 0x00000000, 0x73756c63, 0x73726574, 0x00000000, 0x00030005, 0x00000302, 0x00000000, 0x00040005, + 0x00000307, 0x7366666f, 0x00007465, 0x00040005, 0x0000030a, 0x6e756f63, 0x00000074, 0x00030005, + 0x0000030f, 0x00000069, 0x00030005, 0x00000318, 0x0000696c, 0x00070005, 0x0000031a, 0x6867694c, + 0x646e4974, 0x75427865, 0x72656666, 0x00000000, 0x00070006, 0x0000031a, 0x00000000, 0x6867696c, + 0x646e4974, 0x73656369, 0x00000000, 0x00030005, 0x0000031c, 0x00000000, 0x00040005, 0x00000323, + 0x6867694c, 0x00443374, 0x00060006, 0x00000323, 0x00000000, 0x52736f70, 0x75696461, 0x00000073, + 0x00050006, 0x00000323, 0x00000001, 0x6f6c6f63, 0x00000072, 0x00030005, 0x00000325, 0x0074674c, + 0x00040005, 0x00000326, 0x6867694c, 0x00443374, 0x00060006, 0x00000326, 0x00000000, 0x52736f70, + 0x75696461, 0x00000073, 0x00050006, 0x00000326, 0x00000001, 0x6f6c6f63, 0x00000072, 0x00050005, + 0x00000328, 0x6867694c, 0x66754274, 0x00726566, 0x00050006, 0x00000328, 0x00000000, 0x6867696c, + 0x00007374, 0x00030005, 0x0000032a, 0x00000000, 0x00050005, 0x00000333, 0x69646172, 0x65636e61, + 0x00000000, 0x00030005, 0x00000337, 0x004c6f74, 0x00040005, 0x0000033d, 0x74736964, 0x00000000, + 0x00030005, 0x00000340, 0x0000004c, 0x00040005, 0x00000346, 0x65747461, 0x0000006e, 0x00040005, + 0x00000354, 0x61726170, 0x0000006d, 0x00040005, 0x00000356, 0x61726170, 0x0000006d, 0x00040005, + 0x00000358, 0x61726170, 0x0000006d, 0x00040005, 0x0000035a, 0x61726170, 0x0000006d, 0x00040005, + 0x0000035c, 0x61726170, 0x0000006d, 0x00040005, 0x0000035e, 0x61726170, 0x0000006d, 0x00040005, + 0x00000360, 0x61726170, 0x0000006d, 0x00030005, 0x00000367, 0x00626d61, 0x00040005, 0x0000037a, + 0x6f6c6f63, 0x00000072, 0x00060005, 0x00000383, 0x49766e65, 0x6e65746e, 0x79746973, 0x00000000, + 0x00030005, 0x0000038a, 0x00000052, 0x00030005, 0x0000038f, 0x00646f6c, 0x00040005, 0x00000392, + 0x53766e65, 0x00636570, 0x00050005, 0x00000396, 0x53766e65, 0x6c706d61, 0x00007265, 0x00030005, + 0x0000039e, 0x00003046, 0x00030005, 0x000003a3, 0x00000046, 0x00040005, 0x000003a8, 0x61726170, + 0x0000006d, 0x00040005, 0x000003a9, 0x61726170, 0x0000006d, 0x00030005, 0x000003b1, 0x00727269, + 0x00050005, 0x000003c7, 0x4374756f, 0x726f6c6f, 0x00000000, 0x00040047, 0x00000178, 0x00000006, + 0x00000040, 0x00030047, 0x00000179, 0x00000002, 0x00040048, 0x00000179, 0x00000000, 0x00000005, + 0x00050048, 0x00000179, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000179, 0x00000000, + 0x00000023, 0x00000000, 0x00050048, 0x00000179, 0x00000001, 0x00000023, 0x000000c0, 0x00050048, + 0x00000179, 0x00000002, 0x00000023, 0x000000d0, 0x00040047, 0x0000017b, 0x00000021, 0x00000007, + 0x00040047, 0x0000017b, 0x00000022, 0x00000000, 0x00040047, 0x000001ea, 0x00000021, 0x00000008, + 0x00040047, 0x000001ea, 0x00000022, 0x00000000, 0x00030047, 0x0000022e, 0x00000002, 0x00040048, + 0x0000022e, 0x00000000, 0x00000005, 0x00050048, 0x0000022e, 0x00000000, 0x00000007, 0x00000010, + 0x00050048, 0x0000022e, 0x00000000, 0x00000023, 0x00000000, 0x00040048, 0x0000022e, 0x00000001, + 0x00000005, 0x00050048, 0x0000022e, 0x00000001, 0x00000007, 0x00000010, 0x00050048, 0x0000022e, + 0x00000001, 0x00000023, 0x00000040, 0x00040048, 0x0000022e, 0x00000002, 0x00000005, 0x00050048, + 0x0000022e, 0x00000002, 0x00000007, 0x00000010, 0x00050048, 0x0000022e, 0x00000002, 0x00000023, + 0x00000080, 0x00050048, 0x0000022e, 0x00000003, 0x00000023, 0x000000c0, 0x00050048, 0x0000022e, + 0x00000004, 0x00000023, 0x000000d0, 0x00050048, 0x0000022e, 0x00000005, 0x00000023, 0x000000e0, + 0x00050048, 0x0000022e, 0x00000006, 0x00000023, 0x000000f0, 0x00050048, 0x0000022e, 0x00000007, + 0x00000023, 0x00000100, 0x00050048, 0x0000022e, 0x00000008, 0x00000023, 0x00000110, 0x00050048, + 0x0000022e, 0x00000009, 0x00000023, 0x00000120, 0x00040047, 0x00000230, 0x00000021, 0x00000000, + 0x00040047, 0x00000230, 0x00000022, 0x00000000, 0x00040047, 0x00000254, 0x0000000b, 0x0000000f, + 0x00040047, 0x00000271, 0x0000001e, 0x00000005, 0x00040047, 0x00000297, 0x00000021, 0x00000001, + 0x00040047, 0x00000297, 0x00000022, 0x00000000, 0x00040047, 0x0000029a, 0x0000001e, 0x00000001, + 0x00040047, 0x0000029d, 0x0000001e, 0x00000002, 0x00040047, 0x000002ae, 0x0000001e, 0x00000000, + 0x00040047, 0x000002b2, 0x00000021, 0x00000002, 0x00040047, 0x000002b2, 0x00000022, 0x00000000, + 0x00040047, 0x000002be, 0x0000001e, 0x00000003, 0x00040047, 0x000002c9, 0x0000001e, 0x00000004, + 0x00040047, 0x000002ff, 0x00000006, 0x00000008, 0x00030047, 0x00000300, 0x00000003, 0x00040048, + 0x00000300, 0x00000000, 0x00000018, 0x00050048, 0x00000300, 0x00000000, 0x00000023, 0x00000000, + 0x00030047, 0x00000302, 0x00000018, 0x00040047, 0x00000302, 0x00000021, 0x00000005, 0x00040047, + 0x00000302, 0x00000022, 0x00000000, 0x00040047, 0x00000319, 0x00000006, 0x00000004, 0x00030047, + 0x0000031a, 0x00000003, 0x00040048, 0x0000031a, 0x00000000, 0x00000018, 0x00050048, 0x0000031a, + 0x00000000, 0x00000023, 0x00000000, 0x00030047, 0x0000031c, 0x00000018, 0x00040047, 0x0000031c, + 0x00000021, 0x00000006, 0x00040047, 0x0000031c, 0x00000022, 0x00000000, 0x00050048, 0x00000326, + 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000326, 0x00000001, 0x00000023, 0x00000010, + 0x00040047, 0x00000327, 0x00000006, 0x00000020, 0x00030047, 0x00000328, 0x00000003, 0x00040048, + 0x00000328, 0x00000000, 0x00000018, 0x00050048, 0x00000328, 0x00000000, 0x00000023, 0x00000000, + 0x00030047, 0x0000032a, 0x00000018, 0x00040047, 0x0000032a, 0x00000021, 0x00000004, 0x00040047, + 0x0000032a, 0x00000022, 0x00000000, 0x00040047, 0x00000396, 0x00000021, 0x00000003, 0x00040047, + 0x00000396, 0x00000022, 0x00000000, 0x00040047, 0x000003c7, 0x0000001e, 0x00000000, 0x00020013, + 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, + 0x00000007, 0x00000006, 0x00000003, 0x00040020, 0x00000008, 0x00000007, 0x00000007, 0x00040020, + 0x00000009, 0x00000007, 0x00000006, 0x00060021, 0x0000000a, 0x00000006, 0x00000008, 0x00000008, + 0x00000009, 0x00050021, 0x00000010, 0x00000006, 0x00000009, 0x00000009, 0x00070021, 0x00000015, + 0x00000006, 0x00000008, 0x00000008, 0x00000008, 0x00000009, 0x00050021, 0x0000001c, 0x00000007, + 0x00000009, 0x00000008, 0x00040017, 0x00000021, 0x00000006, 0x00000002, 0x00040020, 0x00000022, + 0x00000007, 0x00000021, 0x00070021, 0x00000023, 0x00000007, 0x00000008, 0x00000008, 0x00000008, + 0x00000022, 0x000a0021, 0x0000002a, 0x00000007, 0x00000008, 0x00000008, 0x00000008, 0x00000009, + 0x00000009, 0x00000008, 0x00000008, 0x00050021, 0x00000034, 0x00000006, 0x00000008, 0x00000009, + 0x00040015, 0x00000039, 0x00000020, 0x00000000, 0x00030021, 0x0000003a, 0x00000039, 0x0004002b, + 0x00000006, 0x00000041, 0x3b03126f, 0x0004002b, 0x00000006, 0x0000004b, 0x00000000, 0x0004002b, + 0x00000006, 0x00000052, 0x3f800000, 0x0004002b, 0x00000006, 0x00000057, 0x40490fdb, 0x0004002b, + 0x00000006, 0x0000005c, 0x38d1b717, 0x0004002b, 0x00000006, 0x00000068, 0x41000000, 0x0004002b, + 0x00000006, 0x0000008f, 0x40a00000, 0x0004002b, 0x00000006, 0x00000097, 0x40000000, 0x00020014, + 0x0000009b, 0x0004002b, 0x00000039, 0x0000009c, 0x00000000, 0x0004002b, 0x00000006, 0x000000a0, + 0x3dcccccd, 0x0004002b, 0x00000039, 0x000000a4, 0x00000001, 0x0004002b, 0x00000039, 0x000000ac, + 0x00000002, 0x0004002b, 0x00000006, 0x000000af, 0x3f59999a, 0x0004002b, 0x00000006, 0x000000cf, + 0x322bcc77, 0x00040018, 0x00000107, 0x00000007, 0x00000003, 0x0004002b, 0x00000006, 0x00000122, + 0x3f000000, 0x0004002b, 0x00000006, 0x00000125, 0x3e800000, 0x0004002b, 0x00000006, 0x0000012d, + 0x3d23d70a, 0x0006002c, 0x00000007, 0x0000012e, 0x0000012d, 0x0000012d, 0x0000012d, 0x0004002b, + 0x00000006, 0x00000154, 0x40800000, 0x0004002b, 0x00000006, 0x0000015b, 0x3a83126f, 0x0006002c, + 0x00000007, 0x00000162, 0x00000052, 0x00000052, 0x00000052, 0x00040017, 0x00000175, 0x00000006, + 0x00000004, 0x00040018, 0x00000176, 0x00000175, 0x00000004, 0x0004002b, 0x00000039, 0x00000177, + 0x00000003, 0x0004001c, 0x00000178, 0x00000176, 0x00000177, 0x0005001e, 0x00000179, 0x00000178, + 0x00000175, 0x00000175, 0x00040020, 0x0000017a, 0x00000002, 0x00000179, 0x0004003b, 0x0000017a, + 0x0000017b, 0x00000002, 0x00040015, 0x0000017c, 0x00000020, 0x00000001, 0x0004002b, 0x0000017c, + 0x0000017d, 0x00000002, 0x00040020, 0x0000017e, 0x00000002, 0x00000006, 0x0004002b, 0x0000017c, + 0x0000018c, 0x00000001, 0x00040020, 0x00000194, 0x00000007, 0x0000017c, 0x0004002b, 0x0000017c, + 0x0000019c, 0x00000000, 0x00040020, 0x000001a4, 0x00000007, 0x00000175, 0x00040020, 0x000001a7, + 0x00000002, 0x00000176, 0x0004002b, 0x00000006, 0x000001b5, 0x358637bd, 0x00090019, 0x000001e7, + 0x00000006, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x0003001b, + 0x000001e8, 0x000001e7, 0x00040020, 0x000001e9, 0x00000000, 0x000001e8, 0x0004003b, 0x000001e9, + 0x000001ea, 0x00000000, 0x00040017, 0x000001ed, 0x0000017c, 0x00000003, 0x00040017, 0x000001ef, + 0x0000017c, 0x00000002, 0x0004002b, 0x0000017c, 0x000001f6, 0xffffffff, 0x0004002b, 0x00000006, + 0x00000225, 0x41100000, 0x000c001e, 0x0000022e, 0x00000176, 0x00000176, 0x00000176, 0x00000175, + 0x00000175, 0x00000175, 0x00000175, 0x00000175, 0x00000175, 0x00000175, 0x00040020, 0x0000022f, + 0x00000002, 0x0000022e, 0x0004003b, 0x0000022f, 0x00000230, 0x00000002, 0x0004002b, 0x0000017c, + 0x00000231, 0x00000008, 0x0004002b, 0x0000017c, 0x00000241, 0x00000009, 0x00040020, 0x00000253, + 0x00000001, 0x00000175, 0x0004003b, 0x00000253, 0x00000254, 0x00000001, 0x00040020, 0x00000255, + 0x00000001, 0x00000006, 0x00040020, 0x00000270, 0x00000001, 0x00000007, 0x0004003b, 0x00000270, + 0x00000271, 0x00000001, 0x00090019, 0x00000294, 0x00000006, 0x00000001, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x00000295, 0x00000294, 0x00040020, 0x00000296, + 0x00000000, 0x00000295, 0x0004003b, 0x00000296, 0x00000297, 0x00000000, 0x00040020, 0x00000299, + 0x00000001, 0x00000021, 0x0004003b, 0x00000299, 0x0000029a, 0x00000001, 0x0004003b, 0x00000253, + 0x0000029d, 0x00000001, 0x0004002b, 0x0000017c, 0x000002a4, 0x00000007, 0x0004002b, 0x0000017c, + 0x000002a9, 0x00000006, 0x0004003b, 0x00000270, 0x000002ae, 0x00000001, 0x0004003b, 0x00000296, + 0x000002b2, 0x00000000, 0x0006002c, 0x00000007, 0x000002b8, 0x00000122, 0x00000122, 0x00000052, + 0x0004003b, 0x00000270, 0x000002be, 0x00000001, 0x0004003b, 0x00000270, 0x000002c9, 0x00000001, + 0x0006002c, 0x00000007, 0x000002cf, 0x0000004b, 0x0000004b, 0x0000004b, 0x0004002b, 0x0000017c, + 0x000002d9, 0x00000003, 0x00040020, 0x000002df, 0x00000002, 0x00000175, 0x0004002b, 0x0000017c, + 0x000002e4, 0x00000004, 0x00040020, 0x000002f9, 0x00000007, 0x00000039, 0x00040017, 0x000002fc, + 0x00000039, 0x00000002, 0x00040020, 0x000002fd, 0x00000007, 0x000002fc, 0x0003001d, 0x000002ff, + 0x000002fc, 0x0003001e, 0x00000300, 0x000002ff, 0x00040020, 0x00000301, 0x00000002, 0x00000300, + 0x0004003b, 0x00000301, 0x00000302, 0x00000002, 0x00040020, 0x00000304, 0x00000002, 0x000002fc, + 0x0004002b, 0x00000039, 0x0000030d, 0x00000020, 0x0003001d, 0x00000319, 0x00000039, 0x0003001e, + 0x0000031a, 0x00000319, 0x00040020, 0x0000031b, 0x00000002, 0x0000031a, 0x0004003b, 0x0000031b, + 0x0000031c, 0x00000002, 0x00040020, 0x00000320, 0x00000002, 0x00000039, 0x0004001e, 0x00000323, + 0x00000175, 0x00000175, 0x00040020, 0x00000324, 0x00000007, 0x00000323, 0x0004001e, 0x00000326, + 0x00000175, 0x00000175, 0x0003001d, 0x00000327, 0x00000326, 0x0003001e, 0x00000328, 0x00000327, + 0x00040020, 0x00000329, 0x00000002, 0x00000328, 0x0004003b, 0x00000329, 0x0000032a, 0x00000002, + 0x00040020, 0x0000032c, 0x00000002, 0x00000326, 0x0004002b, 0x00000006, 0x00000371, 0x3d4ccccd, + 0x0004002b, 0x00000006, 0x00000375, 0x3e6147ae, 0x0004002b, 0x00000006, 0x00000378, 0x3df5c28f, + 0x00090019, 0x00000393, 0x00000006, 0x00000003, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000000, 0x0003001b, 0x00000394, 0x00000393, 0x00040020, 0x00000395, 0x00000000, 0x00000394, + 0x0004003b, 0x00000395, 0x00000396, 0x00000000, 0x0004002b, 0x00000006, 0x000003c2, 0x3eb33333, + 0x00040020, 0x000003c6, 0x00000003, 0x00000175, 0x0004003b, 0x000003c6, 0x000003c7, 0x00000003, + 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b, + 0x000001a4, 0x00000293, 0x00000007, 0x0004003b, 0x00000008, 0x000002a0, 0x00000007, 0x0004003b, + 0x00000009, 0x000002a3, 0x00000007, 0x0004003b, 0x00000009, 0x000002a8, 0x00000007, 0x0004003b, + 0x00000008, 0x000002ad, 0x00000007, 0x0004003b, 0x00000008, 0x000002b1, 0x00000007, 0x0004003b, + 0x00000008, 0x000002bf, 0x00000007, 0x0004003b, 0x00000008, 0x000002c1, 0x00000007, 0x0004003b, + 0x00000008, 0x000002c3, 0x00000007, 0x0004003b, 0x00000022, 0x000002c5, 0x00000007, 0x0004003b, + 0x00000008, 0x000002c8, 0x00000007, 0x0004003b, 0x00000008, 0x000002ce, 0x00000007, 0x0004003b, + 0x00000009, 0x000002d0, 0x00000007, 0x0004003b, 0x00000008, 0x000002d5, 0x00000007, 0x0004003b, + 0x00000009, 0x000002d7, 0x00000007, 0x0004003b, 0x00000008, 0x000002e5, 0x00000007, 0x0004003b, + 0x00000008, 0x000002e7, 0x00000007, 0x0004003b, 0x00000008, 0x000002e9, 0x00000007, 0x0004003b, + 0x00000009, 0x000002eb, 0x00000007, 0x0004003b, 0x00000009, 0x000002ed, 0x00000007, 0x0004003b, + 0x00000008, 0x000002ef, 0x00000007, 0x0004003b, 0x00000008, 0x000002f0, 0x00000007, 0x0004003b, + 0x000002f9, 0x000002fa, 0x00000007, 0x0004003b, 0x000002fd, 0x000002fe, 0x00000007, 0x0004003b, + 0x000002f9, 0x00000307, 0x00000007, 0x0004003b, 0x000002f9, 0x0000030a, 0x00000007, 0x0004003b, + 0x000002f9, 0x0000030f, 0x00000007, 0x0004003b, 0x000002f9, 0x00000318, 0x00000007, 0x0004003b, + 0x00000324, 0x00000325, 0x00000007, 0x0004003b, 0x00000008, 0x00000333, 0x00000007, 0x0004003b, + 0x00000008, 0x00000337, 0x00000007, 0x0004003b, 0x00000009, 0x0000033d, 0x00000007, 0x0004003b, + 0x00000008, 0x00000340, 0x00000007, 0x0004003b, 0x00000009, 0x00000346, 0x00000007, 0x0004003b, + 0x00000008, 0x00000354, 0x00000007, 0x0004003b, 0x00000008, 0x00000356, 0x00000007, 0x0004003b, + 0x00000008, 0x00000358, 0x00000007, 0x0004003b, 0x00000009, 0x0000035a, 0x00000007, 0x0004003b, + 0x00000009, 0x0000035c, 0x00000007, 0x0004003b, 0x00000008, 0x0000035e, 0x00000007, 0x0004003b, + 0x00000008, 0x00000360, 0x00000007, 0x0004003b, 0x00000009, 0x00000367, 0x00000007, 0x0004003b, + 0x00000008, 0x0000037a, 0x00000007, 0x0004003b, 0x00000009, 0x00000383, 0x00000007, 0x0004003b, + 0x00000008, 0x0000038a, 0x00000007, 0x0004003b, 0x00000009, 0x0000038f, 0x00000007, 0x0004003b, + 0x00000008, 0x00000392, 0x00000007, 0x0004003b, 0x00000008, 0x0000039e, 0x00000007, 0x0004003b, + 0x00000008, 0x000003a3, 0x00000007, 0x0004003b, 0x00000009, 0x000003a8, 0x00000007, 0x0004003b, + 0x00000008, 0x000003a9, 0x00000007, 0x0004003b, 0x00000008, 0x000003b1, 0x00000007, 0x0004003d, + 0x00000295, 0x00000298, 0x00000297, 0x0004003d, 0x00000021, 0x0000029b, 0x0000029a, 0x00050057, + 0x00000175, 0x0000029c, 0x00000298, 0x0000029b, 0x0004003d, 0x00000175, 0x0000029e, 0x0000029d, + 0x00050085, 0x00000175, 0x0000029f, 0x0000029c, 0x0000029e, 0x0003003e, 0x00000293, 0x0000029f, + 0x0004003d, 0x00000175, 0x000002a1, 0x00000293, 0x0008004f, 0x00000007, 0x000002a2, 0x000002a1, + 0x000002a1, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x000002a0, 0x000002a2, 0x00060041, + 0x0000017e, 0x000002a5, 0x00000230, 0x000002a4, 0x00000177, 0x0004003d, 0x00000006, 0x000002a6, + 0x000002a5, 0x0008000c, 0x00000006, 0x000002a7, 0x00000001, 0x0000002b, 0x000002a6, 0x0000004b, + 0x00000052, 0x0003003e, 0x000002a3, 0x000002a7, 0x00060041, 0x0000017e, 0x000002aa, 0x00000230, + 0x000002a9, 0x00000177, 0x0004003d, 0x00000006, 0x000002ab, 0x000002aa, 0x0008000c, 0x00000006, + 0x000002ac, 0x00000001, 0x0000002b, 0x000002ab, 0x0000012d, 0x00000052, 0x0003003e, 0x000002a8, + 0x000002ac, 0x0004003d, 0x00000007, 0x000002af, 0x000002ae, 0x0006000c, 0x00000007, 0x000002b0, + 0x00000001, 0x00000045, 0x000002af, 0x0003003e, 0x000002ad, 0x000002b0, 0x0004003d, 0x00000295, + 0x000002b3, 0x000002b2, 0x0004003d, 0x00000021, 0x000002b4, 0x0000029a, 0x00050057, 0x00000175, + 0x000002b5, 0x000002b3, 0x000002b4, 0x0008004f, 0x00000007, 0x000002b6, 0x000002b5, 0x000002b5, + 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x000002b1, 0x000002b6, 0x0004003d, 0x00000007, + 0x000002b7, 0x000002b1, 0x00050083, 0x00000007, 0x000002b9, 0x000002b7, 0x000002b8, 0x0006000c, + 0x00000006, 0x000002ba, 0x00000001, 0x00000042, 0x000002b9, 0x000500ba, 0x0000009b, 0x000002bb, + 0x000002ba, 0x0000012d, 0x000300f7, 0x000002bd, 0x00000000, 0x000400fa, 0x000002bb, 0x000002bc, + 0x000002bd, 0x000200f8, 0x000002bc, 0x0004003d, 0x00000007, 0x000002c0, 0x000002ad, 0x0003003e, + 0x000002bf, 0x000002c0, 0x0004003d, 0x00000007, 0x000002c2, 0x000002b1, 0x0003003e, 0x000002c1, + 0x000002c2, 0x0004003d, 0x00000007, 0x000002c4, 0x000002be, 0x0003003e, 0x000002c3, 0x000002c4, + 0x0004003d, 0x00000021, 0x000002c6, 0x0000029a, 0x0003003e, 0x000002c5, 0x000002c6, 0x00080039, + 0x00000007, 0x000002c7, 0x00000028, 0x000002bf, 0x000002c1, 0x000002c3, 0x000002c5, 0x0003003e, + 0x000002ad, 0x000002c7, 0x000200f9, 0x000002bd, 0x000200f8, 0x000002bd, 0x0004003d, 0x00000007, + 0x000002ca, 0x000002c9, 0x0004003d, 0x00000007, 0x000002cb, 0x000002be, 0x00050083, 0x00000007, + 0x000002cc, 0x000002ca, 0x000002cb, 0x0006000c, 0x00000007, 0x000002cd, 0x00000001, 0x00000045, + 0x000002cc, 0x0003003e, 0x000002c8, 0x000002cd, 0x0003003e, 0x000002ce, 0x000002cf, 0x00050041, + 0x00000255, 0x000002d1, 0x00000271, 0x000000ac, 0x0004003d, 0x00000006, 0x000002d2, 0x000002d1, + 0x0004007f, 0x00000006, 0x000002d3, 0x000002d2, 0x0007000c, 0x00000006, 0x000002d4, 0x00000001, + 0x00000028, 0x000002d3, 0x0000004b, 0x0004003d, 0x00000007, 0x000002d6, 0x000002be, 0x0003003e, + 0x000002d5, 0x000002d6, 0x0003003e, 0x000002d7, 0x000002d4, 0x00060039, 0x00000006, 0x000002d8, + 0x00000037, 0x000002d5, 0x000002d7, 0x0003003e, 0x000002d0, 0x000002d8, 0x00060041, 0x0000017e, + 0x000002da, 0x00000230, 0x000002d9, 0x00000177, 0x0004003d, 0x00000006, 0x000002db, 0x000002da, + 0x000500ba, 0x0000009b, 0x000002dc, 0x000002db, 0x00000122, 0x000300f7, 0x000002de, 0x00000000, + 0x000400fa, 0x000002dc, 0x000002dd, 0x000002de, 0x000200f8, 0x000002dd, 0x00050041, 0x000002df, + 0x000002e0, 0x00000230, 0x000002d9, 0x0004003d, 0x00000175, 0x000002e1, 0x000002e0, 0x0008004f, + 0x00000007, 0x000002e2, 0x000002e1, 0x000002e1, 0x00000000, 0x00000001, 0x00000002, 0x0006000c, + 0x00000007, 0x000002e3, 0x00000001, 0x00000045, 0x000002e2, 0x0004003d, 0x00000007, 0x000002e6, + 0x000002ad, 0x0003003e, 0x000002e5, 0x000002e6, 0x0004003d, 0x00000007, 0x000002e8, 0x000002c8, + 0x0003003e, 0x000002e7, 0x000002e8, 0x0004003d, 0x00000007, 0x000002ea, 0x000002a0, 0x0003003e, + 0x000002e9, 0x000002ea, 0x0004003d, 0x00000006, 0x000002ec, 0x000002a3, 0x0003003e, 0x000002eb, + 0x000002ec, 0x0004003d, 0x00000006, 0x000002ee, 0x000002a8, 0x0003003e, 0x000002ed, 0x000002ee, + 0x0003003e, 0x000002ef, 0x000002e3, 0x00050041, 0x000002df, 0x000002f1, 0x00000230, 0x000002e4, + 0x0004003d, 0x00000175, 0x000002f2, 0x000002f1, 0x0008004f, 0x00000007, 0x000002f3, 0x000002f2, + 0x000002f2, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x000002f0, 0x000002f3, 0x000b0039, + 0x00000007, 0x000002f4, 0x00000032, 0x000002e5, 0x000002e7, 0x000002e9, 0x000002eb, 0x000002ed, + 0x000002ef, 0x000002f0, 0x0004003d, 0x00000006, 0x000002f5, 0x000002d0, 0x0005008e, 0x00000007, + 0x000002f6, 0x000002f4, 0x000002f5, 0x0004003d, 0x00000007, 0x000002f7, 0x000002ce, 0x00050081, + 0x00000007, 0x000002f8, 0x000002f7, 0x000002f6, 0x0003003e, 0x000002ce, 0x000002f8, 0x000200f9, + 0x000002de, 0x000200f8, 0x000002de, 0x00040039, 0x00000039, 0x000002fb, 0x0000003b, 0x0003003e, + 0x000002fa, 0x000002fb, 0x0004003d, 0x00000039, 0x00000303, 0x000002fa, 0x00060041, 0x00000304, + 0x00000305, 0x00000302, 0x0000019c, 0x00000303, 0x0004003d, 0x000002fc, 0x00000306, 0x00000305, + 0x0003003e, 0x000002fe, 0x00000306, 0x00050041, 0x000002f9, 0x00000308, 0x000002fe, 0x0000009c, + 0x0004003d, 0x00000039, 0x00000309, 0x00000308, 0x0003003e, 0x00000307, 0x00000309, 0x00050041, + 0x000002f9, 0x0000030b, 0x000002fe, 0x000000a4, 0x0004003d, 0x00000039, 0x0000030c, 0x0000030b, + 0x0007000c, 0x00000039, 0x0000030e, 0x00000001, 0x00000026, 0x0000030c, 0x0000030d, 0x0003003e, + 0x0000030a, 0x0000030e, 0x0003003e, 0x0000030f, 0x0000009c, 0x000200f9, 0x00000310, 0x000200f8, + 0x00000310, 0x000400f6, 0x00000312, 0x00000313, 0x00000000, 0x000200f9, 0x00000314, 0x000200f8, + 0x00000314, 0x0004003d, 0x00000039, 0x00000315, 0x0000030f, 0x0004003d, 0x00000039, 0x00000316, + 0x0000030a, 0x000500b0, 0x0000009b, 0x00000317, 0x00000315, 0x00000316, 0x000400fa, 0x00000317, + 0x00000311, 0x00000312, 0x000200f8, 0x00000311, 0x0004003d, 0x00000039, 0x0000031d, 0x00000307, + 0x0004003d, 0x00000039, 0x0000031e, 0x0000030f, 0x00050080, 0x00000039, 0x0000031f, 0x0000031d, + 0x0000031e, 0x00060041, 0x00000320, 0x00000321, 0x0000031c, 0x0000019c, 0x0000031f, 0x0004003d, + 0x00000039, 0x00000322, 0x00000321, 0x0003003e, 0x00000318, 0x00000322, 0x0004003d, 0x00000039, + 0x0000032b, 0x00000318, 0x00060041, 0x0000032c, 0x0000032d, 0x0000032a, 0x0000019c, 0x0000032b, + 0x0004003d, 0x00000326, 0x0000032e, 0x0000032d, 0x00050051, 0x00000175, 0x0000032f, 0x0000032e, + 0x00000000, 0x00050041, 0x000001a4, 0x00000330, 0x00000325, 0x0000019c, 0x0003003e, 0x00000330, + 0x0000032f, 0x00050051, 0x00000175, 0x00000331, 0x0000032e, 0x00000001, 0x00050041, 0x000001a4, + 0x00000332, 0x00000325, 0x0000018c, 0x0003003e, 0x00000332, 0x00000331, 0x00050041, 0x000001a4, + 0x00000334, 0x00000325, 0x0000018c, 0x0004003d, 0x00000175, 0x00000335, 0x00000334, 0x0008004f, + 0x00000007, 0x00000336, 0x00000335, 0x00000335, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, + 0x00000333, 0x00000336, 0x00050041, 0x000001a4, 0x00000338, 0x00000325, 0x0000019c, 0x0004003d, + 0x00000175, 0x00000339, 0x00000338, 0x0008004f, 0x00000007, 0x0000033a, 0x00000339, 0x00000339, + 0x00000000, 0x00000001, 0x00000002, 0x0004003d, 0x00000007, 0x0000033b, 0x000002be, 0x00050083, + 0x00000007, 0x0000033c, 0x0000033a, 0x0000033b, 0x0003003e, 0x00000337, 0x0000033c, 0x0004003d, + 0x00000007, 0x0000033e, 0x00000337, 0x0006000c, 0x00000006, 0x0000033f, 0x00000001, 0x00000042, + 0x0000033e, 0x0003003e, 0x0000033d, 0x0000033f, 0x0004003d, 0x00000007, 0x00000341, 0x00000337, + 0x0004003d, 0x00000006, 0x00000342, 0x0000033d, 0x0007000c, 0x00000006, 0x00000343, 0x00000001, + 0x00000028, 0x00000342, 0x0000005c, 0x00060050, 0x00000007, 0x00000344, 0x00000343, 0x00000343, + 0x00000343, 0x00050088, 0x00000007, 0x00000345, 0x00000341, 0x00000344, 0x0003003e, 0x00000340, + 0x00000345, 0x0004003d, 0x00000006, 0x00000347, 0x0000033d, 0x00060041, 0x00000009, 0x00000348, + 0x00000325, 0x0000019c, 0x00000177, 0x0004003d, 0x00000006, 0x00000349, 0x00000348, 0x0007000c, + 0x00000006, 0x0000034a, 0x00000001, 0x00000028, 0x00000349, 0x0000015b, 0x00050088, 0x00000006, + 0x0000034b, 0x00000347, 0x0000034a, 0x00050083, 0x00000006, 0x0000034c, 0x00000052, 0x0000034b, + 0x0008000c, 0x00000006, 0x0000034d, 0x00000001, 0x0000002b, 0x0000034c, 0x0000004b, 0x00000052, + 0x0003003e, 0x00000346, 0x0000034d, 0x0004003d, 0x00000006, 0x0000034e, 0x00000346, 0x0004003d, + 0x00000006, 0x0000034f, 0x00000346, 0x00050085, 0x00000006, 0x00000350, 0x0000034f, 0x0000034e, + 0x0003003e, 0x00000346, 0x00000350, 0x0004003d, 0x00000006, 0x00000351, 0x00000346, 0x0004003d, + 0x00000007, 0x00000352, 0x00000333, 0x0005008e, 0x00000007, 0x00000353, 0x00000352, 0x00000351, + 0x0003003e, 0x00000333, 0x00000353, 0x0004003d, 0x00000007, 0x00000355, 0x000002ad, 0x0003003e, + 0x00000354, 0x00000355, 0x0004003d, 0x00000007, 0x00000357, 0x000002c8, 0x0003003e, 0x00000356, + 0x00000357, 0x0004003d, 0x00000007, 0x00000359, 0x000002a0, 0x0003003e, 0x00000358, 0x00000359, + 0x0004003d, 0x00000006, 0x0000035b, 0x000002a3, 0x0003003e, 0x0000035a, 0x0000035b, 0x0004003d, + 0x00000006, 0x0000035d, 0x000002a8, 0x0003003e, 0x0000035c, 0x0000035d, 0x0004003d, 0x00000007, + 0x0000035f, 0x00000340, 0x0003003e, 0x0000035e, 0x0000035f, 0x0004003d, 0x00000007, 0x00000361, + 0x00000333, 0x0003003e, 0x00000360, 0x00000361, 0x000b0039, 0x00000007, 0x00000362, 0x00000032, + 0x00000354, 0x00000356, 0x00000358, 0x0000035a, 0x0000035c, 0x0000035e, 0x00000360, 0x0004003d, + 0x00000007, 0x00000363, 0x000002ce, 0x00050081, 0x00000007, 0x00000364, 0x00000363, 0x00000362, + 0x0003003e, 0x000002ce, 0x00000364, 0x000200f9, 0x00000313, 0x000200f8, 0x00000313, 0x0004003d, + 0x00000039, 0x00000365, 0x0000030f, 0x00050080, 0x00000039, 0x00000366, 0x00000365, 0x0000018c, + 0x0003003e, 0x0000030f, 0x00000366, 0x000200f9, 0x00000310, 0x000200f8, 0x00000312, 0x00060041, + 0x0000017e, 0x00000368, 0x00000230, 0x000002a4, 0x0000009c, 0x0004003d, 0x00000006, 0x00000369, + 0x00000368, 0x00060041, 0x0000017e, 0x0000036a, 0x00000230, 0x000002a4, 0x000000a4, 0x0004003d, + 0x00000006, 0x0000036b, 0x0000036a, 0x0007000c, 0x00000006, 0x0000036c, 0x00000001, 0x00000028, + 0x00000369, 0x0000036b, 0x00060041, 0x0000017e, 0x0000036d, 0x00000230, 0x000002a4, 0x000000ac, + 0x0004003d, 0x00000006, 0x0000036e, 0x0000036d, 0x0007000c, 0x00000006, 0x0000036f, 0x00000001, + 0x00000028, 0x0000036c, 0x0000036e, 0x0003003e, 0x00000367, 0x0000036f, 0x0004003d, 0x00000006, + 0x00000370, 0x00000367, 0x000500b8, 0x0000009b, 0x00000372, 0x00000370, 0x00000371, 0x000300f7, + 0x00000374, 0x00000000, 0x000400fa, 0x00000372, 0x00000373, 0x00000376, 0x000200f8, 0x00000373, + 0x0003003e, 0x00000367, 0x00000375, 0x000200f9, 0x00000374, 0x000200f8, 0x00000376, 0x0004003d, + 0x00000006, 0x00000377, 0x00000367, 0x0007000c, 0x00000006, 0x00000379, 0x00000001, 0x00000028, + 0x00000377, 0x00000378, 0x0003003e, 0x00000367, 0x00000379, 0x000200f9, 0x00000374, 0x000200f8, + 0x00000374, 0x0004003d, 0x00000007, 0x0000037b, 0x000002a0, 0x0004003d, 0x00000006, 0x0000037c, + 0x00000367, 0x0005008e, 0x00000007, 0x0000037d, 0x0000037b, 0x0000037c, 0x0004003d, 0x00000006, + 0x0000037e, 0x000002a3, 0x00050083, 0x00000006, 0x0000037f, 0x00000052, 0x0000037e, 0x0005008e, + 0x00000007, 0x00000380, 0x0000037d, 0x0000037f, 0x0004003d, 0x00000007, 0x00000381, 0x000002ce, + 0x00050081, 0x00000007, 0x00000382, 0x00000380, 0x00000381, 0x0003003e, 0x0000037a, 0x00000382, + 0x00060041, 0x0000017e, 0x00000384, 0x00000230, 0x000002e4, 0x00000177, 0x0004003d, 0x00000006, + 0x00000385, 0x00000384, 0x0003003e, 0x00000383, 0x00000385, 0x0004003d, 0x00000006, 0x00000386, + 0x00000383, 0x000500ba, 0x0000009b, 0x00000387, 0x00000386, 0x0000005c, 0x000300f7, 0x00000389, + 0x00000000, 0x000400fa, 0x00000387, 0x00000388, 0x00000389, 0x000200f8, 0x00000388, 0x0004003d, + 0x00000007, 0x0000038b, 0x000002c8, 0x0004007f, 0x00000007, 0x0000038c, 0x0000038b, 0x0004003d, + 0x00000007, 0x0000038d, 0x000002ad, 0x0007000c, 0x00000007, 0x0000038e, 0x00000001, 0x00000047, + 0x0000038c, 0x0000038d, 0x0003003e, 0x0000038a, 0x0000038e, 0x0004003d, 0x00000006, 0x00000390, + 0x000002a8, 0x00050085, 0x00000006, 0x00000391, 0x00000390, 0x0000008f, 0x0003003e, 0x0000038f, + 0x00000391, 0x0004003d, 0x00000394, 0x00000397, 0x00000396, 0x0004003d, 0x00000007, 0x00000398, + 0x0000038a, 0x0004003d, 0x00000006, 0x00000399, 0x0000038f, 0x00070058, 0x00000175, 0x0000039a, + 0x00000397, 0x00000398, 0x00000002, 0x00000399, 0x0008004f, 0x00000007, 0x0000039b, 0x0000039a, + 0x0000039a, 0x00000000, 0x00000001, 0x00000002, 0x0004003d, 0x00000006, 0x0000039c, 0x00000383, + 0x0005008e, 0x00000007, 0x0000039d, 0x0000039b, 0x0000039c, 0x0003003e, 0x00000392, 0x0000039d, + 0x0004003d, 0x00000007, 0x0000039f, 0x000002a0, 0x0004003d, 0x00000006, 0x000003a0, 0x000002a3, + 0x00060050, 0x00000007, 0x000003a1, 0x000003a0, 0x000003a0, 0x000003a0, 0x0008000c, 0x00000007, + 0x000003a2, 0x00000001, 0x0000002e, 0x0000012e, 0x0000039f, 0x000003a1, 0x0003003e, 0x0000039e, + 0x000003a2, 0x0004003d, 0x00000007, 0x000003a4, 0x000002ad, 0x0004003d, 0x00000007, 0x000003a5, + 0x000002c8, 0x00050094, 0x00000006, 0x000003a6, 0x000003a4, 0x000003a5, 0x0007000c, 0x00000006, + 0x000003a7, 0x00000001, 0x00000028, 0x000003a6, 0x0000004b, 0x0003003e, 0x000003a8, 0x000003a7, + 0x0004003d, 0x00000007, 0x000003aa, 0x0000039e, 0x0003003e, 0x000003a9, 0x000003aa, 0x00060039, + 0x00000007, 0x000003ab, 0x0000001f, 0x000003a8, 0x000003a9, 0x0003003e, 0x000003a3, 0x000003ab, + 0x0004003d, 0x00000007, 0x000003ac, 0x00000392, 0x0004003d, 0x00000007, 0x000003ad, 0x000003a3, + 0x00050085, 0x00000007, 0x000003ae, 0x000003ac, 0x000003ad, 0x0004003d, 0x00000007, 0x000003af, + 0x0000037a, 0x00050081, 0x00000007, 0x000003b0, 0x000003af, 0x000003ae, 0x0003003e, 0x0000037a, + 0x000003b0, 0x0004003d, 0x00000394, 0x000003b2, 0x00000396, 0x0004003d, 0x00000007, 0x000003b3, + 0x000002ad, 0x00070058, 0x00000175, 0x000003b4, 0x000003b2, 0x000003b3, 0x00000002, 0x0000008f, + 0x0008004f, 0x00000007, 0x000003b5, 0x000003b4, 0x000003b4, 0x00000000, 0x00000001, 0x00000002, + 0x0004003d, 0x00000006, 0x000003b6, 0x00000383, 0x0005008e, 0x00000007, 0x000003b7, 0x000003b5, + 0x000003b6, 0x0003003e, 0x000003b1, 0x000003b7, 0x0004003d, 0x00000007, 0x000003b8, 0x000002a0, + 0x0004003d, 0x00000007, 0x000003b9, 0x000003b1, 0x00050085, 0x00000007, 0x000003ba, 0x000003b8, + 0x000003b9, 0x0004003d, 0x00000006, 0x000003bb, 0x000002a3, 0x00050083, 0x00000006, 0x000003bc, + 0x00000052, 0x000003bb, 0x0005008e, 0x00000007, 0x000003bd, 0x000003ba, 0x000003bc, 0x0004003d, + 0x00000007, 0x000003be, 0x000003a3, 0x00060050, 0x00000007, 0x000003bf, 0x00000052, 0x00000052, + 0x00000052, 0x00050083, 0x00000007, 0x000003c0, 0x000003bf, 0x000003be, 0x00050085, 0x00000007, + 0x000003c1, 0x000003bd, 0x000003c0, 0x0005008e, 0x00000007, 0x000003c3, 0x000003c1, 0x000003c2, + 0x0004003d, 0x00000007, 0x000003c4, 0x0000037a, 0x00050081, 0x00000007, 0x000003c5, 0x000003c4, + 0x000003c3, 0x0003003e, 0x0000037a, 0x000003c5, 0x000200f9, 0x00000389, 0x000200f8, 0x00000389, + 0x0004003d, 0x00000007, 0x000003c8, 0x0000037a, 0x00050041, 0x00000009, 0x000003c9, 0x00000293, + 0x00000177, 0x0004003d, 0x00000006, 0x000003ca, 0x000003c9, 0x00050051, 0x00000006, 0x000003cb, + 0x000003c8, 0x00000000, 0x00050051, 0x00000006, 0x000003cc, 0x000003c8, 0x00000001, 0x00050051, + 0x00000006, 0x000003cd, 0x000003c8, 0x00000002, 0x00070050, 0x00000175, 0x000003ce, 0x000003cb, + 0x000003cc, 0x000003cd, 0x000003ca, 0x0003003e, 0x000003c7, 0x000003ce, 0x000100fd, 0x00010038, + 0x00050036, 0x00000006, 0x0000000e, 0x00000000, 0x0000000a, 0x00030037, 0x00000008, 0x0000000b, + 0x00030037, 0x00000008, 0x0000000c, 0x00030037, 0x00000009, 0x0000000d, 0x000200f8, 0x0000000f, + 0x0004003b, 0x00000009, 0x0000003d, 0x00000007, 0x0004003b, 0x00000009, 0x00000043, 0x00000007, + 0x0004003b, 0x00000009, 0x00000047, 0x00000007, 0x0004003b, 0x00000009, 0x0000004d, 0x00000007, + 0x0004003d, 0x00000006, 0x0000003e, 0x0000000d, 0x0004003d, 0x00000006, 0x0000003f, 0x0000000d, + 0x00050085, 0x00000006, 0x00000040, 0x0000003e, 0x0000003f, 0x0007000c, 0x00000006, 0x00000042, + 0x00000001, 0x00000028, 0x00000040, 0x00000041, 0x0003003e, 0x0000003d, 0x00000042, 0x0004003d, + 0x00000006, 0x00000044, 0x0000003d, 0x0004003d, 0x00000006, 0x00000045, 0x0000003d, 0x00050085, + 0x00000006, 0x00000046, 0x00000044, 0x00000045, 0x0003003e, 0x00000043, 0x00000046, 0x0004003d, + 0x00000007, 0x00000048, 0x0000000b, 0x0004003d, 0x00000007, 0x00000049, 0x0000000c, 0x00050094, + 0x00000006, 0x0000004a, 0x00000048, 0x00000049, 0x0007000c, 0x00000006, 0x0000004c, 0x00000001, + 0x00000028, 0x0000004a, 0x0000004b, 0x0003003e, 0x00000047, 0x0000004c, 0x0004003d, 0x00000006, + 0x0000004e, 0x00000047, 0x0004003d, 0x00000006, 0x0000004f, 0x00000047, 0x00050085, 0x00000006, + 0x00000050, 0x0000004e, 0x0000004f, 0x0004003d, 0x00000006, 0x00000051, 0x00000043, 0x00050083, + 0x00000006, 0x00000053, 0x00000051, 0x00000052, 0x00050085, 0x00000006, 0x00000054, 0x00000050, + 0x00000053, 0x00050081, 0x00000006, 0x00000055, 0x00000054, 0x00000052, 0x0003003e, 0x0000004d, + 0x00000055, 0x0004003d, 0x00000006, 0x00000056, 0x00000043, 0x0004003d, 0x00000006, 0x00000058, + 0x0000004d, 0x00050085, 0x00000006, 0x00000059, 0x00000057, 0x00000058, 0x0004003d, 0x00000006, + 0x0000005a, 0x0000004d, 0x00050085, 0x00000006, 0x0000005b, 0x00000059, 0x0000005a, 0x0007000c, + 0x00000006, 0x0000005d, 0x00000001, 0x00000028, 0x0000005b, 0x0000005c, 0x00050088, 0x00000006, + 0x0000005e, 0x00000056, 0x0000005d, 0x000200fe, 0x0000005e, 0x00010038, 0x00050036, 0x00000006, + 0x00000013, 0x00000000, 0x00000010, 0x00030037, 0x00000009, 0x00000011, 0x00030037, 0x00000009, + 0x00000012, 0x000200f8, 0x00000014, 0x0004003b, 0x00000009, 0x00000061, 0x00000007, 0x0004003b, + 0x00000009, 0x00000064, 0x00000007, 0x0004003d, 0x00000006, 0x00000062, 0x00000012, 0x00050081, + 0x00000006, 0x00000063, 0x00000062, 0x00000052, 0x0003003e, 0x00000061, 0x00000063, 0x0004003d, + 0x00000006, 0x00000065, 0x00000061, 0x0004003d, 0x00000006, 0x00000066, 0x00000061, 0x00050085, + 0x00000006, 0x00000067, 0x00000065, 0x00000066, 0x00050088, 0x00000006, 0x00000069, 0x00000067, + 0x00000068, 0x0003003e, 0x00000064, 0x00000069, 0x0004003d, 0x00000006, 0x0000006a, 0x00000011, + 0x0004003d, 0x00000006, 0x0000006b, 0x00000011, 0x0004003d, 0x00000006, 0x0000006c, 0x00000064, + 0x00050083, 0x00000006, 0x0000006d, 0x00000052, 0x0000006c, 0x00050085, 0x00000006, 0x0000006e, + 0x0000006b, 0x0000006d, 0x0004003d, 0x00000006, 0x0000006f, 0x00000064, 0x00050081, 0x00000006, + 0x00000070, 0x0000006e, 0x0000006f, 0x0007000c, 0x00000006, 0x00000071, 0x00000001, 0x00000028, + 0x00000070, 0x0000005c, 0x00050088, 0x00000006, 0x00000072, 0x0000006a, 0x00000071, 0x000200fe, + 0x00000072, 0x00010038, 0x00050036, 0x00000006, 0x0000001a, 0x00000000, 0x00000015, 0x00030037, + 0x00000008, 0x00000016, 0x00030037, 0x00000008, 0x00000017, 0x00030037, 0x00000008, 0x00000018, + 0x00030037, 0x00000009, 0x00000019, 0x000200f8, 0x0000001b, 0x0004003b, 0x00000009, 0x00000079, + 0x00000007, 0x0004003b, 0x00000009, 0x0000007a, 0x00000007, 0x0004003b, 0x00000009, 0x00000081, + 0x00000007, 0x0004003b, 0x00000009, 0x00000082, 0x00000007, 0x0004003d, 0x00000007, 0x00000075, + 0x00000016, 0x0004003d, 0x00000007, 0x00000076, 0x00000017, 0x00050094, 0x00000006, 0x00000077, + 0x00000075, 0x00000076, 0x0007000c, 0x00000006, 0x00000078, 0x00000001, 0x00000028, 0x00000077, + 0x0000004b, 0x0003003e, 0x00000079, 0x00000078, 0x0004003d, 0x00000006, 0x0000007b, 0x00000019, + 0x0003003e, 0x0000007a, 0x0000007b, 0x00060039, 0x00000006, 0x0000007c, 0x00000013, 0x00000079, + 0x0000007a, 0x0004003d, 0x00000007, 0x0000007d, 0x00000016, 0x0004003d, 0x00000007, 0x0000007e, + 0x00000018, 0x00050094, 0x00000006, 0x0000007f, 0x0000007d, 0x0000007e, 0x0007000c, 0x00000006, + 0x00000080, 0x00000001, 0x00000028, 0x0000007f, 0x0000004b, 0x0003003e, 0x00000081, 0x00000080, + 0x0004003d, 0x00000006, 0x00000083, 0x00000019, 0x0003003e, 0x00000082, 0x00000083, 0x00060039, + 0x00000006, 0x00000084, 0x00000013, 0x00000081, 0x00000082, 0x00050085, 0x00000006, 0x00000085, + 0x0000007c, 0x00000084, 0x000200fe, 0x00000085, 0x00010038, 0x00050036, 0x00000007, 0x0000001f, + 0x00000000, 0x0000001c, 0x00030037, 0x00000009, 0x0000001d, 0x00030037, 0x00000008, 0x0000001e, + 0x000200f8, 0x00000020, 0x0004003d, 0x00000007, 0x00000088, 0x0000001e, 0x0004003d, 0x00000007, + 0x00000089, 0x0000001e, 0x00060050, 0x00000007, 0x0000008a, 0x00000052, 0x00000052, 0x00000052, + 0x00050083, 0x00000007, 0x0000008b, 0x0000008a, 0x00000089, 0x0004003d, 0x00000006, 0x0000008c, + 0x0000001d, 0x00050083, 0x00000006, 0x0000008d, 0x00000052, 0x0000008c, 0x0008000c, 0x00000006, + 0x0000008e, 0x00000001, 0x0000002b, 0x0000008d, 0x0000004b, 0x00000052, 0x0007000c, 0x00000006, + 0x00000090, 0x00000001, 0x0000001a, 0x0000008e, 0x0000008f, 0x0005008e, 0x00000007, 0x00000091, + 0x0000008b, 0x00000090, 0x00050081, 0x00000007, 0x00000092, 0x00000088, 0x00000091, 0x000200fe, + 0x00000092, 0x00010038, 0x00050036, 0x00000007, 0x00000028, 0x00000000, 0x00000023, 0x00030037, + 0x00000008, 0x00000024, 0x00030037, 0x00000008, 0x00000025, 0x00030037, 0x00000008, 0x00000026, + 0x00030037, 0x00000022, 0x00000027, 0x000200f8, 0x00000029, 0x0004003b, 0x00000008, 0x00000095, + 0x00000007, 0x0004003b, 0x00000008, 0x000000b7, 0x00000007, 0x0004003b, 0x00000008, 0x000000ba, + 0x00000007, 0x0004003b, 0x00000022, 0x000000bd, 0x00000007, 0x0004003b, 0x00000022, 0x000000c0, + 0x00000007, 0x0004003b, 0x00000008, 0x000000d6, 0x00000007, 0x0004003b, 0x00000008, 0x000000e5, + 0x00000007, 0x0004003d, 0x00000007, 0x00000096, 0x00000025, 0x0005008e, 0x00000007, 0x00000098, + 0x00000096, 0x00000097, 0x00060050, 0x00000007, 0x00000099, 0x00000052, 0x00000052, 0x00000052, + 0x00050083, 0x00000007, 0x0000009a, 0x00000098, 0x00000099, 0x0003003e, 0x00000095, 0x0000009a, + 0x00050041, 0x00000009, 0x0000009d, 0x00000095, 0x0000009c, 0x0004003d, 0x00000006, 0x0000009e, + 0x0000009d, 0x0006000c, 0x00000006, 0x0000009f, 0x00000001, 0x00000004, 0x0000009e, 0x000500b8, + 0x0000009b, 0x000000a1, 0x0000009f, 0x000000a0, 0x000300f7, 0x000000a3, 0x00000000, 0x000400fa, + 0x000000a1, 0x000000a2, 0x000000a3, 0x000200f8, 0x000000a2, 0x00050041, 0x00000009, 0x000000a5, + 0x00000095, 0x000000a4, 0x0004003d, 0x00000006, 0x000000a6, 0x000000a5, 0x0006000c, 0x00000006, + 0x000000a7, 0x00000001, 0x00000004, 0x000000a6, 0x000500b8, 0x0000009b, 0x000000a8, 0x000000a7, + 0x000000a0, 0x000200f9, 0x000000a3, 0x000200f8, 0x000000a3, 0x000700f5, 0x0000009b, 0x000000a9, + 0x000000a1, 0x00000029, 0x000000a8, 0x000000a2, 0x000300f7, 0x000000ab, 0x00000000, 0x000400fa, + 0x000000a9, 0x000000aa, 0x000000ab, 0x000200f8, 0x000000aa, 0x00050041, 0x00000009, 0x000000ad, + 0x00000095, 0x000000ac, 0x0004003d, 0x00000006, 0x000000ae, 0x000000ad, 0x000500ba, 0x0000009b, + 0x000000b0, 0x000000ae, 0x000000af, 0x000200f9, 0x000000ab, 0x000200f8, 0x000000ab, 0x000700f5, + 0x0000009b, 0x000000b1, 0x000000a9, 0x000000a3, 0x000000b0, 0x000000aa, 0x000300f7, 0x000000b3, + 0x00000000, 0x000400fa, 0x000000b1, 0x000000b2, 0x000000b3, 0x000200f8, 0x000000b2, 0x0004003d, + 0x00000007, 0x000000b4, 0x00000024, 0x0006000c, 0x00000007, 0x000000b5, 0x00000001, 0x00000045, + 0x000000b4, 0x000200fe, 0x000000b5, 0x000200f8, 0x000000b3, 0x0004003d, 0x00000007, 0x000000b8, + 0x00000026, 0x000400cf, 0x00000007, 0x000000b9, 0x000000b8, 0x0003003e, 0x000000b7, 0x000000b9, + 0x0004003d, 0x00000007, 0x000000bb, 0x00000026, 0x000400d0, 0x00000007, 0x000000bc, 0x000000bb, + 0x0003003e, 0x000000ba, 0x000000bc, 0x0004003d, 0x00000021, 0x000000be, 0x00000027, 0x000400cf, + 0x00000021, 0x000000bf, 0x000000be, 0x0003003e, 0x000000bd, 0x000000bf, 0x0004003d, 0x00000021, + 0x000000c1, 0x00000027, 0x000400d0, 0x00000021, 0x000000c2, 0x000000c1, 0x0003003e, 0x000000c0, + 0x000000c2, 0x00050041, 0x00000009, 0x000000c3, 0x000000bd, 0x0000009c, 0x0004003d, 0x00000006, + 0x000000c4, 0x000000c3, 0x00050041, 0x00000009, 0x000000c5, 0x000000c0, 0x000000a4, 0x0004003d, + 0x00000006, 0x000000c6, 0x000000c5, 0x00050085, 0x00000006, 0x000000c7, 0x000000c4, 0x000000c6, + 0x00050041, 0x00000009, 0x000000c8, 0x000000c0, 0x0000009c, 0x0004003d, 0x00000006, 0x000000c9, + 0x000000c8, 0x00050041, 0x00000009, 0x000000ca, 0x000000bd, 0x000000a4, 0x0004003d, 0x00000006, + 0x000000cb, 0x000000ca, 0x00050085, 0x00000006, 0x000000cc, 0x000000c9, 0x000000cb, 0x00050083, + 0x00000006, 0x000000cd, 0x000000c7, 0x000000cc, 0x0006000c, 0x00000006, 0x000000ce, 0x00000001, + 0x00000004, 0x000000cd, 0x000500b8, 0x0000009b, 0x000000d0, 0x000000ce, 0x000000cf, 0x000300f7, + 0x000000d2, 0x00000000, 0x000400fa, 0x000000d0, 0x000000d1, 0x000000d2, 0x000200f8, 0x000000d1, + 0x0004003d, 0x00000007, 0x000000d3, 0x00000024, 0x0006000c, 0x00000007, 0x000000d4, 0x00000001, + 0x00000045, 0x000000d3, 0x000200fe, 0x000000d4, 0x000200f8, 0x000000d2, 0x0004003d, 0x00000007, + 0x000000d7, 0x000000ba, 0x0004003d, 0x00000007, 0x000000d8, 0x00000024, 0x0007000c, 0x00000007, + 0x000000d9, 0x00000001, 0x00000044, 0x000000d7, 0x000000d8, 0x00050041, 0x00000009, 0x000000da, + 0x000000bd, 0x0000009c, 0x0004003d, 0x00000006, 0x000000db, 0x000000da, 0x0005008e, 0x00000007, + 0x000000dc, 0x000000d9, 0x000000db, 0x0004003d, 0x00000007, 0x000000dd, 0x00000024, 0x0004003d, + 0x00000007, 0x000000de, 0x000000b7, 0x0007000c, 0x00000007, 0x000000df, 0x00000001, 0x00000044, + 0x000000dd, 0x000000de, 0x00050041, 0x00000009, 0x000000e0, 0x000000c0, 0x0000009c, 0x0004003d, + 0x00000006, 0x000000e1, 0x000000e0, 0x0005008e, 0x00000007, 0x000000e2, 0x000000df, 0x000000e1, + 0x00050081, 0x00000007, 0x000000e3, 0x000000dc, 0x000000e2, 0x0006000c, 0x00000007, 0x000000e4, + 0x00000001, 0x00000045, 0x000000e3, 0x0003003e, 0x000000d6, 0x000000e4, 0x0004003d, 0x00000007, + 0x000000e6, 0x000000ba, 0x0004003d, 0x00000007, 0x000000e7, 0x00000024, 0x0007000c, 0x00000007, + 0x000000e8, 0x00000001, 0x00000044, 0x000000e6, 0x000000e7, 0x00050041, 0x00000009, 0x000000e9, + 0x000000bd, 0x000000a4, 0x0004003d, 0x00000006, 0x000000ea, 0x000000e9, 0x0005008e, 0x00000007, + 0x000000eb, 0x000000e8, 0x000000ea, 0x0004003d, 0x00000007, 0x000000ec, 0x00000024, 0x0004003d, + 0x00000007, 0x000000ed, 0x000000b7, 0x0007000c, 0x00000007, 0x000000ee, 0x00000001, 0x00000044, + 0x000000ec, 0x000000ed, 0x00050041, 0x00000009, 0x000000ef, 0x000000c0, 0x000000a4, 0x0004003d, + 0x00000006, 0x000000f0, 0x000000ef, 0x0005008e, 0x00000007, 0x000000f1, 0x000000ee, 0x000000f0, + 0x00050081, 0x00000007, 0x000000f2, 0x000000eb, 0x000000f1, 0x0006000c, 0x00000007, 0x000000f3, + 0x00000001, 0x00000045, 0x000000f2, 0x0003003e, 0x000000e5, 0x000000f3, 0x0004003d, 0x00000007, + 0x000000f4, 0x000000d6, 0x0006000c, 0x00000006, 0x000000f5, 0x00000001, 0x00000042, 0x000000f4, + 0x000500b8, 0x0000009b, 0x000000f6, 0x000000f5, 0x0000005c, 0x000400a8, 0x0000009b, 0x000000f7, + 0x000000f6, 0x000300f7, 0x000000f9, 0x00000000, 0x000400fa, 0x000000f7, 0x000000f8, 0x000000f9, + 0x000200f8, 0x000000f8, 0x0004003d, 0x00000007, 0x000000fa, 0x000000e5, 0x0006000c, 0x00000006, + 0x000000fb, 0x00000001, 0x00000042, 0x000000fa, 0x000500b8, 0x0000009b, 0x000000fc, 0x000000fb, + 0x0000005c, 0x000200f9, 0x000000f9, 0x000200f8, 0x000000f9, 0x000700f5, 0x0000009b, 0x000000fd, + 0x000000f6, 0x000000d2, 0x000000fc, 0x000000f8, 0x000300f7, 0x000000ff, 0x00000000, 0x000400fa, + 0x000000fd, 0x000000fe, 0x000000ff, 0x000200f8, 0x000000fe, 0x0004003d, 0x00000007, 0x00000100, + 0x00000024, 0x0006000c, 0x00000007, 0x00000101, 0x00000001, 0x00000045, 0x00000100, 0x000200fe, + 0x00000101, 0x000200f8, 0x000000ff, 0x0004003d, 0x00000007, 0x00000103, 0x000000d6, 0x0004003d, + 0x00000007, 0x00000104, 0x000000e5, 0x0004003d, 0x00000007, 0x00000105, 0x00000024, 0x0006000c, + 0x00000007, 0x00000106, 0x00000001, 0x00000045, 0x00000105, 0x00050051, 0x00000006, 0x00000108, + 0x00000103, 0x00000000, 0x00050051, 0x00000006, 0x00000109, 0x00000103, 0x00000001, 0x00050051, + 0x00000006, 0x0000010a, 0x00000103, 0x00000002, 0x00050051, 0x00000006, 0x0000010b, 0x00000104, + 0x00000000, 0x00050051, 0x00000006, 0x0000010c, 0x00000104, 0x00000001, 0x00050051, 0x00000006, + 0x0000010d, 0x00000104, 0x00000002, 0x00050051, 0x00000006, 0x0000010e, 0x00000106, 0x00000000, + 0x00050051, 0x00000006, 0x0000010f, 0x00000106, 0x00000001, 0x00050051, 0x00000006, 0x00000110, + 0x00000106, 0x00000002, 0x00060050, 0x00000007, 0x00000111, 0x00000108, 0x00000109, 0x0000010a, + 0x00060050, 0x00000007, 0x00000112, 0x0000010b, 0x0000010c, 0x0000010d, 0x00060050, 0x00000007, + 0x00000113, 0x0000010e, 0x0000010f, 0x00000110, 0x00060050, 0x00000107, 0x00000114, 0x00000111, + 0x00000112, 0x00000113, 0x0004003d, 0x00000007, 0x00000115, 0x00000095, 0x00050091, 0x00000007, + 0x00000116, 0x00000114, 0x00000115, 0x0006000c, 0x00000007, 0x00000117, 0x00000001, 0x00000045, + 0x00000116, 0x000200fe, 0x00000117, 0x00010038, 0x00050036, 0x00000007, 0x00000032, 0x00000000, + 0x0000002a, 0x00030037, 0x00000008, 0x0000002b, 0x00030037, 0x00000008, 0x0000002c, 0x00030037, + 0x00000008, 0x0000002d, 0x00030037, 0x00000009, 0x0000002e, 0x00030037, 0x00000009, 0x0000002f, + 0x00030037, 0x00000008, 0x00000030, 0x00030037, 0x00000008, 0x00000031, 0x000200f8, 0x00000033, + 0x0004003b, 0x00000009, 0x0000011a, 0x00000007, 0x0004003b, 0x00000009, 0x0000011f, 0x00000007, + 0x0004003b, 0x00000008, 0x00000127, 0x00000007, 0x0004003b, 0x00000008, 0x0000012c, 0x00000007, + 0x0004003b, 0x00000009, 0x00000133, 0x00000007, 0x0004003b, 0x00000008, 0x00000134, 0x00000007, + 0x0004003b, 0x00000008, 0x00000136, 0x00000007, 0x0004003b, 0x00000009, 0x00000138, 0x00000007, + 0x0004003b, 0x00000009, 0x0000013b, 0x00000007, 0x0004003b, 0x00000008, 0x0000013c, 0x00000007, + 0x0004003b, 0x00000008, 0x0000013e, 0x00000007, 0x0004003b, 0x00000008, 0x00000140, 0x00000007, + 0x0004003b, 0x00000009, 0x00000142, 0x00000007, 0x0004003b, 0x00000008, 0x00000145, 0x00000007, + 0x0004003b, 0x00000009, 0x0000014a, 0x00000007, 0x0004003b, 0x00000008, 0x0000014b, 0x00000007, + 0x0004003b, 0x00000008, 0x0000014e, 0x00000007, 0x0004003b, 0x00000008, 0x00000161, 0x00000007, + 0x0004003d, 0x00000007, 0x0000011b, 0x0000002b, 0x0004003d, 0x00000007, 0x0000011c, 0x00000030, + 0x00050094, 0x00000006, 0x0000011d, 0x0000011b, 0x0000011c, 0x0007000c, 0x00000006, 0x0000011e, + 0x00000001, 0x00000028, 0x0000011d, 0x0000004b, 0x0003003e, 0x0000011a, 0x0000011e, 0x0004003d, + 0x00000006, 0x00000120, 0x0000011a, 0x0004003d, 0x00000006, 0x00000121, 0x0000011a, 0x00050085, + 0x00000006, 0x00000123, 0x00000121, 0x00000122, 0x00050081, 0x00000006, 0x00000124, 0x00000123, + 0x00000122, 0x0008000c, 0x00000006, 0x00000126, 0x00000001, 0x0000002e, 0x00000120, 0x00000124, + 0x00000125, 0x0003003e, 0x0000011f, 0x00000126, 0x0004003d, 0x00000007, 0x00000128, 0x0000002c, + 0x0004003d, 0x00000007, 0x00000129, 0x00000030, 0x00050081, 0x00000007, 0x0000012a, 0x00000128, + 0x00000129, 0x0006000c, 0x00000007, 0x0000012b, 0x00000001, 0x00000045, 0x0000012a, 0x0003003e, + 0x00000127, 0x0000012b, 0x0004003d, 0x00000007, 0x0000012f, 0x0000002d, 0x0004003d, 0x00000006, + 0x00000130, 0x0000002e, 0x00060050, 0x00000007, 0x00000131, 0x00000130, 0x00000130, 0x00000130, + 0x0008000c, 0x00000007, 0x00000132, 0x00000001, 0x0000002e, 0x0000012e, 0x0000012f, 0x00000131, + 0x0003003e, 0x0000012c, 0x00000132, 0x0004003d, 0x00000007, 0x00000135, 0x0000002b, 0x0003003e, + 0x00000134, 0x00000135, 0x0004003d, 0x00000007, 0x00000137, 0x00000127, 0x0003003e, 0x00000136, + 0x00000137, 0x0004003d, 0x00000006, 0x00000139, 0x0000002f, 0x0003003e, 0x00000138, 0x00000139, + 0x00070039, 0x00000006, 0x0000013a, 0x0000000e, 0x00000134, 0x00000136, 0x00000138, 0x0003003e, + 0x00000133, 0x0000013a, 0x0004003d, 0x00000007, 0x0000013d, 0x0000002b, 0x0003003e, 0x0000013c, + 0x0000013d, 0x0004003d, 0x00000007, 0x0000013f, 0x0000002c, 0x0003003e, 0x0000013e, 0x0000013f, + 0x0004003d, 0x00000007, 0x00000141, 0x00000030, 0x0003003e, 0x00000140, 0x00000141, 0x0004003d, + 0x00000006, 0x00000143, 0x0000002f, 0x0003003e, 0x00000142, 0x00000143, 0x00080039, 0x00000006, + 0x00000144, 0x0000001a, 0x0000013c, 0x0000013e, 0x00000140, 0x00000142, 0x0003003e, 0x0000013b, + 0x00000144, 0x0004003d, 0x00000007, 0x00000146, 0x00000127, 0x0004003d, 0x00000007, 0x00000147, + 0x0000002c, 0x00050094, 0x00000006, 0x00000148, 0x00000146, 0x00000147, 0x0007000c, 0x00000006, + 0x00000149, 0x00000001, 0x00000028, 0x00000148, 0x0000004b, 0x0003003e, 0x0000014a, 0x00000149, + 0x0004003d, 0x00000007, 0x0000014c, 0x0000012c, 0x0003003e, 0x0000014b, 0x0000014c, 0x00060039, + 0x00000007, 0x0000014d, 0x0000001f, 0x0000014a, 0x0000014b, 0x0003003e, 0x00000145, 0x0000014d, + 0x0004003d, 0x00000006, 0x0000014f, 0x00000133, 0x0004003d, 0x00000006, 0x00000150, 0x0000013b, + 0x00050085, 0x00000006, 0x00000151, 0x0000014f, 0x00000150, 0x0004003d, 0x00000007, 0x00000152, + 0x00000145, 0x0005008e, 0x00000007, 0x00000153, 0x00000152, 0x00000151, 0x0004003d, 0x00000007, + 0x00000155, 0x0000002b, 0x0004003d, 0x00000007, 0x00000156, 0x0000002c, 0x00050094, 0x00000006, + 0x00000157, 0x00000155, 0x00000156, 0x0007000c, 0x00000006, 0x00000158, 0x00000001, 0x00000028, + 0x00000157, 0x0000004b, 0x00050085, 0x00000006, 0x00000159, 0x00000154, 0x00000158, 0x0004003d, + 0x00000006, 0x0000015a, 0x0000011a, 0x0007000c, 0x00000006, 0x0000015c, 0x00000001, 0x00000028, + 0x0000015a, 0x0000015b, 0x00050085, 0x00000006, 0x0000015d, 0x00000159, 0x0000015c, 0x0007000c, + 0x00000006, 0x0000015e, 0x00000001, 0x00000028, 0x0000015d, 0x0000015b, 0x00060050, 0x00000007, + 0x0000015f, 0x0000015e, 0x0000015e, 0x0000015e, 0x00050088, 0x00000007, 0x00000160, 0x00000153, + 0x0000015f, 0x0003003e, 0x0000014e, 0x00000160, 0x0004003d, 0x00000007, 0x00000163, 0x00000145, + 0x00050083, 0x00000007, 0x00000164, 0x00000162, 0x00000163, 0x0004003d, 0x00000006, 0x00000165, + 0x0000002e, 0x00050083, 0x00000006, 0x00000166, 0x00000052, 0x00000165, 0x0005008e, 0x00000007, + 0x00000167, 0x00000164, 0x00000166, 0x0003003e, 0x00000161, 0x00000167, 0x0004003d, 0x00000007, + 0x00000168, 0x00000161, 0x0004003d, 0x00000007, 0x00000169, 0x0000002d, 0x00050085, 0x00000007, + 0x0000016a, 0x00000168, 0x00000169, 0x0004003d, 0x00000006, 0x0000016b, 0x0000011f, 0x0005008e, + 0x00000007, 0x0000016c, 0x0000016a, 0x0000016b, 0x0004003d, 0x00000007, 0x0000016d, 0x0000014e, + 0x0004003d, 0x00000006, 0x0000016e, 0x0000011a, 0x0005008e, 0x00000007, 0x0000016f, 0x0000016d, + 0x0000016e, 0x00050081, 0x00000007, 0x00000170, 0x0000016c, 0x0000016f, 0x0004003d, 0x00000007, + 0x00000171, 0x00000031, 0x00050085, 0x00000007, 0x00000172, 0x00000170, 0x00000171, 0x000200fe, + 0x00000172, 0x00010038, 0x00050036, 0x00000006, 0x00000037, 0x00000000, 0x00000034, 0x00030037, + 0x00000008, 0x00000035, 0x00030037, 0x00000009, 0x00000036, 0x000200f8, 0x00000038, 0x0004003b, + 0x00000194, 0x00000195, 0x00000007, 0x0004003b, 0x000001a4, 0x000001a5, 0x00000007, 0x0004003b, + 0x00000008, 0x000001b0, 0x00000007, 0x0004003b, 0x00000022, 0x000001b9, 0x00000007, 0x0004003b, + 0x00000009, 0x000001bf, 0x00000007, 0x0004003b, 0x00000009, 0x000001e3, 0x00000007, 0x0004003b, + 0x00000022, 0x000001e6, 0x00000007, 0x0004003b, 0x00000009, 0x000001f4, 0x00000007, 0x0004003b, + 0x00000194, 0x000001f5, 0x00000007, 0x0004003b, 0x00000194, 0x000001fe, 0x00000007, 0x0004003b, + 0x00000009, 0x00000206, 0x00000007, 0x00060041, 0x0000017e, 0x0000017f, 0x0000017b, 0x0000017d, + 0x000000a4, 0x0004003d, 0x00000006, 0x00000180, 0x0000017f, 0x000500b8, 0x0000009b, 0x00000181, + 0x00000180, 0x00000122, 0x000400a8, 0x0000009b, 0x00000182, 0x00000181, 0x000300f7, 0x00000184, + 0x00000000, 0x000400fa, 0x00000182, 0x00000183, 0x00000184, 0x000200f8, 0x00000183, 0x00060041, + 0x0000017e, 0x00000185, 0x0000017b, 0x0000017d, 0x000000ac, 0x0004003d, 0x00000006, 0x00000186, + 0x00000185, 0x000500b8, 0x0000009b, 0x00000187, 0x00000186, 0x00000122, 0x000200f9, 0x00000184, + 0x000200f8, 0x00000184, 0x000700f5, 0x0000009b, 0x00000188, 0x00000181, 0x00000038, 0x00000187, + 0x00000183, 0x000400a8, 0x0000009b, 0x00000189, 0x00000188, 0x000300f7, 0x0000018b, 0x00000000, + 0x000400fa, 0x00000189, 0x0000018a, 0x0000018b, 0x000200f8, 0x0000018a, 0x00060041, 0x0000017e, + 0x0000018d, 0x0000017b, 0x0000018c, 0x00000177, 0x0004003d, 0x00000006, 0x0000018e, 0x0000018d, + 0x000500b8, 0x0000009b, 0x0000018f, 0x0000018e, 0x0000005c, 0x000200f9, 0x0000018b, 0x000200f8, + 0x0000018b, 0x000700f5, 0x0000009b, 0x00000190, 0x00000188, 0x00000184, 0x0000018f, 0x0000018a, + 0x000300f7, 0x00000192, 0x00000000, 0x000400fa, 0x00000190, 0x00000191, 0x00000192, 0x000200f8, + 0x00000191, 0x000200fe, 0x00000052, 0x000200f8, 0x00000192, 0x0003003e, 0x00000195, 0x0000017d, + 0x0004003d, 0x00000006, 0x00000196, 0x00000036, 0x00060041, 0x0000017e, 0x00000197, 0x0000017b, + 0x0000018c, 0x0000009c, 0x0004003d, 0x00000006, 0x00000198, 0x00000197, 0x000500b8, 0x0000009b, + 0x00000199, 0x00000196, 0x00000198, 0x000300f7, 0x0000019b, 0x00000000, 0x000400fa, 0x00000199, + 0x0000019a, 0x0000019d, 0x000200f8, 0x0000019a, 0x0003003e, 0x00000195, 0x0000019c, 0x000200f9, + 0x0000019b, 0x000200f8, 0x0000019d, 0x0004003d, 0x00000006, 0x0000019e, 0x00000036, 0x00060041, + 0x0000017e, 0x0000019f, 0x0000017b, 0x0000018c, 0x000000a4, 0x0004003d, 0x00000006, 0x000001a0, + 0x0000019f, 0x000500b8, 0x0000009b, 0x000001a1, 0x0000019e, 0x000001a0, 0x000300f7, 0x000001a3, + 0x00000000, 0x000400fa, 0x000001a1, 0x000001a2, 0x000001a3, 0x000200f8, 0x000001a2, 0x0003003e, + 0x00000195, 0x0000018c, 0x000200f9, 0x000001a3, 0x000200f8, 0x000001a3, 0x000200f9, 0x0000019b, + 0x000200f8, 0x0000019b, 0x0004003d, 0x0000017c, 0x000001a6, 0x00000195, 0x00060041, 0x000001a7, + 0x000001a8, 0x0000017b, 0x0000019c, 0x000001a6, 0x0004003d, 0x00000176, 0x000001a9, 0x000001a8, + 0x0004003d, 0x00000007, 0x000001aa, 0x00000035, 0x00050051, 0x00000006, 0x000001ab, 0x000001aa, + 0x00000000, 0x00050051, 0x00000006, 0x000001ac, 0x000001aa, 0x00000001, 0x00050051, 0x00000006, + 0x000001ad, 0x000001aa, 0x00000002, 0x00070050, 0x00000175, 0x000001ae, 0x000001ab, 0x000001ac, + 0x000001ad, 0x00000052, 0x00050091, 0x00000175, 0x000001af, 0x000001a9, 0x000001ae, 0x0003003e, + 0x000001a5, 0x000001af, 0x0004003d, 0x00000175, 0x000001b1, 0x000001a5, 0x0008004f, 0x00000007, + 0x000001b2, 0x000001b1, 0x000001b1, 0x00000000, 0x00000001, 0x00000002, 0x00050041, 0x00000009, + 0x000001b3, 0x000001a5, 0x00000177, 0x0004003d, 0x00000006, 0x000001b4, 0x000001b3, 0x0007000c, + 0x00000006, 0x000001b6, 0x00000001, 0x00000028, 0x000001b4, 0x000001b5, 0x00060050, 0x00000007, + 0x000001b7, 0x000001b6, 0x000001b6, 0x000001b6, 0x00050088, 0x00000007, 0x000001b8, 0x000001b2, + 0x000001b7, 0x0003003e, 0x000001b0, 0x000001b8, 0x0004003d, 0x00000007, 0x000001ba, 0x000001b0, + 0x0007004f, 0x00000021, 0x000001bb, 0x000001ba, 0x000001ba, 0x00000000, 0x00000001, 0x0005008e, + 0x00000021, 0x000001bc, 0x000001bb, 0x00000122, 0x00050050, 0x00000021, 0x000001bd, 0x00000122, + 0x00000122, 0x00050081, 0x00000021, 0x000001be, 0x000001bc, 0x000001bd, 0x0003003e, 0x000001b9, + 0x000001be, 0x00050041, 0x00000009, 0x000001c0, 0x000001b0, 0x000000ac, 0x0004003d, 0x00000006, + 0x000001c1, 0x000001c0, 0x0003003e, 0x000001bf, 0x000001c1, 0x00050041, 0x00000009, 0x000001c2, + 0x000001b9, 0x0000009c, 0x0004003d, 0x00000006, 0x000001c3, 0x000001c2, 0x000500b8, 0x0000009b, + 0x000001c4, 0x000001c3, 0x0000004b, 0x000400a8, 0x0000009b, 0x000001c5, 0x000001c4, 0x000300f7, + 0x000001c7, 0x00000000, 0x000400fa, 0x000001c5, 0x000001c6, 0x000001c7, 0x000200f8, 0x000001c6, + 0x00050041, 0x00000009, 0x000001c8, 0x000001b9, 0x0000009c, 0x0004003d, 0x00000006, 0x000001c9, + 0x000001c8, 0x000500ba, 0x0000009b, 0x000001ca, 0x000001c9, 0x00000052, 0x000200f9, 0x000001c7, + 0x000200f8, 0x000001c7, 0x000700f5, 0x0000009b, 0x000001cb, 0x000001c4, 0x0000019b, 0x000001ca, + 0x000001c6, 0x000400a8, 0x0000009b, 0x000001cc, 0x000001cb, 0x000300f7, 0x000001ce, 0x00000000, + 0x000400fa, 0x000001cc, 0x000001cd, 0x000001ce, 0x000200f8, 0x000001cd, 0x00050041, 0x00000009, + 0x000001cf, 0x000001b9, 0x000000a4, 0x0004003d, 0x00000006, 0x000001d0, 0x000001cf, 0x000500b8, + 0x0000009b, 0x000001d1, 0x000001d0, 0x0000004b, 0x000200f9, 0x000001ce, 0x000200f8, 0x000001ce, + 0x000700f5, 0x0000009b, 0x000001d2, 0x000001cb, 0x000001c7, 0x000001d1, 0x000001cd, 0x000400a8, + 0x0000009b, 0x000001d3, 0x000001d2, 0x000300f7, 0x000001d5, 0x00000000, 0x000400fa, 0x000001d3, + 0x000001d4, 0x000001d5, 0x000200f8, 0x000001d4, 0x00050041, 0x00000009, 0x000001d6, 0x000001b9, + 0x000000a4, 0x0004003d, 0x00000006, 0x000001d7, 0x000001d6, 0x000500ba, 0x0000009b, 0x000001d8, + 0x000001d7, 0x00000052, 0x000200f9, 0x000001d5, 0x000200f8, 0x000001d5, 0x000700f5, 0x0000009b, + 0x000001d9, 0x000001d2, 0x000001ce, 0x000001d8, 0x000001d4, 0x0004003d, 0x00000006, 0x000001da, + 0x000001bf, 0x000500b8, 0x0000009b, 0x000001db, 0x000001da, 0x0000004b, 0x000500a6, 0x0000009b, + 0x000001dc, 0x000001d9, 0x000001db, 0x0004003d, 0x00000006, 0x000001dd, 0x000001bf, 0x000500ba, + 0x0000009b, 0x000001de, 0x000001dd, 0x00000052, 0x000500a6, 0x0000009b, 0x000001df, 0x000001dc, + 0x000001de, 0x000300f7, 0x000001e1, 0x00000000, 0x000400fa, 0x000001df, 0x000001e0, 0x000001e1, + 0x000200f8, 0x000001e0, 0x000200fe, 0x00000052, 0x000200f8, 0x000001e1, 0x00060041, 0x0000017e, + 0x000001e4, 0x0000017b, 0x0000017d, 0x0000009c, 0x0004003d, 0x00000006, 0x000001e5, 0x000001e4, + 0x0003003e, 0x000001e3, 0x000001e5, 0x0004003d, 0x000001e8, 0x000001eb, 0x000001ea, 0x00040064, + 0x000001e7, 0x000001ec, 0x000001eb, 0x00050067, 0x000001ed, 0x000001ee, 0x000001ec, 0x0000019c, + 0x0007004f, 0x000001ef, 0x000001f0, 0x000001ee, 0x000001ee, 0x00000000, 0x00000001, 0x0004006f, + 0x00000021, 0x000001f1, 0x000001f0, 0x00050050, 0x00000021, 0x000001f2, 0x00000052, 0x00000052, + 0x00050088, 0x00000021, 0x000001f3, 0x000001f2, 0x000001f1, 0x0003003e, 0x000001e6, 0x000001f3, + 0x0003003e, 0x000001f4, 0x0000004b, 0x0003003e, 0x000001f5, 0x000001f6, 0x000200f9, 0x000001f7, + 0x000200f8, 0x000001f7, 0x000400f6, 0x000001f9, 0x000001fa, 0x00000000, 0x000200f9, 0x000001fb, + 0x000200f8, 0x000001fb, 0x0004003d, 0x0000017c, 0x000001fc, 0x000001f5, 0x000500b3, 0x0000009b, + 0x000001fd, 0x000001fc, 0x0000018c, 0x000400fa, 0x000001fd, 0x000001f8, 0x000001f9, 0x000200f8, + 0x000001f8, 0x0003003e, 0x000001fe, 0x000001f6, 0x000200f9, 0x000001ff, 0x000200f8, 0x000001ff, + 0x000400f6, 0x00000201, 0x00000202, 0x00000000, 0x000200f9, 0x00000203, 0x000200f8, 0x00000203, + 0x0004003d, 0x0000017c, 0x00000204, 0x000001fe, 0x000500b3, 0x0000009b, 0x00000205, 0x00000204, + 0x0000018c, 0x000400fa, 0x00000205, 0x00000200, 0x00000201, 0x000200f8, 0x00000200, 0x0004003d, + 0x000001e8, 0x00000207, 0x000001ea, 0x0004003d, 0x00000021, 0x00000208, 0x000001b9, 0x0004003d, + 0x0000017c, 0x00000209, 0x000001fe, 0x0004006f, 0x00000006, 0x0000020a, 0x00000209, 0x0004003d, + 0x0000017c, 0x0000020b, 0x000001f5, 0x0004006f, 0x00000006, 0x0000020c, 0x0000020b, 0x00050050, + 0x00000021, 0x0000020d, 0x0000020a, 0x0000020c, 0x0004003d, 0x00000021, 0x0000020e, 0x000001e6, + 0x00050085, 0x00000021, 0x0000020f, 0x0000020d, 0x0000020e, 0x00050081, 0x00000021, 0x00000210, + 0x00000208, 0x0000020f, 0x0004003d, 0x0000017c, 0x00000211, 0x00000195, 0x0004006f, 0x00000006, + 0x00000212, 0x00000211, 0x00050051, 0x00000006, 0x00000213, 0x00000210, 0x00000000, 0x00050051, + 0x00000006, 0x00000214, 0x00000210, 0x00000001, 0x00060050, 0x00000007, 0x00000215, 0x00000213, + 0x00000214, 0x00000212, 0x00050057, 0x00000175, 0x00000216, 0x00000207, 0x00000215, 0x00050051, + 0x00000006, 0x00000217, 0x00000216, 0x00000000, 0x0003003e, 0x00000206, 0x00000217, 0x0004003d, + 0x00000006, 0x00000218, 0x000001bf, 0x0004003d, 0x00000006, 0x00000219, 0x000001e3, 0x00050083, + 0x00000006, 0x0000021a, 0x00000218, 0x00000219, 0x0004003d, 0x00000006, 0x0000021b, 0x00000206, + 0x000500ba, 0x0000009b, 0x0000021c, 0x0000021a, 0x0000021b, 0x000600a9, 0x00000006, 0x0000021d, + 0x0000021c, 0x0000004b, 0x00000052, 0x0004003d, 0x00000006, 0x0000021e, 0x000001f4, 0x00050081, + 0x00000006, 0x0000021f, 0x0000021e, 0x0000021d, 0x0003003e, 0x000001f4, 0x0000021f, 0x000200f9, + 0x00000202, 0x000200f8, 0x00000202, 0x0004003d, 0x0000017c, 0x00000220, 0x000001fe, 0x00050080, + 0x0000017c, 0x00000221, 0x00000220, 0x0000018c, 0x0003003e, 0x000001fe, 0x00000221, 0x000200f9, + 0x000001ff, 0x000200f8, 0x00000201, 0x000200f9, 0x000001fa, 0x000200f8, 0x000001fa, 0x0004003d, + 0x0000017c, 0x00000222, 0x000001f5, 0x00050080, 0x0000017c, 0x00000223, 0x00000222, 0x0000018c, + 0x0003003e, 0x000001f5, 0x00000223, 0x000200f9, 0x000001f7, 0x000200f8, 0x000001f9, 0x0004003d, + 0x00000006, 0x00000224, 0x000001f4, 0x00050088, 0x00000006, 0x00000226, 0x00000224, 0x00000225, + 0x00060041, 0x0000017e, 0x00000227, 0x0000017b, 0x0000018c, 0x00000177, 0x0004003d, 0x00000006, + 0x00000228, 0x00000227, 0x0008000c, 0x00000006, 0x00000229, 0x00000001, 0x0000002b, 0x00000228, + 0x0000004b, 0x00000052, 0x0008000c, 0x00000006, 0x0000022a, 0x00000001, 0x0000002e, 0x00000052, + 0x00000226, 0x00000229, 0x000200fe, 0x0000022a, 0x00010038, 0x00050036, 0x00000039, 0x0000003b, + 0x00000000, 0x0000003a, 0x000200f8, 0x0000003c, 0x0004003b, 0x00000194, 0x0000022d, 0x00000007, + 0x0004003b, 0x00000194, 0x00000236, 0x00000007, 0x0004003b, 0x00000194, 0x0000023b, 0x00000007, + 0x0004003b, 0x00000009, 0x00000240, 0x00000007, 0x0004003b, 0x00000009, 0x00000244, 0x00000007, + 0x0004003b, 0x00000009, 0x0000024a, 0x00000007, 0x0004003b, 0x00000009, 0x0000024e, 0x00000007, + 0x0004003b, 0x00000194, 0x00000252, 0x00000007, 0x0004003b, 0x00000194, 0x00000262, 0x00000007, + 0x0004003b, 0x00000009, 0x0000026f, 0x00000007, 0x0004003b, 0x00000194, 0x00000277, 0x00000007, + 0x00060041, 0x0000017e, 0x00000232, 0x00000230, 0x00000231, 0x0000009c, 0x0004003d, 0x00000006, + 0x00000233, 0x00000232, 0x00050081, 0x00000006, 0x00000234, 0x00000233, 0x00000122, 0x0004006e, + 0x0000017c, 0x00000235, 0x00000234, 0x0003003e, 0x0000022d, 0x00000235, 0x00060041, 0x0000017e, + 0x00000237, 0x00000230, 0x00000231, 0x000000a4, 0x0004003d, 0x00000006, 0x00000238, 0x00000237, + 0x00050081, 0x00000006, 0x00000239, 0x00000238, 0x00000122, 0x0004006e, 0x0000017c, 0x0000023a, + 0x00000239, 0x0003003e, 0x00000236, 0x0000023a, 0x00060041, 0x0000017e, 0x0000023c, 0x00000230, + 0x00000231, 0x000000ac, 0x0004003d, 0x00000006, 0x0000023d, 0x0000023c, 0x00050081, 0x00000006, + 0x0000023e, 0x0000023d, 0x00000122, 0x0004006e, 0x0000017c, 0x0000023f, 0x0000023e, 0x0003003e, + 0x0000023b, 0x0000023f, 0x00060041, 0x0000017e, 0x00000242, 0x00000230, 0x00000241, 0x0000009c, + 0x0004003d, 0x00000006, 0x00000243, 0x00000242, 0x0003003e, 0x00000240, 0x00000243, 0x00060041, + 0x0000017e, 0x00000245, 0x00000230, 0x00000241, 0x000000a4, 0x0004003d, 0x00000006, 0x00000246, + 0x00000245, 0x0004003d, 0x00000006, 0x00000247, 0x00000240, 0x00050081, 0x00000006, 0x00000248, + 0x00000247, 0x0000015b, 0x0007000c, 0x00000006, 0x00000249, 0x00000001, 0x00000028, 0x00000246, + 0x00000248, 0x0003003e, 0x00000244, 0x00000249, 0x00060041, 0x0000017e, 0x0000024b, 0x00000230, + 0x00000241, 0x000000ac, 0x0004003d, 0x00000006, 0x0000024c, 0x0000024b, 0x0007000c, 0x00000006, + 0x0000024d, 0x00000001, 0x00000028, 0x0000024c, 0x00000052, 0x0003003e, 0x0000024a, 0x0000024d, + 0x00060041, 0x0000017e, 0x0000024f, 0x00000230, 0x00000241, 0x00000177, 0x0004003d, 0x00000006, + 0x00000250, 0x0000024f, 0x0007000c, 0x00000006, 0x00000251, 0x00000001, 0x00000028, 0x00000250, + 0x00000052, 0x0003003e, 0x0000024e, 0x00000251, 0x00050041, 0x00000255, 0x00000256, 0x00000254, + 0x0000009c, 0x0004003d, 0x00000006, 0x00000257, 0x00000256, 0x0004003d, 0x00000006, 0x00000258, + 0x0000024a, 0x00050088, 0x00000006, 0x00000259, 0x00000257, 0x00000258, 0x0004003d, 0x0000017c, + 0x0000025a, 0x0000022d, 0x0004006f, 0x00000006, 0x0000025b, 0x0000025a, 0x00050085, 0x00000006, + 0x0000025c, 0x00000259, 0x0000025b, 0x0006000c, 0x00000006, 0x0000025d, 0x00000001, 0x00000008, + 0x0000025c, 0x0004006e, 0x0000017c, 0x0000025e, 0x0000025d, 0x0004003d, 0x0000017c, 0x0000025f, + 0x0000022d, 0x00050082, 0x0000017c, 0x00000260, 0x0000025f, 0x0000018c, 0x0008000c, 0x0000017c, + 0x00000261, 0x00000001, 0x0000002d, 0x0000025e, 0x0000019c, 0x00000260, 0x0003003e, 0x00000252, + 0x00000261, 0x00050041, 0x00000255, 0x00000263, 0x00000254, 0x000000a4, 0x0004003d, 0x00000006, + 0x00000264, 0x00000263, 0x0004003d, 0x00000006, 0x00000265, 0x0000024e, 0x00050088, 0x00000006, + 0x00000266, 0x00000264, 0x00000265, 0x0004003d, 0x0000017c, 0x00000267, 0x00000236, 0x0004006f, + 0x00000006, 0x00000268, 0x00000267, 0x00050085, 0x00000006, 0x00000269, 0x00000266, 0x00000268, + 0x0006000c, 0x00000006, 0x0000026a, 0x00000001, 0x00000008, 0x00000269, 0x0004006e, 0x0000017c, + 0x0000026b, 0x0000026a, 0x0004003d, 0x0000017c, 0x0000026c, 0x00000236, 0x00050082, 0x0000017c, + 0x0000026d, 0x0000026c, 0x0000018c, 0x0008000c, 0x0000017c, 0x0000026e, 0x00000001, 0x0000002d, + 0x0000026b, 0x0000019c, 0x0000026d, 0x0003003e, 0x00000262, 0x0000026e, 0x00050041, 0x00000255, + 0x00000272, 0x00000271, 0x000000ac, 0x0004003d, 0x00000006, 0x00000273, 0x00000272, 0x0004007f, + 0x00000006, 0x00000274, 0x00000273, 0x0004003d, 0x00000006, 0x00000275, 0x00000240, 0x0007000c, + 0x00000006, 0x00000276, 0x00000001, 0x00000028, 0x00000274, 0x00000275, 0x0003003e, 0x0000026f, + 0x00000276, 0x0004003d, 0x00000006, 0x00000278, 0x0000026f, 0x0004003d, 0x00000006, 0x00000279, + 0x00000240, 0x00050083, 0x00000006, 0x0000027a, 0x00000278, 0x00000279, 0x0004003d, 0x00000006, + 0x0000027b, 0x00000244, 0x0004003d, 0x00000006, 0x0000027c, 0x00000240, 0x00050083, 0x00000006, + 0x0000027d, 0x0000027b, 0x0000027c, 0x00050088, 0x00000006, 0x0000027e, 0x0000027a, 0x0000027d, + 0x0004003d, 0x0000017c, 0x0000027f, 0x0000023b, 0x0004006f, 0x00000006, 0x00000280, 0x0000027f, + 0x00050085, 0x00000006, 0x00000281, 0x0000027e, 0x00000280, 0x0006000c, 0x00000006, 0x00000282, + 0x00000001, 0x00000008, 0x00000281, 0x0004006e, 0x0000017c, 0x00000283, 0x00000282, 0x0004003d, + 0x0000017c, 0x00000284, 0x0000023b, 0x00050082, 0x0000017c, 0x00000285, 0x00000284, 0x0000018c, + 0x0008000c, 0x0000017c, 0x00000286, 0x00000001, 0x0000002d, 0x00000283, 0x0000019c, 0x00000285, + 0x0003003e, 0x00000277, 0x00000286, 0x0004003d, 0x0000017c, 0x00000287, 0x00000277, 0x0004003d, + 0x0000017c, 0x00000288, 0x00000236, 0x00050084, 0x0000017c, 0x00000289, 0x00000287, 0x00000288, + 0x0004003d, 0x0000017c, 0x0000028a, 0x00000262, 0x00050080, 0x0000017c, 0x0000028b, 0x00000289, + 0x0000028a, 0x0004003d, 0x0000017c, 0x0000028c, 0x0000022d, 0x00050084, 0x0000017c, 0x0000028d, + 0x0000028b, 0x0000028c, 0x0004003d, 0x0000017c, 0x0000028e, 0x00000252, 0x00050080, 0x0000017c, + 0x0000028f, 0x0000028d, 0x0000028e, 0x0004007c, 0x00000039, 0x00000290, 0x0000028f, 0x000200fe, + 0x00000290, 0x00010038, +}; +static const size_t mesh3d_clustered_frag_spv_count = sizeof(mesh3d_clustered_frag_spv)/sizeof(mesh3d_clustered_frag_spv[0]); diff --git a/src/modules/graphics/shaders/mesh3d_clustered_vert_spv.inc b/src/modules/graphics/shaders/mesh3d_clustered_vert_spv.inc new file mode 100644 index 00000000..eb9266a1 --- /dev/null +++ b/src/modules/graphics/shaders/mesh3d_clustered_vert_spv.inc @@ -0,0 +1,102 @@ +static const uint32_t mesh3d_clustered_vert_spv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x0000005b, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x000f000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000019, 0x0000002f, + 0x00000032, 0x00000045, 0x00000047, 0x0000004d, 0x0000004f, 0x00000051, 0x00000056, 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, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x0000000b, + 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x0000000b, 0x00000001, 0x505f6c67, + 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x0000000b, 0x00000002, 0x435f6c67, 0x4470696c, + 0x61747369, 0x0065636e, 0x00070006, 0x0000000b, 0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, + 0x0065636e, 0x00030005, 0x0000000d, 0x00000000, 0x00040005, 0x00000011, 0x6d617246, 0x00000065, + 0x00040006, 0x00000011, 0x00000000, 0x0070766d, 0x00050006, 0x00000011, 0x00000001, 0x65646f6d, + 0x0000006c, 0x00050006, 0x00000011, 0x00000002, 0x77656976, 0x00000000, 0x00060006, 0x00000011, + 0x00000003, 0x6867696c, 0x72694474, 0x00000000, 0x00060006, 0x00000011, 0x00000004, 0x6867696c, + 0x6c6f4374, 0x0000726f, 0x00050006, 0x00000011, 0x00000005, 0x746e6974, 0x00000000, 0x00060006, + 0x00000011, 0x00000006, 0x656d6163, 0x6f506172, 0x00000073, 0x00050006, 0x00000011, 0x00000007, + 0x69626d61, 0x00746e65, 0x00060006, 0x00000011, 0x00000008, 0x64697267, 0x6f666e49, 0x00000000, + 0x00060006, 0x00000011, 0x00000009, 0x70696c63, 0x6f666e49, 0x00000000, 0x00030005, 0x00000013, + 0x006f6275, 0x00040005, 0x00000019, 0x6f506e69, 0x00000073, 0x00040005, 0x00000024, 0x6c726f77, + 0x00000064, 0x00050005, 0x0000002f, 0x726f5776, 0x6f50646c, 0x00000073, 0x00050005, 0x00000032, + 0x65695676, 0x736f5077, 0x00000000, 0x00050005, 0x0000003b, 0x6d726f6e, 0x614d6c61, 0x00000074, + 0x00040005, 0x00000045, 0x726f4e76, 0x006c616d, 0x00050005, 0x00000047, 0x6f4e6e69, 0x6c616d72, + 0x00000000, 0x00030005, 0x0000004d, 0x00565576, 0x00040005, 0x0000004f, 0x56556e69, 0x00000000, + 0x00040005, 0x00000051, 0x6e695476, 0x00000074, 0x00050005, 0x00000056, 0x6d614376, 0x50617265, + 0x0000736f, 0x00030047, 0x0000000b, 0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x0000000b, + 0x00000000, 0x00050048, 0x0000000b, 0x00000001, 0x0000000b, 0x00000001, 0x00050048, 0x0000000b, + 0x00000002, 0x0000000b, 0x00000003, 0x00050048, 0x0000000b, 0x00000003, 0x0000000b, 0x00000004, + 0x00030047, 0x00000011, 0x00000002, 0x00040048, 0x00000011, 0x00000000, 0x00000005, 0x00050048, + 0x00000011, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000011, 0x00000000, 0x00000023, + 0x00000000, 0x00040048, 0x00000011, 0x00000001, 0x00000005, 0x00050048, 0x00000011, 0x00000001, + 0x00000007, 0x00000010, 0x00050048, 0x00000011, 0x00000001, 0x00000023, 0x00000040, 0x00040048, + 0x00000011, 0x00000002, 0x00000005, 0x00050048, 0x00000011, 0x00000002, 0x00000007, 0x00000010, + 0x00050048, 0x00000011, 0x00000002, 0x00000023, 0x00000080, 0x00050048, 0x00000011, 0x00000003, + 0x00000023, 0x000000c0, 0x00050048, 0x00000011, 0x00000004, 0x00000023, 0x000000d0, 0x00050048, + 0x00000011, 0x00000005, 0x00000023, 0x000000e0, 0x00050048, 0x00000011, 0x00000006, 0x00000023, + 0x000000f0, 0x00050048, 0x00000011, 0x00000007, 0x00000023, 0x00000100, 0x00050048, 0x00000011, + 0x00000008, 0x00000023, 0x00000110, 0x00050048, 0x00000011, 0x00000009, 0x00000023, 0x00000120, + 0x00040047, 0x00000013, 0x00000021, 0x00000000, 0x00040047, 0x00000013, 0x00000022, 0x00000000, + 0x00040047, 0x00000019, 0x0000001e, 0x00000000, 0x00040047, 0x0000002f, 0x0000001e, 0x00000003, + 0x00040047, 0x00000032, 0x0000001e, 0x00000005, 0x00040047, 0x00000045, 0x0000001e, 0x00000000, + 0x00040047, 0x00000047, 0x0000001e, 0x00000001, 0x00040047, 0x0000004d, 0x0000001e, 0x00000001, + 0x00040047, 0x0000004f, 0x0000001e, 0x00000002, 0x00040047, 0x00000051, 0x0000001e, 0x00000002, + 0x00040047, 0x00000056, 0x0000001e, 0x00000004, 0x00020013, 0x00000002, 0x00030021, 0x00000003, + 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, + 0x00040015, 0x00000008, 0x00000020, 0x00000000, 0x0004002b, 0x00000008, 0x00000009, 0x00000001, + 0x0004001c, 0x0000000a, 0x00000006, 0x00000009, 0x0006001e, 0x0000000b, 0x00000007, 0x00000006, + 0x0000000a, 0x0000000a, 0x00040020, 0x0000000c, 0x00000003, 0x0000000b, 0x0004003b, 0x0000000c, + 0x0000000d, 0x00000003, 0x00040015, 0x0000000e, 0x00000020, 0x00000001, 0x0004002b, 0x0000000e, + 0x0000000f, 0x00000000, 0x00040018, 0x00000010, 0x00000007, 0x00000004, 0x000c001e, 0x00000011, + 0x00000010, 0x00000010, 0x00000010, 0x00000007, 0x00000007, 0x00000007, 0x00000007, 0x00000007, + 0x00000007, 0x00000007, 0x00040020, 0x00000012, 0x00000002, 0x00000011, 0x0004003b, 0x00000012, + 0x00000013, 0x00000002, 0x00040020, 0x00000014, 0x00000002, 0x00000010, 0x00040017, 0x00000017, + 0x00000006, 0x00000003, 0x00040020, 0x00000018, 0x00000001, 0x00000017, 0x0004003b, 0x00000018, + 0x00000019, 0x00000001, 0x0004002b, 0x00000006, 0x0000001b, 0x3f800000, 0x00040020, 0x00000021, + 0x00000003, 0x00000007, 0x00040020, 0x00000023, 0x00000007, 0x00000007, 0x0004002b, 0x0000000e, + 0x00000025, 0x00000001, 0x00040020, 0x0000002e, 0x00000003, 0x00000017, 0x0004003b, 0x0000002e, + 0x0000002f, 0x00000003, 0x0004003b, 0x0000002e, 0x00000032, 0x00000003, 0x0004002b, 0x0000000e, + 0x00000033, 0x00000002, 0x00040018, 0x00000039, 0x00000017, 0x00000003, 0x00040020, 0x0000003a, + 0x00000007, 0x00000039, 0x0004003b, 0x0000002e, 0x00000045, 0x00000003, 0x0004003b, 0x00000018, + 0x00000047, 0x00000001, 0x00040017, 0x0000004b, 0x00000006, 0x00000002, 0x00040020, 0x0000004c, + 0x00000003, 0x0000004b, 0x0004003b, 0x0000004c, 0x0000004d, 0x00000003, 0x00040020, 0x0000004e, + 0x00000001, 0x0000004b, 0x0004003b, 0x0000004e, 0x0000004f, 0x00000001, 0x0004003b, 0x00000021, + 0x00000051, 0x00000003, 0x0004002b, 0x0000000e, 0x00000052, 0x00000005, 0x00040020, 0x00000053, + 0x00000002, 0x00000007, 0x0004003b, 0x0000002e, 0x00000056, 0x00000003, 0x0004002b, 0x0000000e, + 0x00000057, 0x00000006, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, + 0x00000005, 0x0004003b, 0x00000023, 0x00000024, 0x00000007, 0x0004003b, 0x0000003a, 0x0000003b, + 0x00000007, 0x00050041, 0x00000014, 0x00000015, 0x00000013, 0x0000000f, 0x0004003d, 0x00000010, + 0x00000016, 0x00000015, 0x0004003d, 0x00000017, 0x0000001a, 0x00000019, 0x00050051, 0x00000006, + 0x0000001c, 0x0000001a, 0x00000000, 0x00050051, 0x00000006, 0x0000001d, 0x0000001a, 0x00000001, + 0x00050051, 0x00000006, 0x0000001e, 0x0000001a, 0x00000002, 0x00070050, 0x00000007, 0x0000001f, + 0x0000001c, 0x0000001d, 0x0000001e, 0x0000001b, 0x00050091, 0x00000007, 0x00000020, 0x00000016, + 0x0000001f, 0x00050041, 0x00000021, 0x00000022, 0x0000000d, 0x0000000f, 0x0003003e, 0x00000022, + 0x00000020, 0x00050041, 0x00000014, 0x00000026, 0x00000013, 0x00000025, 0x0004003d, 0x00000010, + 0x00000027, 0x00000026, 0x0004003d, 0x00000017, 0x00000028, 0x00000019, 0x00050051, 0x00000006, + 0x00000029, 0x00000028, 0x00000000, 0x00050051, 0x00000006, 0x0000002a, 0x00000028, 0x00000001, + 0x00050051, 0x00000006, 0x0000002b, 0x00000028, 0x00000002, 0x00070050, 0x00000007, 0x0000002c, + 0x00000029, 0x0000002a, 0x0000002b, 0x0000001b, 0x00050091, 0x00000007, 0x0000002d, 0x00000027, + 0x0000002c, 0x0003003e, 0x00000024, 0x0000002d, 0x0004003d, 0x00000007, 0x00000030, 0x00000024, + 0x0008004f, 0x00000017, 0x00000031, 0x00000030, 0x00000030, 0x00000000, 0x00000001, 0x00000002, + 0x0003003e, 0x0000002f, 0x00000031, 0x00050041, 0x00000014, 0x00000034, 0x00000013, 0x00000033, + 0x0004003d, 0x00000010, 0x00000035, 0x00000034, 0x0004003d, 0x00000007, 0x00000036, 0x00000024, + 0x00050091, 0x00000007, 0x00000037, 0x00000035, 0x00000036, 0x0008004f, 0x00000017, 0x00000038, + 0x00000037, 0x00000037, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x00000032, 0x00000038, + 0x00050041, 0x00000014, 0x0000003c, 0x00000013, 0x00000025, 0x0004003d, 0x00000010, 0x0000003d, + 0x0000003c, 0x00050051, 0x00000007, 0x0000003e, 0x0000003d, 0x00000000, 0x0008004f, 0x00000017, + 0x0000003f, 0x0000003e, 0x0000003e, 0x00000000, 0x00000001, 0x00000002, 0x00050051, 0x00000007, + 0x00000040, 0x0000003d, 0x00000001, 0x0008004f, 0x00000017, 0x00000041, 0x00000040, 0x00000040, + 0x00000000, 0x00000001, 0x00000002, 0x00050051, 0x00000007, 0x00000042, 0x0000003d, 0x00000002, + 0x0008004f, 0x00000017, 0x00000043, 0x00000042, 0x00000042, 0x00000000, 0x00000001, 0x00000002, + 0x00060050, 0x00000039, 0x00000044, 0x0000003f, 0x00000041, 0x00000043, 0x0003003e, 0x0000003b, + 0x00000044, 0x0004003d, 0x00000039, 0x00000046, 0x0000003b, 0x0004003d, 0x00000017, 0x00000048, + 0x00000047, 0x00050091, 0x00000017, 0x00000049, 0x00000046, 0x00000048, 0x0006000c, 0x00000017, + 0x0000004a, 0x00000001, 0x00000045, 0x00000049, 0x0003003e, 0x00000045, 0x0000004a, 0x0004003d, + 0x0000004b, 0x00000050, 0x0000004f, 0x0003003e, 0x0000004d, 0x00000050, 0x00050041, 0x00000053, + 0x00000054, 0x00000013, 0x00000052, 0x0004003d, 0x00000007, 0x00000055, 0x00000054, 0x0003003e, + 0x00000051, 0x00000055, 0x00050041, 0x00000053, 0x00000058, 0x00000013, 0x00000057, 0x0004003d, + 0x00000007, 0x00000059, 0x00000058, 0x0008004f, 0x00000017, 0x0000005a, 0x00000059, 0x00000059, + 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x00000056, 0x0000005a, 0x000100fd, 0x00010038, +}; +static const size_t mesh3d_clustered_vert_spv_count = sizeof(mesh3d_clustered_vert_spv)/sizeof(mesh3d_clustered_vert_spv[0]); diff --git a/src/modules/graphics/shaders/mesh3d_frag_spv.inc b/src/modules/graphics/shaders/mesh3d_frag_spv.inc index d17bf0a7..636012d4 100644 --- a/src/modules/graphics/shaders/mesh3d_frag_spv.inc +++ b/src/modules/graphics/shaders/mesh3d_frag_spv.inc @@ -1,62 +1,668 @@ static const uint32_t mesh3d_frag_spv[] = { - 0x07230203, 0x00010000, 0x0008000b, 0x0000004a, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, - 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, - 0x000b000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000011, 0x00000015, 0x0000001c, - 0x00000020, 0x00000039, 0x00000040, 0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002, - 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00040005, 0x00000009, 0x65736162, - 0x00000000, 0x00040005, 0x0000000d, 0x65626c61, 0x00006f64, 0x00030005, 0x00000011, 0x00565576, - 0x00040005, 0x00000015, 0x6e695476, 0x00000074, 0x00030005, 0x0000001a, 0x0000004e, 0x00040005, - 0x0000001c, 0x726f4e76, 0x006c616d, 0x00030005, 0x0000001f, 0x0000004c, 0x00050005, 0x00000020, - 0x67694c76, 0x69447468, 0x00000072, 0x00040005, 0x00000024, 0x746f646e, 0x0000006c, 0x00040005, - 0x0000002a, 0x66666964, 0x00657375, 0x00040005, 0x00000032, 0x69626d61, 0x00746e65, 0x00030005, - 0x00000034, 0x0074696c, 0x00050005, 0x00000039, 0x67694c76, 0x6f437468, 0x00726f6c, 0x00050005, - 0x00000040, 0x4374756f, 0x726f6c6f, 0x00000000, 0x00040047, 0x0000000d, 0x00000021, 0x00000001, - 0x00040047, 0x0000000d, 0x00000022, 0x00000000, 0x00040047, 0x00000011, 0x0000001e, 0x00000001, - 0x00040047, 0x00000015, 0x0000001e, 0x00000002, 0x00040047, 0x0000001c, 0x0000001e, 0x00000000, - 0x00040047, 0x00000020, 0x0000001e, 0x00000003, 0x00040047, 0x00000039, 0x0000001e, 0x00000004, - 0x00040047, 0x00000040, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, - 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, - 0x00040020, 0x00000008, 0x00000007, 0x00000007, 0x00090019, 0x0000000a, 0x00000006, 0x00000001, - 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x0000000b, 0x0000000a, - 0x00040020, 0x0000000c, 0x00000000, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000000, - 0x00040017, 0x0000000f, 0x00000006, 0x00000002, 0x00040020, 0x00000010, 0x00000001, 0x0000000f, - 0x0004003b, 0x00000010, 0x00000011, 0x00000001, 0x00040020, 0x00000014, 0x00000001, 0x00000007, - 0x0004003b, 0x00000014, 0x00000015, 0x00000001, 0x00040017, 0x00000018, 0x00000006, 0x00000003, - 0x00040020, 0x00000019, 0x00000007, 0x00000018, 0x00040020, 0x0000001b, 0x00000001, 0x00000018, - 0x0004003b, 0x0000001b, 0x0000001c, 0x00000001, 0x0004003b, 0x0000001b, 0x00000020, 0x00000001, - 0x00040020, 0x00000023, 0x00000007, 0x00000006, 0x0004002b, 0x00000006, 0x00000028, 0x00000000, - 0x0004002b, 0x00000006, 0x0000002d, 0x3f000000, 0x0004002b, 0x00000006, 0x00000030, 0x3e800000, - 0x0004002b, 0x00000006, 0x00000033, 0x3e6147ae, 0x0004003b, 0x0000001b, 0x00000039, 0x00000001, - 0x00040020, 0x0000003f, 0x00000003, 0x00000007, 0x0004003b, 0x0000003f, 0x00000040, 0x00000003, - 0x00040015, 0x00000042, 0x00000020, 0x00000000, 0x0004002b, 0x00000042, 0x00000043, 0x00000003, + 0x07230203, 0x00010000, 0x000d000b, 0x00000367, 0x00000000, 0x00020011, 0x00000001, 0x00020011, + 0x00000032, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, + 0x00000000, 0x00000001, 0x000b000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000233, + 0x00000237, 0x00000254, 0x00000264, 0x0000026f, 0x0000035f, 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, 0x00090005, 0x0000000e, 0x74736944, 0x75626972, 0x6e6f6974, 0x28584747, 0x3b336676, + 0x3b336676, 0x003b3166, 0x00030005, 0x0000000b, 0x0000004e, 0x00030005, 0x0000000c, 0x00000048, + 0x00050005, 0x0000000d, 0x67756f72, 0x73656e68, 0x00000073, 0x00090005, 0x00000013, 0x6d6f6547, + 0x79727465, 0x6c686353, 0x476b6369, 0x66285847, 0x31663b31, 0x0000003b, 0x00040005, 0x00000011, + 0x746f644e, 0x00000056, 0x00050005, 0x00000012, 0x67756f72, 0x73656e68, 0x00000073, 0x000a0005, + 0x0000001a, 0x6d6f6547, 0x79727465, 0x74696d53, 0x66762868, 0x66763b33, 0x66763b33, 0x31663b33, + 0x0000003b, 0x00030005, 0x00000016, 0x0000004e, 0x00030005, 0x00000017, 0x00000056, 0x00030005, + 0x00000018, 0x0000004c, 0x00050005, 0x00000019, 0x67756f72, 0x73656e68, 0x00000073, 0x00080005, + 0x0000001f, 0x73657266, 0x536c656e, 0x696c6863, 0x66286b63, 0x66763b31, 0x00003b33, 0x00050005, + 0x0000001d, 0x54736f63, 0x61746568, 0x00000000, 0x00030005, 0x0000001e, 0x00003046, 0x000a0005, + 0x00000028, 0x6c707061, 0x726f4e79, 0x4d6c616d, 0x76287061, 0x763b3366, 0x763b3366, 0x763b3366, + 0x003b3266, 0x00030005, 0x00000024, 0x0000004e, 0x00050005, 0x00000025, 0x5370616d, 0x6c706d61, + 0x00000065, 0x00050005, 0x00000026, 0x6c726f77, 0x736f5064, 0x00000000, 0x00030005, 0x00000027, + 0x00007675, 0x000c0005, 0x00000032, 0x64616873, 0x67694c65, 0x76287468, 0x763b3366, 0x763b3366, + 0x663b3366, 0x31663b31, 0x3366763b, 0x3366763b, 0x0000003b, 0x00030005, 0x0000002b, 0x0000004e, + 0x00030005, 0x0000002c, 0x00000056, 0x00040005, 0x0000002d, 0x65626c61, 0x00006f64, 0x00050005, + 0x0000002e, 0x6174656d, 0x63696c6c, 0x00000000, 0x00050005, 0x0000002f, 0x67756f72, 0x73656e68, + 0x00000073, 0x00030005, 0x00000030, 0x0000004c, 0x00050005, 0x00000031, 0x69646172, 0x65636e61, + 0x00000000, 0x00080005, 0x00000037, 0x706d6173, 0x6853656c, 0x776f6461, 0x28464350, 0x3b336676, + 0x003b3166, 0x00050005, 0x00000035, 0x6c726f77, 0x736f5064, 0x00000000, 0x00050005, 0x00000036, + 0x77656976, 0x74706544, 0x00000068, 0x00030005, 0x00000039, 0x00000061, 0x00030005, 0x0000003f, + 0x00003261, 0x00040005, 0x00000043, 0x746f644e, 0x00000048, 0x00040005, 0x00000049, 0x6f6e6564, + 0x0000006d, 0x00030005, 0x0000005d, 0x00000072, 0x00030005, 0x00000060, 0x0000006b, 0x00040005, + 0x00000075, 0x61726170, 0x0000006d, 0x00040005, 0x00000076, 0x61726170, 0x0000006d, 0x00040005, + 0x0000007d, 0x61726170, 0x0000006d, 0x00040005, 0x0000007e, 0x61726170, 0x0000006d, 0x00040005, + 0x00000091, 0x4e70616d, 0x00000000, 0x00030005, 0x000000b4, 0x00317064, 0x00030005, 0x000000b7, + 0x00327064, 0x00040005, 0x000000ba, 0x31767564, 0x00000000, 0x00040005, 0x000000bd, 0x32767564, + 0x00000000, 0x00030005, 0x000000d3, 0x00000054, 0x00030005, 0x000000e2, 0x00000042, 0x00040005, + 0x00000117, 0x746f644e, 0x0000004c, 0x00040005, 0x0000011c, 0x66666964, 0x00657375, 0x00030005, + 0x00000124, 0x00000048, 0x00030005, 0x00000129, 0x00003046, 0x00030005, 0x00000130, 0x0046444e, + 0x00040005, 0x00000131, 0x61726170, 0x0000006d, 0x00040005, 0x00000133, 0x61726170, 0x0000006d, + 0x00040005, 0x00000135, 0x61726170, 0x0000006d, 0x00030005, 0x00000138, 0x00000047, 0x00040005, + 0x00000139, 0x61726170, 0x0000006d, 0x00040005, 0x0000013b, 0x61726170, 0x0000006d, 0x00040005, + 0x0000013d, 0x61726170, 0x0000006d, 0x00040005, 0x0000013f, 0x61726170, 0x0000006d, 0x00030005, + 0x00000142, 0x00000046, 0x00040005, 0x00000147, 0x61726170, 0x0000006d, 0x00040005, 0x00000148, + 0x61726170, 0x0000006d, 0x00050005, 0x0000014b, 0x63657073, 0x72616c75, 0x00000000, 0x00030005, + 0x0000015e, 0x0000446b, 0x00050005, 0x00000176, 0x64616853, 0x7246776f, 0x00656d61, 0x00050006, + 0x00000176, 0x00000000, 0x6867696c, 0x00505674, 0x00050006, 0x00000176, 0x00000001, 0x696c7073, + 0x00007374, 0x00050006, 0x00000176, 0x00000002, 0x73616962, 0x00000000, 0x00040005, 0x00000178, + 0x64616873, 0x0000776f, 0x00040005, 0x00000192, 0x63736163, 0x00656461, 0x00050005, 0x000001a2, + 0x6867696c, 0x696c4374, 0x00000070, 0x00030005, 0x000001ad, 0x0063646e, 0x00030005, 0x000001b6, + 0x00007675, 0x00040005, 0x000001bc, 0x74706564, 0x00000068, 0x00040005, 0x000001e0, 0x73616962, + 0x00000000, 0x00040005, 0x000001e3, 0x65786574, 0x0000006c, 0x00050005, 0x000001e7, 0x64616873, + 0x614d776f, 0x00000070, 0x00030005, 0x000001f1, 0x006d7573, 0x00030005, 0x000001f2, 0x00000079, + 0x00030005, 0x000001fb, 0x00000078, 0x00040005, 0x00000203, 0x736f6c63, 0x00747365, 0x00030005, + 0x00000221, 0x00736976, 0x00040005, 0x0000022c, 0x65736162, 0x00000000, 0x00060005, 0x00000230, + 0x65626c61, 0x61536f64, 0x656c706d, 0x00000072, 0x00030005, 0x00000233, 0x00565576, 0x00040005, + 0x00000237, 0x6e695476, 0x00000074, 0x00040005, 0x0000023a, 0x65626c61, 0x00006f64, 0x00050005, + 0x0000023d, 0x6174656d, 0x63696c6c, 0x00000000, 0x00040005, 0x0000023e, 0x6867694c, 0x00443374, + 0x00060006, 0x0000023e, 0x00000000, 0x52736f70, 0x75696461, 0x00000073, 0x00050006, 0x0000023e, + 0x00000001, 0x6f6c6f63, 0x00000072, 0x00040005, 0x00000241, 0x6d617246, 0x00000065, 0x00040006, + 0x00000241, 0x00000000, 0x0070766d, 0x00050006, 0x00000241, 0x00000001, 0x65646f6d, 0x0000006c, + 0x00080006, 0x00000241, 0x00000002, 0x6867696c, 0x72694474, 0x65746e49, 0x7469736e, 0x00000079, + 0x00060006, 0x00000241, 0x00000003, 0x6867696c, 0x6c6f4374, 0x0000726f, 0x00050006, 0x00000241, + 0x00000004, 0x746e6974, 0x00000000, 0x00060006, 0x00000241, 0x00000005, 0x656d6163, 0x6f506172, + 0x00000073, 0x00050006, 0x00000241, 0x00000006, 0x69626d61, 0x00746e65, 0x00050006, 0x00000241, + 0x00000007, 0x6867696c, 0x00007374, 0x00030005, 0x00000243, 0x006f6275, 0x00050005, 0x00000248, + 0x67756f72, 0x73656e68, 0x00000073, 0x00040005, 0x0000024d, 0x6e756f63, 0x00000074, 0x00030005, + 0x00000252, 0x0000004e, 0x00040005, 0x00000254, 0x726f4e76, 0x006c616d, 0x00040005, 0x00000257, + 0x6d61536e, 0x00656c70, 0x00060005, 0x00000258, 0x6d726f6e, 0x61536c61, 0x656c706d, 0x00000072, + 0x00050005, 0x00000264, 0x726f5776, 0x6f50646c, 0x00000073, 0x00040005, 0x00000265, 0x61726170, + 0x0000006d, 0x00040005, 0x00000267, 0x61726170, 0x0000006d, 0x00040005, 0x00000269, 0x61726170, + 0x0000006d, 0x00040005, 0x0000026b, 0x61726170, 0x0000006d, 0x00030005, 0x0000026e, 0x00000056, + 0x00050005, 0x0000026f, 0x6d614376, 0x50617265, 0x0000736f, 0x00030005, 0x00000274, 0x00006f4c, + 0x00050005, 0x00000276, 0x77656976, 0x74706544, 0x00000068, 0x00050005, 0x0000027b, 0x64616873, + 0x6956776f, 0x00000073, 0x00040005, 0x0000027c, 0x61726170, 0x0000006d, 0x00040005, 0x0000027e, + 0x61726170, 0x0000006d, 0x00040005, 0x00000287, 0x61726170, 0x0000006d, 0x00040005, 0x00000289, + 0x61726170, 0x0000006d, 0x00040005, 0x0000028b, 0x61726170, 0x0000006d, 0x00040005, 0x0000028d, + 0x61726170, 0x0000006d, 0x00040005, 0x0000028f, 0x61726170, 0x0000006d, 0x00040005, 0x00000291, + 0x61726170, 0x0000006d, 0x00040005, 0x00000292, 0x61726170, 0x0000006d, 0x00030005, 0x0000029b, + 0x00000069, 0x00040005, 0x000002aa, 0x6867694c, 0x00443374, 0x00060006, 0x000002aa, 0x00000000, + 0x52736f70, 0x75696461, 0x00000073, 0x00050006, 0x000002aa, 0x00000001, 0x6f6c6f63, 0x00000072, + 0x00030005, 0x000002ac, 0x0074674c, 0x00050005, 0x000002b6, 0x69646172, 0x65636e61, 0x00000000, + 0x00030005, 0x000002bf, 0x0000004c, 0x00030005, 0x000002d0, 0x004c6f74, 0x00040005, 0x000002d6, + 0x74736964, 0x00000000, 0x00040005, 0x000002de, 0x65747461, 0x0000006e, 0x00040005, 0x000002ec, + 0x61726170, 0x0000006d, 0x00040005, 0x000002ee, 0x61726170, 0x0000006d, 0x00040005, 0x000002f0, + 0x61726170, 0x0000006d, 0x00040005, 0x000002f2, 0x61726170, 0x0000006d, 0x00040005, 0x000002f4, + 0x61726170, 0x0000006d, 0x00040005, 0x000002f6, 0x61726170, 0x0000006d, 0x00040005, 0x000002f8, + 0x61726170, 0x0000006d, 0x00030005, 0x000002ff, 0x00626d61, 0x00040005, 0x00000312, 0x6f6c6f63, + 0x00000072, 0x00060005, 0x0000031b, 0x49766e65, 0x6e65746e, 0x79746973, 0x00000000, 0x00030005, + 0x00000322, 0x00000052, 0x00030005, 0x00000327, 0x00646f6c, 0x00040005, 0x0000032a, 0x53766e65, + 0x00636570, 0x00050005, 0x0000032e, 0x53766e65, 0x6c706d61, 0x00007265, 0x00030005, 0x00000336, + 0x00003046, 0x00030005, 0x0000033b, 0x00000046, 0x00040005, 0x00000340, 0x61726170, 0x0000006d, + 0x00040005, 0x00000341, 0x61726170, 0x0000006d, 0x00030005, 0x00000349, 0x00727269, 0x00050005, + 0x0000035f, 0x4374756f, 0x726f6c6f, 0x00000000, 0x00040047, 0x00000175, 0x00000006, 0x00000040, + 0x00030047, 0x00000176, 0x00000002, 0x00040048, 0x00000176, 0x00000000, 0x00000005, 0x00050048, + 0x00000176, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000176, 0x00000000, 0x00000023, + 0x00000000, 0x00050048, 0x00000176, 0x00000001, 0x00000023, 0x000000c0, 0x00050048, 0x00000176, + 0x00000002, 0x00000023, 0x000000d0, 0x00040047, 0x00000178, 0x00000021, 0x00000004, 0x00040047, + 0x00000178, 0x00000022, 0x00000000, 0x00040047, 0x000001e7, 0x00000021, 0x00000005, 0x00040047, + 0x000001e7, 0x00000022, 0x00000000, 0x00040047, 0x00000230, 0x00000021, 0x00000001, 0x00040047, + 0x00000230, 0x00000022, 0x00000000, 0x00040047, 0x00000233, 0x0000001e, 0x00000001, 0x00040047, + 0x00000237, 0x0000001e, 0x00000002, 0x00050048, 0x0000023e, 0x00000000, 0x00000023, 0x00000000, + 0x00050048, 0x0000023e, 0x00000001, 0x00000023, 0x00000010, 0x00040047, 0x00000240, 0x00000006, + 0x00000020, 0x00030047, 0x00000241, 0x00000002, 0x00040048, 0x00000241, 0x00000000, 0x00000005, + 0x00050048, 0x00000241, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000241, 0x00000000, + 0x00000023, 0x00000000, 0x00040048, 0x00000241, 0x00000001, 0x00000005, 0x00050048, 0x00000241, + 0x00000001, 0x00000007, 0x00000010, 0x00050048, 0x00000241, 0x00000001, 0x00000023, 0x00000040, + 0x00050048, 0x00000241, 0x00000002, 0x00000023, 0x00000080, 0x00050048, 0x00000241, 0x00000003, + 0x00000023, 0x00000090, 0x00050048, 0x00000241, 0x00000004, 0x00000023, 0x000000a0, 0x00050048, + 0x00000241, 0x00000005, 0x00000023, 0x000000b0, 0x00050048, 0x00000241, 0x00000006, 0x00000023, + 0x000000c0, 0x00050048, 0x00000241, 0x00000007, 0x00000023, 0x000000d0, 0x00040047, 0x00000243, + 0x00000021, 0x00000000, 0x00040047, 0x00000243, 0x00000022, 0x00000000, 0x00040047, 0x00000254, + 0x0000001e, 0x00000000, 0x00040047, 0x00000258, 0x00000021, 0x00000002, 0x00040047, 0x00000258, + 0x00000022, 0x00000000, 0x00040047, 0x00000264, 0x0000001e, 0x00000003, 0x00040047, 0x0000026f, + 0x0000001e, 0x00000004, 0x00040047, 0x0000032e, 0x00000021, 0x00000003, 0x00040047, 0x0000032e, + 0x00000022, 0x00000000, 0x00040047, 0x0000035f, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, + 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, + 0x00000006, 0x00000003, 0x00040020, 0x00000008, 0x00000007, 0x00000007, 0x00040020, 0x00000009, + 0x00000007, 0x00000006, 0x00060021, 0x0000000a, 0x00000006, 0x00000008, 0x00000008, 0x00000009, + 0x00050021, 0x00000010, 0x00000006, 0x00000009, 0x00000009, 0x00070021, 0x00000015, 0x00000006, + 0x00000008, 0x00000008, 0x00000008, 0x00000009, 0x00050021, 0x0000001c, 0x00000007, 0x00000009, + 0x00000008, 0x00040017, 0x00000021, 0x00000006, 0x00000002, 0x00040020, 0x00000022, 0x00000007, + 0x00000021, 0x00070021, 0x00000023, 0x00000007, 0x00000008, 0x00000008, 0x00000008, 0x00000022, + 0x000a0021, 0x0000002a, 0x00000007, 0x00000008, 0x00000008, 0x00000008, 0x00000009, 0x00000009, + 0x00000008, 0x00000008, 0x00050021, 0x00000034, 0x00000006, 0x00000008, 0x00000009, 0x0004002b, + 0x00000006, 0x0000003d, 0x3b03126f, 0x0004002b, 0x00000006, 0x00000047, 0x00000000, 0x0004002b, + 0x00000006, 0x0000004e, 0x3f800000, 0x0004002b, 0x00000006, 0x00000053, 0x40490fdb, 0x0004002b, + 0x00000006, 0x00000058, 0x38d1b717, 0x0004002b, 0x00000006, 0x00000064, 0x41000000, 0x0004002b, + 0x00000006, 0x0000008b, 0x40a00000, 0x0004002b, 0x00000006, 0x00000093, 0x40000000, 0x00020014, + 0x00000097, 0x00040015, 0x00000098, 0x00000020, 0x00000000, 0x0004002b, 0x00000098, 0x00000099, + 0x00000000, 0x0004002b, 0x00000006, 0x0000009d, 0x3dcccccd, 0x0004002b, 0x00000098, 0x000000a1, + 0x00000001, 0x0004002b, 0x00000098, 0x000000a9, 0x00000002, 0x0004002b, 0x00000006, 0x000000ac, + 0x3f59999a, 0x0004002b, 0x00000006, 0x000000cc, 0x322bcc77, 0x00040018, 0x00000104, 0x00000007, + 0x00000003, 0x0004002b, 0x00000006, 0x0000011f, 0x3f000000, 0x0004002b, 0x00000006, 0x00000122, + 0x3e800000, 0x0004002b, 0x00000006, 0x0000012a, 0x3d23d70a, 0x0006002c, 0x00000007, 0x0000012b, + 0x0000012a, 0x0000012a, 0x0000012a, 0x0004002b, 0x00000006, 0x00000151, 0x40800000, 0x0004002b, + 0x00000006, 0x00000158, 0x3a83126f, 0x0006002c, 0x00000007, 0x0000015f, 0x0000004e, 0x0000004e, + 0x0000004e, 0x00040017, 0x00000172, 0x00000006, 0x00000004, 0x00040018, 0x00000173, 0x00000172, + 0x00000004, 0x0004002b, 0x00000098, 0x00000174, 0x00000003, 0x0004001c, 0x00000175, 0x00000173, + 0x00000174, 0x0005001e, 0x00000176, 0x00000175, 0x00000172, 0x00000172, 0x00040020, 0x00000177, + 0x00000002, 0x00000176, 0x0004003b, 0x00000177, 0x00000178, 0x00000002, 0x00040015, 0x00000179, + 0x00000020, 0x00000001, 0x0004002b, 0x00000179, 0x0000017a, 0x00000002, 0x00040020, 0x0000017b, + 0x00000002, 0x00000006, 0x0004002b, 0x00000179, 0x00000189, 0x00000001, 0x00040020, 0x00000191, + 0x00000007, 0x00000179, 0x0004002b, 0x00000179, 0x00000199, 0x00000000, 0x00040020, 0x000001a1, + 0x00000007, 0x00000172, 0x00040020, 0x000001a4, 0x00000002, 0x00000173, 0x0004002b, 0x00000006, + 0x000001b2, 0x358637bd, 0x00090019, 0x000001e4, 0x00000006, 0x00000001, 0x00000000, 0x00000001, + 0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x000001e5, 0x000001e4, 0x00040020, 0x000001e6, + 0x00000000, 0x000001e5, 0x0004003b, 0x000001e6, 0x000001e7, 0x00000000, 0x00040017, 0x000001ea, + 0x00000179, 0x00000003, 0x00040017, 0x000001ec, 0x00000179, 0x00000002, 0x0004002b, 0x00000179, + 0x000001f3, 0xffffffff, 0x0004002b, 0x00000006, 0x00000223, 0x41100000, 0x00090019, 0x0000022d, + 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x0003001b, + 0x0000022e, 0x0000022d, 0x00040020, 0x0000022f, 0x00000000, 0x0000022e, 0x0004003b, 0x0000022f, + 0x00000230, 0x00000000, 0x00040020, 0x00000232, 0x00000001, 0x00000021, 0x0004003b, 0x00000232, + 0x00000233, 0x00000001, 0x00040020, 0x00000236, 0x00000001, 0x00000172, 0x0004003b, 0x00000236, + 0x00000237, 0x00000001, 0x0004001e, 0x0000023e, 0x00000172, 0x00000172, 0x0004002b, 0x00000098, + 0x0000023f, 0x00000008, 0x0004001c, 0x00000240, 0x0000023e, 0x0000023f, 0x000a001e, 0x00000241, + 0x00000173, 0x00000173, 0x00000172, 0x00000172, 0x00000172, 0x00000172, 0x00000172, 0x00000240, + 0x00040020, 0x00000242, 0x00000002, 0x00000241, 0x0004003b, 0x00000242, 0x00000243, 0x00000002, + 0x0004002b, 0x00000179, 0x00000244, 0x00000006, 0x0004002b, 0x00000179, 0x00000249, 0x00000005, + 0x00040020, 0x00000253, 0x00000001, 0x00000007, 0x0004003b, 0x00000253, 0x00000254, 0x00000001, + 0x0004003b, 0x0000022f, 0x00000258, 0x00000000, 0x0006002c, 0x00000007, 0x0000025e, 0x0000011f, + 0x0000011f, 0x0000004e, 0x0004003b, 0x00000253, 0x00000264, 0x00000001, 0x0004003b, 0x00000253, + 0x0000026f, 0x00000001, 0x0006002c, 0x00000007, 0x00000275, 0x00000047, 0x00000047, 0x00000047, + 0x00040020, 0x00000281, 0x00000002, 0x00000172, 0x0004002b, 0x00000179, 0x00000286, 0x00000003, + 0x0004002b, 0x00000179, 0x000002a2, 0x00000008, 0x0004001e, 0x000002aa, 0x00000172, 0x00000172, + 0x00040020, 0x000002ab, 0x00000007, 0x000002aa, 0x0004002b, 0x00000179, 0x000002ad, 0x00000007, + 0x00040020, 0x000002af, 0x00000002, 0x0000023e, 0x0004002b, 0x00000006, 0x00000309, 0x3d4ccccd, + 0x0004002b, 0x00000006, 0x0000030d, 0x3e6147ae, 0x0004002b, 0x00000006, 0x00000310, 0x3df5c28f, + 0x00090019, 0x0000032b, 0x00000006, 0x00000003, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0x00000000, 0x0003001b, 0x0000032c, 0x0000032b, 0x00040020, 0x0000032d, 0x00000000, 0x0000032c, + 0x0004003b, 0x0000032d, 0x0000032e, 0x00000000, 0x0004002b, 0x00000006, 0x0000035a, 0x3eb33333, + 0x00040020, 0x0000035e, 0x00000003, 0x00000172, 0x0004003b, 0x0000035e, 0x0000035f, 0x00000003, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b, - 0x00000008, 0x00000009, 0x00000007, 0x0004003b, 0x00000019, 0x0000001a, 0x00000007, 0x0004003b, - 0x00000019, 0x0000001f, 0x00000007, 0x0004003b, 0x00000023, 0x00000024, 0x00000007, 0x0004003b, - 0x00000023, 0x0000002a, 0x00000007, 0x0004003b, 0x00000023, 0x00000032, 0x00000007, 0x0004003b, - 0x00000019, 0x00000034, 0x00000007, 0x0004003d, 0x0000000b, 0x0000000e, 0x0000000d, 0x0004003d, - 0x0000000f, 0x00000012, 0x00000011, 0x00050057, 0x00000007, 0x00000013, 0x0000000e, 0x00000012, - 0x0004003d, 0x00000007, 0x00000016, 0x00000015, 0x00050085, 0x00000007, 0x00000017, 0x00000013, - 0x00000016, 0x0003003e, 0x00000009, 0x00000017, 0x0004003d, 0x00000018, 0x0000001d, 0x0000001c, - 0x0006000c, 0x00000018, 0x0000001e, 0x00000001, 0x00000045, 0x0000001d, 0x0003003e, 0x0000001a, - 0x0000001e, 0x0004003d, 0x00000018, 0x00000021, 0x00000020, 0x0006000c, 0x00000018, 0x00000022, - 0x00000001, 0x00000045, 0x00000021, 0x0003003e, 0x0000001f, 0x00000022, 0x0004003d, 0x00000018, - 0x00000025, 0x0000001a, 0x0004003d, 0x00000018, 0x00000026, 0x0000001f, 0x00050094, 0x00000006, - 0x00000027, 0x00000025, 0x00000026, 0x0007000c, 0x00000006, 0x00000029, 0x00000001, 0x00000028, - 0x00000027, 0x00000028, 0x0003003e, 0x00000024, 0x00000029, 0x0004003d, 0x00000006, 0x0000002b, - 0x00000024, 0x0004003d, 0x00000006, 0x0000002c, 0x00000024, 0x00050085, 0x00000006, 0x0000002e, - 0x0000002c, 0x0000002d, 0x00050081, 0x00000006, 0x0000002f, 0x0000002e, 0x0000002d, 0x0008000c, - 0x00000006, 0x00000031, 0x00000001, 0x0000002e, 0x0000002b, 0x0000002f, 0x00000030, 0x0003003e, - 0x0000002a, 0x00000031, 0x0003003e, 0x00000032, 0x00000033, 0x0004003d, 0x00000007, 0x00000035, - 0x00000009, 0x0008004f, 0x00000018, 0x00000036, 0x00000035, 0x00000035, 0x00000000, 0x00000001, - 0x00000002, 0x0004003d, 0x00000006, 0x00000037, 0x00000032, 0x0004003d, 0x00000006, 0x00000038, - 0x0000002a, 0x0004003d, 0x00000018, 0x0000003a, 0x00000039, 0x0005008e, 0x00000018, 0x0000003b, - 0x0000003a, 0x00000038, 0x00060050, 0x00000018, 0x0000003c, 0x00000037, 0x00000037, 0x00000037, - 0x00050081, 0x00000018, 0x0000003d, 0x0000003c, 0x0000003b, 0x00050085, 0x00000018, 0x0000003e, - 0x00000036, 0x0000003d, 0x0003003e, 0x00000034, 0x0000003e, 0x0004003d, 0x00000018, 0x00000041, - 0x00000034, 0x00050041, 0x00000023, 0x00000044, 0x00000009, 0x00000043, 0x0004003d, 0x00000006, - 0x00000045, 0x00000044, 0x00050051, 0x00000006, 0x00000046, 0x00000041, 0x00000000, 0x00050051, - 0x00000006, 0x00000047, 0x00000041, 0x00000001, 0x00050051, 0x00000006, 0x00000048, 0x00000041, - 0x00000002, 0x00070050, 0x00000007, 0x00000049, 0x00000046, 0x00000047, 0x00000048, 0x00000045, - 0x0003003e, 0x00000040, 0x00000049, 0x000100fd, 0x00010038, + 0x000001a1, 0x0000022c, 0x00000007, 0x0004003b, 0x00000008, 0x0000023a, 0x00000007, 0x0004003b, + 0x00000009, 0x0000023d, 0x00000007, 0x0004003b, 0x00000009, 0x00000248, 0x00000007, 0x0004003b, + 0x00000191, 0x0000024d, 0x00000007, 0x0004003b, 0x00000008, 0x00000252, 0x00000007, 0x0004003b, + 0x00000008, 0x00000257, 0x00000007, 0x0004003b, 0x00000008, 0x00000265, 0x00000007, 0x0004003b, + 0x00000008, 0x00000267, 0x00000007, 0x0004003b, 0x00000008, 0x00000269, 0x00000007, 0x0004003b, + 0x00000022, 0x0000026b, 0x00000007, 0x0004003b, 0x00000008, 0x0000026e, 0x00000007, 0x0004003b, + 0x00000008, 0x00000274, 0x00000007, 0x0004003b, 0x00000009, 0x00000276, 0x00000007, 0x0004003b, + 0x00000009, 0x0000027b, 0x00000007, 0x0004003b, 0x00000008, 0x0000027c, 0x00000007, 0x0004003b, + 0x00000009, 0x0000027e, 0x00000007, 0x0004003b, 0x00000008, 0x00000287, 0x00000007, 0x0004003b, + 0x00000008, 0x00000289, 0x00000007, 0x0004003b, 0x00000008, 0x0000028b, 0x00000007, 0x0004003b, + 0x00000009, 0x0000028d, 0x00000007, 0x0004003b, 0x00000009, 0x0000028f, 0x00000007, 0x0004003b, + 0x00000008, 0x00000291, 0x00000007, 0x0004003b, 0x00000008, 0x00000292, 0x00000007, 0x0004003b, + 0x00000191, 0x0000029b, 0x00000007, 0x0004003b, 0x000002ab, 0x000002ac, 0x00000007, 0x0004003b, + 0x00000008, 0x000002b6, 0x00000007, 0x0004003b, 0x00000008, 0x000002bf, 0x00000007, 0x0004003b, + 0x00000008, 0x000002d0, 0x00000007, 0x0004003b, 0x00000009, 0x000002d6, 0x00000007, 0x0004003b, + 0x00000009, 0x000002de, 0x00000007, 0x0004003b, 0x00000008, 0x000002ec, 0x00000007, 0x0004003b, + 0x00000008, 0x000002ee, 0x00000007, 0x0004003b, 0x00000008, 0x000002f0, 0x00000007, 0x0004003b, + 0x00000009, 0x000002f2, 0x00000007, 0x0004003b, 0x00000009, 0x000002f4, 0x00000007, 0x0004003b, + 0x00000008, 0x000002f6, 0x00000007, 0x0004003b, 0x00000008, 0x000002f8, 0x00000007, 0x0004003b, + 0x00000009, 0x000002ff, 0x00000007, 0x0004003b, 0x00000008, 0x00000312, 0x00000007, 0x0004003b, + 0x00000009, 0x0000031b, 0x00000007, 0x0004003b, 0x00000008, 0x00000322, 0x00000007, 0x0004003b, + 0x00000009, 0x00000327, 0x00000007, 0x0004003b, 0x00000008, 0x0000032a, 0x00000007, 0x0004003b, + 0x00000008, 0x00000336, 0x00000007, 0x0004003b, 0x00000008, 0x0000033b, 0x00000007, 0x0004003b, + 0x00000009, 0x00000340, 0x00000007, 0x0004003b, 0x00000008, 0x00000341, 0x00000007, 0x0004003b, + 0x00000008, 0x00000349, 0x00000007, 0x0004003d, 0x0000022e, 0x00000231, 0x00000230, 0x0004003d, + 0x00000021, 0x00000234, 0x00000233, 0x00050057, 0x00000172, 0x00000235, 0x00000231, 0x00000234, + 0x0004003d, 0x00000172, 0x00000238, 0x00000237, 0x00050085, 0x00000172, 0x00000239, 0x00000235, + 0x00000238, 0x0003003e, 0x0000022c, 0x00000239, 0x0004003d, 0x00000172, 0x0000023b, 0x0000022c, + 0x0008004f, 0x00000007, 0x0000023c, 0x0000023b, 0x0000023b, 0x00000000, 0x00000001, 0x00000002, + 0x0003003e, 0x0000023a, 0x0000023c, 0x00060041, 0x0000017b, 0x00000245, 0x00000243, 0x00000244, + 0x00000174, 0x0004003d, 0x00000006, 0x00000246, 0x00000245, 0x0008000c, 0x00000006, 0x00000247, + 0x00000001, 0x0000002b, 0x00000246, 0x00000047, 0x0000004e, 0x0003003e, 0x0000023d, 0x00000247, + 0x00060041, 0x0000017b, 0x0000024a, 0x00000243, 0x00000249, 0x00000174, 0x0004003d, 0x00000006, + 0x0000024b, 0x0000024a, 0x0008000c, 0x00000006, 0x0000024c, 0x00000001, 0x0000002b, 0x0000024b, + 0x0000012a, 0x0000004e, 0x0003003e, 0x00000248, 0x0000024c, 0x00060041, 0x0000017b, 0x0000024e, + 0x00000243, 0x0000017a, 0x00000174, 0x0004003d, 0x00000006, 0x0000024f, 0x0000024e, 0x00050081, + 0x00000006, 0x00000250, 0x0000024f, 0x0000011f, 0x0004006e, 0x00000179, 0x00000251, 0x00000250, + 0x0003003e, 0x0000024d, 0x00000251, 0x0004003d, 0x00000007, 0x00000255, 0x00000254, 0x0006000c, + 0x00000007, 0x00000256, 0x00000001, 0x00000045, 0x00000255, 0x0003003e, 0x00000252, 0x00000256, + 0x0004003d, 0x0000022e, 0x00000259, 0x00000258, 0x0004003d, 0x00000021, 0x0000025a, 0x00000233, + 0x00050057, 0x00000172, 0x0000025b, 0x00000259, 0x0000025a, 0x0008004f, 0x00000007, 0x0000025c, + 0x0000025b, 0x0000025b, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x00000257, 0x0000025c, + 0x0004003d, 0x00000007, 0x0000025d, 0x00000257, 0x00050083, 0x00000007, 0x0000025f, 0x0000025d, + 0x0000025e, 0x0006000c, 0x00000006, 0x00000260, 0x00000001, 0x00000042, 0x0000025f, 0x000500ba, + 0x00000097, 0x00000261, 0x00000260, 0x0000012a, 0x000300f7, 0x00000263, 0x00000000, 0x000400fa, + 0x00000261, 0x00000262, 0x00000263, 0x000200f8, 0x00000262, 0x0004003d, 0x00000007, 0x00000266, + 0x00000252, 0x0003003e, 0x00000265, 0x00000266, 0x0004003d, 0x00000007, 0x00000268, 0x00000257, + 0x0003003e, 0x00000267, 0x00000268, 0x0004003d, 0x00000007, 0x0000026a, 0x00000264, 0x0003003e, + 0x00000269, 0x0000026a, 0x0004003d, 0x00000021, 0x0000026c, 0x00000233, 0x0003003e, 0x0000026b, + 0x0000026c, 0x00080039, 0x00000007, 0x0000026d, 0x00000028, 0x00000265, 0x00000267, 0x00000269, + 0x0000026b, 0x0003003e, 0x00000252, 0x0000026d, 0x000200f9, 0x00000263, 0x000200f8, 0x00000263, + 0x0004003d, 0x00000007, 0x00000270, 0x0000026f, 0x0004003d, 0x00000007, 0x00000271, 0x00000264, + 0x00050083, 0x00000007, 0x00000272, 0x00000270, 0x00000271, 0x0006000c, 0x00000007, 0x00000273, + 0x00000001, 0x00000045, 0x00000272, 0x0003003e, 0x0000026e, 0x00000273, 0x0003003e, 0x00000274, + 0x00000275, 0x0004003d, 0x00000007, 0x00000277, 0x0000026f, 0x0004003d, 0x00000007, 0x00000278, + 0x00000264, 0x00050083, 0x00000007, 0x00000279, 0x00000277, 0x00000278, 0x0006000c, 0x00000006, + 0x0000027a, 0x00000001, 0x00000042, 0x00000279, 0x0003003e, 0x00000276, 0x0000027a, 0x0004003d, + 0x00000007, 0x0000027d, 0x00000264, 0x0003003e, 0x0000027c, 0x0000027d, 0x0004003d, 0x00000006, + 0x0000027f, 0x00000276, 0x0003003e, 0x0000027e, 0x0000027f, 0x00060039, 0x00000006, 0x00000280, + 0x00000037, 0x0000027c, 0x0000027e, 0x0003003e, 0x0000027b, 0x00000280, 0x00050041, 0x00000281, + 0x00000282, 0x00000243, 0x0000017a, 0x0004003d, 0x00000172, 0x00000283, 0x00000282, 0x0008004f, + 0x00000007, 0x00000284, 0x00000283, 0x00000283, 0x00000000, 0x00000001, 0x00000002, 0x0006000c, + 0x00000007, 0x00000285, 0x00000001, 0x00000045, 0x00000284, 0x0004003d, 0x00000007, 0x00000288, + 0x00000252, 0x0003003e, 0x00000287, 0x00000288, 0x0004003d, 0x00000007, 0x0000028a, 0x0000026e, + 0x0003003e, 0x00000289, 0x0000028a, 0x0004003d, 0x00000007, 0x0000028c, 0x0000023a, 0x0003003e, + 0x0000028b, 0x0000028c, 0x0004003d, 0x00000006, 0x0000028e, 0x0000023d, 0x0003003e, 0x0000028d, + 0x0000028e, 0x0004003d, 0x00000006, 0x00000290, 0x00000248, 0x0003003e, 0x0000028f, 0x00000290, + 0x0003003e, 0x00000291, 0x00000285, 0x00050041, 0x00000281, 0x00000293, 0x00000243, 0x00000286, + 0x0004003d, 0x00000172, 0x00000294, 0x00000293, 0x0008004f, 0x00000007, 0x00000295, 0x00000294, + 0x00000294, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x00000292, 0x00000295, 0x000b0039, + 0x00000007, 0x00000296, 0x00000032, 0x00000287, 0x00000289, 0x0000028b, 0x0000028d, 0x0000028f, + 0x00000291, 0x00000292, 0x0004003d, 0x00000006, 0x00000297, 0x0000027b, 0x0005008e, 0x00000007, + 0x00000298, 0x00000296, 0x00000297, 0x0004003d, 0x00000007, 0x00000299, 0x00000274, 0x00050081, + 0x00000007, 0x0000029a, 0x00000299, 0x00000298, 0x0003003e, 0x00000274, 0x0000029a, 0x0003003e, + 0x0000029b, 0x00000199, 0x000200f9, 0x0000029c, 0x000200f8, 0x0000029c, 0x000400f6, 0x0000029e, + 0x0000029f, 0x00000000, 0x000200f9, 0x000002a0, 0x000200f8, 0x000002a0, 0x0004003d, 0x00000179, + 0x000002a1, 0x0000029b, 0x000500b1, 0x00000097, 0x000002a3, 0x000002a1, 0x000002a2, 0x000400fa, + 0x000002a3, 0x0000029d, 0x0000029e, 0x000200f8, 0x0000029d, 0x0004003d, 0x00000179, 0x000002a4, + 0x0000029b, 0x0004003d, 0x00000179, 0x000002a5, 0x0000024d, 0x000500af, 0x00000097, 0x000002a6, + 0x000002a4, 0x000002a5, 0x000300f7, 0x000002a8, 0x00000000, 0x000400fa, 0x000002a6, 0x000002a7, + 0x000002a8, 0x000200f8, 0x000002a7, 0x000200f9, 0x0000029e, 0x000200f8, 0x000002a8, 0x0004003d, + 0x00000179, 0x000002ae, 0x0000029b, 0x00060041, 0x000002af, 0x000002b0, 0x00000243, 0x000002ad, + 0x000002ae, 0x0004003d, 0x0000023e, 0x000002b1, 0x000002b0, 0x00050051, 0x00000172, 0x000002b2, + 0x000002b1, 0x00000000, 0x00050041, 0x000001a1, 0x000002b3, 0x000002ac, 0x00000199, 0x0003003e, + 0x000002b3, 0x000002b2, 0x00050051, 0x00000172, 0x000002b4, 0x000002b1, 0x00000001, 0x00050041, + 0x000001a1, 0x000002b5, 0x000002ac, 0x00000189, 0x0003003e, 0x000002b5, 0x000002b4, 0x00050041, + 0x000001a1, 0x000002b7, 0x000002ac, 0x00000189, 0x0004003d, 0x00000172, 0x000002b8, 0x000002b7, + 0x0008004f, 0x00000007, 0x000002b9, 0x000002b8, 0x000002b8, 0x00000000, 0x00000001, 0x00000002, + 0x0003003e, 0x000002b6, 0x000002b9, 0x00060041, 0x00000009, 0x000002ba, 0x000002ac, 0x00000199, + 0x00000174, 0x0004003d, 0x00000006, 0x000002bb, 0x000002ba, 0x000500bc, 0x00000097, 0x000002bc, + 0x000002bb, 0x00000047, 0x000300f7, 0x000002be, 0x00000000, 0x000400fa, 0x000002bc, 0x000002bd, + 0x000002cf, 0x000200f8, 0x000002bd, 0x00050041, 0x000001a1, 0x000002c0, 0x000002ac, 0x00000199, + 0x0004003d, 0x00000172, 0x000002c1, 0x000002c0, 0x0008004f, 0x00000007, 0x000002c2, 0x000002c1, + 0x000002c1, 0x00000000, 0x00000001, 0x00000002, 0x0006000c, 0x00000007, 0x000002c3, 0x00000001, + 0x00000045, 0x000002c2, 0x0003003e, 0x000002bf, 0x000002c3, 0x0004003d, 0x00000007, 0x000002c4, + 0x000002bf, 0x00050041, 0x00000281, 0x000002c5, 0x00000243, 0x0000017a, 0x0004003d, 0x00000172, + 0x000002c6, 0x000002c5, 0x0008004f, 0x00000007, 0x000002c7, 0x000002c6, 0x000002c6, 0x00000000, + 0x00000001, 0x00000002, 0x0006000c, 0x00000007, 0x000002c8, 0x00000001, 0x00000045, 0x000002c7, + 0x00050083, 0x00000007, 0x000002c9, 0x000002c4, 0x000002c8, 0x0006000c, 0x00000006, 0x000002ca, + 0x00000001, 0x00000042, 0x000002c9, 0x000500b8, 0x00000097, 0x000002cb, 0x000002ca, 0x00000158, + 0x000300f7, 0x000002cd, 0x00000000, 0x000400fa, 0x000002cb, 0x000002cc, 0x000002cd, 0x000200f8, + 0x000002cc, 0x000200f9, 0x0000029f, 0x000200f8, 0x000002cd, 0x000200f9, 0x000002be, 0x000200f8, + 0x000002cf, 0x00050041, 0x000001a1, 0x000002d1, 0x000002ac, 0x00000199, 0x0004003d, 0x00000172, + 0x000002d2, 0x000002d1, 0x0008004f, 0x00000007, 0x000002d3, 0x000002d2, 0x000002d2, 0x00000000, + 0x00000001, 0x00000002, 0x0004003d, 0x00000007, 0x000002d4, 0x00000264, 0x00050083, 0x00000007, + 0x000002d5, 0x000002d3, 0x000002d4, 0x0003003e, 0x000002d0, 0x000002d5, 0x0004003d, 0x00000007, + 0x000002d7, 0x000002d0, 0x0006000c, 0x00000006, 0x000002d8, 0x00000001, 0x00000042, 0x000002d7, + 0x0003003e, 0x000002d6, 0x000002d8, 0x0004003d, 0x00000007, 0x000002d9, 0x000002d0, 0x0004003d, + 0x00000006, 0x000002da, 0x000002d6, 0x0007000c, 0x00000006, 0x000002db, 0x00000001, 0x00000028, + 0x000002da, 0x00000058, 0x00060050, 0x00000007, 0x000002dc, 0x000002db, 0x000002db, 0x000002db, + 0x00050088, 0x00000007, 0x000002dd, 0x000002d9, 0x000002dc, 0x0003003e, 0x000002bf, 0x000002dd, + 0x0004003d, 0x00000006, 0x000002df, 0x000002d6, 0x00060041, 0x00000009, 0x000002e0, 0x000002ac, + 0x00000199, 0x00000174, 0x0004003d, 0x00000006, 0x000002e1, 0x000002e0, 0x0007000c, 0x00000006, + 0x000002e2, 0x00000001, 0x00000028, 0x000002e1, 0x00000158, 0x00050088, 0x00000006, 0x000002e3, + 0x000002df, 0x000002e2, 0x00050083, 0x00000006, 0x000002e4, 0x0000004e, 0x000002e3, 0x0008000c, + 0x00000006, 0x000002e5, 0x00000001, 0x0000002b, 0x000002e4, 0x00000047, 0x0000004e, 0x0003003e, + 0x000002de, 0x000002e5, 0x0004003d, 0x00000006, 0x000002e6, 0x000002de, 0x0004003d, 0x00000006, + 0x000002e7, 0x000002de, 0x00050085, 0x00000006, 0x000002e8, 0x000002e7, 0x000002e6, 0x0003003e, + 0x000002de, 0x000002e8, 0x0004003d, 0x00000006, 0x000002e9, 0x000002de, 0x0004003d, 0x00000007, + 0x000002ea, 0x000002b6, 0x0005008e, 0x00000007, 0x000002eb, 0x000002ea, 0x000002e9, 0x0003003e, + 0x000002b6, 0x000002eb, 0x000200f9, 0x000002be, 0x000200f8, 0x000002be, 0x0004003d, 0x00000007, + 0x000002ed, 0x00000252, 0x0003003e, 0x000002ec, 0x000002ed, 0x0004003d, 0x00000007, 0x000002ef, + 0x0000026e, 0x0003003e, 0x000002ee, 0x000002ef, 0x0004003d, 0x00000007, 0x000002f1, 0x0000023a, + 0x0003003e, 0x000002f0, 0x000002f1, 0x0004003d, 0x00000006, 0x000002f3, 0x0000023d, 0x0003003e, + 0x000002f2, 0x000002f3, 0x0004003d, 0x00000006, 0x000002f5, 0x00000248, 0x0003003e, 0x000002f4, + 0x000002f5, 0x0004003d, 0x00000007, 0x000002f7, 0x000002bf, 0x0003003e, 0x000002f6, 0x000002f7, + 0x0004003d, 0x00000007, 0x000002f9, 0x000002b6, 0x0003003e, 0x000002f8, 0x000002f9, 0x000b0039, + 0x00000007, 0x000002fa, 0x00000032, 0x000002ec, 0x000002ee, 0x000002f0, 0x000002f2, 0x000002f4, + 0x000002f6, 0x000002f8, 0x0004003d, 0x00000007, 0x000002fb, 0x00000274, 0x00050081, 0x00000007, + 0x000002fc, 0x000002fb, 0x000002fa, 0x0003003e, 0x00000274, 0x000002fc, 0x000200f9, 0x0000029f, + 0x000200f8, 0x0000029f, 0x0004003d, 0x00000179, 0x000002fd, 0x0000029b, 0x00050080, 0x00000179, + 0x000002fe, 0x000002fd, 0x00000189, 0x0003003e, 0x0000029b, 0x000002fe, 0x000200f9, 0x0000029c, + 0x000200f8, 0x0000029e, 0x00060041, 0x0000017b, 0x00000300, 0x00000243, 0x00000244, 0x00000099, + 0x0004003d, 0x00000006, 0x00000301, 0x00000300, 0x00060041, 0x0000017b, 0x00000302, 0x00000243, + 0x00000244, 0x000000a1, 0x0004003d, 0x00000006, 0x00000303, 0x00000302, 0x0007000c, 0x00000006, + 0x00000304, 0x00000001, 0x00000028, 0x00000301, 0x00000303, 0x00060041, 0x0000017b, 0x00000305, + 0x00000243, 0x00000244, 0x000000a9, 0x0004003d, 0x00000006, 0x00000306, 0x00000305, 0x0007000c, + 0x00000006, 0x00000307, 0x00000001, 0x00000028, 0x00000304, 0x00000306, 0x0003003e, 0x000002ff, + 0x00000307, 0x0004003d, 0x00000006, 0x00000308, 0x000002ff, 0x000500b8, 0x00000097, 0x0000030a, + 0x00000308, 0x00000309, 0x000300f7, 0x0000030c, 0x00000000, 0x000400fa, 0x0000030a, 0x0000030b, + 0x0000030e, 0x000200f8, 0x0000030b, 0x0003003e, 0x000002ff, 0x0000030d, 0x000200f9, 0x0000030c, + 0x000200f8, 0x0000030e, 0x0004003d, 0x00000006, 0x0000030f, 0x000002ff, 0x0007000c, 0x00000006, + 0x00000311, 0x00000001, 0x00000028, 0x0000030f, 0x00000310, 0x0003003e, 0x000002ff, 0x00000311, + 0x000200f9, 0x0000030c, 0x000200f8, 0x0000030c, 0x0004003d, 0x00000007, 0x00000313, 0x0000023a, + 0x0004003d, 0x00000006, 0x00000314, 0x000002ff, 0x0005008e, 0x00000007, 0x00000315, 0x00000313, + 0x00000314, 0x0004003d, 0x00000006, 0x00000316, 0x0000023d, 0x00050083, 0x00000006, 0x00000317, + 0x0000004e, 0x00000316, 0x0005008e, 0x00000007, 0x00000318, 0x00000315, 0x00000317, 0x0004003d, + 0x00000007, 0x00000319, 0x00000274, 0x00050081, 0x00000007, 0x0000031a, 0x00000318, 0x00000319, + 0x0003003e, 0x00000312, 0x0000031a, 0x00060041, 0x0000017b, 0x0000031c, 0x00000243, 0x00000286, + 0x00000174, 0x0004003d, 0x00000006, 0x0000031d, 0x0000031c, 0x0003003e, 0x0000031b, 0x0000031d, + 0x0004003d, 0x00000006, 0x0000031e, 0x0000031b, 0x000500ba, 0x00000097, 0x0000031f, 0x0000031e, + 0x00000058, 0x000300f7, 0x00000321, 0x00000000, 0x000400fa, 0x0000031f, 0x00000320, 0x00000321, + 0x000200f8, 0x00000320, 0x0004003d, 0x00000007, 0x00000323, 0x0000026e, 0x0004007f, 0x00000007, + 0x00000324, 0x00000323, 0x0004003d, 0x00000007, 0x00000325, 0x00000252, 0x0007000c, 0x00000007, + 0x00000326, 0x00000001, 0x00000047, 0x00000324, 0x00000325, 0x0003003e, 0x00000322, 0x00000326, + 0x0004003d, 0x00000006, 0x00000328, 0x00000248, 0x00050085, 0x00000006, 0x00000329, 0x00000328, + 0x0000008b, 0x0003003e, 0x00000327, 0x00000329, 0x0004003d, 0x0000032c, 0x0000032f, 0x0000032e, + 0x0004003d, 0x00000007, 0x00000330, 0x00000322, 0x0004003d, 0x00000006, 0x00000331, 0x00000327, + 0x00070058, 0x00000172, 0x00000332, 0x0000032f, 0x00000330, 0x00000002, 0x00000331, 0x0008004f, + 0x00000007, 0x00000333, 0x00000332, 0x00000332, 0x00000000, 0x00000001, 0x00000002, 0x0004003d, + 0x00000006, 0x00000334, 0x0000031b, 0x0005008e, 0x00000007, 0x00000335, 0x00000333, 0x00000334, + 0x0003003e, 0x0000032a, 0x00000335, 0x0004003d, 0x00000007, 0x00000337, 0x0000023a, 0x0004003d, + 0x00000006, 0x00000338, 0x0000023d, 0x00060050, 0x00000007, 0x00000339, 0x00000338, 0x00000338, + 0x00000338, 0x0008000c, 0x00000007, 0x0000033a, 0x00000001, 0x0000002e, 0x0000012b, 0x00000337, + 0x00000339, 0x0003003e, 0x00000336, 0x0000033a, 0x0004003d, 0x00000007, 0x0000033c, 0x00000252, + 0x0004003d, 0x00000007, 0x0000033d, 0x0000026e, 0x00050094, 0x00000006, 0x0000033e, 0x0000033c, + 0x0000033d, 0x0007000c, 0x00000006, 0x0000033f, 0x00000001, 0x00000028, 0x0000033e, 0x00000047, + 0x0003003e, 0x00000340, 0x0000033f, 0x0004003d, 0x00000007, 0x00000342, 0x00000336, 0x0003003e, + 0x00000341, 0x00000342, 0x00060039, 0x00000007, 0x00000343, 0x0000001f, 0x00000340, 0x00000341, + 0x0003003e, 0x0000033b, 0x00000343, 0x0004003d, 0x00000007, 0x00000344, 0x0000032a, 0x0004003d, + 0x00000007, 0x00000345, 0x0000033b, 0x00050085, 0x00000007, 0x00000346, 0x00000344, 0x00000345, + 0x0004003d, 0x00000007, 0x00000347, 0x00000312, 0x00050081, 0x00000007, 0x00000348, 0x00000347, + 0x00000346, 0x0003003e, 0x00000312, 0x00000348, 0x0004003d, 0x0000032c, 0x0000034a, 0x0000032e, + 0x0004003d, 0x00000007, 0x0000034b, 0x00000252, 0x00070058, 0x00000172, 0x0000034c, 0x0000034a, + 0x0000034b, 0x00000002, 0x0000008b, 0x0008004f, 0x00000007, 0x0000034d, 0x0000034c, 0x0000034c, + 0x00000000, 0x00000001, 0x00000002, 0x0004003d, 0x00000006, 0x0000034e, 0x0000031b, 0x0005008e, + 0x00000007, 0x0000034f, 0x0000034d, 0x0000034e, 0x0003003e, 0x00000349, 0x0000034f, 0x0004003d, + 0x00000007, 0x00000350, 0x0000023a, 0x0004003d, 0x00000007, 0x00000351, 0x00000349, 0x00050085, + 0x00000007, 0x00000352, 0x00000350, 0x00000351, 0x0004003d, 0x00000006, 0x00000353, 0x0000023d, + 0x00050083, 0x00000006, 0x00000354, 0x0000004e, 0x00000353, 0x0005008e, 0x00000007, 0x00000355, + 0x00000352, 0x00000354, 0x0004003d, 0x00000007, 0x00000356, 0x0000033b, 0x00060050, 0x00000007, + 0x00000357, 0x0000004e, 0x0000004e, 0x0000004e, 0x00050083, 0x00000007, 0x00000358, 0x00000357, + 0x00000356, 0x00050085, 0x00000007, 0x00000359, 0x00000355, 0x00000358, 0x0005008e, 0x00000007, + 0x0000035b, 0x00000359, 0x0000035a, 0x0004003d, 0x00000007, 0x0000035c, 0x00000312, 0x00050081, + 0x00000007, 0x0000035d, 0x0000035c, 0x0000035b, 0x0003003e, 0x00000312, 0x0000035d, 0x000200f9, + 0x00000321, 0x000200f8, 0x00000321, 0x0004003d, 0x00000007, 0x00000360, 0x00000312, 0x00050041, + 0x00000009, 0x00000361, 0x0000022c, 0x00000174, 0x0004003d, 0x00000006, 0x00000362, 0x00000361, + 0x00050051, 0x00000006, 0x00000363, 0x00000360, 0x00000000, 0x00050051, 0x00000006, 0x00000364, + 0x00000360, 0x00000001, 0x00050051, 0x00000006, 0x00000365, 0x00000360, 0x00000002, 0x00070050, + 0x00000172, 0x00000366, 0x00000363, 0x00000364, 0x00000365, 0x00000362, 0x0003003e, 0x0000035f, + 0x00000366, 0x000100fd, 0x00010038, 0x00050036, 0x00000006, 0x0000000e, 0x00000000, 0x0000000a, + 0x00030037, 0x00000008, 0x0000000b, 0x00030037, 0x00000008, 0x0000000c, 0x00030037, 0x00000009, + 0x0000000d, 0x000200f8, 0x0000000f, 0x0004003b, 0x00000009, 0x00000039, 0x00000007, 0x0004003b, + 0x00000009, 0x0000003f, 0x00000007, 0x0004003b, 0x00000009, 0x00000043, 0x00000007, 0x0004003b, + 0x00000009, 0x00000049, 0x00000007, 0x0004003d, 0x00000006, 0x0000003a, 0x0000000d, 0x0004003d, + 0x00000006, 0x0000003b, 0x0000000d, 0x00050085, 0x00000006, 0x0000003c, 0x0000003a, 0x0000003b, + 0x0007000c, 0x00000006, 0x0000003e, 0x00000001, 0x00000028, 0x0000003c, 0x0000003d, 0x0003003e, + 0x00000039, 0x0000003e, 0x0004003d, 0x00000006, 0x00000040, 0x00000039, 0x0004003d, 0x00000006, + 0x00000041, 0x00000039, 0x00050085, 0x00000006, 0x00000042, 0x00000040, 0x00000041, 0x0003003e, + 0x0000003f, 0x00000042, 0x0004003d, 0x00000007, 0x00000044, 0x0000000b, 0x0004003d, 0x00000007, + 0x00000045, 0x0000000c, 0x00050094, 0x00000006, 0x00000046, 0x00000044, 0x00000045, 0x0007000c, + 0x00000006, 0x00000048, 0x00000001, 0x00000028, 0x00000046, 0x00000047, 0x0003003e, 0x00000043, + 0x00000048, 0x0004003d, 0x00000006, 0x0000004a, 0x00000043, 0x0004003d, 0x00000006, 0x0000004b, + 0x00000043, 0x00050085, 0x00000006, 0x0000004c, 0x0000004a, 0x0000004b, 0x0004003d, 0x00000006, + 0x0000004d, 0x0000003f, 0x00050083, 0x00000006, 0x0000004f, 0x0000004d, 0x0000004e, 0x00050085, + 0x00000006, 0x00000050, 0x0000004c, 0x0000004f, 0x00050081, 0x00000006, 0x00000051, 0x00000050, + 0x0000004e, 0x0003003e, 0x00000049, 0x00000051, 0x0004003d, 0x00000006, 0x00000052, 0x0000003f, + 0x0004003d, 0x00000006, 0x00000054, 0x00000049, 0x00050085, 0x00000006, 0x00000055, 0x00000053, + 0x00000054, 0x0004003d, 0x00000006, 0x00000056, 0x00000049, 0x00050085, 0x00000006, 0x00000057, + 0x00000055, 0x00000056, 0x0007000c, 0x00000006, 0x00000059, 0x00000001, 0x00000028, 0x00000057, + 0x00000058, 0x00050088, 0x00000006, 0x0000005a, 0x00000052, 0x00000059, 0x000200fe, 0x0000005a, + 0x00010038, 0x00050036, 0x00000006, 0x00000013, 0x00000000, 0x00000010, 0x00030037, 0x00000009, + 0x00000011, 0x00030037, 0x00000009, 0x00000012, 0x000200f8, 0x00000014, 0x0004003b, 0x00000009, + 0x0000005d, 0x00000007, 0x0004003b, 0x00000009, 0x00000060, 0x00000007, 0x0004003d, 0x00000006, + 0x0000005e, 0x00000012, 0x00050081, 0x00000006, 0x0000005f, 0x0000005e, 0x0000004e, 0x0003003e, + 0x0000005d, 0x0000005f, 0x0004003d, 0x00000006, 0x00000061, 0x0000005d, 0x0004003d, 0x00000006, + 0x00000062, 0x0000005d, 0x00050085, 0x00000006, 0x00000063, 0x00000061, 0x00000062, 0x00050088, + 0x00000006, 0x00000065, 0x00000063, 0x00000064, 0x0003003e, 0x00000060, 0x00000065, 0x0004003d, + 0x00000006, 0x00000066, 0x00000011, 0x0004003d, 0x00000006, 0x00000067, 0x00000011, 0x0004003d, + 0x00000006, 0x00000068, 0x00000060, 0x00050083, 0x00000006, 0x00000069, 0x0000004e, 0x00000068, + 0x00050085, 0x00000006, 0x0000006a, 0x00000067, 0x00000069, 0x0004003d, 0x00000006, 0x0000006b, + 0x00000060, 0x00050081, 0x00000006, 0x0000006c, 0x0000006a, 0x0000006b, 0x0007000c, 0x00000006, + 0x0000006d, 0x00000001, 0x00000028, 0x0000006c, 0x00000058, 0x00050088, 0x00000006, 0x0000006e, + 0x00000066, 0x0000006d, 0x000200fe, 0x0000006e, 0x00010038, 0x00050036, 0x00000006, 0x0000001a, + 0x00000000, 0x00000015, 0x00030037, 0x00000008, 0x00000016, 0x00030037, 0x00000008, 0x00000017, + 0x00030037, 0x00000008, 0x00000018, 0x00030037, 0x00000009, 0x00000019, 0x000200f8, 0x0000001b, + 0x0004003b, 0x00000009, 0x00000075, 0x00000007, 0x0004003b, 0x00000009, 0x00000076, 0x00000007, + 0x0004003b, 0x00000009, 0x0000007d, 0x00000007, 0x0004003b, 0x00000009, 0x0000007e, 0x00000007, + 0x0004003d, 0x00000007, 0x00000071, 0x00000016, 0x0004003d, 0x00000007, 0x00000072, 0x00000017, + 0x00050094, 0x00000006, 0x00000073, 0x00000071, 0x00000072, 0x0007000c, 0x00000006, 0x00000074, + 0x00000001, 0x00000028, 0x00000073, 0x00000047, 0x0003003e, 0x00000075, 0x00000074, 0x0004003d, + 0x00000006, 0x00000077, 0x00000019, 0x0003003e, 0x00000076, 0x00000077, 0x00060039, 0x00000006, + 0x00000078, 0x00000013, 0x00000075, 0x00000076, 0x0004003d, 0x00000007, 0x00000079, 0x00000016, + 0x0004003d, 0x00000007, 0x0000007a, 0x00000018, 0x00050094, 0x00000006, 0x0000007b, 0x00000079, + 0x0000007a, 0x0007000c, 0x00000006, 0x0000007c, 0x00000001, 0x00000028, 0x0000007b, 0x00000047, + 0x0003003e, 0x0000007d, 0x0000007c, 0x0004003d, 0x00000006, 0x0000007f, 0x00000019, 0x0003003e, + 0x0000007e, 0x0000007f, 0x00060039, 0x00000006, 0x00000080, 0x00000013, 0x0000007d, 0x0000007e, + 0x00050085, 0x00000006, 0x00000081, 0x00000078, 0x00000080, 0x000200fe, 0x00000081, 0x00010038, + 0x00050036, 0x00000007, 0x0000001f, 0x00000000, 0x0000001c, 0x00030037, 0x00000009, 0x0000001d, + 0x00030037, 0x00000008, 0x0000001e, 0x000200f8, 0x00000020, 0x0004003d, 0x00000007, 0x00000084, + 0x0000001e, 0x0004003d, 0x00000007, 0x00000085, 0x0000001e, 0x00060050, 0x00000007, 0x00000086, + 0x0000004e, 0x0000004e, 0x0000004e, 0x00050083, 0x00000007, 0x00000087, 0x00000086, 0x00000085, + 0x0004003d, 0x00000006, 0x00000088, 0x0000001d, 0x00050083, 0x00000006, 0x00000089, 0x0000004e, + 0x00000088, 0x0008000c, 0x00000006, 0x0000008a, 0x00000001, 0x0000002b, 0x00000089, 0x00000047, + 0x0000004e, 0x0007000c, 0x00000006, 0x0000008c, 0x00000001, 0x0000001a, 0x0000008a, 0x0000008b, + 0x0005008e, 0x00000007, 0x0000008d, 0x00000087, 0x0000008c, 0x00050081, 0x00000007, 0x0000008e, + 0x00000084, 0x0000008d, 0x000200fe, 0x0000008e, 0x00010038, 0x00050036, 0x00000007, 0x00000028, + 0x00000000, 0x00000023, 0x00030037, 0x00000008, 0x00000024, 0x00030037, 0x00000008, 0x00000025, + 0x00030037, 0x00000008, 0x00000026, 0x00030037, 0x00000022, 0x00000027, 0x000200f8, 0x00000029, + 0x0004003b, 0x00000008, 0x00000091, 0x00000007, 0x0004003b, 0x00000008, 0x000000b4, 0x00000007, + 0x0004003b, 0x00000008, 0x000000b7, 0x00000007, 0x0004003b, 0x00000022, 0x000000ba, 0x00000007, + 0x0004003b, 0x00000022, 0x000000bd, 0x00000007, 0x0004003b, 0x00000008, 0x000000d3, 0x00000007, + 0x0004003b, 0x00000008, 0x000000e2, 0x00000007, 0x0004003d, 0x00000007, 0x00000092, 0x00000025, + 0x0005008e, 0x00000007, 0x00000094, 0x00000092, 0x00000093, 0x00060050, 0x00000007, 0x00000095, + 0x0000004e, 0x0000004e, 0x0000004e, 0x00050083, 0x00000007, 0x00000096, 0x00000094, 0x00000095, + 0x0003003e, 0x00000091, 0x00000096, 0x00050041, 0x00000009, 0x0000009a, 0x00000091, 0x00000099, + 0x0004003d, 0x00000006, 0x0000009b, 0x0000009a, 0x0006000c, 0x00000006, 0x0000009c, 0x00000001, + 0x00000004, 0x0000009b, 0x000500b8, 0x00000097, 0x0000009e, 0x0000009c, 0x0000009d, 0x000300f7, + 0x000000a0, 0x00000000, 0x000400fa, 0x0000009e, 0x0000009f, 0x000000a0, 0x000200f8, 0x0000009f, + 0x00050041, 0x00000009, 0x000000a2, 0x00000091, 0x000000a1, 0x0004003d, 0x00000006, 0x000000a3, + 0x000000a2, 0x0006000c, 0x00000006, 0x000000a4, 0x00000001, 0x00000004, 0x000000a3, 0x000500b8, + 0x00000097, 0x000000a5, 0x000000a4, 0x0000009d, 0x000200f9, 0x000000a0, 0x000200f8, 0x000000a0, + 0x000700f5, 0x00000097, 0x000000a6, 0x0000009e, 0x00000029, 0x000000a5, 0x0000009f, 0x000300f7, + 0x000000a8, 0x00000000, 0x000400fa, 0x000000a6, 0x000000a7, 0x000000a8, 0x000200f8, 0x000000a7, + 0x00050041, 0x00000009, 0x000000aa, 0x00000091, 0x000000a9, 0x0004003d, 0x00000006, 0x000000ab, + 0x000000aa, 0x000500ba, 0x00000097, 0x000000ad, 0x000000ab, 0x000000ac, 0x000200f9, 0x000000a8, + 0x000200f8, 0x000000a8, 0x000700f5, 0x00000097, 0x000000ae, 0x000000a6, 0x000000a0, 0x000000ad, + 0x000000a7, 0x000300f7, 0x000000b0, 0x00000000, 0x000400fa, 0x000000ae, 0x000000af, 0x000000b0, + 0x000200f8, 0x000000af, 0x0004003d, 0x00000007, 0x000000b1, 0x00000024, 0x0006000c, 0x00000007, + 0x000000b2, 0x00000001, 0x00000045, 0x000000b1, 0x000200fe, 0x000000b2, 0x000200f8, 0x000000b0, + 0x0004003d, 0x00000007, 0x000000b5, 0x00000026, 0x000400cf, 0x00000007, 0x000000b6, 0x000000b5, + 0x0003003e, 0x000000b4, 0x000000b6, 0x0004003d, 0x00000007, 0x000000b8, 0x00000026, 0x000400d0, + 0x00000007, 0x000000b9, 0x000000b8, 0x0003003e, 0x000000b7, 0x000000b9, 0x0004003d, 0x00000021, + 0x000000bb, 0x00000027, 0x000400cf, 0x00000021, 0x000000bc, 0x000000bb, 0x0003003e, 0x000000ba, + 0x000000bc, 0x0004003d, 0x00000021, 0x000000be, 0x00000027, 0x000400d0, 0x00000021, 0x000000bf, + 0x000000be, 0x0003003e, 0x000000bd, 0x000000bf, 0x00050041, 0x00000009, 0x000000c0, 0x000000ba, + 0x00000099, 0x0004003d, 0x00000006, 0x000000c1, 0x000000c0, 0x00050041, 0x00000009, 0x000000c2, + 0x000000bd, 0x000000a1, 0x0004003d, 0x00000006, 0x000000c3, 0x000000c2, 0x00050085, 0x00000006, + 0x000000c4, 0x000000c1, 0x000000c3, 0x00050041, 0x00000009, 0x000000c5, 0x000000bd, 0x00000099, + 0x0004003d, 0x00000006, 0x000000c6, 0x000000c5, 0x00050041, 0x00000009, 0x000000c7, 0x000000ba, + 0x000000a1, 0x0004003d, 0x00000006, 0x000000c8, 0x000000c7, 0x00050085, 0x00000006, 0x000000c9, + 0x000000c6, 0x000000c8, 0x00050083, 0x00000006, 0x000000ca, 0x000000c4, 0x000000c9, 0x0006000c, + 0x00000006, 0x000000cb, 0x00000001, 0x00000004, 0x000000ca, 0x000500b8, 0x00000097, 0x000000cd, + 0x000000cb, 0x000000cc, 0x000300f7, 0x000000cf, 0x00000000, 0x000400fa, 0x000000cd, 0x000000ce, + 0x000000cf, 0x000200f8, 0x000000ce, 0x0004003d, 0x00000007, 0x000000d0, 0x00000024, 0x0006000c, + 0x00000007, 0x000000d1, 0x00000001, 0x00000045, 0x000000d0, 0x000200fe, 0x000000d1, 0x000200f8, + 0x000000cf, 0x0004003d, 0x00000007, 0x000000d4, 0x000000b7, 0x0004003d, 0x00000007, 0x000000d5, + 0x00000024, 0x0007000c, 0x00000007, 0x000000d6, 0x00000001, 0x00000044, 0x000000d4, 0x000000d5, + 0x00050041, 0x00000009, 0x000000d7, 0x000000ba, 0x00000099, 0x0004003d, 0x00000006, 0x000000d8, + 0x000000d7, 0x0005008e, 0x00000007, 0x000000d9, 0x000000d6, 0x000000d8, 0x0004003d, 0x00000007, + 0x000000da, 0x00000024, 0x0004003d, 0x00000007, 0x000000db, 0x000000b4, 0x0007000c, 0x00000007, + 0x000000dc, 0x00000001, 0x00000044, 0x000000da, 0x000000db, 0x00050041, 0x00000009, 0x000000dd, + 0x000000bd, 0x00000099, 0x0004003d, 0x00000006, 0x000000de, 0x000000dd, 0x0005008e, 0x00000007, + 0x000000df, 0x000000dc, 0x000000de, 0x00050081, 0x00000007, 0x000000e0, 0x000000d9, 0x000000df, + 0x0006000c, 0x00000007, 0x000000e1, 0x00000001, 0x00000045, 0x000000e0, 0x0003003e, 0x000000d3, + 0x000000e1, 0x0004003d, 0x00000007, 0x000000e3, 0x000000b7, 0x0004003d, 0x00000007, 0x000000e4, + 0x00000024, 0x0007000c, 0x00000007, 0x000000e5, 0x00000001, 0x00000044, 0x000000e3, 0x000000e4, + 0x00050041, 0x00000009, 0x000000e6, 0x000000ba, 0x000000a1, 0x0004003d, 0x00000006, 0x000000e7, + 0x000000e6, 0x0005008e, 0x00000007, 0x000000e8, 0x000000e5, 0x000000e7, 0x0004003d, 0x00000007, + 0x000000e9, 0x00000024, 0x0004003d, 0x00000007, 0x000000ea, 0x000000b4, 0x0007000c, 0x00000007, + 0x000000eb, 0x00000001, 0x00000044, 0x000000e9, 0x000000ea, 0x00050041, 0x00000009, 0x000000ec, + 0x000000bd, 0x000000a1, 0x0004003d, 0x00000006, 0x000000ed, 0x000000ec, 0x0005008e, 0x00000007, + 0x000000ee, 0x000000eb, 0x000000ed, 0x00050081, 0x00000007, 0x000000ef, 0x000000e8, 0x000000ee, + 0x0006000c, 0x00000007, 0x000000f0, 0x00000001, 0x00000045, 0x000000ef, 0x0003003e, 0x000000e2, + 0x000000f0, 0x0004003d, 0x00000007, 0x000000f1, 0x000000d3, 0x0006000c, 0x00000006, 0x000000f2, + 0x00000001, 0x00000042, 0x000000f1, 0x000500b8, 0x00000097, 0x000000f3, 0x000000f2, 0x00000058, + 0x000400a8, 0x00000097, 0x000000f4, 0x000000f3, 0x000300f7, 0x000000f6, 0x00000000, 0x000400fa, + 0x000000f4, 0x000000f5, 0x000000f6, 0x000200f8, 0x000000f5, 0x0004003d, 0x00000007, 0x000000f7, + 0x000000e2, 0x0006000c, 0x00000006, 0x000000f8, 0x00000001, 0x00000042, 0x000000f7, 0x000500b8, + 0x00000097, 0x000000f9, 0x000000f8, 0x00000058, 0x000200f9, 0x000000f6, 0x000200f8, 0x000000f6, + 0x000700f5, 0x00000097, 0x000000fa, 0x000000f3, 0x000000cf, 0x000000f9, 0x000000f5, 0x000300f7, + 0x000000fc, 0x00000000, 0x000400fa, 0x000000fa, 0x000000fb, 0x000000fc, 0x000200f8, 0x000000fb, + 0x0004003d, 0x00000007, 0x000000fd, 0x00000024, 0x0006000c, 0x00000007, 0x000000fe, 0x00000001, + 0x00000045, 0x000000fd, 0x000200fe, 0x000000fe, 0x000200f8, 0x000000fc, 0x0004003d, 0x00000007, + 0x00000100, 0x000000d3, 0x0004003d, 0x00000007, 0x00000101, 0x000000e2, 0x0004003d, 0x00000007, + 0x00000102, 0x00000024, 0x0006000c, 0x00000007, 0x00000103, 0x00000001, 0x00000045, 0x00000102, + 0x00050051, 0x00000006, 0x00000105, 0x00000100, 0x00000000, 0x00050051, 0x00000006, 0x00000106, + 0x00000100, 0x00000001, 0x00050051, 0x00000006, 0x00000107, 0x00000100, 0x00000002, 0x00050051, + 0x00000006, 0x00000108, 0x00000101, 0x00000000, 0x00050051, 0x00000006, 0x00000109, 0x00000101, + 0x00000001, 0x00050051, 0x00000006, 0x0000010a, 0x00000101, 0x00000002, 0x00050051, 0x00000006, + 0x0000010b, 0x00000103, 0x00000000, 0x00050051, 0x00000006, 0x0000010c, 0x00000103, 0x00000001, + 0x00050051, 0x00000006, 0x0000010d, 0x00000103, 0x00000002, 0x00060050, 0x00000007, 0x0000010e, + 0x00000105, 0x00000106, 0x00000107, 0x00060050, 0x00000007, 0x0000010f, 0x00000108, 0x00000109, + 0x0000010a, 0x00060050, 0x00000007, 0x00000110, 0x0000010b, 0x0000010c, 0x0000010d, 0x00060050, + 0x00000104, 0x00000111, 0x0000010e, 0x0000010f, 0x00000110, 0x0004003d, 0x00000007, 0x00000112, + 0x00000091, 0x00050091, 0x00000007, 0x00000113, 0x00000111, 0x00000112, 0x0006000c, 0x00000007, + 0x00000114, 0x00000001, 0x00000045, 0x00000113, 0x000200fe, 0x00000114, 0x00010038, 0x00050036, + 0x00000007, 0x00000032, 0x00000000, 0x0000002a, 0x00030037, 0x00000008, 0x0000002b, 0x00030037, + 0x00000008, 0x0000002c, 0x00030037, 0x00000008, 0x0000002d, 0x00030037, 0x00000009, 0x0000002e, + 0x00030037, 0x00000009, 0x0000002f, 0x00030037, 0x00000008, 0x00000030, 0x00030037, 0x00000008, + 0x00000031, 0x000200f8, 0x00000033, 0x0004003b, 0x00000009, 0x00000117, 0x00000007, 0x0004003b, + 0x00000009, 0x0000011c, 0x00000007, 0x0004003b, 0x00000008, 0x00000124, 0x00000007, 0x0004003b, + 0x00000008, 0x00000129, 0x00000007, 0x0004003b, 0x00000009, 0x00000130, 0x00000007, 0x0004003b, + 0x00000008, 0x00000131, 0x00000007, 0x0004003b, 0x00000008, 0x00000133, 0x00000007, 0x0004003b, + 0x00000009, 0x00000135, 0x00000007, 0x0004003b, 0x00000009, 0x00000138, 0x00000007, 0x0004003b, + 0x00000008, 0x00000139, 0x00000007, 0x0004003b, 0x00000008, 0x0000013b, 0x00000007, 0x0004003b, + 0x00000008, 0x0000013d, 0x00000007, 0x0004003b, 0x00000009, 0x0000013f, 0x00000007, 0x0004003b, + 0x00000008, 0x00000142, 0x00000007, 0x0004003b, 0x00000009, 0x00000147, 0x00000007, 0x0004003b, + 0x00000008, 0x00000148, 0x00000007, 0x0004003b, 0x00000008, 0x0000014b, 0x00000007, 0x0004003b, + 0x00000008, 0x0000015e, 0x00000007, 0x0004003d, 0x00000007, 0x00000118, 0x0000002b, 0x0004003d, + 0x00000007, 0x00000119, 0x00000030, 0x00050094, 0x00000006, 0x0000011a, 0x00000118, 0x00000119, + 0x0007000c, 0x00000006, 0x0000011b, 0x00000001, 0x00000028, 0x0000011a, 0x00000047, 0x0003003e, + 0x00000117, 0x0000011b, 0x0004003d, 0x00000006, 0x0000011d, 0x00000117, 0x0004003d, 0x00000006, + 0x0000011e, 0x00000117, 0x00050085, 0x00000006, 0x00000120, 0x0000011e, 0x0000011f, 0x00050081, + 0x00000006, 0x00000121, 0x00000120, 0x0000011f, 0x0008000c, 0x00000006, 0x00000123, 0x00000001, + 0x0000002e, 0x0000011d, 0x00000121, 0x00000122, 0x0003003e, 0x0000011c, 0x00000123, 0x0004003d, + 0x00000007, 0x00000125, 0x0000002c, 0x0004003d, 0x00000007, 0x00000126, 0x00000030, 0x00050081, + 0x00000007, 0x00000127, 0x00000125, 0x00000126, 0x0006000c, 0x00000007, 0x00000128, 0x00000001, + 0x00000045, 0x00000127, 0x0003003e, 0x00000124, 0x00000128, 0x0004003d, 0x00000007, 0x0000012c, + 0x0000002d, 0x0004003d, 0x00000006, 0x0000012d, 0x0000002e, 0x00060050, 0x00000007, 0x0000012e, + 0x0000012d, 0x0000012d, 0x0000012d, 0x0008000c, 0x00000007, 0x0000012f, 0x00000001, 0x0000002e, + 0x0000012b, 0x0000012c, 0x0000012e, 0x0003003e, 0x00000129, 0x0000012f, 0x0004003d, 0x00000007, + 0x00000132, 0x0000002b, 0x0003003e, 0x00000131, 0x00000132, 0x0004003d, 0x00000007, 0x00000134, + 0x00000124, 0x0003003e, 0x00000133, 0x00000134, 0x0004003d, 0x00000006, 0x00000136, 0x0000002f, + 0x0003003e, 0x00000135, 0x00000136, 0x00070039, 0x00000006, 0x00000137, 0x0000000e, 0x00000131, + 0x00000133, 0x00000135, 0x0003003e, 0x00000130, 0x00000137, 0x0004003d, 0x00000007, 0x0000013a, + 0x0000002b, 0x0003003e, 0x00000139, 0x0000013a, 0x0004003d, 0x00000007, 0x0000013c, 0x0000002c, + 0x0003003e, 0x0000013b, 0x0000013c, 0x0004003d, 0x00000007, 0x0000013e, 0x00000030, 0x0003003e, + 0x0000013d, 0x0000013e, 0x0004003d, 0x00000006, 0x00000140, 0x0000002f, 0x0003003e, 0x0000013f, + 0x00000140, 0x00080039, 0x00000006, 0x00000141, 0x0000001a, 0x00000139, 0x0000013b, 0x0000013d, + 0x0000013f, 0x0003003e, 0x00000138, 0x00000141, 0x0004003d, 0x00000007, 0x00000143, 0x00000124, + 0x0004003d, 0x00000007, 0x00000144, 0x0000002c, 0x00050094, 0x00000006, 0x00000145, 0x00000143, + 0x00000144, 0x0007000c, 0x00000006, 0x00000146, 0x00000001, 0x00000028, 0x00000145, 0x00000047, + 0x0003003e, 0x00000147, 0x00000146, 0x0004003d, 0x00000007, 0x00000149, 0x00000129, 0x0003003e, + 0x00000148, 0x00000149, 0x00060039, 0x00000007, 0x0000014a, 0x0000001f, 0x00000147, 0x00000148, + 0x0003003e, 0x00000142, 0x0000014a, 0x0004003d, 0x00000006, 0x0000014c, 0x00000130, 0x0004003d, + 0x00000006, 0x0000014d, 0x00000138, 0x00050085, 0x00000006, 0x0000014e, 0x0000014c, 0x0000014d, + 0x0004003d, 0x00000007, 0x0000014f, 0x00000142, 0x0005008e, 0x00000007, 0x00000150, 0x0000014f, + 0x0000014e, 0x0004003d, 0x00000007, 0x00000152, 0x0000002b, 0x0004003d, 0x00000007, 0x00000153, + 0x0000002c, 0x00050094, 0x00000006, 0x00000154, 0x00000152, 0x00000153, 0x0007000c, 0x00000006, + 0x00000155, 0x00000001, 0x00000028, 0x00000154, 0x00000047, 0x00050085, 0x00000006, 0x00000156, + 0x00000151, 0x00000155, 0x0004003d, 0x00000006, 0x00000157, 0x00000117, 0x0007000c, 0x00000006, + 0x00000159, 0x00000001, 0x00000028, 0x00000157, 0x00000158, 0x00050085, 0x00000006, 0x0000015a, + 0x00000156, 0x00000159, 0x0007000c, 0x00000006, 0x0000015b, 0x00000001, 0x00000028, 0x0000015a, + 0x00000158, 0x00060050, 0x00000007, 0x0000015c, 0x0000015b, 0x0000015b, 0x0000015b, 0x00050088, + 0x00000007, 0x0000015d, 0x00000150, 0x0000015c, 0x0003003e, 0x0000014b, 0x0000015d, 0x0004003d, + 0x00000007, 0x00000160, 0x00000142, 0x00050083, 0x00000007, 0x00000161, 0x0000015f, 0x00000160, + 0x0004003d, 0x00000006, 0x00000162, 0x0000002e, 0x00050083, 0x00000006, 0x00000163, 0x0000004e, + 0x00000162, 0x0005008e, 0x00000007, 0x00000164, 0x00000161, 0x00000163, 0x0003003e, 0x0000015e, + 0x00000164, 0x0004003d, 0x00000007, 0x00000165, 0x0000015e, 0x0004003d, 0x00000007, 0x00000166, + 0x0000002d, 0x00050085, 0x00000007, 0x00000167, 0x00000165, 0x00000166, 0x0004003d, 0x00000006, + 0x00000168, 0x0000011c, 0x0005008e, 0x00000007, 0x00000169, 0x00000167, 0x00000168, 0x0004003d, + 0x00000007, 0x0000016a, 0x0000014b, 0x0004003d, 0x00000006, 0x0000016b, 0x00000117, 0x0005008e, + 0x00000007, 0x0000016c, 0x0000016a, 0x0000016b, 0x00050081, 0x00000007, 0x0000016d, 0x00000169, + 0x0000016c, 0x0004003d, 0x00000007, 0x0000016e, 0x00000031, 0x00050085, 0x00000007, 0x0000016f, + 0x0000016d, 0x0000016e, 0x000200fe, 0x0000016f, 0x00010038, 0x00050036, 0x00000006, 0x00000037, + 0x00000000, 0x00000034, 0x00030037, 0x00000008, 0x00000035, 0x00030037, 0x00000009, 0x00000036, + 0x000200f8, 0x00000038, 0x0004003b, 0x00000191, 0x00000192, 0x00000007, 0x0004003b, 0x000001a1, + 0x000001a2, 0x00000007, 0x0004003b, 0x00000008, 0x000001ad, 0x00000007, 0x0004003b, 0x00000022, + 0x000001b6, 0x00000007, 0x0004003b, 0x00000009, 0x000001bc, 0x00000007, 0x0004003b, 0x00000009, + 0x000001e0, 0x00000007, 0x0004003b, 0x00000022, 0x000001e3, 0x00000007, 0x0004003b, 0x00000009, + 0x000001f1, 0x00000007, 0x0004003b, 0x00000191, 0x000001f2, 0x00000007, 0x0004003b, 0x00000191, + 0x000001fb, 0x00000007, 0x0004003b, 0x00000009, 0x00000203, 0x00000007, 0x0004003b, 0x00000009, + 0x00000221, 0x00000007, 0x00060041, 0x0000017b, 0x0000017c, 0x00000178, 0x0000017a, 0x000000a1, + 0x0004003d, 0x00000006, 0x0000017d, 0x0000017c, 0x000500b8, 0x00000097, 0x0000017e, 0x0000017d, + 0x0000011f, 0x000400a8, 0x00000097, 0x0000017f, 0x0000017e, 0x000300f7, 0x00000181, 0x00000000, + 0x000400fa, 0x0000017f, 0x00000180, 0x00000181, 0x000200f8, 0x00000180, 0x00060041, 0x0000017b, + 0x00000182, 0x00000178, 0x0000017a, 0x000000a9, 0x0004003d, 0x00000006, 0x00000183, 0x00000182, + 0x000500b8, 0x00000097, 0x00000184, 0x00000183, 0x0000011f, 0x000200f9, 0x00000181, 0x000200f8, + 0x00000181, 0x000700f5, 0x00000097, 0x00000185, 0x0000017e, 0x00000038, 0x00000184, 0x00000180, + 0x000400a8, 0x00000097, 0x00000186, 0x00000185, 0x000300f7, 0x00000188, 0x00000000, 0x000400fa, + 0x00000186, 0x00000187, 0x00000188, 0x000200f8, 0x00000187, 0x00060041, 0x0000017b, 0x0000018a, + 0x00000178, 0x00000189, 0x00000174, 0x0004003d, 0x00000006, 0x0000018b, 0x0000018a, 0x000500b8, + 0x00000097, 0x0000018c, 0x0000018b, 0x00000058, 0x000200f9, 0x00000188, 0x000200f8, 0x00000188, + 0x000700f5, 0x00000097, 0x0000018d, 0x00000185, 0x00000181, 0x0000018c, 0x00000187, 0x000300f7, + 0x0000018f, 0x00000000, 0x000400fa, 0x0000018d, 0x0000018e, 0x0000018f, 0x000200f8, 0x0000018e, + 0x000200fe, 0x0000004e, 0x000200f8, 0x0000018f, 0x0003003e, 0x00000192, 0x0000017a, 0x0004003d, + 0x00000006, 0x00000193, 0x00000036, 0x00060041, 0x0000017b, 0x00000194, 0x00000178, 0x00000189, + 0x00000099, 0x0004003d, 0x00000006, 0x00000195, 0x00000194, 0x000500b8, 0x00000097, 0x00000196, + 0x00000193, 0x00000195, 0x000300f7, 0x00000198, 0x00000000, 0x000400fa, 0x00000196, 0x00000197, + 0x0000019a, 0x000200f8, 0x00000197, 0x0003003e, 0x00000192, 0x00000199, 0x000200f9, 0x00000198, + 0x000200f8, 0x0000019a, 0x0004003d, 0x00000006, 0x0000019b, 0x00000036, 0x00060041, 0x0000017b, + 0x0000019c, 0x00000178, 0x00000189, 0x000000a1, 0x0004003d, 0x00000006, 0x0000019d, 0x0000019c, + 0x000500b8, 0x00000097, 0x0000019e, 0x0000019b, 0x0000019d, 0x000300f7, 0x000001a0, 0x00000000, + 0x000400fa, 0x0000019e, 0x0000019f, 0x000001a0, 0x000200f8, 0x0000019f, 0x0003003e, 0x00000192, + 0x00000189, 0x000200f9, 0x000001a0, 0x000200f8, 0x000001a0, 0x000200f9, 0x00000198, 0x000200f8, + 0x00000198, 0x0004003d, 0x00000179, 0x000001a3, 0x00000192, 0x00060041, 0x000001a4, 0x000001a5, + 0x00000178, 0x00000199, 0x000001a3, 0x0004003d, 0x00000173, 0x000001a6, 0x000001a5, 0x0004003d, + 0x00000007, 0x000001a7, 0x00000035, 0x00050051, 0x00000006, 0x000001a8, 0x000001a7, 0x00000000, + 0x00050051, 0x00000006, 0x000001a9, 0x000001a7, 0x00000001, 0x00050051, 0x00000006, 0x000001aa, + 0x000001a7, 0x00000002, 0x00070050, 0x00000172, 0x000001ab, 0x000001a8, 0x000001a9, 0x000001aa, + 0x0000004e, 0x00050091, 0x00000172, 0x000001ac, 0x000001a6, 0x000001ab, 0x0003003e, 0x000001a2, + 0x000001ac, 0x0004003d, 0x00000172, 0x000001ae, 0x000001a2, 0x0008004f, 0x00000007, 0x000001af, + 0x000001ae, 0x000001ae, 0x00000000, 0x00000001, 0x00000002, 0x00050041, 0x00000009, 0x000001b0, + 0x000001a2, 0x00000174, 0x0004003d, 0x00000006, 0x000001b1, 0x000001b0, 0x0007000c, 0x00000006, + 0x000001b3, 0x00000001, 0x00000028, 0x000001b1, 0x000001b2, 0x00060050, 0x00000007, 0x000001b4, + 0x000001b3, 0x000001b3, 0x000001b3, 0x00050088, 0x00000007, 0x000001b5, 0x000001af, 0x000001b4, + 0x0003003e, 0x000001ad, 0x000001b5, 0x0004003d, 0x00000007, 0x000001b7, 0x000001ad, 0x0007004f, + 0x00000021, 0x000001b8, 0x000001b7, 0x000001b7, 0x00000000, 0x00000001, 0x0005008e, 0x00000021, + 0x000001b9, 0x000001b8, 0x0000011f, 0x00050050, 0x00000021, 0x000001ba, 0x0000011f, 0x0000011f, + 0x00050081, 0x00000021, 0x000001bb, 0x000001b9, 0x000001ba, 0x0003003e, 0x000001b6, 0x000001bb, + 0x00050041, 0x00000009, 0x000001bd, 0x000001ad, 0x000000a9, 0x0004003d, 0x00000006, 0x000001be, + 0x000001bd, 0x0003003e, 0x000001bc, 0x000001be, 0x00050041, 0x00000009, 0x000001bf, 0x000001b6, + 0x00000099, 0x0004003d, 0x00000006, 0x000001c0, 0x000001bf, 0x000500b8, 0x00000097, 0x000001c1, + 0x000001c0, 0x00000047, 0x000400a8, 0x00000097, 0x000001c2, 0x000001c1, 0x000300f7, 0x000001c4, + 0x00000000, 0x000400fa, 0x000001c2, 0x000001c3, 0x000001c4, 0x000200f8, 0x000001c3, 0x00050041, + 0x00000009, 0x000001c5, 0x000001b6, 0x00000099, 0x0004003d, 0x00000006, 0x000001c6, 0x000001c5, + 0x000500ba, 0x00000097, 0x000001c7, 0x000001c6, 0x0000004e, 0x000200f9, 0x000001c4, 0x000200f8, + 0x000001c4, 0x000700f5, 0x00000097, 0x000001c8, 0x000001c1, 0x00000198, 0x000001c7, 0x000001c3, + 0x000400a8, 0x00000097, 0x000001c9, 0x000001c8, 0x000300f7, 0x000001cb, 0x00000000, 0x000400fa, + 0x000001c9, 0x000001ca, 0x000001cb, 0x000200f8, 0x000001ca, 0x00050041, 0x00000009, 0x000001cc, + 0x000001b6, 0x000000a1, 0x0004003d, 0x00000006, 0x000001cd, 0x000001cc, 0x000500b8, 0x00000097, + 0x000001ce, 0x000001cd, 0x00000047, 0x000200f9, 0x000001cb, 0x000200f8, 0x000001cb, 0x000700f5, + 0x00000097, 0x000001cf, 0x000001c8, 0x000001c4, 0x000001ce, 0x000001ca, 0x000400a8, 0x00000097, + 0x000001d0, 0x000001cf, 0x000300f7, 0x000001d2, 0x00000000, 0x000400fa, 0x000001d0, 0x000001d1, + 0x000001d2, 0x000200f8, 0x000001d1, 0x00050041, 0x00000009, 0x000001d3, 0x000001b6, 0x000000a1, + 0x0004003d, 0x00000006, 0x000001d4, 0x000001d3, 0x000500ba, 0x00000097, 0x000001d5, 0x000001d4, + 0x0000004e, 0x000200f9, 0x000001d2, 0x000200f8, 0x000001d2, 0x000700f5, 0x00000097, 0x000001d6, + 0x000001cf, 0x000001cb, 0x000001d5, 0x000001d1, 0x0004003d, 0x00000006, 0x000001d7, 0x000001bc, + 0x000500b8, 0x00000097, 0x000001d8, 0x000001d7, 0x00000047, 0x000500a6, 0x00000097, 0x000001d9, + 0x000001d6, 0x000001d8, 0x0004003d, 0x00000006, 0x000001da, 0x000001bc, 0x000500ba, 0x00000097, + 0x000001db, 0x000001da, 0x0000004e, 0x000500a6, 0x00000097, 0x000001dc, 0x000001d9, 0x000001db, + 0x000300f7, 0x000001de, 0x00000000, 0x000400fa, 0x000001dc, 0x000001dd, 0x000001de, 0x000200f8, + 0x000001dd, 0x000200fe, 0x0000004e, 0x000200f8, 0x000001de, 0x00060041, 0x0000017b, 0x000001e1, + 0x00000178, 0x0000017a, 0x00000099, 0x0004003d, 0x00000006, 0x000001e2, 0x000001e1, 0x0003003e, + 0x000001e0, 0x000001e2, 0x0004003d, 0x000001e5, 0x000001e8, 0x000001e7, 0x00040064, 0x000001e4, + 0x000001e9, 0x000001e8, 0x00050067, 0x000001ea, 0x000001eb, 0x000001e9, 0x00000199, 0x0007004f, + 0x000001ec, 0x000001ed, 0x000001eb, 0x000001eb, 0x00000000, 0x00000001, 0x0004006f, 0x00000021, + 0x000001ee, 0x000001ed, 0x00050050, 0x00000021, 0x000001ef, 0x0000004e, 0x0000004e, 0x00050088, + 0x00000021, 0x000001f0, 0x000001ef, 0x000001ee, 0x0003003e, 0x000001e3, 0x000001f0, 0x0003003e, + 0x000001f1, 0x00000047, 0x0003003e, 0x000001f2, 0x000001f3, 0x000200f9, 0x000001f4, 0x000200f8, + 0x000001f4, 0x000400f6, 0x000001f6, 0x000001f7, 0x00000000, 0x000200f9, 0x000001f8, 0x000200f8, + 0x000001f8, 0x0004003d, 0x00000179, 0x000001f9, 0x000001f2, 0x000500b3, 0x00000097, 0x000001fa, + 0x000001f9, 0x00000189, 0x000400fa, 0x000001fa, 0x000001f5, 0x000001f6, 0x000200f8, 0x000001f5, + 0x0003003e, 0x000001fb, 0x000001f3, 0x000200f9, 0x000001fc, 0x000200f8, 0x000001fc, 0x000400f6, + 0x000001fe, 0x000001ff, 0x00000000, 0x000200f9, 0x00000200, 0x000200f8, 0x00000200, 0x0004003d, + 0x00000179, 0x00000201, 0x000001fb, 0x000500b3, 0x00000097, 0x00000202, 0x00000201, 0x00000189, + 0x000400fa, 0x00000202, 0x000001fd, 0x000001fe, 0x000200f8, 0x000001fd, 0x0004003d, 0x000001e5, + 0x00000204, 0x000001e7, 0x0004003d, 0x00000021, 0x00000205, 0x000001b6, 0x0004003d, 0x00000179, + 0x00000206, 0x000001fb, 0x0004006f, 0x00000006, 0x00000207, 0x00000206, 0x0004003d, 0x00000179, + 0x00000208, 0x000001f2, 0x0004006f, 0x00000006, 0x00000209, 0x00000208, 0x00050050, 0x00000021, + 0x0000020a, 0x00000207, 0x00000209, 0x0004003d, 0x00000021, 0x0000020b, 0x000001e3, 0x00050085, + 0x00000021, 0x0000020c, 0x0000020a, 0x0000020b, 0x00050081, 0x00000021, 0x0000020d, 0x00000205, + 0x0000020c, 0x0004003d, 0x00000179, 0x0000020e, 0x00000192, 0x0004006f, 0x00000006, 0x0000020f, + 0x0000020e, 0x00050051, 0x00000006, 0x00000210, 0x0000020d, 0x00000000, 0x00050051, 0x00000006, + 0x00000211, 0x0000020d, 0x00000001, 0x00060050, 0x00000007, 0x00000212, 0x00000210, 0x00000211, + 0x0000020f, 0x00050057, 0x00000172, 0x00000213, 0x00000204, 0x00000212, 0x00050051, 0x00000006, + 0x00000214, 0x00000213, 0x00000000, 0x0003003e, 0x00000203, 0x00000214, 0x0004003d, 0x00000006, + 0x00000215, 0x000001bc, 0x0004003d, 0x00000006, 0x00000216, 0x000001e0, 0x00050083, 0x00000006, + 0x00000217, 0x00000215, 0x00000216, 0x0004003d, 0x00000006, 0x00000218, 0x00000203, 0x000500ba, + 0x00000097, 0x00000219, 0x00000217, 0x00000218, 0x000600a9, 0x00000006, 0x0000021a, 0x00000219, + 0x00000047, 0x0000004e, 0x0004003d, 0x00000006, 0x0000021b, 0x000001f1, 0x00050081, 0x00000006, + 0x0000021c, 0x0000021b, 0x0000021a, 0x0003003e, 0x000001f1, 0x0000021c, 0x000200f9, 0x000001ff, + 0x000200f8, 0x000001ff, 0x0004003d, 0x00000179, 0x0000021d, 0x000001fb, 0x00050080, 0x00000179, + 0x0000021e, 0x0000021d, 0x00000189, 0x0003003e, 0x000001fb, 0x0000021e, 0x000200f9, 0x000001fc, + 0x000200f8, 0x000001fe, 0x000200f9, 0x000001f7, 0x000200f8, 0x000001f7, 0x0004003d, 0x00000179, + 0x0000021f, 0x000001f2, 0x00050080, 0x00000179, 0x00000220, 0x0000021f, 0x00000189, 0x0003003e, + 0x000001f2, 0x00000220, 0x000200f9, 0x000001f4, 0x000200f8, 0x000001f6, 0x0004003d, 0x00000006, + 0x00000222, 0x000001f1, 0x00050088, 0x00000006, 0x00000224, 0x00000222, 0x00000223, 0x0003003e, + 0x00000221, 0x00000224, 0x0004003d, 0x00000006, 0x00000225, 0x00000221, 0x00060041, 0x0000017b, + 0x00000226, 0x00000178, 0x00000189, 0x00000174, 0x0004003d, 0x00000006, 0x00000227, 0x00000226, + 0x0008000c, 0x00000006, 0x00000228, 0x00000001, 0x0000002b, 0x00000227, 0x00000047, 0x0000004e, + 0x0008000c, 0x00000006, 0x00000229, 0x00000001, 0x0000002e, 0x0000004e, 0x00000225, 0x00000228, + 0x000200fe, 0x00000229, 0x00010038, }; -static const size_t mesh3d_frag_spv_count = 469; +static const size_t mesh3d_frag_spv_count = sizeof(mesh3d_frag_spv)/sizeof(mesh3d_frag_spv[0]); diff --git a/src/modules/graphics/shaders/mesh3d_shadow.frag b/src/modules/graphics/shaders/mesh3d_shadow.frag new file mode 100644 index 00000000..36176b1c --- /dev/null +++ b/src/modules/graphics/shaders/mesh3d_shadow.frag @@ -0,0 +1,3 @@ +#version 450 + +void main() {} diff --git a/src/modules/graphics/shaders/mesh3d_shadow.frag.spv b/src/modules/graphics/shaders/mesh3d_shadow.frag.spv new file mode 100644 index 00000000..f73da9ea Binary files /dev/null and b/src/modules/graphics/shaders/mesh3d_shadow.frag.spv differ diff --git a/src/modules/graphics/shaders/mesh3d_shadow.vert b/src/modules/graphics/shaders/mesh3d_shadow.vert new file mode 100644 index 00000000..c2d0d4ee --- /dev/null +++ b/src/modules/graphics/shaders/mesh3d_shadow.vert @@ -0,0 +1,13 @@ +#version 450 + +layout(location = 0) in vec3 inPos; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec2 inUV; + +layout(push_constant) uniform Push { + mat4 mvp; +} pc; + +void main() { + gl_Position = pc.mvp * vec4(inPos, 1.0); +} diff --git a/src/modules/graphics/shaders/mesh3d_shadow.vert.spv b/src/modules/graphics/shaders/mesh3d_shadow.vert.spv new file mode 100644 index 00000000..0020e733 Binary files /dev/null and b/src/modules/graphics/shaders/mesh3d_shadow.vert.spv differ diff --git a/src/modules/graphics/shaders/mesh3d_shadow_frag_spv.inc b/src/modules/graphics/shaders/mesh3d_shadow_frag_spv.inc new file mode 100644 index 00000000..cdaa6497 --- /dev/null +++ b/src/modules/graphics/shaders/mesh3d_shadow_frag_spv.inc @@ -0,0 +1,11 @@ +static const uint32_t mesh3d_shadow_frag_spv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x00000006, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0005000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 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, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00050036, 0x00000002, + 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x000100fd, 0x00010038, +}; +static const size_t mesh3d_shadow_frag_spv_count = sizeof(mesh3d_shadow_frag_spv)/sizeof(mesh3d_shadow_frag_spv[0]); diff --git a/src/modules/graphics/shaders/mesh3d_shadow_vert_spv.inc b/src/modules/graphics/shaders/mesh3d_shadow_vert_spv.inc new file mode 100644 index 00000000..b7d9eb9d --- /dev/null +++ b/src/modules/graphics/shaders/mesh3d_shadow_vert_spv.inc @@ -0,0 +1,43 @@ +static const uint32_t mesh3d_shadow_vert_spv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x00000027, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0009000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000019, 0x00000023, + 0x00000026, 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, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, + 0x00060006, 0x0000000b, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x0000000b, + 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x0000000b, 0x00000002, + 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00070006, 0x0000000b, 0x00000003, 0x435f6c67, + 0x446c6c75, 0x61747369, 0x0065636e, 0x00030005, 0x0000000d, 0x00000000, 0x00040005, 0x00000011, + 0x68737550, 0x00000000, 0x00040006, 0x00000011, 0x00000000, 0x0070766d, 0x00030005, 0x00000013, + 0x00006370, 0x00040005, 0x00000019, 0x6f506e69, 0x00000073, 0x00050005, 0x00000023, 0x6f4e6e69, + 0x6c616d72, 0x00000000, 0x00040005, 0x00000026, 0x56556e69, 0x00000000, 0x00030047, 0x0000000b, + 0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x0000000b, + 0x00000001, 0x0000000b, 0x00000001, 0x00050048, 0x0000000b, 0x00000002, 0x0000000b, 0x00000003, + 0x00050048, 0x0000000b, 0x00000003, 0x0000000b, 0x00000004, 0x00030047, 0x00000011, 0x00000002, + 0x00040048, 0x00000011, 0x00000000, 0x00000005, 0x00050048, 0x00000011, 0x00000000, 0x00000007, + 0x00000010, 0x00050048, 0x00000011, 0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x00000019, + 0x0000001e, 0x00000000, 0x00040047, 0x00000023, 0x0000001e, 0x00000001, 0x00040047, 0x00000026, + 0x0000001e, 0x00000002, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, + 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040015, 0x00000008, + 0x00000020, 0x00000000, 0x0004002b, 0x00000008, 0x00000009, 0x00000001, 0x0004001c, 0x0000000a, + 0x00000006, 0x00000009, 0x0006001e, 0x0000000b, 0x00000007, 0x00000006, 0x0000000a, 0x0000000a, + 0x00040020, 0x0000000c, 0x00000003, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000003, + 0x00040015, 0x0000000e, 0x00000020, 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, + 0x00040018, 0x00000010, 0x00000007, 0x00000004, 0x0003001e, 0x00000011, 0x00000010, 0x00040020, + 0x00000012, 0x00000009, 0x00000011, 0x0004003b, 0x00000012, 0x00000013, 0x00000009, 0x00040020, + 0x00000014, 0x00000009, 0x00000010, 0x00040017, 0x00000017, 0x00000006, 0x00000003, 0x00040020, + 0x00000018, 0x00000001, 0x00000017, 0x0004003b, 0x00000018, 0x00000019, 0x00000001, 0x0004002b, + 0x00000006, 0x0000001b, 0x3f800000, 0x00040020, 0x00000021, 0x00000003, 0x00000007, 0x0004003b, + 0x00000018, 0x00000023, 0x00000001, 0x00040017, 0x00000024, 0x00000006, 0x00000002, 0x00040020, + 0x00000025, 0x00000001, 0x00000024, 0x0004003b, 0x00000025, 0x00000026, 0x00000001, 0x00050036, + 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x00000014, + 0x00000015, 0x00000013, 0x0000000f, 0x0004003d, 0x00000010, 0x00000016, 0x00000015, 0x0004003d, + 0x00000017, 0x0000001a, 0x00000019, 0x00050051, 0x00000006, 0x0000001c, 0x0000001a, 0x00000000, + 0x00050051, 0x00000006, 0x0000001d, 0x0000001a, 0x00000001, 0x00050051, 0x00000006, 0x0000001e, + 0x0000001a, 0x00000002, 0x00070050, 0x00000007, 0x0000001f, 0x0000001c, 0x0000001d, 0x0000001e, + 0x0000001b, 0x00050091, 0x00000007, 0x00000020, 0x00000016, 0x0000001f, 0x00050041, 0x00000021, + 0x00000022, 0x0000000d, 0x0000000f, 0x0003003e, 0x00000022, 0x00000020, 0x000100fd, 0x00010038, +}; +static const size_t mesh3d_shadow_vert_spv_count = sizeof(mesh3d_shadow_vert_spv)/sizeof(mesh3d_shadow_vert_spv[0]); diff --git a/src/modules/graphics/shaders/mesh3d_vert_spv.inc b/src/modules/graphics/shaders/mesh3d_vert_spv.inc index 4972881e..ae9cfa9b 100644 --- a/src/modules/graphics/shaders/mesh3d_vert_spv.inc +++ b/src/modules/graphics/shaders/mesh3d_vert_spv.inc @@ -1,8 +1,8 @@ static const uint32_t mesh3d_vert_spv[] = { - 0x07230203, 0x00010000, 0x000d000b, 0x0000004d, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x07230203, 0x00010000, 0x000d000b, 0x00000057, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, - 0x000e000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000019, 0x00000031, - 0x00000033, 0x00000039, 0x0000003b, 0x0000003d, 0x00000042, 0x00000048, 0x00030003, 0x00000002, + 0x000e000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x0000001c, 0x00000032, + 0x00000041, 0x00000043, 0x00000049, 0x0000004b, 0x0000004d, 0x00000052, 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, @@ -10,74 +10,89 @@ static const uint32_t mesh3d_vert_spv[] = { 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x0000000b, 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x0000000b, 0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00070006, 0x0000000b, 0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, - 0x00030005, 0x0000000d, 0x00000000, 0x00040005, 0x00000011, 0x6d617246, 0x00000065, 0x00040006, - 0x00000011, 0x00000000, 0x0070766d, 0x00050006, 0x00000011, 0x00000001, 0x65646f6d, 0x0000006c, - 0x00080006, 0x00000011, 0x00000002, 0x6867696c, 0x72694474, 0x65746e49, 0x7469736e, 0x00000079, - 0x00060006, 0x00000011, 0x00000003, 0x6867696c, 0x6c6f4374, 0x0000726f, 0x00050006, 0x00000011, - 0x00000004, 0x746e6974, 0x00000000, 0x00030005, 0x00000013, 0x006f6275, 0x00040005, 0x00000019, - 0x6f506e69, 0x00000073, 0x00050005, 0x00000025, 0x6d726f6e, 0x614d6c61, 0x00000074, 0x00040005, - 0x00000031, 0x726f4e76, 0x006c616d, 0x00050005, 0x00000033, 0x6f4e6e69, 0x6c616d72, 0x00000000, - 0x00030005, 0x00000039, 0x00565576, 0x00040005, 0x0000003b, 0x56556e69, 0x00000000, 0x00040005, - 0x0000003d, 0x6e695476, 0x00000074, 0x00050005, 0x00000042, 0x67694c76, 0x69447468, 0x00000072, - 0x00050005, 0x00000048, 0x67694c76, 0x6f437468, 0x00726f6c, 0x00030047, 0x0000000b, 0x00000002, - 0x00050048, 0x0000000b, 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x0000000b, 0x00000001, - 0x0000000b, 0x00000001, 0x00050048, 0x0000000b, 0x00000002, 0x0000000b, 0x00000003, 0x00050048, - 0x0000000b, 0x00000003, 0x0000000b, 0x00000004, 0x00030047, 0x00000011, 0x00000002, 0x00040048, - 0x00000011, 0x00000000, 0x00000005, 0x00050048, 0x00000011, 0x00000000, 0x00000007, 0x00000010, - 0x00050048, 0x00000011, 0x00000000, 0x00000023, 0x00000000, 0x00040048, 0x00000011, 0x00000001, - 0x00000005, 0x00050048, 0x00000011, 0x00000001, 0x00000007, 0x00000010, 0x00050048, 0x00000011, - 0x00000001, 0x00000023, 0x00000040, 0x00050048, 0x00000011, 0x00000002, 0x00000023, 0x00000080, - 0x00050048, 0x00000011, 0x00000003, 0x00000023, 0x00000090, 0x00050048, 0x00000011, 0x00000004, - 0x00000023, 0x000000a0, 0x00040047, 0x00000013, 0x00000021, 0x00000000, 0x00040047, 0x00000013, - 0x00000022, 0x00000000, 0x00040047, 0x00000019, 0x0000001e, 0x00000000, 0x00040047, 0x00000031, - 0x0000001e, 0x00000000, 0x00040047, 0x00000033, 0x0000001e, 0x00000001, 0x00040047, 0x00000039, - 0x0000001e, 0x00000001, 0x00040047, 0x0000003b, 0x0000001e, 0x00000002, 0x00040047, 0x0000003d, - 0x0000001e, 0x00000002, 0x00040047, 0x00000042, 0x0000001e, 0x00000003, 0x00040047, 0x00000048, + 0x00030005, 0x0000000d, 0x00000000, 0x00040005, 0x00000011, 0x6867694c, 0x00443374, 0x00060006, + 0x00000011, 0x00000000, 0x52736f70, 0x75696461, 0x00000073, 0x00050006, 0x00000011, 0x00000001, + 0x6f6c6f63, 0x00000072, 0x00040005, 0x00000014, 0x6d617246, 0x00000065, 0x00040006, 0x00000014, + 0x00000000, 0x0070766d, 0x00050006, 0x00000014, 0x00000001, 0x65646f6d, 0x0000006c, 0x00080006, + 0x00000014, 0x00000002, 0x6867696c, 0x72694474, 0x65746e49, 0x7469736e, 0x00000079, 0x00060006, + 0x00000014, 0x00000003, 0x6867696c, 0x6c6f4374, 0x0000726f, 0x00050006, 0x00000014, 0x00000004, + 0x746e6974, 0x00000000, 0x00060006, 0x00000014, 0x00000005, 0x656d6163, 0x6f506172, 0x00000073, + 0x00050006, 0x00000014, 0x00000006, 0x69626d61, 0x00746e65, 0x00050006, 0x00000014, 0x00000007, + 0x6867696c, 0x00007374, 0x00030005, 0x00000016, 0x006f6275, 0x00040005, 0x0000001c, 0x6f506e69, + 0x00000073, 0x00040005, 0x00000027, 0x6c726f77, 0x00000064, 0x00050005, 0x00000032, 0x726f5776, + 0x6f50646c, 0x00000073, 0x00050005, 0x00000037, 0x6d726f6e, 0x614d6c61, 0x00000074, 0x00040005, + 0x00000041, 0x726f4e76, 0x006c616d, 0x00050005, 0x00000043, 0x6f4e6e69, 0x6c616d72, 0x00000000, + 0x00030005, 0x00000049, 0x00565576, 0x00040005, 0x0000004b, 0x56556e69, 0x00000000, 0x00040005, + 0x0000004d, 0x6e695476, 0x00000074, 0x00050005, 0x00000052, 0x6d614376, 0x50617265, 0x0000736f, + 0x00030047, 0x0000000b, 0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x0000000b, 0x00000000, + 0x00050048, 0x0000000b, 0x00000001, 0x0000000b, 0x00000001, 0x00050048, 0x0000000b, 0x00000002, + 0x0000000b, 0x00000003, 0x00050048, 0x0000000b, 0x00000003, 0x0000000b, 0x00000004, 0x00050048, + 0x00000011, 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000011, 0x00000001, 0x00000023, + 0x00000010, 0x00040047, 0x00000013, 0x00000006, 0x00000020, 0x00030047, 0x00000014, 0x00000002, + 0x00040048, 0x00000014, 0x00000000, 0x00000005, 0x00050048, 0x00000014, 0x00000000, 0x00000007, + 0x00000010, 0x00050048, 0x00000014, 0x00000000, 0x00000023, 0x00000000, 0x00040048, 0x00000014, + 0x00000001, 0x00000005, 0x00050048, 0x00000014, 0x00000001, 0x00000007, 0x00000010, 0x00050048, + 0x00000014, 0x00000001, 0x00000023, 0x00000040, 0x00050048, 0x00000014, 0x00000002, 0x00000023, + 0x00000080, 0x00050048, 0x00000014, 0x00000003, 0x00000023, 0x00000090, 0x00050048, 0x00000014, + 0x00000004, 0x00000023, 0x000000a0, 0x00050048, 0x00000014, 0x00000005, 0x00000023, 0x000000b0, + 0x00050048, 0x00000014, 0x00000006, 0x00000023, 0x000000c0, 0x00050048, 0x00000014, 0x00000007, + 0x00000023, 0x000000d0, 0x00040047, 0x00000016, 0x00000021, 0x00000000, 0x00040047, 0x00000016, + 0x00000022, 0x00000000, 0x00040047, 0x0000001c, 0x0000001e, 0x00000000, 0x00040047, 0x00000032, + 0x0000001e, 0x00000003, 0x00040047, 0x00000041, 0x0000001e, 0x00000000, 0x00040047, 0x00000043, + 0x0000001e, 0x00000001, 0x00040047, 0x00000049, 0x0000001e, 0x00000001, 0x00040047, 0x0000004b, + 0x0000001e, 0x00000002, 0x00040047, 0x0000004d, 0x0000001e, 0x00000002, 0x00040047, 0x00000052, 0x0000001e, 0x00000004, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040015, 0x00000008, 0x00000020, 0x00000000, 0x0004002b, 0x00000008, 0x00000009, 0x00000001, 0x0004001c, 0x0000000a, 0x00000006, 0x00000009, 0x0006001e, 0x0000000b, 0x00000007, 0x00000006, 0x0000000a, 0x0000000a, 0x00040020, 0x0000000c, 0x00000003, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000003, 0x00040015, 0x0000000e, 0x00000020, 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, - 0x00040018, 0x00000010, 0x00000007, 0x00000004, 0x0007001e, 0x00000011, 0x00000010, 0x00000010, - 0x00000007, 0x00000007, 0x00000007, 0x00040020, 0x00000012, 0x00000002, 0x00000011, 0x0004003b, - 0x00000012, 0x00000013, 0x00000002, 0x00040020, 0x00000014, 0x00000002, 0x00000010, 0x00040017, - 0x00000017, 0x00000006, 0x00000003, 0x00040020, 0x00000018, 0x00000001, 0x00000017, 0x0004003b, - 0x00000018, 0x00000019, 0x00000001, 0x0004002b, 0x00000006, 0x0000001b, 0x3f800000, 0x00040020, - 0x00000021, 0x00000003, 0x00000007, 0x00040018, 0x00000023, 0x00000017, 0x00000003, 0x00040020, - 0x00000024, 0x00000007, 0x00000023, 0x0004002b, 0x0000000e, 0x00000026, 0x00000001, 0x00040020, - 0x00000030, 0x00000003, 0x00000017, 0x0004003b, 0x00000030, 0x00000031, 0x00000003, 0x0004003b, - 0x00000018, 0x00000033, 0x00000001, 0x00040017, 0x00000037, 0x00000006, 0x00000002, 0x00040020, - 0x00000038, 0x00000003, 0x00000037, 0x0004003b, 0x00000038, 0x00000039, 0x00000003, 0x00040020, - 0x0000003a, 0x00000001, 0x00000037, 0x0004003b, 0x0000003a, 0x0000003b, 0x00000001, 0x0004003b, - 0x00000021, 0x0000003d, 0x00000003, 0x0004002b, 0x0000000e, 0x0000003e, 0x00000004, 0x00040020, - 0x0000003f, 0x00000002, 0x00000007, 0x0004003b, 0x00000030, 0x00000042, 0x00000003, 0x0004002b, - 0x0000000e, 0x00000043, 0x00000002, 0x0004003b, 0x00000030, 0x00000048, 0x00000003, 0x0004002b, - 0x0000000e, 0x00000049, 0x00000003, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, - 0x000200f8, 0x00000005, 0x0004003b, 0x00000024, 0x00000025, 0x00000007, 0x00050041, 0x00000014, - 0x00000015, 0x00000013, 0x0000000f, 0x0004003d, 0x00000010, 0x00000016, 0x00000015, 0x0004003d, - 0x00000017, 0x0000001a, 0x00000019, 0x00050051, 0x00000006, 0x0000001c, 0x0000001a, 0x00000000, - 0x00050051, 0x00000006, 0x0000001d, 0x0000001a, 0x00000001, 0x00050051, 0x00000006, 0x0000001e, - 0x0000001a, 0x00000002, 0x00070050, 0x00000007, 0x0000001f, 0x0000001c, 0x0000001d, 0x0000001e, - 0x0000001b, 0x00050091, 0x00000007, 0x00000020, 0x00000016, 0x0000001f, 0x00050041, 0x00000021, - 0x00000022, 0x0000000d, 0x0000000f, 0x0003003e, 0x00000022, 0x00000020, 0x00050041, 0x00000014, - 0x00000027, 0x00000013, 0x00000026, 0x0004003d, 0x00000010, 0x00000028, 0x00000027, 0x00050051, - 0x00000007, 0x00000029, 0x00000028, 0x00000000, 0x0008004f, 0x00000017, 0x0000002a, 0x00000029, - 0x00000029, 0x00000000, 0x00000001, 0x00000002, 0x00050051, 0x00000007, 0x0000002b, 0x00000028, - 0x00000001, 0x0008004f, 0x00000017, 0x0000002c, 0x0000002b, 0x0000002b, 0x00000000, 0x00000001, - 0x00000002, 0x00050051, 0x00000007, 0x0000002d, 0x00000028, 0x00000002, 0x0008004f, 0x00000017, - 0x0000002e, 0x0000002d, 0x0000002d, 0x00000000, 0x00000001, 0x00000002, 0x00060050, 0x00000023, - 0x0000002f, 0x0000002a, 0x0000002c, 0x0000002e, 0x0003003e, 0x00000025, 0x0000002f, 0x0004003d, - 0x00000023, 0x00000032, 0x00000025, 0x0004003d, 0x00000017, 0x00000034, 0x00000033, 0x00050091, - 0x00000017, 0x00000035, 0x00000032, 0x00000034, 0x0006000c, 0x00000017, 0x00000036, 0x00000001, - 0x00000045, 0x00000035, 0x0003003e, 0x00000031, 0x00000036, 0x0004003d, 0x00000037, 0x0000003c, - 0x0000003b, 0x0003003e, 0x00000039, 0x0000003c, 0x00050041, 0x0000003f, 0x00000040, 0x00000013, - 0x0000003e, 0x0004003d, 0x00000007, 0x00000041, 0x00000040, 0x0003003e, 0x0000003d, 0x00000041, - 0x00050041, 0x0000003f, 0x00000044, 0x00000013, 0x00000043, 0x0004003d, 0x00000007, 0x00000045, - 0x00000044, 0x0008004f, 0x00000017, 0x00000046, 0x00000045, 0x00000045, 0x00000000, 0x00000001, - 0x00000002, 0x0006000c, 0x00000017, 0x00000047, 0x00000001, 0x00000045, 0x00000046, 0x0003003e, - 0x00000042, 0x00000047, 0x00050041, 0x0000003f, 0x0000004a, 0x00000013, 0x00000049, 0x0004003d, - 0x00000007, 0x0000004b, 0x0000004a, 0x0008004f, 0x00000017, 0x0000004c, 0x0000004b, 0x0000004b, - 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x00000048, 0x0000004c, 0x000100fd, 0x00010038 + 0x00040018, 0x00000010, 0x00000007, 0x00000004, 0x0004001e, 0x00000011, 0x00000007, 0x00000007, + 0x0004002b, 0x00000008, 0x00000012, 0x00000008, 0x0004001c, 0x00000013, 0x00000011, 0x00000012, + 0x000a001e, 0x00000014, 0x00000010, 0x00000010, 0x00000007, 0x00000007, 0x00000007, 0x00000007, + 0x00000007, 0x00000013, 0x00040020, 0x00000015, 0x00000002, 0x00000014, 0x0004003b, 0x00000015, + 0x00000016, 0x00000002, 0x00040020, 0x00000017, 0x00000002, 0x00000010, 0x00040017, 0x0000001a, + 0x00000006, 0x00000003, 0x00040020, 0x0000001b, 0x00000001, 0x0000001a, 0x0004003b, 0x0000001b, + 0x0000001c, 0x00000001, 0x0004002b, 0x00000006, 0x0000001e, 0x3f800000, 0x00040020, 0x00000024, + 0x00000003, 0x00000007, 0x00040020, 0x00000026, 0x00000007, 0x00000007, 0x0004002b, 0x0000000e, + 0x00000028, 0x00000001, 0x00040020, 0x00000031, 0x00000003, 0x0000001a, 0x0004003b, 0x00000031, + 0x00000032, 0x00000003, 0x00040018, 0x00000035, 0x0000001a, 0x00000003, 0x00040020, 0x00000036, + 0x00000007, 0x00000035, 0x0004003b, 0x00000031, 0x00000041, 0x00000003, 0x0004003b, 0x0000001b, + 0x00000043, 0x00000001, 0x00040017, 0x00000047, 0x00000006, 0x00000002, 0x00040020, 0x00000048, + 0x00000003, 0x00000047, 0x0004003b, 0x00000048, 0x00000049, 0x00000003, 0x00040020, 0x0000004a, + 0x00000001, 0x00000047, 0x0004003b, 0x0000004a, 0x0000004b, 0x00000001, 0x0004003b, 0x00000024, + 0x0000004d, 0x00000003, 0x0004002b, 0x0000000e, 0x0000004e, 0x00000004, 0x00040020, 0x0000004f, + 0x00000002, 0x00000007, 0x0004003b, 0x00000031, 0x00000052, 0x00000003, 0x0004002b, 0x0000000e, + 0x00000053, 0x00000005, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, + 0x00000005, 0x0004003b, 0x00000026, 0x00000027, 0x00000007, 0x0004003b, 0x00000036, 0x00000037, + 0x00000007, 0x00050041, 0x00000017, 0x00000018, 0x00000016, 0x0000000f, 0x0004003d, 0x00000010, + 0x00000019, 0x00000018, 0x0004003d, 0x0000001a, 0x0000001d, 0x0000001c, 0x00050051, 0x00000006, + 0x0000001f, 0x0000001d, 0x00000000, 0x00050051, 0x00000006, 0x00000020, 0x0000001d, 0x00000001, + 0x00050051, 0x00000006, 0x00000021, 0x0000001d, 0x00000002, 0x00070050, 0x00000007, 0x00000022, + 0x0000001f, 0x00000020, 0x00000021, 0x0000001e, 0x00050091, 0x00000007, 0x00000023, 0x00000019, + 0x00000022, 0x00050041, 0x00000024, 0x00000025, 0x0000000d, 0x0000000f, 0x0003003e, 0x00000025, + 0x00000023, 0x00050041, 0x00000017, 0x00000029, 0x00000016, 0x00000028, 0x0004003d, 0x00000010, + 0x0000002a, 0x00000029, 0x0004003d, 0x0000001a, 0x0000002b, 0x0000001c, 0x00050051, 0x00000006, + 0x0000002c, 0x0000002b, 0x00000000, 0x00050051, 0x00000006, 0x0000002d, 0x0000002b, 0x00000001, + 0x00050051, 0x00000006, 0x0000002e, 0x0000002b, 0x00000002, 0x00070050, 0x00000007, 0x0000002f, + 0x0000002c, 0x0000002d, 0x0000002e, 0x0000001e, 0x00050091, 0x00000007, 0x00000030, 0x0000002a, + 0x0000002f, 0x0003003e, 0x00000027, 0x00000030, 0x0004003d, 0x00000007, 0x00000033, 0x00000027, + 0x0008004f, 0x0000001a, 0x00000034, 0x00000033, 0x00000033, 0x00000000, 0x00000001, 0x00000002, + 0x0003003e, 0x00000032, 0x00000034, 0x00050041, 0x00000017, 0x00000038, 0x00000016, 0x00000028, + 0x0004003d, 0x00000010, 0x00000039, 0x00000038, 0x00050051, 0x00000007, 0x0000003a, 0x00000039, + 0x00000000, 0x0008004f, 0x0000001a, 0x0000003b, 0x0000003a, 0x0000003a, 0x00000000, 0x00000001, + 0x00000002, 0x00050051, 0x00000007, 0x0000003c, 0x00000039, 0x00000001, 0x0008004f, 0x0000001a, + 0x0000003d, 0x0000003c, 0x0000003c, 0x00000000, 0x00000001, 0x00000002, 0x00050051, 0x00000007, + 0x0000003e, 0x00000039, 0x00000002, 0x0008004f, 0x0000001a, 0x0000003f, 0x0000003e, 0x0000003e, + 0x00000000, 0x00000001, 0x00000002, 0x00060050, 0x00000035, 0x00000040, 0x0000003b, 0x0000003d, + 0x0000003f, 0x0003003e, 0x00000037, 0x00000040, 0x0004003d, 0x00000035, 0x00000042, 0x00000037, + 0x0004003d, 0x0000001a, 0x00000044, 0x00000043, 0x00050091, 0x0000001a, 0x00000045, 0x00000042, + 0x00000044, 0x0006000c, 0x0000001a, 0x00000046, 0x00000001, 0x00000045, 0x00000045, 0x0003003e, + 0x00000041, 0x00000046, 0x0004003d, 0x00000047, 0x0000004c, 0x0000004b, 0x0003003e, 0x00000049, + 0x0000004c, 0x00050041, 0x0000004f, 0x00000050, 0x00000016, 0x0000004e, 0x0004003d, 0x00000007, + 0x00000051, 0x00000050, 0x0003003e, 0x0000004d, 0x00000051, 0x00050041, 0x0000004f, 0x00000054, + 0x00000016, 0x00000053, 0x0004003d, 0x00000007, 0x00000055, 0x00000054, 0x0008004f, 0x0000001a, + 0x00000056, 0x00000055, 0x00000055, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x00000052, + 0x00000056, 0x000100fd, 0x00010038, }; -static const size_t mesh3d_vert_spv_count = 640; +static const size_t mesh3d_vert_spv_count = sizeof(mesh3d_vert_spv)/sizeof(mesh3d_vert_spv[0]); diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 35bdc44f..2d2e3321 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -1,6 +1,7 @@ #define VKB_IMPL #include "graphics/vulkan/Graphics.h" #include "graphics/vulkan/Canvas.h" +#include "graphics/Light.h" #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #include #include #if !defined(_WIN32) @@ -31,6 +33,12 @@ #include "graphics/shaders/textured_frag_spv.inc" #include "graphics/shaders/mesh3d_vert_spv.inc" #include "graphics/shaders/mesh3d_frag_spv.inc" +#include "graphics/shaders/mesh3d_clustered_vert_spv.inc" +#include "graphics/shaders/mesh3d_clustered_frag_spv.inc" +#include "graphics/shaders/mesh3d_shadow_vert_spv.inc" +#include "graphics/shaders/mesh3d_shadow_frag_spv.inc" +#include "graphics/shaders/lit2d_vert_spv.inc" +#include "graphics/shaders/lit2d_frag_spv.inc" #include #include @@ -68,6 +76,34 @@ Graphics::~Graphics() { if (mesh3dShaderPipelineLayout) device->destroyPipelineLayout(mesh3dShaderPipelineLayout); mesh3dSetLayoutUnique.reset(); mesh3dUboSlots.clear(); + if (mesh3dClusteredPipeline) device->destroyPipeline(mesh3dClusteredPipeline); + if (mesh3dClusteredPipelineLayout) device->destroyPipelineLayout(mesh3dClusteredPipelineLayout); + mesh3dClusteredSetLayoutUnique.reset(); + mesh3dClusteredUboSlots.clear(); + destroyShadowResources(); + auto destroyBuf = [&](vkb::GenericBuffer &b) { + if (b.buffer) { + device->destroyBuffer(b.buffer, device.allocation_callbacks); + device->freeMemory(b.memory, device.allocation_callbacks); + b.buffer = vk::Buffer{}; + b.memory = vk::DeviceMemory{}; + } + }; + destroyBuf(clusteredLightsBuf); + destroyBuf(clusteredTableBuf); + destroyBuf(clusteredIndicesBuf); + clusteredLightsCap = clusteredTableCap = clusteredIndicesCap = 0; + if (lit2dPipeline) device->destroyPipeline(lit2dPipeline); + if (offscreenLitPipeline) device->destroyPipeline(offscreenLitPipeline); + if (lit2dPipelineLayout) device->destroyPipelineLayout(lit2dPipelineLayout); + lit2dSetLayoutUnique.reset(); + lit2dSets.clear(); + if (lighting2dUbo.buffer) { + device->destroyBuffer(lighting2dUbo.buffer, device.allocation_callbacks); + device->freeMemory(lighting2dUbo.memory, device.allocation_callbacks); + lighting2dUbo.buffer = vk::Buffer{}; + lighting2dUbo.memory = vk::DeviceMemory{}; + } if (offscreenSolidPipeline) device->destroyPipeline(offscreenSolidPipeline); if (offscreenTexPipeline) device->destroyPipeline(offscreenTexPipeline); if (offscreenRenderPass) device->destroyRenderPass(offscreenRenderPass); @@ -147,7 +183,12 @@ void Graphics::initWithWindow(void *nativeWindow) { createSwapchainAndPipeline(); createTexturedPipeline(); createMesh3DPipeline(); + createMesh3DClusteredPipeline(); + // Must be set before createShadowResources(): it clears the shadow cascade + // layers by calling beginShadowPass()/endShadowPass(), and beginShadowPass() + // asserts `initialized` is already true. initialized = true; + createShadowResources(); const uint8_t whitePixel[4] = {255, 255, 255, 255}; whiteTexture = newTexture(1, 1, whitePixel); } @@ -343,12 +384,13 @@ void Graphics::createTexturedPipeline() { texSetLayout = *texSetLayoutUnique; vk::DescriptorPoolSize poolSizes[] = { - {vk::DescriptorType::eCombinedImageSampler, 128}, - {vk::DescriptorType::eUniformBuffer, 64}, + {vk::DescriptorType::eCombinedImageSampler, 1536}, + {vk::DescriptorType::eUniformBuffer, 512}, + {vk::DescriptorType::eStorageBuffer, 128}, }; vk::DescriptorPoolCreateInfo poolInfo{}; - poolInfo.maxSets = 160; - poolInfo.poolSizeCount = 2; + poolInfo.maxSets = 1024; + poolInfo.poolSizeCount = 3; poolInfo.pPoolSizes = poolSizes; descriptorPool = device->createDescriptorPool(poolInfo); @@ -371,6 +413,8 @@ void Graphics::createTexturedPipeline() { std::vector vert(textured_vert_spv, textured_vert_spv + textured_vert_spv_count); std::vector frag(textured_frag_spv, textured_frag_spv + textured_frag_spv_count); texPipeline = createTexturedStylePipeline(vert, frag, renderpass, texPipelineLayout); + + createLit2DPipeline(); } vk::Pipeline Graphics::createTexturedStylePipeline(const std::vector &vert, @@ -460,6 +504,32 @@ vk::Pipeline Graphics::createTexturedStylePipeline(const std::vector & return result.value; } +void Graphics::createLit2DPipeline() { + if (lit2dPipeline) return; + + vkb::DescriptorSetLayoutBuilder layoutBuilder; + lit2dSetLayoutUnique = + layoutBuilder + .image(0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1) + .image(1, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1) + .buffer(2, vk::DescriptorType::eUniformBuffer, vk::ShaderStageFlagBits::eFragment, 1) + .createUnique(device.instance); + lit2dSetLayout = *lit2dSetLayoutUnique; + + vk::PipelineLayoutCreateInfo plInfo{}; + plInfo.setLayoutCount = 1; + plInfo.pSetLayouts = &lit2dSetLayout; + lit2dPipelineLayout = device->createPipelineLayout(plInfo); + + lighting2dUbo.allocate(device, vk::BufferUsageFlagBits::eUniformBuffer, sizeof(Lighting2DUBO), + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent); + + std::vector vert(lit2d_vert_spv, lit2d_vert_spv + lit2d_vert_spv_count); + std::vector frag(lit2d_frag_spv, lit2d_frag_spv + lit2d_frag_spv_count); + lit2dPipeline = createTexturedStylePipeline(vert, frag, renderpass, lit2dPipelineLayout); +} + void Graphics::createMesh3DPipeline() { if (mesh3dPipeline) return; @@ -470,6 +540,10 @@ void Graphics::createMesh3DPipeline() { .buffer(0, vk::DescriptorType::eUniformBuffer, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 1) .image(1, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1) + .image(2, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1) + .image(3, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1) + .buffer(4, vk::DescriptorType::eUniformBuffer, vk::ShaderStageFlagBits::eFragment, 1) + .image(5, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1) .createUnique(device.instance); mesh3dSetLayout = *mesh3dSetLayoutUnique; @@ -498,6 +572,382 @@ void Graphics::createMesh3DPipeline() { mesh3dPipeline = createMesh3DStylePipeline(vert, frag, mesh3dPipelineLayout); } +void Graphics::createMesh3DClusteredPipeline() { + if (mesh3dClusteredPipeline) return; + + vkb::DescriptorSetLayoutBuilder layoutBuilder; + mesh3dClusteredSetLayoutUnique = + layoutBuilder + .buffer(0, vk::DescriptorType::eUniformBuffer, + vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 1) + .image(1, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1) + .image(2, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1) + .image(3, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1) + .buffer(4, vk::DescriptorType::eStorageBuffer, vk::ShaderStageFlagBits::eFragment, 1) + .buffer(5, vk::DescriptorType::eStorageBuffer, vk::ShaderStageFlagBits::eFragment, 1) + .buffer(6, vk::DescriptorType::eStorageBuffer, vk::ShaderStageFlagBits::eFragment, 1) + .buffer(7, vk::DescriptorType::eUniformBuffer, vk::ShaderStageFlagBits::eFragment, 1) + .image(8, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1) + .createUnique(device.instance); + mesh3dClusteredSetLayout = *mesh3dClusteredSetLayoutUnique; + + vk::PipelineLayoutCreateInfo plInfo{}; + plInfo.setLayoutCount = 1; + plInfo.pSetLayouts = &mesh3dClusteredSetLayout; + mesh3dClusteredPipelineLayout = device->createPipelineLayout(plInfo); + + mesh3dClusteredUboSlots.clear(); + mesh3dClusteredDrawIndex = 0; + + std::vector vert(mesh3d_clustered_vert_spv, + mesh3d_clustered_vert_spv + mesh3d_clustered_vert_spv_count); + std::vector frag(mesh3d_clustered_frag_spv, + mesh3d_clustered_frag_spv + mesh3d_clustered_frag_spv_count); + mesh3dClusteredPipeline = createMesh3DStylePipeline(vert, frag, mesh3dClusteredPipelineLayout); +} + +void Graphics::destroyShadowResources() { + if (shadowPipeline) { + device->destroyPipeline(shadowPipeline); + shadowPipeline = vk::Pipeline{}; + } + if (shadowPipelineLayout) { + device->destroyPipelineLayout(shadowPipelineLayout); + shadowPipelineLayout = vk::PipelineLayout{}; + } + for (int i = 0; i < ShadowConfig::kCascades; ++i) { + if (shadowFramebuffers[i]) { + device->destroyFramebuffer(shadowFramebuffers[i]); + shadowFramebuffers[i] = vk::Framebuffer{}; + } + if (shadowLayerViews[i]) { + device->destroyImageView(shadowLayerViews[i]); + shadowLayerViews[i] = vk::ImageView{}; + } + } + if (shadowArrayView) { + device->destroyImageView(shadowArrayView); + shadowArrayView = vk::ImageView{}; + } + if (shadowSampler) { + device->destroySampler(shadowSampler); + shadowSampler = vk::Sampler{}; + } + if (shadowImage) { + device->destroyImage(shadowImage); + shadowImage = vk::Image{}; + } + if (shadowMemory) { + device->freeMemory(shadowMemory); + shadowMemory = vk::DeviceMemory{}; + } + if (shadowRenderPass) { + device->destroyRenderPass(shadowRenderPass); + shadowRenderPass = vk::RenderPass{}; + } + shadowPassCascade = -1; + shadowPassDraws.clear(); +} + +void Graphics::createShadowResources() { + if (shadowImage) return; + + const uint32_t size = uint32_t(ShadowConfig::kMapSize); + const uint32_t layers = uint32_t(ShadowConfig::kCascades); + + vk::ImageCreateInfo imgInfo{}; + imgInfo.imageType = vk::ImageType::e2D; + imgInfo.format = vk::Format::eD32Sfloat; + imgInfo.extent = vk::Extent3D{size, size, 1}; + imgInfo.mipLevels = 1; + imgInfo.arrayLayers = layers; + imgInfo.samples = vk::SampleCountFlagBits::e1; + imgInfo.tiling = vk::ImageTiling::eOptimal; + imgInfo.usage = + vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; + imgInfo.initialLayout = vk::ImageLayout::eUndefined; + shadowImage = device->createImage(imgInfo); + + auto memReq = device->getImageMemoryRequirements(shadowImage); + vk::MemoryAllocateInfo mai{}; + mai.allocationSize = memReq.size; + mai.memoryTypeIndex = device.physical_device.findMemoryTypeIndex( + memReq.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal); + shadowMemory = device->allocateMemory(mai); + device->bindImageMemory(shadowImage, shadowMemory, 0); + + vk::ImageViewCreateInfo arrayViewInfo{}; + arrayViewInfo.image = shadowImage; + arrayViewInfo.viewType = vk::ImageViewType::e2DArray; + arrayViewInfo.format = vk::Format::eD32Sfloat; + arrayViewInfo.subresourceRange = {vk::ImageAspectFlagBits::eDepth, 0, 1, 0, layers}; + shadowArrayView = device->createImageView(arrayViewInfo); + + for (uint32_t i = 0; i < layers; ++i) { + vk::ImageViewCreateInfo layerInfo{}; + layerInfo.image = shadowImage; + layerInfo.viewType = vk::ImageViewType::e2D; + layerInfo.format = vk::Format::eD32Sfloat; + layerInfo.subresourceRange = {vk::ImageAspectFlagBits::eDepth, 0, 1, i, 1}; + shadowLayerViews[i] = device->createImageView(layerInfo); + } + + vkb::SamplerBuilder sb; + shadowSampler = sb.magFilter(vk::Filter::eLinear) + .minFilter(vk::Filter::eLinear) + .addressModeU(vk::SamplerAddressMode::eClampToEdge) + .addressModeV(vk::SamplerAddressMode::eClampToEdge) + .addressModeW(vk::SamplerAddressMode::eClampToEdge) + .build(device.instance); + + vk::AttachmentDescription depthAtt{}; + depthAtt.format = vk::Format::eD32Sfloat; + depthAtt.samples = vk::SampleCountFlagBits::e1; + depthAtt.loadOp = vk::AttachmentLoadOp::eClear; + depthAtt.storeOp = vk::AttachmentStoreOp::eStore; + depthAtt.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; + depthAtt.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; + depthAtt.initialLayout = vk::ImageLayout::eUndefined; + depthAtt.finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + + vk::AttachmentReference depthRef{}; + depthRef.attachment = 0; + depthRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + + vk::SubpassDescription subpass{}; + subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; + subpass.pDepthStencilAttachment = &depthRef; + + vk::SubpassDependency deps[2]{}; + deps[0].srcSubpass = VK_SUBPASS_EXTERNAL; + deps[0].dstSubpass = 0; + deps[0].srcStageMask = vk::PipelineStageFlagBits::eFragmentShader; + deps[0].dstStageMask = vk::PipelineStageFlagBits::eEarlyFragmentTests; + deps[0].srcAccessMask = vk::AccessFlagBits::eShaderRead; + deps[0].dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite; + deps[1].srcSubpass = 0; + deps[1].dstSubpass = VK_SUBPASS_EXTERNAL; + deps[1].srcStageMask = vk::PipelineStageFlagBits::eLateFragmentTests; + deps[1].dstStageMask = vk::PipelineStageFlagBits::eFragmentShader; + deps[1].srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite; + deps[1].dstAccessMask = vk::AccessFlagBits::eShaderRead; + + vk::RenderPassCreateInfo rpInfo{}; + rpInfo.attachmentCount = 1; + rpInfo.pAttachments = &depthAtt; + rpInfo.subpassCount = 1; + rpInfo.pSubpasses = &subpass; + rpInfo.dependencyCount = 2; + rpInfo.pDependencies = deps; + shadowRenderPass = device->createRenderPass(rpInfo); + + for (uint32_t i = 0; i < layers; ++i) { + vk::FramebufferCreateInfo fbInfo{}; + fbInfo.renderPass = shadowRenderPass; + fbInfo.attachmentCount = 1; + fbInfo.pAttachments = &shadowLayerViews[i]; + fbInfo.width = size; + fbInfo.height = size; + fbInfo.layers = 1; + shadowFramebuffers[i] = device->createFramebuffer(fbInfo); + } + + vk::PushConstantRange pcr{}; + pcr.stageFlags = vk::ShaderStageFlagBits::eVertex; + pcr.offset = 0; + pcr.size = sizeof(glm::mat4); + vk::PipelineLayoutCreateInfo plInfo{}; + plInfo.pushConstantRangeCount = 1; + plInfo.pPushConstantRanges = &pcr; + shadowPipelineLayout = device->createPipelineLayout(plInfo); + + std::vector vert(mesh3d_shadow_vert_spv, + mesh3d_shadow_vert_spv + mesh3d_shadow_vert_spv_count); + std::vector frag(mesh3d_shadow_frag_spv, + mesh3d_shadow_frag_spv + mesh3d_shadow_frag_spv_count); + vk::ShaderModule vertModule = vkb::PipelineBuilder::createShaderModule(device.instance, vert); + vk::ShaderModule fragModule = vkb::PipelineBuilder::createShaderModule(device.instance, frag); + + vk::PipelineShaderStageCreateInfo stages[2]{}; + stages[0].stage = vk::ShaderStageFlagBits::eVertex; + stages[0].module = vertModule; + stages[0].pName = "main"; + stages[1].stage = vk::ShaderStageFlagBits::eFragment; + stages[1].module = fragModule; + stages[1].pName = "main"; + + auto binding = MeshVertex::getBindingDescription(0); + auto attrs = MeshVertex::getAttributeDescription(0); + vk::PipelineVertexInputStateCreateInfo vi{}; + vi.vertexBindingDescriptionCount = 1; + vi.pVertexBindingDescriptions = &binding; + vi.vertexAttributeDescriptionCount = uint32_t(attrs.size()); + vi.pVertexAttributeDescriptions = attrs.data(); + + vk::PipelineInputAssemblyStateCreateInfo ia{}; + ia.topology = vk::PrimitiveTopology::eTriangleList; + + vk::PipelineViewportStateCreateInfo vp{}; + vp.viewportCount = 1; + vp.scissorCount = 1; + + vk::PipelineRasterizationStateCreateInfo rs{}; + rs.polygonMode = vk::PolygonMode::eFill; + rs.cullMode = vk::CullModeFlagBits::eFront; // reduce shadow acne + rs.frontFace = vk::FrontFace::eCounterClockwise; + rs.lineWidth = 1.0f; + rs.depthBiasEnable = VK_TRUE; + rs.depthBiasConstantFactor = 1.25f; + rs.depthBiasSlopeFactor = 1.75f; + + vk::PipelineMultisampleStateCreateInfo ms{}; + ms.rasterizationSamples = vk::SampleCountFlagBits::e1; + + vk::PipelineDepthStencilStateCreateInfo ds{}; + ds.depthTestEnable = true; + ds.depthWriteEnable = true; + ds.depthCompareOp = vk::CompareOp::eLess; + + vk::PipelineColorBlendStateCreateInfo blend{}; + blend.attachmentCount = 0; + + vk::DynamicState dynStates[] = {vk::DynamicState::eViewport, vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dyn{}; + dyn.dynamicStateCount = 2; + dyn.pDynamicStates = dynStates; + + vk::GraphicsPipelineCreateInfo pci{}; + pci.stageCount = 2; + pci.pStages = stages; + pci.pVertexInputState = &vi; + pci.pInputAssemblyState = &ia; + pci.pViewportState = &vp; + pci.pRasterizationState = &rs; + pci.pMultisampleState = &ms; + pci.pDepthStencilState = &ds; + pci.pColorBlendState = &blend; + pci.pDynamicState = &dyn; + pci.layout = shadowPipelineLayout; + pci.renderPass = shadowRenderPass; + pci.subpass = 0; + shadowPipeline = device->createGraphicsPipeline(vk::PipelineCache{}, pci).value; + + device->destroyShaderModule(vertModule); + device->destroyShaderModule(fragModule); + + // Clear all cascade layers so the array is in SHADER_READ_ONLY before first sample. + for (int c = 0; c < ShadowConfig::kCascades; ++c) { + beginShadowPass(c); + endShadowPass(); + } +} + +void Graphics::ensureClusteredBuffers(size_t lightsBytes, size_t tableBytes, size_t indicesBytes) { + auto ensure = [&](vkb::GenericBuffer &buf, size_t &cap, size_t need) { + if (need == 0) need = 4; + if (cap >= need && buf.buffer) return; + if (buf.buffer) { + device->destroyBuffer(buf.buffer, device.allocation_callbacks); + device->freeMemory(buf.memory, device.allocation_callbacks); + buf.buffer = vk::Buffer{}; + buf.memory = vk::DeviceMemory{}; + } + // Grow with some slack. + size_t alloc = std::max(need, cap ? cap * 2 : need); + buf.allocate(device, vk::BufferUsageFlagBits::eStorageBuffer, vk::DeviceSize(alloc), + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent); + cap = alloc; + // Descriptor sets cache buffer handles — clear so they rebind. + mesh3dClusteredUboSlots.clear(); + mesh3dClusteredDrawIndex = 0; + }; + ensure(clusteredLightsBuf, clusteredLightsCap, lightsBytes); + ensure(clusteredTableBuf, clusteredTableCap, tableBytes); + ensure(clusteredIndicesBuf, clusteredIndicesCap, indicesBytes); +} + +void Graphics::uploadClusteredLighting(const ClusteredLightingUpload &upload) { + const size_t lightsBytes = + std::max(size_t(1), upload.lights.size()) * sizeof(ClusteredLightGpu); + const size_t tableBytes = + std::max(size_t(1), upload.clusterTable.size()) * sizeof(ClusterTableEntry); + const size_t indicesBytes = + std::max(size_t(1), upload.lightIndices.size()) * sizeof(uint32_t); + ensureClusteredBuffers(lightsBytes, tableBytes, indicesBytes); + + if (!upload.lights.empty()) + clusteredLightsBuf.updateLocal(upload.lights.data(), + upload.lights.size() * sizeof(ClusteredLightGpu)); + else { + ClusteredLightGpu zero{}; + clusteredLightsBuf.updateLocal(&zero, sizeof(zero)); + } + clusteredTableBuf.updateLocal(upload.clusterTable.data(), + upload.clusterTable.size() * sizeof(ClusterTableEntry)); + clusteredIndicesBuf.updateLocal(upload.lightIndices.data(), + upload.lightIndices.size() * sizeof(uint32_t)); +} + +void Graphics::setMesh3DClusteredLighting(const ClusteredLightingUpload &upload) { + mesh3dClusteredActive = upload.active; + mesh3dClustered = upload; + if (upload.active) uploadClusteredLighting(upload); +} + +vk::DescriptorSet Graphics::mesh3dClusteredSetFor(GpuTexture *gpuTex, GpuTexture *normalTex, + GpuTexture *envTex, size_t uboSlot) { + ASSERT(gpuTex != nullptr); + ASSERT(normalTex != nullptr); + ASSERT(envTex != nullptr); + ASSERT(shadowArrayView); + while (mesh3dClusteredUboSlots.size() <= uboSlot) { + mesh3dClusteredUboSlots.emplace_back(); + auto &s = mesh3dClusteredUboSlots.back(); + s.ubo.allocate(device, vk::BufferUsageFlagBits::eUniformBuffer, sizeof(Mesh3DClusteredUBO), + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent); + s.shadowUbo.allocate(device, vk::BufferUsageFlagBits::eUniformBuffer, sizeof(ShadowUBO), + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent); + } + auto &slot = mesh3dClusteredUboSlots[uboSlot]; + Mesh3dSetKey key{gpuTex, normalTex, envTex}; + auto it = slot.sets.find(key); + if (it != slot.sets.end()) return it->second; + + vk::DescriptorSetAllocateInfo alloc{}; + alloc.descriptorPool = descriptorPool; + alloc.descriptorSetCount = 1; + alloc.pSetLayouts = &mesh3dClusteredSetLayout; + vk::DescriptorSet set = device->allocateDescriptorSets(alloc).front(); + + vkb::DescriptorSetUpdater updater(12, 12, 0); + updater.beginDescriptorSet(set) + .beginBuffers(0, 0, vk::DescriptorType::eUniformBuffer) + .buffer(slot.ubo.buffer, 0, sizeof(Mesh3DClusteredUBO)) + .beginImages(1, 0, vk::DescriptorType::eCombinedImageSampler) + .image(gpuTex->sampler, gpuTex->imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) + .beginImages(2, 0, vk::DescriptorType::eCombinedImageSampler) + .image(normalTex->sampler, normalTex->imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) + .beginImages(3, 0, vk::DescriptorType::eCombinedImageSampler) + .image(envTex->sampler, envTex->imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) + .beginBuffers(4, 0, vk::DescriptorType::eStorageBuffer) + .buffer(clusteredLightsBuf.buffer, 0, vk::DeviceSize(clusteredLightsCap)) + .beginBuffers(5, 0, vk::DescriptorType::eStorageBuffer) + .buffer(clusteredTableBuf.buffer, 0, vk::DeviceSize(clusteredTableCap)) + .beginBuffers(6, 0, vk::DescriptorType::eStorageBuffer) + .buffer(clusteredIndicesBuf.buffer, 0, vk::DeviceSize(clusteredIndicesCap)) + .beginBuffers(7, 0, vk::DescriptorType::eUniformBuffer) + .buffer(slot.shadowUbo.buffer, 0, sizeof(ShadowUBO)) + .beginImages(8, 0, vk::DescriptorType::eCombinedImageSampler) + .image(shadowSampler, shadowArrayView, vk::ImageLayout::eShaderReadOnlyOptimal) + .update(device.instance); + + slot.sets.emplace(key, set); + return set; +} + vk::Pipeline Graphics::createMesh3DStylePipeline(const std::vector &vert, const std::vector &frag, vk::PipelineLayout layout) { @@ -616,6 +1066,13 @@ void Graphics::ensureOffscreenPipelines() { offscreenTexPipeline = createTexturedStylePipeline(tvert, tfrag, offscreenRenderPass, texPipelineLayout); + if (lit2dPipelineLayout) { + std::vector lvert(lit2d_vert_spv, lit2d_vert_spv + lit2d_vert_spv_count); + std::vector lfrag(lit2d_frag_spv, lit2d_frag_spv + lit2d_frag_spv_count); + offscreenLitPipeline = + createTexturedStylePipeline(lvert, lfrag, offscreenRenderPass, lit2dPipelineLayout); + } + // Lazily create offscreen pipelines for any custom shaders already loaded. for (auto &sh : ownedShaders) ensureShaderOffscreenPipeline(sh.get()); } @@ -797,6 +1254,7 @@ void Graphics::clear(std::optional color, std::optional, std::option hasPendingClear = true; solidBatch.clear(); texturedBatches.clear(); + litBatches.clear(); if (auto *oc = dynamic_cast(activeCanvas)) { oc->clear(clearColor, std::nullopt, std::nullopt); } @@ -817,6 +1275,7 @@ Texture *Graphics::newTexture(int w, int h, const uint8_t *rgba, bool repeatU, b auto gpu = std::make_unique(); gpu->width = w; gpu->height = h; + gpu->isCube = false; gpu->image = vkb::TextureImage2D(device, uint32_t(w), uint32_t(h)); std::vector bytes(rgba, rgba + size_t(w) * size_t(h) * 4); gpu->image.upload(uploadPool, device.getQueue(vkb::QueueType::graphics), bytes); @@ -837,7 +1296,7 @@ Texture *Graphics::newTexture(int w, int h, const uint8_t *rgba, bool repeatU, b vkb::DescriptorSetUpdater updater; updater.beginDescriptorSet(gpu->descriptorSet) .beginImages(0, 0, vk::DescriptorType::eCombinedImageSampler) - .image(gpu->sampler, gpu->image.imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) + .image(gpu->sampler, gpu->imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) .update(device.instance); auto tex = std::make_unique(); @@ -853,6 +1312,48 @@ Texture *Graphics::newTexture(int w, int h, const uint8_t *rgba, bool repeatU, b return raw; } +Texture *Graphics::newCubemap(int faceSize, const uint8_t *rgbaFaces) { + ASSERT(initialized); + ASSERT_GT(faceSize, 0); + ASSERT(rgbaFaces != nullptr); + if (!initialized) throw Exception("newCubemap: graphics not initialized"); + if (faceSize <= 0 || !rgbaFaces) throw Exception("newCubemap: invalid args"); + + const size_t faceBytes = size_t(faceSize) * size_t(faceSize) * 4u; + auto gpu = std::make_unique(); + gpu->width = faceSize; + gpu->height = faceSize; + gpu->isCube = true; + gpu->cubeImage = vkb::TextureImageCube(device, device.physical_device.memory_properties, + uint32_t(faceSize), uint32_t(faceSize)); + std::vector bytes(rgbaFaces, rgbaFaces + faceBytes * 6u); + gpu->cubeImage.upload(uploadPool, device.getQueue(vkb::QueueType::graphics), bytes); + + vkb::SamplerBuilder sb; + gpu->sampler = sb.magFilter(vk::Filter::eLinear) + .minFilter(vk::Filter::eLinear) + .mipmapMode(vk::SamplerMipmapMode::eLinear) + .addressModeU(vk::SamplerAddressMode::eClampToEdge) + .addressModeV(vk::SamplerAddressMode::eClampToEdge) + .addressModeW(vk::SamplerAddressMode::eClampToEdge) + .minLod(0.f) + .maxLod(1.f) + .build(device.instance); + + auto tex = std::make_unique(); + tex->width = faceSize; + tex->height = faceSize; + tex->pixelWidth = faceSize; + tex->pixelHeight = faceSize; + tex->layers = 6; + tex->gpuHandle = gpu.get(); + + Texture *raw = tex.get(); + ownedTextures.push_back(std::move(tex)); + ownedGpuTextures.push_back(std::move(gpu)); + return raw; +} + Texture *Graphics::newTexture(image::ImageData *data) { ASSERT(data != nullptr); if (!data) throw Exception("newTexture: null ImageData"); @@ -1001,6 +1502,92 @@ void Graphics::drawTexturedRectShaderUV(Texture *texture, Shader *shader, float texturedBatches.back().batch.addTexturedRect(x, y, w, h, color, u0, v0, u1, v1); } +void Graphics::setLighting2D(const Lighting2DUBO &ubo) { lighting2dFrame = ubo; } + +void Graphics::ensureFlatNormalTexture() { + if (flatNormalTexture) return; + const uint8_t px[4] = {128, 128, 255, 255}; // flat normal pointing +Z + flatNormalTexture = newTexture(1, 1, px); +} + +void Graphics::drawTexturedRectLitUV(Texture *albedo, Texture *normal, float x, float y, float w, + float h, float u0, float v0, float u1, float v1, + const Color &color) { + if (!albedo) { + drawSolidRect(x, y, w, h, color); + return; + } + ensureFlatNormalTexture(); + if (!normal) normal = flatNormalTexture; + if (litBatches.empty() || litBatches.back().albedo != albedo || + litBatches.back().normal != normal) { + litBatches.push_back(LitBatch{albedo, normal, Batcher{}}); + } + litBatches.back().batch.addTexturedRect(x, y, w, h, color, u0, v0, u1, v1); +} + +vk::DescriptorSet Graphics::lit2dSetFor(GpuTexture *albedo, GpuTexture *normal) { + ASSERT(albedo != nullptr); + ASSERT(normal != nullptr); + LitSetKey key{albedo, normal}; + auto it = lit2dSets.find(key); + if (it != lit2dSets.end()) return it->second; + + vk::DescriptorSetAllocateInfo alloc{}; + alloc.descriptorPool = descriptorPool; + alloc.descriptorSetCount = 1; + alloc.pSetLayouts = &lit2dSetLayout; + vk::DescriptorSet set = device->allocateDescriptorSets(alloc).front(); + + vkb::DescriptorSetUpdater updater; + updater.beginDescriptorSet(set) + .beginImages(0, 0, vk::DescriptorType::eCombinedImageSampler) + .image(albedo->sampler, albedo->image.imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) + .beginImages(1, 0, vk::DescriptorType::eCombinedImageSampler) + .image(normal->sampler, normal->image.imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) + .beginBuffers(2, 0, vk::DescriptorType::eUniformBuffer) + .buffer(lighting2dUbo.buffer, 0, sizeof(Lighting2DUBO)) + .update(device.instance); + + lit2dSets.emplace(key, set); + return set; +} + +void Graphics::drawLitBatches(vk::CommandBuffer cb, int viewW, int viewH, vk::Pipeline pipeline, + std::vector &batches, + std::vector &texBufs) { + if (!pipeline || batches.empty() || !lit2dPipelineLayout) return; + lighting2dFrame.meta.y = float(viewW); + lighting2dFrame.meta.z = float(viewH); + lighting2dUbo.updateLocal(&lighting2dFrame, sizeof(Lighting2DUBO)); + + for (auto &lb : batches) { + if (lb.batch.empty() || !lb.albedo || !lb.albedo->gpuHandle) continue; + ensureFlatNormalTexture(); + Texture *ntex = lb.normal ? lb.normal : flatNormalTexture; + if (!ntex || !ntex->gpuHandle) continue; + auto *albedoGpu = static_cast(lb.albedo->gpuHandle); + auto *normalGpu = static_cast(ntex->gpuHandle); + + Batcher ndc = lb.batch; + ndc.toNDC(viewW, viewH); + std::vector gpuVerts; + gpuVerts.reserve(ndc.vertices().size()); + for (const auto &v : ndc.vertices()) + gpuVerts.push_back(TexturedVertex{v.pos, v.color, v.uv}); + texBufs.emplace_back(); + texBufs.back().allocate(device, gpuVerts); + + vk::DescriptorSet set = lit2dSetFor(albedoGpu, normalGpu); + cb.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); + cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, lit2dPipelineLayout, 0, 1, &set, 0, + nullptr); + vk::DeviceSize offset = 0; + cb.bindVertexBuffers(0, 1, texBufs.back(), &offset); + cb.draw(uint32_t(gpuVerts.size()), 1, 0, 0); + } +} + namespace { std::vector loadSpirvBytes(const void *data, size_t size) { @@ -1180,12 +1767,14 @@ void Graphics::flushBatch() { void Graphics::flushToOffscreen(OffscreenCanvas *canvas) { Batcher solid = solidBatch; auto textured = std::move(texturedBatches); + auto lit = std::move(litBatches); solidBatch.clear(); texturedBatches.clear(); + litBatches.clear(); const Color cc = canvas->pendingClearColor(); const bool needClear = canvas->takePendingClear(); - if (solid.empty() && textured.empty() && !needClear) return; + if (solid.empty() && textured.empty() && lit.empty() && !needClear) return; vkb::executeImmediately(device.instance, uploadPool, device.getQueue(vkb::QueueType::graphics), [&](vk::CommandBuffer cb) { @@ -1267,6 +1856,10 @@ void Graphics::flushToOffscreen(OffscreenCanvas *canvas) { } } + if (offscreenLitPipeline) + drawLitBatches(cb, canvas->getWidth(), canvas->getHeight(), + offscreenLitPipeline, lit, texBufs); + cb.endRenderPass(); canvas->colorImage().setCurrentLayout(vk::ImageLayout::eShaderReadOnlyOptimal); }); @@ -1275,8 +1868,10 @@ void Graphics::flushToOffscreen(OffscreenCanvas *canvas) { void Graphics::flushToSwapchain() { Batcher solid = solidBatch; auto textured = std::move(texturedBatches); + auto lit = std::move(litBatches); solidBatch.clear(); texturedBatches.clear(); + litBatches.clear(); const bool continue3D = swapchainPassOpen; if (!continue3D) { @@ -1350,6 +1945,8 @@ void Graphics::flushToSwapchain() { } } + if (lit2dPipeline) drawLitBatches(cb, width, height, lit2dPipeline, lit, texBufs); + // Invalidate prior readback so a failed present cannot reuse a stale frame. if (screenReadbackEnabled) hasPresentedFrame = false; @@ -1390,6 +1987,7 @@ void Graphics::begin3DFrame() { cb.setScissor(0, 1, &scissor); mesh3dDrawIndex = 0; + mesh3dClusteredDrawIndex = 0; swapchainPassOpen = true; frameHad3D = true; hasPendingClear = false; @@ -1398,25 +1996,143 @@ void Graphics::begin3DFrame() { void Graphics::setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) { glm::vec3 d = glm::normalize(dir); if (glm::length(d) < 1e-6f) d = glm::vec3(0.f, 1.f, 0.f); - mesh3dFrameUbo.lightDir = glm::vec4(d, 0.f); - mesh3dFrameUbo.lightColor = glm::vec4(color, 1.f); + mesh3dFrameUbo.lightDir = glm::vec4(d, mesh3dFrameUbo.lightDir.w); + // Preserve .w (envIntensity) — filled per-draw in drawMeshShader. + mesh3dFrameUbo.lightColor = glm::vec4(color, mesh3dFrameUbo.lightColor.w); } void Graphics::setMesh3DCameraPos(const glm::vec3 &eye) { - mesh3dFrameUbo.cameraPos = glm::vec4(eye, 0.f); + mesh3dFrameUbo.cameraPos = glm::vec4(eye, mesh3dFrameUbo.cameraPos.w); } -vk::DescriptorSet Graphics::mesh3dSetFor(GpuTexture *gpuTex, size_t uboSlot) { +void Graphics::setMesh3DNormalTexture(Texture *normal) { mesh3dNormalTexture = normal; } + +void Graphics::setMesh3DEnv(Texture *cube, float intensity) { + mesh3dEnvTexture = cube; + mesh3dEnvIntensity = intensity < 0.f ? 0.f : intensity; + if (!cube) mesh3dEnvIntensity = 0.f; +} + +void Graphics::setMesh3DShadows(const ShadowUpload &upload) { mesh3dShadows = upload; } + +void Graphics::setMesh3DShadowReceive(bool receive) { mesh3dShadowReceive = receive; } + +void Graphics::beginShadowPass(int cascadeIndex) { + ASSERT(initialized); + if (!shadowPipeline) createShadowResources(); + if (cascadeIndex < 0 || cascadeIndex >= ShadowConfig::kCascades) { + throw Exception("beginShadowPass: cascadeIndex out of range"); + } + shadowPassCascade = cascadeIndex; + shadowPassDraws.clear(); +} + +void Graphics::drawMeshShadow(Mesh *mesh, const glm::mat4 &lightMVP) { + if (shadowPassCascade < 0) throw Exception("drawMeshShadow: call beginShadowPass first"); + if (!mesh || !mesh->gpuHandle) throw Exception("drawMeshShadow: null mesh"); + shadowPassDraws.push_back(ShadowDraw{mesh, lightMVP}); +} + +void Graphics::endShadowPass() { + if (shadowPassCascade < 0) throw Exception("endShadowPass: no active shadow pass"); + if (!shadowPipeline || !shadowRenderPass) { + shadowPassCascade = -1; + shadowPassDraws.clear(); + return; + } + const int cascade = shadowPassCascade; + const auto draws = std::move(shadowPassDraws); + shadowPassDraws.clear(); + shadowPassCascade = -1; + + const uint32_t size = uint32_t(ShadowConfig::kMapSize); + vkb::executeImmediately(device.instance, uploadPool, device.getQueue(vkb::QueueType::graphics), + [&](vk::CommandBuffer cb) { + vk::ClearValue clear{}; + clear.depthStencil = vk::ClearDepthStencilValue{1.0f, 0}; + vk::RenderPassBeginInfo rpBegin{}; + rpBegin.renderPass = shadowRenderPass; + rpBegin.framebuffer = shadowFramebuffers[cascade]; + rpBegin.renderArea = vk::Rect2D{{0, 0}, {size, size}}; + rpBegin.clearValueCount = 1; + rpBegin.pClearValues = &clear; + cb.beginRenderPass(rpBegin, vk::SubpassContents::eInline); + + vk::Viewport vp{0.f, 0.f, float(size), float(size), 0.f, 1.f}; + vk::Rect2D scissor{{0, 0}, {size, size}}; + cb.setViewport(0, 1, &vp); + cb.setScissor(0, 1, &scissor); + cb.bindPipeline(vk::PipelineBindPoint::eGraphics, shadowPipeline); + + for (const auto &d : draws) { + auto *gpuMesh = static_cast(d.mesh->gpuHandle); + cb.pushConstants(shadowPipelineLayout, vk::ShaderStageFlagBits::eVertex, + 0, sizeof(glm::mat4), &d.mvp); + vk::DeviceSize offset = 0; + cb.bindVertexBuffers(0, 1, gpuMesh->vertices, &offset); + cb.bindIndexBuffer(gpuMesh->indices.buffer, 0, vk::IndexType::eUint32); + cb.drawIndexed(gpuMesh->indexCount, 1, 0, 0, 0); + } + cb.endRenderPass(); + }); +} + +void Graphics::ensureDefaultEnvCubemap() { + if (defaultEnvCubemap) return; + const uint8_t black[4] = {0, 0, 0, 255}; + std::vector faces(6u * 4u); + for (int i = 0; i < 6; ++i) { + faces[size_t(i) * 4u + 0] = black[0]; + faces[size_t(i) * 4u + 1] = black[1]; + faces[size_t(i) * 4u + 2] = black[2]; + faces[size_t(i) * 4u + 3] = black[3]; + } + defaultEnvCubemap = newCubemap(1, faces.data()); +} + +void Graphics::setMesh3DMaterial(float metallic, float roughness) { + mesh3dMetallic = metallic; + mesh3dRoughness = roughness; + mesh3dFrameUbo.ambient.w = metallic; + mesh3dFrameUbo.cameraPos.w = roughness; +} + +void Graphics::setMesh3DLighting(const Lighting3DPack &pack) { + mesh3dLighting = pack; + mesh3dFrameUbo.ambient = glm::vec4(glm::vec3(pack.ambient), mesh3dMetallic); + mesh3dFrameUbo.lightDir.w = float(pack.count); + if (pack.count > 0) { + mesh3dFrameUbo.lightDir = + glm::vec4(glm::vec3(pack.lights[0].posRadius), float(pack.count)); + mesh3dFrameUbo.lightColor = glm::vec4(glm::vec3(pack.lights[0].color), 1.f); + } +} + +void Graphics::ensureFlatNormalTexture3D() { + if (flatNormalTexture3D) return; + const uint8_t px[4] = {128, 128, 255, 255}; + flatNormalTexture3D = newTexture(1, 1, px); +} + +vk::DescriptorSet Graphics::mesh3dSetFor(GpuTexture *gpuTex, GpuTexture *normalTex, GpuTexture *envTex, + size_t uboSlot) { ASSERT(gpuTex != nullptr); + ASSERT(normalTex != nullptr); + ASSERT(envTex != nullptr); + ASSERT(shadowArrayView); while (mesh3dUboSlots.size() <= uboSlot) { mesh3dUboSlots.emplace_back(); - mesh3dUboSlots.back().ubo.allocate(device, vk::BufferUsageFlagBits::eUniformBuffer, - sizeof(Mesh3DUBO), - vk::MemoryPropertyFlagBits::eHostVisible | - vk::MemoryPropertyFlagBits::eHostCoherent); + auto &s = mesh3dUboSlots.back(); + s.ubo.allocate(device, vk::BufferUsageFlagBits::eUniformBuffer, sizeof(Mesh3DUBO), + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent); + s.shadowUbo.allocate(device, vk::BufferUsageFlagBits::eUniformBuffer, sizeof(ShadowUBO), + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent); } auto &slot = mesh3dUboSlots[uboSlot]; - auto it = slot.sets.find(gpuTex); + Mesh3dSetKey key{gpuTex, normalTex, envTex}; + auto it = slot.sets.find(key); if (it != slot.sets.end()) return it->second; vk::DescriptorSetAllocateInfo alloc{}; @@ -1425,16 +2141,23 @@ vk::DescriptorSet Graphics::mesh3dSetFor(GpuTexture *gpuTex, size_t uboSlot) { alloc.pSetLayouts = &mesh3dSetLayout; vk::DescriptorSet set = device->allocateDescriptorSets(alloc).front(); - // Fresh set — never bound to a command buffer yet, so Update is safe. - vkb::DescriptorSetUpdater updater; + vkb::DescriptorSetUpdater updater(10, 10, 0); updater.beginDescriptorSet(set) .beginBuffers(0, 0, vk::DescriptorType::eUniformBuffer) .buffer(slot.ubo.buffer, 0, sizeof(Mesh3DUBO)) .beginImages(1, 0, vk::DescriptorType::eCombinedImageSampler) - .image(gpuTex->sampler, gpuTex->image.imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) + .image(gpuTex->sampler, gpuTex->imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) + .beginImages(2, 0, vk::DescriptorType::eCombinedImageSampler) + .image(normalTex->sampler, normalTex->imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) + .beginImages(3, 0, vk::DescriptorType::eCombinedImageSampler) + .image(envTex->sampler, envTex->imageView(), vk::ImageLayout::eShaderReadOnlyOptimal) + .beginBuffers(4, 0, vk::DescriptorType::eUniformBuffer) + .buffer(slot.shadowUbo.buffer, 0, sizeof(ShadowUBO)) + .beginImages(5, 0, vk::DescriptorType::eCombinedImageSampler) + .image(shadowSampler, shadowArrayView, vk::ImageLayout::eShaderReadOnlyOptimal) .update(device.instance); - slot.sets.emplace(gpuTex, set); + slot.sets.emplace(key, set); return set; } @@ -1609,16 +2332,79 @@ void Graphics::drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *textu if (!tex || !tex->gpuHandle) throw Exception("drawMesh: missing texture"); auto *gpuTex = static_cast(tex->gpuHandle); + ensureFlatNormalTexture3D(); + Texture *ntex = mesh3dNormalTexture ? mesh3dNormalTexture : flatNormalTexture3D; + if (!ntex || !ntex->gpuHandle) throw Exception("drawMesh: missing normal texture"); + auto *gpuNormal = static_cast(ntex->gpuHandle); + + ensureDefaultEnvCubemap(); + Texture *envTex = mesh3dEnvTexture ? mesh3dEnvTexture : defaultEnvCubemap; + if (!envTex || !envTex->gpuHandle) throw Exception("drawMesh: missing env cubemap"); + auto *gpuEnv = static_cast(envTex->gpuHandle); + if (!gpuEnv->isCube) throw Exception("drawMesh: env texture is not a cubemap"); + const float envIntensity = (mesh3dEnvTexture && mesh3dEnvIntensity > 0.f) ? mesh3dEnvIntensity : 0.f; + + const bool useClustered = mesh3dClusteredActive && !shader && mesh3dClusteredPipeline; + auto &cb = presentModel.getCurrentCommandBuffer(); + + auto makeShadowUbo = [&]() { + ShadowUBO s = mesh3dShadows.ubo; + if (!mesh3dShadows.active) { + s.bias.y = 0.f; + s.splits.w = 0.f; + } + s.bias.z = mesh3dShadowReceive ? 1.f : 0.f; + return s; + }; + + if (useClustered) { + Mesh3DClusteredUBO ubo{}; + ubo.model = model; + ubo.mvp = mesh3dFrameUbo.mvp * model; + ubo.view = mesh3dClustered.view; + ubo.lightDir = mesh3dClustered.primaryDir; + ubo.lightColor = glm::vec4(glm::vec3(mesh3dClustered.primaryColor), envIntensity); + ubo.tint = glm::vec4(tint.r, tint.g, tint.b, tint.a); + ubo.cameraPos = glm::vec4(glm::vec3(mesh3dFrameUbo.cameraPos), mesh3dRoughness); + ubo.ambient = glm::vec4(glm::vec3(mesh3dClustered.ambient), mesh3dMetallic); + ubo.gridInfo = mesh3dClustered.gridInfo; + ubo.clipInfo = mesh3dClustered.clipInfo; + + const size_t slot = mesh3dClusteredDrawIndex++; + vk::DescriptorSet set = mesh3dClusteredSetFor(gpuTex, gpuNormal, gpuEnv, slot); + mesh3dClusteredUboSlots[slot].ubo.updateLocal(ubo); + mesh3dClusteredUboSlots[slot].shadowUbo.updateLocal(makeShadowUbo()); + + cb.bindPipeline(vk::PipelineBindPoint::eGraphics, mesh3dClusteredPipeline); + cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dClusteredPipelineLayout, 0, 1, + &set, 0, nullptr); + vk::DeviceSize offset = 0; + cb.bindVertexBuffers(0, 1, gpuMesh->vertices, &offset); + cb.bindIndexBuffer(gpuMesh->indices.buffer, 0, vk::IndexType::eUint32); + cb.drawIndexed(gpuMesh->indexCount, 1, 0, 0, 0); + return; + } + Mesh3DUBO ubo = mesh3dFrameUbo; ubo.model = model; ubo.mvp = mesh3dFrameUbo.mvp * model; ubo.tint = glm::vec4(tint.r, tint.g, tint.b, tint.a); + ubo.ambient = glm::vec4(glm::vec3(mesh3dLighting.ambient), mesh3dMetallic); + const int lightCount = std::max(0, std::min(mesh3dLighting.count, Lighting3DPack::kMaxLights)); + ubo.lightDir.w = float(lightCount); + ubo.cameraPos.w = mesh3dRoughness; + ubo.lightColor.w = envIntensity; + for (int i = 0; i < lightCount; ++i) ubo.lights[i] = mesh3dLighting.lights[i]; + if (lightCount > 0) { + ubo.lightDir = glm::vec4(glm::vec3(mesh3dLighting.lights[0].posRadius), float(lightCount)); + ubo.lightColor = glm::vec4(glm::vec3(mesh3dLighting.lights[0].color), envIntensity); + } const size_t slot = mesh3dDrawIndex++; - vk::DescriptorSet set = mesh3dSetFor(gpuTex, slot); + vk::DescriptorSet set = mesh3dSetFor(gpuTex, gpuNormal, gpuEnv, slot); mesh3dUboSlots[slot].ubo.updateLocal(ubo); + mesh3dUboSlots[slot].shadowUbo.updateLocal(makeShadowUbo()); - auto &cb = presentModel.getCurrentCommandBuffer(); if (shader) { auto *gs = static_cast(shader->gpuHandle); cb.bindPipeline(vk::PipelineBindPoint::eGraphics, gs->mesh3dPipeline); @@ -1647,6 +2433,7 @@ void Graphics::present() { // instead of touching an invalid swapchain (which crashes the driver). solidBatch.clear(); texturedBatches.clear(); + litBatches.clear(); hasPendingClear = false; return; } diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index 7589b7d8..a6e1bb44 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -4,6 +4,9 @@ #include "graphics/Texture.h" #include "graphics/Mesh.h" #include "graphics/Shader.h" +#include "graphics/Light.h" +#include "graphics/ClusteredLight.h" +#include "graphics/Shadow.h" #include "vkbuilder.hpp" #include #include @@ -78,20 +81,42 @@ struct MeshVertex { }; struct Mesh3DUBO { + // Layout prefix matches legacy custom Mesh3D shaders (toon etc.). glm::mat4 mvp{1.f}; glm::mat4 model{1.f}; - glm::vec4 lightDir{0.4f, 1.f, 0.3f, 0.f}; - glm::vec4 lightColor{1.f, 1.f, 1.f, 1.f}; + glm::vec4 lightDir{0.4f, 1.f, 0.3f, 0.f}; // xyz = primary dir; w = lightCount + glm::vec4 lightColor{1.f, 1.f, 1.f, 0.f}; // rgb = primary color; w = envIntensity (IBL) glm::vec4 tint{1.f, 1.f, 1.f, 1.f}; - glm::vec4 cameraPos{0.f, 0.f, 3.f, 0.f}; // xyz = eye (for rim / custom shaders) + glm::vec4 cameraPos{0.f, 0.f, 3.f, 0.45f}; // xyz = eye; w = roughness + glm::vec4 ambient{0.12f, 0.12f, 0.14f, 0.f}; // rgb = ambient; w = metallic + Light3DGpu lights[Lighting3DPack::kMaxLights]{}; +}; + +struct Mesh3DClusteredUBO { + glm::mat4 mvp{1.f}; + glm::mat4 model{1.f}; + glm::mat4 view{1.f}; + glm::vec4 lightDir{0.4f, 1.f, 0.3f, 0.f}; + glm::vec4 lightColor{1.f, 1.f, 1.f, 0.f}; // rgb = primary; w = envIntensity + glm::vec4 tint{1.f, 1.f, 1.f, 1.f}; + glm::vec4 cameraPos{0.f, 0.f, 3.f, 0.45f}; + glm::vec4 ambient{0.12f, 0.12f, 0.14f, 0.f}; + glm::vec4 gridInfo{16.f, 9.f, 24.f, 0.f}; + glm::vec4 clipInfo{0.1f, 100.f, 1.f, 1.f}; }; struct GpuTexture { vkb::TextureImage2D image; + vkb::TextureImageCube cubeImage; + bool isCube = false; vk::Sampler sampler; vk::DescriptorSet descriptorSet; int width = 0; int height = 0; + + vk::ImageView imageView() const { + return isCube ? cubeImage.imageView() : image.imageView(); + } }; struct GpuMesh { @@ -119,6 +144,7 @@ public: void drawSolidRect(float x, float y, float w, float h, const Color &color) override; Texture *newTexture(int width, int height, const uint8_t *rgba, bool repeatU = false, bool repeatV = false) override; + Texture *newCubemap(int faceSize, const uint8_t *rgbaFaces) override; Texture *newTexture(image::ImageData *data) override; Texture *newTextureFromFile(const std::string &filename) override; bool reloadTextureFromFile(const std::string &filename) override; @@ -131,6 +157,9 @@ public: void drawTexturedRectShaderUV(Texture *texture, Shader *shader, float x, float y, float w, float h, float u0, float v0, float u1, float v1, const Color &color) override; + void drawTexturedRectLitUV(Texture *albedo, Texture *normal, float x, float y, float w, float h, + float u0, float v0, float u1, float v1, const Color &color) override; + void setLighting2D(const Lighting2DUBO &ubo) override; Shader *newShaderFromSpv(const std::vector &vertSpv, const std::vector &fragSpv) override; Shader *newShaderFromSpvFile(const std::string &vertPath, const std::string &fragPath) override; @@ -145,8 +174,18 @@ public: void drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint) override; void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint, Shader *shader) override; + void setMesh3DNormalTexture(Texture *normal) override; + void setMesh3DMaterial(float metallic, float roughness) override; + void setMesh3DLighting(const Lighting3DPack &pack) override; + void setMesh3DClusteredLighting(const ClusteredLightingUpload &upload) override; void setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) override; void setMesh3DCameraPos(const glm::vec3 &eye) override; + void setMesh3DEnv(Texture *cube, float intensity) override; + void setMesh3DShadows(const ShadowUpload &upload) override; + void setMesh3DShadowReceive(bool receive) override; + void beginShadowPass(int cascadeIndex) override; + void drawMeshShadow(Mesh *mesh, const glm::mat4 &lightMVP) override; + void endShadowPass() override; Canvas *newCanvas(int width, int height) override; void setCanvas(Canvas *canvas) override; @@ -174,10 +213,24 @@ public: friend class OffscreenCanvas; + struct LitBatch { + Texture *albedo = nullptr; + Texture *normal = nullptr; + Batcher batch; + }; + private: void createSwapchainAndPipeline(); void createTexturedPipeline(); + void createLit2DPipeline(); void createMesh3DPipeline(); + void createMesh3DClusteredPipeline(); + void createShadowResources(); + void destroyShadowResources(); + void ensureClusteredBuffers(size_t lightsBytes, size_t tableBytes, size_t indicesBytes); + void uploadClusteredLighting(const ClusteredLightingUpload &upload); + vk::DescriptorSet mesh3dClusteredSetFor(GpuTexture *gpuTex, GpuTexture *normalTex, + GpuTexture *envTex, size_t uboSlot); void ensureOffscreenPipelines(); void ensureShaderOffscreenPipeline(Shader *shader); vk::Pipeline createTexturedStylePipeline(const std::vector &vert, @@ -190,9 +243,16 @@ private: void flushBatch(); void flushToSwapchain(); void flushToOffscreen(OffscreenCanvas *canvas); + void drawLitBatches(vk::CommandBuffer cb, int viewW, int viewH, vk::Pipeline pipeline, + std::vector &batches, std::vector &texBufs); + vk::DescriptorSet lit2dSetFor(GpuTexture *albedo, GpuTexture *normal); + void ensureFlatNormalTexture(); void captureSwapchainImage(uint32_t imageIndex); void ensurePresentCaptureHook(); - vk::DescriptorSet mesh3dSetFor(GpuTexture *gpuTex, size_t uboSlot); + vk::DescriptorSet mesh3dSetFor(GpuTexture *gpuTex, GpuTexture *normalTex, GpuTexture *envTex, + size_t uboSlot); + void ensureDefaultEnvCubemap(); + void ensureFlatNormalTexture3D(); /** Rebuild surface/swapchain when dirty. Returns false if surface not ready. */ bool rebuildSwapchainIfNeeded(); /** acquire + begin command buffer; recreates swapchain and retries on failure. */ @@ -249,13 +309,77 @@ private: // One UBO (+ per-texture descriptor sets) per draw in the current 3D frame. // Avoids vkUpdateDescriptorSets on a set already bound in a recording / // executable command buffer (which invalidates the CB). + struct Mesh3dSetKey { + GpuTexture *albedo = nullptr; + GpuTexture *normal = nullptr; + GpuTexture *env = nullptr; + bool operator==(const Mesh3dSetKey &o) const { + return albedo == o.albedo && normal == o.normal && env == o.env; + } + }; + struct Mesh3dSetKeyHash { + size_t operator()(const Mesh3dSetKey &k) const { + return std::hash()(k.albedo) ^ + (std::hash()(k.normal) << 1) ^ + (std::hash()(k.env) << 2); + } + }; struct Mesh3dUboSlot { vkb::GenericBuffer ubo; - std::unordered_map sets; + vkb::GenericBuffer shadowUbo; + std::unordered_map sets; }; std::vector mesh3dUboSlots; size_t mesh3dDrawIndex = 0; Texture *whiteTexture = nullptr; + Texture *flatNormalTexture3D = nullptr; + Texture *defaultEnvCubemap = nullptr; + Texture *mesh3dNormalTexture = nullptr; + Texture *mesh3dEnvTexture = nullptr; + float mesh3dEnvIntensity = 0.f; + float mesh3dMetallic = 0.f; + float mesh3dRoughness = 0.45f; + Lighting3DPack mesh3dLighting{}; + ShadowUpload mesh3dShadows{}; + bool mesh3dShadowReceive = true; + + // Clustered forward (separate set layout / pipeline; default PBR only). + bool mesh3dClusteredActive = false; + ClusteredLightingUpload mesh3dClustered{}; + vk::DescriptorSetLayout mesh3dClusteredSetLayout; + vk::UniqueDescriptorSetLayout mesh3dClusteredSetLayoutUnique; + vk::PipelineLayout mesh3dClusteredPipelineLayout; + vk::Pipeline mesh3dClusteredPipeline; + vkb::GenericBuffer clusteredLightsBuf; + vkb::GenericBuffer clusteredTableBuf; + vkb::GenericBuffer clusteredIndicesBuf; + size_t clusteredLightsCap = 0; + size_t clusteredTableCap = 0; + size_t clusteredIndicesCap = 0; + struct Mesh3dClusteredUboSlot { + vkb::GenericBuffer ubo; + vkb::GenericBuffer shadowUbo; + std::unordered_map sets; + }; + std::vector mesh3dClusteredUboSlots; + size_t mesh3dClusteredDrawIndex = 0; + + // CSM shadow map (3 cascade layers). + vk::Image shadowImage{}; + vk::DeviceMemory shadowMemory{}; + vk::ImageView shadowArrayView{}; + vk::ImageView shadowLayerViews[ShadowConfig::kCascades]{}; + vk::Framebuffer shadowFramebuffers[ShadowConfig::kCascades]{}; + vk::Sampler shadowSampler{}; + vk::RenderPass shadowRenderPass{}; + vk::PipelineLayout shadowPipelineLayout{}; + vk::Pipeline shadowPipeline{}; + int shadowPassCascade = -1; + struct ShadowDraw { + Mesh *mesh = nullptr; + glm::mat4 mvp{1.f}; + }; + std::vector shadowPassDraws; vkb::Present presentModel; vkb::DepthStencilImage depthImage; @@ -269,6 +393,30 @@ private: }; std::vector texturedBatches; + std::vector litBatches; + + vk::DescriptorSetLayout lit2dSetLayout; + vk::UniqueDescriptorSetLayout lit2dSetLayoutUnique; + vk::PipelineLayout lit2dPipelineLayout; + vk::Pipeline lit2dPipeline; + vk::Pipeline offscreenLitPipeline; + vkb::GenericBuffer lighting2dUbo; + Lighting2DUBO lighting2dFrame{}; + struct LitSetKey { + GpuTexture *albedo = nullptr; + GpuTexture *normal = nullptr; + bool operator==(const LitSetKey &o) const { + return albedo == o.albedo && normal == o.normal; + } + }; + struct LitSetKeyHash { + size_t operator()(const LitSetKey &k) const { + return std::hash()(k.albedo) ^ (std::hash()(k.normal) << 1); + } + }; + std::unordered_map lit2dSets; + Texture *flatNormalTexture = nullptr; + Color clearColor{0.1f, 0.1f, 0.12f, 1.0f}; bool hasPendingClear = true; diff --git a/src/modules/math/Mat4.cpp b/src/modules/math/Mat4.cpp new file mode 100644 index 00000000..b0ffd079 --- /dev/null +++ b/src/modules/math/Mat4.cpp @@ -0,0 +1,63 @@ +#include "math/Mat4.h" +#include "math/Vec2.h" +#include "math/Vec3.h" + +#include "common/Exception.h" + +#include +#include + +namespace eve::math { + +Mat4::Mat4() : m_(1.f) {} +Mat4::Mat4(const glm::mat4 &m) : m_(m) {} + +void Mat4::identity() { m_ = glm::mat4(1.f); } + +void Mat4::translate(float x, float y, float z) { + m_ = glm::translate(m_, glm::vec3(x, y, z)); +} + +void Mat4::rotateX(float radians) { m_ = glm::rotate(m_, radians, glm::vec3(1.f, 0.f, 0.f)); } +void Mat4::rotateY(float radians) { m_ = glm::rotate(m_, radians, glm::vec3(0.f, 1.f, 0.f)); } +void Mat4::rotateZ(float radians) { m_ = glm::rotate(m_, radians, glm::vec3(0.f, 0.f, 1.f)); } + +void Mat4::scale(float sx, float sy, float sz) { + m_ = glm::scale(m_, glm::vec3(sx, sy, sz)); +} + +void Mat4::multiply(const Mat4 *other) { + if (!other) throw eve::Exception("Mat4.multiply: other is null"); + m_ = m_ * other->m_; +} + +Mat4 *Mat4::multiplied(const Mat4 *other) const { + if (!other) throw eve::Exception("Mat4.multiplied: other is null"); + return new Mat4(m_ * other->m_); +} + +Vec3 *Mat4::transformVec3(const Vec3 *v) const { + if (!v) throw eve::Exception("Mat4.transformVec3: v is null"); + glm::vec4 r = m_ * glm::vec4(v->getX(), v->getY(), v->getZ(), 1.f); + return new Vec3(r.x, r.y, r.z); +} + +Vec2 *Mat4::transformPoint2(const Vec2 *v) const { + if (!v) throw eve::Exception("Mat4.transformPoint2: v is null"); + glm::vec4 r = m_ * glm::vec4(v->getX(), v->getY(), 0.f, 1.f); + return new Vec2(r.x, r.y); +} + +float Mat4::get(int index) const { + if (index < 0 || index > 15) throw eve::Exception("Mat4.get: index must be 0..15"); + return glm::value_ptr(m_)[index]; +} + +void Mat4::set(int index, float value) { + if (index < 0 || index > 15) throw eve::Exception("Mat4.set: index must be 0..15"); + glm::value_ptr(m_)[index] = value; +} + +Mat4 *Mat4::clone() const { return new Mat4(m_); } + +} // namespace eve::math diff --git a/src/modules/math/Mat4.h b/src/modules/math/Mat4.h new file mode 100644 index 00000000..259049cc --- /dev/null +++ b/src/modules/math/Mat4.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace eve::math { + +class Vec2; +class Vec3; + +/** Column-major 4x4 matrix wrapping glm::mat4. */ +class Mat4 { +public: + Mat4(); + explicit Mat4(const glm::mat4 &m); + + void identity(); + void translate(float x, float y, float z); + void rotateX(float radians); + void rotateY(float radians); + void rotateZ(float radians); + void scale(float sx, float sy, float sz); + + /** this = this * other (column-vector convention). */ + void multiply(const Mat4 *other); + Mat4 *multiplied(const Mat4 *other) const; + + Vec3 *transformVec3(const Vec3 *v) const; + Vec2 *transformPoint2(const Vec2 *v) const; + + /** Column-major element 0..15. */ + float get(int index) const; + void set(int index, float value); + + Mat4 *clone() const; + + const glm::mat4 &raw() const { return m_; } + glm::mat4 &raw() { return m_; } + +private: + glm::mat4 m_{1.f}; +}; + +} // namespace eve::math diff --git a/src/modules/math/Math.cpp b/src/modules/math/Math.cpp new file mode 100644 index 00000000..0adb24f7 --- /dev/null +++ b/src/modules/math/Math.cpp @@ -0,0 +1,629 @@ +#include "math/Math.h" +#include "math/Vec2.h" +#include "math/Vec3.h" +#include "math/Mat4.h" + +#include "common/Exception.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace eve::math { +namespace { + +float clamp01(float t) { return std::clamp(t, 0.f, 1.f); } + +float noiseToUnit(float n) { + // glm::simplex / perlin is roughly [-1, 1]; map to [0, 1] like LÖVE. + return clamp01(n * 0.5f + 0.5f); +} + +float hash11(float p) { + p = p - std::floor(p); + p *= 0.1031f; + p = p - std::floor(p); + p *= p + 33.33f; + p *= p + p; + return p - std::floor(p); +} + +float hash21(float x, float y) { + glm::vec3 p3 = glm::fract(glm::vec3(x, y, x) * 0.1031f); + p3 += glm::dot(p3, glm::vec3(p3.y + 33.33f, p3.z + 33.33f, p3.x + 33.33f)); + return glm::fract((p3.x + p3.y) * p3.z); +} + +float hash31(float x, float y, float z) { + glm::vec3 p3 = glm::fract(glm::vec3(x, y, z) * 0.1031f); + p3 += glm::dot(p3, glm::vec3(p3.y + 33.33f, p3.z + 33.33f, p3.x + 33.33f)); + return glm::fract((p3.x + p3.y) * p3.z); +} + +glm::vec2 voronoiPoint(int ix, int iy) { + float hx = hash21(float(ix), float(iy)); + float hy = hash21(float(ix) + 19.19f, float(iy) + 47.47f); + return glm::vec2(float(ix) + hx, float(iy) + hy); +} + +void voronoiF1F2(float x, float y, float &f1, float &f2) { + int ix = int(std::floor(x)); + int iy = int(std::floor(y)); + f1 = 1e9f; + f2 = 1e9f; + for (int j = -1; j <= 1; ++j) { + for (int i = -1; i <= 1; ++i) { + glm::vec2 p = voronoiPoint(ix + i, iy + j); + float d = glm::length(glm::vec2(x, y) - p); + if (d < f1) { + f2 = f1; + f1 = d; + } else if (d < f2) { + f2 = d; + } + } + } +} + +} // namespace + +Module_IMPL(Math, new Math()); + +Math::Math() : seed_(1), rng_(1) {} + +Vec2 *Math::newVec2(float x, float y) { return new Vec2(x, y); } +Vec3 *Math::newVec3(float x, float y, float z) { return new Vec3(x, y, z); } + +Mat4 *Math::newMat4() { return new Mat4(); } + +Mat4 *Math::newMat4Translation(float x, float y, float z) { + auto *m = new Mat4(); + m->translate(x, y, z); + return m; +} + +Mat4 *Math::newMat4Scale(float sx, float sy, float sz) { + auto *m = new Mat4(); + m->scale(sx, sy, sz); + return m; +} + +Mat4 *Math::newMat4RotationZ(float radians) { + auto *m = new Mat4(); + m->rotateZ(radians); + return m; +} + +float Math::clamp(float x, float lo, float hi) const { + if (lo > hi) std::swap(lo, hi); + return std::clamp(x, lo, hi); +} + +float Math::lerp(float a, float b, float t) const { return a + (b - a) * t; } + +float Math::smoothstep(float edge0, float edge1, float x) const { + if (edge0 == edge1) return x < edge0 ? 0.f : 1.f; + float t = clamp01((x - edge0) / (edge1 - edge0)); + return t * t * (3.f - 2.f * t); +} + +float Math::remap(float x, float inMin, float inMax, float outMin, float outMax) const { + if (inMin == inMax) return outMin; + float t = (x - inMin) / (inMax - inMin); + return outMin + (outMax - outMin) * t; +} + +float Math::degToRad(float deg) const { return deg * float(M_PI) / 180.f; } +float Math::radToDeg(float rad) const { return rad * 180.f / float(M_PI); } + +float Math::sign(float x) const { + if (x > 0.f) return 1.f; + if (x < 0.f) return -1.f; + return 0.f; +} + +float Math::fract(float x) const { return x - std::floor(x); } + +float Math::approach(float current, float target, float maxDelta) const { + float d = target - current; + if (std::fabs(d) <= maxDelta) return target; + return current + sign(d) * maxDelta; +} + +float Math::wrap(float x, float lo, float hi) const { + if (lo == hi) return lo; + if (lo > hi) std::swap(lo, hi); + float range = hi - lo; + float t = std::fmod(x - lo, range); + if (t < 0.f) t += range; + return lo + t; +} + +float Math::pingPong(float t, float length) const { + if (length <= 0.f) return 0.f; + t = wrap(t, 0.f, length * 2.f); + return length - std::fabs(t - length); +} + +float Math::inverseLerp(float a, float b, float x) const { + if (a == b) return 0.f; + return (x - a) / (b - a); +} + +float Math::smootherstep(float edge0, float edge1, float x) const { + if (edge0 == edge1) return x < edge0 ? 0.f : 1.f; + float t = clamp01((x - edge0) / (edge1 - edge0)); + return t * t * t * (t * (t * 6.f - 15.f) + 10.f); +} + +float Math::bias(float t, float b) const { + t = clamp01(t); + if (b <= 0.f) return 0.f; + if (b >= 1.f) return 1.f; + return t / ((1.f / b - 2.f) * (1.f - t) + 1.f); +} + +float Math::gain(float t, float g) const { + t = clamp01(t); + if (t < 0.5f) return bias(t * 2.f, g) * 0.5f; + return bias(t * 2.f - 1.f, 1.f - g) * 0.5f + 0.5f; +} + +float Math::ease(float t, const std::string &kind) const { + t = clamp01(t); + if (kind == "linear" || kind.empty()) return t; + if (kind == "inQuad") return t * t; + if (kind == "outQuad") return 1.f - (1.f - t) * (1.f - t); + if (kind == "inOutQuad") + return t < 0.5f ? 2.f * t * t : 1.f - std::pow(-2.f * t + 2.f, 2.f) * 0.5f; + if (kind == "inCubic") return t * t * t; + if (kind == "outCubic") return 1.f - std::pow(1.f - t, 3.f); + if (kind == "inOutCubic") + return t < 0.5f ? 4.f * t * t * t : 1.f - std::pow(-2.f * t + 2.f, 3.f) * 0.5f; + if (kind == "inSine") return 1.f - std::cos(t * float(M_PI) * 0.5f); + if (kind == "outSine") return std::sin(t * float(M_PI) * 0.5f); + if (kind == "inOutSine") return -(std::cos(float(M_PI) * t) - 1.f) * 0.5f; + if (kind == "inExpo") return t <= 0.f ? 0.f : std::pow(2.f, 10.f * t - 10.f); + if (kind == "outExpo") return t >= 1.f ? 1.f : 1.f - std::pow(2.f, -10.f * t); + if (kind == "inOutExpo") { + if (t <= 0.f) return 0.f; + if (t >= 1.f) return 1.f; + return t < 0.5f ? std::pow(2.f, 20.f * t - 10.f) * 0.5f + : (2.f - std::pow(2.f, -20.f * t + 10.f)) * 0.5f; + } + throw Exception("Math.ease: unknown kind '%s'", kind.c_str()); +} + +float Math::step(float edge, float x) const { return x < edge ? 0.f : 1.f; } + +float Math::quantize(float x, float stepSize) const { + if (stepSize == 0.f) return x; + return std::floor(x / stepSize) * stepSize; +} + +float Math::snap(float x, float grid) const { + if (grid == 0.f) return x; + return std::round(x / grid) * grid; +} + +float Math::length2(float x, float y) const { return std::sqrt(x * x + y * y); } +float Math::length3(float x, float y, float z) const { + return std::sqrt(x * x + y * y + z * z); +} + +float Math::distance2(float x1, float y1, float x2, float y2) const { + return length2(x2 - x1, y2 - y1); +} + +float Math::distance3(float x1, float y1, float z1, float x2, float y2, float z2) const { + return length3(x2 - x1, y2 - y1, z2 - z1); +} + +float Math::dot2(float x1, float y1, float x2, float y2) const { return x1 * x2 + y1 * y2; } +float Math::dot3(float x1, float y1, float z1, float x2, float y2, float z2) const { + return x1 * x2 + y1 * y2 + z1 * z2; +} + +float Math::cross2(float x1, float y1, float x2, float y2) const { return x1 * y2 - y1 * x2; } +float Math::angle2(float x, float y) const { return std::atan2(y, x); } + +float Math::angleBetween2(float x1, float y1, float x2, float y2) const { + return std::atan2(y2, x2) - std::atan2(y1, x1); +} + +float Math::lerpAngle(float a, float b, float t) const { + float twoPi = float(M_PI) * 2.f; + float diff = std::fmod(b - a + float(M_PI), twoPi); + if (diff < 0.f) diff += twoPi; + diff -= float(M_PI); + return a + diff * t; +} + +float Math::normalize2X(float x, float y) const { + float len = length2(x, y); + return len > 0.f ? x / len : 0.f; +} +float Math::normalize2Y(float x, float y) const { + float len = length2(x, y); + return len > 0.f ? y / len : 0.f; +} +float Math::normalize3X(float x, float y, float z) const { + float len = length3(x, y, z); + return len > 0.f ? x / len : 0.f; +} +float Math::normalize3Y(float x, float y, float z) const { + float len = length3(x, y, z); + return len > 0.f ? y / len : 0.f; +} +float Math::normalize3Z(float x, float y, float z) const { + float len = length3(x, y, z); + return len > 0.f ? z / len : 0.f; +} + +float Math::rotate2X(float x, float y, float radians) const { + float c = std::cos(radians), s = std::sin(radians); + return x * c - y * s; +} +float Math::rotate2Y(float x, float y, float radians) const { + float c = std::cos(radians), s = std::sin(radians); + return x * s + y * c; +} + +float Math::polarX(float radius, float radians) const { return radius * std::cos(radians); } +float Math::polarY(float radius, float radians) const { return radius * std::sin(radians); } +float Math::cartesianRadius(float x, float y) const { return length2(x, y); } +float Math::cartesianAngle(float x, float y) const { return angle2(x, y); } + +bool Math::pointInCircle(float px, float py, float cx, float cy, float radius) const { + return distance2(px, py, cx, cy) <= radius; +} + +bool Math::pointInRect(float px, float py, float rx, float ry, float rw, float rh) const { + return px >= rx && py >= ry && px <= rx + rw && py <= ry + rh; +} + +float Math::bilinear(float v00, float v10, float v01, float v11, float u, float v) const { + float a = lerp(v00, v10, u); + float b = lerp(v01, v11, u); + return lerp(a, b, v); +} + +void Math::setRandomSeed(uint32_t seed) { + seed_ = seed == 0 ? 1u : seed; + rng_.seed(seed_); +} + +void Math::setRandomSeedFromTime() { + auto us = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); + setRandomSeed(static_cast(us) ^ 0xA5A5A5A5u); +} + +uint32_t Math::getRandomSeed() const { return seed_; } + +float Math::random() { + std::uniform_real_distribution dist(0.f, 1.f); + return dist(rng_); +} + +float Math::randomRange(float min, float max) { + if (min > max) std::swap(min, max); + std::uniform_real_distribution dist(min, max); + return dist(rng_); +} + +int Math::randomInt(int min, int maxInclusive) { + if (min > maxInclusive) std::swap(min, maxInclusive); + std::uniform_int_distribution dist(min, maxInclusive); + return dist(rng_); +} + +float Math::randomGaussian(float mean, float stddev) { + std::normal_distribution dist(mean, stddev); + return dist(rng_); +} + +float Math::hash1(float x) const { return hash11(x); } +float Math::hash2(float x, float y) const { return hash21(x, y); } +float Math::hash3(float x, float y, float z) const { return hash31(x, y, z); } + +float Math::noise1(float x) const { return noiseToUnit(glm::simplex(glm::vec2(x, 0.f))); } +float Math::noise2(float x, float y) const { return noiseToUnit(glm::simplex(glm::vec2(x, y))); } +float Math::noise3(float x, float y, float z) const { + return noiseToUnit(glm::simplex(glm::vec3(x, y, z))); +} + +float Math::perlin2(float x, float y) const { return noiseToUnit(glm::perlin(glm::vec2(x, y))); } +float Math::perlin3(float x, float y, float z) const { + return noiseToUnit(glm::perlin(glm::vec3(x, y, z))); +} + +float Math::fbm2(float x, float y, int octaves, float lacunarity, float gain) const { + if (octaves < 1) octaves = 1; + if (octaves > 16) octaves = 16; + float sum = 0.f, amp = 1.f, freq = 1.f, norm = 0.f; + for (int i = 0; i < octaves; ++i) { + sum += noise2(x * freq, y * freq) * amp; + norm += amp; + amp *= gain; + freq *= lacunarity; + } + return norm > 0.f ? sum / norm : 0.f; +} + +float Math::fbm3(float x, float y, float z, int octaves, float lacunarity, float gain) const { + if (octaves < 1) octaves = 1; + if (octaves > 16) octaves = 16; + float sum = 0.f, amp = 1.f, freq = 1.f, norm = 0.f; + for (int i = 0; i < octaves; ++i) { + sum += noise3(x * freq, y * freq, z * freq) * amp; + norm += amp; + amp *= gain; + freq *= lacunarity; + } + return norm > 0.f ? sum / norm : 0.f; +} + +float Math::ridged2(float x, float y, int octaves, float lacunarity, float gain) const { + if (octaves < 1) octaves = 1; + if (octaves > 16) octaves = 16; + float sum = 0.f, amp = 0.5f, freq = 1.f, prev = 1.f; + for (int i = 0; i < octaves; ++i) { + float n = noise2(x * freq, y * freq); + n = 1.f - std::fabs(n * 2.f - 1.f); + n = n * n; + sum += n * amp * prev; + prev = n; + freq *= lacunarity; + amp *= gain; + } + return clamp01(sum); +} + +float Math::ridged3(float x, float y, float z, int octaves, float lacunarity, float gain) const { + if (octaves < 1) octaves = 1; + if (octaves > 16) octaves = 16; + float sum = 0.f, amp = 0.5f, freq = 1.f, prev = 1.f; + for (int i = 0; i < octaves; ++i) { + float n = noise3(x * freq, y * freq, z * freq); + n = 1.f - std::fabs(n * 2.f - 1.f); + n = n * n; + sum += n * amp * prev; + prev = n; + freq *= lacunarity; + amp *= gain; + } + return clamp01(sum); +} + +float Math::turbulence2(float x, float y, int octaves, float lacunarity, float gain) const { + if (octaves < 1) octaves = 1; + if (octaves > 16) octaves = 16; + float sum = 0.f, amp = 1.f, freq = 1.f, norm = 0.f; + for (int i = 0; i < octaves; ++i) { + sum += std::fabs(noise2(x * freq, y * freq) * 2.f - 1.f) * amp; + norm += amp; + amp *= gain; + freq *= lacunarity; + } + return norm > 0.f ? clamp01(sum / norm) : 0.f; +} + +float Math::voronoi2(float x, float y) const { + float f1, f2; + voronoiF1F2(x, y, f1, f2); + return f1; +} + +float Math::voronoiEdge2(float x, float y) const { + float f1, f2; + voronoiF1F2(x, y, f1, f2); + return f2 - f1; +} + +float Math::warpNoise2(float x, float y, float warpAmp) const { + float wx = noise2(x, y) - 0.5f; + float wy = noise2(x + 5.2f, y + 1.3f) - 0.5f; + return noise2(x + wx * warpAmp, y + wy * warpAmp); +} + +float Math::bezierQuadratic(float t, float p0, float p1, float p2) const { + t = clamp01(t); + float u = 1.f - t; + return u * u * p0 + 2.f * u * t * p1 + t * t * p2; +} + +float Math::bezierCubic(float t, float p0, float p1, float p2, float p3) const { + t = clamp01(t); + float u = 1.f - t; + float uu = u * u; + float tt = t * t; + return uu * u * p0 + 3.f * uu * t * p1 + 3.f * u * tt * p2 + tt * t * p3; +} + +float Math::bezierQuadratic2X(float t, float x0, float /*y0*/, float x1, float /*y1*/, float x2, + float /*y2*/) const { + return bezierQuadratic(t, x0, x1, x2); +} + +float Math::bezierQuadratic2Y(float t, float /*x0*/, float y0, float /*x1*/, float y1, float /*x2*/, + float y2) const { + return bezierQuadratic(t, y0, y1, y2); +} + +float Math::bezierCubic2X(float t, float x0, float /*y0*/, float x1, float /*y1*/, float x2, + float /*y2*/, float x3, float /*y3*/) const { + return bezierCubic(t, x0, x1, x2, x3); +} + +float Math::bezierCubic2Y(float t, float /*x0*/, float y0, float /*x1*/, float y1, float /*x2*/, + float y2, float /*x3*/, float y3) const { + return bezierCubic(t, y0, y1, y2, y3); +} + +void Math::expose(ssq::Table &table) { + auto cls = table.addClass(name, Math::create, false); + expose(cls); + + auto v2 = table.addClass( + "Vec2", std::function([]() -> Vec2 * { return nullptr; }), true); + v2.addFunc("getX", &Vec2::getX); + v2.addFunc("getY", &Vec2::getY); + v2.addFunc("setX", &Vec2::setX); + v2.addFunc("setY", &Vec2::setY); + v2.addFunc("set", &Vec2::set); + v2.addFunc("length", &Vec2::length); + v2.addFunc("lengthSquared", &Vec2::lengthSquared); + v2.addFunc("normalize", &Vec2::normalize); + v2.addFunc("normalized", &Vec2::normalized); + v2.addFunc("dot", &Vec2::dot); + v2.addFunc("cross", &Vec2::cross); + v2.addFunc("distanceTo", &Vec2::distanceTo); + v2.addFunc("angle", &Vec2::angle); + v2.addFunc("add", &Vec2::add); + v2.addFunc("sub", &Vec2::sub); + v2.addFunc("scale", &Vec2::scale); + v2.addFunc("lerpTo", &Vec2::lerpTo); + v2.addFunc("clone", &Vec2::clone); + + auto v3 = table.addClass( + "Vec3", std::function([]() -> Vec3 * { return nullptr; }), true); + v3.addFunc("getX", &Vec3::getX); + v3.addFunc("getY", &Vec3::getY); + v3.addFunc("getZ", &Vec3::getZ); + v3.addFunc("setX", &Vec3::setX); + v3.addFunc("setY", &Vec3::setY); + v3.addFunc("setZ", &Vec3::setZ); + v3.addFunc("set", &Vec3::set); + v3.addFunc("length", &Vec3::length); + v3.addFunc("lengthSquared", &Vec3::lengthSquared); + v3.addFunc("normalize", &Vec3::normalize); + v3.addFunc("normalized", &Vec3::normalized); + v3.addFunc("dot", &Vec3::dot); + v3.addFunc("cross", &Vec3::cross); + v3.addFunc("distanceTo", &Vec3::distanceTo); + v3.addFunc("add", &Vec3::add); + v3.addFunc("sub", &Vec3::sub); + v3.addFunc("scale", &Vec3::scale); + v3.addFunc("lerpTo", &Vec3::lerpTo); + v3.addFunc("clone", &Vec3::clone); + + auto m4 = table.addClass( + "Mat4", std::function([]() -> Mat4 * { return nullptr; }), true); + m4.addFunc("identity", &Mat4::identity); + m4.addFunc("translate", &Mat4::translate); + m4.addFunc("rotateX", &Mat4::rotateX); + m4.addFunc("rotateY", &Mat4::rotateY); + m4.addFunc("rotateZ", &Mat4::rotateZ); + m4.addFunc("scale", &Mat4::scale); + m4.addFunc("multiply", &Mat4::multiply); + m4.addFunc("multiplied", &Mat4::multiplied); + m4.addFunc("transformVec3", &Mat4::transformVec3); + m4.addFunc("transformPoint2", &Mat4::transformPoint2); + m4.addFunc("get", &Mat4::get); + m4.addFunc("set", &Mat4::set); + m4.addFunc("clone", &Mat4::clone); +} + +void Math::expose(ssq::Class &cls) { + cls.addFunc("getName", &Math::getName); + cls.addFunc("newVec2", &Math::newVec2); + cls.addFunc("newVec3", &Math::newVec3); + cls.addFunc("newMat4", &Math::newMat4); + cls.addFunc("newMat4Translation", &Math::newMat4Translation); + cls.addFunc("newMat4Scale", &Math::newMat4Scale); + cls.addFunc("newMat4RotationZ", &Math::newMat4RotationZ); + + cls.addFunc("clamp", &Math::clamp); + cls.addFunc("lerp", &Math::lerp); + cls.addFunc("smoothstep", &Math::smoothstep); + cls.addFunc("remap", &Math::remap); + cls.addFunc("degToRad", &Math::degToRad); + cls.addFunc("radToDeg", &Math::radToDeg); + cls.addFunc("sign", &Math::sign); + cls.addFunc("fract", &Math::fract); + cls.addFunc("approach", &Math::approach); + cls.addFunc("wrap", &Math::wrap); + cls.addFunc("pingPong", &Math::pingPong); + cls.addFunc("inverseLerp", &Math::inverseLerp); + cls.addFunc("smootherstep", &Math::smootherstep); + cls.addFunc("bias", &Math::bias); + cls.addFunc("gain", &Math::gain); + cls.addFunc("ease", &Math::ease); + cls.addFunc("step", &Math::step); + cls.addFunc("quantize", &Math::quantize); + cls.addFunc("snap", &Math::snap); + + cls.addFunc("length2", &Math::length2); + cls.addFunc("length3", &Math::length3); + cls.addFunc("distance2", &Math::distance2); + cls.addFunc("distance3", &Math::distance3); + cls.addFunc("dot2", &Math::dot2); + cls.addFunc("dot3", &Math::dot3); + cls.addFunc("cross2", &Math::cross2); + cls.addFunc("angle2", &Math::angle2); + cls.addFunc("angleBetween2", &Math::angleBetween2); + cls.addFunc("lerpAngle", &Math::lerpAngle); + cls.addFunc("normalize2X", &Math::normalize2X); + cls.addFunc("normalize2Y", &Math::normalize2Y); + cls.addFunc("normalize3X", &Math::normalize3X); + cls.addFunc("normalize3Y", &Math::normalize3Y); + cls.addFunc("normalize3Z", &Math::normalize3Z); + cls.addFunc("rotate2X", &Math::rotate2X); + cls.addFunc("rotate2Y", &Math::rotate2Y); + cls.addFunc("polarX", &Math::polarX); + cls.addFunc("polarY", &Math::polarY); + cls.addFunc("cartesianRadius", &Math::cartesianRadius); + cls.addFunc("cartesianAngle", &Math::cartesianAngle); + cls.addFunc("pointInCircle", &Math::pointInCircle); + cls.addFunc("pointInRect", &Math::pointInRect); + cls.addFunc("bilinear", &Math::bilinear); + + cls.addFunc("setRandomSeed", &Math::setRandomSeed); + cls.addFunc("setRandomSeedFromTime", &Math::setRandomSeedFromTime); + cls.addFunc("getRandomSeed", &Math::getRandomSeed); + cls.addFunc("random", &Math::random); + cls.addFunc("randomRange", &Math::randomRange); + cls.addFunc("randomInt", &Math::randomInt); + cls.addFunc("randomGaussian", &Math::randomGaussian); + + cls.addFunc("hash1", &Math::hash1); + cls.addFunc("hash2", &Math::hash2); + cls.addFunc("hash3", &Math::hash3); + + cls.addFunc("noise1", &Math::noise1); + cls.addFunc("noise2", &Math::noise2); + cls.addFunc("noise3", &Math::noise3); + cls.addFunc("perlin2", &Math::perlin2); + cls.addFunc("perlin3", &Math::perlin3); + cls.addFunc("fbm2", &Math::fbm2); + cls.addFunc("fbm3", &Math::fbm3); + cls.addFunc("ridged2", &Math::ridged2); + cls.addFunc("ridged3", &Math::ridged3); + cls.addFunc("turbulence2", &Math::turbulence2); + cls.addFunc("voronoi2", &Math::voronoi2); + cls.addFunc("voronoiEdge2", &Math::voronoiEdge2); + cls.addFunc("warpNoise2", &Math::warpNoise2); + + cls.addFunc("bezierQuadratic", &Math::bezierQuadratic); + cls.addFunc("bezierCubic", &Math::bezierCubic); + cls.addFunc("bezierQuadratic2X", &Math::bezierQuadratic2X); + cls.addFunc("bezierQuadratic2Y", &Math::bezierQuadratic2Y); + cls.addFunc("bezierCubic2X", &Math::bezierCubic2X); + cls.addFunc("bezierCubic2Y", &Math::bezierCubic2Y); +} + +} // namespace eve::math diff --git a/src/modules/math/Math.h b/src/modules/math/Math.h new file mode 100644 index 00000000..7b76e837 --- /dev/null +++ b/src/modules/math/Math.h @@ -0,0 +1,161 @@ +#pragma once + +#include "common/Module.h" + +#include +#include +#include + +namespace eve::math { + +class Vec2; +class Vec3; +class Mat4; + +/** + * Math module — glm-backed vectors/matrices, noise, bezier, random. + * Script: `math <- eve.Math();` + * + * No overloads: distinct names (length2/length3, noise1/noise2/…). + */ +class Math : public Module { +public: + Module_REG(Math); + Math(); + ~Math() override = default; + + Vec2 *newVec2(float x = 0.f, float y = 0.f); + Vec3 *newVec3(float x = 0.f, float y = 0.f, float z = 0.f); + Mat4 *newMat4(); + Mat4 *newMat4Translation(float x, float y, float z); + Mat4 *newMat4Scale(float sx, float sy, float sz); + Mat4 *newMat4RotationZ(float radians); + + // --- scalar --- + float clamp(float x, float lo, float hi) const; + float lerp(float a, float b, float t) const; + float smoothstep(float edge0, float edge1, float x) const; + float remap(float x, float inMin, float inMax, float outMin, float outMax) const; + float degToRad(float deg) const; + float radToDeg(float rad) const; + float sign(float x) const; + float fract(float x) const; + float approach(float current, float target, float maxDelta) const; + float wrap(float x, float lo, float hi) const; + float pingPong(float t, float length) const; + + /** Inverse of lerp: t such that lerp(a,b,t) ≈ x. */ + float inverseLerp(float a, float b, float x) const; + float smootherstep(float edge0, float edge1, float x) const; + /** Schlick bias/gain — shape [0,1] distributions (procgen falloff). */ + float bias(float t, float b) const; + float gain(float t, float g) const; + /** + * Easing on [0,1]. kind: + * "linear"|"inQuad"|"outQuad"|"inOutQuad"|"inCubic"|"outCubic"|"inOutCubic"| + * "inSine"|"outSine"|"inOutSine"|"inExpo"|"outExpo"|"inOutExpo" + */ + float ease(float t, const std::string &kind) const; + float step(float edge, float x) const; + float quantize(float x, float stepSize) const; + float snap(float x, float grid) const; + + // --- geometry (float components) --- + float length2(float x, float y) const; + float length3(float x, float y, float z) const; + float distance2(float x1, float y1, float x2, float y2) const; + float distance3(float x1, float y1, float z1, float x2, float y2, float z2) const; + float dot2(float x1, float y1, float x2, float y2) const; + float dot3(float x1, float y1, float z1, float x2, float y2, float z2) const; + float cross2(float x1, float y1, float x2, float y2) const; + float angle2(float x, float y) const; + float angleBetween2(float x1, float y1, float x2, float y2) const; + float lerpAngle(float a, float b, float t) const; + float normalize2X(float x, float y) const; + float normalize2Y(float x, float y) const; + float normalize3X(float x, float y, float z) const; + float normalize3Y(float x, float y, float z) const; + float normalize3Z(float x, float y, float z) const; + + /** Rotate (x,y) by radians around origin. */ + float rotate2X(float x, float y, float radians) const; + float rotate2Y(float x, float y, float radians) const; + float polarX(float radius, float radians) const; + float polarY(float radius, float radians) const; + float cartesianRadius(float x, float y) const; + float cartesianAngle(float x, float y) const; + + bool pointInCircle(float px, float py, float cx, float cy, float radius) const; + bool pointInRect(float px, float py, float rx, float ry, float rw, float rh) const; + + /** Bilinear sample of 4 corners (v00,v10,v01,v11) with u,v in [0,1]. */ + float bilinear(float v00, float v10, float v01, float v11, float u, float v) const; + + // --- random --- + void setRandomSeed(uint32_t seed); + void setRandomSeedFromTime(); + uint32_t getRandomSeed() const; + float random(); + float randomRange(float min, float max); + int randomInt(int min, int maxInclusive); + /** Box-Muller Gaussian (mean, stddev). */ + float randomGaussian(float mean, float stddev); + + // --- deterministic hash [0,1] (grid / WFC / tile seeds) --- + float hash1(float x) const; + float hash2(float x, float y) const; + float hash3(float x, float y, float z) const; + + // --- noise [0, 1] --- + float noise1(float x) const; + float noise2(float x, float y) const; + float noise3(float x, float y, float z) const; + float perlin2(float x, float y) const; + float perlin3(float x, float y, float z) const; + + /** + * Fractal Brownian Motion / ridged / turbulence. + * octaves >= 1; lacunarity ~2; gain/persistence ~0.5. + */ + float fbm2(float x, float y, int octaves = 4, float lacunarity = 2.f, + float gain = 0.5f) const; + float fbm3(float x, float y, float z, int octaves = 4, float lacunarity = 2.f, + float gain = 0.5f) const; + float ridged2(float x, float y, int octaves = 4, float lacunarity = 2.f, + float gain = 0.5f) const; + float ridged3(float x, float y, float z, int octaves = 4, float lacunarity = 2.f, + float gain = 0.5f) const; + float turbulence2(float x, float y, int octaves = 4, float lacunarity = 2.f, + float gain = 0.5f) const; + + /** + * Worley / Voronoi F1 distance in [0, ~1.5] (cell size 1). + * voronoiEdge2 = F2 - F1 (cell borders). + */ + float voronoi2(float x, float y) const; + float voronoiEdge2(float x, float y) const; + + /** + * Domain warp: sample noise at (x,y) + warpAmp * (noise-0.5). + * Useful for organic terrain / caves. + */ + float warpNoise2(float x, float y, float warpAmp = 1.f) const; + + // --- bezier --- + float bezierQuadratic(float t, float p0, float p1, float p2) const; + float bezierCubic(float t, float p0, float p1, float p2, float p3) const; + float bezierQuadratic2X(float t, float x0, float y0, float x1, float y1, float x2, + float y2) const; + float bezierQuadratic2Y(float t, float x0, float y0, float x1, float y1, float x2, + float y2) const; + float bezierCubic2X(float t, float x0, float y0, float x1, float y1, float x2, float y2, + float x3, float y3) const; + float bezierCubic2Y(float t, float x0, float y0, float x1, float y1, float x2, float y2, + float x3, float y3) const; + +private: + uint32_t seed_ = 1; + std::mt19937 rng_; +}; + +} // namespace eve::math diff --git a/src/modules/math/Vec2.cpp b/src/modules/math/Vec2.cpp new file mode 100644 index 00000000..438a103c --- /dev/null +++ b/src/modules/math/Vec2.cpp @@ -0,0 +1,61 @@ +#include "math/Vec2.h" + +#include "common/Exception.h" + +#include + +namespace eve::math { + +void Vec2::normalize() { + float len = length(); + if (len > 0.f) { + x_ /= len; + y_ /= len; + } +} + +Vec2 *Vec2::normalized() const { + auto *v = new Vec2(x_, y_); + v->normalize(); + return v; +} + +float Vec2::dot(const Vec2 *other) const { + if (!other) throw eve::Exception("Vec2.dot: other is null"); + return x_ * other->x_ + y_ * other->y_; +} + +float Vec2::cross(const Vec2 *other) const { + if (!other) throw eve::Exception("Vec2.cross: other is null"); + return x_ * other->y_ - y_ * other->x_; +} + +float Vec2::distanceTo(const Vec2 *other) const { + if (!other) throw eve::Exception("Vec2.distanceTo: other is null"); + float dx = x_ - other->x_; + float dy = y_ - other->y_; + return std::sqrt(dx * dx + dy * dy); +} + +float Vec2::angle() const { return std::atan2(y_, x_); } + +Vec2 *Vec2::add(const Vec2 *other) const { + if (!other) throw eve::Exception("Vec2.add: other is null"); + return new Vec2(x_ + other->x_, y_ + other->y_); +} + +Vec2 *Vec2::sub(const Vec2 *other) const { + if (!other) throw eve::Exception("Vec2.sub: other is null"); + return new Vec2(x_ - other->x_, y_ - other->y_); +} + +Vec2 *Vec2::scale(float s) const { return new Vec2(x_ * s, y_ * s); } + +Vec2 *Vec2::lerpTo(const Vec2 *other, float t) const { + if (!other) throw eve::Exception("Vec2.lerpTo: other is null"); + return new Vec2(x_ + (other->x_ - x_) * t, y_ + (other->y_ - y_) * t); +} + +Vec2 *Vec2::clone() const { return new Vec2(x_, y_); } + +} // namespace eve::math diff --git a/src/modules/math/Vec2.h b/src/modules/math/Vec2.h new file mode 100644 index 00000000..d825c199 --- /dev/null +++ b/src/modules/math/Vec2.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace eve::math { + +class Vec2 { +public: + Vec2() = default; + Vec2(float x, float y) : x_(x), y_(y) {} + + float getX() const { return x_; } + float getY() const { return y_; } + void setX(float x) { x_ = x; } + void setY(float y) { y_ = y; } + void set(float x, float y) { + x_ = x; + y_ = y; + } + + float length() const { return std::sqrt(x_ * x_ + y_ * y_); } + float lengthSquared() const { return x_ * x_ + y_ * y_; } + + void normalize(); + Vec2 *normalized() const; + + float dot(const Vec2 *other) const; + float cross(const Vec2 *other) const; + float distanceTo(const Vec2 *other) const; + float angle() const; + + Vec2 *add(const Vec2 *other) const; + Vec2 *sub(const Vec2 *other) const; + Vec2 *scale(float s) const; + Vec2 *lerpTo(const Vec2 *other, float t) const; + Vec2 *clone() const; + +private: + float x_ = 0.f; + float y_ = 0.f; +}; + +} // namespace eve::math diff --git a/src/modules/math/Vec3.cpp b/src/modules/math/Vec3.cpp new file mode 100644 index 00000000..831e6243 --- /dev/null +++ b/src/modules/math/Vec3.cpp @@ -0,0 +1,63 @@ +#include "math/Vec3.h" + +#include "common/Exception.h" + +#include + +namespace eve::math { + +void Vec3::normalize() { + float len = length(); + if (len > 0.f) { + x_ /= len; + y_ /= len; + z_ /= len; + } +} + +Vec3 *Vec3::normalized() const { + auto *v = new Vec3(x_, y_, z_); + v->normalize(); + return v; +} + +float Vec3::dot(const Vec3 *other) const { + if (!other) throw eve::Exception("Vec3.dot: other is null"); + return x_ * other->x_ + y_ * other->y_ + z_ * other->z_; +} + +Vec3 *Vec3::cross(const Vec3 *other) const { + if (!other) throw eve::Exception("Vec3.cross: other is null"); + return new Vec3(y_ * other->z_ - z_ * other->y_, z_ * other->x_ - x_ * other->z_, + x_ * other->y_ - y_ * other->x_); +} + +float Vec3::distanceTo(const Vec3 *other) const { + if (!other) throw eve::Exception("Vec3.distanceTo: other is null"); + float dx = x_ - other->x_; + float dy = y_ - other->y_; + float dz = z_ - other->z_; + return std::sqrt(dx * dx + dy * dy + dz * dz); +} + +Vec3 *Vec3::add(const Vec3 *other) const { + if (!other) throw eve::Exception("Vec3.add: other is null"); + return new Vec3(x_ + other->x_, y_ + other->y_, z_ + other->z_); +} + +Vec3 *Vec3::sub(const Vec3 *other) const { + if (!other) throw eve::Exception("Vec3.sub: other is null"); + return new Vec3(x_ - other->x_, y_ - other->y_, z_ - other->z_); +} + +Vec3 *Vec3::scale(float s) const { return new Vec3(x_ * s, y_ * s, z_ * s); } + +Vec3 *Vec3::lerpTo(const Vec3 *other, float t) const { + if (!other) throw eve::Exception("Vec3.lerpTo: other is null"); + return new Vec3(x_ + (other->x_ - x_) * t, y_ + (other->y_ - y_) * t, + z_ + (other->z_ - z_) * t); +} + +Vec3 *Vec3::clone() const { return new Vec3(x_, y_, z_); } + +} // namespace eve::math diff --git a/src/modules/math/Vec3.h b/src/modules/math/Vec3.h new file mode 100644 index 00000000..e6f49869 --- /dev/null +++ b/src/modules/math/Vec3.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace eve::math { + +class Vec3 { +public: + Vec3() = default; + Vec3(float x, float y, float z) : x_(x), y_(y), z_(z) {} + + float getX() const { return x_; } + float getY() const { return y_; } + float getZ() const { return z_; } + void setX(float x) { x_ = x; } + void setY(float y) { y_ = y; } + void setZ(float z) { z_ = z; } + void set(float x, float y, float z) { + x_ = x; + y_ = y; + z_ = z; + } + + float length() const { return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); } + float lengthSquared() const { return x_ * x_ + y_ * y_ + z_ * z_; } + + void normalize(); + Vec3 *normalized() const; + + float dot(const Vec3 *other) const; + Vec3 *cross(const Vec3 *other) const; + float distanceTo(const Vec3 *other) const; + + Vec3 *add(const Vec3 *other) const; + Vec3 *sub(const Vec3 *other) const; + Vec3 *scale(float s) const; + Vec3 *lerpTo(const Vec3 *other, float t) const; + Vec3 *clone() const; + +private: + float x_ = 0.f; + float y_ = 0.f; + float z_ = 0.f; +}; + +} // namespace eve::math diff --git a/src/modules/tensor/GpuBackend.cpp b/src/modules/tensor/GpuBackend.cpp new file mode 100644 index 00000000..3f3d71dc --- /dev/null +++ b/src/modules/tensor/GpuBackend.cpp @@ -0,0 +1,506 @@ +#include "tensor/GpuBackend.h" +#include "tensor/Graph.h" + +#include "gpgpu/Gpgpu.h" +#include "gpgpu/ComputeShader.h" +#include "gpgpu/GpuBuffer.h" + +#include "common/Exception.h" + +#include +#include +#include +#include +#include +#include + +namespace eve::tensor { +namespace { + +// --- shared compute kernels (compiled once, reused by every GpuProgram) --- +// +// All kernels use set=0 storage-buffer bindings + a 32-float push constant +// block (matches eve::gpgpu::ComputeShader's fixed layout). + +constexpr int kLocalSize = 256; + +const char *kBinaryGlsl = R"(#version 450 +layout(local_size_x = 256) in; +layout(set = 0, binding = 0) readonly buffer A { float a[]; }; +layout(set = 0, binding = 1) readonly buffer B { float b[]; }; +layout(set = 0, binding = 2) writeonly buffer C { float c[]; }; +layout(push_constant) uniform PC { float data[32]; } pc; +void main() { + uint i = gl_GlobalInvocationID.x; + uint size = uint(pc.data[0] + 0.5); + if (i >= size) return; + int op = int(pc.data[1] + 0.5); + float x = a[i]; + float y = b[i]; + float r = x; + if (op == 0) r = x + y; + else if (op == 1) r = x - y; + else if (op == 2) r = x * y; + else r = x / y; + c[i] = r; +} +)"; + +const char *kUnaryGlsl = R"(#version 450 +layout(local_size_x = 256) in; +layout(set = 0, binding = 0) readonly buffer In { float a[]; }; +layout(set = 0, binding = 1) writeonly buffer Out { float c[]; }; +layout(push_constant) uniform PC { float data[32]; } pc; +void main() { + uint i = gl_GlobalInvocationID.x; + uint size = uint(pc.data[0] + 0.5); + if (i >= size) return; + int op = int(pc.data[1] + 0.5); + float s0 = pc.data[2]; + float s1 = pc.data[3]; + float x = a[i]; + float r = x; + if (op == 0) r = -x; + else if (op == 1) r = abs(x); + else if (op == 2) r = sqrt(x); + else if (op == 3) r = exp(x); + else if (op == 4) r = log(x); + else if (op == 5) r = sin(x); + else if (op == 6) r = cos(x); + else if (op == 7) r = tanh(x); + else if (op == 8) r = x > 0.0 ? x : 0.0; + else if (op == 9) r = 1.0 / (1.0 + exp(-x)); + else if (op == 10) r = x + s0; + else if (op == 11) r = x - s0; + else if (op == 12) r = x * s0; + else if (op == 13) r = x / s0; + else if (op == 14) r = pow(x, s0); + else if (op == 15) { float lo = min(s0, s1); float hi = max(s0, s1); r = clamp(x, lo, hi); } + else if (op == 16) r = max(x, s0); + else if (op == 17) r = min(x, s0); + c[i] = r; +} +)"; + +const char *kMatMulGlsl = R"(#version 450 +layout(local_size_x = 256) in; +layout(set = 0, binding = 0) readonly buffer A { float a[]; }; +layout(set = 0, binding = 1) readonly buffer B { float b[]; }; +layout(set = 0, binding = 2) writeonly buffer C { float c[]; }; +layout(push_constant) uniform PC { float data[32]; } pc; +void main() { + uint idx = gl_GlobalInvocationID.x; + uint m = uint(pc.data[0] + 0.5); + uint k = uint(pc.data[1] + 0.5); + uint n = uint(pc.data[2] + 0.5); + if (idx >= m * n) return; + uint i = idx / n; + uint j = idx % n; + float acc = 0.0; + for (uint t = 0u; t < k; ++t) acc += a[i * k + t] * b[t * n + j]; + c[idx] = acc; +} +)"; + +const char *kTransposeGlsl = R"(#version 450 +layout(local_size_x = 256) in; +layout(set = 0, binding = 0) readonly buffer In { float a[]; }; +layout(set = 0, binding = 1) writeonly buffer Out { float b[]; }; +layout(push_constant) uniform PC { float data[32]; } pc; +void main() { + uint idx = gl_GlobalInvocationID.x; + uint rows = uint(pc.data[0] + 0.5); + uint cols = uint(pc.data[1] + 0.5); + if (idx >= rows * cols) return; + uint i = idx / cols; + uint j = idx % cols; + b[j * rows + i] = a[idx]; +} +)"; + +const char *kWhereGlsl = R"(#version 450 +layout(local_size_x = 256) in; +layout(set = 0, binding = 0) readonly buffer Cond { float cnd[]; }; +layout(set = 0, binding = 1) readonly buffer A { float a[]; }; +layout(set = 0, binding = 2) readonly buffer B { float b[]; }; +layout(set = 0, binding = 3) writeonly buffer Out { float o[]; }; +layout(push_constant) uniform PC { float data[32]; } pc; +void main() { + uint i = gl_GlobalInvocationID.x; + uint size = uint(pc.data[0] + 0.5); + if (i >= size) return; + o[i] = cnd[i] > 0.5 ? a[i] : b[i]; +} +)"; + +// Partial (per-workgroup) reduction; final combine happens on the CPU over +// the small `partial[]` output (one float per workgroup). +const char *kReduceGlsl = R"(#version 450 +layout(local_size_x = 256) in; +layout(set = 0, binding = 0) readonly buffer In { float a[]; }; +layout(set = 0, binding = 1) writeonly buffer Out { float partial[]; }; +layout(push_constant) uniform PC { float data[32]; } pc; +shared float sdata[256]; +void main() { + uint tid = gl_LocalInvocationID.x; + uint gid = gl_GlobalInvocationID.x; + uint size = uint(pc.data[0] + 0.5); + int op = int(pc.data[1] + 0.5); + float ident = (op == 0) ? 0.0 : (op == 1 ? 3.402823e38 : -3.402823e38); + sdata[tid] = (gid < size) ? a[gid] : ident; + barrier(); + for (uint s = 128u; s > 0u; s >>= 1u) { + if (tid < s) { + if (op == 0) sdata[tid] += sdata[tid + s]; + else if (op == 1) sdata[tid] = min(sdata[tid], sdata[tid + s]); + else sdata[tid] = max(sdata[tid], sdata[tid + s]); + } + barrier(); + } + if (tid == 0u) partial[gl_WorkGroupID.x] = sdata[0]; +} +)"; + +struct Kernels { + gpgpu::Gpgpu *gpgpu = nullptr; + gpgpu::ComputeShader *binary = nullptr; + gpgpu::ComputeShader *unary = nullptr; + gpgpu::ComputeShader *matmul = nullptr; + gpgpu::ComputeShader *transpose = nullptr; + gpgpu::ComputeShader *where = nullptr; + gpgpu::ComputeShader *reduce = nullptr; +}; + +/** Lazily compiled, process-lifetime singleton. Returns nullptr if unavailable. */ +Kernels *getKernels() { + static Kernels *kernels = nullptr; + static bool failed = false; + if (kernels) return kernels; + if (failed) return nullptr; + try { + auto *gp = gpgpu::Gpgpu::create(); + if (!gp || !gp->isAvailable()) { + failed = true; + return nullptr; + } + auto *k = new Kernels(); + k->gpgpu = gp; + k->binary = gp->newShader(kBinaryGlsl); + k->unary = gp->newShader(kUnaryGlsl); + k->matmul = gp->newShader(kMatMulGlsl); + k->transpose = gp->newShader(kTransposeGlsl); + k->where = gp->newShader(kWhereGlsl); + k->reduce = gp->newShader(kReduceGlsl); + kernels = k; + return kernels; + } catch (...) { + failed = true; + return nullptr; + } +} + +int groupsFor(int count) { return (count + kLocalSize - 1) / kLocalSize; } + +int binaryOpCode(OpType t) { + switch (t) { + case OpType::Add: return 0; + case OpType::Sub: return 1; + case OpType::Multiply: return 2; + case OpType::Divide: return 3; + default: return -1; + } +} + +int unaryOpCode(OpType t) { + switch (t) { + case OpType::Neg: return 0; + case OpType::Abs: return 1; + case OpType::Sqrt: return 2; + case OpType::Exp: return 3; + case OpType::Log: return 4; + case OpType::Sin: return 5; + case OpType::Cos: return 6; + case OpType::Tanh: return 7; + case OpType::Relu: return 8; + case OpType::Sigmoid: return 9; + case OpType::AddScalar: return 10; + case OpType::SubScalar: return 11; + case OpType::MulScalar: return 12; + case OpType::DivScalar: return 13; + case OpType::PowScalar: return 14; + case OpType::Clamp: return 15; + case OpType::MaximumScalar: return 16; + case OpType::MinimumScalar: return 17; + default: return -1; + } +} + +} // namespace + +struct GpuProgram::Impl { + struct Step { + enum class Kind { Binary, Unary, MatMul, Transpose, Where } kind; + int opCode = 0; + float s0 = 0.f, s1 = 0.f; + int size = 0; // elementwise / where element count + int m = 0, k = 0, n = 0; // matmul + int rows = 0, cols = 0; // transpose + gpgpu::GpuBuffer *in0 = nullptr; + gpgpu::GpuBuffer *in1 = nullptr; + gpgpu::GpuBuffer *in2 = nullptr; + gpgpu::GpuBuffer *out = nullptr; + }; + + Kernels *kernels = nullptr; + std::vector> ownedBuffers; + std::vector steps; + std::vector placeholderBuffers; + std::vector placeholderSizes; + gpgpu::GpuBuffer *outputBuffer = nullptr; + int outputSize = 0; + + void executeStep(const Step &st) const { + switch (st.kind) { + case Step::Kind::Binary: { + auto *sh = kernels->binary; + sh->bindBuffer(0, st.in0); + sh->bindBuffer(1, st.in1); + sh->bindBuffer(2, st.out); + sh->setFloat(0, float(st.size)); + sh->setFloat(1, float(st.opCode)); + kernels->gpgpu->dispatch(sh, groupsFor(st.size)); + break; + } + case Step::Kind::Unary: { + auto *sh = kernels->unary; + sh->bindBuffer(0, st.in0); + sh->bindBuffer(1, st.out); + sh->setFloat(0, float(st.size)); + sh->setFloat(1, float(st.opCode)); + sh->setFloat(2, st.s0); + sh->setFloat(3, st.s1); + kernels->gpgpu->dispatch(sh, groupsFor(st.size)); + break; + } + case Step::Kind::MatMul: { + auto *sh = kernels->matmul; + sh->bindBuffer(0, st.in0); + sh->bindBuffer(1, st.in1); + sh->bindBuffer(2, st.out); + sh->setFloat(0, float(st.m)); + sh->setFloat(1, float(st.k)); + sh->setFloat(2, float(st.n)); + kernels->gpgpu->dispatch(sh, groupsFor(st.m * st.n)); + break; + } + case Step::Kind::Transpose: { + auto *sh = kernels->transpose; + sh->bindBuffer(0, st.in0); + sh->bindBuffer(1, st.out); + sh->setFloat(0, float(st.rows)); + sh->setFloat(1, float(st.cols)); + kernels->gpgpu->dispatch(sh, groupsFor(st.rows * st.cols)); + break; + } + case Step::Kind::Where: { + auto *sh = kernels->where; + sh->bindBuffer(0, st.in0); + sh->bindBuffer(1, st.in1); + sh->bindBuffer(2, st.in2); + sh->bindBuffer(3, st.out); + sh->setFloat(0, float(st.size)); + kernels->gpgpu->dispatch(sh, groupsFor(st.size)); + break; + } + } + } +}; + +GpuProgram::GpuProgram() : impl_(new Impl()) {} + +GpuProgram::~GpuProgram() { delete impl_; } + +GpuProgram *GpuProgram::tryBuild(const Graph &graph, const std::vector &order, int outputNode) { + Kernels *kernels = getKernels(); + if (!kernels) return nullptr; + if (outputNode < 0 || outputNode >= graph.nodeCount()) return nullptr; + + auto *prog = new GpuProgram(); + prog->impl_->kernels = kernels; + std::unordered_map alias; // Reshape/Flatten -> source node id + std::unordered_map bufOf; + + std::function resolve = [&](int id) { + auto it = alias.find(id); + while (it != alias.end()) { + id = it->second; + it = alias.find(id); + } + return id; + }; + auto bufferFor = [&](int id) -> gpgpu::GpuBuffer * { return bufOf.at(resolve(id)); }; + auto newBuf = [&](int elemCount) -> gpgpu::GpuBuffer * { + auto *buf = kernels->gpgpu->newBuffer(elemCount * int(sizeof(float)), "storage"); + prog->impl_->ownedBuffers.emplace_back(buf); + return buf; + }; + + try { + for (int id : order) { + const GraphNode &nd = graph.node(id); + switch (nd.type) { + case OpType::Placeholder: { + auto *buf = newBuf(nd.size); + bufOf[id] = buf; + int slot = nd.placeholderSlot; + if (slot < 0) throw eve::Exception("GpuProgram: bad placeholder slot"); + if (int(prog->impl_->placeholderBuffers.size()) <= slot) { + prog->impl_->placeholderBuffers.resize(size_t(slot) + 1, nullptr); + prog->impl_->placeholderSizes.resize(size_t(slot) + 1, 0); + } + prog->impl_->placeholderBuffers[size_t(slot)] = buf; + prog->impl_->placeholderSizes[size_t(slot)] = nd.size; + break; + } + case OpType::Const: { + auto *buf = newBuf(nd.size); + buf->uploadBytes(nd.constData.data(), sizeof(float) * size_t(nd.size)); + bufOf[id] = buf; + break; + } + case OpType::Reshape: + case OpType::Flatten: { + alias[id] = nd.in0; + break; + } + case OpType::MatMul: { + const auto &A = graph.node(nd.in0); + const auto &B = graph.node(nd.in1); + Impl::Step st; + st.kind = Impl::Step::Kind::MatMul; + st.m = A.dims[0]; + st.k = A.dims[1]; + st.n = B.dims[1]; + st.in0 = bufferFor(nd.in0); + st.in1 = bufferFor(nd.in1); + st.out = newBuf(nd.size); + bufOf[id] = st.out; + prog->impl_->steps.push_back(st); + break; + } + case OpType::Transpose: { + const auto &X = graph.node(nd.in0); + Impl::Step st; + st.kind = Impl::Step::Kind::Transpose; + st.rows = X.dims[0]; + st.cols = X.dims[1]; + st.in0 = bufferFor(nd.in0); + st.out = newBuf(nd.size); + bufOf[id] = st.out; + prog->impl_->steps.push_back(st); + break; + } + case OpType::Where: { + Impl::Step st; + st.kind = Impl::Step::Kind::Where; + st.size = nd.size; + st.in0 = bufferFor(nd.in0); + st.in1 = bufferFor(nd.in1); + st.in2 = bufferFor(nd.in2); + st.out = newBuf(nd.size); + bufOf[id] = st.out; + prog->impl_->steps.push_back(st); + break; + } + default: { + int bop = binaryOpCode(nd.type); + if (bop >= 0) { + Impl::Step st; + st.kind = Impl::Step::Kind::Binary; + st.opCode = bop; + st.size = nd.size; + st.in0 = bufferFor(nd.in0); + st.in1 = bufferFor(nd.in1); + st.out = newBuf(nd.size); + bufOf[id] = st.out; + prog->impl_->steps.push_back(st); + break; + } + int uop = unaryOpCode(nd.type); + if (uop >= 0) { + Impl::Step st; + st.kind = Impl::Step::Kind::Unary; + st.opCode = uop; + st.s0 = nd.s0; + st.s1 = nd.s1; + st.size = nd.size; + st.in0 = bufferFor(nd.in0); + st.out = newBuf(nd.size); + bufOf[id] = st.out; + prog->impl_->steps.push_back(st); + break; + } + // Unknown / unsupported op type — bail out to the CPU interpreter. + throw eve::Exception("GpuProgram: unsupported op"); + } + } + } + } catch (...) { + delete prog; + return nullptr; + } + + prog->impl_->outputBuffer = bufOf.count(resolve(outputNode)) ? bufOf.at(resolve(outputNode)) : nullptr; + prog->impl_->outputSize = graph.node(outputNode).size; + if (!prog->impl_->outputBuffer) { + delete prog; + return nullptr; + } + return prog; +} + +std::vector GpuProgram::run(const std::vector &feeds) const { + for (size_t i = 0; i < feeds.size(); ++i) { + if (i >= impl_->placeholderBuffers.size() || !impl_->placeholderBuffers[i]) continue; + impl_->placeholderBuffers[i]->uploadBytes(feeds[i], sizeof(float) * size_t(impl_->placeholderSizes[i])); + } + for (const auto &st : impl_->steps) impl_->executeStep(st); + std::vector out(size_t(impl_->outputSize)); + impl_->outputBuffer->downloadBytes(out.data(), sizeof(float) * size_t(impl_->outputSize)); + return out; +} + +bool gpuReduce(const float *data, int size, int op, float &outResult) { + if (!data || size <= 0) return false; + Kernels *kernels = getKernels(); + if (!kernels) return false; + try { + std::unique_ptr in(kernels->gpgpu->newBuffer(size * int(sizeof(float)), "storage")); + in->uploadBytes(data, sizeof(float) * size_t(size)); + + const int groups = groupsFor(size); + std::unique_ptr partial(kernels->gpgpu->newBuffer(groups * int(sizeof(float)), "storage")); + + kernels->reduce->bindBuffer(0, in.get()); + kernels->reduce->bindBuffer(1, partial.get()); + kernels->reduce->setFloat(0, float(size)); + kernels->reduce->setFloat(1, float(op)); + kernels->gpgpu->dispatch(kernels->reduce, groups); + + std::vector parts(static_cast(groups)); + partial->downloadBytes(parts.data(), sizeof(float) * size_t(groups)); + + float acc = op == 0 ? 0.f : (op == 1 ? std::numeric_limits::max() : -std::numeric_limits::max()); + for (float v : parts) { + if (op == 0) acc += v; + else if (op == 1) acc = std::min(acc, v); + else acc = std::max(acc, v); + } + outResult = acc; + return true; + } catch (...) { + return false; + } +} + +} // namespace eve::tensor diff --git a/src/modules/tensor/GpuBackend.h b/src/modules/tensor/GpuBackend.h new file mode 100644 index 00000000..83a75579 --- /dev/null +++ b/src/modules/tensor/GpuBackend.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +namespace eve::tensor { + +class Graph; + +/** + * GPU execution of a compiled tensor Graph via eve::gpgpu compute shaders. + * + * Built once per CompiledFunction (shapes are static at compile time): every + * graph node gets a GPU storage buffer, ops are mapped to a handful of shared + * compute kernels (binary / unary / matmul / transpose / where), and the + * dispatch order is precomputed. run() only needs to re-upload placeholder + * feeds, replay the dispatch sequence, and download the output. + * + * tryBuild() never throws: it returns nullptr when Vulkan/gpgpu isn't + * available (no window yet, missing glslc, unsupported driver, ...), in + * which case the caller should fall back to the CPU interpreter. + */ +class GpuProgram { +public: + ~GpuProgram(); + + static GpuProgram *tryBuild(const Graph &graph, const std::vector &order, int outputNode); + + /** feeds[slot] must point to `placeholderSize(slot)` floats. Returns the output buffer. */ + std::vector run(const std::vector &feeds) const; + +private: + GpuProgram(); + GpuProgram(const GpuProgram &) = delete; + GpuProgram &operator=(const GpuProgram &) = delete; + + struct Impl; + Impl *impl_ = nullptr; +}; + +/** + * GPU-accelerated reduction for large eager tensors. + * op: 0 = sum, 1 = min, 2 = max. Returns false (caller should fall back to CPU) + * when Vulkan/gpgpu isn't available. + */ +bool gpuReduce(const float *data, int size, int op, float &outResult); + +} // namespace eve::tensor diff --git a/src/modules/tensor/Graph.cpp b/src/modules/tensor/Graph.cpp new file mode 100644 index 00000000..4038dc43 --- /dev/null +++ b/src/modules/tensor/Graph.cpp @@ -0,0 +1,545 @@ +#include "tensor/Graph.h" +#include "tensor/Tensor.h" +#include "tensor/TF.h" +#include "tensor/GpuBackend.h" + +#include "common/Exception.h" + +#include +#include +#include +#include + +namespace eve::tensor { +namespace { + +bool isUnaryElementwise(OpType t) { + switch (t) { + case OpType::Neg: + case OpType::Abs: + case OpType::Sqrt: + case OpType::Exp: + case OpType::Log: + case OpType::Sin: + case OpType::Cos: + case OpType::Tanh: + case OpType::Relu: + case OpType::Sigmoid: + case OpType::AddScalar: + case OpType::SubScalar: + case OpType::MulScalar: + case OpType::DivScalar: + case OpType::PowScalar: + case OpType::Clamp: + case OpType::MaximumScalar: + case OpType::MinimumScalar: + return true; + default: + return false; + } +} + +float applyUnary(OpType t, float x, float s0, float s1) { + switch (t) { + case OpType::Neg: return -x; + case OpType::Abs: return std::fabs(x); + case OpType::Sqrt: return std::sqrt(x); + case OpType::Exp: return std::exp(x); + case OpType::Log: return std::log(x); + case OpType::Sin: return std::sin(x); + case OpType::Cos: return std::cos(x); + case OpType::Tanh: return std::tanh(x); + case OpType::Relu: return x > 0.f ? x : 0.f; + case OpType::Sigmoid: return 1.f / (1.f + std::exp(-x)); + case OpType::AddScalar: return x + s0; + case OpType::SubScalar: return x - s0; + case OpType::MulScalar: return x * s0; + case OpType::DivScalar: return x / s0; + case OpType::PowScalar: return std::pow(x, s0); + case OpType::Clamp: { + float lo = s0, hi = s1; + if (lo > hi) std::swap(lo, hi); + return std::clamp(x, lo, hi); + } + case OpType::MaximumScalar: return std::max(x, s0); + case OpType::MinimumScalar: return std::min(x, s0); + default: return x; + } +} + +} // namespace + +int Graph::product(const int *dims, int rank) { + int n = 1; + for (int i = 0; i < rank; ++i) { + if (dims[i] <= 0) throw eve::Exception("Graph: dims must be > 0"); + n *= dims[i]; + } + return n; +} + +int Graph::addNode(GraphNode node) { + if (node.rank > 0 && node.size <= 0) node.size = product(node.dims, node.rank); + nodes_.push_back(std::move(node)); + return int(nodes_.size()) - 1; +} + +void optimizeGraph(Graph &g, int outputNode, std::vector &order) { + const int n = g.nodeCount(); + if (n <= 0 || outputNode < 0 || outputNode >= n) + throw eve::Exception("optimizeGraph: invalid output"); + + std::vector need(static_cast(n), 0); + std::vector stack = {outputNode}; + while (!stack.empty()) { + int u = stack.back(); + stack.pop_back(); + if (u < 0 || u >= n || need[static_cast(u)]) continue; + need[static_cast(u)] = 1; + const auto &nd = g.node(u); + if (nd.in0 >= 0) stack.push_back(nd.in0); + if (nd.in1 >= 0) stack.push_back(nd.in1); + if (nd.in2 >= 0) stack.push_back(nd.in2); + } + + std::vector indeg(static_cast(n), 0); + std::vector> outs(static_cast(n)); + for (int i = 0; i < n; ++i) { + if (!need[static_cast(i)]) continue; + const auto &nd = g.node(i); + auto link = [&](int from) { + if (from < 0 || !need[static_cast(from)]) return; + outs[static_cast(from)].push_back(i); + indeg[static_cast(i)]++; + }; + link(nd.in0); + link(nd.in1); + link(nd.in2); + } + + std::queue q; + for (int i = 0; i < n; ++i) { + if (need[static_cast(i)] && indeg[static_cast(i)] == 0) q.push(i); + } + + order.clear(); + while (!q.empty()) { + int u = q.front(); + q.pop(); + order.push_back(u); + for (int v : outs[static_cast(u)]) { + if (--indeg[static_cast(v)] == 0) q.push(v); + } + } + if (int(order.size()) != int(std::count(need.begin(), need.end(), char(1)))) + throw eve::Exception("optimizeGraph: cycle in graph"); + // Unary elementwise chains are fused at execute time (see executeNode). +} + +Func::Func(TF *owner) : owner_(owner) { + if (!owner_) throw eve::Exception("Func: null owner"); + owner_->pushTrace(this); +} + +Func::~Func() { + if (tracing_ && owner_) owner_->popTrace(this); +} + +GraphNode Func::makeShapeNode(OpType type, const int *dims, int rank) { + GraphNode n; + n.type = type; + n.rank = rank; + for (int i = 0; i < 4; ++i) n.dims[i] = 0; + for (int i = 0; i < rank; ++i) n.dims[i] = dims[i]; + n.size = Graph::product(dims, rank); + return n; +} + +Tensor *Func::makeSymbolicFromNode(int nodeId) { + const auto &n = graph_.node(nodeId); + return Tensor::makeSymbolic(&graph_, nodeId, n.dims, n.rank); +} + +Tensor *Func::input1(int d0) { + int d[] = {d0}; + auto n = makeShapeNode(OpType::Placeholder, d, 1); + n.placeholderSlot = placeholderCount_++; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +Tensor *Func::input2(int d0, int d1) { + int d[] = {d0, d1}; + auto n = makeShapeNode(OpType::Placeholder, d, 2); + n.placeholderSlot = placeholderCount_++; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +Tensor *Func::input3(int d0, int d1, int d2) { + int d[] = {d0, d1, d2}; + auto n = makeShapeNode(OpType::Placeholder, d, 3); + n.placeholderSlot = placeholderCount_++; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +Tensor *Func::input4(int d0, int d1, int d2, int d3) { + int d[] = {d0, d1, d2, d3}; + auto n = makeShapeNode(OpType::Placeholder, d, 4); + n.placeholderSlot = placeholderCount_++; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +void Func::setOutput(Tensor *t) { + if (!t) throw eve::Exception("Func.setOutput: null"); + outputNode_ = ensureNode(t); +} + +int Func::ensureNode(const Tensor *t) { + if (!t) throw eve::Exception("Func.ensureNode: null"); + if (t->isSymbolic()) { + if (t->graph() != &graph_) + throw eve::Exception("Func: tensor from another graph"); + return t->nodeId(); + } + t->ensureEager("capture"); + auto n = makeShapeNode(OpType::Const, t->dims_, t->rank_); + n.constData.assign(t->data(), t->data() + t->getSize()); + return graph_.addNode(std::move(n)); +} + +Tensor *Func::emitFill(const int *dims, int rank, float value) { + auto n = makeShapeNode(OpType::Const, dims, rank); + n.constData.assign(static_cast(n.size), value); + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +Tensor *Func::emitUnary(OpType type, const Tensor *x) { + int ix = ensureNode(x); + const auto &src = graph_.node(ix); + auto n = makeShapeNode(type, src.dims, src.rank); + n.in0 = ix; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +Tensor *Func::emitUnaryScalar(OpType type, const Tensor *x, float s0, float s1) { + int ix = ensureNode(x); + const auto &src = graph_.node(ix); + auto n = makeShapeNode(type, src.dims, src.rank); + n.in0 = ix; + n.s0 = s0; + n.s1 = s1; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +Tensor *Func::emitBinary(OpType type, const Tensor *a, const Tensor *b) { + int ia = ensureNode(a); + int ib = ensureNode(b); + const auto &na = graph_.node(ia); + const auto &nb = graph_.node(ib); + if (na.rank != nb.rank || na.size != nb.size) + throw eve::Exception("Func binary: shape mismatch"); + for (int i = 0; i < na.rank; ++i) + if (na.dims[i] != nb.dims[i]) throw eve::Exception("Func binary: shape mismatch"); + auto n = makeShapeNode(type, na.dims, na.rank); + n.in0 = ia; + n.in1 = ib; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +Tensor *Func::emitTernary(OpType type, const Tensor *a, const Tensor *b, const Tensor *c) { + int ia = ensureNode(a); + int ib = ensureNode(b); + int ic = ensureNode(c); + const auto &na = graph_.node(ia); + auto n = makeShapeNode(type, na.dims, na.rank); + n.in0 = ia; + n.in1 = ib; + n.in2 = ic; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +Tensor *Func::emitMatMul(const Tensor *a, const Tensor *b) { + int ia = ensureNode(a); + int ib = ensureNode(b); + const auto &na = graph_.node(ia); + const auto &nb = graph_.node(ib); + if (na.rank != 2 || nb.rank != 2) throw eve::Exception("Func.matmul: rank 2 required"); + if (na.dims[1] != nb.dims[0]) throw eve::Exception("Func.matmul: inner dims mismatch"); + int od[] = {na.dims[0], nb.dims[1]}; + auto n = makeShapeNode(OpType::MatMul, od, 2); + n.in0 = ia; + n.in1 = ib; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +Tensor *Func::emitTranspose(const Tensor *x) { + int ix = ensureNode(x); + const auto &src = graph_.node(ix); + if (src.rank != 2) throw eve::Exception("Func.transpose: rank 2 required"); + int od[] = {src.dims[1], src.dims[0]}; + auto n = makeShapeNode(OpType::Transpose, od, 2); + n.in0 = ix; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +Tensor *Func::emitReshape(const Tensor *x, const int *dims, int rank) { + int ix = ensureNode(x); + const auto &src = graph_.node(ix); + int newSize = Graph::product(dims, rank); + if (newSize != src.size) throw eve::Exception("Func.reshape: size mismatch"); + auto n = makeShapeNode(OpType::Reshape, dims, rank); + n.in0 = ix; + int id = graph_.addNode(std::move(n)); + return makeSymbolicFromNode(id); +} + +CompiledFunction *Func::compile() { + if (outputNode_ < 0) throw eve::Exception("Func.compile: setOutput required"); + tracing_ = false; + if (owner_) owner_->popTrace(this); + return CompiledFunction::fromFunc(this); +} + +CompiledFunction::CompiledFunction() = default; +CompiledFunction::~CompiledFunction() = default; + +CompiledFunction *CompiledFunction::fromFunc(Func *fn) { + if (!fn) throw eve::Exception("CompiledFunction: null func"); + auto *cf = new CompiledFunction(); + cf->graph_ = fn->graph(); // copy + cf->outputNode_ = fn->outputNode(); + cf->placeholderCount_ = fn->placeholderCount(); + cf->device_ = "cpu"; + optimizeGraph(cf->graph_, cf->outputNode_, cf->order_); + + // Best-effort: run on GPU via eve::gpgpu compute shaders when a Vulkan + // device is available (window already created) and glslc can compile the + // shared kernels. Falls back to the CPU interpreter below otherwise. + try { + cf->gpuProgram_.reset(GpuProgram::tryBuild(cf->graph_, cf->order_, cf->outputNode_)); + } catch (...) { + cf->gpuProgram_.reset(); + } + if (cf->gpuProgram_) cf->device_ = "gpu"; + return cf; +} + +Tensor *CompiledFunction::run0() { return runWithFeeds(nullptr, 0); } + +Tensor *CompiledFunction::run1(Tensor *in0) { + Tensor *feeds[] = {in0}; + return runWithFeeds(feeds, 1); +} + +Tensor *CompiledFunction::run2(Tensor *in0, Tensor *in1) { + Tensor *feeds[] = {in0, in1}; + return runWithFeeds(feeds, 2); +} + +Tensor *CompiledFunction::run3(Tensor *in0, Tensor *in1, Tensor *in2) { + Tensor *feeds[] = {in0, in1, in2}; + return runWithFeeds(feeds, 3); +} + +Tensor *CompiledFunction::run4(Tensor *in0, Tensor *in1, Tensor *in2, Tensor *in3) { + Tensor *feeds[] = {in0, in1, in2, in3}; + return runWithFeeds(feeds, 4); +} + +Tensor *CompiledFunction::runWithFeeds(Tensor *const *feeds, int nFeeds) { + if (nFeeds != placeholderCount_) + throw eve::Exception("CompiledFunction.run: expected %d feeds, got %d", placeholderCount_, + nFeeds); + for (int i = 0; i < nFeeds; ++i) { + if (!feeds[i]) throw eve::Exception("CompiledFunction.run: null feed"); + feeds[i]->ensureEager("run"); + } + + const int n = graph_.nodeCount(); + + // Validate placeholder shapes (shared by the GPU and CPU execution paths). + for (int i = 0; i < n; ++i) { + const auto &nd = graph_.node(i); + if (nd.type != OpType::Placeholder) continue; + int slot = nd.placeholderSlot; + if (slot < 0 || slot >= nFeeds) throw eve::Exception("CompiledFunction: bad placeholder slot"); + Tensor *feed = feeds[slot]; + if (feed->getRank() != nd.rank || feed->getSize() != nd.size) + throw eve::Exception("CompiledFunction: feed shape mismatch"); + for (int a = 0; a < nd.rank; ++a) + if (feed->getDim(a) != nd.dims[a]) + throw eve::Exception("CompiledFunction: feed shape mismatch"); + } + + if (gpuProgram_) { + std::vector ptrs(static_cast(nFeeds)); + for (int i = 0; i < nFeeds; ++i) ptrs[static_cast(i)] = feeds[i]->data(); + std::vector result = gpuProgram_->run(ptrs); + + const auto &outN = graph_.node(outputNode_); + auto *out = new Tensor(outN.dims, outN.rank); + if (int(result.size()) != out->getSize()) + throw eve::Exception("CompiledFunction: gpu output size mismatch"); + std::memcpy(out->data(), result.data(), sizeof(float) * static_cast(out->getSize())); + return out; + } + + std::vector> bufs(static_cast(n)); + for (int i = 0; i < n; ++i) { + const auto &nd = graph_.node(i); + if (nd.type != OpType::Placeholder) continue; + Tensor *feed = feeds[nd.placeholderSlot]; + bufs[static_cast(i)].assign(feed->data(), feed->data() + feed->getSize()); + } + + for (int nodeId : order_) executeNode(nodeId, bufs); + + const auto &outN = graph_.node(outputNode_); + auto *out = new Tensor(outN.dims, outN.rank); + const auto &src = bufs[static_cast(outputNode_)]; + if (int(src.size()) != out->getSize()) + throw eve::Exception("CompiledFunction: output size mismatch"); + std::memcpy(out->data(), src.data(), sizeof(float) * static_cast(out->getSize())); + return out; +} + +void CompiledFunction::executeNode(int nodeId, std::vector> &bufs) const { + const auto &nd = graph_.node(nodeId); + auto &out = bufs[static_cast(nodeId)]; + + switch (nd.type) { + case OpType::Placeholder: + return; // already filled + case OpType::Const: + out = nd.constData; + return; + case OpType::Add: + case OpType::Sub: + case OpType::Multiply: + case OpType::Divide: { + const auto &a = bufs[static_cast(nd.in0)]; + const auto &b = bufs[static_cast(nd.in1)]; + out.resize(static_cast(nd.size)); + for (int i = 0; i < nd.size; ++i) { + float x = a[static_cast(i)]; + float y = b[static_cast(i)]; + float r = 0.f; + switch (nd.type) { + case OpType::Add: r = x + y; break; + case OpType::Sub: r = x - y; break; + case OpType::Multiply: r = x * y; break; + case OpType::Divide: r = x / y; break; + default: break; + } + out[static_cast(i)] = r; + } + return; + } + case OpType::Neg: + case OpType::Abs: + case OpType::Sqrt: + case OpType::Exp: + case OpType::Log: + case OpType::Sin: + case OpType::Cos: + case OpType::Tanh: + case OpType::Relu: + case OpType::Sigmoid: + case OpType::AddScalar: + case OpType::SubScalar: + case OpType::MulScalar: + case OpType::DivScalar: + case OpType::PowScalar: + case OpType::Clamp: + case OpType::MaximumScalar: + case OpType::MinimumScalar: { + // Fuse unary chain: walk back through single-use unary parents in order + // For simplicity execute this node; optional multi-op fusion: + const auto &a = bufs[static_cast(nd.in0)]; + out.resize(static_cast(nd.size)); + // Collect chain of unary ops ending at this node (from root input) + std::vector chain; + int cur = nodeId; + while (true) { + const auto &cn = graph_.node(cur); + if (!isUnaryElementwise(cn.type)) break; + chain.push_back(&cn); + // only fuse if input is also unary we will process — stop at non-unary buffer + int pred = cn.in0; + if (pred < 0) break; + const auto &pn = graph_.node(pred); + if (!isUnaryElementwise(pn.type)) break; + // fuse only single-consumer chains roughly: always include immediate preds that are unary + cur = pred; + } + std::reverse(chain.begin(), chain.end()); + // Input buffer is the in0 of the first chain node + int inputId = chain.front()->in0; + // If we walked into another unary, input is that unary's input + // Actually chain includes only unaries; first's in0 is the data source + const auto &src = bufs[static_cast(inputId)]; + for (int i = 0; i < nd.size; ++i) { + float v = src[static_cast(i)]; + for (const GraphNode *step : chain) v = applyUnary(step->type, v, step->s0, step->s1); + out[static_cast(i)] = v; + } + return; + } + case OpType::MatMul: { + const auto &A = graph_.node(nd.in0); + const auto &B = graph_.node(nd.in1); + const auto &a = bufs[static_cast(nd.in0)]; + const auto &b = bufs[static_cast(nd.in1)]; + int m = A.dims[0], k = A.dims[1], n = B.dims[1]; + out.assign(static_cast(m * n), 0.f); + for (int i = 0; i < m; ++i) { + for (int j = 0; j < n; ++j) { + double acc = 0.0; + for (int t = 0; t < k; ++t) + acc += double(a[static_cast(i * k + t)]) * + double(b[static_cast(t * n + j)]); + out[static_cast(i * n + j)] = float(acc); + } + } + return; + } + case OpType::Transpose: { + const auto &X = graph_.node(nd.in0); + const auto &a = bufs[static_cast(nd.in0)]; + int r = X.dims[0], c = X.dims[1]; + out.resize(static_cast(r * c)); + for (int i = 0; i < r; ++i) + for (int j = 0; j < c; ++j) + out[static_cast(j * r + i)] = a[static_cast(i * c + j)]; + return; + } + case OpType::Reshape: + case OpType::Flatten: { + out = bufs[static_cast(nd.in0)]; + return; + } + case OpType::Where: { + const auto &c = bufs[static_cast(nd.in0)]; + const auto &a = bufs[static_cast(nd.in1)]; + const auto &b = bufs[static_cast(nd.in2)]; + out.resize(static_cast(nd.size)); + for (int i = 0; i < nd.size; ++i) + out[static_cast(i)] = + c[static_cast(i)] > 0.5f ? a[static_cast(i)] + : b[static_cast(i)]; + return; + } + } +} + +} // namespace eve::tensor diff --git a/src/modules/tensor/Graph.h b/src/modules/tensor/Graph.h new file mode 100644 index 00000000..f4f60289 --- /dev/null +++ b/src/modules/tensor/Graph.h @@ -0,0 +1,159 @@ +#pragma once + +#include +#include +#include +#include + +namespace eve::tensor { + +class Tensor; +class TF; +class Func; +class GpuProgram; + +enum class OpType : uint8_t { + Placeholder = 0, + Const, + Add, + Sub, + Multiply, + Divide, + AddScalar, + SubScalar, + MulScalar, + DivScalar, + Neg, + Abs, + Sqrt, + Exp, + Log, + Sin, + Cos, + Tanh, + Relu, + Sigmoid, + PowScalar, + Clamp, + MaximumScalar, + MinimumScalar, + MatMul, + Transpose, + Reshape, + Flatten, + Where, +}; + +struct GraphNode { + OpType type = OpType::Const; + int dims[4] = {0, 0, 0, 0}; + int rank = 0; + int size = 0; + int in0 = -1; + int in1 = -1; + int in2 = -1; + float s0 = 0.f; + float s1 = 0.f; + int placeholderSlot = -1; + std::vector constData; +}; + +class Graph { +public: + int addNode(GraphNode node); + const GraphNode &node(int id) const { return nodes_[static_cast(id)]; } + GraphNode &node(int id) { return nodes_[static_cast(id)]; } + int nodeCount() const { return int(nodes_.size()); } + const std::vector &nodes() const { return nodes_; } + std::vector &nodes() { return nodes_; } + + static int product(const int *dims, int rank); + +private: + std::vector nodes_; +}; + +/** + * Trace builder — TF2 `tf.function` analogue (`tf.func` in scripts). + * While active, TF ops record into this graph. + */ +class Func { +public: + explicit Func(TF *owner); + ~Func(); + + Tensor *input1(int d0); + Tensor *input2(int d0, int d1); + Tensor *input3(int d0, int d1, int d2); + Tensor *input4(int d0, int d1, int d2, int d3); + + void setOutput(Tensor *t); + + class CompiledFunction *compile(); + + Graph &graph() { return graph_; } + const Graph &graph() const { return graph_; } + TF *owner() const { return owner_; } + bool isTracing() const { return tracing_; } + int outputNode() const { return outputNode_; } + int placeholderCount() const { return placeholderCount_; } + + /** Ensure tensor is a node in this graph (Const-capture if eager). */ + int ensureNode(const Tensor *t); + + Tensor *emitUnary(OpType type, const Tensor *x); + Tensor *emitUnaryScalar(OpType type, const Tensor *x, float s0, float s1 = 0.f); + Tensor *emitBinary(OpType type, const Tensor *a, const Tensor *b); + Tensor *emitTernary(OpType type, const Tensor *a, const Tensor *b, const Tensor *c); + Tensor *emitMatMul(const Tensor *a, const Tensor *b); + Tensor *emitTranspose(const Tensor *x); + Tensor *emitReshape(const Tensor *x, const int *dims, int rank); + Tensor *emitFill(const int *dims, int rank, float value); + +private: + Tensor *makeSymbolicFromNode(int nodeId); + GraphNode makeShapeNode(OpType type, const int *dims, int rank); + + TF *owner_ = nullptr; + Graph graph_; + int outputNode_ = -1; + int placeholderCount_ = 0; + bool tracing_ = true; +}; + +/** + * Optimized / scheduled graph ready to run with feeds. + */ +class CompiledFunction { +public: + CompiledFunction(); + ~CompiledFunction(); + + Tensor *run0(); + Tensor *run1(Tensor *in0); + Tensor *run2(Tensor *in0, Tensor *in1); + Tensor *run3(Tensor *in0, Tensor *in1, Tensor *in2); + Tensor *run4(Tensor *in0, Tensor *in1, Tensor *in2, Tensor *in3); + + int getPlaceholderCount() const { return placeholderCount_; } + std::string getDevice() const { return device_; } + + static CompiledFunction *fromFunc(Func *fn); + +private: + Tensor *runWithFeeds(Tensor *const *feeds, int nFeeds); + void executeNode(int nodeId, std::vector> &bufs) const; + + Graph graph_; + std::vector order_; + int outputNode_ = -1; + int placeholderCount_ = 0; + std::string device_ = "cpu"; + /** Set when the graph could be built for GPU execution (see GpuBackend.cpp). */ + std::unique_ptr gpuProgram_; +}; + +/** Cheap unary-chain fusion: collapse A->unary->unary into one fused pass marker via rewriting. */ +void optimizeGraph(Graph &g, int outputNode, std::vector &order); + +} // namespace eve::tensor diff --git a/src/modules/tensor/TF.cpp b/src/modules/tensor/TF.cpp new file mode 100644 index 00000000..81a19580 --- /dev/null +++ b/src/modules/tensor/TF.cpp @@ -0,0 +1,524 @@ +#include "tensor/TF.h" +#include "tensor/Tensor.h" +#include "tensor/Graph.h" +#include "tensor/GpuBackend.h" + +#include "common/Exception.h" + +#include + +#include +#include + +namespace eve::tensor { +namespace { +// Below this element count the GPU dispatch/readback round-trip costs more +// than the CPU loop saves; only worth trying the compute-shader path above it. +constexpr int kGpuReduceMinSize = 1 << 14; // 16384 floats +} // namespace + +Module_IMPL(TF, new TF()); + +TF::TF() : seed_(1), rngState_(1) {} + +float TF::nextUniform() const { + rngState_ = rngState_ * 1664525u + 1013904223u; + return float(rngState_ >> 8) * (1.f / 16777216.f); +} + +float TF::nextGaussian() const { + float u1 = nextUniform(); + float u2 = nextUniform(); + if (u1 < 1e-7f) u1 = 1e-7f; + return std::sqrt(-2.f * std::log(u1)) * std::cos(6.28318530718f * u2); +} + +void TF::setRandomSeed(uint32_t seed) { + seed_ = seed == 0 ? 1u : seed; + rngState_ = seed_; +} + +uint32_t TF::getRandomSeed() const { return seed_; } + +void TF::pushTrace(Func *f) { + if (f) traceStack_.push_back(f); +} + +void TF::popTrace(Func *f) { + if (traceStack_.empty()) return; + if (traceStack_.back() == f) { + traceStack_.pop_back(); + return; + } + for (auto it = traceStack_.begin(); it != traceStack_.end(); ++it) { + if (*it == f) { + traceStack_.erase(it); + return; + } + } +} + +Func *TF::tracing() const { + return traceStack_.empty() ? nullptr : traceStack_.back(); +} + +Func *TF::func() { return new Func(this); } + +Tensor *TF::filled(const int *dims, int rank, float value) { + if (Func *f = tracing()) return f->emitFill(dims, rank, value); + auto *t = new Tensor(dims, rank); + t->fill(value); + return t; +} + +Tensor *TF::zeros1(int d0) { + int d[] = {d0}; + return filled(d, 1, 0.f); +} +Tensor *TF::zeros2(int d0, int d1) { + int d[] = {d0, d1}; + return filled(d, 2, 0.f); +} +Tensor *TF::zeros3(int d0, int d1, int d2) { + int d[] = {d0, d1, d2}; + return filled(d, 3, 0.f); +} +Tensor *TF::zeros4(int d0, int d1, int d2, int d3) { + int d[] = {d0, d1, d2, d3}; + return filled(d, 4, 0.f); +} + +Tensor *TF::ones1(int d0) { + int d[] = {d0}; + return filled(d, 1, 1.f); +} +Tensor *TF::ones2(int d0, int d1) { + int d[] = {d0, d1}; + return filled(d, 2, 1.f); +} +Tensor *TF::ones3(int d0, int d1, int d2) { + int d[] = {d0, d1, d2}; + return filled(d, 3, 1.f); +} + +Tensor *TF::fill1(int d0, float value) { + int d[] = {d0}; + return filled(d, 1, value); +} +Tensor *TF::fill2(int d0, int d1, float value) { + int d[] = {d0, d1}; + return filled(d, 2, value); +} +Tensor *TF::fill3(int d0, int d1, int d2, float value) { + int d[] = {d0, d1, d2}; + return filled(d, 3, value); +} + +Tensor *TF::constantScalar(float value) { + int d[] = {1}; + return filled(d, 1, value); +} + +Tensor *TF::arange(int n) { + if (n <= 0) throw Exception("TF.arange: n must be > 0"); + if (tracing()) throw Exception("TF.arange: not supported while tracing (use inputs/constants)"); + auto *t = new Tensor(n); + for (int i = 0; i < n; ++i) t->set1(i, float(i)); + return t; +} + +Tensor *TF::linspace(float start, float end, int n) { + if (n <= 0) throw Exception("TF.linspace: n must be > 0"); + if (tracing()) throw Exception("TF.linspace: not supported while tracing"); + auto *t = new Tensor(n); + if (n == 1) { + t->set1(0, start); + return t; + } + float step = (end - start) / float(n - 1); + for (int i = 0; i < n; ++i) t->set1(i, start + step * float(i)); + return t; +} + +Tensor *TF::eye(int n) { + if (n <= 0) throw Exception("TF.eye: n must be > 0"); + if (Func *f = tracing()) { + // Const identity + auto *eager = new Tensor(n, n); + for (int i = 0; i < n; ++i) eager->set2(i, i, 1.f); + int id = f->ensureNode(eager); + delete eager; + return Tensor::makeSymbolic(&f->graph(), id, f->graph().node(id).dims, + f->graph().node(id).rank); + } + auto *t = new Tensor(n, n); + for (int i = 0; i < n; ++i) t->set2(i, i, 1.f); + return t; +} + +Tensor *TF::randomUniform1(int d0) { + if (tracing()) throw Exception("TF.randomUniform: not supported while tracing"); + auto *t = zeros1(d0); + for (int i = 0; i < t->getSize(); ++i) t->set(i, nextUniform()); + return t; +} + +Tensor *TF::randomUniform2(int d0, int d1) { + if (tracing()) throw Exception("TF.randomUniform: not supported while tracing"); + auto *t = zeros2(d0, d1); + for (int i = 0; i < t->getSize(); ++i) t->set(i, nextUniform()); + return t; +} + +Tensor *TF::randomNormal1(int d0) { + if (tracing()) throw Exception("TF.randomNormal: not supported while tracing"); + auto *t = zeros1(d0); + for (int i = 0; i < t->getSize(); ++i) t->set(i, nextGaussian()); + return t; +} + +Tensor *TF::randomNormal2(int d0, int d1) { + if (tracing()) throw Exception("TF.randomNormal: not supported while tracing"); + auto *t = zeros2(d0, d1); + for (int i = 0; i < t->getSize(); ++i) t->set(i, nextGaussian()); + return t; +} + +#define EVE_TF_UNARY(name, method, opType) \ + Tensor *TF::name(Tensor *a) { \ + if (!a) throw Exception("TF." #name ": null"); \ + if (Func *f = tracing()) return f->emitUnary(opType, a); \ + a->ensureEager(#name); \ + return a->method(); \ + } + +EVE_TF_UNARY(neg, neg, OpType::Neg) +EVE_TF_UNARY(abs, abs, OpType::Abs) +EVE_TF_UNARY(sqrt, sqrt, OpType::Sqrt) +EVE_TF_UNARY(exp, exp, OpType::Exp) +EVE_TF_UNARY(log, log, OpType::Log) +EVE_TF_UNARY(sin, sin, OpType::Sin) +EVE_TF_UNARY(cos, cos, OpType::Cos) +EVE_TF_UNARY(tanh, tanh, OpType::Tanh) +EVE_TF_UNARY(relu, relu, OpType::Relu) +EVE_TF_UNARY(sigmoid, sigmoid, OpType::Sigmoid) + +#undef EVE_TF_UNARY + +#define EVE_TF_BINARY(name, method, opType) \ + Tensor *TF::name(Tensor *a, Tensor *b) { \ + if (!a || !b) throw Exception("TF." #name ": null"); \ + if (Func *f = tracing()) return f->emitBinary(opType, a, b); \ + a->ensureEager(#name); \ + b->ensureEager(#name); \ + return a->method(b); \ + } + +EVE_TF_BINARY(add, add, OpType::Add) +EVE_TF_BINARY(sub, sub, OpType::Sub) +EVE_TF_BINARY(multiply, multiply, OpType::Multiply) +EVE_TF_BINARY(div, div, OpType::Divide) + +#undef EVE_TF_BINARY + +Tensor *TF::addScalar(Tensor *a, float s) { + if (!a) throw Exception("TF.addScalar: null"); + if (Func *f = tracing()) return f->emitUnaryScalar(OpType::AddScalar, a, s); + return a->addScalar(s); +} +Tensor *TF::subScalar(Tensor *a, float s) { + if (!a) throw Exception("TF.subScalar: null"); + if (Func *f = tracing()) return f->emitUnaryScalar(OpType::SubScalar, a, s); + return a->subScalar(s); +} +Tensor *TF::mulScalar(Tensor *a, float s) { + if (!a) throw Exception("TF.mulScalar: null"); + if (Func *f = tracing()) return f->emitUnaryScalar(OpType::MulScalar, a, s); + return a->mulScalar(s); +} +Tensor *TF::divScalar(Tensor *a, float s) { + if (!a) throw Exception("TF.divScalar: null"); + if (Func *f = tracing()) return f->emitUnaryScalar(OpType::DivScalar, a, s); + return a->divScalar(s); +} +Tensor *TF::powScalar(Tensor *a, float exp) { + if (!a) throw Exception("TF.powScalar: null"); + if (Func *f = tracing()) return f->emitUnaryScalar(OpType::PowScalar, a, exp); + return a->powScalar(exp); +} +Tensor *TF::clamp(Tensor *a, float lo, float hi) { + if (!a) throw Exception("TF.clamp: null"); + if (Func *f = tracing()) return f->emitUnaryScalar(OpType::Clamp, a, lo, hi); + return a->clamp(lo, hi); +} +Tensor *TF::maximumScalar(Tensor *a, float s) { + if (!a) throw Exception("TF.maximumScalar: null"); + if (Func *f = tracing()) return f->emitUnaryScalar(OpType::MaximumScalar, a, s); + return a->maximumScalar(s); +} +Tensor *TF::minimumScalar(Tensor *a, float s) { + if (!a) throw Exception("TF.minimumScalar: null"); + if (Func *f = tracing()) return f->emitUnaryScalar(OpType::MinimumScalar, a, s); + return a->minimumScalar(s); +} + +Tensor *TF::matmul(Tensor *a, Tensor *b) { + if (!a || !b) throw Exception("TF.matmul: null"); + if (Func *f = tracing()) return f->emitMatMul(a, b); + return a->matmul(b); +} + +Tensor *TF::transpose(Tensor *a) { + if (!a) throw Exception("TF.transpose: null"); + if (Func *f = tracing()) return f->emitTranspose(a); + return a->transpose(); +} + +Tensor *TF::reshape1(Tensor *a, int d0) { + if (!a) throw Exception("TF.reshape1: null"); + int d[] = {d0}; + if (Func *f = tracing()) return f->emitReshape(a, d, 1); + return a->reshape1(d0); +} +Tensor *TF::reshape2(Tensor *a, int d0, int d1) { + if (!a) throw Exception("TF.reshape2: null"); + int d[] = {d0, d1}; + if (Func *f = tracing()) return f->emitReshape(a, d, 2); + return a->reshape2(d0, d1); +} +Tensor *TF::reshape3(Tensor *a, int d0, int d1, int d2) { + if (!a) throw Exception("TF.reshape3: null"); + int d[] = {d0, d1, d2}; + if (Func *f = tracing()) return f->emitReshape(a, d, 3); + return a->reshape3(d0, d1, d2); +} +Tensor *TF::reshape4(Tensor *a, int d0, int d1, int d2, int d3) { + if (!a) throw Exception("TF.reshape4: null"); + int d[] = {d0, d1, d2, d3}; + if (Func *f = tracing()) return f->emitReshape(a, d, 4); + return a->reshape4(d0, d1, d2, d3); +} +Tensor *TF::flatten(Tensor *a) { + if (!a) throw Exception("TF.flatten: null"); + int d[] = {a->getSize()}; + if (Func *f = tracing()) return f->emitReshape(a, d, 1); + return a->flatten(); +} + +Tensor *TF::where(Tensor *cond, Tensor *a, Tensor *b) { + if (!cond || !a || !b) throw Exception("TF.where: null"); + if (Func *f = tracing()) return f->emitTernary(OpType::Where, cond, a, b); + if (cond->getRank() != a->getRank() || a->getRank() != b->getRank() || + cond->getSize() != a->getSize() || a->getSize() != b->getSize()) + throw Exception("TF.where: shape mismatch"); + for (int i = 0; i < cond->getRank(); ++i) { + if (cond->getDim(i) != a->getDim(i) || a->getDim(i) != b->getDim(i)) + throw Exception("TF.where: shape mismatch"); + } + int dims[Tensor::kMaxRank] = {}; + for (int i = 0; i < a->getRank(); ++i) dims[i] = a->getDim(i); + auto *out = new Tensor(dims, a->getRank()); + for (int i = 0; i < a->getSize(); ++i) + out->set(i, cond->get(i) > 0.5f ? a->get(i) : b->get(i)); + return out; +} + +float TF::reduceSum(Tensor *a) { + if (!a) throw Exception("TF.reduceSum: null"); + if (tracing()) throw Exception("TF.reduceSum: not supported while tracing"); + a->ensureEager("reduceSum"); + float gpuResult = 0.f; + if (a->getSize() >= kGpuReduceMinSize && gpuReduce(a->data(), a->getSize(), 0, gpuResult)) + return gpuResult; + return a->reduceSum(); +} +float TF::reduceMean(Tensor *a) { + if (!a) throw Exception("TF.reduceMean: null"); + if (tracing()) throw Exception("TF.reduceMean: not supported while tracing"); + a->ensureEager("reduceMean"); + if (a->getSize() <= 0) return 0.f; + return reduceSum(a) / float(a->getSize()); +} +float TF::reduceMin(Tensor *a) { + if (!a) throw Exception("TF.reduceMin: null"); + if (tracing()) throw Exception("TF.reduceMin: not supported while tracing"); + a->ensureEager("reduceMin"); + float gpuResult = 0.f; + if (a->getSize() >= kGpuReduceMinSize && gpuReduce(a->data(), a->getSize(), 1, gpuResult)) + return gpuResult; + return a->reduceMin(); +} +float TF::reduceMax(Tensor *a) { + if (!a) throw Exception("TF.reduceMax: null"); + if (tracing()) throw Exception("TF.reduceMax: not supported while tracing"); + a->ensureEager("reduceMax"); + float gpuResult = 0.f; + if (a->getSize() >= kGpuReduceMinSize && gpuReduce(a->data(), a->getSize(), 2, gpuResult)) + return gpuResult; + return a->reduceMax(); +} + +void TF::expose(ssq::Table &table) { + auto cls = table.addClass(name, TF::create, false); + expose(cls); + + auto ten = table.addClass( + "Tensor", std::function([]() -> Tensor * { return nullptr; }), true); + + ten.addFunc("isSymbolic", &Tensor::isSymbolic); + ten.addFunc("isEager", &Tensor::isEager); + ten.addFunc("getRank", &Tensor::getRank); + ten.addFunc("getSize", &Tensor::getSize); + ten.addFunc("getDim", &Tensor::getDim); + ten.addFunc("getDim0", &Tensor::getDim0); + ten.addFunc("getDim1", &Tensor::getDim1); + ten.addFunc("getDim2", &Tensor::getDim2); + ten.addFunc("getDim3", &Tensor::getDim3); + ten.addFunc("getDevice", &Tensor::getDevice); + ten.addFunc("getDtype", &Tensor::getDtype); + + ten.addFunc("get", &Tensor::get); + ten.addFunc("set", &Tensor::set); + ten.addFunc("get1", &Tensor::get1); + ten.addFunc("set1", &Tensor::set1); + ten.addFunc("get2", &Tensor::get2); + ten.addFunc("set2", &Tensor::set2); + ten.addFunc("get3", &Tensor::get3); + ten.addFunc("set3", &Tensor::set3); + ten.addFunc("get4", &Tensor::get4); + ten.addFunc("set4", &Tensor::set4); + + ten.addFunc("fill", &Tensor::fill); + ten.addFunc("copyFrom", &Tensor::copyFrom); + ten.addFunc("clone", &Tensor::clone); + + ten.addFunc("add", &Tensor::add); + ten.addFunc("sub", &Tensor::sub); + ten.addFunc("multiply", &Tensor::multiply); + ten.addFunc("div", &Tensor::div); + ten.addFunc("addScalar", &Tensor::addScalar); + ten.addFunc("subScalar", &Tensor::subScalar); + ten.addFunc("mulScalar", &Tensor::mulScalar); + ten.addFunc("divScalar", &Tensor::divScalar); + ten.addFunc("neg", &Tensor::neg); + ten.addFunc("abs", &Tensor::abs); + ten.addFunc("sqrt", &Tensor::sqrt); + ten.addFunc("exp", &Tensor::exp); + ten.addFunc("log", &Tensor::log); + ten.addFunc("sin", &Tensor::sin); + ten.addFunc("cos", &Tensor::cos); + ten.addFunc("tanh", &Tensor::tanh); + ten.addFunc("relu", &Tensor::relu); + ten.addFunc("sigmoid", &Tensor::sigmoid); + ten.addFunc("powScalar", &Tensor::powScalar); + ten.addFunc("clamp", &Tensor::clamp); + ten.addFunc("maximumScalar", &Tensor::maximumScalar); + ten.addFunc("minimumScalar", &Tensor::minimumScalar); + + ten.addFunc("addInPlace", &Tensor::addInPlace); + ten.addFunc("multiplyInPlace", &Tensor::multiplyInPlace); + ten.addFunc("addScalarInPlace", &Tensor::addScalarInPlace); + ten.addFunc("mulScalarInPlace", &Tensor::mulScalarInPlace); + ten.addFunc("reluInPlace", &Tensor::reluInPlace); + + ten.addFunc("reduceSum", &Tensor::reduceSum); + ten.addFunc("reduceMean", &Tensor::reduceMean); + ten.addFunc("reduceMin", &Tensor::reduceMin); + ten.addFunc("reduceMax", &Tensor::reduceMax); + ten.addFunc("dot", &Tensor::dot); + + ten.addFunc("matmul", &Tensor::matmul); + ten.addFunc("transpose", &Tensor::transpose); + ten.addFunc("reshape1", &Tensor::reshape1); + ten.addFunc("reshape2", &Tensor::reshape2); + ten.addFunc("reshape3", &Tensor::reshape3); + ten.addFunc("reshape4", &Tensor::reshape4); + ten.addFunc("flatten", &Tensor::flatten); + + auto fn = table.addClass( + "Func", std::function([]() -> Func * { return nullptr; }), true); + fn.addFunc("input1", &Func::input1); + fn.addFunc("input2", &Func::input2); + fn.addFunc("input3", &Func::input3); + fn.addFunc("input4", &Func::input4); + fn.addFunc("setOutput", &Func::setOutput); + fn.addFunc("compile", &Func::compile); + + auto cf = table.addClass( + "CompiledFunction", + std::function([]() -> CompiledFunction * { return nullptr; }), true); + cf.addFunc("run0", &CompiledFunction::run0); + cf.addFunc("run1", &CompiledFunction::run1); + cf.addFunc("run2", &CompiledFunction::run2); + cf.addFunc("run3", &CompiledFunction::run3); + cf.addFunc("run4", &CompiledFunction::run4); + cf.addFunc("getPlaceholderCount", &CompiledFunction::getPlaceholderCount); + cf.addFunc("getDevice", &CompiledFunction::getDevice); +} + +void TF::expose(ssq::Class &cls) { + cls.addFunc("getName", &TF::getName); + cls.addFunc("func", &TF::func); + + cls.addFunc("zeros1", &TF::zeros1); + cls.addFunc("zeros2", &TF::zeros2); + cls.addFunc("zeros3", &TF::zeros3); + cls.addFunc("zeros4", &TF::zeros4); + cls.addFunc("ones1", &TF::ones1); + cls.addFunc("ones2", &TF::ones2); + cls.addFunc("ones3", &TF::ones3); + cls.addFunc("fill1", &TF::fill1); + cls.addFunc("fill2", &TF::fill2); + cls.addFunc("fill3", &TF::fill3); + cls.addFunc("constantScalar", &TF::constantScalar); + cls.addFunc("arange", &TF::arange); + cls.addFunc("linspace", &TF::linspace); + cls.addFunc("eye", &TF::eye); + cls.addFunc("randomUniform1", &TF::randomUniform1); + cls.addFunc("randomUniform2", &TF::randomUniform2); + cls.addFunc("randomNormal1", &TF::randomNormal1); + cls.addFunc("randomNormal2", &TF::randomNormal2); + cls.addFunc("rand1", &TF::rand1); + cls.addFunc("rand2", &TF::rand2); + cls.addFunc("randn1", &TF::randn1); + cls.addFunc("randn2", &TF::randn2); + cls.addFunc("setRandomSeed", &TF::setRandomSeed); + cls.addFunc("getRandomSeed", &TF::getRandomSeed); + + cls.addFunc("add", &TF::add); + cls.addFunc("sub", &TF::sub); + cls.addFunc("multiply", &TF::multiply); + cls.addFunc("div", &TF::div); + cls.addFunc("addScalar", &TF::addScalar); + cls.addFunc("subScalar", &TF::subScalar); + cls.addFunc("mulScalar", &TF::mulScalar); + cls.addFunc("divScalar", &TF::divScalar); + cls.addFunc("neg", &TF::neg); + cls.addFunc("abs", &TF::abs); + cls.addFunc("sqrt", &TF::sqrt); + cls.addFunc("exp", &TF::exp); + cls.addFunc("log", &TF::log); + cls.addFunc("sin", &TF::sin); + cls.addFunc("cos", &TF::cos); + cls.addFunc("tanh", &TF::tanh); + cls.addFunc("relu", &TF::relu); + cls.addFunc("sigmoid", &TF::sigmoid); + cls.addFunc("powScalar", &TF::powScalar); + cls.addFunc("clamp", &TF::clamp); + cls.addFunc("maximumScalar", &TF::maximumScalar); + cls.addFunc("minimumScalar", &TF::minimumScalar); + cls.addFunc("matmul", &TF::matmul); + cls.addFunc("transpose", &TF::transpose); + cls.addFunc("reshape1", &TF::reshape1); + cls.addFunc("reshape2", &TF::reshape2); + cls.addFunc("reshape3", &TF::reshape3); + cls.addFunc("reshape4", &TF::reshape4); + cls.addFunc("flatten", &TF::flatten); + cls.addFunc("where", &TF::where); + cls.addFunc("reduceSum", &TF::reduceSum); + cls.addFunc("reduceMean", &TF::reduceMean); + cls.addFunc("reduceMin", &TF::reduceMin); + cls.addFunc("reduceMax", &TF::reduceMax); +} + +} // namespace eve::tensor diff --git a/src/modules/tensor/TF.h b/src/modules/tensor/TF.h new file mode 100644 index 00000000..b177328b --- /dev/null +++ b/src/modules/tensor/TF.h @@ -0,0 +1,111 @@ +#pragma once + +#include "common/Module.h" + +#include +#include + +namespace eve::tensor { + +class Tensor; +class Func; +class CompiledFunction; + +/** + * TF2-like namespace module. Script: `tf <- eve.TF();` + * Default eager; `tf.func()` traces a graph for compile/run. + */ +class TF : public Module { +public: + Module_REG(TF); + TF(); + ~TF() override = default; + + Func *func(); + + void pushTrace(Func *f); + void popTrace(Func *f); + Func *tracing() const; + + // --- factories (eager, or Const nodes while tracing) --- + Tensor *zeros1(int d0); + Tensor *zeros2(int d0, int d1); + Tensor *zeros3(int d0, int d1, int d2); + Tensor *zeros4(int d0, int d1, int d2, int d3); + + Tensor *ones1(int d0); + Tensor *ones2(int d0, int d1); + Tensor *ones3(int d0, int d1, int d2); + + Tensor *fill1(int d0, float value); + Tensor *fill2(int d0, int d1, float value); + Tensor *fill3(int d0, int d1, int d2, float value); + + Tensor *constantScalar(float value); + Tensor *arange(int n); + Tensor *linspace(float start, float end, int n); + Tensor *eye(int n); + + Tensor *randomUniform1(int d0); + Tensor *randomUniform2(int d0, int d1); + Tensor *randomNormal1(int d0); + Tensor *randomNormal2(int d0, int d1); + + // aliases + Tensor *rand1(int d0) { return randomUniform1(d0); } + Tensor *rand2(int d0, int d1) { return randomUniform2(d0, d1); } + Tensor *randn1(int d0) { return randomNormal1(d0); } + Tensor *randn2(int d0, int d1) { return randomNormal2(d0, d1); } + + void setRandomSeed(uint32_t seed); + uint32_t getRandomSeed() const; + + // --- module-level ops (TF style) --- + Tensor *add(Tensor *a, Tensor *b); + Tensor *sub(Tensor *a, Tensor *b); + Tensor *multiply(Tensor *a, Tensor *b); + Tensor *div(Tensor *a, Tensor *b); + Tensor *addScalar(Tensor *a, float s); + Tensor *subScalar(Tensor *a, float s); + Tensor *mulScalar(Tensor *a, float s); + Tensor *divScalar(Tensor *a, float s); + Tensor *neg(Tensor *a); + Tensor *abs(Tensor *a); + Tensor *sqrt(Tensor *a); + Tensor *exp(Tensor *a); + Tensor *log(Tensor *a); + Tensor *sin(Tensor *a); + Tensor *cos(Tensor *a); + Tensor *tanh(Tensor *a); + Tensor *relu(Tensor *a); + Tensor *sigmoid(Tensor *a); + Tensor *powScalar(Tensor *a, float exp); + Tensor *clamp(Tensor *a, float lo, float hi); + Tensor *maximumScalar(Tensor *a, float s); + Tensor *minimumScalar(Tensor *a, float s); + + Tensor *matmul(Tensor *a, Tensor *b); + Tensor *transpose(Tensor *a); + Tensor *reshape1(Tensor *a, int d0); + Tensor *reshape2(Tensor *a, int d0, int d1); + Tensor *reshape3(Tensor *a, int d0, int d1, int d2); + Tensor *reshape4(Tensor *a, int d0, int d1, int d2, int d3); + Tensor *flatten(Tensor *a); + Tensor *where(Tensor *cond, Tensor *a, Tensor *b); + + float reduceSum(Tensor *a); + float reduceMean(Tensor *a); + float reduceMin(Tensor *a); + float reduceMax(Tensor *a); + +private: + uint32_t seed_ = 1; + mutable uint32_t rngState_ = 1; + std::vector traceStack_; + + float nextUniform() const; + float nextGaussian() const; + Tensor *filled(const int *dims, int rank, float value); +}; + +} // namespace eve::tensor diff --git a/src/modules/tensor/Tensor.cpp b/src/modules/tensor/Tensor.cpp new file mode 100644 index 00000000..6b1d0c87 --- /dev/null +++ b/src/modules/tensor/Tensor.cpp @@ -0,0 +1,408 @@ +#include "tensor/Tensor.h" +#include "tensor/Graph.h" + +#include "common/Exception.h" + +#include +#include +#include + +namespace eve::tensor { +namespace { + +int productLocal(const int *dims, int rank) { + int n = 1; + for (int i = 0; i < rank; ++i) { + if (dims[i] <= 0) throw eve::Exception("Tensor: dims must be > 0"); + n *= dims[i]; + } + return n; +} + +} // namespace + +void Tensor::initDims(const int *dims, int rank) { + if (rank < 1 || rank > kMaxRank) + throw eve::Exception("Tensor: rank must be 1..%d", kMaxRank); + rank_ = rank; + for (int i = 0; i < kMaxRank; ++i) dims_[i] = 0; + for (int i = 0; i < rank; ++i) dims_[i] = dims[i]; + size_ = productLocal(dims, rank); + data_.assign(static_cast(size_), 0.f); + device_ = "cpu"; + kind_ = Kind::Eager; + graph_ = nullptr; + nodeId_ = -1; +} + +Tensor::Tensor(const int *dims, int rank) { initDims(dims, rank); } +Tensor::Tensor(int d0) { + int d[] = {d0}; + initDims(d, 1); +} +Tensor::Tensor(int d0, int d1) { + int d[] = {d0, d1}; + initDims(d, 2); +} +Tensor::Tensor(int d0, int d1, int d2) { + int d[] = {d0, d1, d2}; + initDims(d, 3); +} +Tensor::Tensor(int d0, int d1, int d2, int d3) { + int d[] = {d0, d1, d2, d3}; + initDims(d, 4); +} + +Tensor *Tensor::makeSymbolic(Graph *graph, int nodeId, const int *dims, int rank) { + auto *t = new Tensor(); + t->kind_ = Kind::Symbolic; + t->graph_ = graph; + t->nodeId_ = nodeId; + t->rank_ = rank; + for (int i = 0; i < kMaxRank; ++i) t->dims_[i] = 0; + for (int i = 0; i < rank; ++i) t->dims_[i] = dims[i]; + t->size_ = productLocal(dims, rank); + t->device_ = "cpu"; + return t; +} + +void Tensor::ensureEager(const char *op) const { + if (kind_ != Kind::Eager) + throw eve::Exception("Tensor.%s: symbolic tensor has no value (compile/run first)", op); +} + +int Tensor::getDim(int axis) const { + if (axis < 0 || axis >= rank_) throw eve::Exception("Tensor.getDim: axis out of range"); + return dims_[axis]; +} + +int Tensor::offset2(int i0, int i1) const { + if (rank_ != 2) throw eve::Exception("Tensor: expected rank 2"); + if (i0 < 0 || i0 >= dims_[0] || i1 < 0 || i1 >= dims_[1]) + throw eve::Exception("Tensor: index out of range"); + return i0 * dims_[1] + i1; +} + +int Tensor::offset3(int i0, int i1, int i2) const { + if (rank_ != 3) throw eve::Exception("Tensor: expected rank 3"); + if (i0 < 0 || i0 >= dims_[0] || i1 < 0 || i1 >= dims_[1] || i2 < 0 || i2 >= dims_[2]) + throw eve::Exception("Tensor: index out of range"); + return (i0 * dims_[1] + i1) * dims_[2] + i2; +} + +int Tensor::offset4(int i0, int i1, int i2, int i3) const { + if (rank_ != 4) throw eve::Exception("Tensor: expected rank 4"); + if (i0 < 0 || i0 >= dims_[0] || i1 < 0 || i1 >= dims_[1] || i2 < 0 || i2 >= dims_[2] || + i3 < 0 || i3 >= dims_[3]) + throw eve::Exception("Tensor: index out of range"); + return ((i0 * dims_[1] + i1) * dims_[2] + i2) * dims_[3] + i3; +} + +float *Tensor::data() { + ensureEager("data"); + return data_.data(); +} +const float *Tensor::data() const { + ensureEager("data"); + return data_.data(); +} + +float Tensor::get(int flatIndex) const { + ensureEager("get"); + if (flatIndex < 0 || flatIndex >= size_) throw eve::Exception("Tensor.get: index out of range"); + return data_[static_cast(flatIndex)]; +} + +void Tensor::set(int flatIndex, float value) { + ensureEager("set"); + if (flatIndex < 0 || flatIndex >= size_) throw eve::Exception("Tensor.set: index out of range"); + data_[static_cast(flatIndex)] = value; +} + +float Tensor::get1(int i0) const { + if (rank_ != 1) throw eve::Exception("Tensor.get1: expected rank 1"); + return get(i0); +} +void Tensor::set1(int i0, float value) { + if (rank_ != 1) throw eve::Exception("Tensor.set1: expected rank 1"); + set(i0, value); +} +float Tensor::get2(int i0, int i1) const { + ensureEager("get2"); + return data_[static_cast(offset2(i0, i1))]; +} +void Tensor::set2(int i0, int i1, float value) { + ensureEager("set2"); + data_[static_cast(offset2(i0, i1))] = value; +} +float Tensor::get3(int i0, int i1, int i2) const { + ensureEager("get3"); + return data_[static_cast(offset3(i0, i1, i2))]; +} +void Tensor::set3(int i0, int i1, int i2, float value) { + ensureEager("set3"); + data_[static_cast(offset3(i0, i1, i2))] = value; +} +float Tensor::get4(int i0, int i1, int i2, int i3) const { + ensureEager("get4"); + return data_[static_cast(offset4(i0, i1, i2, i3))]; +} +void Tensor::set4(int i0, int i1, int i2, int i3, float value) { + ensureEager("set4"); + data_[static_cast(offset4(i0, i1, i2, i3))] = value; +} + +void Tensor::fill(float value) { + ensureEager("fill"); + std::fill(data_.begin(), data_.end(), value); +} + +void Tensor::copyFrom(const Tensor *other) { + ensureEager("copyFrom"); + if (!other) throw eve::Exception("Tensor.copyFrom: other is null"); + other->ensureEager("copyFrom"); + checkSameShape(other, "copyFrom"); + std::memcpy(data_.data(), other->data_.data(), sizeof(float) * static_cast(size_)); +} + +Tensor *Tensor::clone() const { + ensureEager("clone"); + auto *out = new Tensor(dims_, rank_); + out->data_ = data_; + out->device_ = device_; + return out; +} + +void Tensor::checkSameShape(const Tensor *other, const char *op) const { + if (!other) throw eve::Exception("Tensor.%s: other is null", op); + if (rank_ != other->rank_ || size_ != other->size_) + throw eve::Exception("Tensor.%s: shape mismatch", op); + for (int i = 0; i < rank_; ++i) { + if (dims_[i] != other->dims_[i]) throw eve::Exception("Tensor.%s: shape mismatch", op); + } +} + +#define EVE_TENSOR_BINARY_OP(name, expr) \ + Tensor *Tensor::name(const Tensor *other) const { \ + ensureEager(#name); \ + other->ensureEager(#name); \ + checkSameShape(other, #name); \ + auto *out = new Tensor(dims_, rank_); \ + const float *a = data_.data(); \ + const float *b = other->data_.data(); \ + float *o = out->data_.data(); \ + for (int i = 0; i < size_; ++i) { \ + float x = a[i]; \ + float y = b[i]; \ + o[i] = (expr); \ + } \ + return out; \ + } + +EVE_TENSOR_BINARY_OP(add, x + y) +EVE_TENSOR_BINARY_OP(sub, x - y) +EVE_TENSOR_BINARY_OP(multiply, x * y) +EVE_TENSOR_BINARY_OP(div, x / y) + +#undef EVE_TENSOR_BINARY_OP + +#define EVE_TENSOR_UNARY_SCALAR(name, expr) \ + Tensor *Tensor::name(float s) const { \ + ensureEager(#name); \ + auto *out = new Tensor(dims_, rank_); \ + const float *a = data_.data(); \ + float *o = out->data_.data(); \ + for (int i = 0; i < size_; ++i) { \ + float x = a[i]; \ + o[i] = (expr); \ + } \ + return out; \ + } + +EVE_TENSOR_UNARY_SCALAR(addScalar, x + s) +EVE_TENSOR_UNARY_SCALAR(subScalar, x - s) +EVE_TENSOR_UNARY_SCALAR(mulScalar, x * s) +EVE_TENSOR_UNARY_SCALAR(divScalar, x / s) +EVE_TENSOR_UNARY_SCALAR(powScalar, std::pow(x, s)) +EVE_TENSOR_UNARY_SCALAR(maximumScalar, std::max(x, s)) +EVE_TENSOR_UNARY_SCALAR(minimumScalar, std::min(x, s)) + +#undef EVE_TENSOR_UNARY_SCALAR + +#define EVE_TENSOR_UNARY(name, expr) \ + Tensor *Tensor::name() const { \ + ensureEager(#name); \ + auto *out = new Tensor(dims_, rank_); \ + const float *a = data_.data(); \ + float *o = out->data_.data(); \ + for (int i = 0; i < size_; ++i) { \ + float x = a[i]; \ + o[i] = (expr); \ + } \ + return out; \ + } + +EVE_TENSOR_UNARY(neg, -x) +EVE_TENSOR_UNARY(abs, std::fabs(x)) +EVE_TENSOR_UNARY(sqrt, std::sqrt(x)) +EVE_TENSOR_UNARY(exp, std::exp(x)) +EVE_TENSOR_UNARY(log, std::log(x)) +EVE_TENSOR_UNARY(sin, std::sin(x)) +EVE_TENSOR_UNARY(cos, std::cos(x)) +EVE_TENSOR_UNARY(tanh, std::tanh(x)) +EVE_TENSOR_UNARY(relu, x > 0.f ? x : 0.f) +EVE_TENSOR_UNARY(sigmoid, 1.f / (1.f + std::exp(-x))) + +#undef EVE_TENSOR_UNARY + +Tensor *Tensor::clamp(float lo, float hi) const { + ensureEager("clamp"); + if (lo > hi) std::swap(lo, hi); + auto *out = new Tensor(dims_, rank_); + const float *a = data_.data(); + float *o = out->data_.data(); + for (int i = 0; i < size_; ++i) o[i] = std::clamp(a[i], lo, hi); + return out; +} + +void Tensor::addInPlace(const Tensor *other) { + ensureEager("addInPlace"); + other->ensureEager("addInPlace"); + checkSameShape(other, "addInPlace"); + float *a = data_.data(); + const float *b = other->data_.data(); + for (int i = 0; i < size_; ++i) a[i] += b[i]; +} + +void Tensor::multiplyInPlace(const Tensor *other) { + ensureEager("multiplyInPlace"); + other->ensureEager("multiplyInPlace"); + checkSameShape(other, "multiplyInPlace"); + float *a = data_.data(); + const float *b = other->data_.data(); + for (int i = 0; i < size_; ++i) a[i] *= b[i]; +} + +void Tensor::addScalarInPlace(float s) { + ensureEager("addScalarInPlace"); + float *a = data_.data(); + for (int i = 0; i < size_; ++i) a[i] += s; +} + +void Tensor::mulScalarInPlace(float s) { + ensureEager("mulScalarInPlace"); + float *a = data_.data(); + for (int i = 0; i < size_; ++i) a[i] *= s; +} + +void Tensor::reluInPlace() { + ensureEager("reluInPlace"); + float *a = data_.data(); + for (int i = 0; i < size_; ++i) + if (a[i] < 0.f) a[i] = 0.f; +} + +float Tensor::reduceSum() const { + ensureEager("reduceSum"); + double acc = 0.0; + for (int i = 0; i < size_; ++i) acc += data_[static_cast(i)]; + return float(acc); +} + +float Tensor::reduceMean() const { + return size_ > 0 ? reduceSum() / float(size_) : 0.f; +} + +float Tensor::reduceMin() const { + ensureEager("reduceMin"); + if (size_ <= 0) return 0.f; + float m = data_[0]; + for (int i = 1; i < size_; ++i) m = std::min(m, data_[static_cast(i)]); + return m; +} + +float Tensor::reduceMax() const { + ensureEager("reduceMax"); + if (size_ <= 0) return 0.f; + float m = data_[0]; + for (int i = 1; i < size_; ++i) m = std::max(m, data_[static_cast(i)]); + return m; +} + +float Tensor::dot(const Tensor *other) const { + ensureEager("dot"); + other->ensureEager("dot"); + checkSameShape(other, "dot"); + double acc = 0.0; + const float *a = data_.data(); + const float *b = other->data_.data(); + for (int i = 0; i < size_; ++i) acc += double(a[i]) * double(b[i]); + return float(acc); +} + +Tensor *Tensor::matmul(const Tensor *other) const { + ensureEager("matmul"); + if (!other) throw eve::Exception("Tensor.matmul: other is null"); + other->ensureEager("matmul"); + if (rank_ != 2 || other->rank_ != 2) throw eve::Exception("Tensor.matmul: both must be rank 2"); + int m = dims_[0], k = dims_[1], n = other->dims_[1]; + if (k != other->dims_[0]) throw eve::Exception("Tensor.matmul: inner dims mismatch"); + auto *out = new Tensor(m, n); + const float *a = data_.data(); + const float *b = other->data_.data(); + float *c = out->data_.data(); + for (int i = 0; i < m; ++i) { + for (int j = 0; j < n; ++j) { + double acc = 0.0; + for (int t = 0; t < k; ++t) acc += double(a[i * k + t]) * double(b[t * n + j]); + c[i * n + j] = float(acc); + } + } + return out; +} + +Tensor *Tensor::transpose() const { + ensureEager("transpose"); + if (rank_ != 2) throw eve::Exception("Tensor.transpose: expected rank 2"); + int r = dims_[0], c = dims_[1]; + auto *out = new Tensor(c, r); + for (int i = 0; i < r; ++i) + for (int j = 0; j < c; ++j) out->set2(j, i, get2(i, j)); + return out; +} + +Tensor *Tensor::reshape1(int d0) const { + ensureEager("reshape1"); + if (d0 != size_) throw eve::Exception("Tensor.reshape1: size mismatch"); + auto *out = new Tensor(d0); + out->data_ = data_; + return out; +} + +Tensor *Tensor::reshape2(int d0, int d1) const { + ensureEager("reshape2"); + if (d0 * d1 != size_) throw eve::Exception("Tensor.reshape2: size mismatch"); + auto *out = new Tensor(d0, d1); + out->data_ = data_; + return out; +} + +Tensor *Tensor::reshape3(int d0, int d1, int d2) const { + ensureEager("reshape3"); + if (d0 * d1 * d2 != size_) throw eve::Exception("Tensor.reshape3: size mismatch"); + auto *out = new Tensor(d0, d1, d2); + out->data_ = data_; + return out; +} + +Tensor *Tensor::reshape4(int d0, int d1, int d2, int d3) const { + ensureEager("reshape4"); + if (d0 * d1 * d2 * d3 != size_) throw eve::Exception("Tensor.reshape4: size mismatch"); + auto *out = new Tensor(d0, d1, d2, d3); + out->data_ = data_; + return out; +} + +Tensor *Tensor::flatten() const { return reshape1(size_); } + +} // namespace eve::tensor diff --git a/src/modules/tensor/Tensor.h b/src/modules/tensor/Tensor.h new file mode 100644 index 00000000..0fc1f32d --- /dev/null +++ b/src/modules/tensor/Tensor.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include +#include + +namespace eve::tensor { + +class Graph; +class Func; +class TF; + +/** + * float32 tensor (rank 1–4), row-major. + * Eager: owns a buffer. Symbolic: node in a Func graph (no buffer until run). + */ +class Tensor { +public: + static constexpr int kMaxRank = 4; + + Tensor() = default; + explicit Tensor(const int *dims, int rank); + Tensor(int d0); + Tensor(int d0, int d1); + Tensor(int d0, int d1, int d2); + Tensor(int d0, int d1, int d2, int d3); + + /** Symbolic handle into a graph node. */ + static Tensor *makeSymbolic(Graph *graph, int nodeId, const int *dims, int rank); + + bool isSymbolic() const { return kind_ == Kind::Symbolic; } + bool isEager() const { return kind_ == Kind::Eager; } + Graph *graph() const { return graph_; } + int nodeId() const { return nodeId_; } + + int getRank() const { return rank_; } + int getSize() const { return size_; } + int getDim(int axis) const; + int getDim0() const { return dims_[0]; } + int getDim1() const { return dims_[1]; } + int getDim2() const { return dims_[2]; } + int getDim3() const { return dims_[3]; } + std::string getDevice() const { return device_; } + std::string getDtype() const { return "float32"; } + + float get(int flatIndex) const; + void set(int flatIndex, float value); + float get1(int i0) const; + void set1(int i0, float value); + float get2(int i0, int i1) const; + void set2(int i0, int i1, float value); + float get3(int i0, int i1, int i2) const; + void set3(int i0, int i1, int i2, float value); + float get4(int i0, int i1, int i2, int i3) const; + void set4(int i0, int i1, int i2, int i3, float value); + + void fill(float value); + void copyFrom(const Tensor *other); + Tensor *clone() const; + + // Eager instance sugar (throws if symbolic) + Tensor *add(const Tensor *other) const; + Tensor *sub(const Tensor *other) const; + Tensor *multiply(const Tensor *other) const; + Tensor *div(const Tensor *other) const; + Tensor *addScalar(float s) const; + Tensor *subScalar(float s) const; + Tensor *mulScalar(float s) const; + Tensor *divScalar(float s) const; + Tensor *neg() const; + Tensor *abs() const; + Tensor *sqrt() const; + Tensor *exp() const; + Tensor *log() const; + Tensor *sin() const; + Tensor *cos() const; + Tensor *tanh() const; + Tensor *relu() const; + Tensor *sigmoid() const; + Tensor *powScalar(float exp) const; + Tensor *clamp(float lo, float hi) const; + Tensor *maximumScalar(float s) const; + Tensor *minimumScalar(float s) const; + + void addInPlace(const Tensor *other); + void multiplyInPlace(const Tensor *other); + void addScalarInPlace(float s); + void mulScalarInPlace(float s); + void reluInPlace(); + + float reduceSum() const; + float reduceMean() const; + float reduceMin() const; + float reduceMax() const; + float dot(const Tensor *other) const; + + Tensor *matmul(const Tensor *other) const; + Tensor *transpose() const; + Tensor *reshape1(int d0) const; + Tensor *reshape2(int d0, int d1) const; + Tensor *reshape3(int d0, int d1, int d2) const; + Tensor *reshape4(int d0, int d1, int d2, int d3) const; + Tensor *flatten() const; + + float *data(); + const float *data() const; + + void ensureEager(const char *op) const; + +private: + friend class TF; + friend class Func; + friend class CompiledFunction; + friend class Graph; + + enum class Kind { Eager, Symbolic }; + + void initDims(const int *dims, int rank); + void checkSameShape(const Tensor *other, const char *op) const; + int offset2(int i0, int i1) const; + int offset3(int i0, int i1, int i2) const; + int offset4(int i0, int i1, int i2, int i3) const; + + Kind kind_ = Kind::Eager; + Graph *graph_ = nullptr; + int nodeId_ = -1; + int rank_ = 0; + int dims_[kMaxRank] = {0, 0, 0, 0}; + int size_ = 0; + std::vector data_; + std::string device_ = "cpu"; +}; + +} // namespace eve::tensor diff --git a/src/modules/thread/Thread.cpp b/src/modules/thread/Thread.cpp index 35813763..e09ae6e0 100644 --- a/src/modules/thread/Thread.cpp +++ b/src/modules/thread/Thread.cpp @@ -1,6 +1,7 @@ #include "thread/Thread.h" #include "common/Exception.h" +#include "event/Event.h" #include @@ -52,6 +53,15 @@ Channel *Thread::getChannel(std::string name) { Channel *Thread::newChannel() { return new Channel(); } +void Thread::postMain(std::string name, std::string data) { + if (name.empty()) + throw eve::Exception("Thread::postMain: name must not be empty"); + auto *ev = ModuleManager::getInstance("Event"); + if (!ev) + ev = event::Event::create(); + ev->pushData(std::move(name), std::move(data)); +} + void Thread::expose(ssq::Table &table) { auto cls = table.addClass(name, Thread::create, false); expose(cls); @@ -83,6 +93,7 @@ void Thread::expose(ssq::Table &table) { pool.addFunc("isRunning", &ThreadPool::isRunning); pool.addFunc("submitSleep", &ThreadPool::submitSleep); pool.addFunc("submitPush", &ThreadPool::submitPush); + pool.addFunc("submitPost", &ThreadPool::submitPost); pool.addFunc("waitAll", &ThreadPool::waitAll); pool.addFunc("stop", &ThreadPool::stop); } @@ -94,6 +105,7 @@ void Thread::expose(ssq::Class &cls) { cls.addFunc("newThreadPool", &Thread::newThreadPool); cls.addFunc("getChannel", &Thread::getChannel); cls.addFunc("newChannel", &Thread::newChannel); + cls.addFunc("postMain", &Thread::postMain); } } // namespace thread diff --git a/src/modules/thread/Thread.h b/src/modules/thread/Thread.h index de46b48e..9312e988 100644 --- a/src/modules/thread/Thread.h +++ b/src/modules/thread/Thread.h @@ -42,6 +42,12 @@ public: /** Anonymous channel (not registered in the name map). Caller owns it. */ Channel *newChannel(); + /** + * Thread-safe post onto the Event module queue (any thread). + * Main loop: event.pump is unrelated; just event.poll / pollData. + */ + void postMain(std::string name, std::string data = ""); + private: std::mutex mu_; std::unique_ptr defaultPool_; diff --git a/src/modules/thread/ThreadPool.cpp b/src/modules/thread/ThreadPool.cpp index 3749ebea..3697c6b5 100644 --- a/src/modules/thread/ThreadPool.cpp +++ b/src/modules/thread/ThreadPool.cpp @@ -1,6 +1,8 @@ #include "thread/ThreadPool.h" #include "common/Exception.h" +#include "common/Module.h" +#include "event/Event.h" #include "thread/Channel.h" #include @@ -73,6 +75,21 @@ Task *ThreadPool::submitPush(Channel *channel, std::string message, int delayMs) }); } +Task *ThreadPool::submitPost(std::string name, std::string data, int delayMs) { + if (name.empty()) + throw eve::Exception("ThreadPool::submitPost: name must not be empty"); + if (delayMs < 0) + delayMs = 0; + return submit([name = std::move(name), data = std::move(data), delayMs] { + if (delayMs > 0) + std::this_thread::sleep_for(std::chrono::milliseconds(delayMs)); + auto *ev = ModuleManager::getInstance("Event"); + if (!ev) + ev = event::Event::create(); + ev->pushData(name, data); + }); +} + void ThreadPool::waitAll() { std::unique_lock lock(mu_); idleCv_.wait(lock, [this] { return queue_.empty() && busy_ == 0; }); diff --git a/src/modules/thread/ThreadPool.h b/src/modules/thread/ThreadPool.h index 2695bbbf..529aed1d 100644 --- a/src/modules/thread/ThreadPool.h +++ b/src/modules/thread/ThreadPool.h @@ -41,6 +41,12 @@ public: /** Sleep, then push a message onto a channel (cross-thread signalling). */ Task *submitPush(Channel *channel, std::string message, int delayMs = 0); + /** + * Sleep, then post an Event on the main queue (thread-safe). + * Scripts poll via event.poll / event.pollData, or async helpers. + */ + Task *submitPost(std::string name, std::string data = "", int delayMs = 0); + /** Block until the queue is empty and no worker is busy. */ void waitAll(); diff --git a/src/scripts/async.nut b/src/scripts/async.nut new file mode 100644 index 00000000..d1b79634 --- /dev/null +++ b/src/scripts/async.nut @@ -0,0 +1,434 @@ +// Node.js-style async runtime for EVEngine (main-thread callbacks only). +// Workers must not touch the Squirrel VM — use ThreadChannel / Thread.postMain, +// then resolve Promises from async_pump() on the game thread. +// +// Loaded via eve.asyncScript from load.nut before the frame loop. + +if (!("_async" in getroottable())) { + _async <- { + nextTicks = [] + timers = [] // { id, due, fn, interval } + nextId = 1 + draining = false + maxDrain = 1000 + // Pending worker polls: { ch, resolve, reject, task } + waits = [] + eventWaits = [] // { name, resolve, reject } + }; +} + +function nextTick(fn) { + if (typeof fn != "function") + throw "nextTick: expected function"; + _async.nextTicks.append(fn); +} + +function setImmediate(fn) { + nextTick(fn); + return 0; +} + +function setTimeout(fn, ms) { + if (typeof fn != "function") + throw "setTimeout: expected function"; + if (ms == null) ms = 0; + if (ms < 0) ms = 0; + local id = _async.nextId; + _async.nextId += 1; + local due = timer.getTime() + (ms * 1.0 / 1000.0); + _async.timers.append({ id = id, due = due, fn = fn, interval = 0.0 }); + return id; +} + +function setInterval(fn, ms) { + if (typeof fn != "function") + throw "setInterval: expected function"; + if (ms == null) ms = 0; + if (ms < 0) ms = 0; + local id = _async.nextId; + _async.nextId += 1; + local period = ms * 1.0 / 1000.0; + local due = timer.getTime() + period; + _async.timers.append({ id = id, due = due, fn = fn, interval = period }); + return id; +} + +function clearTimeout(id) { + local out = []; + foreach (t in _async.timers) { + if (t.id != id) + out.append(t); + } + _async.timers = out; +} + +function clearInterval(id) { + clearTimeout(id); +} + +function _async_drain_microtasks() { + local n = 0; + while (_async.nextTicks.len() > 0 && n < _async.maxDrain) { + local fn = _async.nextTicks[0]; + _async.nextTicks.remove(0); + n += 1; + try { + fn(); + } catch (e) { + print("nextTick error: " + e + "\n"); + } + } + if (_async.nextTicks.len() > 0) + print("async: microtask queue truncated after " + _async.maxDrain + "\n"); +} + +function _async_fire_timers() { + if (_async.timers.len() == 0) + return; + local now = timer.getTime(); + local remain = []; + local dueFns = []; + foreach (t in _async.timers) { + if (t.due <= now) { + dueFns.append(t); + if (t.interval > 0.0) { + t.due = now + t.interval; + remain.append(t); + } + } else { + remain.append(t); + } + } + _async.timers = remain; + foreach (t in dueFns) { + try { + t.fn(); + } catch (e) { + print("timer error: " + e + "\n"); + } + } +} + +function _async_poll_waits() { + if (_async.waits.len() == 0) + return; + local remain = []; + foreach (w in _async.waits) { + local done = false; + try { + if (w.task != null && w.task.hasFailed()) { + w.reject(w.task.getError()); + done = true; + } else if (w.ch != null && w.ch.hasData()) { + w.resolve(w.ch.pop()); + done = true; + } + } catch (e) { + try { w.reject(e); } catch (e2) {} + done = true; + } + if (!done) + remain.append(w); + } + _async.waits = remain; +} + +// Drain timers, worker waits, and microtasks. Call once per frame (load.nut). +function async_pump() { + if (_async.draining) + return; + _async.draining = true; + try { + _async_fire_timers(); + _async_poll_waits(); + _async_drain_microtasks(); + // Timers / waits may have queued more microtasks. + _async_drain_microtasks(); + } catch (e) { + print("async_pump error: " + e + "\n"); + } + _async.draining = false; +} + +// ---- Promise (thenable, main-thread only) --------------------------------- + +class Promise { + _state = "pending" // pending | fulfilled | rejected + _value = null + _handlers = null + + constructor(executor) { + this._handlers = []; + if (typeof executor != "function") + throw "Promise: executor must be a function"; + local self = this; + local resolve = function(value) { self._settle("fulfilled", value); }; + local reject = function(reason) { self._settle("rejected", reason); }; + try { + executor(resolve, reject); + } catch (e) { + reject(e); + } + } + + function _settle(state, value) { + if (this._state != "pending") + return; + // Promise resolution procedure (simplified): flatten thenables. + if (state == "fulfilled" && value != null && typeof value == "instance" && + ("then" in value) && typeof value.then == "function") { + local self = this; + try { + value.then( + function(v) { self._settle("fulfilled", v); }, + function(e) { self._settle("rejected", e); } + ); + } catch (e) { + this._state = "rejected"; + this._value = e; + this._flush(); + } + return; + } + this._state = state; + this._value = value; + this._flush(); + } + + function _flush() { + local handlers = this._handlers; + this._handlers = []; + local self = this; + foreach (h in handlers) { + nextTick(function() { + try { + if (self._state == "fulfilled") { + if (typeof h.onFulfilled == "function") { + local v = h.onFulfilled(self._value); + h.resolve(v); + } else { + h.resolve(self._value); + } + } else { + if (typeof h.onRejected == "function") { + local v = h.onRejected(self._value); + h.resolve(v); + } else { + h.reject(self._value); + } + } + } catch (e) { + h.reject(e); + } + }); + } + } + + function then(onFulfilled, onRejected = null) { + local self = this; + return Promise(function(resolve, reject) { + self._handlers.append({ + onFulfilled = onFulfilled + onRejected = onRejected + resolve = resolve + reject = reject + }); + if (self._state != "pending") + self._flush(); + }); + } + + // Named fail — Squirrel reserves `catch` as a keyword. + function fail(onRejected) { + return this.then(null, onRejected); + } + + function fin(onFinally) { + return this.then( + function(v) { + if (typeof onFinally == "function") onFinally(); + return v; + }, + function(e) { + if (typeof onFinally == "function") onFinally(); + throw e; + } + ); + } +} + +Promise.resolve <- function(value) { + return Promise(function(resolve, reject) { resolve(value); }); +}; + +Promise.reject <- function(reason) { + return Promise(function(resolve, reject) { reject(reason); }); +}; + +Promise.all <- function(arr) { + return Promise(function(resolve, reject) { + if (arr == null || arr.len() == 0) { + resolve([]); + return; + } + local results = []; + results.resize(arr.len(), null); + local left = arr.len(); + for (local i = 0; i < arr.len(); i++) { + local idx = i; + local p = arr[i]; + if (p == null || typeof p != "instance" || !("then" in p)) + p = Promise.resolve(p); + p.then( + function(v) { + results[idx] = v; + left -= 1; + if (left == 0) + resolve(results); + }, + function(e) { reject(e); } + ); + } + }); +}; + +Promise.race <- function(arr) { + return Promise(function(resolve, reject) { + if (arr == null || arr.len() == 0) + return; + foreach (p in arr) { + if (p == null || typeof p != "instance" || !("then" in p)) + p = Promise.resolve(p); + p.then(resolve, reject); + } + }); +}; + +// ---- Helpers -------------------------------------------------------------- + +function asyncSleep(ms) { + return Promise(function(resolve, reject) { + setTimeout(function() { resolve(null); }, ms); + }); +} + +// Run delay on a worker thread, then resolve with `value` on the main thread. +function asyncDelay(ms, value = "") { + if (value == null) value = ""; + return Promise(function(resolve, reject) { + local ch = thread.newChannel(); + local pool = thread.getPool(); + local task = pool.submitPush(ch, "" + value, ms); + _async.waits.append({ ch = ch, resolve = resolve, reject = reject, task = task }); + }); +} + +// Wait for a named Event posted via thread.postMain / pool.submitPost / event.pushData. +function asyncOnEvent(name) { + return Promise(function(resolve, reject) { + if (name == null || name == "") { + reject("asyncOnEvent: empty name"); + return; + } + _async.eventWaits.append({ name = name, resolve = resolve, reject = reject }); + }); +} + +// Called from load.nut for each polled event (before quit handling). +function async_dispatch_event(name, data) { + if (_async.eventWaits.len() == 0) + return false; + local remain = []; + local matched = false; + foreach (w in _async.eventWaits) { + if (!matched && w.name == name) { + try { + w.resolve(data); + } catch (e) { + try { w.reject(e); } catch (e2) {} + } + matched = true; + } else { + remain.append(w); + } + } + _async.eventWaits = remain; + return matched; +} + +// Delay on a worker, then post Event(name, data); resolves when that event is polled. +function asyncPost(name, data, ms) { + if (data == null) data = ""; + if (ms == null) ms = 0; + return Promise(function(resolve, reject) { + local pool = thread.getPool(); + pool.submitPost(name, "" + data, ms); + _async.eventWaits.append({ name = name, resolve = resolve, reject = reject }); + }); +} + +// Poll a ThreadChannel until a message arrives (non-blocking via async_pump). +function asyncReceive(ch) { + return Promise(function(resolve, reject) { + if (ch == null) { + reject("asyncReceive: null channel"); + return; + } + _async.waits.append({ ch = ch, resolve = resolve, reject = reject, task = null }); + }); +} + +// ---- Sequential async (closest to JS async/await) ------------------------- +// +// Squirrel has no async/await keywords. Generators' `yield` is statement-only +// here, so `local x = yield promise` is impossible. Use: +// 1) Promise.then chains, or +// 2) asyncSeq([...step factories...]) for linear multi-step flows. +// +// Example: +// asyncSeq([ +// function() { return asyncSleep(50); }, +// function(_) { return asyncDelay(20, "ok"); }, +// function(msg) { print(msg + "\n"); return msg; }, +// ]).then(function(v) { /* done */ }); + +function asyncSeq(steps) { + return Promise(function(resolve, reject) { + if (steps == null || steps.len() == 0) { + resolve(null); + return; + } + local i = 0; + local value = null; + local function runNext() { + if (i >= steps.len()) { + resolve(value); + return; + } + local step = steps[i]; + i += 1; + local out = null; + try { + out = step(value); + } catch (e) { + reject(e); + return; + } + if (out != null && typeof out == "instance" && ("then" in out) && + typeof out.then == "function") { + out.then( + function(v) { + value = v; + runNext(); + }, + function(e) { reject(e); } + ); + } else { + value = out; + nextTick(runNext); + } + } + runNext(); + }); +} + diff --git a/src/scripts/load.nut b/src/scripts/load.nut index 09440d1c..cfabf6e6 100644 --- a/src/scripts/load.nut +++ b/src/scripts/load.nut @@ -55,6 +55,8 @@ config.height = win.getHeight(); event <- eve.Event(); timer <- eve.Timer(); system <- eve.System(); +math <- eve.Math(); +tf <- eve.TF(); ui <- eve.UI(); particles <- eve.Particles(); map <- eve.Map(); @@ -71,6 +73,21 @@ thread <- eve.Thread(); fs <- eve.Filesystem(); hot <- eve.HotReload(); +// Node-style async (Promise / nextTick / setTimeout). Embedded via eve.asyncScript. +if ("asyncScript" in eve && eve.asyncScript != null && eve.asyncScript != "") { + try { + compilestring(eve.asyncScript)(); + } catch (e) { + print("async runtime failed to load: " + e + "\n"); + } +} else if (file_exists("async.nut")) { + try { + dofile("async.nut"); + } catch (e) { + print("async.nut failed to load: " + e + "\n"); + } +} + eve_init <- function() {}; eve_update <- function(dt) {}; eve_render <- function() { @@ -186,6 +203,9 @@ while (running) { while (true) { local name = event.poll(); if (name == "") break; + local data = event.getLastData(); + if ("async_dispatch_event" in getroottable()) + async_dispatch_event(name, data); if (name == "quit") { if (startupFrames <= 0) running = false; @@ -202,7 +222,13 @@ while (running) { local dt = timer.step(); try { + // Timers + Promise microtasks before game logic (Node-like macrotask boundary). + if ("async_pump" in getroottable()) + async_pump(); eve_update(dt); + // Flush reactions scheduled during eve_update. + if ("async_pump" in getroottable()) + async_pump(); eve_render(); gfx.present(); ui.dispatchEvents(); diff --git a/src/scripts/scripts.h b/src/scripts/scripts.h index 3beec756..641903cb 100644 --- a/src/scripts/scripts.h +++ b/src/scripts/scripts.h @@ -3,6 +3,7 @@ namespace eve { extern const char* boot_content; extern const char* load_content; +extern const char* async_content; extern const char* demo_content; extern const char* vertex_content; extern const char* fragment_content; diff --git a/src/scripts/template.cpp.in b/src/scripts/template.cpp.in index 3270fcd3..e778ba48 100644 --- a/src/scripts/template.cpp.in +++ b/src/scripts/template.cpp.in @@ -5,6 +5,8 @@ const char* boot_content = R"(@boot_content@)"; const char* load_content = R"(@load_content@)"; +const char* async_content = R"(@async_content@)"; + const char* demo_content = R"(@demo_content@)"; const char* vertex_content = R"(@vertex_content@)"; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8e0c44e3..bcbc576f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,6 +16,11 @@ file(GLOB all_test_cpp network.cpp RenderSystem.cpp RenderSystem3D.cpp + Quad.cpp + Lighting2D.cpp + ClusteredLighting.cpp + IBL.cpp + Shadow3D.cpp UISystem.cpp UIBuilder.cpp UIPhaseB.cpp @@ -33,6 +38,7 @@ file(GLOB all_test_cpp audio.cpp font.cpp thread.cpp + async.cpp window.cpp window_cpp.cpp image.cpp @@ -40,6 +46,8 @@ file(GLOB all_test_cpp timer.cpp timer_cpp.cpp system.cpp + math.cpp + tensor.cpp main.cpp ) @@ -130,7 +138,7 @@ else() endif() endif() -set(EVELIBS EVCmdLine EVScripts EVWindow EVFileSystem EVMouse EVKeyboard EVJoystick EVTouch EVGraphics EVImage EVDevTools EVCommon EVEvent EVTimer EVSystem EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles EVPhysics EVMap EVGpgpu EVModel3D EVFont EVThread) +set(EVELIBS EVCmdLine EVScripts EVWindow EVFileSystem EVMouse EVKeyboard EVJoystick EVTouch EVGraphics EVImage EVDevTools EVCommon EVEvent EVTimer EVSystem EVMath EVTensor EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles EVPhysics EVMap EVGpgpu EVModel3D EVFont EVThread) if(EVENGINE_BUILD_DEMO) list(APPEND EVELIBS EVDemo) diff --git a/test/ClusteredLighting.cpp b/test/ClusteredLighting.cpp new file mode 100644 index 00000000..3e2d0ed8 --- /dev/null +++ b/test/ClusteredLighting.cpp @@ -0,0 +1,152 @@ +#include "zeroerr/assert.h" +#include "zeroerr/unittest.h" + +#include +#include +#include + +#include "graphics/ClusteredLight.h" +#include "graphics/Graphics.h" +#include "graphics/Light.h" +#include "graphics/RenderSystem.h" +#include "graphics/RenderSystem3D.h" +#include "window/Window.h" + +using namespace eve::graphics; + +static float luma(const Color &c) { return (c.r + c.g + c.b) / 3.f; } + +static void openGfxWindow(eve::window::Window *&win, Graphics *&gfx, int w = 320, int h = 240) { + win = eve::window::Window::create(); + gfx = Graphics::create(); + REQUIRE(win != nullptr); + REQUIRE(gfx != nullptr); + win->setGraphics(gfx); + eve::window::WindowSettings s; + s.width = w; + s.height = h; + s.centered = true; + REQUIRE(win->setWindowSettings(s)); +} + +static void resetScene3D() { + if (ecs::ComponentManager::inst().registy != nullptr) { + auto view = ecs::View(); + for (auto it = view.begin(); it != view.end(); ++it) { + auto [mr] = *it; + mr->visible = false; + } + } + if (ecs::ComponentManager::inst().registy != nullptr) { + auto camView = ecs::View(); + for (auto it = camView.begin(); it != camView.end(); ++it) { + auto [data] = *it; + data->active = false; + } + } + if (ecs::ComponentManager::inst().registy != nullptr) { + auto lightView = ecs::View(); + for (auto it = lightView.begin(); it != lightView.end(); ++it) { + auto [d] = *it; + d->enabled = false; + } + } + if (ecs::ComponentManager::inst().registy != nullptr) { + auto view = ecs::View(); + for (auto it = view.begin(); it != view.end(); ++it) { + auto [sp] = *it; + sp->visible = false; + } + } +} + +static Texture *makeSolidGray(Graphics *gfx, uint8_t g) { + uint8_t px[4] = {g, g, g, 255}; + return gfx->newTexture(1, 1, px); +} + +TEST_CASE("ClusteredLighting.buildProducesNonEmptyTables") { + std::vector points; + for (int i = 0; i < 32; ++i) { + ClusteredLightGpu L{}; + const float a = float(i) * 0.2f; + L.posRadius = glm::vec4(std::cos(a) * 1.5f, 0.f, std::sin(a) * 1.5f, 2.f); + L.color = glm::vec4(1.f, 1.f, 1.f, 1.f); + points.push_back(L); + } + std::vector dirs; + ClusteredLightGpu d{}; + d.posRadius = glm::vec4(0.f, 1.f, 0.f, 0.f); + d.color = glm::vec4(0.2f, 0.2f, 0.2f, 1.f); + dirs.push_back(d); + + glm::mat4 view = glm::lookAtRH(glm::vec3(0, 0, 3), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)); + auto upload = buildClusteredLighting(points, dirs, view, 0.1f, 100.f, 320, 240, 1.0f, + glm::vec4(0.1f, 0.1f, 0.1f, 0.f)); + REQUIRE(upload.active); + CHECK_EQ(upload.lights.size(), 32u); + CHECK_EQ(upload.clusterTable.size(), size_t(ClusteredLightConfig::kClusterCount)); + CHECK(!upload.lightIndices.empty()); + uint32_t total = 0; + for (const auto &e : upload.clusterTable) total += e.count; + CHECK_GT(total, 0u); +} + +TEST_CASE("ClusteredLighting.manyPointLightsBrightenCenter") { + eve::window::Window *win = nullptr; + Graphics *gfx = nullptr; + openGfxWindow(win, gfx); + resetScene3D(); + + Mesh *mesh = gfx->newMeshSphere(24, 12); + REQUIRE(mesh != nullptr); + + auto *cam = Camera3D::createCamera(); + cam->data()->eyeZ = 3.f; + cam->setAmbient(0.04f, 0.04f, 0.04f); + + auto *ent = Renderable3D::create(); + ent->meshRenderer()->mesh = mesh; + ent->meshRenderer()->texture = makeSolidGray(gfx, 220); + ent->setMetallic(0.05f); + ent->setRoughness(0.55f); + + auto *hud = Renderable2D::create(); + hud->transform()->x = 0; + hud->transform()->y = 0; + hud->sprite()->width = 2; + hud->sprite()->height = 2; + + // >8 lights triggers clustered path. + std::vector lights; + for (int i = 0; i < 24; ++i) { + auto *L = Light3D::createLight("point"); + const float a = float(i) * (6.2831853f / 24.f); + L->setPosition(std::cos(a) * 1.2f, 0.3f, std::sin(a) * 1.2f); + L->setColor(1.f, 1.f, 1.f, 1.8f); + L->setRadius(3.5f); + lights.push_back(L); + } + auto *dir = Light3D::createLight("dir"); + dir->setDirection(0.f, 0.4f, 1.f); + dir->setColor(0.15f, 0.15f, 0.15f, 1.f); + + gfx->setScreenReadbackEnabled(true); + for (int i = 0; i < 3; ++i) { + RenderSystem3D::render(*gfx); + RenderSystem::render(*gfx); + } + const float L_on = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2)); + + for (auto *L : lights) L->setEnabled(false); + for (int i = 0; i < 3; ++i) { + RenderSystem3D::render(*gfx); + RenderSystem::render(*gfx); + } + const float L_off = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2)); + + REQUIRE(L_on > L_off + 0.04f); + + dir->setEnabled(false); + win->close(); +} diff --git a/test/IBL.cpp b/test/IBL.cpp new file mode 100644 index 00000000..58ba2422 --- /dev/null +++ b/test/IBL.cpp @@ -0,0 +1,159 @@ +#include "zeroerr/assert.h" +#include "zeroerr/unittest.h" + +#include +#include +#include + +#include "graphics/Graphics.h" +#include "graphics/Light.h" +#include "graphics/Mesh.h" +#include "graphics/RenderSystem.h" +#include "graphics/RenderSystem3D.h" +#include "window/Window.h" + +using namespace eve::graphics; + +namespace { + +void openGfxWindow(eve::window::Window *&win, Graphics *&gfx, int w = 320, int h = 240) { + win = eve::window::Window::create(); + gfx = Graphics::create(); + REQUIRE(win != nullptr); + REQUIRE(gfx != nullptr); + win->setGraphics(gfx); + eve::window::WindowSettings s; + s.width = w; + s.height = h; + s.centered = true; + REQUIRE(win->setWindowSettings(s)); +} + +void resetScene3D() { + if (ecs::ComponentManager::inst().registy != nullptr) { + auto view = ecs::View(); + for (auto it = view.begin(); it != view.end(); ++it) { + auto [mr] = *it; + mr->visible = false; + } + } + if (ecs::ComponentManager::inst().registy != nullptr) { + auto camView = ecs::View(); + for (auto it = camView.begin(); it != camView.end(); ++it) { + auto [data] = *it; + data->active = false; + data->envMap = nullptr; + data->envIntensity = 1.f; + } + } + if (ecs::ComponentManager::inst().registy != nullptr) { + auto lightView = ecs::View(); + for (auto it = lightView.begin(); it != lightView.end(); ++it) { + auto [d] = *it; + d->enabled = false; + } + } + if (ecs::ComponentManager::inst().registy != nullptr) { + auto view = ecs::View(); + for (auto it = view.begin(); it != view.end(); ++it) { + auto [sp] = *it; + sp->visible = false; + } + } +} + +Texture *makeSolidGray(Graphics *gfx, uint8_t v) { + const uint8_t px[4] = {v, v, v, 255}; + return gfx->newTexture(1, 1, px); +} + +/** All faces the same solid RGBA — strong specular tint for metals. */ +Texture *makeSolidCubemap(Graphics *gfx, uint8_t r, uint8_t g, uint8_t b) { + const int face = 4; + std::vector faces(size_t(face) * size_t(face) * 4u * 6u); + for (size_t i = 0; i < faces.size(); i += 4) { + faces[i + 0] = r; + faces[i + 1] = g; + faces[i + 2] = b; + faces[i + 3] = 255; + } + return gfx->newCubemap(face, faces.data()); +} + +void addHud(Graphics *) { + auto *hud = Renderable2D::create(); + hud->transform()->x = 0; + hud->transform()->y = 0; + hud->sprite()->width = 2; + hud->sprite()->height = 2; + hud->sprite()->r = 0.f; + hud->sprite()->g = 0.f; + hud->sprite()->b = 0.f; +} + +void warmPresent(Graphics *gfx) { + for (int i = 0; i < 3; ++i) { + RenderSystem3D::render(*gfx); + RenderSystem::render(*gfx); + } +} + +} // namespace + +TEST_CASE("IBL.metallicPicksUpEnvColor") { + eve::window::Window *win = nullptr; + Graphics *gfx = nullptr; + openGfxWindow(win, gfx); + resetScene3D(); + + Mesh *mesh = gfx->newMeshSphere(24, 16); + REQUIRE(mesh != nullptr); + + auto *cam = Camera3D::createCamera(); + cam->data()->eyeZ = 3.f; + cam->setAmbient(0.03f, 0.03f, 0.03f); + + auto *ent = Renderable3D::create(); + ent->meshRenderer()->mesh = mesh; + ent->meshRenderer()->texture = makeSolidGray(gfx, 220); + ent->setMetallic(1.0f); + ent->setRoughness(0.08f); + + addHud(gfx); + + // Kill direct light so specular IBL dominates metals. + RenderSystem3D::setDirectionalLight(0.f, 1.f, 0.f, 0.f, 0.f, 0.f); + + gfx->setScreenReadbackEnabled(true); + warmPresent(gfx); + const Color c0 = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2); + + Texture *env = makeSolidCubemap(gfx, 240, 40, 40); + cam->setEnvMap(env); + cam->setEnvIntensity(1.5f); + warmPresent(gfx); + const Color c1 = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2); + + // Red env should lift R more than G/B on a metal sphere. + REQUIRE(c1.r > c0.r + 0.04f); + REQUIRE(c1.r > c1.g + 0.03f); + REQUIRE(c1.r > c1.b + 0.03f); + + cam->setEnvIntensity(0.f); + warmPresent(gfx); + const Color c2 = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2); + REQUIRE(std::abs(c2.r - c0.r) < 0.05f); + + win->close(); +} + +TEST_CASE("IBL.newCubemapCreatesSixLayers") { + eve::window::Window *win = nullptr; + Graphics *gfx = nullptr; + openGfxWindow(win, gfx); + Texture *cube = makeSolidCubemap(gfx, 10, 20, 30); + REQUIRE(cube != nullptr); + REQUIRE(cube->layers == 6); + REQUIRE(cube->getWidth() == 4); + win->close(); +} diff --git a/test/Lighting2D.cpp b/test/Lighting2D.cpp new file mode 100644 index 00000000..67fed7b7 --- /dev/null +++ b/test/Lighting2D.cpp @@ -0,0 +1,216 @@ +#include "zeroerr/assert.h" +#include "zeroerr/unittest.h" + +#include +#include +#include +#include + +#include "graphics/Graphics.h" +#include "graphics/Light.h" +#include "graphics/RenderSystem.h" +#include "image/ImageData.h" +#include "window/Window.h" + +using namespace eve::graphics; + +static float luma(const Color &c) { return 0.2126f * c.r + 0.7152f * c.g + 0.0722f * c.b; } + +static Texture *makeSolidTexture(Graphics *gfx, int w, int h, uint8_t r, uint8_t g, uint8_t b) { + std::vector px(size_t(w) * size_t(h) * 4, 255); + for (size_t i = 0; i < px.size(); i += 4) { + px[i + 0] = r; + px[i + 1] = g; + px[i + 2] = b; + } + eve::image::ImageData imageData(w, h, "RGBA8"); + std::memcpy(imageData.getData(), px.data(), px.size()); + return gfx->newTexture(&imageData); +} + +/** + * Normal map biased toward +X on the right half and -X on the left half so a light + * from the right brightens the right side more. + */ +static Texture *makeBiasedNormal(Graphics *gfx, int w, int h) { + std::vector px(size_t(w) * size_t(h) * 4); + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + size_t i = (size_t(y) * size_t(w) + size_t(x)) * 4; + // Encode [-1,1] → [0,255]; left leans -X, right leans +X. + float nx = (x < w / 2) ? -0.7f : 0.7f; + float ny = 0.f; + float nz = 0.7f; + px[i + 0] = uint8_t((nx * 0.5f + 0.5f) * 255.f); + px[i + 1] = uint8_t((ny * 0.5f + 0.5f) * 255.f); + px[i + 2] = uint8_t((nz * 0.5f + 0.5f) * 255.f); + px[i + 3] = 255; + } + } + eve::image::ImageData imageData(w, h, "RGBA8"); + std::memcpy(imageData.getData(), px.data(), px.size()); + return gfx->newTexture(&imageData); +} + +TEST_CASE("Lighting2D.pointLightBrightensNearbyUnlitSprite") { + auto *win = eve::window::Window::create(); + auto *gfx = Graphics::create(); + REQUIRE(win != nullptr); + REQUIRE(gfx != nullptr); + + win->setGraphics(gfx); + eve::window::WindowSettings s; + s.width = 320; + s.height = 240; + s.centered = true; + REQUIRE(win->setWindowSettings(s)); + + Canvas *rt = gfx->newCanvas(128, 64); + REQUIRE(rt != nullptr); + + auto *cam = Camera2D::createCamera(); + cam->data()->canvas = rt; + cam->data()->active = true; + cam->data()->x = 64.f; + cam->data()->y = 32.f; + cam->data()->zoom = 1.f; + cam->setAmbient(0.05f, 0.05f, 0.05f); + cam->data()->r = 0.f; + cam->data()->g = 0.f; + cam->data()->b = 0.f; + cam->data()->a = 1.f; + + Texture *albedo = makeSolidTexture(gfx, 8, 8, 255, 255, 255); + REQUIRE(albedo != nullptr); + + auto *nearSp = Renderable2D::create(); + nearSp->transform()->x = 8.f; + nearSp->transform()->y = 16.f; + nearSp->sprite()->width = 24.f; + nearSp->sprite()->height = 24.f; + nearSp->sprite()->texture = albedo; + nearSp->sprite()->receiveLight = true; + nearSp->sprite()->canvas = rt; + nearSp->sprite()->visible = true; + + auto *farSp = Renderable2D::create(); + farSp->transform()->x = 96.f; + farSp->transform()->y = 16.f; + farSp->sprite()->width = 24.f; + farSp->sprite()->height = 24.f; + farSp->sprite()->texture = albedo; + farSp->sprite()->receiveLight = true; + farSp->sprite()->canvas = rt; + farSp->sprite()->visible = true; + + auto *light = Light2D::createLight("point"); + light->setCanvas(rt); + light->setPosition(20.f, 28.f); + light->setColor(1.f, 1.f, 1.f, 2.5f); + light->setRadius(50.f); + light->setEnabled(true); + + RenderSystem::render(*gfx); + + float nearL = luma(rt->getPixel(20, 28)); + float farL = luma(rt->getPixel(108, 28)); + CHECK(nearL > farL + 0.08f); + + nearSp->sprite()->visible = false; + farSp->sprite()->visible = false; + light->setEnabled(false); + cam->data()->active = false; + win->close(); +} + +TEST_CASE("Lighting2D.normalMapLitSideBrighter") { + auto *win = eve::window::Window::create(); + auto *gfx = Graphics::create(); + REQUIRE(win != nullptr); + REQUIRE(gfx != nullptr); + + win->setGraphics(gfx); + eve::window::WindowSettings s; + s.width = 320; + s.height = 240; + s.centered = true; + REQUIRE(win->setWindowSettings(s)); + + Canvas *rt = gfx->newCanvas(128, 64); + REQUIRE(rt != nullptr); + + auto *cam = Camera2D::createCamera(); + cam->data()->canvas = rt; + cam->data()->active = true; + cam->data()->x = 64.f; + cam->data()->y = 32.f; + cam->data()->zoom = 1.f; + cam->setAmbient(0.08f, 0.08f, 0.08f); + cam->data()->r = 0.f; + cam->data()->g = 0.f; + cam->data()->b = 0.f; + cam->data()->a = 1.f; + + Texture *albedo = makeSolidTexture(gfx, 32, 32, 220, 220, 220); + Texture *normal = makeBiasedNormal(gfx, 32, 32); + REQUIRE(albedo != nullptr); + REQUIRE(normal != nullptr); + + auto *sp = Renderable2D::create(); + sp->transform()->x = 32.f; + sp->transform()->y = 8.f; + sp->sprite()->width = 64.f; + sp->sprite()->height = 48.f; + sp->sprite()->texture = albedo; + sp->sprite()->normalTexture = normal; + sp->sprite()->receiveLight = true; + sp->sprite()->canvas = rt; + sp->sprite()->visible = true; + + auto *light = Light2D::createLight("point"); + light->setCanvas(rt); + // Light to the right of the sprite → right-facing normals should be brighter. + light->setPosition(110.f, 32.f); + light->setColor(1.f, 1.f, 1.f, 3.f); + light->setRadius(120.f); + light->setEnabled(true); + + // Second light (also collected) slightly above — ensures multi-light packing. + auto *light2 = Light2D::createLight("point"); + light2->setCanvas(rt); + light2->setPosition(100.f, 10.f); + light2->setColor(0.4f, 0.4f, 1.f, 1.2f); + light2->setRadius(80.f); + light2->setEnabled(true); + + RenderSystem::render(*gfx); + + float leftL = luma(rt->getPixel(42, 32)); + float rightL = luma(rt->getPixel(86, 32)); + CHECK(rightL > leftL + 0.05f); + + sp->sprite()->visible = false; + light->setEnabled(false); + light2->setEnabled(false); + cam->data()->active = false; + win->close(); +} + +TEST_CASE("Lighting2D.createLightTypesAndAmbient") { + auto *pt = Light2D::createLight("point"); + CHECK_EQ(pt->getType(), std::string("point")); + auto *dir = Light2D::createLight("dir"); + CHECK_EQ(dir->getType(), std::string("dir")); + dir->setDirection(1.f, 0.f); + CHECK(std::abs(dir->getDirX() - 1.f) < 1e-5f); + + auto *cam = Camera2D::createCamera(); + cam->setAmbient(0.2f, 0.3f, 0.4f); + CHECK(std::abs(cam->data()->ambientR - 0.2f) < 1e-5f); + CHECK(std::abs(cam->data()->ambientG - 0.3f) < 1e-5f); + CHECK(std::abs(cam->data()->ambientB - 0.4f) < 1e-5f); + + pt->setEnabled(false); + dir->setEnabled(false); + cam->data()->active = false; +} diff --git a/test/Quad.cpp b/test/Quad.cpp new file mode 100644 index 00000000..44850a6e --- /dev/null +++ b/test/Quad.cpp @@ -0,0 +1,102 @@ +#include "zeroerr/assert.h" +#include "zeroerr/unittest.h" + +#include +#include +#include +#include + +#include "graphics/Graphics.h" +#include "graphics/Quad.h" +#include "graphics/RenderSystem.h" +#include "image/ImageData.h" +#include "window/Window.h" + +using namespace eve::graphics; + +TEST_CASE("Quad.getUVMapsPixelRect") { + Quad q(16, 8, 32, 16); + float u0 = 0, v0 = 0, u1 = 0, v1 = 0; + q.getUV(64, 64, u0, v0, u1, v1); + CHECK(std::abs(u0 - 0.25f) < 1e-5f); + CHECK(std::abs(v0 - 0.125f) < 1e-5f); + CHECK(std::abs(u1 - 0.75f) < 1e-5f); + CHECK(std::abs(v1 - 0.375f) < 1e-5f); + + q.setViewport(0, 0, 64, 64); + q.getUV(64, 64, u0, v0, u1, v1); + CHECK(std::abs(u0) < 1e-5f); + CHECK(std::abs(v0) < 1e-5f); + CHECK(std::abs(u1 - 1.f) < 1e-5f); + CHECK(std::abs(v1 - 1.f) < 1e-5f); +} + +TEST_CASE("Quad.spriteAtlasSamplesSubRect") { + auto *win = eve::window::Window::create(); + auto *gfx = Graphics::create(); + REQUIRE(win != nullptr); + REQUIRE(gfx != nullptr); + + win->setGraphics(gfx); + eve::window::WindowSettings s; + s.width = 320; + s.height = 240; + s.centered = true; + REQUIRE(win->setWindowSettings(s)); + + // Atlas: left half red, right half green. + constexpr int TW = 32, TH = 16; + std::vector px(size_t(TW) * size_t(TH) * 4); + for (int y = 0; y < TH; ++y) { + for (int x = 0; x < TW; ++x) { + size_t i = (size_t(y) * size_t(TW) + size_t(x)) * 4; + bool left = x < TW / 2; + px[i + 0] = left ? 255 : 0; + px[i + 1] = left ? 0 : 255; + px[i + 2] = 0; + px[i + 3] = 255; + } + } + eve::image::ImageData imageData(TW, TH, "RGBA8"); + std::memcpy(imageData.getData(), px.data(), px.size()); + Texture *atlas = gfx->newTexture(&imageData); + REQUIRE(atlas != nullptr); + + Quad *right = gfx->newQuad(TW / 2, 0, TW / 2, TH); + REQUIRE(right != nullptr); + + Canvas *rt = gfx->newCanvas(64, 64); + REQUIRE(rt != nullptr); + + auto *cam = Camera2D::createCamera(); + cam->data()->canvas = rt; + cam->data()->active = true; + cam->data()->x = 32.f; + cam->data()->y = 32.f; + cam->data()->zoom = 1.f; + cam->data()->r = 0.f; + cam->data()->g = 0.f; + cam->data()->b = 0.f; + cam->data()->a = 1.f; + + auto *sp = Renderable2D::create(); + sp->transform()->x = 16.f; + sp->transform()->y = 16.f; + sp->sprite()->width = 32.f; + sp->sprite()->height = 32.f; + sp->sprite()->texture = atlas; + sp->sprite()->quad = right; + sp->sprite()->receiveLight = false; + sp->sprite()->canvas = rt; + sp->sprite()->visible = true; + + RenderSystem::render(*gfx); + + Color mid = rt->getPixel(32, 32); + CHECK(mid.g > 0.7f); + CHECK(mid.r < 0.2f); + + sp->sprite()->visible = false; + cam->data()->active = false; + win->close(); +} diff --git a/test/RenderSystem3D.cpp b/test/RenderSystem3D.cpp index b7594b36..7cd40c8a 100644 --- a/test/RenderSystem3D.cpp +++ b/test/RenderSystem3D.cpp @@ -9,6 +9,7 @@ #include #include "graphics/Graphics.h" +#include "graphics/Light.h" #include "graphics/Mesh.h" #include "graphics/RenderSystem.h" #include "graphics/RenderSystem3D.h" @@ -135,6 +136,13 @@ static void resetScene3D() { data->active = false; } } + if (ecs::ComponentManager::inst().registy != nullptr) { + auto lightView = ecs::View(); + for (auto it = lightView.begin(); it != lightView.end(); ++it) { + auto [d] = *it; + d->enabled = false; + } + } if (ecs::ComponentManager::inst().registy != nullptr) { auto view = ecs::View(); for (auto it = view.begin(); it != view.end(); ++it) { @@ -265,7 +273,8 @@ TEST_CASE("RenderSystem3D.textureCheckerPixels") { // Sample left vs right of the projected front face (UV u≈0.25 vs u≈0.75). Color a = gfx->getPixel(cx - dx, cy); Color b = gfx->getPixel(cx + dx, cy); - REQUIRE(std::abs(luma(a) - luma(b)) > 0.08f); + // Lit PBR softens albedo contrast vs unlit; still require a clear left/right split. + REQUIRE(std::abs(luma(a) - luma(b)) > 0.05f); win->close(); } @@ -297,22 +306,127 @@ TEST_CASE("RenderSystem3D.lightingAffectsPixels") { const int cy = gfx->getHeight() / 2; gfx->setScreenReadbackEnabled(true); - // Empirically (and matching the mesh/view setup): -Z lights the visible face brighter. - RenderSystem3D::setDirectionalLight(0.f, 0.f, -1.f, 1.f, 1.f, 1.f); + // Empirically for this mesh/view under the PBR path, +Z lights the visible face brighter. + RenderSystem3D::setDirectionalLight(0.f, 0.f, 1.f, 1.f, 1.f, 1.f); for (int i = 0; i < 3; ++i) { RenderSystem3D::render(*gfx); RenderSystem::render(*gfx); } const float L_bright = luma(gfx->getPixel(cx, cy)); - RenderSystem3D::setDirectionalLight(0.f, 0.f, 1.f, 1.f, 1.f, 1.f); + RenderSystem3D::setDirectionalLight(0.f, 0.f, -1.f, 1.f, 1.f, 1.f); for (int i = 0; i < 3; ++i) { RenderSystem3D::render(*gfx); RenderSystem::render(*gfx); } const float L_dark = luma(gfx->getPixel(cx, cy)); - REQUIRE(L_bright - L_dark > 0.1f); + REQUIRE(L_bright - L_dark > 0.08f); win->close(); } + +TEST_CASE("Lighting3D.pointLightBrightensNearbyFace") { + eve::window::Window *win = nullptr; + Graphics *gfx = nullptr; + openGfxWindow(win, gfx); + resetScene3D(); + + Mesh *mesh = loadUvCube(gfx); + auto *cam = Camera3D::createCamera(); + cam->data()->eyeZ = 3.f; + cam->setAmbient(0.04f, 0.04f, 0.04f); + + auto *ent = Renderable3D::create(); + ent->meshRenderer()->mesh = mesh; + ent->meshRenderer()->texture = makeSolidGray(gfx, 220); + ent->setMetallic(0.05f); + ent->setRoughness(0.55f); + + auto *hud = Renderable2D::create(); + hud->transform()->x = 0; + hud->transform()->y = 0; + hud->sprite()->width = 2; + hud->sprite()->height = 2; + hud->sprite()->r = 0.f; + hud->sprite()->g = 0.f; + hud->sprite()->b = 0.f; + + // Point light in front of +Z face. + auto *light = Light3D::createLight("point"); + light->setPosition(0.f, 0.f, 2.f); + light->setColor(1.f, 1.f, 1.f, 4.f); + light->setRadius(6.f); + + // Disable legacy directional fallback by also having Light3D enabled (already). + gfx->setScreenReadbackEnabled(true); + for (int i = 0; i < 3; ++i) { + RenderSystem3D::render(*gfx); + RenderSystem::render(*gfx); + } + const float L_near = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2)); + + light->setPosition(0.f, 0.f, -4.f); // behind cube → front face darker + for (int i = 0; i < 3; ++i) { + RenderSystem3D::render(*gfx); + RenderSystem::render(*gfx); + } + const float L_far = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2)); + + REQUIRE(L_near > L_far + 0.05f); + + light->setEnabled(false); + win->close(); +} + +TEST_CASE("Lighting3D.metallicIncreasesSpecularHighlight") { + eve::window::Window *win = nullptr; + Graphics *gfx = nullptr; + openGfxWindow(win, gfx); + resetScene3D(); + + Mesh *mesh = loadUvCube(gfx); + auto *cam = Camera3D::createCamera(); + cam->data()->eyeZ = 3.f; + cam->setAmbient(0.05f, 0.05f, 0.05f); + + auto *ent = Renderable3D::create(); + ent->meshRenderer()->mesh = mesh; + ent->meshRenderer()->texture = makeSolidGray(gfx, 200); + ent->setRoughness(0.2f); + ent->setMetallic(0.0f); + + auto *hud = Renderable2D::create(); + hud->transform()->x = 0; + hud->transform()->y = 0; + hud->sprite()->width = 2; + hud->sprite()->height = 2; + + auto *light = Light3D::createLight("dir"); + // Light from the camera side so N·L > 0 and a specular lobe can form. + light->setDirection(0.f, 0.35f, 1.f); + light->setColor(1.f, 1.f, 1.f, 2.0f); + + gfx->setScreenReadbackEnabled(true); + for (int i = 0; i < 3; ++i) { + RenderSystem3D::render(*gfx); + RenderSystem::render(*gfx); + } + const float L_dielectric = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2)); + + ent->setMetallic(1.0f); + ent->setRoughness(0.12f); + for (int i = 0; i < 3; ++i) { + RenderSystem3D::render(*gfx); + RenderSystem::render(*gfx); + } + const float L_metal = luma(gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2)); + + REQUIRE(L_dielectric > 0.08f); + // With this view, the specular lobe may miss the center pixel; metals still + // drop Lambert diffuse, so the metal sample should be darker than dielectric. + REQUIRE(L_metal < L_dielectric - 0.02f); + + light->setEnabled(false); + win->close(); +} diff --git a/test/Shadow3D.cpp b/test/Shadow3D.cpp new file mode 100644 index 00000000..ad1f568b --- /dev/null +++ b/test/Shadow3D.cpp @@ -0,0 +1,205 @@ +#include "zeroerr/assert.h" +#include "zeroerr/unittest.h" + +#include +#include +#include + +#include "graphics/Graphics.h" +#include "graphics/Light.h" +#include "graphics/Mesh.h" +#include "graphics/RenderSystem.h" +#include "graphics/RenderSystem3D.h" +#include "window/Window.h" + +using namespace eve::graphics; + +namespace { + +void openGfxWindow(eve::window::Window *&win, Graphics *&gfx, int w = 400, int h = 300) { + win = eve::window::Window::create(); + gfx = Graphics::create(); + REQUIRE(win != nullptr); + REQUIRE(gfx != nullptr); + win->setGraphics(gfx); + eve::window::WindowSettings s; + s.width = w; + s.height = h; + s.centered = true; + REQUIRE(win->setWindowSettings(s)); +} + +void resetScene3D() { + if (ecs::ComponentManager::inst().registy != nullptr) { + auto view = ecs::View(); + for (auto it = view.begin(); it != view.end(); ++it) { + auto [mr] = *it; + mr->visible = false; + } + } + if (ecs::ComponentManager::inst().registy != nullptr) { + auto camView = ecs::View(); + for (auto it = camView.begin(); it != camView.end(); ++it) { + auto [data] = *it; + data->active = false; + } + } + if (ecs::ComponentManager::inst().registy != nullptr) { + auto lightView = ecs::View(); + for (auto it = lightView.begin(); it != lightView.end(); ++it) { + auto [d] = *it; + d->enabled = false; + } + } + if (ecs::ComponentManager::inst().registy != nullptr) { + auto view = ecs::View(); + for (auto it = view.begin(); it != view.end(); ++it) { + auto [sp] = *it; + sp->visible = false; + } + } +} + +Texture *makeSolid(Graphics *gfx, uint8_t r, uint8_t g, uint8_t b) { + const uint8_t px[4] = {r, g, b, 255}; + return gfx->newTexture(1, 1, px); +} + +/** Unit box centered at origin (from newMeshSphere-style: use a scaled sphere as occluder). */ +Mesh *makeGroundPlane(Graphics *gfx) { + // Thin flat box via scaled sphere is awkward; use a large shallow scale on sphere. + return gfx->newMeshSphere(16, 8); +} + +void addHud() { + auto *hud = Renderable2D::create(); + hud->transform()->x = 0; + hud->transform()->y = 0; + hud->sprite()->width = 2; + hud->sprite()->height = 2; +} + +float luma(const Color &c) { return (c.r + c.g + c.b) / 3.f; } + +void warmPresent(Graphics *gfx) { + for (int i = 0; i < 3; ++i) { + RenderSystem3D::render(*gfx); + RenderSystem::render(*gfx); + } +} + +} // namespace + +TEST_CASE("Shadow3D.dirLightDarkensOccludedGround") { + eve::window::Window *win = nullptr; + Graphics *gfx = nullptr; + openGfxWindow(win, gfx); + resetScene3D(); + + auto *cam = Camera3D::createCamera(); + cam->setEye(0.f, 3.5f, 5.5f); + cam->setTarget(0.f, 0.f, 0.f); + cam->setAmbient(0.05f, 0.05f, 0.05f); + + // Ground: large flat-ish sphere at y=-0.85 + auto *ground = Renderable3D::create(); + ground->setMesh(makeGroundPlane(gfx)); + ground->setTexture(makeSolid(gfx, 220, 220, 220)); + ground->setPosition(0.f, -1.2f, 0.f); + ground->setScale(4.f, 0.15f, 4.f); + ground->setMetallic(0.f); + ground->setRoughness(0.85f); + ground->setCastShadow(false); + ground->setReceiveShadow(true); + + // Occluder above ground + auto *box = Renderable3D::create(); + box->setMesh(gfx->newMeshSphere(20, 12)); + box->setTexture(makeSolid(gfx, 200, 200, 200)); + box->setPosition(0.f, 0.35f, 0.f); + box->setScale(0.55f, 0.55f, 0.55f); + box->setMetallic(0.f); + box->setRoughness(0.6f); + box->setCastShadow(true); + box->setReceiveShadow(false); + + auto *sun = Light3D::createLight("dir"); + sun->setDirection(0.55f, 1.f, 0.35f); + sun->setColor(1.f, 1.f, 1.f, 2.5f); + sun->setCastShadow(true); + sun->setShadowStrength(1.f); + sun->setShadowBias(0.003f); + + addHud(); + gfx->setScreenReadbackEnabled(true); + warmPresent(gfx); + + // Sample near center (likely under occluder) vs far right (lit ground). + const int cx = gfx->getWidth() / 2; + const int cy = gfx->getHeight() / 2 + gfx->getHeight() / 8; + const int dx = gfx->getWidth() / 3; + const float L_center = luma(gfx->getPixel(cx, cy)); + const float L_side = luma(gfx->getPixel(cx + dx, cy)); + + // With shadows, side should be brighter than the occluded center region. + // Also compare against shadows disabled. + sun->setCastShadow(false); + warmPresent(gfx); + const float L_center_off = luma(gfx->getPixel(cx, cy)); + + sun->setCastShadow(true); + warmPresent(gfx); + const float L_center_on = luma(gfx->getPixel(cx, cy)); + + REQUIRE(L_center_off > L_center_on + 0.03f); + REQUIRE(L_side > L_center + 0.02f); + + win->close(); +} + +TEST_CASE("Shadow3D.receiveShadowFalseIgnoresMap") { + eve::window::Window *win = nullptr; + Graphics *gfx = nullptr; + openGfxWindow(win, gfx); + resetScene3D(); + + auto *cam = Camera3D::createCamera(); + cam->setEye(0.f, 3.5f, 5.5f); + cam->setTarget(0.f, 0.f, 0.f); + cam->setAmbient(0.05f, 0.05f, 0.05f); + + auto *ground = Renderable3D::create(); + ground->setMesh(gfx->newMeshSphere(16, 8)); + ground->setTexture(makeSolid(gfx, 220, 220, 220)); + ground->setPosition(0.f, -1.2f, 0.f); + ground->setScale(4.f, 0.15f, 4.f); + ground->setCastShadow(false); + ground->setReceiveShadow(false); + + auto *box = Renderable3D::create(); + box->setMesh(gfx->newMeshSphere(20, 12)); + box->setTexture(makeSolid(gfx, 200, 200, 200)); + box->setPosition(0.f, 0.35f, 0.f); + box->setScale(0.55f, 0.55f, 0.55f); + box->setCastShadow(true); + + auto *sun = Light3D::createLight("dir"); + sun->setDirection(0.55f, 1.f, 0.35f); + sun->setColor(1.f, 1.f, 1.f, 2.5f); + sun->setCastShadow(true); + + addHud(); + gfx->setScreenReadbackEnabled(true); + warmPresent(gfx); + const int cx = gfx->getWidth() / 2; + const int cy = gfx->getHeight() / 2 + gfx->getHeight() / 8; + const float L_offRecv = luma(gfx->getPixel(cx, cy)); + + ground->setReceiveShadow(true); + warmPresent(gfx); + const float L_onRecv = luma(gfx->getPixel(cx, cy)); + + REQUIRE(L_offRecv > L_onRecv + 0.02f); + + win->close(); +} diff --git a/test/async.cpp b/test/async.cpp new file mode 100644 index 00000000..a6a7045e --- /dev/null +++ b/test/async.cpp @@ -0,0 +1,151 @@ +#include "zeroerr/assert.h" +#include "zeroerr/unittest.h" + +#include "common/Module.h" +#include "event/Event.h" +#include "scripts.h" +#include "thread/Thread.h" +#include "timer/Timer.h" + +#include + +#include +#include + +namespace { + +void exposeCore(ssq::VM &vm) { + eve::ModuleManager::expose(vm); +} + +std::string runAsyncSnippet(const std::string &body) { + ssq::VM vm(1024, ssq::Libs::ALL); + exposeCore(vm); + { + ssq::Table eve(vm.find("eve")); + eve.set("asyncScript", std::string(eve::async_content ? eve::async_content : "")); + } + + // Minimal globals expected by async.nut / load prelude. + std::string prelude = R"( + timer <- eve.Timer(); + thread <- eve.Thread(); + event <- eve.Event(); + if ("asyncScript" in eve && eve.asyncScript != "") + compilestring(eve.asyncScript)(); + )"; + + vm.run(vm.compileSource((prelude + "\n" + body).c_str())); + // Return a global `result` string if set. + try { + return vm.find("result").toString(); + } catch (...) { + return {}; + } +} + +} // namespace + +TEST_CASE("async.promise.resolveThen") { + std::string out = runAsyncSnippet(R"( + result <- "fail"; + local p = Promise.resolve(42); + p.then(function(v) { result = "" + v; }); + async_pump(); + async_pump(); + )"); + CHECK_EQ(out, std::string("42")); +} + +TEST_CASE("async.setTimeout") { + std::string out = runAsyncSnippet(R"( + result <- "pending"; + setTimeout(function() { result = "fired"; }, 5); + local t0 = timer.getTime(); + while (timer.getTime() - t0 < 0.05) { + // busy wait wall time; timer uses SDL performance counter + } + // Advance timer.step so getTime keeps moving; fire timers. + for (local i = 0; i < 5; i++) { + timer.step(); + async_pump(); + } + // If still pending, force due by pumping after sleep via thread + if (result == "pending") { + local pool = thread.getPool(); + local task = pool.submitSleep(20); + task.wait(); + timer.step(); + async_pump(); + async_pump(); + } + )"); + CHECK_EQ(out, std::string("fired")); +} + +TEST_CASE("async.asyncSleep") { + std::string out = runAsyncSnippet(R"( + result <- "pending"; + asyncSleep(1).then(function(v) { result = "ok"; }); + local pool = thread.getPool(); + local task = pool.submitSleep(15); + task.wait(); + for (local i = 0; i < 8; i++) { + timer.step(); + async_pump(); + } + )"); + CHECK_EQ(out, std::string("ok")); +} + +TEST_CASE("async.asyncDelay.worker") { + std::string out = runAsyncSnippet(R"( + result <- "pending"; + asyncDelay(5, "hello").then(function(v) { result = v; }); + local pool = thread.getPool(); + local task = pool.submitSleep(30); + task.wait(); + for (local i = 0; i < 10; i++) { + async_pump(); + } + )"); + CHECK_EQ(out, std::string("hello")); +} + +TEST_CASE("async.postMain.event") { + auto *th = eve::thread::Thread::create(); + auto *ev = eve::event::Event::create(); + th->postMain("asynctest", "payload"); + CHECK_EQ(ev->pollName(), std::string("asynctest")); + CHECK_EQ(ev->getLastData(), std::string("payload")); +} + +TEST_CASE("async.Promise.all") { + std::string out = runAsyncSnippet(R"( + result <- "fail"; + Promise.all([Promise.resolve(1), Promise.resolve(2)]).then(function(arr) { + result = "" + arr[0] + "," + arr[1]; + }); + async_pump(); + async_pump(); + async_pump(); + )"); + CHECK_EQ(out, std::string("1,2")); +} + +TEST_CASE("async.asyncSeq") { + std::string out = runAsyncSnippet(R"( + result <- "fail"; + asyncSeq([ + function() { return Promise.resolve(1); }, + function(v) { return Promise.resolve(v + 2); }, + function(v) { return v * 10; }, + ]).then(function(v) { result = "" + v; }); + async_pump(); + async_pump(); + async_pump(); + async_pump(); + async_pump(); + )"); + CHECK_EQ(out, std::string("30")); +} diff --git a/test/math.cpp b/test/math.cpp new file mode 100644 index 00000000..1b408d39 --- /dev/null +++ b/test/math.cpp @@ -0,0 +1,140 @@ +#include "zeroerr/assert.h" +#include "zeroerr/unittest.h" + +#include "math/Math.h" +#include "math/Vec2.h" +#include "math/Vec3.h" +#include "math/Mat4.h" + +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +using namespace eve::math; + +TEST_CASE("math.scalar.clampLerp") { + auto *m = Math::create(); + CHECK_EQ(m->clamp(5.f, 0.f, 3.f), 3.f); + CHECK_EQ(m->clamp(-1.f, 0.f, 3.f), 0.f); + CHECK(std::fabs(m->lerp(0.f, 10.f, 0.5f) - 5.f) < 1e-5f); + CHECK(std::fabs(m->smoothstep(0.f, 1.f, 0.5f) - 0.5f) < 1e-5f); + CHECK(std::fabs(m->degToRad(180.f) - float(M_PI)) < 1e-5f); + CHECK(std::fabs(m->radToDeg(float(M_PI)) - 180.f) < 1e-4f); + CHECK_EQ(m->sign(-2.f), -1.f); + CHECK(std::fabs(m->fract(3.25f) - 0.25f) < 1e-5f); + CHECK(std::fabs(m->approach(0.f, 10.f, 3.f) - 3.f) < 1e-5f); + CHECK(std::fabs(m->wrap(5.f, 0.f, 4.f) - 1.f) < 1e-5f); +} + +TEST_CASE("math.geometry.vec2ops") { + auto *m = Math::create(); + CHECK(std::fabs(m->length2(3.f, 4.f) - 5.f) < 1e-5f); + CHECK(std::fabs(m->distance2(0.f, 0.f, 3.f, 4.f) - 5.f) < 1e-5f); + CHECK(std::fabs(m->dot2(1.f, 0.f, 0.f, 1.f)) < 1e-6f); + CHECK(std::fabs(m->normalize2X(3.f, 4.f) - 0.6f) < 1e-5f); + CHECK(std::fabs(m->normalize2Y(3.f, 4.f) - 0.8f) < 1e-5f); + CHECK(m->pointInCircle(1.f, 1.f, 0.f, 0.f, 2.f)); + CHECK(!m->pointInRect(5.f, 5.f, 0.f, 0.f, 2.f, 2.f)); +} + +TEST_CASE("math.vec2.vec3.classes") { + auto *m = Math::create(); + std::unique_ptr a(m->newVec2(3.f, 4.f)); + CHECK(std::fabs(a->length() - 5.f) < 1e-5f); + a->normalize(); + CHECK(std::fabs(a->length() - 1.f) < 1e-5f); + + std::unique_ptr b(m->newVec3(1.f, 0.f, 0.f)); + std::unique_ptr c(m->newVec3(0.f, 1.f, 0.f)); + std::unique_ptr cr(b->cross(c.get())); + CHECK(std::fabs(cr->getZ() - 1.f) < 1e-5f); +} + +TEST_CASE("math.mat4.transform") { + auto *m = Math::create(); + std::unique_ptr mat(m->newMat4Translation(10.f, 20.f, 0.f)); + std::unique_ptr p(m->newVec2(1.f, 2.f)); + std::unique_ptr out(mat->transformPoint2(p.get())); + CHECK(std::fabs(out->getX() - 11.f) < 1e-4f); + CHECK(std::fabs(out->getY() - 22.f) < 1e-4f); + CHECK(std::fabs(mat->get(12) - 10.f) < 1e-5f); // translation x in column-major +} + +TEST_CASE("math.random.deterministic") { + auto *m = Math::create(); + m->setRandomSeed(42); + float a = m->random(); + float b = m->randomRange(10.f, 20.f); + int c = m->randomInt(1, 6); + m->setRandomSeed(42); + CHECK(std::fabs(m->random() - a) < 1e-7f); + CHECK(std::fabs(m->randomRange(10.f, 20.f) - b) < 1e-7f); + CHECK_EQ(m->randomInt(1, 6), c); + CHECK_GE(c, 1); + CHECK_LE(c, 6); +} + +TEST_CASE("math.noise.range") { + auto *m = Math::create(); + for (int i = 0; i < 20; ++i) { + float n = m->noise2(float(i) * 0.1f, float(i) * 0.07f); + CHECK_GE(n, 0.f); + CHECK_LE(n, 1.f); + } + float n0 = m->noise2(1.5f, 2.5f); + float n1 = m->noise2(1.5f, 2.5f); + CHECK(std::fabs(n0 - n1) < 1e-6f); +} + +TEST_CASE("math.bezier.endpoints") { + auto *m = Math::create(); + CHECK(std::fabs(m->bezierQuadratic(0.f, 0.f, 5.f, 10.f) - 0.f) < 1e-5f); + CHECK(std::fabs(m->bezierQuadratic(1.f, 0.f, 5.f, 10.f) - 10.f) < 1e-5f); + CHECK(std::fabs(m->bezierCubic(0.f, 0.f, 1.f, 2.f, 3.f) - 0.f) < 1e-5f); + CHECK(std::fabs(m->bezierCubic(1.f, 0.f, 1.f, 2.f, 3.f) - 3.f) < 1e-5f); + CHECK(std::fabs(m->bezierCubic2X(0.5f, 0.f, 0.f, 0.f, 10.f, 10.f, 10.f, 10.f, 0.f) - + m->bezierCubic(0.5f, 0.f, 0.f, 10.f, 10.f)) < 1e-5f); +} + +TEST_CASE("math.procgen.hashFbmVoronoi") { + auto *m = Math::create(); + float h0 = m->hash2(3.f, 7.f); + float h1 = m->hash2(3.f, 7.f); + CHECK(std::fabs(h0 - h1) < 1e-6f); + CHECK_GE(h0, 0.f); + CHECK_LE(h0, 1.f); + CHECK(std::fabs(m->hash2(3.f, 7.f) - m->hash2(3.1f, 7.f)) > 1e-4f); + + float f = m->fbm2(0.2f, 0.3f, 4, 2.f, 0.5f); + CHECK_GE(f, 0.f); + CHECK_LE(f, 1.f); + + float r = m->ridged2(1.1f, 2.2f, 3, 2.f, 0.5f); + CHECK_GE(r, 0.f); + CHECK_LE(r, 1.f); + + float v = m->voronoi2(1.3f, 2.7f); + CHECK_GE(v, 0.f); + float e = m->voronoiEdge2(1.3f, 2.7f); + CHECK_GE(e, 0.f); + + float w = m->warpNoise2(0.5f, 0.5f, 2.f); + CHECK_GE(w, 0.f); + CHECK_LE(w, 1.f); +} + +TEST_CASE("math.procgen.easeBiasSnap") { + auto *m = Math::create(); + CHECK(std::fabs(m->inverseLerp(0.f, 10.f, 5.f) - 0.5f) < 1e-5f); + CHECK(std::fabs(m->ease(0.5f, "linear") - 0.5f) < 1e-5f); + CHECK(m->ease(0.5f, "inQuad") < 0.5f); + CHECK(m->ease(0.5f, "outQuad") > 0.5f); + CHECK(std::fabs(m->snap(13.f, 5.f) - 15.f) < 1e-5f); + CHECK(std::fabs(m->quantize(13.f, 5.f) - 10.f) < 1e-5f); + CHECK(std::fabs(m->rotate2X(1.f, 0.f, float(M_PI) * 0.5f)) < 1e-5f); + CHECK(std::fabs(m->rotate2Y(1.f, 0.f, float(M_PI) * 0.5f) - 1.f) < 1e-5f); + CHECK(std::fabs(m->bilinear(0.f, 1.f, 0.f, 1.f, 0.5f, 0.f) - 0.5f) < 1e-5f); +} diff --git a/test/tensor.cpp b/test/tensor.cpp new file mode 100644 index 00000000..66189836 --- /dev/null +++ b/test/tensor.cpp @@ -0,0 +1,297 @@ +#include "zeroerr/assert.h" +#include "zeroerr/unittest.h" + +#include "common/Exception.h" +#include "tensor/TF.h" +#include "tensor/Tensor.h" +#include "tensor/Graph.h" + +#include "graphics/Graphics.h" +#include "window/Window.h" + +#include +#include +#include + +using namespace eve::tensor; + +namespace { + +/** GPU tensor tests need a live Vulkan device, which only exists after a window is created. */ +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("tensor.create.zerosOnes") { + auto *tf = TF::create(); + std::unique_ptr a(tf->zeros2(3, 4)); + CHECK_EQ(a->getRank(), 2); + CHECK_EQ(a->getDim0(), 3); + CHECK_EQ(a->getDim1(), 4); + CHECK_EQ(a->getSize(), 12); + CHECK_EQ(a->getDevice(), std::string("cpu")); + CHECK(a->isEager()); + CHECK(std::fabs(a->reduceSum()) < 1e-6f); + + std::unique_ptr b(tf->ones2(2, 2)); + CHECK(std::fabs(b->reduceSum() - 4.f) < 1e-5f); + CHECK_EQ(b->getDtype(), std::string("float32")); +} + +TEST_CASE("tensor.elementwise.matmul") { + auto *tf = TF::create(); + std::unique_ptr a(tf->fill2(2, 3, 2.f)); + std::unique_ptr b(tf->fill2(2, 3, 3.f)); + std::unique_ptr c(tf->multiply(a.get(), b.get())); + CHECK(std::fabs(c->get2(0, 0) - 6.f) < 1e-5f); + + std::unique_ptr m(tf->zeros2(2, 3)); + m->set2(0, 0, 1.f); + m->set2(0, 1, 2.f); + m->set2(0, 2, 3.f); + m->set2(1, 0, 4.f); + m->set2(1, 1, 5.f); + m->set2(1, 2, 6.f); + std::unique_ptr n(tf->zeros2(3, 2)); + n->set2(0, 0, 1.f); + n->set2(1, 0, 1.f); + n->set2(2, 0, 1.f); + n->set2(0, 1, 0.f); + n->set2(1, 1, 0.f); + n->set2(2, 1, 0.f); + std::unique_ptr p(tf->matmul(m.get(), n.get())); + CHECK_EQ(p->getDim0(), 2); + CHECK_EQ(p->getDim1(), 2); + CHECK(std::fabs(p->get2(0, 0) - 6.f) < 1e-4f); + CHECK(std::fabs(p->get2(1, 0) - 15.f) < 1e-4f); +} + +TEST_CASE("tensor.inplace.reduce") { + auto *tf = TF::create(); + std::unique_ptr a(tf->arange(5)); + CHECK(std::fabs(tf->reduceSum(a.get()) - 10.f) < 1e-5f); + CHECK(std::fabs(tf->reduceMean(a.get()) - 2.f) < 1e-5f); + a->mulScalarInPlace(2.f); + CHECK(std::fabs(a->get1(4) - 8.f) < 1e-5f); + a->reluInPlace(); +} + +TEST_CASE("tensor.reshape.transpose") { + auto *tf = TF::create(); + std::unique_ptr a(tf->arange(6)); + std::unique_ptr b(tf->reshape2(a.get(), 2, 3)); + CHECK_EQ(b->get2(1, 2), 5.f); + std::unique_ptr c(tf->transpose(b.get())); + CHECK_EQ(c->getDim0(), 3); + CHECK_EQ(c->getDim1(), 2); + CHECK_EQ(c->get2(2, 1), 5.f); +} + +TEST_CASE("tensor.activations.where") { + auto *tf = TF::create(); + std::unique_ptr x(tf->fill1(3, -1.f)); + x->set1(1, 2.f); + std::unique_ptr y(tf->relu(x.get())); + CHECK(std::fabs(y->get1(0)) < 1e-6f); + CHECK(std::fabs(y->get1(1) - 2.f) < 1e-5f); + + std::unique_ptr cond(tf->zeros1(3)); + cond->set1(0, 1.f); + std::unique_ptr a(tf->fill1(3, 10.f)); + std::unique_ptr b(tf->fill1(3, 20.f)); + std::unique_ptr w(tf->where(cond.get(), a.get(), b.get())); + CHECK(std::fabs(w->get1(0) - 10.f) < 1e-5f); + CHECK(std::fabs(w->get1(1) - 20.f) < 1e-5f); +} + +TEST_CASE("tensor.random.seeded") { + auto *tf = TF::create(); + tf->setRandomSeed(7); + std::unique_ptr a(tf->randomUniform2(4, 4)); + tf->setRandomSeed(7); + std::unique_ptr b(tf->rand2(4, 4)); + CHECK(std::fabs(a->get(0) - b->get(0)) < 1e-7f); + CHECK_GE(a->reduceMin(), 0.f); + CHECK_LE(a->reduceMax(), 1.f); +} + +TEST_CASE("tensor.func.compileRun") { + auto *tf = TF::create(); + + // Eager reference + std::unique_ptr xEager(tf->zeros2(2, 3)); + xEager->set2(0, 0, 1.f); + xEager->set2(0, 1, 2.f); + xEager->set2(0, 2, 3.f); + xEager->set2(1, 0, 4.f); + xEager->set2(1, 1, 5.f); + xEager->set2(1, 2, 6.f); + std::unique_ptr w(tf->zeros2(3, 2)); + w->set2(0, 0, 1.f); + w->set2(1, 0, 1.f); + w->set2(2, 0, 1.f); + w->set2(0, 1, 0.f); + w->set2(1, 1, 0.f); + w->set2(2, 1, 0.f); + std::unique_ptr mm(tf->matmul(xEager.get(), w.get())); + std::unique_ptr eagerOut(tf->relu(mm.get())); + + std::unique_ptr fn(tf->func()); + Tensor *x = fn->input2(2, 3); + Tensor *y = tf->matmul(x, w.get()); // captures w as Const + y = tf->relu(y); + fn->setOutput(y); + std::unique_ptr compiled(fn->compile()); + + std::unique_ptr out(compiled->run1(xEager.get())); + CHECK_EQ(out->getDim0(), 2); + CHECK_EQ(out->getDim1(), 2); + CHECK(std::fabs(out->get2(0, 0) - eagerOut->get2(0, 0)) < 1e-4f); + CHECK(std::fabs(out->get2(1, 0) - eagerOut->get2(1, 0)) < 1e-4f); + CHECK_EQ(compiled->getDevice(), std::string("cpu")); +} + +TEST_CASE("tensor.func.symbolicReadThrows") { + auto *tf = TF::create(); + std::unique_ptr fn(tf->func()); + Tensor *x = fn->input1(4); + CHECK(x->isSymbolic()); + bool threw = false; + try { + (void)x->get1(0); + } catch (const eve::Exception &) { + threw = true; + } + CHECK(threw); + // Finish tracing cleanly + fn->setOutput(x); + std::unique_ptr compiled(fn->compile()); + std::unique_ptr feed(tf->arange(4)); + std::unique_ptr out(compiled->run1(feed.get())); + CHECK(std::fabs(out->get1(3) - 3.f) < 1e-5f); +} + +// --- GPU (compute shader) path --------------------------------------------- +// These skip (return) when no Vulkan device / glslc is available (e.g. some CI +// images), matching test/gpgpu.cpp's tolerant pattern. When the environment +// does support it, CompiledFunction::getDevice() should report "gpu". + +TEST_CASE("tensor.gpu.compiledFunctionElementwiseAndMatmul") { + if (!tryInitGpuWindow()) return; + + auto *tf = TF::create(); + + std::unique_ptr w(tf->zeros2(3, 2)); + w->set2(0, 0, 1.f); + w->set2(1, 0, 0.f); + w->set2(2, 0, 1.f); + w->set2(0, 1, 0.f); + w->set2(1, 1, 1.f); + w->set2(2, 1, 0.f); + + // relu(x * 2 + 1) matmul w — exercises binary(add), unary(mul/relu chain as + // separate nodes) and matmul kernels together. + std::unique_ptr fn(tf->func()); + Tensor *x = fn->input2(2, 3); + Tensor *y = tf->mulScalar(x, 2.f); + y = tf->addScalar(y, 1.f); + y = tf->relu(y); + y = tf->matmul(y, w.get()); + fn->setOutput(y); + std::unique_ptr compiled(fn->compile()); + + std::unique_ptr xd(tf->zeros2(2, 3)); + xd->set2(0, 0, -1.f); + xd->set2(0, 1, 2.f); + xd->set2(0, 2, -3.f); + xd->set2(1, 0, 4.f); + xd->set2(1, 1, -5.f); + xd->set2(1, 2, 6.f); + + std::unique_ptr out(compiled->run1(xd.get())); + + // CPU reference via eager ops. + std::unique_ptr ref1(xd->mulScalar(2.f)); + std::unique_ptr ref2(ref1->addScalar(1.f)); + std::unique_ptr ref3(ref2->relu()); + std::unique_ptr ref(ref3->matmul(w.get())); + + REQUIRE_EQ(out->getDim0(), ref->getDim0()); + REQUIRE_EQ(out->getDim1(), ref->getDim1()); + for (int i = 0; i < out->getSize(); ++i) CHECK(std::fabs(out->get(i) - ref->get(i)) < 1e-3f); + + // Confirms the compute-shader path actually ran (not a silent CPU fallback). + // Only meaningful when glslc + a Vulkan device are both available. + CHECK_EQ(compiled->getDevice(), std::string("gpu")); +} + +TEST_CASE("tensor.gpu.compiledFunctionWhereTranspose") { + if (!tryInitGpuWindow()) return; + + auto *tf = TF::create(); + + std::unique_ptr fn(tf->func()); + Tensor *cond = fn->input1(4); + Tensor *a = fn->input1(4); + Tensor *b = fn->input1(4); + Tensor *sel = tf->where(cond, a, b); + Tensor *sel2 = tf->reshape2(sel, 2, 2); + Tensor *out = tf->transpose(sel2); + fn->setOutput(out); + std::unique_ptr compiled(fn->compile()); + + std::unique_ptr c(tf->zeros1(4)); + c->set1(0, 1.f); + c->set1(1, 0.f); + c->set1(2, 1.f); + c->set1(3, 0.f); + std::unique_ptr av(tf->fill1(4, 10.f)); + std::unique_ptr bv(tf->fill1(4, 20.f)); + + // select -> [10,20,10,20]; reshape(2,2) -> [[10,20],[10,20]]; transpose -> [[10,10],[20,20]] + std::unique_ptr result(compiled->run3(c.get(), av.get(), bv.get())); + REQUIRE_EQ(result->getDim0(), 2); + REQUIRE_EQ(result->getDim1(), 2); + CHECK(std::fabs(result->get2(0, 0) - 10.f) < 1e-4f); + CHECK(std::fabs(result->get2(0, 1) - 10.f) < 1e-4f); + CHECK(std::fabs(result->get2(1, 0) - 20.f) < 1e-4f); + CHECK(std::fabs(result->get2(1, 1) - 20.f) < 1e-4f); +} + +TEST_CASE("tensor.gpu.reduceLargeTensor") { + if (!tryInitGpuWindow()) return; + + auto *tf = TF::create(); + const int n = 20000; // above the GPU-reduce size threshold + std::unique_ptr t(tf->zeros1(n)); + for (int i = 0; i < n; ++i) t->set1(i, float(i % 17) - 5.f); + + float gpuSum = tf->reduceSum(t.get()); + float gpuMin = tf->reduceMin(t.get()); + float gpuMax = tf->reduceMax(t.get()); + float gpuMean = tf->reduceMean(t.get()); + + double cpuSum = 0.0; + float cpuMin = t->get(0); + float cpuMax = t->get(0); + for (int i = 0; i < n; ++i) { + float v = t->get(i); + cpuSum += v; + cpuMin = std::min(cpuMin, v); + cpuMax = std::max(cpuMax, v); + } + CHECK(std::fabs(gpuSum - float(cpuSum)) < std::max(1.f, std::fabs(float(cpuSum))) * 1e-3f); + CHECK(std::fabs(gpuMin - cpuMin) < 1e-4f); + CHECK(std::fabs(gpuMax - cpuMax) < 1e-4f); + CHECK(std::fabs(gpuMean - float(cpuSum / n)) < 1.f); +}