Favour compile-time dispatch
This commit is contained in:
@@ -359,10 +359,11 @@ void fs_main(...) { ... }
|
|||||||
```
|
```
|
||||||
src/prism/
|
src/prism/
|
||||||
├── rhi/
|
├── rhi/
|
||||||
│ ├── pr_rhi.h ← Public API (backend-agnostic)
|
│ ├── pr_rhi.h ← Umbrella header: canonical API + dispatch
|
||||||
│ ├── pr_rhi_types.h ← Shared types (PrRhiBufferDesc, etc.)
|
│ ├── pr_rhi_types.h ← Shared types (PrRhiBufferDesc, etc.)
|
||||||
│ ├── vulkan/
|
│ ├── 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_device.c
|
||||||
│ │ ├── pr_rhi_vk_buffer.c
|
│ │ ├── pr_rhi_vk_buffer.c
|
||||||
│ │ ├── pr_rhi_vk_texture.c
|
│ │ ├── pr_rhi_vk_texture.c
|
||||||
@@ -371,7 +372,8 @@ src/prism/
|
|||||||
│ │ ├── pr_rhi_vk_descriptor.c
|
│ │ ├── pr_rhi_vk_descriptor.c
|
||||||
│ │ ├── pr_rhi_vk_command.c
|
│ │ ├── pr_rhi_vk_command.c
|
||||||
│ │ └── pr_rhi_vk_swapchain.c
|
│ │ └── pr_rhi_vk_swapchain.c
|
||||||
│ └── d3d12/ ← (future)
|
│ ├── d3d12/ ← (future)
|
||||||
|
│ └── metal/ ← (future)
|
||||||
└── ...
|
└── ...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -390,27 +392,148 @@ void prRhiCmdCopyBuffer(PrRhiCommandBuffer *cb,
|
|||||||
PrRhiBuffer *src, PrRhiBuffer *dst);
|
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
|
```c
|
||||||
// pr_rhi.h
|
// pr_rhi.h
|
||||||
typedef struct PrRhiDevice PrRhiDevice;
|
#ifndef PR_RHI_H
|
||||||
struct PrRhiDevice {
|
#define PR_RHI_H
|
||||||
PrRhiDeviceVtbl *vtbl; // function pointer table
|
|
||||||
void *backend; // VkDevice or ID3D12Device
|
|
||||||
};
|
|
||||||
|
|
||||||
// Each backend fills the vtbl
|
#include "pr_rhi_types.h"
|
||||||
typedef struct PrRhiDeviceVtbl {
|
|
||||||
PrRhiBuffer* (*createBuffer)(PrRhiDevice*, const PrRhiBufferDesc*, WpAllocator*);
|
// ── Public API (documented here) ────────────────────────────────────
|
||||||
void (*destroyBuffer)(PrRhiBuffer*, WpAllocator*);
|
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
|
// ... 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
|
Each aliases header maps the generic names to the backend's concrete names:
|
||||||
the API clean. The renderer calls `device->vtbl->createBuffer(...)` and the
|
|
||||||
backend resolves the call.
|
```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
|
### 8.4 Frame lifecycle
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user