This guide covers coding conventions for the VisRTX project. Mechanical formatting
is handled by .clang-format; this document covers everything else.
See tsd/STYLEGUIDE.md for TSD-specific addenda.
- Canonical formatter:
clang-formatwith the project's.clang-formatfile. Run it before committing:clang-format -i <file>
- Line length: 80–100 characters. Longer lines are acceptable when breaking would harm readability (e.g., long string literals, complex template signatures).
- Brace style: opening brace on the same line (K&R style) for all constructs —
functions, classes,
if/else, loops. clang-formatoverrides: use// clang-format off/// clang-format onsparingly — only for data tables, enum lists, or structured blocks where column alignment would otherwise be destroyed.
| Entity | Style | Example |
|---|---|---|
| Classes / structs | PascalCase | ObjectPool, RenderIndex |
| Functions / methods | camelCase | setParameter(), valid() |
| Private member variables | m_ + snake_case |
m_storage, m_freeIndices |
| Public data members | camelCase | size, colorType |
| Local variables | camelCase | controlPoints, diff |
| Namespaces | lowercase | tsd::core, tsd::scene |
| Macros | UPPER_SNAKE_CASE | TSD_NOT_COPYABLE, VISRTX_DEVICE |
constexpr constants |
UPPER_SNAKE_CASE | INVALID_INDEX, MAX_LOCAL_STORAGE |
| Type aliases | PascalCase with suffix | Ptr, Ref, element_t |
| GPU data structs | PascalCase + GPUData suffix |
FrameGPUData, MaterialGPUData |
Additional rules:
- No
C/Iprefix on class or interface names. - Getter methods prefer the bare property name (
name(),type()) overgetName()/getType(). - Boolean query methods follow the pattern:
is<T>(),valid(),empty(),contains().
#pragma onceexclusively — no#ifndef/#defineinclude guards.- Include order (blank line between each group):
- Project headers (
"tsd/...","visrtx/...") - External library headers (
<anari/...>,<helium/...>) - Standard library headers (
<vector>,<memory>, …)
- Project headers (
- Forward-declare types in headers where possible to minimize include depth. Use fully-qualified names in forward declarations.
- Hierarchical, 2–3 levels deep:
tsd::core,tsd::scene,tsd::rendering,tsd::io,tsd::app; device namespaces:visrtx,visgl. - Sub-namespaces for implementation details:
detail,tokens,colormap. - Anonymous namespaces (
namespace { ... }) for translation-unit–local linkage in.cppfiles. using namespaceis allowed inside.cppfiles and inside namespace bodies. Never at global scope in a header.
Order within a class/struct body:
- Public type aliases and nested types
- Public constructors / destructor
- Lifetime macros (
TSD_NOT_COPYABLE,TSD_DEFAULT_MOVEABLE, …) - Public method declarations (queries before mutators)
protectedvirtual hooks / overrides — declarations onlyprivatedata members
Method definitions are never written inside the class/struct body — even
trivial one-liners. All definitions (inline, constexpr, and template) go in a
clearly delimited section after the class declaration:
struct Foo {
int bar() const;
void setBaz(int v);
// ...
private:
int m_bar{0};
};
// Inlined definitions ////////////////////////////////////////////////////////
inline int Foo::bar() const
{
return m_bar;
}
inline void Foo::setBaz(int v)
{
m_bar = v;
}This keeps the class declaration readable as an interface, with all implementation detail below.
- No raw
new/delete.std::unique_ptr<T>for exclusive ownership.std::shared_ptr<T>for shared ownership (use sparingly).
- Non-owning references use raw pointers or project-specific ref wrappers
(
ObjectPoolRef<T>). - Express copy/move intent explicitly with the macros defined in
TypeMacros.hpp:TSD_NOT_COPYABLE(MyClass) TSD_DEFAULT_MOVEABLE(MyClass) - RAII everywhere — resources are owned and released by objects, never managed manually.
Use C++17 features freely where they improve clarity:
if constexpr— compile-time branching in templates.std::optional<T>— nullable return values.std::string_view— read-only string parameters.std::byte— raw byte buffers.- Structured bindings — where they aid readability.
Avoid features that obscure intent or have poor tooling support.
Use auto when:
- The type is immediately obvious from the right-hand side.
- The spelled-out type would be verbose (iterators, template instantiations, results of explicit casts).
Avoid auto in:
- Public API declarations and function signatures.
- Situations where the type is not clear without additional context.
- Mark all member functions that do not mutate state
const. - Pass large or non-trivial types by
const &; pass scalars and cheap types by value. - Use
constexprfor all compile-time constants — not#defineorstatic const. - Prefer
constlocal variables whenever the value does not change after initialization.
- Place full template definitions in headers. They belong in the Inlined definitions section after the class declaration (same rule as non-template inline methods — never inside the class body).
- Use
static_assertto enforce template parameter constraints early, with a clear diagnostic message. - Avoid CRTP unless virtual dispatch is genuinely unacceptable for performance. Prefer virtual inheritance for most extensibility patterns.
| Scenario | Mechanism |
|---|---|
| API misuse (programmer error) | throw std::runtime_error(...) |
| Compile-time invariants | static_assert(condition, "message") |
| Recoverable / expected failure | Return bool or std::optional |
Do not use try/catch inside library code — let exceptions propagate to the
application layer.
/* ... */block comments for class-level and file-level documentation.//inline comments sparingly, only where the logic is non-obvious.- Section divider pattern:
// Section name /////////////////////////////////////////////////////////////// - No Doxygen
///triple-slash style. - Comments explain why, not what — the code itself should convey what it does.
| Extension | Purpose |
|---|---|
.cu |
OptiX programs and CUDA kernels compiled to PTX |
.cuh |
Device-side inline utilities and declarations |
.cpp |
Host-side management: object lifetime, memory upload, pipeline setup |
.h |
Shared definitions visible to both host and device (guarded by #ifdef __CUDACC__) |
Always use the project macros defined in gpu_decl.h — never raw CUDA
qualifiers directly:
| Macro | Expands to | Use for |
|---|---|---|
VISRTX_HOST_DEVICE |
inline __host__ __device__ |
Math helpers callable from both sides |
VISRTX_DEVICE |
inline __device__ |
Device-only helper functions |
VISRTX_GLOBAL |
extern "C" __global__ |
CUDA kernels (non-OptiX) |
VISRTX_CALLABLE |
extern "C" __device__ |
OptiX direct-callable programs |
Follow the OptiX double-underscore convention with an optional descriptive suffix:
VISRTX_GLOBAL void __raygen__() { ... }
VISRTX_GLOBAL void __closesthit__primary() { ... }
VISRTX_GLOBAL void __anyhit__shadow() { ... }
VISRTX_GLOBAL void __miss__() { ... }
VISRTX_CALLABLE void __direct_callable__init() { ... }Structures passed to the device via launch parameters or constant memory:
- Suffix with
GPUData:FrameGPUData,MaterialGPUData,GeometryGPUData. - Fields must be device pointers (
const T *) — never host-side smart pointers. - Declare
__constant__launch parameters with theDECLARE_FRAME_DATA(name)macro.
- Use
DeviceBufferwrappers for managed GPU memory. Avoid barecudaMallocin new code. - Prefer
thrustalgorithms (thrust::transform,thrust::scan,thrust::fill, etc.) over custom__global__kernels when a standard operation suffices. - Host-side array objects expose a
gpuData()method returning the correspondingGPUDatastruct with device pointers.
#ifdef __CUDACC__guards control CUDA-specific paths in shared headers.- The
VISRTX_*macros keep shared math headers compilable by both the C++ and CUDA compilers without duplication. - Data crosses the boundary via explicit
cudaMemcpyorDeviceBufferupload — never implicitly.