Roadmap — Materials, backends, and interchangeable display
Compile-time friendly rendering
Plan to evolve novolis-rendering from the current bootstrap (CpuRayTracer, RenderMesh) into a full authoring → compile → trace → present pipeline, with Raylib and Silk.NET as swappable display hosts only.
Non-negotiable boundary: scene, materials, CompiledScene, and IRayTracingBackend live in rendering. Nothing in novolis-raylib references Scene, IMaterial, or CompiledScene. Raylib only implements presentation (upload/blit pixels or a host texture handle).
Governance: library-boundaries.md.
Target architecture
┌─────────────────────────────────────────────────────────────────┐
│ novolis-rendering (authoring + compile + backends) │
│ Scene, IMaterial*, Materials.*, SceneCompiler, CompiledScene │
│ IRayTracingBackend, Cpu/Igpu/Vulkan backends, accumulation │
└────────────────────────────┬────────────────────────────────────┘
│ IRenderOutput (pixels or opaque GPU id)
▼
┌─────────────────────────────────────────────────────────────────┐
│ Novolis.Rendering.Presentation.Abstractions │
│ IFramePresenter — host-neutral “show this frame” contract │
└──────────────┬──────────────────────────────┬───────────────────┘
│ │
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ Novolis.Raylib. │ │ Novolis.Silk. │
│ Presentation │ │ Presentation │
│ (novolis-raylib repo) │ │ (novolis-silk or │
│ │ │ rendering optional pkg) │
│ Texture upload + blit │ │ GL/Vulkan blit │
│ NO Scene / Material │ │ NO Scene / Material │
└──────────────────────────┘ └──────────────────────────┘
│ │
▼ ▼
Raylib window Silk window / swapchainApp / dogfood wires: ViewPose → CameraSnapshot → backend → presenter → host loop.
Package map (end state)
| Package | Repo | Depends on | Must NOT reference |
|---|---|---|---|
| `Novolis.Rendering.Abstractions` | rendering | Math | Raylib, Simulation, Silk |
| `Novolis.Rendering.Materials` | rendering | Abstractions | GPU APIs |
| `Novolis.Rendering.Scene` | rendering | Materials, Math | GPU APIs |
| `Novolis.Rendering.Compile` | rendering | Scene, Materials | GPU APIs |
| `Novolis.Rendering.Runtime` | rendering | Compile | GPU APIs (structs only) |
| `Novolis.Rendering.Backends.Cpu` | rendering | Runtime | Raylib, Silk |
| `Novolis.Rendering.Backends.Igpu` | rendering | Runtime, ILGPU | Raylib, Silk |
| `Novolis.Rendering.Backends.Vulkan` | rendering | Runtime, Silk.NET.Vulkan | Raylib |
| `Novolis.Rendering` | rendering | meta | — |
| `Novolis.Rendering.Presentation.Abstractions` | rendering | Abstractions | Raylib, Silk |
| `Novolis.Rendering.DependencyInjection` | rendering | Backends.*, Presentation.Abstractions | Raylib, Silk |
| `Novolis.Rendering.Presentation.Raylib` | **rendering** | Presentation.Abstractions, Raylib.Runtime | Scene, Materials |
| `Novolis.Silk.Presentation` | silk or rendering | Presentation.Abstractions, Silk | Scene, Materials |
Migrate / rename today:
| Current | Target |
|---|---|
| `Novolis.Rendering.Raytrace` / `CpuRayTracer` | `Novolis.Rendering.Backends.Cpu` |
| `IRayTracer` + `RenderScene` | superseded by `IRayTracingBackend` + `CompiledScene` |
| `RenderMesh` / per-mesh color | `Scene` authoring + `GpuTriangle` + material indices |
Keep IRayTracer as obsolete shim until Phase 2 completes.
Two worlds (authoring vs runtime)
Authoring (human-friendly) Runtime (flat, blittable)
───────────────────────── ─────────────────────────
Scene CompiledScene
IMaterial records GpuMaterial[]
MeshInstance + material ref GpuTriangle[]
Light definitions GpuLight[]
BvhNode[]
SceneCompiler.Compile()Rules
- Backends consume only
CompiledScene+CameraSnapshot. - Authoring types never appear in hot paths or GPU kernels.
GpuMaterialis fixed-size (Vector4× 3 +MaterialModel); no interfaces in runtime arrays.- Materials describe light transport, not gameplay tags, physics, or chemistry.
Phase 0 — Foundation (done / stabilize)
Goal: Repo builds, tests, CI, local pack; boundary documented.
- [x]
novolis-renderingrepo,Novolis.Rendering.Abstractions, bootstrapCpuRayTracer - [x] Governance entry for rendering ⊥ raylib
- [ ] CI: ensure
Novolis.Math.Geometryavailable on CI feed (pack math in workflow or publish pin) - [ ] Add
docs/materials-and-backends.md(this spec, trimmed) as normative API reference
Exit: dotnet build + TUnit green on main.
Phase 1 — Presentation layer (Raylib + Silk interchangeable)
Goal: Same traced pixels on screen via either host; zero scene leakage into raylib.
1a — `Novolis.Rendering.Presentation.Abstractions`
/// <summary>Displays a finished frame. Implementations live in host repos.</summary>
public interface IFramePresenter
{
void PresentCpuFrame(ReadOnlySpan<Rgba32> pixels, int width, int height);
}
/// <summary>Optional: backends that render directly to a GPU resource.</summary>
public interface IGpuFramePresenter
{
void PresentGpuFrame(IRenderGpuSurface surface);
}
public interface IRenderGpuSurface
{
IntPtr NativeHandle { get; } // interpreted only by the matching presenter
int Width { get; }
int Height { get; }
}IRenderOutputon backend:TryGetCpuPixels()ORTryGetGpuSurface()— CPU path always available for tests.
1b — `Novolis.Rendering.Presentation.Raylib` (in **novolis-rendering**)
- References
Novolis.Rendering.Presentation.Abstractions+Novolis.Raylib.Runtimeonly. RaylibCpuFramePresenter—UpdateTexture/DrawTexturefromRgba32[].- `novolis-raylib` stays pure — no
Novolis.Rendering.*references. - Extension:
RayGameContext.PresentFrame(IFramePresenter, ImageBuffer). - Forbidden in this package:
using Novolis.Rendering.Scene,Materials,Compile, anyCompiledScene.
1c — `Novolis.Silk.Presentation`
- Same contract as 1b; Silk.NET OpenGL or Vulkan swapchain blit.
- Repo choice:
novolis-silk(preferred long-term) orNovolis.Rendering.Presentation.Silkuntil silk host exists.
1d — Dogfood sample `RaytraceHello`
- App references: Rendering (backend + DI) + Raylib.Game + Rendering.Presentation.Raylib.
- Loop: compile stub scene → render →
PresentCpuFrame→ HUD via existing Raylib.
Exit: Toggle presenter implementation in DI without changing scene code; grep confirms no Scene in novolis-raylib/src.
Phase 2 — Materials (authoring) + compilation
Goal: Spec material models + Materials.* presets; compile to GpuMaterial.
2a — `Novolis.Rendering.Materials`
public interface IMaterial { }
public sealed record StandardMaterial : IMaterial { /* spec fields */ }
public sealed record GlassMaterial : IMaterial { /* spec */ }
public sealed record SkinMaterial : IMaterial { /* spec */ }
public sealed record EmissiveMaterial : IMaterial { /* spec */ }
public static class Materials
{
public static StandardMaterial Standard(...) => ...;
public static StandardMaterial Metal(...) => ...;
public static GlassMaterial Glass(...) => ...;
// ...
}- No inheritance between material types (only
IMaterial). - Unit tests: preset → expected
StandardMaterialfield values.
2b — `MaterialModel` + `GpuMaterial` + `MaterialCompiler`
public enum MaterialModel { Standard, Glass, Skin, Emissive }
public readonly record struct GpuMaterial
{
public MaterialModel Model;
public Vector4 A, B, C;
}MaterialCompiler.Compile(IMaterial)→GpuMaterialwith documented packing per model.- Golden tests: known material → fixed
A/B/Cfloats.
2c — Textures (deferred)
MaterialTextures+TextureHandleas opaque int in rendering only.- No file I/O in rendering; apps load via Raylib/Silk and pass handles at compile time (Phase 6).
Exit: All four material types compile; no GPU types in Materials package.
Phase 3 — Scene authoring + `CompiledScene`
Goal: Replace RenderMesh / RenderScene with spec-aligned authoring and runtime.
3a — `Novolis.Rendering.Scene`
public sealed class Scene
{
public IList<MeshInstance> Meshes { get; }
public IList<LightDefinition> Lights { get; }
}
public sealed record MeshInstance(
ReadOnlyMemory<Vector3> Vertices,
ReadOnlyMemory<int> Indices,
IMaterial Material,
Matrix4x4 Transform);- Builders:
SceneBuilder.AddBox,AddGroundPlane, etc. - Lights: directional, point (minimal set for Phase 3).
3b — `Novolis.Rendering.Compile`
public static class SceneCompiler
{
public static CompiledScene Compile(Scene scene);
}
public sealed record CompiledScene
{
public required ImmutableArray<GpuTriangle> Triangles { get; init; }
public required ImmutableArray<GpuMaterial> Materials { get; init; }
public required ImmutableArray<GpuLight> Lights { get; init; }
public required ImmutableArray<BvhNode> BvhNodes { get; init; }
}GpuTriangle(Vector4 A, B, C, int MaterialIndex)— world-space after transform.- BVH: port SAH builder from
Novolis.Physics.Collision.Simplelogic into Math or Rendering.Compile (prefer extract toNovolis.Math.Geometryper governance).
3c — Deprecate bootstrap types
- Obsolete
RenderMesh,RenderScene,IRayTracer; adapter maps old API → compile → backend for one release.
Exit: SceneCompiler tests: triangle count, material indices, BVH hit consistency vs brute force.
Phase 4 — `IRayTracingBackend` + CPU path tracer v2
Goal: Backend interface from spec; progressive accumulation; physically clearer shading.
4a — Abstractions
public readonly record struct CameraSnapshot(
Vector3 Position, Vector3 Forward, Vector3 Right, Vector3 Up,
float VerticalFovRadians, float AspectRatio);
public interface IRayTracingBackend
{
ValueTask ResizeAsync(int width, int height, CancellationToken ct = default);
ValueTask UploadSceneAsync(CompiledScene scene, CancellationToken ct = default);
ValueTask RenderAsync(CameraSnapshot camera, int sampleIndex, CancellationToken ct = default);
IRenderOutput Output { get; }
int SampleCount { get; }
void ResetAccumulation();
}4b — `CpuRayTracingBackend`
Parallel.Forover pixels;ArrayPoolfor ray stacks.- Per material model switch in shading (start with Standard + Emissive; Glass/Skin simplified then refined).
- Progressive:
accumulation += sample; display = accumulation / sampleCount. - Deterministic mode for golden PNG tests (fixed seed, single thread).
4c — Shading milestones
| Milestone | Models | Technique |
|---|---|---|
| M1 | Standard | Lambert + ambient + one directional |
| M2 | Standard | GGX microfacet (metallic/roughness) |
| M3 | Emissive | Mesh lights |
| M4 | Glass | Refraction (single bounce) |
| M5 | Skin | Approximate SSS (burley-style or diffusion) |
Exit: Dogfood demo with progressive refine; golden tests per milestone.
Phase 5 — Dependency injection + backend selection
Goal: services.AddRayTracing().UseCpuBackend() without pulling Raylib.
// Novolis.Rendering.DependencyInjection
public static IServiceCollection AddRayTracing(this IServiceCollection services)
{
services.AddSingleton<SceneCompiler>();
return services;
}
public static IServiceCollection UseCpuBackend(this IServiceCollection services)
{
services.AddSingleton<IRayTracingBackend, CpuRayTracingBackend>();
return services;
}- Separate extension packages or conditions:
UseIlgpuBackend,UseVulkanBackend(optional NuGet deps). - Presentation registered in app or host repo:
services.AddRayTracing().UseCpuBackend();
services.AddSingleton<IFramePresenter, RaylibCpuFramePresenter>();Exit: RandoriFight-scale app could swap IFramePresenter registration only.
Phase 6 — GPU backends (ILGPU, then Silk Vulkan)
Goal: Same CompiledScene uploaded to GPU buffers; same IRayTracingBackend contract.
6a — `Novolis.Rendering.Backends.Igpu`
- Flat buffers mirror
GpuTriangle,GpuMaterial, BVH nodes. - C# kernels for trace + shade (parity tests vs CPU on small scenes).
6b — `Novolis.Rendering.Backends.Vulkan` (Silk.NET)
- Compute pipeline; no scene types in Silk package.
IRenderGpuSurfaceimplemented by backend;SilkPresentationblits handle.
6c — OpenGL compute (optional)
- Same as Vulkan but GL 4.3 path for broader hardware.
Exit: Backend parity test suite: same CompiledScene + camera → images within tolerance (PSNR/SSIM threshold).
Phase 7 — Quality, tooling, governance
- Analyzers: ban
Novolis.Rendering.Sceneimports innovolis-raylib(Roslyn analyzer innovolis-analyzersor repo-local). - Golden harness: PNG SHA256 over
IRenderOutputCPU path (no native window). - Registry: publish packages to
novolis-registry. - Governance: extend
library-boundaries.mdwith material/scene placement table. - BVH in Math: extract shared BVH from Physics →
Novolis.Math.Geometry; Physics and Rendering.Compile both use it.
Critical “do not” checklist
| Do not | Why |
|---|---|
| Add `Scene` / `IMaterial` to `Novolis.Raylib.*` | Scene is rendering domain; raylib is display/input |
| Let `IRaylibFrameRenderer` accept `CompiledScene` | Keeps frame hook host-agnostic |
| Put `Camera3D` in rendering | GPU type; use `CameraSnapshot` |
| Inherit `SkinMaterial : StandardMaterial` | Different transport models |
| Expose descriptor sets / pipelines in authoring APIs | Breaks backend swap |
| Reference Simulation from Rendering | Apps glue `ViewPose` → `CameraSnapshot` |
Suggested implementation order (sprints)
Sprint 1 Phase 1 Presentation abstractions + Raylib.Presentation + RaytraceHello
Sprint 2 Phase 2 Materials + MaterialCompiler + tests
Sprint 3 Phase 3 Scene + SceneCompiler + BVH
Sprint 4 Phase 4 IRayTracingBackend + CPU M1–M2 + progressive
Sprint 5 Phase 5 DI + dogfood integration (Artillery or new sample)
Sprint 6 Phase 4 CPU M3–M5 (glass, skin, emissive lights)
Sprint 7 Phase 1c Silk.Presentation (swap demo)
Sprint 8 Phase 6 ILGPU backend
Sprint 9 Phase 6 Vulkan/Silk compute
Sprint 10 Phase 7 Analyzers, registry, Math BVH extractionApp integration pattern (stable)
// 1. Author (rendering only)
var scene = new SceneBuilder()
.AddGround(Materials.Standard(...))
.AddSphere(Materials.Metal(Colors.Silver, 0.08f))
.Build();
var compiled = sceneCompiler.Compile(scene);
// 2. Trace (rendering only)
await backend.UploadSceneAsync(compiled);
await backend.RenderAsync(cameraSnapshot, sampleIndex);
// 3. Present (host package only — Raylib OR Silk)
presenter.PresentCpuFrame(backend.Output.GetCpuPixels());
// 4. Simulation cameras (optional, app layer)
var camera = CameraSnapshot.FromObserver(viewPose, aspect);Open decisions (resolve before Phase 1b)
- Silk repo name:
novolis-silkvs presentation-only package under rendering. - `TextureHandle`:
intid + registry in rendering vs defer until Phase 6c. - BVH ownership: move to Math in Phase 3 vs duplicate in Compile temporarily.
- Package version alignment: 0.0.1.1 org versioning vs
-localmonorepo feeds (already used elsewhere).
Related
- design.md — current bootstrap scope
- getting-started.md — build instructions
- novolis-rendering
- novolis-raylib
- Silk.NET