Состояние на момент заведения репозитория. C++ приложение (src/, shaders/, tests/) — минимальный редактор 3D-моделей на Vulkan 1.3: орбитальная камера, три опорные сетки через начало координат, загрузка .obj с режимами отображения. Весь Vulkan изолирован в src/vk/. Исследование (docs/) — оригинальные статьи по KBC (docs/origins) и Python-решатель D2Q9 KBC-N1 с AMR 2x и SDF+Bouzidi (docs/theory). В решателе перед коммитом исправлены дефекты, найденные сверкой с первоисточниками: относительный порог знаменателя энтропийного стабилизатора (абсолютный вырождал KBC в LBGK на 77-99% узлов), заворот вход/выход в углах домена, диагностика средней плотности по фиктивным узлам тела, зашитый refine=2. Подробности — docs/theory/solver_2x_sdf/README.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
10 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
SimVulcan is a minimal Vulkan 1.3 / C++20 3D model editor. It renders a
3D editor space — three reference grid planes (XY, XZ, YZ) through the origin plus
coloured X/Y/Z axes — viewed through an orbit camera, and loads/displays .obj
models with selectable display modes (solid, wireframe, solid + wireframe).
The C++ side runs no simulation: it is a focused rendering skeleton with an ImGui
interface (a Viewport control panel and a Mesh load panel). The repository also
carries an unrelated Python research prototype under docs/theory/ — see
Research prototype below; it is not part of the
CMake build.
Build
Prerequisites: Vulkan SDK 1.3.290+ (provides glslangValidator for offline
shader compilation), CMake 3.26+, Ninja, a C++20 compiler (MSVC 19.36+,
gcc 11+, clang 14+). On macOS, Vulkan is via MoltenVK (Apple Silicon only).
cmake --preset windows-msvc-release
cmake --build --preset windows-msvc-release
Presets: windows-msvc-debug, windows-msvc-release, linux-gcc-release,
linux-clang-release, macos-arm64-release (all Ninja, one dir per preset under
build/<presetName>/). The first configure fetches dependencies via FetchContent
(GLFW, GLM, volk, vk-bootstrap, VulkanMemoryAllocator, Dear ImGui, spdlog,
tinyobjloader, Catch2) and needs network access. VK_NO_PROTOTYPES is set
project-wide; Vulkan entry points load through volk.
Warnings come from simv_set_warnings (/W4 /permissive-, or
-Wall -Wextra -Wpedantic -Wshadow -Wold-style-cast …); they are not errors.
cmake/Sanitizers.cmake defines simv_enable_sanitizers (Debug-only ASan/UBSan)
but no target currently calls it — wire it in manually when chasing memory bugs.
Running
The executable resolves SPIR-V relative to the working directory (FindSpvPath
probes spirv/<rel> and current_path()/spirv/<rel>). The SimVulcan POST_BUILD
step copies the compiled spirv/ tree and assets/ next to the executable, so
run from the executable's own directory (build/<preset>/src/app/). main.cpp
also probes several ../ ancestors for assets/meshes. Meshes load at runtime
through the ImGui Mesh panel.
Running writes two files into the CWD: pipeline_cache.bin (serialised
VkPipelineCache, reloaded on the next start) and ImGui's imgui.ini. Both are
disposable — delete them if pipeline creation or the panel layout misbehaves.
ContextOptions::enableValidation / enableDebugUtils default to true in
every build config, so the Khronos validation layer is requested even in Release;
messages (error + warning severity) go through spdlog.
run.bat at the repo root is stale: it launches
build/vs2022/src/app/Release/SimVulcan.exe, a path the Ninja presets never
produce. Do not point users at it without fixing the path first.
Tests
Catch2 unit tests, pure CPU/math (mesh bounds + welding). No GPU required.
cmake --build --preset windows-msvc-debug --target simv_tests
ctest --preset windows-msvc-debug
windows-msvc-debug is the only preset with a testPreset — for the other
configs, invoke ctest in build/<preset>/ directly.
Single test / subset — either through CTest (each TEST_CASE is registered
individually by catch_discover_tests):
ctest --preset windows-msvc-debug -R "WeldVertices" -V
or by running the binary with a Catch2 name or tag filter:
./build/windows-msvc-debug/tests/simv_tests.exe "[decimator]"
./build/windows-msvc-debug/tests/simv_tests.exe --list-tests
Architecture
Library / target map
SimVulcan (exe) → simv_core, simv_vk, simv_mesh, simv_editor
simv_core → simv_vk (App owns Window + vk::Renderer; no Vulkan calls)
simv_editor → simv_core, simv_mesh (Camera, input, ImGui panels; no Vulkan)
simv_mesh → simv_core (CPU mesh only; no Vulkan)
simv_vk → third-party (volk, vk-bootstrap, VMA, GLFW, GLM, spdlog, imgui)
simv_shaders → glslangValidator (GLSL → SPIR-V)
Namespaces follow directories: simv::core, simv::vk, simv::mesh,
simv::editor. Each library exports src/ as its include root, so includes are
written module-qualified (#include "mesh/Mesh.h", #include "vk/Renderer.h").
Frame loop
main.cpp creates core::App, which owns the core::Window and a
vk::Renderer, then runs the loop. Each frame Renderer::DrawFrame calls the UI
callback (between ImGui NewFrame/Render), then records the scene: grid + mesh into
one dynamic-rendering pass with a depth attachment, followed by ImGui, then
presents (2 frames in flight, sync2 submits).
main.cpp wires the editor via the UI callback: it draws the panels, applies
mouse input to the editor::Camera, and pushes the resulting state into the
renderer (SetViewProj, SetRenderMode, SetGridVisible). Mesh loads go through
MeshLoadPanel's callback → Renderer::SetMeshCpu.
Mesh load path
MeshLoadPanel lists *.obj in the mesh directory and kicks off
mesh::LoadObjAsync (worker thread, std::future). The future is drained on
the main thread at the top of MeshLoadPanel::Draw, so the OnLoaded callback
— and therefore the GPU upload — always runs on the render thread. Loader
exceptions surface as the panel's status string.
main.cpp's OnLoaded welds the mesh (WeldVertices, tolerance 1e-4), logs
the counts, uploads via SetMeshCpu, and reframes the camera to the bbox radius.
Despite the file name, mesh/MeshDecimator.h implements only spatial-hash
vertex welding (collapse near-duplicates, drop degenerate triangles, recompute
bounds) — there is no LOD/decimation.
Non-obvious invariants (read before editing)
- All Vulkan lives in
src/vk/.core/,mesh/,editor/andapp/make no Vulkan API calls.vk::Renderer's public header is deliberately Vulkan-free (pImpl + glm/RenderModeonly) soAppcan own it without pulling in volk. Keep it that way — do not leakVk*types into the public interfaces of those modules. - Single render pass with depth. Scene (grid + mesh) and ImGui draw into one
vkCmdBeginRenderingpass that has both a colour and aD32_SFLOATdepth attachment. The ImGui backend is initialised withdepthAttachmentFormatset so its pipeline matches the pass; the depth buffer is recreated with the swapchain. - Wireframe needs
fillModeNonSolid.MeshRendererbuilds a fill pipeline and aVK_POLYGON_MODE_LINEpipeline; the line pipeline uses a small depth bias so the overlay sits on top of the fill. The device feature is requested inContext. Culling is off (VK_CULL_MODE_NONE) — loaded models may have mixed winding. - Camera is fixed on the origin.
editor::Cameraorbits (yaw/pitch/distance) the world origin; loaded models are recentred there via a translate-only model matrix. Projection uses Vulkan clip space (GLM_FORCE_DEPTH_ZERO_TO_ONE+ Y flip, isolated toCamera.cpp). - Buffers.
vk::GpuMeshowns the model's vertex/index buffers (staged upload on the transfer queue).GridRendererbuilds a static host-visible line buffer once. Both scene renderers use only a push constant — no descriptor sets. - Push-constant layout is a cross-file contract.
MeshRenderer.cpp's anonymousMeshPC { mat4 mvp; vec4 color; }must stay byte-identical to thepush_constantblock inmesh.vert/mesh.frag;color.ais a flag, not alpha (1 = flat-shaded, 0 = constant colour for wireframe).GridRendererpushes a baremat4(vertex stage only). Change either side and you must change both. - Swapchain recreation rebuilds sync objects.
RecreateSwapDependentrecreates the per-frameimageAvailablesemaphores (a failed acquire can leave one signalled) and the per-swapchain-imagerenderFinishedsemaphores (the image count may change), then re-ensures both pipelines. Keep that ordering if you touch resize handling. - Mesh upload stalls the device.
SetMeshCpu/ClearMeshcallWaitIdlebefore touchingGpuMesh— acceptable because loads are rare; do not copy that pattern into per-frame paths.
Shaders
GLSL under shaders/editor/ (mesh.{vert,frag}, grid.{vert,frag}). simv_shaders
compiles each to build/<preset>/spirv/editor/<name>.spv targeting vulkan1.3,
with shaders/ as the -I root (so #include "common/foo.glsl" would resolve).
Adding a shader under that globbed dir is picked up automatically
(CONFIGURE_DEPENDS). mesh.frag reconstructs a flat normal from screen-space
derivatives, so the vertex stream carries only positions (tight float3, one
binding, one attribute).
Logging
simv::core::Logger (spdlog-backed, singleton) with category-based throttling.
Log via LogFmt(LogCategory, LogLevel, fmt, args...). Categories: Core,
Vulkan, MeshIO, UI, Test. Per-category level, throttle interval and
on/off are settable at runtime (SetMinLevel / SetThrottle / SetEnabled).
Some low-level Vulkan code still calls spdlog:: directly.
Research prototype (docs/theory/)
Separate from the C++ application and from the CMake build: a Python Lattice Boltzmann (D2Q9, KBC-N1) research codebase — cylinder flow with a ×2 nested AMR patch and SDF+Bouzidi boundaries. GPU/CuPy only, no CPU fallback. Its documentation and the running experiment log are in Russian.
docs/theory/solver_2x_sdf/— the component-split solver (run.py,run_blockage.py,run_factors.py); itsREADME.mdis the authoritative status/experiment log and maps every file to the physics it owns.docs/theory/demos_gpu/— demo runs that produce the notebook's figures/GIFs; they import physics fromsolver_2x_sdf, never duplicate it.docs/theory/kbc_lbm.ipynb— the write-up; it embeds pre-rendered artefacts and does not execute the simulations.docs/origins/— source PDFs behind the theory.
Do not fold this into the CMake build, and do not treat it as dead code — it is active research with a documented experiment history.