Favour compile-time dispatch
This commit is contained in:
@@ -359,10 +359,11 @@ void fs_main(...) { ... }
|
||||
```
|
||||
src/prism/
|
||||
├── rhi/
|
||||
│ ├── pr_rhi.h ← Public API (backend-agnostic)
|
||||
│ ├── pr_rhi.h ← Umbrella header: canonical API + dispatch
|
||||
│ ├── pr_rhi_types.h ← Shared types (PrRhiBufferDesc, etc.)
|
||||
│ ├── vulkan/
|
||||
│ │ ├── pr_rhi_vk.h ← Vulkan backend internal header
|
||||
│ │ ├── pr_rhi_vk.h ← Declares prRhiCreateDeviceVk, etc.
|
||||
│ │ ├── pr_rhi_vk_aliases.h ← #define prRhiCreateDevice prRhiCreateDeviceVk
|
||||
│ │ ├── pr_rhi_vk_device.c
|
||||
│ │ ├── pr_rhi_vk_buffer.c
|
||||
│ │ ├── pr_rhi_vk_texture.c
|
||||
@@ -371,7 +372,8 @@ src/prism/
|
||||
│ │ ├── pr_rhi_vk_descriptor.c
|
||||
│ │ ├── pr_rhi_vk_command.c
|
||||
│ │ └── pr_rhi_vk_swapchain.c
|
||||
│ └── d3d12/ ← (future)
|
||||
│ ├── d3d12/ ← (future)
|
||||
│ └── metal/ ← (future)
|
||||
└── ...
|
||||
```
|
||||
|
||||
@@ -390,27 +392,148 @@ void prRhiCmdCopyBuffer(PrRhiCommandBuffer *cb,
|
||||
PrRhiBuffer *src, PrRhiBuffer *dst);
|
||||
```
|
||||
|
||||
### 8.3 Backend dispatch (compile-time)
|
||||
### 8.3 Backend dispatch (compile-time via preprocessor aliases)
|
||||
|
||||
Backend selection happens at compile time via preprocessor aliases — no vtbl,
|
||||
no runtime dispatch overhead. Each backend is a set of standalone `.c` files;
|
||||
the build system compiles only the selected backend's sources.
|
||||
|
||||
```
|
||||
src/prism/rhi/
|
||||
├── pr_rhi.h ← umbrella: canonical API + dispatch
|
||||
├── pr_rhi_types.h ← shared types (all backends include this)
|
||||
├── vulkan/
|
||||
│ ├── pr_rhi_vk.h ← declares prRhiCreateDeviceVk, etc.
|
||||
│ ├── pr_rhi_vk_aliases.h ← #define prRhiCreateDevice prRhiCreateDeviceVk
|
||||
│ ├── pr_rhi_vk_device.c
|
||||
│ └── pr_rhi_vk_buffer.c
|
||||
├── d3d12/
|
||||
│ ├── pr_rhi_d3d12.h ← declares prRhiCreateDeviceD3D12, etc.
|
||||
│ ├── pr_rhi_d3d12_aliases.h ← #define prRhiCreateDevice prRhiCreateDeviceD3D12
|
||||
│ └── pr_rhi_d3d12_device.c
|
||||
└── metal/
|
||||
├── pr_rhi_metal.h
|
||||
├── pr_rhi_metal_aliases.h
|
||||
└── pr_rhi_metal_device.c
|
||||
```
|
||||
|
||||
The umbrella header documents the public API and conditionally includes the
|
||||
selected backend's aliases:
|
||||
|
||||
```c
|
||||
// pr_rhi.h
|
||||
typedef struct PrRhiDevice PrRhiDevice;
|
||||
struct PrRhiDevice {
|
||||
PrRhiDeviceVtbl *vtbl; // function pointer table
|
||||
void *backend; // VkDevice or ID3D12Device
|
||||
};
|
||||
#ifndef PR_RHI_H
|
||||
#define PR_RHI_H
|
||||
|
||||
// Each backend fills the vtbl
|
||||
typedef struct PrRhiDeviceVtbl {
|
||||
PrRhiBuffer* (*createBuffer)(PrRhiDevice*, const PrRhiBufferDesc*, WpAllocator*);
|
||||
void (*destroyBuffer)(PrRhiBuffer*, WpAllocator*);
|
||||
#include "pr_rhi_types.h"
|
||||
|
||||
// ── Public API (documented here) ────────────────────────────────────
|
||||
PrRhiDevice *prRhiCreateDevice(const PrRhiDeviceDesc *desc, WpAllocator *alloc);
|
||||
void prRhiDestroyDevice(PrRhiDevice *device, WpAllocator *alloc);
|
||||
PrRhiBuffer *prRhiCreateBuffer(PrRhiDevice *d, const PrRhiBufferDesc *desc, WpAllocator *a);
|
||||
void prRhiDestroyBuffer(PrRhiBuffer *b, WpAllocator *a);
|
||||
// ... etc
|
||||
} PrRhiDeviceVtbl;
|
||||
|
||||
// ── Backend dispatch ──────────────────────────────────────────────
|
||||
#if defined(PR_RHI_VULKAN)
|
||||
# include "vulkan/pr_rhi_vk_aliases.h"
|
||||
#elif defined(PR_RHI_D3D12)
|
||||
# include "d3d12/pr_rhi_d3d12_aliases.h"
|
||||
#elif defined(PR_RHI_METAL)
|
||||
# include "metal/pr_rhi_metal_aliases.h"
|
||||
#else
|
||||
# error "Define one of: PR_RHI_VULKAN, PR_RHI_D3D12, PR_RHI_METAL"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
```
|
||||
|
||||
This gives zero-cost abstraction (pointer indirection on calls) while keeping
|
||||
the API clean. The renderer calls `device->vtbl->createBuffer(...)` and the
|
||||
backend resolves the call.
|
||||
Each aliases header maps the generic names to the backend's concrete names:
|
||||
|
||||
```c
|
||||
// vulkan/pr_rhi_vk_aliases.h
|
||||
#ifndef PR_RHI_VK_ALIASES_H
|
||||
#define PR_RHI_VK_ALIASES_H
|
||||
|
||||
#include "pr_rhi_vk.h"
|
||||
|
||||
#define prRhiCreateDevice prRhiCreateDeviceVk
|
||||
#define prRhiDestroyDevice prRhiDestroyDeviceVk
|
||||
#define prRhiCreateBuffer prRhiCreateBufferVk
|
||||
#define prRhiDestroyBuffer prRhiDestroyBufferVk
|
||||
|
||||
#endif
|
||||
```
|
||||
|
||||
The backend implementation headers declare only their own real names:
|
||||
|
||||
```c
|
||||
// vulkan/pr_rhi_vk.h
|
||||
#ifndef PR_RHI_VK_H
|
||||
#define PR_RHI_VK_H
|
||||
|
||||
#include "../pr_rhi_types.h"
|
||||
|
||||
PrRhiDevice *prRhiCreateDeviceVk(const PrRhiDeviceDesc *desc, WpAllocator *alloc);
|
||||
void prRhiDestroyDeviceVk(PrRhiDevice *device, WpAllocator *alloc);
|
||||
// ...
|
||||
|
||||
#endif
|
||||
```
|
||||
|
||||
Backend `.c` files are normal standalone translation units — no `.c` inclusion:
|
||||
|
||||
```c
|
||||
// vulkan/pr_rhi_vk_device.c
|
||||
#include "pr_rhi_vk.h"
|
||||
|
||||
PrRhiDevice *prRhiCreateDeviceVk(const PrRhiDeviceDesc *desc, WpAllocator *alloc) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Render code uses the generic names via the umbrella:
|
||||
|
||||
```c
|
||||
#include "prism/rhi/pr_rhi.h"
|
||||
|
||||
int main(void) {
|
||||
PrRhiDevice *dev = prRhiCreateDevice(&desc, &scratch); // → prRhiCreateDeviceVk
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Or explicitly selects a backend by including its header directly:
|
||||
|
||||
```c
|
||||
#include "prism/rhi/vulkan/pr_rhi_vk.h"
|
||||
|
||||
int main(void) {
|
||||
PrRhiDevice *dev = prRhiCreateDeviceVk(&desc, &scratch); // real name, no alias
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The build system controls selection by defining the preprocessor macro and
|
||||
listing only the chosen backend's `.c` files:
|
||||
|
||||
```sh
|
||||
# Vulkan build
|
||||
clang -DPR_RHI_VULKAN \
|
||||
main.c \
|
||||
src/prism/rhi/vulkan/pr_rhi_vk_device.c \
|
||||
src/prism/rhi/vulkan/pr_rhi_vk_buffer.c \
|
||||
src/wapp/wapp.c \
|
||||
-o compositor
|
||||
```
|
||||
|
||||
**Properties:**
|
||||
- Zero runtime overhead (macro expansion is a text substitution)
|
||||
- Dead code elimination is automatic — unselected backends are never compiled
|
||||
- Transparent debugging — stack traces show `prRhiCreateDeviceVk` directly
|
||||
- Documentation lives in one place — the umbrella `pr_rhi.h`
|
||||
- Each backend is a proper compilation unit — no `#include` of `.c` files
|
||||
- Explicit override path for single-backend builds or testing
|
||||
|
||||
### 8.4 Frame lifecycle
|
||||
|
||||
|
||||
Reference in New Issue
Block a user