AnimSkin can now write CPU-skinning results back to the render mesh so skeletal animation actually plays from scripts:
- capture bind normals at fromModel and skin them per pose (updateSkinnedNormals / getSkinnedNormals, cofactor inverse-transpose + normalize)
- packed getSkinnedPositions() array getter, matching AnimLattice's script pattern
- one-call applyToMesh(gfx, mesh, pose): skin positions+normals then in-place gpu.updateMeshVertices
- Renderable3D.getMesh() script binding so createRenderable meshes are reachable
- Mesh::gpuVertexCount so getVertexCount() is correct for non-morph meshes (both backends)
- tests: packed positions == skinPositionsTo, normals follow pose and stay unit length, applyToMesh render test with in-place updates
- docs: animation/graphics user guides + design spec
dev is the repo's feature branch (main is release-gated to promote/docs PRs).
Resolved JobSystemThreadPool.cpp to our completeJob ordering (decrement
outstanding counters after fireCompletion/releaseDependents) — dev's merged
PR #134 version kept the pre-fix order that can reset the frame arena under a
still-running worker.
Port the deferred FrameGraph integration onto main's split Vulkan backend:
- Graphics3D.cpp owns buildDeferredFrameGraphs / recordDeferredFrameGraph /
recordShadowCascadePass / recordGBufferPassDraws (parallel JobSystem
recording, one command pool per frame-slot per pass).
- begin3DFrame / beginSwapchainRenderPass / flushToSwapchain 2D-only path
call recordDeferredFrameGraph() instead of the serial pending passes.
- Drop the superseded serial recordPendingShadowPasses/GBufferPass and the
duplicate culling implementation (main's RenderSystem3D already carries
the per-camera / per-cascade frustum culling).
- VKBuilder submodule -> 51514d9 (master; our spec-safe pool/lifecycle PR
was merged upstream as sunxfancy/VKBuilder#2).
CPU trilinear lattice deformation for procedural 3D scale/squash-stretch
animation: per-control-point scale + offset, model/array binding, deformed
position/normal cache, script API (anim.newLattice + AnimLattice class) and
gfx.newMeshFromArrays / gfx.updateMeshVertices script wrappers so scripts can
upload deformed meshes per frame. Includes unit tests and the
examples/lattice-deform demo.
TSan (WSL) proves the JobSystem + executor + framegraph planning are
data-race-free; on Windows the parallel record path AVs inside
amdvlk64.dll from vkb::FrameGraph::record's recordOne on two workers, and
still crashes after device->waitIdle() — an AMDVLK concurrent-recording
defect, not a pending-CB reuse issue. Engine keeps serial recording; the
executor stays available for drivers/CI builds that support it.
Stage D (shadow part): each in-flight slot's FrameGraph now owns both the
shadow and G-buffer passes as declarative, dependency-free passes in one
layer. The CSM array is imported per cascade via its layer view with
arrayLayers=3 so barriers cover the whole array; every cascade ends in
DepthStencilReadOnlyOptimal for the engine-side forward pass to sample.
- recordPendingShadowPasses / recordPendingGBufferPass are gone, merged
into recordDeferredFrameGraph(): graph record + submit before the
swapchain pass, with a once-per-slot-per-frame guard so repeated
render3D() calls in one script frame never re-record the same command
buffers (resetting in-flight CBs hung the driver).
- buildDeferredFrameGraphs() rebuilds one graph per slot from the current
shadow array + G-buffer targets; shadows can be enabled without a
G-buffer pass and vice versa (graphs are built on demand).
- JobSystem fix (PR #134 scheduler bug): completeJob now decrements
outstanding/outstandingFrame only after fireCompletion +
releaseDependents have finished using the job. Previously a waiter
(beginFrame/endFrame) could observe outstandingFrame == 0 and reset the
per-frame arena while a worker still touched the job (use-after-free:
lost forked job -> hang, or crash). Two stress tests
(render_graph.jobSystemGroupStress, render_graph.jobSystemEngineFramePattern)
cover the engine's exact beginFrame/parallel_for/task-group frame
pattern.
- Recording stays serial (graph->record()) for now: the JobSystem
help-execution path still has a residual timing-sensitive race that can
leave a worker stuck in a pass callback. The parallel executor
(jobSystemPassExecutor) is ready and CPU-tested; enable it once that
race is root-caused upstream (documented in FrameGraphJobs.h).
Verified locally (win32-debug, full clean rebuild): render_graph.*,
thread.*, Shadow3D.*, RenderSystem3D.*, Lighting3D.*, Mesh.* and
AO/AA/Outline/GI — 77 cases pass.
- GpuDrivenCaps: probe + enable Vulkan 1.2 core features (drawIndirectCount,
descriptor indexing, non-uniform indexing, update-after-bind) at device
creation; the toolchain's Vulkan headers omit the core drawIndirect bit so
it is assumed supported and left untouched.
- Bindless Set1: sampler2D array (4096) + samplerCube array (64) + SSBO table
array (64) with PARTIALLY_BOUND | UPDATE_AFTER_BIND; textures register on
creation, placeholder-filled with the 1x1 white texture/cubemap.
- GPU tables: GpuMeshRecord (bounds + index/vertex ranges) filled at mesh
upload and GpuMaterialRecord built from Material params, uploaded to
host-visible SSBOs with fixed capacity.
- FrameArena: per-frame-slot host-visible ring allocator, reset each
begin3DFrame; unused until phase 1 (indirect draw).
- Tests: GpuDriven.capsAvailable / bindlessTextureRegistration /
meshTableRegistration (GPU runtime verification pending driver recovery).
Rendering is unchanged: nothing consumes the new infrastructure yet.
Stage B of the FrameGraph migration: the G-buffer fill is now a
declarative FrameGraph pass recorded with the JobSystem executor and
submitted before the swapchain pass, replacing the hand-rolled
render-pass + manual begin/end layout tracking in recordPendingGBufferPass.
Design:
- One vkb::FrameGraph per in-flight present slot (framesInFlight=1),
importing the engine-owned ColorTarget images via importTexture. The
engine keeps image ownership, so renderEntityIdMask /
readGBufferToImageData and the AO/outline/AA descriptor wrappers work
unchanged.
- Each graph declares addPass("gbuffer") with 3 color attachments + 1
depth attachment (clear ops matching the old path). compilePlan derives
the dependency/barrier/layout plan; attachment->sampled transitions
fold into render-pass initial/final layouts (ShaderReadOnly /
DepthStencilReadOnly after the pass).
- recordPendingGBufferPass now runs recordFrameGraphWithJobSystem() +
graph->submit() inside a JobSystem frame bracket; the graph's command
buffer is recorded (serial for one pass today; parallel once shadow
cascades join in Stage D) and submitted on the graphics queue before
the swapchain pass. The graph's RP is render-pass compatible with the
engine's gbuffer pipelines (same formats/samples/subpass layouts).
- Command-buffer safety without a swapchain-driven FrameGraph: each
graph's CB is only reused two frames later, by which time the present
slot fence (waited in Present::begin) guarantees the previous graph
submit completed (same queue, submitted before the present CB).
An upstream VKBuilder patch (swapchain-less frame advancement + fences)
would let this collapse back to one graph.
Verified locally (win32-debug, full clean rebuild): render_graph.*,
thread.*, Shadow3D.*, RenderSystem3D.*, Lighting3D.*, Mesh.* and
AO/AA/Outline/GI (75 cases) all pass. Also fixed the test build's
stale-obj layout mismatch that made the new member corrupt ownedTextures
until the graphics module was rebuilt from scratch.
PR #134's secondary-command-buffer recording violated the Vulkan state
machine (secondaries began their own render pass instances without
pInheritanceInfo; the spec-compliant fix 1291df8 was reverted). Replace
that path with the groundwork for the vkb::FrameGraph migration:
- Drop the illegal secondary-CB deferred recording and restore the
branch base's legal inline recording for shadow/GBuffer passes.
- Fix frame-prep culling regressions from PR #134:
* per-item camera frustum culling (item.culled) instead of always
testing against the main camera;
* default-camera culling for the G-buffer pass (item.culledMain);
* restore per-cascade CSM caster masks (item.cascadeMask).
Camera view-proj matrices are snapshotted on the main thread before
the parallel job forks; workers only read immutable data. The light
job's CSM output is joined before the entity job forks so cascade
culling never races with the shadow-upload write.
- Add FrameGraphJobs.h: vkb::PassRecordExecutor backed by the JobSystem
(layer-internal fork/join via frame task groups, layers sequential),
the exact executor the render backend will hand to FrameGraph::record().
- Add device-free tests (test/render_graph.cpp): the engine's target
pass topology (3 CSM cascades + GBuffer in one parallel layer ->
forward -> present) plans with attachment->sampled barrier folding
(zero explicit barriers on forward/present), and the JobSystem
executor records every pass exactly once, in parallel per layer.
Verified locally (win32-debug): render_graph.*, thread.*, Shadow3D.*,
RenderSystem3D.*, Lighting3D.* and Mesh.* all pass (58/58). Bundle-mode
SEGFAULTs are the known pre-existing zeroerr single-process issue, not
this change. See docs/dev/superpowers/specs/2026-08-20-framegraph-mt-render.md
for the staged migration plan (GBuffer into the graph next, then the
dedicated render thread).
* chore(ci): add clang-format gate, CODEOWNERS and agent collaboration rules
* refactor(modules): clear remaining back-edges and split the largest TUs
Three known dependency back-edges are gone (module_depgraph.py KNOWN_BACK_EDGES
is now empty; --check passes with 0 violations, 0 cycles):
- window -> graphics: window queries the new IWindowSurfaceHost capability
(common/WindowSurfaceHost.h) instead of holding a Graphics pointer;
Window::setGraphics was removed and all call sites updated.
- window -> image: Window::setIcon was replaced by setIconRGBA(const uint8_t*,
w, h); the window no longer decodes ImageData.
- scene -> graphics: pickScreenAt / collectFrustumIdsAt implementations moved
to graphics/ScenePicking.cpp (graphics -> scene is a legal downward edge);
scene now depends only on spatial.
TU splits (pure moves, verified identical method sets and brace balance):
- vulkan/Graphics.cpp (5118 lines) -> Graphics.cpp (lifecycle), GraphicsPipeline,
Graphics2D, Graphics3D, GraphicsMesh + shared GraphicsInternal.h.
- scene/Scene.cpp -> Scene.cpp (script layer / host lifecycle / serialization)
+ SceneNodes.cpp + shared SceneInternal.h.
Verified with a real build (MSVC 19.51, prebuilt third-party): full build green;
window.* and Scene.* suites pass, including scene picking/culling and GPU cases.
* refactor(graphics): narrow Graphics interfaces and clear public-header leaks
Step 4 — interface segregation:
- New lean interfaces: IGraphics2D, IGraphics3D, IResourceFactory, IPostFX
(pure virtuals, forward-declared types only; graphics/Color.h extracted from
Canvas.h so modules that only need the color type stop pulling Canvas.h).
- Graphics now implements all four facets; consumers can depend on the narrow
interface they use instead of the 129-method god class.
- Migrated voxel (VoxelWorld::drawVisible -> IGraphics3D*) and model3d
(ModelRenderer::buildRenderable(s) -> IResourceFactory&).
- Graphics.h slims from 24 sub-header includes to a handful of small type
headers + forward declarations; the destructor moved out-of-line so
unique_ptr effect members are destroyed where types are complete.
Step 5 — header leaks (module_depgraph.py '*' marks: 14 -> 7):
- map/TileLayer.h, particles/ParticleEmitter.h, avatar/AvatarInstance.h,
animation/SpineAnim.h, map/TileSystem.h, model3d/ModelData.h,
model3d/ModelRenderer.h, audio/Audio.h + Source.h, image/ImageData.h +
Image.h, dialogue/Dialogue.h, procgen Palette/OutputSpec/TextureRecipe/
PbrMaterial -> forward declarations, includes moved to .cpp.
- voxel/VoxelWorld.h: terrain sampler Pimpl'd (unique_ptr + out-of-line
destructor), removing the procgen header dependency.
- gpgpu/vulkan backend headers stop pulling graphics/vulkan/Graphics.h.
- Remaining '*' marks are deliberate: tiny POD/enum headers (GridConfig,
MapObject, BlendMode, Color), an internal backend header
(GraphicsInternal.h -> filesystem), and lean interface references.
Verified with a full clean build (MSVC) and tests: window/Scene/voxel/
RenderSystem/Quad/shader/TextureSampler/resourceLifetime pass; broad suite
only shows the pre-existing PHYSFS-init filesystem-environment failures.
* feat(resources): revive ResourceManager as unified IAssetReloader-backed cache
* fix(resources): leak ResourceManager singleton to avoid exit-time teardown crashes
Cached CPU resources may own third-party handles (FreeType faces, Assimp
scenes, image decode handlers) whose libraries are torn down at process
exit in an unspecified TU order; destroying cached entries from the
singleton destructor can segfault at exit (seen on CI Windows in
model3d.materialApi.objMtl). Keep the singleton alive until OS reclaim;
explicit unload()/clear() still release entries during the run.
* refactor(devtools): consume business modules through capability interfaces
DevTools / cmdline no longer include business-module headers, so trimming
scene / physics / procgen / particles / audio / ui / graphics no longer
requires disabling devtools, and devtools can be switched off with
-DEVENGINE_MODULE_DEVTOOLS=OFF.
New capability interfaces in common/:
- ISceneQuery (scene) - host/node inspection + mutation + transform sync
- IRenderCapture (graphics) - render status, PNG capture, camera control,
visible-entity snapshot, entity-ID mask, G-buffer PNGs
- IPhysicsQuery (physics) - 2D world lifecycle + ray casts
- IProcgenQuery (procgen) - recipe listing, map/mesh generation
- IParticlesQuery (particles) - emitter status + one-shot emit
- IAudioQuery (audio) - volume / stop-all
- IEditorHost (ui) - declarative editor host (used by cmdline too)
Providers register in their module constructors. DevTools rewires McpServer
(render/scene/procgen/physics/particles/audio/host tools), SceneInspect
(now delegates to ISceneQuery + IRenderCapture) and RenderVision (uses
IRenderCapture::capturePngDataUrl); cmdline/Mcp.cpp uses IEditorHost.
manifest: devtools DEPS graphics scene physics procgen particles audio ui
image event filesystem -> event; cmdline DEPS filesystem ui -> filesystem.
Verified: full build green; devtools.mcp.* tests pass; broad suite shows
only the pre-existing PHYSFS-init filesystem-environment failures.
* style: apply clang-format to changed lines after dev merge
* fix(webgpu): include RenderControl/TextureSampler explicitly after Graphics.h slimming
* fix(webgpu): provide newFont/print stubs to satisfy IGraphics2D/IResourceFactory vtables
* fix(common): make ProcgenQuery.h self-sufficient (cstdint/utility for GCC)
- compute main's changes since the merge-base with dev instead of per-commit
cherry-picks, so merge/squash/rebase doc merges all work and dev-only work
is never touched;
- allow release version touchpoints (RELEASE_PATHS) in the non-doc gate, fixing
the spurious failure every sync-docs run after a non-ff release merge;
- sync by copying doc paths from main onto sync-docs/from-main and opening a
PR to dev (no direct push), matching the PR-only branch model;
- docs updated in 发布流程.md and the release-branch-model spec.
- install_sdk.cmake: export headers for exactly the modules the build enabled
(EVE_ENABLED_MODULES from cmake/module_manifest.cmake) instead of a stale
whitelist; package engine + vendored third-party licenses under
share/eve/licenses/.
- test-sdk.sh: hard-gate native C++ plugin compile (find_package(EVEngine) +
add_eve_plugin) on win32/linux/macosx; android now assembles an APK from the
SDK template (gradlew assembleDebug) instead of skipping.
- sdk-release.yml: run the android APK smoke; publish generates SHA256SUMS,
verifies every zip's share/eve/VERSION against the tag, uploads SHA256SUMS
with the SDKs, and (best-effort) attests build provenance.
- release.py: new cleanup-branches subcommand deletes merged vX.Y.Z /
promote/* / rebase/* branches (tags and main/dev are never touched);
new cleanup-branches.yml runs it weekly + on demand.
- docs: SDK packaging and branch cleanup sections in 发布流程.md and the
release-branch-model spec.
- release.py: use explicit refs/heads + refs/tags refspecs everywhere (short
refspecs are ambiguous when the release branch and tag share a name, and git
refuses to push them); rebase dev onto the branch's -dev write-back instead
of the official tag; add a real-git integration test suite.
- version single-sourcing: fix config.h.in PATCH macro bug and include -dev;
derive Android versionCode; sync required version touchpoints in start and
enforce them via check-versions in CI.
- SDK: exact version assertions in test-sdk.sh, publish-time VERSION cross-check.
- workflows: converge toolchain versions to repo vars, SHA-pin actions, add
Dependabot config.
finish no longer updates main or force-pushes dest. It opens promote/vX.X.X to main and rebase/vX.X.X to dest for a human merge, and posts the main-gate check so GITHUB_TOKEN PRs stay mergeable.
Factory-created cards, hands, decks, and zones live in the ECS table
so ecs::View and eve.view can enumerate them without changing the
Squirrel API or ordered membership.
- Updated `.gitignore` to reflect changes in documentation generation commands.
- Modified `CMakeLists.txt` to set a new version format and streamline documentation handling by adding a subdirectory for docs.
- Removed the outdated `Doxyfile` and replaced it with a new configuration in `docs/Doxyfile.in`.
- Updated README files to reference the new documentation structure and generation process.
- Deleted unused `imgui.ini` and API conventions documentation file to clean up the repository.
- Introduced a new document detailing the design of a universal quest tracker for the RPG module, covering objectives, policies, and event handling.
- Updated existing RPG framework documentation to reference the new quest tracking system.
- Created an implementation plan outlining the architecture, file structure, and initial tasks for developing the quest tracker.
CI showed GPU/window tests are ~70x slower when several share one
process (bundle/animation_sprite_spine.cpp: 373s vs 5s per-case under
Lavapipe), so the default make test/* now runs per-case (fast, process
isolated) and excludes bundle/* entries. Bundle runs remain available
via FILTER=bundle/<file>.cpp or ctest -L bundle for CPU-focused local
runs. ClassicScenes fast headless defaults (VIEW_SECONDS, PERF_FRAMES)
stay enabled.
- test/voxel.cpp: merge parameter-variant families into single cases
(pack roundtrip, single voxel mesh, mesher API, frustum, decode
planes/UV/origin, visible batches, dirty flags, remove chunk,
registry) and drop 3 face-cull cases subsumed by the six-axis test;
134 -> 90 cases, all assertions preserved.
- test/particles_attach_skin.cpp: fold followRotationOff / yzPlane /
skinnedCacheReadable assertions into their siblings; 11 -> 8 cases.
- cmake/ZeroErrDiscoverTestsImpl.cmake: register one CTest bundle/<file>
per test source (label bundle) alongside per-case entries, preferring
zeroerr --list-format=plain with a legacy-format fallback; default
make test/* runs -L bundle so the suite spawns ~115 processes instead
of ~1385.
- Makefile: fast headless defaults for ClassicScenes (VIEW_SECONDS=0.3,
PERF_FRAMES=30) and bundle-mode ctest invocation.
- sceneloader: apply the known scene-ecs merge fix (linkTarget/linkKind
-> SceneLink) from codex/fix-sceneloader-merge-ci so this branch builds.
- docs: test-suite optimization + per-file pruning log.
Introduce a comprehensive implementation plan for GPGPU backend abstraction, detailing the transition to backend-agnostic interfaces for ComputeShader, GpuBuffer, and Gpgpu. The plan outlines architecture, global constraints, file structure, and specific tasks for integrating Vulkan while maintaining API compatibility. This change aims to enhance modularity and facilitate future backend support.