--- name: prism-rhi description: RHI (Rendering Hardware Interface) patterns — compile-time dispatch, by-value desc structs, wapp array aliases, backend file layout license: MIT compatibility: opencode metadata: domain: rendering --- ## What I do Captures the RHI conventions for Prism: how backends are dispatched, how descriptor structs and arrays are handled, and how the files are organized. ## When to use me Use this when working on any file in `src/prism/rhi/`, or when creating a new backend (Vulkan, D3D12, Metal). ## Conventions ### Global context RHI functions do **not** take allocator parameters. A global `PrRhiContext` provides two allocators: - `allocator` — for user-facing objects (buffers, textures, pipelines, etc.) - `tmp` — for short-lived internal temporaries ```c extern PrRhiContext _G_RHI_CONTEXT; void prRhiInit(void); // sets up both allocators void prRhiDestroy(void); // tears down context ``` All RHI functions access `_G_RHI_CONTEXT` directly. Do not pass allocators to RHI API calls. ### Backend dispatch Backend selection is compile-time via `-D PR_RHI_VULKAN` / `-D PR_RHI_D3D12` / `-D PR_RHI_METAL`. The umbrella header `pr_rhi.h` includes the appropriate alias file: ```c #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 ``` Each `_aliases.h` file maps generic names to backend-specific names: ```c #define prRhiCreateDevice prRhiCreateDeviceVk #define prRhiCreateSwapchain prRhiCreateSwapchainVk #define prRhiCreateBuffer prRhiCreateBufferVk // … ``` Backend implementations are suffixed with the backend name: `pr_rhi_vk_device.c`, `pr_rhi_vk_swapchain.c`, etc. ### Desc structs All descriptor structs are passed **by value**, not `const *`: ```c // correct PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface, PrRhiDeviceDesc desc); // wrong PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface, const PrRhiDeviceDesc *desc); ``` ### Frame-by-frame command batching Commands that run every frame must avoid arena allocation. Use stack arrays with a while-loop to batch operations: ```c // correct — stack array, batched submission void prRhiCmdBindDescriptorSetsVk(PrRhiCommandBuffer *cb, PrRhiPipelineBindPoint bind_point, PrRhiPipelineLayout *layout, u32 first_set, PrRhiDescriptorSetArray sets) { u32 set_count = sets ? (u32)wpArrayCount(sets) : 0; while (set_count > 0) { VkDescriptorSetArray vk_sets = wpArrayWithCapacity(VkDescriptorSet, 16, WP_ARRAY_INIT_FILLED); u32 total_capacity = (u32)wpArrayCapacity(vk_sets); u32 real_count = set_count < total_capacity ? set_count : total_capacity; for (u32 i = 0; i < real_count; ++i) { vk_sets[i] = sets[i]->handle; } vkCmdBindDescriptorSets(cb->handle, vk_bp, vk_layout, first_set, real_count, vk_sets, 0, NULL); set_count -= real_count; first_set += real_count; } } // wrong — allocates from arena on every call void prRhiCmdBindDescriptorSetsBad(PrRhiCommandBuffer *cb, ...) { VkDescriptorSetArray vk_sets = wpArrayAllocCapacity(VkDescriptorSet, &_G_RHI_CONTEXT.allocator, count, ...); // ... this leaks every frame } ``` Apply this pattern to: `prRhiCmdBindDescriptorSets`, `prRhiCmdBindVertexBuffers`, `prRhiCmdCopyBufferToImage`, and any other command that processes user-provided arrays. ### Opaque struct handles — no casts Handle types in opaque structs are already the correct Vulkan type. Do not cast: ```c // correct vk_device = device->handle; vk_buffer = buffer->handle; // wrong vk_device = (VkDevice)device->handle; vk_buffer = (VkBuffer)buffer->handle; ``` ### Vulkan struct initialisation — designated initializers Always use C99 designated initializers for Vulkan info structs: ```c // correct VkBufferCreateInfo buf_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = desc.size, .usage = _toVkBufferUsage(desc.usage), }; // wrong VkBufferCreateInfo buf_info = {}; buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; buf_info.size = desc.size; buf_info.usage = _toVkBufferUsage(desc.usage); ``` ### API patterns - **`prRhiCreateCommandPool`**: Takes only `PrRhiDevice *device` (uses `device->queue_family_index` internally) - **`prRhiFreeCommandBuffers`**: Takes `PrRhiCommandBufferArray buffers` (count derived from `wpArrayCount`) - **`prRhiAllocateDescriptorSet`**: Takes `WpU32Array variable_descriptor_counts` for variable descriptor support - **`prRhiCmdBindVertexBuffers`**: Takes `WpU64Array offsets` (count matched to buffers internally) - **Shader entry points**: Configurable via `vertex_shader_entry_point` / `fragment_shader_entry_point` in pipeline desc (not hardcoded to "main") ### File layout ``` src/prism/rhi/ ├── pr_rhi.h ← umbrella header (API declarations + backend dispatch) ├── pr_rhi.c ← global context definition (prRhiInit, prRhiDestroy) ├── pr_rhi_types.h ← shared types (enums, element types, array aliases, desc structs, opaque handles) ├── vulkan/ │ ├── pr_rhi_vk.h ← Vulkan backend header (opaque struct defs + Vk-suffixed decls) │ ├── pr_rhi_vk.c ← Vulkan backend implementation │ ├── pr_rhi_vk_aliases.h ← #define alias mapping │ └── profiles/ ← generated Vulkan Profiles library ├── d3d12/ │ └── … └── metal/ └── … ```