Skip to content

Add graphics render host module#40

Draft
jayrulez wants to merge 14 commits into
Redot-Engine:masterfrom
jayrulez:graphics
Draft

Add graphics render host module#40
jayrulez wants to merge 14 commits into
Redot-Engine:masterfrom
jayrulez:graphics

Conversation

@jayrulez

@jayrulez jayrulez commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

This builds on the rhi PR and should be merged after it. It should be rebased on master once the rhi PR is merged there.

Adds the graphics module - the shared RHI render host that samples, the UI, and the renderer can all build on, instead of re-deriving swapchain/frame plumbing per sample.

  • GraphicsDevice - the shared GPU: owns the backend (validation-wrapped on request), adapter, logical device, and graphics queue, plus the CPU frame-in-flight ring index. Created once per app; hands out RenderWindows.
  • RenderWindow - a single window's presentation target: surface + swapchain + a per-window ring of command pools and fences. Created/destroyed at runtime (the basis for detachable UI windows); the main window is just the first.
  • FrameContext - the per-window, per-frame hand-off. The host owns acquire, fence sync, submit, present, and the backbuffer state transitions; the consumer records content (or uses beginBackbufferPass for the common clear+pass case).

Multi-window is uniform - there's no "main window" special case; an app renders a list of independent RenderWindows sharing one GraphicsDevice.

Three modules in one target so the core stays GPU-backend-agnostic and builds headlessly:

  • graphics - backend-agnostic core, imports only base RHI + Shell.
  • graphics.null - headless factory over the Null backend (CI/servers/tests).
  • graphics.gpu - Vulkan/DX12 factory. DX12 is compiled in only on Windows via DRACO_HAS_DX12; validation defaults on for dev builds and off under NDEBUG.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4abd02fb-7043-4c23-94cd-63491c6cf705

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jayrulez jayrulez force-pushed the graphics branch 5 times, most recently from 8774623 to 6f544d6 Compare July 11, 2026 17:02
@jayrulez jayrulez force-pushed the graphics branch 4 times, most recently from 059da8e to bd909b8 Compare July 15, 2026 12:13
jayrulez added 14 commits July 15, 2026 08:57
Introduce core.math.matrix4 (4x4 row-major, row-vector convention: identity/
translation/scale/rotation, perspective/ortho/lookAt RH, multiply, transpose,
determinant, transformPoint/Direction/Point2D, and a scale-aware tryInverse/
inverse) and core.math.quaternion (fromAxisAngle, Hamilton product, conjugate/
dot/inverse/normalize/rotateVector/slerp, rotationMatrix), with the Matrix4
inverse bodies kept in Matrix4.cpp so the interface stays light.

Rewrite Transform to position / rotation (Quaternion) / scale with an inline
toMatrix() returning Matrix4 and a lerp helper, removing the last bx dependency
from Core. Update its two callers (Renderer, RenderSample) to the new API.

Add the math helpers these types need (sin/cos/tan/acos/sqrt, scalar and vector
nearlyEqual/nearlyZero) and unit tests for Matrix4/Quaternion/Transform.

Review follow-ups: expose singular inverses through tryInverse with a Hadamard
row-norm threshold instead of a silent identity; guard a zero-length axis in
fromAxisAngle; treat antipodal quaternions as equal; make the near-equal helpers
constexpr and drop the redundant approx_eq; use the multidimensional subscript
operator on Matrix4.
Drop the bgfx rendering stack entirely: delete the Rendering and Scene modules,
the bx/bimg/bgfx submodules and their ThirdParty build tooling, and the shader
compilation (cmake/Shaders.cmake). Rewire Runtime, Engine, and the draconic
module so the engine builds on Core + Shell + SDL3 only.

RenderSample becomes a shell-only placeholder that opens a window and pumps
events until it is closed.
Introduce the rhi module: a backend-agnostic GPU render-hardware-interface, as the primary module 'rhi' with partitions for enums, texture formats, core types, forward declarations, resource/descriptor structs, resources, command recording, extensions (mesh shader / ray tracing), queue, swap chain, and the Backend/Adapter/Device factory. Interface only - concrete backends (Null, Validation, Vulkan, DX12) land as separate modules.

Add a small Status/ErrorCode facility to Core (core.status, re-exported via core.defs) for the interface's status-code returns; value-or-error paths use std::expected.

Uses this engine's conventions throughout: draco::rhi namespace, camelCase methods/functions/constants (types and enum values stay PascalCase), and std container types (std::vector/span/optional/u8string). Wired via add_modules_library(Rendering/RHI) into Runtime.
Headless no-op RHI backend implementing every rhi interface with stubs: a NullDevice that creates/destroys resources, a mappable NullBuffer backed by host memory, no-op command encoders, and a createNullBackend() entry point. Unsupported extensions (mesh shaders, ray tracing) degrade to NotSupported rather than crash. Intended for headless testing, CI, and tools. Includes a doctest suite covering adapter enumeration, device/resource creation, buffer mapping, and extension degradation. Allocations use new/delete for now (the engine's Slice-based allocator does not compose with polymorphic base-pointer frees; revisit with the GPU backends).
The validation backend (rhi.validation) wraps any RHI backend and checks API usage before forwarding: encoder state machines (recording / in-pass / finished), non-null resource arguments, pipeline-bound-before-draw, viewport/scissor set before draw, monotonic fence signaling, swap chain acquire/present balance, and per-device live-resource leak tracking. Validated wrappers unwrap to the inner objects before forwarding, so an inner backend only ever sees its own types.
The Vulkan backend (rhi.vk) implements the full RHI surface on Vulkan 1.3+ dynamic rendering: adapter enumeration with preference ordering, logical device and queue setup, all resource/pipeline/bind-group creation, command encoders (render/compute/bundle passes), swap chains with platform surfaces (Win32/X11/Wayland), transfer batches, and the mesh-shader and ray-tracing extensions. Requires the Vulkan loader (find_package(Vulkan)); the loader is a private dependency since the headers live in the module's global fragment.

Includes a platform-agnostic smoke test that initializes the backend, enumerates adapters, and creates a device. It never touches surfaces, so it runs anywhere a loader is present and skips the device step gracefully when no adapter is available.
The DX12 backend (rhi.dx12) implements the full RHI surface on D3D12: adapter enumeration via DXGI, device/queue setup, all resource/pipeline/bind-group creation, descriptor heap management (CPU staging + GPU-visible ring), command encoders (render/compute/bundle passes with copy-on-bind descriptor staging), swap chains, transfer batches, and the mesh-shader and ray-tracing extensions. Windows only: registration is guarded by if(WIN32) and links the D3D12/DXGI system libraries privately.

Includes a platform-agnostic smoke test (built only on Windows) that initializes the backend, enumerates adapters, and creates a device.

Not yet compiled — this box has no D3D12 toolchain; the port is to be validated on Windows.
- DxBackend: sortAdaptersByPreference casing to match Device.cppm
- DxRenderBundleEncoder: PascalCase delegate calls to camelCase
- Math.test: qualify std::cos to resolve overload ambiguity
The shaders module wraps DXC (IDxcCompiler3) to compile HLSL to SPIR-V or DXIL. It loads dxcompiler at runtime (LoadLibrary/dlopen) rather than linking it, so nothing hard-links the compiler. The public compile types (ShaderStage, CompileOptions, CompileResult, ShaderFlags + variant key) are self-contained: the compiler produces bytecode and has no RHI dependency — the RHI-coupled variant cache is a separate module (shaders.system, not yet ported).

Draco::DXCHeaders (ThirdParty/DXC) carries the DXC include dir plus DRACO_DXC_PATH, the absolute path the compiler falls back to for the runtime library. The prebuilt DXC runtime libraries themselves are not committed here.

Test compiles a trivial vertex shader to SPIR-V (checks the magic number) and confirms a bad shader reports an error instead of crashing — passes against the vendored DXC on Linux.
The sample framework (samples.rhi.framework) is the shared base for the RHI samples, relocated into Samples/cpp/RHI so it is scoped to RHI samples only. SampleApp brings up an SDL3 window (shell.desktop), a Vulkan or DX12 backend (validation-wrapped), device, graphics queue, and swap chain, then runs an event/resize/render loop calling the subclass's onInit/onRender/onResize/onShutdown. DepthBuffer is a recreate-on-resize depth target; ShaderHelpers compiles HLSL to a ShaderModule, picking SPIR-V or DXIL by device type and applying Vulkan binding shifts.

The framework re-exports rhi + shell so samples import one module. DX12 is compiled in only on Windows (DRACO_HAS_DX12). Individual samples are added on top of this.
Ports the 30 numbered RHI samples (Triangle through RenderBundles) plus the Smoketest, verbatim apart from the namespace (draco::) and camelCase API changes. They exercise the RHI across vertex/index buffers, textures, uniform buffers, compute, bind groups, blending, instancing, depth, mipmaps, MSAA, MRT, wireframe, samplers, blit, queries, readback, multi-queue, bindless, batch upload, mesh shaders, ray tracing, stencil, cube maps, occlusion queries, multi-draw-indirect, dynamic offsets, 3D textures, procedural RT, resolve, and render bundles.

Each sample is one Main.cpp on the shared framework; a draco_add_rhi_sample() helper wires each executable with the whole-archive SDL link so its static video/input drivers survive. Math types are qualified under draco::math (Vector, Matrix4, Quaternion, degToRad). Build only: they open an SDL3 window and need a GPU/display, so they are not run in CI.
Commit the prebuilt DXC runtime libraries (dxcompiler + dxil, for win-x64 and linux-x86_64) that the shaders module loads at runtime via DRACO_DXC_PATH. Tracking them directly keeps a fresh checkout self-contained: the samples compile HLSL at runtime and need dxcompiler present.
The shader system (shaders.system) is a compile-on-demand cache for shader variants on top of the RHI-free shaders compiler. A shader is registered by name per stage; getVariant(name, stage, flags) turns the flags into #defines, compiles the permutation via DXC, creates a GPU ShaderModule, and caches it by (nameHash, stage, flags). Compile failures are not cached, so a later request retries after a fix. invalidateShader drops and destroys a shader's variants and bumps a monotonic version the PSO cache can poll.

Variants are keyed in a std::unordered_map via a hash functor for the variant key. Needs the RHI to create modules, so it is a separate library from the base shaders module. Test compiles real SPIR-V via DXC and creates modules on the Null backend, covering flags-to-defines, caching, distinct variants, and invalidation.
The graphics module is the shared RHI render host: GraphicsDevice (backend + adapter + logical device + graphics queue + CPU frame-in-flight ring) hands out RenderWindows, each owning a surface/swapchain and a per-window ring of command pools and fences. FrameContext is the per-window, per-frame hand-off — the host owns acquire, fence sync, submit, present, and the backbuffer state transitions.

Split into three modules in one target: graphics (backend-agnostic core, imports only base RHI + Shell), graphics.null (headless factory over the Null backend), and graphics.gpu (Vulkan/DX12 factory, validation-wrapped on request). DX12 is compiled in only on Windows via DRACO_HAS_DX12; validation defaults on for dev builds and off under NDEBUG.

Value-or-error returns use std::expected; owned handles use std::unique_ptr. Headless test over the Null backend + null shell passes (5 cases, 38 assertions).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants