Repository navigation
Architecture
NWBLot is not arranged as a single monolithic application. It is organized around reusable engine infrastructure, implementation-specific engine systems, a deterministic asset pipeline, a loader/runtime shell, and project code. The current project is the Testbed, but the loader/project boundary is intentionally small enough for other projects to use the same runtime path.
Major source roots:
-
global/: platform-independent utilities, containers, strings, smart pointers, math (including the NEON/SSE-accelerated f64 triangle-area path inglobal/simd/collision_triangle.h), filesystem helpers, text helpers (includingIsAsciiIdentifierCharinglobal/text_utils.handHasEnvironmentValue/EnvironmentVariableEqualsinglobal/environment.h), blocking read/write helpers, compile/platform macros, synchronization primitives, process helpers, atomics, shared texture-payload layouts (global/texture_payload.h, the single source of truth also consumed byimpl/assets_texture/andutilities/tex_conv), and low-level algorithms.global/is the neutral global-unit domain, not the owner of theNWBnamespace wrapper: rootengine_namespace.his the sole owner of theNWB_BEGIN/NWB_ENDmacro definitions. Domain umbrellas such ascore/global.h,impl/global.h, andlogger/global.hinclude that header and define their own domain wrappers. Applications own separate namespace headers, such asCoolStuff/Testbed/namespace.h, without directly including the engine namespace header. -
core/: reusable engine services, including allocation, application/log helpers, crash capture, assets, filesystem volumes, frame/window loop, input, graphics, CPU and GPU task scheduling, ECS, mesh utilities, metascript parsing, performance capture, and telemetry. -
impl/: concrete engine implementations built on the core service layer, including authored/cooked asset types, asset cook modules, ECS scene/content components, renderer systems, mesh/skinning/model systems, CSG integration, and the custom UI stack (CPU paint/layout/text toolkit underimpl/ecs_ui/toolkitplus theimpl/ecs_uiECS adapter). The renderer implementation is split by domain underimpl/ecs_render:kernel,shared,shader,mesh,material,deferred,csg,avboit,optics,raytrace, andreflection; the model-renderer adapter is an explicit neighboring module atimpl/ecs_model_renderer. -
pipeline/: the staged asset tools that scan authored asset roots, build standalone cooked artifacts, and publish the runtimegraphicsvolume. The three executables aredependency_computer,asset_builder, andasset_gatherer;pipeline/launch.pyis the orchestrator discovered as thepipelinelauncher command. -
loader/: the runtime executable shell. It sets up logging, crash capture, frame/graphics/input, mounts cooked resources throughIFilesystem, registers assets, and runs project callbacks. -
CoolStuff/Testbed/: the current project using the engine. It builds a startup scene with a skinned character, ground plane, camera, lights, and custom UI controls. -
tests/: focused GTest suites and smoke executables for core systems, rendering behavior, CSG, transparency, scene components, crash packaging, telemetry, mesh processing, and Testbed/window capture. -
utilities/: standalone helper tools such asnwb_fbx_to_nwbandtex_conv, plus Python authoring workflows such asui_skin. Each runnable utility owns its leaflaunch.py, discovered through the utilities category launcher. -
launcher/: the rootpython -m launcherpackage for automatic configure/build workflows, project and utility dispatch, and executable launches, with shared Windows process helpers underlauncher/repository_windows_process.py. Native projects and utilities support--build-only;build <targets...>also builds libraries and aggregate targets. -
logger/: the logging client/server system (and standalone logger) used by the loader, tools, and tests. -
configuration/: toolchain and build-configuration support. -
3rd_parties/: flat, offline third-party packages used by the engine; see Third-Party Packages for vendoring metadata and CMake wiring rules.
global/ and core/ are separate top-level architectural domains. Low-level, engine-independent facilities belong in global/, while reusable engine services belong in core/.
global is the shared substrate. It provides math, containers, string/storage primitives, filesystem helpers, process helpers, synchronization, compiler/platform macros, and shared texture-payload layouts (global/texture_payload.h) consumed by both impl/assets_texture/ and utilities/tex_conv. NWBLot-owned utility, configuration, diagnostic, namespace, and shader feature macro APIs use NWB_, including NWB_TEXT, NWB_ASSERT, NWB_DEBUG, engine namespace wrappers, logging, and graphics selection. Application-owned macro APIs use their own prefix, such as TESTBED_. External/compiler interoperability spellings and existing g_* shader resource-view aliases retain their current contracts. The defining header and directory identify each macro's domain. Global utility types and functions retain their existing scopes; the common prefix does not move them into the engine namespace. Root engine_namespace.h alone defines NWB_BEGIN/NWB_END. Each domain reaches those macros through its own umbrella header, including core/global.h, rather than redefining them in global/.
CMake emits NWB_DEBUG, NWB_OPTIMIZE, and NWB_FINAL directly for dbg, opt, and fin. Project-owned C++ abstract interface definitions and forward declarations use the canonical lowercase interface annotation. global/compile.h expands it to struct under #ifndef interface, preserving an existing SDK definition. This is an intentional exception to the NWB_ macro-prefix rule. Local push/undef/pop boundaries suspend the annotation around external Wayland headers that use interface as an ordinary identifier, then immediately restore it. Existing access, inheritance, destructor, virtual, lifetime, and exception contracts remain explicit. Ordinary aggregates and concrete implementations retain their struct or class declarations.
The core service layer under core/ owns reusable engine infrastructure. It does not own production scene content. Its systems are content-agnostic: assets, ECS mechanics, frame ownership, graphics abstractions, CPU and GPU task scheduling, filesystem volumes, logging, crash handling, telemetry, and common runtime utilities.
impl owns concrete engine behavior. Scene components, renderer-facing ECS systems, material/shader/mesh/model assets, CSG support, skinning, and UI integration live there. This keeps the core service layer generic while still letting the engine ship a complete rendering/content stack.
pipeline is the cook workflow. It is not part of the runtime process; the loader only consumes published volumes.
loader is the runtime shell. Project code supplies callbacks and creates an ECS world, while the loader owns the common startup/shutdown path.
Testbed is an engine client. It links the loader, asset modules, scene/mesh/model/skinning/render/UI ECS modules, and authored project assets. The Testbed does not directly parse authored assets at runtime; it asks the loader-provided AssetManager for cooked typed assets.
Global Expected<T, E>, Unexpected<E> and MakeUnexpected(error) from global/expected.h define fallible producer results. The default error is empty Failure; meaningful domain enums, ErrorCode, diagnostic aggregates and guaranteed borrowed error text remain explicit. Return answers, owned candidates and coherent reports directly, and check admission before value/error access. Successful false, zero, empty and documented no-work values remain valid. Preserve supplied arenas, borrowed lifetimes, publication/partial-progress semantics, complete noexcept contracts and terminal exception unwinding. Genuine buffer reuse, lifecycle mutation and required SDK/callback signatures remain in/out contracts; fallible mutators can return Expected<void, E>. See Diagnostics and Invariants and the source result guide.
Repeated scratch-arena results preserve reclamation order: destroy the prior owned result before producing its replacement when it is no longer needed. Clearing a container retains its storage, and replacing that live allocation can retain non-LIFO scratch memory. Production operations that need capacity reuse keep that state explicit.
| Owner | Header and namespace |
|---|---|
| Neutral utilities |
global/; utility symbols retain their existing global or utility-detail scopes, and owned macros use NWB_. |
| Engine namespace | Root engine_namespace.h defines only NWB_BEGIN/NWB_END for namespace NWB. |
| Engine domain | Its umbrella, such as core/global.h or impl/global.h, includes the engine namespace header and defines its domain wrappers. |
| Application | A local header, such as CoolStuff/Testbed/namespace.h, defines TESTBED_BEGIN/TESTBED_END for namespace Testbed independently of the engine namespace header. |
| Loader integration |
loader/project_entry.h declares the required engine/project entry functions; application adapters implement those functions in NWB and delegate to the application namespace. |
Applications include the engine API headers they use and may receive engine_namespace.h transitively through those APIs. They do not include it directly to declare their own classes or helpers. Testbed classes, UI galleries, and implementation helpers live in Testbed.
Existing application-facing features under NWB::Impl, including the UI toolkit and renderer systems, remain engine contracts that projects may use. The namespace split does not turn the entire impl/ tree into a private layer. Private/detail headers remain with their owners, and native graphics-provider headers remain behind backend_selection. The current target include roots still expose the repository root; this migration does not introduce an exported-header architecture or claim filesystem-level isolation.
Important ownership boundaries:
-
globalshould not depend on engine systems. -
coreshould stay content-agnostic. -
implmay depend on the core service layer and owns engine-provided content systems. - projects depend on the loader/project API and concrete engine modules they choose to use.
- shader-language compilation stays in cook targets.
- runtime graphics consumes cooked shader bytecode and shader archive records.
- project-authored assets live under project trees such as
CoolStuff/Testbed/assets. - engine-owned renderer shaders and shared CPU/GPU ABI assets live under
impl/assets. - material interfaces, surfaces, and BXDF policy are project-owned and live under project asset roots.
global/ contains the low-level substrate used across the repository:
- scalar and SIMD math (
global/simd/): four-laneSIMDVectorSingle/SIMDVectorDoublevectors withSIMDMatrix/SIMDMatrixDoublerow-stored matrices, alignedFloat#/Double#storage, and boundary-onlyLoad/Storeconversion - row-stored matrix conventions
- fixed/vector/hash containers
- string and compact string types
- shared text helpers such as trimming, prefix checks, UTF-8 decoding, BOM stripping, and JSON text escaping
- filesystem paths and archive helpers
- platform blocking I/O helpers for complete file-descriptor or handle reads/writes
- process and shared-library helpers
- platform/compiler macros
- bit operations and representation-preserving
BitCastinglobal/bit.h - smart pointers and refcount helpers
- synchronization and atomics
- generic arena base classes, arena object helpers, and allocation accounting in
global/arena_base.h,global/arena_object.h, andglobal/arena_memory.h; concrete arena implementations remain undercore/alloc
The public math API lives at global scope. SIMDMatrix stores row vectors in v[0..3] (and SIMDMatrixDouble the binary64 counterpart), translation lives in the fourth column, and vector transforms evaluate result = M * v. Scene transforms use decomposed position, quaternion rotation, and scale in ECS components, while asset/model transforms use explicit matrix payloads where that better matches authored data.
Generic arena foundations are intentionally part of global, so low-level code can use their accounting, object construction, and container-allocator propagation without taking a dependency on the higher-level core/alloc domain. ArenaAllocator propagates on container move-assignment and swap so storage keeps the arena that owns it. core/alloc owns the concrete global, persistent, and scratch allocator services plus heap backing. Scratch LIFO reclaim spans chunk boundaries: emptying the active chunk exposes the previous live chunk and caches the empty one for reuse. CPU and GPU execution services live in core/task, not in core/alloc.
Project code uses focused wrappers from global/ instead of introducing direct standard-library facilities throughout engine modules. For example, call BitCast<To>(value) from global/bit.h; the wrapper is the single project boundary around std::bit_cast.
The asset system is typed. Asset classes derive from TypedAsset<T>, expose a stable asset type name, and load binary payloads through registered codecs.
Runtime loading goes through:
-
AssetRegistry: maps asset types to codecs. -
AssetManager: loads typed assets synchronously or asynchronously. -
IAssetBinarySource: abstracts where cooked binary data comes from. -
AssetRef<T>: stores typed virtual-path references between assets/components.
The loader's runtime binary source is a mounted IFilesystem, so assets are loaded from cooked volume records rather than directly from authored source files. The default implementation is VolumeFileSystem. Projects may supply a FilesystemFactory from ConfigureProjectRuntime before device initialization; an empty factory selects the volume backend for every mount. See Filesystem.
More detail:
-
TypedAsset<T>binds a C++ type to an asset type name such asmaterial,mesh,skin,skeleton, ormodel. Shader assets use sharedinterface IShaderstorage andTypedShader<T>with final stage types such asComputeShader/compute_shaderandPixelShader/pixel_shader; each type supplies a compile-time physical stage and its registered codec. -
AssetCodec<T>owns binary deserialization for one typed asset class. - cook builds can also use codecs for serialization.
- Asset modules register runtime codecs through
AssetCodecAutoRegistrar. The asset builder drainsCookEntryAutoRegistrarentries intoCookEntryRegistryand graphics-specific cook preparation throughAssetVolumePrepareAutoRegistrar. The commonCreateAssetCodec<T>helper constructs the standard codec factory product, while collection deduplicates factories before registration. - At startup the loader drains the collected codec factories into
AssetRegistry. Cook registration lives in the builder's cook-entry and volume-prepare registries. -
AssetManager::loadSyncreads bytes from anIAssetBinarySource, then asksAssetRegistryto deserialize by asset type. - async loading is represented by request IDs and
AssetLoadResult, with optional external async executor support.
Shared cook infrastructure also lives here: core/assets/bunch expands generic asset bunches, while core/assets/volume owns the staged volume writer, object cache, and pack-manifest support. Asset-specific parsing and serialization remain in implementation modules.
The filesystem layer separates interface, factory, and volume-backend responsibilities. filesystem.h defines IFilesystem and resource-free FileCursor helpers. factory.h owns FilesystemFactory and CreateFilesystem, including default-backend selection. volume_types.h carries shared mount descriptions; volume_file_system.h is the default segmented backend; volume_build.h handles whole-map batch builds; and volume_staging.h owns stage-directory cleanup and transactional segment promotion.
Runtime assets are stored in cooked volume segments. The loader mounts the graphics volume in res through CreateFilesystem as RuntimeReadOnly. Pipeline-cache namespaces use the same factory with RuntimeReadOnly or RuntimeReadWrite.
Volume publishing is staged. The asset gatherer builds a volume in an empty staging directory through VolumeFileSystem with CookWrite usage and promotes the finished segments into the output directory. Existing segments can be backed up and restored if promotion fails. Pack-manifest object-cache payloads are written through that same staged volume path.
Metascript is the parser/lexer layer used for authored .nwb metadata. Cookers parse source metadata through this layer and validate asset-specific schemas before producing runtime binaries.
The ECS owns entity lifetime, component pools, system storage, queries, message buffers, and ticking. World is the ownership source of truth. Entity is the component operation facade.
Core ECS is content-agnostic. Scene/render/model/skinning components live under impl/, not in core/ecs.
The world borrows the frame-owned CpuTaskScheduler and owns a CpuTaskScope for system updates and parallelEach work. Systems declare component access, and the world handles component pool creation, entity component lists, and message buffers. See CPU Task Scheduler.
CPU and GPU execution are peer services with parallel module layouts:
| Domain | Public entry | Target | Work |
|---|---|---|---|
| CPU | core/task/cpu/scheduler.h |
nwb_cpu_task |
Callables, CpuTaskScope, CPU completion handles |
| GPU | core/task/gpu/scheduler.h |
nwb_gpu_task |
Task graphs, compiled plans, GPU submission tokens |
Frame::cpuTasks() and Frame::gpuTasks() expose the same instances borrowed by GraphicsRuntime and ProjectRuntimeContext. GPU submit() coordinates recording and submission; wait() joins device work. CPU and GPU completion remain distinct.
Dependencies run in one direction:
nwb_graphics (runtime) -> nwb_gpu_task -> nwb_graphics_backend -> nwb_cpu_task -> nwb_alloc
Backend code cannot include GPU graph or graphics-runtime headers, GPU tasks cannot include the graphics runtime, and CPU tasks cannot include graphics. Tests mirror the layout under tests/unit/task/cpu and tests/unit/task/gpu; graphics tests using selected public contracts remain under tests/unit/graphics.
The neutral nwb_shader_bytecode target owns core/graphics/spirv_entry_point.h and .cpp and declares a public nwb_common dependency, alongside the standard internal target defaults. Shader asset runtime/cook targets use it for admission, and nwb_graphics_backend uses the same parser for module validation. Payload encoding, concrete types and codecs remain in impl/assets_shader; provider-specific headers remain behind graphics selection.
Frame owns platform setup, window lifecycle, graphics initialization, input dispatch, clipboard and native text-input services, project update callbacks, render pass execution, timing, arenas, the shared CPU and GPU task schedulers, telemetry, and frame graph capture. Normal teardown joins outstanding CPU work; terminal exception unwind drains the CPU scheduler callback-free so the application-entry exception remains primary.
Project code requests shutdown through ProjectRuntimeContext::requestQuit, which maps to Frame::requestQuit.
The graphics layer selects one concrete provider at compile time. The NWB_GRAPHICS_BACKEND CMake cache setting defaults to Vulkan, currently the only implemented provider; selecting Metal fails configuration until that provider is implemented. The Vulkan target publishes NWB_GRAPHICS_BACKEND_VULKAN=1 and NWB_GRAPHICS_BACKEND_METAL=0, and core/graphics/backend_selection/configuration.h rejects conflicting or unsupported flags.
Neutral contracts live in core/graphics/rhi/. Graphics consumers and tests use those contracts or narrow selected headers under core/graphics/backend_selection/; backend_selection/backend.h selects public device and resource definitions, while backend_selection.h selects the full context. Vulkan native types and operations stay inside core/graphics/vulkan/, reached through the selection boundary. Selection preserves direct concrete calls, ownership, and layout without adding runtime dispatch, per-resource storage, or allocations. All tests, fixtures, and test-only helpers belong under tests/. Graphics tests run after normal backend selection and exercise existing public contracts. Vulkan-, Metal-, and other provider-specific tests, native SDK assertions, private provider fixtures, direct provider replay benchmarks, and production test hooks are prohibited. Harnesses may configure platform loaders and recognize native validation diagnostics to run the selected backend and reject runtime warnings.
The backend contract covers:
- device and adapter enumeration
- swapchain and backbuffer handling
- buffers, textures, heaps, and staging resources
- shader modules and shader libraries
- graphics, compute, meshlet, and ray tracing pipelines
- descriptor-buffer-compatible layouts, heap-backed resource tables, and a live global descriptor heap
- physical Graphics/Compute/Transfer queues, native command recording and accepted submissions, completion tokens, and owner-aware resource-state handoffs
- barriers and resource state tracking
- framebuffers and render passes
- acceleration structures and shader binding tables
- GPU timing
- GPU crash diagnostics hooks
- feature queries
Feature queries expose the active backend capabilities used by the renderer, including meshlets, ray query, ray tracing acceleration structures, ray tracing pipelines, variable rate shading, shader specializations, wave/subgroup lane counts, cooperative vector support, and advanced ray tracing extensions. queryFeatureSupport(feature) is a direct bool predicate with a const noexcept contract, with no feature-info output parameter. Device::getWaveLaneCounts() returns an infallible WaveLaneCountRange containing minimum and maximum lane counts. The Vulkan backend obtains them from core subgroup properties. GraphicsRuntime::queryWaveLaneCount() consumes the range, using the maximum when present and its existing conservative 64-lane numerical default otherwise.
For target and GPU policy, see Platform and GPU Support. For loader details, see Runtime Boot. For project callbacks and world creation, see Project API.
The device owns a live global descriptor heap addressed by an opaque GpuDescriptorHandle. VK_EXT_descriptor_buffer is required: its manager owns the mapped resource and sampler segments used by every renderer pipeline, including generation-safe TLAS blocks. Descriptor indexing remains the required shader feature floor for non-uniform heap indexing, but active code has no classic descriptor-set or alternate heap transport. Pipeline-local layouts carry push constants only; all resources are selected through heap slots. The heap supplies traversal geometry, raster mesh/frame data, scene and light buffers, renderer targets, material-authored cooked texture assets and built-in resources, and UI bindings. Its descriptor segments declare Graphics/AsyncCompute sharing and become Vulkan-concurrent only when those effective lanes use distinct queue families. See Bindless Descriptor Heap.
GraphicsRuntime owns device creation, resources, render passes, and presentation. It borrows the frame-owned CPU and GPU schedulers, attaches the GPU scheduler after device creation, joins GPU work before teardown, and detaches before device destruction. The renderer declares GPU work through GpuTaskGraph; GpuTaskScheduler records and submits compiled packets. Compiler access observes a coherent declaration revision, and the first recording claim freezes that declaration until the attempt is terminal. The compiler chooses compatible physical queues, forms native packets, and emits packet-boundary state/ownership transitions. Exact command-list recording capabilities publish immutable Recorded lists only after native close and retention; diagnostic reads, submission, and revocation serialize through the publication state. Accepted submission tokens establish execution: same-queue dependencies use queue order, while distinct physical queues wait on the producer timeline. Concurrent resources retain their sharing contract; exclusive cross-family resources receive paired release/acquire barriers. Buffer uses may declare byte ranges; disjoint intervals do not create resource hazards. The submission transaction commits irreversible accepted work before observers can propagate an exception and terminalizes unaccepted work callback-free during unwind. CommandListResourceStateHandoff remains the current state-seeding and ownership-transfer contract, not the renderer's scheduling source of truth. See GPU Task Graph, CPU Task Scheduler, and Renderer Frame Flow.
The engine uses explicit arenas and engine-owned allocators rather than broad process-global allocation. Important runtime ownership areas include:
- graphics object arena
- project object arena
- persistent and scratch arenas
- asset arena
- crash reporting arena
- domain-specific arena names for diagnostics, including
core/task/cpu/scheduler
core/alloc provides GlobalArena, PersistentArena, ScratchArena, heap backing, and standalone allocation runtime support. PersistentArena is a native fixed-capacity block pool for reusable metadata whose size is known up front. Choose an arena according to the allocation lifetime and reuse pattern: persistent storage owns reusable domain state, while scratch storage holds temporary work whose consumers finish before reclamation.
Frame owns one CpuTaskScheduler and one GpuTaskScheduler. Graphics, ECS, the loader, and tools borrow those services through scopes. asset_builder and nwb_fbx_to_nwb each construct one process CPU scheduler at entry. Unexpected native-worker exceptions terminate at the native thread; the CPU scheduler does not capture or defer them. Self-waits on a task, ancestor, or required dependent work are rejected; callback-free drain() is noexcept teardown. Production tools use InvokeTerminalEntry from global/terminal_entry.h so unexpected exceptions reach the application-entry boundary.
The main runtime data direction is:
- Cooked volume records provide binary payloads.
-
AssetManagerloads typed assets. - ECS components store typed asset references and runtime state.
- ECS systems expand content into renderer-ready caches and draw state.
-
RendererSystemprepares frame targets, material pipelines, CSG state, shadow state, caustic state, transparent state, and composite work. -
core/graphicsexecutes command lists through the active backend.
This direction is deliberate. Runtime code should not reach backward into authored source files for shader compilation, material parsing, or asset schema validation.
- Architecture
- Engine Concepts
- Asset Flow
- Frame Life Cycle
- Runtime Boot
- Project API
- Runtime and ECS
- CPU Task Scheduler
- Asset Pipeline
- Texture Conversion
- FBX to NWB Conversion
- Asset Types
- Cooked Volumes
- Filesystem
- Shader and Material System
- Mesh Model and Skinning