DirectX 12 / PRIMARY BACKEND

From sample to renderer

Keep the explicit contracts while adding scene extraction, pass dependencies, asynchronous assets, frame arenas, profiling, and backend boundaries.

Replace one big owner with clear systems#

Sample owns everything so a beginner can follow one complete program. An engine separates responsibilities without erasing the underlying operations. PlatformWindow owns operating-system interaction. GraphicsDevice owns native allocation and command execution. AssetStore owns import and readiness. Renderer owns RenderWorld extraction and RenderGraph orchestration. Start from a known working triangle and texture target. Preserve a small reproducible scene as a validation path while refactoring. If every system changes at once, a missing frame can have too many possible causes.

Extract render-visible data#

Scene is mutable simulation state. Renderer reads an immutable RenderWorld containing world transforms, bounds, mesh/material handles, camera data, lights, and skin palette references. Extraction validates handles and substitutes persistent fallback materials for pending textures. RenderWorld keeps the simulation version that produced it. A render capture can therefore explain which pose and asset versions contributed to the frame. CPU snapshot retention and GPU allocation retention are tracked separately.

01SceneSimulation data02RenderWorldImmutable snapshot03RenderGraphPass dependencies04GraphicsDeviceDX12 / Vulkan
Ownership and dependencies in QubicEngine’s documented reference architecture.

Describe passes before recording them#

RenderGraph nodes declare read/write usages for logical resources. The compiler finds dependencies, validates reads have producers or imported inputs, determines first/last use, and builds barriers. Opaque geometry writes depth and surface properties; lighting reads them; transparency adds forward-lit surfaces; temporal/post passes produce a presentation image.

INTERACTIVE ILLUSTRATIONSIMULATION / BROWSER

A frame is a dependency graph This illustration uses canvas. The explanation below describes the same process.

Change a control to inspect the result. Values describe the simulation, not native engine benchmarks. Open full lab ↗

The laboratory omits history and shadows to keep one relationship visible. Its lifetimes are ordered pass indices, not measured GPU timestamps. Resources with non-overlapping lifetimes are only alias candidates; placement size, alignment, memory class, and barrier requirements still apply.

Reference pass declaration
cpp · REFERENCE EXCERPT
struct PassUse {
    GraphResource resource;
    AccessKind access;
};
struct RenderPass {
    std::string name;
    std::vector<PassUse> reads;
    std::vector<PassUse> writes;
    RecordFunction record;
};

GraphResource names a logical allocation or imported resource. AccessKind distinguishes attachment, shader-read, copy, and storage access. RecordFunction receives a backend command context and resolved physical views; it does not create arbitrary undeclared dependencies. The graph compiler owns scheduling and the backend owns API-specific barriers.

Add asynchronous loading and frame storage#

Decode workers produce validated CPU assets. UploadManager owns staging allocations and pending destination resources. Submission produces a FencePoint. Completion publishes the ready AssetStore version and retires staging. The render thread never waits for a PNG decoder while recording a frame. Each FrameContext owns command allocators, transient upload spans, descriptor spans, and timestamp/query ranges. Worker command recording uses independent allocators. Reset happens only after completion. Persistent mesh/texture allocations live beyond a single frame and have their own last-use records.

Introduce a second backend deliberately#

GraphicsDevice exposes the engine’s resource and submission contracts. The DX12 implementation owns heaps, descriptors, root signatures, command lists, and fences. The Vulkan implementation owns memory types, descriptor sets, pipeline layouts, command buffers, semaphores, and fences. Shared RenderWorld and asset import do not remove those responsibilities. Feature queries choose supported rendering techniques. A fallback is usable only if a Vulkan implementation and suitable device exist, and the chosen techniques can run there. The engine should report which backend and capability profile it actually selected.

Build, measure, and investigate#

Refactor one ownership boundary at a time, rebuild the native sample, and validate visible output and debug messages on Windows. Add CPU spans around extraction and recording; add GPU timestamps around passes and read them after completion. Track resource versions, states, and retirement points in diagnostics. A render graph that inserts a barrier between every operation may be correct but slow. A graph that ignores access dependencies may be fast until it corrupts data. Validate correctness first, then measure redundant transitions, pass bandwidth, and synchronization stalls. Read advanced rendering and optimization for the next layer.

Search titles and full article text.