Compare commits
30 Commits
dc2fc22462
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e6f051955 | |||
| e59865bff5 | |||
| e206e4647b | |||
| a0b7c0672a | |||
| 3d4f34c531 | |||
| ebd1801883 | |||
| f5ff6c70ea | |||
| 30408ff244 | |||
| 618f09689d | |||
| 4bbd7dfe01 | |||
| cc2d0cea6e | |||
| 3c1997e2b1 | |||
| 4b08866fc6 | |||
| 96c89dcb86 | |||
| bb6d2eae14 | |||
| 45a34bb151 | |||
| bffe9b8174 | |||
| 943f00345c | |||
| c8680f06c2 | |||
| 1c7a7f6c46 | |||
| 7bd1d9f701 | |||
| 49aba1eb3c | |||
| cb3ef2be1c | |||
| 5a26bf54c8 | |||
| a073dec6a0 | |||
| 7819f2abc3 | |||
| 3e1e09c974 | |||
| 0d1c5b84f2 | |||
| 5bf0ba40ca | |||
| 26f17628a4 |
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
# Prism coding style — enforced by clang-format.
|
|
||||||
# Conventions that cannot be automated (e.g. return-type alignment
|
|
||||||
# across declarations) are documented in AGENTS.md.
|
|
||||||
|
|
||||||
BasedOnStyle: LLVM
|
|
||||||
|
|
||||||
# Indentation — tabs (no spaces)
|
|
||||||
UseTab: Always
|
|
||||||
IndentWidth: 8
|
|
||||||
TabWidth: 8
|
|
||||||
|
|
||||||
# Braces — always required after if/else/for/while/do
|
|
||||||
AllowShortFunctionsOnASingleLine: false
|
|
||||||
AllowShortIfStatementsOnASingleLine: false
|
|
||||||
AllowShortLoopsOnASingleLine: false
|
|
||||||
BreakBeforeBraces: Attach
|
|
||||||
|
|
||||||
# Pointer/reference alignment — * against name not type
|
|
||||||
PointerAlignment: Right
|
|
||||||
ReferenceAlignment: Right
|
|
||||||
|
|
||||||
# Continuation lines — align with first parameter after (
|
|
||||||
AlignAfterOpenBracket: Align
|
|
||||||
AlignConsecutiveAssignments: false
|
|
||||||
AlignConsecutiveDeclarations: false
|
|
||||||
|
|
||||||
# Line width
|
|
||||||
ColumnLimit: 120
|
|
||||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
build
|
||||||
|
compile_commands.json
|
||||||
.vscode
|
.vscode
|
||||||
*.dSYM
|
*.dSYM
|
||||||
|
assets/shaders
|
||||||
scratchpad/**
|
scratchpad/**
|
||||||
!scratchpad/**/
|
!scratchpad/**/
|
||||||
!scratchpad/**/*.h
|
!scratchpad/**/*.h
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "src/vendor/ktx"]
|
||||||
|
path = src/vendor/ktx
|
||||||
|
url = https://github.com/KhronosGroup/KTX-Software
|
||||||
@@ -12,10 +12,25 @@ Captures the RHI conventions for Prism: how backends are dispatched, how descrip
|
|||||||
|
|
||||||
## When to use me
|
## When to use me
|
||||||
|
|
||||||
Use this when working on any file in `scratchpad/rhi/` or `src/prism/rhi/`, or when creating a new backend (Vulkan, D3D12, Metal).
|
Use this when working on any file in `src/prism/rhi/`, or when creating a new backend (Vulkan, D3D12, Metal).
|
||||||
|
|
||||||
## Conventions
|
## 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 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:
|
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:
|
||||||
@@ -49,23 +64,97 @@ All descriptor structs are passed **by value**, not `const *`:
|
|||||||
|
|
||||||
```c
|
```c
|
||||||
// correct
|
// correct
|
||||||
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface,
|
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface, PrRhiDeviceDesc desc);
|
||||||
PrRhiDeviceDesc desc, WpAllocator *alloc);
|
|
||||||
|
|
||||||
// wrong
|
// wrong
|
||||||
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface,
|
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface, const PrRhiDeviceDesc *desc);
|
||||||
const PrRhiDeviceDesc *desc, WpAllocator *alloc);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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
|
### File layout
|
||||||
|
|
||||||
```
|
```
|
||||||
scratchpad/rhi/
|
src/prism/rhi/
|
||||||
├── pr_rhi.h ← umbrella header (API declarations + backend dispatch)
|
├── 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)
|
├── pr_rhi_types.h ← shared types (enums, element types, array aliases, desc structs, opaque handles)
|
||||||
├── vulkan/
|
├── vulkan/
|
||||||
│ ├── pr_rhi_vk.h ← Vulkan backend header (opaque struct defs + Vk-suffixed decls)
|
│ ├── pr_rhi_vk.h ← Vulkan backend header (opaque struct defs + Vk-suffixed decls)
|
||||||
│ └── pr_rhi_vk_aliases.h ← #define alias mapping
|
│ ├── pr_rhi_vk.c ← Vulkan backend implementation
|
||||||
|
│ ├── pr_rhi_vk_aliases.h ← #define alias mapping
|
||||||
|
│ └── profiles/ ← generated Vulkan Profiles library
|
||||||
├── d3d12/
|
├── d3d12/
|
||||||
│ └── …
|
│ └── …
|
||||||
└── metal/
|
└── metal/
|
||||||
|
|||||||
@@ -34,9 +34,21 @@ All code follows the patterns established in `src/wapp/`. The project prefix is
|
|||||||
|
|
||||||
### Formatting
|
### Formatting
|
||||||
|
|
||||||
Machine-enforceable rules (tabs, braces, pointer alignment, continuation
|
- **Tabs for indentation**, 8-column tab width.
|
||||||
alignment) are in `.clang-format` — run `clang-format -i <file>` to apply.
|
- **Braces** on the same line as control statements (Attach style).
|
||||||
|
- **Braces on single-line if statements**: Always use braces, even for single-line bodies:
|
||||||
|
```c
|
||||||
|
// correct
|
||||||
|
if (!buffer) { return; }
|
||||||
|
if (!texture) { _abort("alloc failed"); }
|
||||||
|
|
||||||
|
// wrong
|
||||||
|
if (!buffer) return;
|
||||||
|
if (!texture) _abort("alloc failed");
|
||||||
|
```
|
||||||
|
- **Pointers**: `*` against the name, not the type (`PrRhiBuffer *buf`, not `PrRhiBuffer* buf`).
|
||||||
|
- **Line width**: 120 columns.
|
||||||
|
- **Continuation lines** align to the opening parenthesis.
|
||||||
- **Return-type alignment**: Within each `// =====` section, align function
|
- **Return-type alignment**: Within each `// =====` section, align function
|
||||||
declaration names so the first letter of every function occupies the same
|
declaration names so the first letter of every function occupies the same
|
||||||
column. For pointer return types, place `*` directly against the function
|
column. For pointer return types, place `*` directly against the function
|
||||||
@@ -47,7 +59,6 @@ alignment) are in `.clang-format` — run `clang-format -i <file>` to apply.
|
|||||||
void prRhiDestroySwapchain(…);
|
void prRhiDestroySwapchain(…);
|
||||||
PrRhiSwapchainResult prRhiAcquireNextImage(…);
|
PrRhiSwapchainResult prRhiAcquireNextImage(…);
|
||||||
```
|
```
|
||||||
This cannot be automated by clang-format and must be done manually.
|
|
||||||
|
|
||||||
### Storage qualifiers
|
### Storage qualifiers
|
||||||
|
|
||||||
@@ -83,6 +94,12 @@ void prNodeDestroy(PrNode *n, PrAllocator *alloc);
|
|||||||
Use wapp allocators (`WpAllocator`, arena-based). Stack-allocate where
|
Use wapp allocators (`WpAllocator`, arena-based). Stack-allocate where
|
||||||
possible; pass allocators explicitly.
|
possible; pass allocators explicitly.
|
||||||
|
|
||||||
|
**Never use libc for memory or file I/O.** wapp always takes precedence:
|
||||||
|
- `wpMemAllocatorAlloc` / `wpMemAllocatorFree` instead of `malloc` / `free`
|
||||||
|
- `wpFileOpen` / `wpFileRead` / `wpFileClose` instead of `fopen` / `fread` / `fclose`
|
||||||
|
|
||||||
|
For one-shot loads (e.g. SPIR-V at init), use `&_G_RHI_CONTEXT.allocator`.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
PrGraph *prGraphCreate(PrAllocator *alloc);
|
PrGraph *prGraphCreate(PrAllocator *alloc);
|
||||||
void prGraphDestroy(PrGraph *g, PrAllocator *alloc);
|
void prGraphDestroy(PrGraph *g, PrAllocator *alloc);
|
||||||
@@ -116,6 +133,24 @@ prefer SoA layouts, batch processing, and minimise pointer chasing. Keep
|
|||||||
the DAG in contiguous arrays (e.g. adjacency lists packed in flat buffers)
|
the DAG in contiguous arrays (e.g. adjacency lists packed in flat buffers)
|
||||||
rather than individually allocated linked structures.
|
rather than individually allocated linked structures.
|
||||||
|
|
||||||
|
### Frame-by-frame command batching
|
||||||
|
|
||||||
|
Commands that execute every frame must avoid arena allocation. Use stack
|
||||||
|
arrays with a while-loop to batch operations in fixed-size chunks:
|
||||||
|
|
||||||
|
```c
|
||||||
|
while (count > 0) {
|
||||||
|
VkTypeArray batch = wpArrayWithCapacity(VkType, 16, WP_ARRAY_INIT_FILLED);
|
||||||
|
u32 batch_size = count < wpArrayCapacity(batch) ? count : (u32)wpArrayCapacity(batch);
|
||||||
|
// ... process batch ...
|
||||||
|
vkCmd*(cb->handle, ...);
|
||||||
|
count -= batch_size;
|
||||||
|
first += batch_size;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This avoids per-frame arena churn while handling arbitrarily large inputs.
|
||||||
|
|
||||||
### Graph / adjacency lists
|
### Graph / adjacency lists
|
||||||
|
|
||||||
Adjacency list nodes must be **separately allocated from the vertex array**.
|
Adjacency list nodes must be **separately allocated from the vertex array**.
|
||||||
@@ -199,7 +234,7 @@ WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(16));
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Save research notes and implementation plans as markdown in `documents/`:
|
Save research notes, implementation plans and session logs as markdown in `documents/`:
|
||||||
|
|
||||||
```
|
```
|
||||||
documents/
|
documents/
|
||||||
@@ -213,6 +248,9 @@ documents/
|
|||||||
└── YYYY-MM-DD.md
|
└── YYYY-MM-DD.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
At the start of each new session, read the previous session logs to understand what
|
||||||
|
we've implemented so far
|
||||||
|
|
||||||
## Skills
|
## Skills
|
||||||
|
|
||||||
Domain-specific conventions are stored as skills in `.opencode/skills/<name>/SKILL.md`.
|
Domain-specific conventions are stored as skills in `.opencode/skills/<name>/SKILL.md`.
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Prism fullscreen texture blit shader.
|
||||||
|
//
|
||||||
|
// Draws a selected texture from the bindless array as a fullscreen quad that
|
||||||
|
// is letterboxed/pillarboxed to preserve aspect ratio (contain-fit). The NDC
|
||||||
|
// content rect is supplied via push constants so the texture is never
|
||||||
|
// stretched, squashed, or cropped.
|
||||||
|
|
||||||
|
struct BlitData {
|
||||||
|
float4 rect; // NDC fit rect: x0, y0, x1, y1
|
||||||
|
uint selected;
|
||||||
|
uint mode; // 0 = sample texture, 1 = solid background
|
||||||
|
uint pad[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
[[vk::push_constant]]
|
||||||
|
BlitData blit;
|
||||||
|
|
||||||
|
Sampler2D textures[];
|
||||||
|
|
||||||
|
struct VSOutput {
|
||||||
|
float4 Pos : SV_POSITION;
|
||||||
|
float2 UV;
|
||||||
|
};
|
||||||
|
|
||||||
|
[shader("vertex")]
|
||||||
|
VSOutput main(uint vertexIndex : SV_VertexID) {
|
||||||
|
VSOutput output;
|
||||||
|
float2 uv = float2(float(vertexIndex & 1), float((vertexIndex >> 1) & 1));
|
||||||
|
output.UV = uv;
|
||||||
|
float2 pos = lerp(blit.rect.xy, blit.rect.zw, uv);
|
||||||
|
output.Pos = float4(pos, 0.0, 1.0);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
float4 main(VSOutput input) {
|
||||||
|
if (blit.mode == 1) {
|
||||||
|
return float4(0.18, 0.18, 0.18, 1.0);
|
||||||
|
}
|
||||||
|
return textures[NonUniformResourceIndex(blit.selected)].Sample(input.UV);
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,554 @@
|
|||||||
|
# Plan: Texture Pool + Node Evaluation
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Design the texture pool and node-to-shader dispatch so the node DAG doubles as the
|
||||||
|
frame graph. Each node type maps to a single Slang shader (no fusion). The texture
|
||||||
|
pool enables concurrent branches by allowing multiple intermediate textures to
|
||||||
|
coexist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Pool Allocator
|
||||||
|
|
||||||
|
### 1.1 Purpose
|
||||||
|
|
||||||
|
A reusable pool allocator for fixed-size blocks. This replaces the ad-hoc
|
||||||
|
`PrPool` in scratchpad/dag.c and can be used for any fixed-size allocation
|
||||||
|
throughout the project: node structs, edge structs, texture slots, descriptor
|
||||||
|
sets, etc.
|
||||||
|
|
||||||
|
Lives in `src/prism/allocators/`, **not** in wapp. wapp is vendored and may be
|
||||||
|
replaced — the pool allocator must not be part of it.
|
||||||
|
|
||||||
|
The pool owns its memory. No external allocator is passed — the pool allocates
|
||||||
|
blocks internally via wapp OS allocation and grows on demand when free slots
|
||||||
|
run out.
|
||||||
|
|
||||||
|
### 1.2 Design
|
||||||
|
|
||||||
|
The pool manages fixed-size slots arranged in contiguous blocks. Free slots are
|
||||||
|
tracked via an intrusive free list (first `sizeof(void*)` bytes of each free
|
||||||
|
slot hold a pointer to the next free slot). When the free list is empty, the
|
||||||
|
pool allocates a new block of `block_slots` slots and carves them into the
|
||||||
|
free list.
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct PrPool PrPool;
|
||||||
|
|
||||||
|
struct PrPool {
|
||||||
|
void **blocks; // array of allocated block pointers (for destroy)
|
||||||
|
u64 block_count; // number of allocated blocks
|
||||||
|
u64 block_cap; // capacity of blocks array
|
||||||
|
void *free_list; // intrusive free list head
|
||||||
|
u64 slot_size; // user-requested slot size
|
||||||
|
u64 alloc_size; // actual slot size used internally (>= slot_size, >= sizeof(void*))
|
||||||
|
u64 block_slots; // slots per block
|
||||||
|
u64 total; // total slots ever allocated (diagnostics)
|
||||||
|
u64 active; // currently in use (diagnostics)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 API
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Initialise a pool.
|
||||||
|
// slot_size: fixed size of each slot
|
||||||
|
// initial_slots: starting capacity in slots (also used as block size)
|
||||||
|
void prPoolInit(PrPool *pool, u64 slot_size, u64 initial_slots);
|
||||||
|
|
||||||
|
// Allocate one slot. Grows by a new block if the free list is empty.
|
||||||
|
// Returns NULL only on allocation failure.
|
||||||
|
void *prPoolAlloc(PrPool *pool);
|
||||||
|
|
||||||
|
// Return a slot to the pool's free list. Safe no-op on NULL.
|
||||||
|
void prPoolFree(PrPool *pool, void *slot);
|
||||||
|
|
||||||
|
// Free all blocks and zero the pool.
|
||||||
|
void prPoolDestroy(PrPool *pool);
|
||||||
|
|
||||||
|
// Diagnostics
|
||||||
|
u64 prPoolTotalSlots(const PrPool *pool);
|
||||||
|
u64 prPoolActiveSlots(const PrPool *pool);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 Behavior
|
||||||
|
|
||||||
|
| Operation | Implementation |
|
||||||
|
|-----------|---------------|
|
||||||
|
| `prPoolAlloc` | Pop from free list if non-empty, otherwise allocate a new block of `block_slots` slots via wapp OS allocation, link it into the `blocks` array, carve it into the free list, and pop. |
|
||||||
|
| `prPoolFree` | Push slot onto the intrusive free list. Safe no-op on NULL. |
|
||||||
|
| `prPoolDestroy` | Free every block in the `blocks` array, free the array itself, zero the struct. |
|
||||||
|
|
||||||
|
Block growth: each new block has `block_slots` slots (same size as the initial
|
||||||
|
block). The minimum block size is 4096 bytes — if `slot_size * initial_slots`
|
||||||
|
is smaller, `block_slots` is rounded up to the nearest multiple of `slot_size`
|
||||||
|
that meets the minimum. The `blocks` array starts at capacity 4 and doubles
|
||||||
|
when full.
|
||||||
|
|
||||||
|
### 1.5 Usage examples
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Edge pool (replaces PrPool in scratchpad/dag.c):
|
||||||
|
PrPool edge_pool;
|
||||||
|
prPoolInit(&edge_pool, sizeof(PrGraphEdge), 64);
|
||||||
|
PrGraphEdge *edge = prPoolAlloc(&edge_pool);
|
||||||
|
prPoolFree(&edge_pool, edge);
|
||||||
|
prPoolDestroy(&edge_pool);
|
||||||
|
|
||||||
|
// Texture slot pool:
|
||||||
|
PrPool tex_pool;
|
||||||
|
prPoolInit(&tex_pool, sizeof(PrTextureSlot), 16);
|
||||||
|
PrTextureSlot *slot = prPoolAlloc(&tex_pool);
|
||||||
|
prPoolDestroy(&tex_pool);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Texture Pool
|
||||||
|
|
||||||
|
### 2.1 Purpose
|
||||||
|
|
||||||
|
Intermediate textures (node outputs) need GPU resources. The texture pool manages
|
||||||
|
a set of textures that are reused across graph evaluations. Without a pool, a
|
||||||
|
linear chain of N nodes would need N textures. With refcount-based reuse,
|
||||||
|
textures are returned to the pool as soon as all their consumers have executed,
|
||||||
|
keeping the peak live count low.
|
||||||
|
|
||||||
|
### 2.2 Data structures
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct PrTextureSlot {
|
||||||
|
PrRhiTexture *texture; // the GPU texture (SAMPLED | COLOR_ATTACHMENT)
|
||||||
|
u32 refcount; // how many downstream nodes still need to read this
|
||||||
|
b8 in_use; // currently assigned to a node's output
|
||||||
|
} PrTextureSlot;
|
||||||
|
|
||||||
|
typedef struct PrTexturePool {
|
||||||
|
PrPool slot_pool; // pool allocator for PrTextureSlot structs
|
||||||
|
PrTextureSlot *slots; // flat array for iteration (backed by slot_pool)
|
||||||
|
u32 count; // number of allocated slots
|
||||||
|
u32 max; // hard cap (never allocate beyond this)
|
||||||
|
u32 width; // texture width (matches window)
|
||||||
|
u32 height; // texture height (matches window)
|
||||||
|
} PrTexturePool;
|
||||||
|
```
|
||||||
|
|
||||||
|
All pool textures are **RGBA16F, SAMPLED | COLOR_ATTACHMENT**. Any free slot works
|
||||||
|
for any node — no format/dimension matching needed.
|
||||||
|
|
||||||
|
The `slot_pool` is a `PrPool` allocator for `PrTextureSlot` structs. The `slots`
|
||||||
|
pointer provides flat-array access for iteration during evaluation. When the pool
|
||||||
|
grows, a new batch of slots is allocated via the pool allocator and the flat
|
||||||
|
array is extended.
|
||||||
|
|
||||||
|
### 2.3 Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
prTexturePoolInit(pool, device, initial_capacity, max, width, height)
|
||||||
|
→ creates pool allocator, allocates initial slot array
|
||||||
|
|
||||||
|
prTexturePoolReset(pool)
|
||||||
|
→ marks all slots as free, zeroes refcounts (called once per frame)
|
||||||
|
|
||||||
|
prTexturePoolAcquire(pool, device) -> PrTextureSlot*
|
||||||
|
→ returns a free slot (in_use = true)
|
||||||
|
→ if no free slot: allocate new slot + GPU texture, grow array
|
||||||
|
→ if max reached: abort with diagnostic message
|
||||||
|
|
||||||
|
prTexturePoolRelease(pool, slot)
|
||||||
|
→ marks slot as free (in_use = false)
|
||||||
|
→ called when refcount hits 0
|
||||||
|
|
||||||
|
prTexturePoolDestroy(pool, device)
|
||||||
|
→ destroys all GPU textures, destroys pool allocator
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Allocation strategy (growth)
|
||||||
|
|
||||||
|
The pool does **not** pre-allocate all textures upfront. Instead:
|
||||||
|
|
||||||
|
1. Start with `initial_capacity` textures (e.g., 16)
|
||||||
|
2. When all slots are occupied and a new one is needed, allocate a batch of
|
||||||
|
`GROWTH_BATCH` (e.g., 8) additional textures
|
||||||
|
3. Never exceed `max` (e.g., 128)
|
||||||
|
4. If `max` is reached, abort with: `"texture pool exhausted: N in use, max M"`
|
||||||
|
|
||||||
|
Growth is amortized (batch allocation) and the pool never shrinks. The `count`
|
||||||
|
monotonically increases as textures are allocated on demand.
|
||||||
|
|
||||||
|
**Why growth instead of fixed pre-allocation:**
|
||||||
|
- Small graphs don't pay for 64 unused textures
|
||||||
|
- Complex graphs can grow beyond the initial allocation
|
||||||
|
- The hard cap prevents unbounded memory use
|
||||||
|
- vkCreateImage is only called when actually needed
|
||||||
|
|
||||||
|
### 2.5 Refcount management
|
||||||
|
|
||||||
|
Before evaluation, compute the **initial refcount** for each node's output:
|
||||||
|
|
||||||
|
```
|
||||||
|
refcount[node] = out_degree(node) // number of outgoing edges
|
||||||
|
```
|
||||||
|
|
||||||
|
During evaluation, when a node executes and reads an input texture:
|
||||||
|
```
|
||||||
|
input_slot->refcount -= 1
|
||||||
|
if (input_slot->refcount == 0):
|
||||||
|
prTexturePoolRelease(pool, input_slot)
|
||||||
|
```
|
||||||
|
|
||||||
|
This naturally handles:
|
||||||
|
- **Linear chains**: A→B→C. A's output refcount=1, freed after B executes.
|
||||||
|
- **Fan-out**: A→B, A→C. A's output refcount=2, freed after both B and C execute.
|
||||||
|
- **Fan-in**: B→D, C→D. B and C have independent refcounts, freed independently.
|
||||||
|
|
||||||
|
### 2.6 Texture dimensions
|
||||||
|
|
||||||
|
Pool textures are created at the **window/swapchain resolution**. All nodes
|
||||||
|
operate at this resolution. If a node needs a different resolution (e.g., a
|
||||||
|
half-resolution blur), it would need a separate mechanism — out of scope for V1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Node-to-Shader Mapping
|
||||||
|
|
||||||
|
### 3.1 Type registry
|
||||||
|
|
||||||
|
A static table maps `PrNodeType` → shader modules + pipeline + resource
|
||||||
|
signatures:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum PrShaderType {
|
||||||
|
PR_SHADER_TYPE_FRAGMENT, // fullscreen triangle, per-pixel
|
||||||
|
PR_SHADER_TYPE_COMPUTE, // dispatch, shared memory
|
||||||
|
} PrShaderType;
|
||||||
|
|
||||||
|
typedef struct PrNodeTypeEntry {
|
||||||
|
PrNodeType type;
|
||||||
|
PrShaderType shader_type;
|
||||||
|
|
||||||
|
// shaders (pre-compiled SPIR-V, built from .slang via slangc)
|
||||||
|
const char *vertex_shader_path; // NULL for compute
|
||||||
|
const char *fragment_shader_path; // NULL for compute
|
||||||
|
const char *compute_shader_path; // NULL for fragment
|
||||||
|
|
||||||
|
// pipeline (created at init, cached here)
|
||||||
|
PrRhiPipeline *pipeline;
|
||||||
|
|
||||||
|
// resource signature
|
||||||
|
u32 input_count; // number of texture inputs (1 for blur, 2 for blend)
|
||||||
|
u32 output_count; // always 1 for V1
|
||||||
|
|
||||||
|
// descriptor set layout (created at init)
|
||||||
|
PrRhiDescriptorSetLayout *set_layout;
|
||||||
|
|
||||||
|
// push constant size (bytes)
|
||||||
|
u32 push_constant_size;
|
||||||
|
} PrNodeTypeEntry;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Registry instance
|
||||||
|
|
||||||
|
```c
|
||||||
|
wp_persist PrNodeTypeEntry _node_type_table[COUNT_NODE_TYPES] = {
|
||||||
|
[PR_NODE_TYPE_READ] = {
|
||||||
|
.type = PR_NODE_TYPE_READ,
|
||||||
|
.shader_type = PR_SHADER_TYPE_FRAGMENT,
|
||||||
|
.vertex_shader_path = "assets/shaders/blit.vert.spv",
|
||||||
|
.fragment_shader_path= "assets/shaders/read.frag.spv",
|
||||||
|
.input_count = 0,
|
||||||
|
.output_count = 1,
|
||||||
|
.push_constant_size = 0,
|
||||||
|
},
|
||||||
|
[PR_NODE_TYPE_BLUR] = {
|
||||||
|
.type = PR_NODE_TYPE_BLUR,
|
||||||
|
.shader_type = PR_SHADER_TYPE_FRAGMENT,
|
||||||
|
.vertex_shader_path = "assets/shaders/blit.vert.spv",
|
||||||
|
.fragment_shader_path= "assets/shaders/blur.frag.spv",
|
||||||
|
.input_count = 1,
|
||||||
|
.output_count = 1,
|
||||||
|
.push_constant_size = sizeof(PrBlurPushConstants),
|
||||||
|
},
|
||||||
|
[PR_NODE_TYPE_GRADE] = {
|
||||||
|
.type = PR_NODE_TYPE_GRADE,
|
||||||
|
.shader_type = PR_SHADER_TYPE_FRAGMENT,
|
||||||
|
.vertex_shader_path = "assets/shaders/blit.vert.spv",
|
||||||
|
.fragment_shader_path= "assets/shaders/grade.frag.spv",
|
||||||
|
.input_count = 1,
|
||||||
|
.output_count = 1,
|
||||||
|
.push_constant_size = sizeof(PrGradePushConstants),
|
||||||
|
},
|
||||||
|
[PR_NODE_TYPE_BLEND] = {
|
||||||
|
.type = PR_NODE_TYPE_BLEND,
|
||||||
|
.shader_type = PR_SHADER_TYPE_FRAGMENT,
|
||||||
|
.vertex_shader_path = "assets/shaders/blit.vert.spv",
|
||||||
|
.fragment_shader_path= "assets/shaders/blend.frag.spv",
|
||||||
|
.input_count = 2,
|
||||||
|
.output_count = 1,
|
||||||
|
.push_constant_size = sizeof(PrBlendPushConstants),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Shader loading
|
||||||
|
|
||||||
|
Shaders are written in Slang (`src/shaders/*.slang`) and compiled to SPIR-V as
|
||||||
|
a build step via `slangc`. The `.spv` files are output to `assets/shaders/`. At
|
||||||
|
init, the application loads pre-compiled SPIR-V directly:
|
||||||
|
|
||||||
|
```
|
||||||
|
for each entry in _node_type_table:
|
||||||
|
load vertex shader SPIR-V from .spv file
|
||||||
|
load fragment/compute shader SPIR-V from .spv file
|
||||||
|
create PrRhiShader handles
|
||||||
|
create descriptor set layout (input_count combined image samplers)
|
||||||
|
create pipeline layout (set layout + push constant range)
|
||||||
|
create pipeline (vertex + fragment stages, dynamic rendering)
|
||||||
|
cache everything in the entry
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Shaders per node type
|
||||||
|
|
||||||
|
| Node | Shader | Inputs | Push constants |
|
||||||
|
|------|--------|--------|----------------|
|
||||||
|
| READ | `read.frag.spv` | 0 (samples from KTX texture loaded separately) | — |
|
||||||
|
| BLUR | `blur.frag.spv` | 1 input texture | `f32 radius` |
|
||||||
|
| GRADE | `grade.frag.spv` | 1 input texture | `f32 gain, f32 lift, f32 gamma` |
|
||||||
|
| BLEND | `blend.frag.spv` | 2 input textures | `u32 mode` (over/under/add) |
|
||||||
|
|
||||||
|
All share `blit.vert.spv` (fullscreen triangle, no vertex buffer needed).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Evaluation Loop
|
||||||
|
|
||||||
|
### 4.1 Per-frame sequence
|
||||||
|
|
||||||
|
```
|
||||||
|
prGraphEvaluate(graph, device, pool, cb, swapchain_texture):
|
||||||
|
1. topo_order = prGraphTopologicalSort(graph)
|
||||||
|
|
||||||
|
2. // compute initial refcounts
|
||||||
|
for each node in graph:
|
||||||
|
node.output_refcount = out_degree(node)
|
||||||
|
|
||||||
|
3. prTexturePoolReset(pool)
|
||||||
|
|
||||||
|
4. // reset per-frame descriptor pool (allocated once at init, reset each frame)
|
||||||
|
prRhiResetDescriptorPool(device, desc_pool)
|
||||||
|
|
||||||
|
5. for each node_id in topo_order:
|
||||||
|
node = &nodes[node_id]
|
||||||
|
entry = &_node_type_table[node->type]
|
||||||
|
|
||||||
|
// acquire output texture from pool
|
||||||
|
output_slot = prTexturePoolAcquire(pool, device)
|
||||||
|
|
||||||
|
// gather input textures (from upstream nodes' output slots)
|
||||||
|
input_count = 0
|
||||||
|
input_slots[4] // max 4 inputs
|
||||||
|
for each upstream edge (upstream → node):
|
||||||
|
input_slots[input_count++] = upstream.output_slot
|
||||||
|
|
||||||
|
// allocate and update descriptor set
|
||||||
|
desc_set = prRhiAllocateDescriptorSet(device, desc_pool, entry->set_layout)
|
||||||
|
|
||||||
|
writes = stack_array(input_count)
|
||||||
|
for i in 0..input_count:
|
||||||
|
writes[i] = {
|
||||||
|
.dst_set = desc_set,
|
||||||
|
.dst_binding = i,
|
||||||
|
.dst_array_element = 0,
|
||||||
|
.type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
|
.image_info = &(PrRhiDescriptorImageInfo){
|
||||||
|
.texture = input_slots[i]->texture,
|
||||||
|
.sampler = shared_sampler,
|
||||||
|
.layout = PR_RHI_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
prRhiUpdateDescriptorSet(device, writes)
|
||||||
|
|
||||||
|
// record commands
|
||||||
|
prRhiCmdBeginRendering(cb, output_slot->texture, ...)
|
||||||
|
prRhiCmdBindPipeline(cb, GRAPHICS, entry->pipeline)
|
||||||
|
prRhiCmdBindDescriptorSets(cb, GRAPHICS, entry->pipeline_layout, 0, 1, &desc_set, 0, NULL)
|
||||||
|
prRhiCmdPushConstants(cb, ..., node->params)
|
||||||
|
prRhiCmdDraw(cb, 3, 1, 0, 0) // fullscreen triangle
|
||||||
|
prRhiCmdEndRendering(cb)
|
||||||
|
|
||||||
|
// release input textures whose refcount hit 0
|
||||||
|
for each input_slot:
|
||||||
|
input_slot->refcount -= 1
|
||||||
|
if input_slot->refcount == 0:
|
||||||
|
prTexturePoolRelease(pool, input_slot)
|
||||||
|
|
||||||
|
// store output slot on node for downstream consumers
|
||||||
|
node->output_slot = output_slot
|
||||||
|
|
||||||
|
6. // final blit to swapchain
|
||||||
|
final_slot = last_node.output_slot
|
||||||
|
blit final_slot->texture → swapchain_texture
|
||||||
|
|
||||||
|
7. prRhiQueueSubmit(cb)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 READ node special case
|
||||||
|
|
||||||
|
READ nodes load a texture from disk (KTX) via `prRhiCreateTextureFromKtx`.
|
||||||
|
The loaded texture is stored directly on the node (persistent, lives across
|
||||||
|
frames). Unlike other nodes, READ's input comes from this persistent texture
|
||||||
|
rather than from an upstream node's output slot.
|
||||||
|
|
||||||
|
READ nodes still render a fullscreen triangle that samples from the loaded
|
||||||
|
texture and writes to the output pool texture. This allows the user to view
|
||||||
|
the raw texture before any modifications, and ensures READ nodes participate
|
||||||
|
uniformly in the evaluation pipeline.
|
||||||
|
|
||||||
|
READ nodes participate in refcount tracking like any other node: their output
|
||||||
|
slot's refcount is set to `out_degree(READ)`, and downstream consumers
|
||||||
|
decrement it normally.
|
||||||
|
|
||||||
|
### 4.3 Barrier insertion
|
||||||
|
|
||||||
|
Between nodes that share a texture (one writes, next reads), a pipeline barrier
|
||||||
|
is needed to transition the texture layout:
|
||||||
|
|
||||||
|
```
|
||||||
|
after node A executes (writes to texture T):
|
||||||
|
barrier: T from COLOR_ATTACHMENT → SHADER_READ_ONLY
|
||||||
|
|
||||||
|
before node B executes (reads texture T):
|
||||||
|
(barrier already inserted above)
|
||||||
|
```
|
||||||
|
|
||||||
|
In practice, the barrier is inserted **after** each node's render pass:
|
||||||
|
- Transition the output texture from `COLOR_ATTACHMENT_OPTIMAL` to
|
||||||
|
`SHADER_READ_ONLY_OPTIMAL`
|
||||||
|
|
||||||
|
The **first** node in a chain (READ) needs a transition from `TRANSFER_DST` to
|
||||||
|
`SHADER_READ_ONLY` after loading from disk. This is already handled by
|
||||||
|
`prRhiCreateTextureFromKtx`.
|
||||||
|
|
||||||
|
Layout transitions per node:
|
||||||
|
```
|
||||||
|
READ: UNDEFINED → TRANSFER_DST → SHADER_READ_ONLY (done by KTX loader)
|
||||||
|
BLUR: SHADER_READ_ONLY (input) → COLOR_ATTACHMENT (output, during render)
|
||||||
|
output transitions to SHADER_READ_ONLY after render pass
|
||||||
|
GRADE: same as BLUR
|
||||||
|
BLEND: same as BLUR (two inputs)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Descriptor management
|
||||||
|
|
||||||
|
Each node needs a descriptor set binding its input textures. The flow:
|
||||||
|
|
||||||
|
**Init (once):**
|
||||||
|
- Create a **per-node-type descriptor set layout** with `input_count` combined
|
||||||
|
image sampler bindings. Stored in `PrNodeTypeEntry.set_layout`.
|
||||||
|
- Create a **persistent descriptor pool** large enough for the worst-case node
|
||||||
|
count (e.g., 128 sets). Created once, reused every frame.
|
||||||
|
|
||||||
|
**Per frame:**
|
||||||
|
1. Reset the descriptor pool via `prRhiResetDescriptorPool`. This is much
|
||||||
|
cheaper than create/destroy — it reuses the pool's internal memory.
|
||||||
|
2. For each node during evaluation:
|
||||||
|
- Allocate a descriptor set from the pool using the node type's layout.
|
||||||
|
- Write each input texture into the set via `prRhiUpdateDescriptorSet`.
|
||||||
|
Each write specifies:
|
||||||
|
- `dst_set` / `dst_binding` — which set and binding index
|
||||||
|
- `type` — `PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`
|
||||||
|
- `image_info` — texture handle, shared sampler, layout
|
||||||
|
- Bind the set during rendering via `prRhiCmdBindDescriptorSets`.
|
||||||
|
|
||||||
|
The pool lives for the lifetime of the application. Only its contents are
|
||||||
|
reset each frame.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. File layout
|
||||||
|
|
||||||
|
```
|
||||||
|
src/prism/allocators/
|
||||||
|
└── pr_pool_allocator.h / .c ← pool allocator (self-managing, wapp OS allocation)
|
||||||
|
|
||||||
|
src/prism/core/
|
||||||
|
├── pr_graph.h / .c ← promoted from scratchpad/dag.c
|
||||||
|
├── pr_node.h / .c ← PrNode, PrNodeType, PrNodeManager
|
||||||
|
├── pr_texture_pool.h / .c ← PrTexturePool
|
||||||
|
└── pr_node_eval.h / .c ← evaluation loop, type registry
|
||||||
|
|
||||||
|
src/shaders/ ← Slang source (compiled to assets/shaders/ via slangc)
|
||||||
|
├── blit.vert.slang ← fullscreen triangle (shared by all fragment nodes)
|
||||||
|
├── read.frag.slang ← passthrough (samples loaded texture)
|
||||||
|
├── blur.frag.slang ← gaussian blur
|
||||||
|
├── grade.frag.slang ← colour grading
|
||||||
|
└── blend.frag.slang ← alpha compositing
|
||||||
|
|
||||||
|
assets/shaders/ ← compiled SPIR-V output (loaded at runtime)
|
||||||
|
├── blit.vert.spv
|
||||||
|
├── read.frag.spv
|
||||||
|
├── blur.frag.spv
|
||||||
|
├── grade.frag.spv
|
||||||
|
└── blend.frag.spv
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Implementation order
|
||||||
|
|
||||||
|
1. **Pool allocator**: Implement `PrPool` in `src/prism/allocators/`.
|
||||||
|
`prPoolInit`, `prPoolAlloc`, `prPoolFree`, `prPoolDestroy`. Self-managing
|
||||||
|
growth via wapp OS allocation. Replace the ad-hoc `PrPool` in scratchpad/dag.c.
|
||||||
|
|
||||||
|
2. **Promote graph to production**: Move `PrGraph`, `PrNodeManager`, topology
|
||||||
|
ops from `scratchpad/dag.c` to `src/prism/core/pr_graph.h/.c` and
|
||||||
|
`pr_node.h/.c`. Clean up — remove the `main()` test harness.
|
||||||
|
|
||||||
|
3. **Define node type registry**: Create `PrNodeTypeEntry` table with resource
|
||||||
|
signatures (input_count, output_count, push_constant_size). No shaders yet.
|
||||||
|
|
||||||
|
4. **Implement PrTexturePool**: Growth-based pool with refcount tracking.
|
||||||
|
`prTexturePoolInit`, `prTexturePoolReset`, `prTexturePoolAcquire`,
|
||||||
|
`prTexturePoolRelease`, `prTexturePoolDestroy`.
|
||||||
|
|
||||||
|
5. **Write blit.vert.slang**: Fullscreen triangle, no vertex buffer. Shared by
|
||||||
|
all fragment-shader nodes. Compile to SPIR-V via `slangc`.
|
||||||
|
|
||||||
|
6. **Write initial frag shaders**: `read.frag.slang`, `blur.frag.slang`,
|
||||||
|
`grade.frag.slang`, `blend.frag.slang`. Simple per-pixel operations.
|
||||||
|
Compile to SPIR-V via `slangc`.
|
||||||
|
|
||||||
|
7. **Wire up shader loading + pipeline creation**: At init, load pre-compiled
|
||||||
|
SPIR-V from `assets/shaders/`, create descriptor set layouts, pipeline
|
||||||
|
layouts, pipelines. Cache in the type registry.
|
||||||
|
|
||||||
|
8. **Implement evaluation loop**: `prGraphEvaluate` — topo sort, refcount
|
||||||
|
compute, pool reset, per-node dispatch, barrier insertion, final blit to
|
||||||
|
swapchain.
|
||||||
|
|
||||||
|
9. **Integrate with main loop**: Replace the current mesh-rendering demo with
|
||||||
|
a node graph evaluation. Create a test graph (Read→Blur→Blend) and render
|
||||||
|
it to the swapchain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Decisions
|
||||||
|
|
||||||
|
- **Pool allocator**: Self-contained `PrPool` with standalone API. No external
|
||||||
|
allocator parameter — pool allocates blocks via wapp OS allocation (`wpOsMemAlloc`
|
||||||
|
/ `wpOsMemFree`) and grows on demand. Handles slot sizes smaller than
|
||||||
|
`sizeof(void*)` transparently via an internal `alloc_size`. Lives in
|
||||||
|
`src/prism/allocators/`, outside vendored wapp.
|
||||||
|
|
||||||
|
- **READ node texture lifetime**: READ nodes hold a persistent `PrRhiTexture`
|
||||||
|
(loaded via `prRhiCreateTextureFromKtx`) outside the pool. The pool slot's
|
||||||
|
`texture` pointer references this persistent texture. This means READ nodes
|
||||||
|
don't consume pool slots — they just participate in refcount tracking.
|
||||||
|
|
||||||
|
- **Sampler**: Single shared sampler (linear filtering, clamp-to-edge) for all
|
||||||
|
nodes in V1. Created once at init.
|
||||||
|
|
||||||
|
- **Push constant layout**: Each node type defines its own push constant struct.
|
||||||
|
The evaluation loop reads the node's params union and passes it via
|
||||||
|
`prRhiCmdPushConstants`. The shader declares matching layout.
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# Plan — Fullscreen Texture Blit with Contain-Fit
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Render a texture fullscreen with aspect-ratio preservation. If the texture's
|
||||||
|
aspect ratio does not match the window's, draw it to fit within the window —
|
||||||
|
never stretched, squashed, or cropped. Tall/narrow textures pillarbox (bars on
|
||||||
|
left/right); wide textures letterbox (bars on top/bottom).
|
||||||
|
|
||||||
|
This replaces the Suzanne mesh demo in `main.cpp`, which was for testing the RHI.
|
||||||
|
|
||||||
|
## Fit semantics (contain — never crop)
|
||||||
|
|
||||||
|
```
|
||||||
|
scale = min(win_w / tex_w, win_h / tex_h)
|
||||||
|
content_w = tex_w * scale
|
||||||
|
content_h = tex_h * scale
|
||||||
|
rect = centered: left = (win_w - content_w)/2, top = (win_h - content_h)/2
|
||||||
|
```
|
||||||
|
|
||||||
|
| Texture vs window aspect | Limiting dim | Result |
|
||||||
|
|--------------------------|--------------|-------------------|
|
||||||
|
| equal | — | fills exactly |
|
||||||
|
| wider (e.g. 2048x512) | width | letterbox (T/B) |
|
||||||
|
| taller (e.g. 512x2048) | height | pillarbox (L/R) |
|
||||||
|
|
||||||
|
The fit rect is recomputed per frame from the selected texture's dimensions and
|
||||||
|
the current window size, so window resize works without extra handling.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### 1. RHI — texture size accessor
|
||||||
|
|
||||||
|
Add a value struct and a by-value getter (matches `prRhiGetSurfaceCapabilities`
|
||||||
|
pattern):
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct PrRhiTextureSize {
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
} PrRhiTextureSize;
|
||||||
|
|
||||||
|
PrRhiTextureSize prRhiGetTextureSize(PrRhiTexture *texture);
|
||||||
|
```
|
||||||
|
|
||||||
|
Files: `pr_rhi_types.h`, `pr_rhi.h`, `vulkan/pr_rhi_vk.h`,
|
||||||
|
`vulkan/pr_rhi_vk.c`, `vulkan/pr_rhi_vk_aliases.h`.
|
||||||
|
|
||||||
|
### 2. Offline shader compilation (drop Slang runtime)
|
||||||
|
|
||||||
|
- New `justfile` `shaders` recipe:
|
||||||
|
`slangc -target spirv -profile spirv_1_4 -o build/shaders/blit.spv assets/blit.slang`
|
||||||
|
(entry points auto-detected from `[shader(...)]` attributes).
|
||||||
|
`build` depends on it.
|
||||||
|
- `main.cpp` loads `build/shaders/blit.spv` via wapp file I/O
|
||||||
|
(`wpFileOpen` / `wpFileGetLength` / `wpFileRead`) into an arena buffer, then
|
||||||
|
`prRhiCreateShader`. No `slang.h` includes, no runtime compilation.
|
||||||
|
- Drop `-lslang` and the `-I .../slang` include from the build.
|
||||||
|
|
||||||
|
### 3. `assets/blit.slang`
|
||||||
|
|
||||||
|
Push constant block (32 bytes — Slang pads structs to 16-byte alignment, so the
|
||||||
|
C++ struct carries explicit `pad[3]` to match):
|
||||||
|
|
||||||
|
```hlsl
|
||||||
|
struct BlitData {
|
||||||
|
float4 rect; // NDC fit rect: x0, y0, x1, y1
|
||||||
|
uint selected;
|
||||||
|
uint pad[3];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- **VS**: generates a 4-vertex triangle-strip quad from `SV_VertexID` (no vertex
|
||||||
|
buffer), maps UV 0–1 into the NDC rect.
|
||||||
|
- **FS**: `textures[NonUniformResourceIndex(selected)].Sample(uv)` — reuses the
|
||||||
|
existing bindless descriptor array.
|
||||||
|
|
||||||
|
### 4. `main.cpp` — clean texture viewer
|
||||||
|
|
||||||
|
Remove everything mesh-related: tinyobj loading, vertex/index buffers,
|
||||||
|
`ShaderData` storage buffers / device addresses, the mesh shader + pipeline,
|
||||||
|
mouse orbit, and the mesh draw. Drop `-ltinyobjloader -lglm` from the link.
|
||||||
|
Remove unused `assets/shader.slang`, `suzanne.obj`, `suzanne.mtl`.
|
||||||
|
|
||||||
|
New flow: RHI init → window/instance/pdev/surface/device/swapchain → load 7
|
||||||
|
textures → bindless descriptor set (variable count 7) → load `blit.spv` →
|
||||||
|
blit pipeline layout (`VERTEX|FRAGMENT` 32-byte push range) → blit pipeline
|
||||||
|
(no vertex input, `TRIANGLE_STRIP`, swapchain color format, no depth,
|
||||||
|
`cull NONE`, dynamic viewport/scissor).
|
||||||
|
|
||||||
|
Render loop per frame:
|
||||||
|
- `compute_fit_rect()` from the selected texture's dims + window size
|
||||||
|
- bind blit pipeline + descriptor set, push `BlitData{ rect, selected }`,
|
||||||
|
`prRhiCmdDraw(cb, 4, 1, 0, 0)`
|
||||||
|
- `+/-` cycles the selected texture; resize recomputes fit automatically
|
||||||
|
|
||||||
|
### 5. Test textures (PIL + `build/bin/toktx`)
|
||||||
|
|
||||||
|
Four generated KTX files in `assets/`, loaded alongside the 3 Suzanne textures
|
||||||
|
(`texture_count = 7`, explicit path array):
|
||||||
|
|
||||||
|
| File | Size | Shows |
|
||||||
|
|-----------------|-----------|--------------------------|
|
||||||
|
| `test_square.ktx` | 1024x1024 | bars on both axes |
|
||||||
|
| `test_fill.ktx` | 1920x1080 | fills the 16:9 window |
|
||||||
|
| `test_wide.ktx` | 2048x512 | letterbox (T/B) |
|
||||||
|
| `test_tall.ktx` | 512x2048 | pillarbox (L/R) |
|
||||||
|
|
||||||
|
Each with distinct gradients + a border grid so any stretch/squash is visible.
|
||||||
|
Generated with `--genmipmap` for mip-aware sampling.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`just build && just run` — each texture fits without crop/stretch, `+/-` cycles,
|
||||||
|
window resize keeps fit, no Slang runtime in the binary.
|
||||||
|
|
||||||
|
## Verification results (2026-08-08)
|
||||||
|
|
||||||
|
Fit behavior confirmed by capturing the window (X11 driver) and sampling pixels:
|
||||||
|
|
||||||
|
| Texture | Window | Result verified |
|
||||||
|
|---------------------|---------------|------------------------------------------------|
|
||||||
|
| square 1024x1024 | 16:9 wide | pillarbox — pure-black L/R bars, full height |
|
||||||
|
| test_wide 2048x512 | 16:9 wide | letterbox — pure-black T/B bars, full width |
|
||||||
|
| test_tall 512x2048 | 16:9 wide | pillarbox — pure-black L/R bars, full height |
|
||||||
|
| test_fill 1920x1080 | 16:9 wide | fills exactly — no bars anywhere |
|
||||||
|
| square (resized) | portrait | fit recomputed per frame — flips to letterbox |
|
||||||
|
|
||||||
|
Texture cycling (`+/-`, `SDLK_PLUS/KP_PLUS/EQUALS`, `SDLK_MINUS/KP_MINUS`) is a
|
||||||
|
straightforward `selected` bump in the key handler; it was reviewed but not
|
||||||
|
exercise-tested in the headless verification env (KWin/XWayland drops XTEST
|
||||||
|
synthesised keys). The other fit cases were verified by temporarily launching
|
||||||
|
each texture as the initial selection.
|
||||||
|
|
||||||
|
### Bugs found and fixed during verification
|
||||||
|
|
||||||
|
1. **`VK_SUBOPTIMAL_KHR` aborted the app.** `prRhiAcquireNextImageVk` /
|
||||||
|
`prRhiPresentVk` routed `SUBOPTIMAL` into `_checkVk` → `__builtin_trap()`.
|
||||||
|
Both now treat it like `OUT_OF_DATE` (return `PR_RHI_SWAPCHAIN_OUT_OF_DATE`).
|
||||||
|
Triggered immediately under X11/XWayland.
|
||||||
|
2. **Swapchain recreate ignored surface extent.** `prRhiRecreateSwapchainVk`
|
||||||
|
hard-coded the passed width/height; on surfaces whose `currentExtent` is
|
||||||
|
meaningful (X11) that mismatched the drawable and re-looped on SUBOPTIMAL.
|
||||||
|
Now queries `vkGetPhysicalDeviceSurfaceCapabilitiesKHR` and falls back to the
|
||||||
|
passed size only when `currentExtent == 0xFFFFFFFF`.
|
||||||
|
3. **App used logical window size for the swapchain.** Under HiDPI the drawable
|
||||||
|
differs from `SDL_GetWindowSize` (1920x1080 logical → 2400x1350 drawable at
|
||||||
|
1.25x scale), which is the root cause of #1 under XWayland. main.cpp now uses
|
||||||
|
`SDL_GetWindowSizeInPixels` for swapchain width/height, fit math, viewport
|
||||||
|
and scissor.
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
# Shader Architecture Patterns for Node-Based Image Compositing
|
||||||
|
|
||||||
|
Research conducted 2026-07-13.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Single Shader vs Multiple Shaders
|
||||||
|
|
||||||
|
### How Professional Compositors Handle It
|
||||||
|
|
||||||
|
**Blender Compositor (GPU backend)** — The most relevant case study:
|
||||||
|
- Blender's GPU compositor collapses multiple connected nodes into a **"compile unit"** and generates a **single compute shader** per unit.
|
||||||
|
- The `ShaderOperation` class iterates through a `compile_unit_` (a set of nodes) and links their GLSL logic into one shader: `source/blender/compositor/intern/shader_operation.cc:122-135`.
|
||||||
|
- Simple per-pixel operations (Math, Color Mix, Invert, etc.) are fused into a single pass. Operations that can't be expressed as shaders fall back to `MultiFunctionProcedureOperation` on CPU.
|
||||||
|
- **Key insight**: Blender uses a **hybrid approach** — fuse what you can into single shaders, fall back to separate passes for complex operations (blur, glare, convolution).
|
||||||
|
|
||||||
|
**Natron** — CPU-based compositor using OpenFX plugins:
|
||||||
|
- Each node is a separate processing unit (separate plugin call).
|
||||||
|
- Multi-threaded tile-based processing per node.
|
||||||
|
- Not GPU-accelerated; no shader fusion.
|
||||||
|
|
||||||
|
**DaVinci Resolve / Fusion** — Proprietary:
|
||||||
|
- Uses a node graph where each node can have internal multi-pass processing.
|
||||||
|
- Fusion's "Flow Region" system groups nodes for optimization.
|
||||||
|
- Effectively separate shaders per node, with internal optimization.
|
||||||
|
|
||||||
|
### Recommended Approach for Prism
|
||||||
|
|
||||||
|
**Use separate shaders per node, with optional fusion of simple nodes.** Rationale:
|
||||||
|
- Nodes in a compositing graph have diverse operations (blur vs. blend vs. color grade). An uber-shader would have massive register pressure and poor occupancy.
|
||||||
|
- Simple per-pixel operations (math, color mix, gamma) can be fused into chains as an optimization.
|
||||||
|
- Complex operations (blur, convolutions, warps) need their own shader passes anyway.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Texture Ping-Ponging
|
||||||
|
|
||||||
|
### The Pattern
|
||||||
|
|
||||||
|
Texture ping-ponging is the fundamental technique for chaining GPU image operations:
|
||||||
|
|
||||||
|
1. Allocate two textures (A and B) at the target resolution.
|
||||||
|
2. Bind texture A as input, render to texture B.
|
||||||
|
3. Swap: bind texture B as input, render to texture A.
|
||||||
|
4. Repeat for as many passes as needed.
|
||||||
|
|
||||||
|
```
|
||||||
|
Pass 1: Read(A) → Write(B) [e.g., blur]
|
||||||
|
Pass 2: Read(B) → Write(A) [e.g., color grade]
|
||||||
|
Pass 3: Read(A) → Write(B) [e.g., blend]
|
||||||
|
Final: Display(B)
|
||||||
|
```
|
||||||
|
|
||||||
|
### How It Works in Practice
|
||||||
|
|
||||||
|
**WebGL/Fragment Shader approach** (from multiple sources):
|
||||||
|
- Create Framebuffer Objects (FBOs) with texture attachments.
|
||||||
|
- Bind FBO → render fullscreen quad → output goes to texture.
|
||||||
|
- Bind different FBO or default framebuffer → read from that texture.
|
||||||
|
|
||||||
|
**Vulkan approach**:
|
||||||
|
- Use `VkImage` objects as both sampler inputs and render targets.
|
||||||
|
- Between passes, issue a pipeline barrier (`VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT → VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT`).
|
||||||
|
- Manage image layouts: `SHADER_READ_ONLY_OPTIMAL` → `COLOR_ATTACHMENT_OPTIMAL` → `SHADER_READ_ONLY_OPTIMAL`.
|
||||||
|
|
||||||
|
**Metal approach** (from Kosikowski's article):
|
||||||
|
- Compute shaders read from `inTexture` and write to `outTexture`.
|
||||||
|
- Swap the texture references between passes.
|
||||||
|
|
||||||
|
### Important Considerations
|
||||||
|
|
||||||
|
- **Image layout transitions** are critical in Vulkan. Each pass requires the texture to be in the correct layout.
|
||||||
|
- **Load/store ops**: For intermediate textures, use `VK_ATTACHMENT_LOAD_OP_DONT_CARE` and `VK_ATTACHMENT_STORE_OP_DONT_CARE` when contents aren't needed — saves bandwidth.
|
||||||
|
- **Resolution management**: Different nodes may operate at different resolutions. The compositor must manage a texture pool and handle up/downsampling.
|
||||||
|
- **On tile-based GPUs (mobile)**: Multiple passes that write/read intermediate textures to external memory is expensive. Use Vulkan subpasses or `VK_KHR_dynamic_rendering_local_read` to keep data on-chip.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Shader Composition Strategies
|
||||||
|
|
||||||
|
### 3a. Runtime Shader Generation
|
||||||
|
|
||||||
|
**Blender's approach** (most relevant):
|
||||||
|
- The compositor has a `gpu_shader_compositor_code_generation.glsl` library.
|
||||||
|
- `ShaderOperation` generates GLSL code by iterating through a compile unit's nodes and concatenating their shader code contributions.
|
||||||
|
- The generated code is compiled via Blender's `GPUMaterial` system.
|
||||||
|
- Node settings are passed as UBOs; images are bound as `image2D`/`sampler2D`.
|
||||||
|
|
||||||
|
**Godot's compositor approach**:
|
||||||
|
- Uses a **template + injection** pattern:
|
||||||
|
```
|
||||||
|
const template_shader = """
|
||||||
|
#version 450
|
||||||
|
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
||||||
|
layout(rgba16f, set = 0, binding = 0) uniform image2D color_image;
|
||||||
|
void main() {
|
||||||
|
// ... boilerplate ...
|
||||||
|
vec4 color = imageLoad(color_image, uv);
|
||||||
|
#COMPUTE_CODE
|
||||||
|
imageStore(color_image, uv, color);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
- User shader code replaces `#COMPUTE_CODE` at runtime.
|
||||||
|
- Compiled via `rd.shader_create_from_spirv()` at runtime.
|
||||||
|
|
||||||
|
**OGRE's RTSS (Run Time Shader System)**:
|
||||||
|
- Not an uber-shader. Manages a set of opaque `SubRenderState` components.
|
||||||
|
- Each component implements a specific effect.
|
||||||
|
- Components are composed and code-generated at runtime.
|
||||||
|
- Avoids the "exploding `#ifdef`" problem of uber-shaders.
|
||||||
|
|
||||||
|
### 3b. Shader Permutations vs Branching
|
||||||
|
|
||||||
|
**The permutation problem** (from MJP's detailed analysis):
|
||||||
|
- Each feature combination = separate compiled shader.
|
||||||
|
- Exponential growth: N binary features = 2^N permutations.
|
||||||
|
- Costs: compilation time, memory, PSO creation, binding overhead, instruction cache pressure.
|
||||||
|
- **Register pressure**: Uber-shaders with many features need more registers, reducing occupancy even for materials that don't use all features.
|
||||||
|
|
||||||
|
**Branching rules for GPUs**:
|
||||||
|
- **Uniform branches** (same path for all pixels in a warp): Essentially free. The driver compiles both paths and selects one.
|
||||||
|
- **Divergent branches** (different paths within a warp): Both paths execute serially, wasting cycles.
|
||||||
|
- **Branches on uniforms/constant data**: OK and performant.
|
||||||
|
- **Branches based on per-pixel data**: Expensive when pixels in the same warp diverge.
|
||||||
|
|
||||||
|
**Best practice**: Use **Vulkan specialization constants** for compile-time branching (uber-shader with static branching). This gives you permutation-like performance with fewer actual shader binaries. The driver can optimize away dead code paths.
|
||||||
|
|
||||||
|
### 3c. Compute Shaders vs Fragment Shaders
|
||||||
|
|
||||||
|
**Fragment shaders are generally faster for simple image processing:**
|
||||||
|
- Fragment shaders benefit from hardware texture prefetch and caching optimized for 2D spatial locality.
|
||||||
|
- For simple per-pixel operations (passthrough, basic color transforms): fragment shaders ~30% faster than compute (Leadwerks benchmarks: 770 FPS vs 600 FPS).
|
||||||
|
- For multi-pass chained operations: fragment shaders maintain advantage (670 FPS vs 180 FPS at 10 passes).
|
||||||
|
|
||||||
|
**Compute shaders are better when:**
|
||||||
|
- You need **shared memory** access within workgroups (e.g., local convolution, shared reductions).
|
||||||
|
- You need **read-write access** to the same texture (e.g., iterative algorithms like Jump Flood).
|
||||||
|
- You're doing operations that aren't naturally per-pixel (histogram, reduction, sorting).
|
||||||
|
- You want explicit control over workgroup dispatch.
|
||||||
|
|
||||||
|
**On tile-based GPUs (mobile)**: Arm documentation explicitly warns: "Compute shaders can be slower and less energy-efficient than fragment shaders for simple post-processing workloads."
|
||||||
|
|
||||||
|
**For compositing**: Use fragment shaders for per-pixel operations (blend, color grade, transform). Use compute for multi-pass algorithms that need shared memory (blur separable passes, glare FFT, flood fill).
|
||||||
|
|
||||||
|
### 3d. Bindless Textures and Descriptor Arrays
|
||||||
|
|
||||||
|
**The concept**: Instead of binding one texture per descriptor set, bind a large array of descriptors once. Access textures by integer index in shaders.
|
||||||
|
|
||||||
|
**Vulkan implementation** (from `VK_EXT_descriptor_indexing`, core since Vulkan 1.2):
|
||||||
|
```glsl
|
||||||
|
// GLSL
|
||||||
|
#extension GL_EXT_nonuniform_qualifier : enable
|
||||||
|
layout(set = 1, binding = 10) uniform sampler2D textures[];
|
||||||
|
vec4 color = texture(textures[albedo_id], uv);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key features**:
|
||||||
|
- `VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT`: Update descriptors after binding.
|
||||||
|
- `VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT`: Not all slots need valid descriptors.
|
||||||
|
- `NonUniformResourceIndex`: For divergent indexing within a warp.
|
||||||
|
|
||||||
|
**For a compositor**: Bindless is extremely useful. All input textures from the graph can live in one descriptor set. Each node shader indexes into the set by texture ID. This avoids re-binding descriptor sets per node.
|
||||||
|
|
||||||
|
**Trade-off**: Indirect memory loads can be slower on some mobile GPUs. Desktop GPUs handle this well.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Slang-Specific Patterns
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
Slang is a Khronos-hosted, open-source shading language. HLSL-like syntax with modern features:
|
||||||
|
- Targets: SPIR-V (Vulkan), DXIL (D3D12), Metal, CUDA, WGSL, CPU.
|
||||||
|
- Hosted by Khronos with broad industry governance.
|
||||||
|
- Based on years of NVIDIA/CMU/Stanford/MIT research.
|
||||||
|
|
||||||
|
### Key Features Relevant to Compositing
|
||||||
|
|
||||||
|
**Modules**: Slang supports `module` and `import` for separate compilation. Modules compile to a custom IR and can be linked at runtime to produce SPIR-V or DXIL. This is **exactly what a node compositor needs** — each node type can be a module, and compositions are linked at runtime.
|
||||||
|
|
||||||
|
**Generics and Interfaces**: Instead of #ifdef permutations, use generics:
|
||||||
|
```slang
|
||||||
|
interface IImageOp {
|
||||||
|
float4 evaluate(float4 input, PixelContext ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BlendOp : IImageOp {
|
||||||
|
float4 evaluate(float4 input, PixelContext ctx) { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic function specialized at compile time
|
||||||
|
T evaluateGraph<T : IImageOp>(T op, float4 input) {
|
||||||
|
return op.evaluate(input, ctx);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Runtime code generation**: Slang supports **runtime compilation and linking**. From the docs: "Slang modules can be independently compiled offline to a custom IR and then linked at runtime to generate code in formats such as DXIL or SPIR-V." This means you can:
|
||||||
|
1. Compile each node's shader as a Slang module.
|
||||||
|
2. At graph edit time, link modules together.
|
||||||
|
3. Generate the final SPIR-V/DXIL for the composed graph.
|
||||||
|
|
||||||
|
**Reflection API**: `TypeReflection`, `VariableReflection`, `getLayout()` allow querying shader structure at runtime — useful for automatically creating descriptor layouts.
|
||||||
|
|
||||||
|
**Automatic Differentiation**: `fwd_diff` and `bwd_diff` for gradient-based operations (relevant for differentiable compositing or learned operations).
|
||||||
|
|
||||||
|
### Slang vs GLSL/HLSL for Compositing
|
||||||
|
|
||||||
|
| Feature | Slang | GLSL | HLSL |
|
||||||
|
|---------|-------|------|------|
|
||||||
|
| Separate compilation | ✅ Modules | ❌ Single TU | ⚠️ Limited |
|
||||||
|
| Runtime linking | ✅ | ❌ | ❌ |
|
||||||
|
| Generics/interfaces | ✅ | ❌ | ⚠️ Templates (limited) |
|
||||||
|
| Cross-platform | ✅ (Vulkan/Metal/DX/CUDA) | ⚠️ (OpenGL/Vulkan) | ⚠️ (DX only) |
|
||||||
|
| Vulkan SPIR-V | ✅ First-class | ✅ via glslc | ⚠️ via dxc |
|
||||||
|
| Runtime compilation | ✅ | ❌ | ❌ |
|
||||||
|
| HLSL compatibility | ✅ Most HLSL compiles out-of-box | ❌ | ✅ |
|
||||||
|
|
||||||
|
### Recommendation
|
||||||
|
|
||||||
|
**Slang is the ideal choice for a Vulkan-based compositor.** Its module system directly solves the "runtime shader composition" problem. Each node type = a Slang module. Graph composition = module linking. No need for runtime string-based code generation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Vulkan-Specific Considerations
|
||||||
|
|
||||||
|
### Multi-Pass Image Processing
|
||||||
|
|
||||||
|
**Render Pass approach** (traditional):
|
||||||
|
```c
|
||||||
|
// Pass 1: Blur
|
||||||
|
VkRenderPassBeginInfo rp1 = { .renderPass = blurPass, .framebuffer = blurFBO };
|
||||||
|
vkCmdBeginRenderPass(cmd, &rp1, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, blurPipeline);
|
||||||
|
vkCmdDraw(cmd, 4, 1, 0, 0); // fullscreen quad
|
||||||
|
vkCmdEndRenderPass(cmd);
|
||||||
|
|
||||||
|
// Barrier between passes
|
||||||
|
VkImageMemoryBarrier barrier = {
|
||||||
|
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||||
|
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
};
|
||||||
|
vkCmdPipelineBarrier(cmd, ...);
|
||||||
|
|
||||||
|
// Pass 2: Color grade
|
||||||
|
VkRenderPassBeginInfo rp2 = { .renderPass = gradePass, .framebuffer = gradeFBO };
|
||||||
|
vkCmdBeginRenderPass(cmd, &rp2, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
vkCmdBindDescriptorSets(cmd, ..., gradeDescriptorSet); // binds blur result as texture
|
||||||
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, gradePipeline);
|
||||||
|
vkCmdDraw(cmd, 4, 1, 0, 0);
|
||||||
|
vkCmdEndRenderPass(cmd);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dynamic Rendering approach** (Vulkan 1.3 / `VK_KHR_dynamic_rendering`):
|
||||||
|
- Skip `VkRenderPass` and `VkFramebuffer` objects entirely.
|
||||||
|
- Use `vkCmdBeginRendering` with `VkRenderingInfo` specifying attachments directly.
|
||||||
|
- Simpler API, fewer objects to manage.
|
||||||
|
|
||||||
|
### Descriptor Management Best Practices
|
||||||
|
|
||||||
|
From ARM and NVIDIA guidelines:
|
||||||
|
- **Don't allocate descriptor sets on hot paths.** Pre-allocate pools.
|
||||||
|
- **Use `VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC` / `VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC`** for per-draw offsets instead of new descriptor sets.
|
||||||
|
- **Pack descriptor bindings** as tightly as possible. No holes.
|
||||||
|
- **Reuse descriptor sets** — update them rather than reallocating.
|
||||||
|
- For a compositor with bindless: create ONE large descriptor set with all textures. Bind once, index by ID.
|
||||||
|
|
||||||
|
### Pipeline Layout Optimization
|
||||||
|
|
||||||
|
- Keep pipeline layouts consistent across similar shaders to reduce pipeline switches.
|
||||||
|
- Use **push constants** for small, per-pass data (resolution, time, parameters) — cheaper than UBOs for small data.
|
||||||
|
- Pre-create pipeline cache and use `VkPipelineCache` to speed up PSO creation.
|
||||||
|
|
||||||
|
### Synchronization for Multi-Pass
|
||||||
|
|
||||||
|
- Use **pipeline barriers** between passes that read/write the same images.
|
||||||
|
- For independent passes (operating on different textures), no barrier needed — can even record in parallel.
|
||||||
|
- Use **events** for fine-grained synchronization within a command buffer.
|
||||||
|
- **Timeline semaphores** (Vulkan 1.2+) for more flexible GPU-GPU synchronization.
|
||||||
|
|
||||||
|
### Tile-Based GPU Optimization (Mobile)
|
||||||
|
|
||||||
|
- Use **subpasses** to keep intermediate data in tile memory (on-chip).
|
||||||
|
- `VK_KHR_dynamic_rendering_local_read` allows subpass-like behavior with dynamic rendering.
|
||||||
|
- Set `loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE` and `storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE` for transient intermediates.
|
||||||
|
- Merge subpasses when they share attachments (ARM: ≤8 unique attachments).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Industry Best Practices
|
||||||
|
|
||||||
|
### The Render Graph Pattern
|
||||||
|
|
||||||
|
Modern engines use a **frame graph** / **render graph** (DAG) for multi-pass rendering:
|
||||||
|
1. **Declare passes** and their resource inputs/outputs.
|
||||||
|
2. **Analyze dependencies** — build execution order automatically.
|
||||||
|
3. **Infer synchronization** — barriers are generated from resource usage.
|
||||||
|
4. **Alias resources** — textures with non-overlapping lifetimes can share memory.
|
||||||
|
5. **Cull unused passes** — if an output isn't used, skip the pass.
|
||||||
|
|
||||||
|
This is the most mature pattern for managing multi-pass image processing. Referenced in:
|
||||||
|
- Vulkan Tutorial: "Engine Architecture: Rendering Pipeline"
|
||||||
|
- Cat Game's "Advanced Vulkan Rendering: Building a Modern Frame Graph"
|
||||||
|
- Frostbite's "FrameGraph" (EA/DICE)
|
||||||
|
|
||||||
|
### Fusing Operations
|
||||||
|
|
||||||
|
From TFLite GPU and Blender compositor:
|
||||||
|
- **Fuse element-wise operations** with computationally expensive ones (activations + convolution, color transforms + blend).
|
||||||
|
- **Inline parameters** directly into shader code instead of passing via uniforms (bakes constants, reduces memory I/O).
|
||||||
|
- **Bake uniforms into source code** when they don't change per-pixel.
|
||||||
|
|
||||||
|
### Texture Pool Management
|
||||||
|
|
||||||
|
For a compositor with potentially many intermediate textures:
|
||||||
|
- Pre-allocate a pool of textures at common resolutions.
|
||||||
|
- Reference-count or track lifetime of each texture.
|
||||||
|
- Reuse textures with matching format/resolution once their producer is done.
|
||||||
|
- On mobile, prefer smaller intermediate formats (RGBA16F over RGBA32F when precision allows).
|
||||||
|
|
||||||
|
### Papers and References
|
||||||
|
|
||||||
|
1. **"Performance Implications of Node Graph Complexity in Real-Time Compositing"** (IEEE, 2024) — Studies Blender EEVEE's node graph rendering performance vs. structural complexity.
|
||||||
|
2. **"Compute Shader in Image Processing Development"** (CEUR Workshop, 2020) — Compares CPU, fragment, compute, and Vulkan fragment for image processing. Found compute shader overhead makes it slower for simple operations.
|
||||||
|
3. **Blender Real-time Compositor** (code.blender.org, 2022) — GPU-accelerated compositor architecture with operation graph, domain system, and shader-based execution.
|
||||||
|
4. **"The Shader Permutation Problem"** (MJP, 2021) — Comprehensive analysis of uber-shader vs. permutation trade-offs.
|
||||||
|
5. **"GPU Rendering Pipeline: Blend Modes, Porter-Duff Compositing"** (Lucio Durán, 2025) — Browser rendering pipeline compositing patterns.
|
||||||
|
6. **"High-Performance Software Rasterization on GPUs"** (NVIDIA Research, 2011) — Software GPU pipeline, relevant for understanding GPU architecture.
|
||||||
|
7. **Vulkan Samples** (Khronos) — Descriptor management, subpasses, async compute, tile-based rendering best practices.
|
||||||
|
|
||||||
|
### Recommended Architecture for Prism
|
||||||
|
|
||||||
|
Based on all research:
|
||||||
|
|
||||||
|
1. **DAG-based execution**: Topological sort the node graph. Execute in dependency order.
|
||||||
|
2. **Separate shaders per node type**: Each node type (Blend, ColorGrade, Blur, etc.) has a dedicated Slang shader module.
|
||||||
|
3. **Runtime composition via Slang modules**: Simple chains of per-pixel operations can be fused into single compute/fragment passes by linking their Slang modules.
|
||||||
|
4. **Texture pool**: Pre-allocated RGBA16F textures. Reference-counted. Reuse when possible.
|
||||||
|
5. **Ping-pong for chains**: Two textures alternating for sequential per-pixel chains.
|
||||||
|
6. **Fragment shaders for per-pixel ops**, compute shaders for operations needing shared memory (blur, convolution, reduction).
|
||||||
|
7. **Bindless descriptors**: One large descriptor set with all input textures. Node shaders index by texture ID.
|
||||||
|
8. **Push constants** for per-pass uniforms (resolution, parameters).
|
||||||
|
9. **Pipeline barriers** between passes on the same texture. No barriers for independent passes.
|
||||||
|
10. **Render graph** for automatic dependency tracking and synchronization.
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
|||||||
|
# Plan: RHI Global Context
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Replace per-call `WpAllocator *allocator` parameters with a global `PrRhiContext`
|
||||||
|
managed by the RHI. The RHI owns the lifetime of all objects it creates.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### 1. `src/prism/rhi/pr_rhi_types.h` — Add context struct
|
||||||
|
|
||||||
|
Add after the existing typedefs:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct PrRhiContext {
|
||||||
|
WpAllocator main; // objects returned to the user
|
||||||
|
WpAllocator scratch; // internal temporaries within functions
|
||||||
|
} PrRhiContext;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `src/prism/rhi/pr_rhi.h` — Public API changes
|
||||||
|
|
||||||
|
- Add `wp_extern PrRhiContext _G_RHI_CONTEXT;` declaration (near top, after includes)
|
||||||
|
- Add `wp_extern b8 prRhiInit(void);` and `wp_extern void prRhiDestroy(void);`
|
||||||
|
- Remove `WpAllocator *allocator` (and `const WpAllocator *allocator`) from **all** function signatures
|
||||||
|
|
||||||
|
### 3. `src/prism/rhi/pr_rhi.c` — New file (shared across backends)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "pr_rhi.h"
|
||||||
|
|
||||||
|
PrRhiContext _G_RHI_CONTEXT;
|
||||||
|
|
||||||
|
b8 prRhiInit(void) {
|
||||||
|
_G_RHI_CONTEXT.main = wpMemArenaAllocatorInit(MiB(64));
|
||||||
|
_G_RHI_CONTEXT.scratch = wpMemArenaAllocatorInit(MiB(32));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void prRhiDestroy(void) {
|
||||||
|
wpMemAllocatorFree(&_G_RHI_CONTEXT.scratch, ...);
|
||||||
|
wpMemAllocatorFree(&_G_RHI_CONTEXT.main, ...);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. `src/prism/rhi/vulkan/pr_rhi_vk.h` — Remove allocator from Vk declarations
|
||||||
|
|
||||||
|
Remove `WpAllocator *allocator` from all function declarations.
|
||||||
|
|
||||||
|
### 5. `src/prism/rhi/vulkan/pr_rhi_vk.c` — Implementation changes
|
||||||
|
|
||||||
|
For every function that previously took `WpAllocator *allocator`:
|
||||||
|
|
||||||
|
- Remove the parameter from the signature
|
||||||
|
- Replace `allocator` with `_G_RHI_CONTEXT.main` for:
|
||||||
|
- `wpMemAllocatorAlloc(allocator, sizeof(...))` for user-facing objects (returned to caller)
|
||||||
|
- `wpArrayAllocCapacity(...)` for arrays that are stored in user-facing structs
|
||||||
|
- Replace `allocator` with `_G_RHI_CONTEXT.scratch` for:
|
||||||
|
- `wpArrayAllocCapacity(...)` for internal temporary arrays
|
||||||
|
- `wpArrayDealloc(...)` for internal temporaries
|
||||||
|
- Replace `wpMemAllocatorFree(allocator, ...)` with `wpMemAllocatorFree(&_G_RHI_CONTEXT.main, ...)` for destroy functions
|
||||||
|
- Update internal calls (e.g. `prRhiCreateBufferVk` called from `prRhiCreateTextureFromKtxVk`)
|
||||||
|
|
||||||
|
**Specific internal/helper changes:**
|
||||||
|
- `_createSwapchainTexture` — remove `allocator` param, use `_G_RHI_CONTEXT.main`
|
||||||
|
- `prRhiCreateTextureFromKtxVk` — staging buffer uses `_G_RHI_CONTEXT.main` (it's a user-facing object destroyed by the user)
|
||||||
|
- `prRhiAllocateCommandBuffersVk` — VkCommandBuffer temp array uses scratch, PrRhiCommandBuffer array + structs use main
|
||||||
|
- `prRhiCreatePipelineLayoutVk` — VkDescriptorSetLayout/VkPushConstantRange overflow arrays use scratch
|
||||||
|
- `prRhiCreateDeviceVk` — VkQueueFamilyProperties2 array uses scratch
|
||||||
|
|
||||||
|
### 6. `src/prism/rhi/vulkan/pr_rhi_vk_aliases.h` — No changes needed
|
||||||
|
|
||||||
|
Aliases only map function names, not parameters.
|
||||||
|
|
||||||
|
## Allocator usage per function
|
||||||
|
|
||||||
|
| Function | Returned object | Allocator |
|
||||||
|
|----------|----------------|-----------|
|
||||||
|
| `prRhiCreateInstance` | PrRhiInstance | main |
|
||||||
|
| `prRhiDestroyInstance` | — | free from main |
|
||||||
|
| `prRhiGetPhysicalDevices` | PrRhiPhysicalDeviceArray + PrRhiPhysicalDevice structs | main |
|
||||||
|
| `prRhiCreateSurfaceFromWindow` | PrRhiSurface | main |
|
||||||
|
| `prRhiDestroySurface` | — | free from main |
|
||||||
|
| `prRhiCreateDevice` | PrRhiDevice | main (internal VkQueueFamilyProperties2 array → scratch) |
|
||||||
|
| `prRhiDestroyDevice` | — | free from main |
|
||||||
|
| `prRhiCreateSwapchain` | PrRhiSwapchain + images + depth | main (internal VkImage array → scratch) |
|
||||||
|
| `prRhiDestroySwapchain` | — | free from main |
|
||||||
|
| `prRhiRecreateSwapchain` | updates existing struct | main for new images/depth, scratch for temp arrays |
|
||||||
|
| `prRhiCreateBuffer` | PrRhiBuffer | main |
|
||||||
|
| `prRhiDestroyBuffer` | — | free from main |
|
||||||
|
| `prRhiCreateTexture` | PrRhiTexture | main |
|
||||||
|
| `prRhiCreateTextureFromKtx` | PrRhiTexture (staging buffer too) | main |
|
||||||
|
| `prRhiDestroyTexture` | — | free from main |
|
||||||
|
| `prRhiCreateSampler` | PrRhiSampler | main |
|
||||||
|
| `prRhiDestroySampler` | — | free from main |
|
||||||
|
| `prRhiCreateShader` | PrRhiShader | main |
|
||||||
|
| `prRhiDestroyShader` | — | free from main |
|
||||||
|
| `prRhiCreatePipelineLayout` | PrRhiPipelineLayout | main (internal overflow arrays → scratch) |
|
||||||
|
| `prRhiDestroyPipelineLayout` | — | free from main |
|
||||||
|
| `prRhiCreateGraphicsPipeline` | PrRhiPipeline | main |
|
||||||
|
| `prRhiCreateComputePipeline` | PrRhiPipeline | main |
|
||||||
|
| `prRhiDestroyPipeline` | — | free from main |
|
||||||
|
| `prRhiCreateDescriptorSetLayout` | PrRhiDescriptorSetLayout | main |
|
||||||
|
| `prRhiDestroyDescriptorSetLayout` | — | free from main |
|
||||||
|
| `prRhiCreateDescriptorPool` | PrRhiDescriptorPool | main |
|
||||||
|
| `prRhiDestroyDescriptorPool` | — | free from main |
|
||||||
|
| `prRhiAllocateDescriptorSet` | PrRhiDescriptorSet | main |
|
||||||
|
| `prRhiFreeDescriptorSet` | — | free from main |
|
||||||
|
| `prRhiCreateFence` | PrRhiFence | main |
|
||||||
|
| `prRhiDestroyFence` | — | free from main |
|
||||||
|
| `prRhiCreateSemaphore` | PrRhiSemaphore | main |
|
||||||
|
| `prRhiDestroySemaphore` | — | free from main |
|
||||||
|
| `prRhiCreateCommandPool` | PrRhiCommandPool | main |
|
||||||
|
| `prRhiDestroyCommandPool` | — | free from main |
|
||||||
|
| `prRhiAllocateCommandBuffers` | PrRhiCommandBufferArray + structs | main (internal VkCommandBuffer array → scratch) |
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Session Log — 2026-07-06
|
||||||
|
|
||||||
|
## Completed
|
||||||
|
- **Build system**: Fixed Just 1.55.1 `[working-directory]` bug (literal paths required).
|
||||||
|
Set up `just build` for 6 object files: volk.c, wapp.c, pr_rhi_vk.c, pr_rhi_vk_vma.cpp,
|
||||||
|
vulkan_profiles.cpp, main.cpp. Linked with `-lSDL3 -lglm -ltinyobjloader -lktx -lslang -lvulkan`.
|
||||||
|
- **RHI backend completeness**: Filled in all missing functions (profiles validation,
|
||||||
|
swapchain/recreate, dynamic rendering, copy, queue submit, barriers, descriptor,
|
||||||
|
pipeline, sync) — 68 functions total in single `pr_rhi_vk.c`.
|
||||||
|
- **Bugfix — `prRhiPresentVk`**: Used `current_image_index` from acquire instead of
|
||||||
|
hardcoded `0`.
|
||||||
|
- **Bugfix — `extern "C"` linkage**: Added `extern "C"` guards in `pr_rhi.h` so C++
|
||||||
|
callers (main.cpp) can link C-compiled backend symbols.
|
||||||
|
- **Bugfix — GPU name dangling pointers**: Added `device_name[256]`/`driver_info[256]`
|
||||||
|
to `PrRhiPhysicalDevice` struct, populated during enumeration. Getters return pointers
|
||||||
|
to these persistent buffers.
|
||||||
|
- **Bugfix — `volkInitialize()`**: Added call at start of `prRhiCreateInstanceVk` —
|
||||||
|
`vpGetInstanceProfileSupport` crashed because volk hadn't loaded the Vulkan loader.
|
||||||
|
- **Bugfix — scratch arena OOM**: Changed initial 128KB → 64MB _and_ the line-320
|
||||||
|
reinit override 8MB → 64MB. Prevents OOM during mesh building.
|
||||||
|
- **Bugfix — stale mesh array pointers**: Captured return values of `wpArrayAppendAlloc`
|
||||||
|
in mesh-building loop — original code ignored the pointer, so `vertices`/`indices`
|
||||||
|
pointed to stale initial array after regrowth.
|
||||||
|
- **Bugfix — uninitialised Vulkan stack arrays**: Zero-initialised all 18 local Vulkan
|
||||||
|
struct array declarations (`VkImageMemoryBarrier2[16]`, `VkBufferMemoryBarrier2[16]`,
|
||||||
|
`VkRenderingAttachmentInfo[8]`, `VkBufferImageCopy[16]`, plus `VkDescriptorSetLayout[8]`,
|
||||||
|
`VkPushConstantRange[8]`, `VkVertexInputBindingDescription[8]`,
|
||||||
|
`VkVertexInputAttributeDescription[16]`, `VkDynamicState[2]`, `VkFormat[8]`,
|
||||||
|
`VkPipelineColorBlendAttachmentState[8]`, `VkDescriptorSetLayoutBinding[16]`,
|
||||||
|
`VkDescriptorBindingFlags[16]`, `VkDescriptorPoolSize[8]`, `VkDescriptorImageInfo[16]`,
|
||||||
|
`VkDescriptorBufferInfo[16]`, `VkFence[16]` (×2), `VkCommandBuffer[16]` (×2),
|
||||||
|
`VkDescriptorSet[16]`, `VkBuffer[16]`). Uninitialised `pNext`/`imageOffset` fields
|
||||||
|
caused GPU-side device-lost crashes.
|
||||||
|
- **Bugfix — `render_completed_semaphores` zero-length array**: `prRhiAcquireNextImage`
|
||||||
|
returns swapchain image INDEX (0 on first call), not image count. Used as array capacity,
|
||||||
|
this allocated 0 semaphores, causing out-of-bounds access in render loop → SIGSEGV.
|
||||||
|
Fixed by reading `app.swapchain->image_count` instead.
|
||||||
|
- **Demo renders**: `build/prism` now launches a window, loads `suzanne0.ktx` / `suzanne1.ktx`,
|
||||||
|
renders textured Suzanne mesh with mouse orbit + keyboard mesh selection.
|
||||||
|
|
||||||
|
## Key Decisions
|
||||||
|
- Just 1.55.1 bug: `[working-directory: '{{BUILDDIR}}']` causes "could not find the shell `sh`"
|
||||||
|
— literal paths or no `[working-directory]` attribute required.
|
||||||
|
- VMA implementation in separate `pr_rhi_vk_vma.cpp` (compiled as C++).
|
||||||
|
- `PrRhiPhysicalDevice` stores `device_name[256]`/`driver_info[256]` to avoid dangling pointers.
|
||||||
|
- All stack Vulkan struct arrays must be `= {0}` initialised — C backend does not zero
|
||||||
|
auto vars, and uninitialised `pNext`/offset fields cause GPU driver crashes.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
1. Review code for structural/design quality issues the user already noticed.
|
||||||
|
2. Add `wpMemArenaAllocatorTempBegin`/`TempEnd` markers around scratch allocations.
|
||||||
|
3. Consider adding `prRhiGetSwapchainImageCount` accessor for encapsulation.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Session Log — 2026-07-12
|
||||||
|
|
||||||
|
## RHI Global Context Refactor
|
||||||
|
|
||||||
|
Reviewed the user's staged changes to the Vulkan RHI backend. The changes encompassed:
|
||||||
|
|
||||||
|
### Global Context Adoption
|
||||||
|
- RHI functions no longer take allocator parameters
|
||||||
|
- `_G_RHI_CONTEXT` provides `allocator` (user-facing objects) and `tmp` (short-lived temporaries)
|
||||||
|
- `prRhiInit(void)` / `prRhiDestroy(void)` manage the global context
|
||||||
|
|
||||||
|
### KTX Texture Bug Fix
|
||||||
|
- Original implementation only copied mip level 0
|
||||||
|
- Fixed to iterate all mip levels using `ktxTexture_GetImageOffset()`
|
||||||
|
- Final layout changed from `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` to `VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL`
|
||||||
|
|
||||||
|
### Frame-by-Frame Command Batching Pattern
|
||||||
|
- Commands that run every frame avoid arena allocation
|
||||||
|
- Use stack arrays with while-loop to batch operations in fixed-size chunks
|
||||||
|
- Applied to: `prRhiCmdBindDescriptorSets`, `prRhiCmdBindVertexBuffers`, `prRhiCmdCopyBufferToImage`
|
||||||
|
|
||||||
|
### API Simplifications
|
||||||
|
- `prRhiCreateCommandPool`: removed desc parameter, uses `device->queue_family_index`
|
||||||
|
- `prRhiFreeCommandBuffers`: removed count parameter, uses `wpArrayCount`
|
||||||
|
- `prRhiAllocateDescriptorSet`: changed `u32 variable_count` to `WpU32Array variable_descriptor_counts`
|
||||||
|
- `prRhiCmdBindVertexBuffers`: changed raw pointer + count to `WpU64Array`
|
||||||
|
|
||||||
|
### New Pipeline Configuration
|
||||||
|
- Added `polygon_mode`, `cull_mode`, `front_face` to rasterization
|
||||||
|
- Added `depth_test_enable`, `depth_write_enable`, `depth_compare_op`
|
||||||
|
- Added `vertex_shader_entry_point`, `fragment_shader_entry_point` (not hardcoded to "main")
|
||||||
|
- Added `line_width`, `multisample_count`
|
||||||
|
|
||||||
|
### Code Style Updates
|
||||||
|
- All Vulkan info structs use C99 designated initializers
|
||||||
|
- Removed unnecessary type casts on opaque struct handles
|
||||||
|
- Added braces to all single-line if statements
|
||||||
|
- Braces rule moved from prism-rhi skill to AGENTS.md formatting section
|
||||||
|
|
||||||
|
## Documentation Updates
|
||||||
|
- Updated `prism-rhi` skill with all new conventions
|
||||||
|
- Updated `AGENTS.md` with frame-by-frame batching pattern
|
||||||
|
- Moved braces rule to `AGENTS.md` (not RHI-specific)
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Session Log — 2026-08-08
|
||||||
|
|
||||||
|
## Fullscreen Texture Viewer — Verification and RHI Fixes
|
||||||
|
|
||||||
|
Continuation of the fullscreen-blit plan (`documents/plans/fullscreen-blit.md`),
|
||||||
|
which rewrote `main.cpp` from the Suzanne mesh demo into a bindless texture
|
||||||
|
viewer with contain-fit. This session visually verified the renderer and fixed
|
||||||
|
three real bugs uncovered by that verification.
|
||||||
|
|
||||||
|
### Verification method
|
||||||
|
|
||||||
|
- Ran the app under the X11 SDL driver (`SDL_VIDEODRIVER=x11`) and captured the
|
||||||
|
window with `xwd -id <window>`, then sampled pixels with ImageMagick
|
||||||
|
(`magick -format "%[pixel:p{x,y}]" info:`). The model cannot view images, so
|
||||||
|
all render checks were programmatic (pure-black bars, centered content).
|
||||||
|
- XTEST synthesised keys (`XTestFakeKeyEvent`) are silently dropped by
|
||||||
|
KWin/XWayland (confirmed with `xev`: FocusIn arrives via `_NET_ACTIVE_WINDOW`,
|
||||||
|
KeyPress never does). So texture cycling could not be driven headlessly; fit
|
||||||
|
cases were verified by temporarily making each texture the initial selection.
|
||||||
|
|
||||||
|
### Contain-fit verified (pixel-sampled)
|
||||||
|
|
||||||
|
| Texture | Window | Result |
|
||||||
|
|---------------------|-----------|---------------------------------------|
|
||||||
|
| square 1024x1024 | 16:9 | pillarbox (black L/R bars) |
|
||||||
|
| test_wide 2048x512 | 16:9 | letterbox (black T/B bars) |
|
||||||
|
| test_tall 512x2048 | 16:9 | pillarbox (black L/R bars) |
|
||||||
|
| test_fill 1920x1080 | 16:9 | fills exactly (no bars) |
|
||||||
|
| square, then resized to portrait | portrait | fit recomputed per frame → flips to letterbox |
|
||||||
|
|
||||||
|
### Bugs found and fixed
|
||||||
|
|
||||||
|
1. **`VK_SUBOPTIMAL_KHR` crashed the app.** `prRhiAcquireNextImageVk` and
|
||||||
|
`prRhiPresentVk` routed SUBOPTIMAL into `_checkVk` → `__builtin_trap()`
|
||||||
|
(SIGILL, caught under X11 immediately). Both now return
|
||||||
|
`PR_RHI_SWAPCHAIN_OUT_OF_DATE` for SUBOPTIMAL, same as OUT_OF_DATE
|
||||||
|
(`pr_rhi_vk.c`).
|
||||||
|
2. **Swapchain recreate ignored surface extent.** `prRhiRecreateSwapchainVk`
|
||||||
|
hard-coded the passed width/height. Now queries
|
||||||
|
`vkGetPhysicalDeviceSurfaceCapabilitiesKHR` and falls back to the passed
|
||||||
|
size only when `currentExtent == 0xFFFFFFFF` (matches the initial-create
|
||||||
|
logic).
|
||||||
|
3. **App used logical window size for the swapchain.** Under HiDPI the drawable
|
||||||
|
differs from `SDL_GetWindowSize` (1920x1080 logical vs 2400x1350 drawable at
|
||||||
|
1.25x scale on XWayland) — the root cause of #1. main.cpp now uses
|
||||||
|
`SDL_GetWindowSizeInPixels` for swapchain width/height, the fit rect, the
|
||||||
|
viewport, and the scissor.
|
||||||
|
|
||||||
|
### CLI arg considered and removed
|
||||||
|
|
||||||
|
Added a `--texture N` startup arg to drive the verification, then removed it at
|
||||||
|
the user's request (`main()` is back to no-args, `app.selected = 0`). If
|
||||||
|
headless key injection is ever needed again, revisit (e.g. `ydotool`/`wtype` on
|
||||||
|
Wayland, or a WM on a real X server).
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- `main.cpp` header comment, `<cstdlib>` include, and plan doc all updated to
|
||||||
|
reflect the removed arg.
|
||||||
|
- Native Wayland run still clean after all fixes; `just build` passes.
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Session Log — 2026-08-09
|
||||||
|
|
||||||
|
## Background colour change (blit shader)
|
||||||
|
|
||||||
|
- User requested changing the letterbox/pillarbox background from black to neutral grey.
|
||||||
|
- Initial attempt: changed the render pass clear color to `(0.5, 0.5, 0.5, 1.0)`. This
|
||||||
|
triggered the NVIDIA validation layer warning
|
||||||
|
`BestPractices-NVIDIA-ClearColor-NotCompressed` — SRGB fast clears only work
|
||||||
|
with 0.0 or 1.0 on NVIDIA tile-based GPUs.
|
||||||
|
- Reverted the clear color and implemented the proper solution: draw a fullscreen
|
||||||
|
grey quad in the fragment shader before the texture quad. The render pass clear
|
||||||
|
stays at 0.0 (fast-compressed).
|
||||||
|
- Added `mode` field to `BlitData` push constant. Mode 0 samples the texture, mode
|
||||||
|
1 outputs solid grey.
|
||||||
|
- User noted that a branch in the shader is free (no warp divergence since `mode`
|
||||||
|
is uniform per draw call). Agreed — no need for a separate clear pipeline.
|
||||||
|
- Changed grey from 0.5 to 0.18 (18% grey card, standard in photography/compositing).
|
||||||
|
- Fixed a Slang compilation warning by updating the profile from `spirv_1_4` to
|
||||||
|
`spirv_1_6` and explicitly declaring the required capabilities.
|
||||||
|
|
||||||
|
## Shader filter node research
|
||||||
|
|
||||||
|
- User requested research on: Gaussian blur, CDL, Laplacian, Sobel, sharpen,
|
||||||
|
posterize, pixelize, Kuwahara.
|
||||||
|
- Launched a research agent that produced `documents/research/shader-filters.md`
|
||||||
|
covering all filters with formulas, Slang pseudocode, parameter tables, and
|
||||||
|
performance notes.
|
||||||
|
|
||||||
|
## Design decisions made during review
|
||||||
|
|
||||||
|
1. **Colour space**: all intermediate textures are linear float
|
||||||
|
(`R16G16B16A16_SFLOAT`, `R32G32B32A32_SFLOAT` for Kuwahara tensor). sRGB images
|
||||||
|
are linearized once at load by the Read node. Final blit to sRGB swapchain
|
||||||
|
handles display encoding.
|
||||||
|
|
||||||
|
2. **Alpha**: premultiplied everywhere by default. Explicit Unpremult/Premult
|
||||||
|
nodes for operations that need unpremultiplied values (Nuke model).
|
||||||
|
|
||||||
|
3. **Edge handling**: per-node parameter, clamp-to-edge default, clamp-to-border
|
||||||
|
option. Affects sampler state, not shader branches.
|
||||||
|
|
||||||
|
4. **Premult has no parameters**: removed the empty push constant struct.
|
||||||
|
|
||||||
|
## Research document fixes
|
||||||
|
|
||||||
|
- Fixed a contradictory sentence about push constant sizes and CDL block size.
|
||||||
|
- Added Unpremult (§9) and Premult (§10) sections with full implementations.
|
||||||
|
- Added `edge_mode` field to all 5 spatial filter push constant blocks (Gaussian,
|
||||||
|
Laplacian, Sobel, Sharpen, Kuwahara).
|
||||||
|
- Restructured the implications section (§12) into open items vs resolved decisions.
|
||||||
|
- Expanded all mathematics sections with plain-language explanations suitable for
|
||||||
|
someone without a strong math background.
|
||||||
|
|
||||||
|
## Open items for next session
|
||||||
|
|
||||||
|
- Begin implementing the actual shader nodes in Prism
|
||||||
|
- Node system needs: per-pass resource signatures, scratch texture hooks, per-node
|
||||||
|
sampler choice, compile-time-bounded loop limits
|
||||||
|
- Classic Kuwahara is the recommended first implementation (single pass)
|
||||||
@@ -3,11 +3,81 @@
|
|||||||
|
|
||||||
default: build
|
default: build
|
||||||
|
|
||||||
# Build the project
|
CC := "clang"
|
||||||
build:
|
CXX := "clang++"
|
||||||
@echo "TODO: implement build"
|
BUILDDIR := "build"
|
||||||
|
|
||||||
# Run linter / typecheck
|
# Resolve VULKAN_SDK once via backtick
|
||||||
|
VK_SDK := `echo $VULKAN_SDK`
|
||||||
|
VENDOR_INC := BUILDDIR + "/include"
|
||||||
|
VENDOR_LIB := BUILDDIR + "/lib"
|
||||||
|
|
||||||
|
VK_FLAGS := "-DPR_RHI_VULKAN -DVK_NO_PROTOTYPES -I" + VK_SDK + "/include -I" + VK_SDK + "/include/vma -I" + VENDOR_INC
|
||||||
|
APP_INC := "-I" + VK_SDK + "/include -I" + VK_SDK + "/include/vma -I" + VENDOR_INC + " -Isrc"
|
||||||
|
|
||||||
|
# Build KTX from source
|
||||||
|
vendor:
|
||||||
|
mkdir -p {{BUILDDIR}}/ktx
|
||||||
|
cmake src/vendor/ktx -B {{BUILDDIR}}/ktx \
|
||||||
|
-D KTX_FEATURE_LOADTEST_APPS=OFF \
|
||||||
|
-D KTX_FEATURE_DOC=OFF \
|
||||||
|
-D CMAKE_EXPORT_COMPILE_COMMANDS=1 \
|
||||||
|
-D CMAKE_BUILD_TYPE=Release \
|
||||||
|
-D CMAKE_INSTALL_PREFIX=$(pwd)/{{BUILDDIR}} \
|
||||||
|
-D CMAKE_CXX_STANDARD=17 \
|
||||||
|
-D CMAKE_CXX_FLAGS="-msse4.1" \
|
||||||
|
-G Ninja
|
||||||
|
cmake --build {{BUILDDIR}}/ktx --config Release
|
||||||
|
cmake --install {{BUILDDIR}}/ktx
|
||||||
|
|
||||||
|
# Compile shaders from .slang to SPIR-V
|
||||||
|
shaders:
|
||||||
|
mkdir -p {{BUILDDIR}}/shaders
|
||||||
|
{{VK_SDK}}/bin/slangc -target spirv \
|
||||||
|
-profile spirv_1_6+\
|
||||||
|
SPV_GOOGLE_user_type+\
|
||||||
|
spvFragmentFullyCoveredEXT+\
|
||||||
|
spvDerivativeControl+\
|
||||||
|
spvImageQuery+\
|
||||||
|
spvImageGatherExtended+\
|
||||||
|
spvSparseResidency+\
|
||||||
|
spvMinLod \
|
||||||
|
-o {{BUILDDIR}}/shaders/blit.spv assets/blit.slang
|
||||||
|
|
||||||
|
# Build all objects, then link
|
||||||
|
build: vendor shaders
|
||||||
|
mkdir -p {{BUILDDIR}}/bin
|
||||||
|
bear -- {{CXX}} -g -c -Wno-nullability-completeness {{VK_FLAGS}} \
|
||||||
|
src/prism/rhi/vulkan/profiles/vulkan_profiles.cpp \
|
||||||
|
-o {{BUILDDIR}}/vulkan_profiles.o
|
||||||
|
bear -a -- {{CXX}} -g -c -Wno-nullability-completeness {{VK_FLAGS}} \
|
||||||
|
src/prism/rhi/vulkan/pr_rhi_vk_vma.cpp \
|
||||||
|
-o {{BUILDDIR}}/pr_rhi_vk_vma.o
|
||||||
|
bear -a -- {{CC}} -g -c {{VK_FLAGS}} {{VK_SDK}}/include/volk/volk.c -o {{BUILDDIR}}/volk.o
|
||||||
|
bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/rhi/pr_rhi.c -o {{BUILDDIR}}/pr_rhi.o
|
||||||
|
bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/rhi/vulkan/pr_rhi_vk.c -o {{BUILDDIR}}/pr_rhi_vk.o
|
||||||
|
bear -a -- {{CC}} -g -c src/vendor/wapp/wapp.c -o {{BUILDDIR}}/wapp.o
|
||||||
|
bear -a -- {{CXX}} -g -c {{VK_FLAGS}} -Wno-nullability-completeness -DVK_NO_PROTOTYPES \
|
||||||
|
{{APP_INC}} \
|
||||||
|
src/main.cpp \
|
||||||
|
-o {{BUILDDIR}}/main.o
|
||||||
|
bear -a -- {{CXX}} -g {{VK_FLAGS}} \
|
||||||
|
-L{{VK_SDK}}/lib -L{{VENDOR_LIB}} \
|
||||||
|
build/*.o \
|
||||||
|
-lSDL3 -lktx -lvulkan \
|
||||||
|
-Wl,-rpath,{{VENDOR_LIB}} -Wl,-rpath,{{VK_SDK}}/lib \
|
||||||
|
-o {{BUILDDIR}}/bin/prism
|
||||||
|
@echo "--- build done: {{BUILDDIR}}/bin/prism ---"
|
||||||
|
@rm {{BUILDDIR}}/*.o
|
||||||
|
|
||||||
|
run:
|
||||||
|
./{{BUILDDIR}}/bin/prism
|
||||||
|
|
||||||
|
# Clean
|
||||||
|
clean:
|
||||||
|
rm -rf {{BUILDDIR}}
|
||||||
|
|
||||||
|
# Run linter
|
||||||
lint:
|
lint:
|
||||||
@echo "TODO: implement linter"
|
@echo "TODO: implement linter"
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
// vim:fileencoding=utf-8:foldmethod=marker
|
// vim:fileencoding=utf-8:foldmethod=marker
|
||||||
|
|
||||||
#include "../src/wapp/wapp.h"
|
#include "../src/vendor/wapp/wapp.h"
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|||||||
+642
@@ -0,0 +1,642 @@
|
|||||||
|
// vim:fileencoding=utf-8:foldmethod=marker
|
||||||
|
//
|
||||||
|
// Prism texture viewer — draws a texture fullscreen with contain-fit
|
||||||
|
// (letterbox/pillarbox to preserve aspect ratio, never crops or stretches).
|
||||||
|
// Texture selection cycles with +/-.
|
||||||
|
|
||||||
|
#include "prism/rhi/pr_rhi_types.h"
|
||||||
|
#include "prism/rhi/pr_rhi.h"
|
||||||
|
#include <SDL3/SDL_timer.h>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_events.h>
|
||||||
|
#include <SDL3/SDL_init.h>
|
||||||
|
#include <SDL3/SDL_keycode.h>
|
||||||
|
#include <SDL3/SDL_video.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Exit codes
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
enum ExitCode {
|
||||||
|
EXIT_CODE_SUCCESS,
|
||||||
|
EXIT_CODE_SDL_INIT_FAILED,
|
||||||
|
EXIT_CODE_WINDOW_CREATION_FAILED,
|
||||||
|
EXIT_CODE_GET_WINDOW_SIZE_FAILED,
|
||||||
|
EXIT_CODE_NO_PHYSICAL_DEVICES,
|
||||||
|
EXIT_CODE_NO_SUITABLE_PHYSICAL_DEVICE,
|
||||||
|
EXIT_CODE_ALLOCATION_FAILURE,
|
||||||
|
EXIT_CODE_SHADER_LOAD_FAILED,
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void check(bool result, i32 code) {
|
||||||
|
if (!result) {
|
||||||
|
std::cerr << "Call returned an error\n";
|
||||||
|
exit(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
struct TextureResources {
|
||||||
|
PrRhiTexture *texture;
|
||||||
|
PrRhiSampler *sampler;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Push constant block — matches BlitData in assets/blit.slang (16-byte aligned,
|
||||||
|
// so the struct is padded to 32 bytes).
|
||||||
|
struct BlitData {
|
||||||
|
f32 rect[4]; // NDC fit rect: x0, y0, x1, y1
|
||||||
|
u32 selected;
|
||||||
|
u32 mode; // 0 = sample texture, 1 = solid background
|
||||||
|
u32 pad[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Typedefs for wapp arrays of our types
|
||||||
|
typedef TextureResources *TextureResourcesArray;
|
||||||
|
typedef PrRhiSemaphore **PrRhiSemaphoreArray;
|
||||||
|
typedef PrRhiFence **PrRhiFenceArray;
|
||||||
|
typedef PrRhiCommandBuffer **PrRhiCommandBufferArray;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Global state
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static const char *const TEXTURE_PATHS[] = {
|
||||||
|
"assets/suzanne0.ktx",
|
||||||
|
"assets/suzanne1.ktx",
|
||||||
|
"assets/suzanne2.ktx",
|
||||||
|
"assets/test_square.ktx",
|
||||||
|
"assets/test_fill.ktx",
|
||||||
|
"assets/test_wide.ktx",
|
||||||
|
"assets/test_tall.ktx",
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
PrRhiInstance *inst;
|
||||||
|
PrRhiPhysicalDevice *pdev;
|
||||||
|
PrRhiDevice *device;
|
||||||
|
PrRhiSurface *surface;
|
||||||
|
PrRhiSwapchain *swapchain;
|
||||||
|
|
||||||
|
PrRhiFormat swapchain_format;
|
||||||
|
|
||||||
|
static constexpr u32 max_frames_in_flight = 2;
|
||||||
|
static constexpr u32 texture_count = (u32)(sizeof(TEXTURE_PATHS) / sizeof(TEXTURE_PATHS[0]));
|
||||||
|
|
||||||
|
PrRhiFenceArray fences;
|
||||||
|
PrRhiSemaphoreArray image_acquired_semaphores;
|
||||||
|
PrRhiSemaphoreArray render_completed_semaphores;
|
||||||
|
u32 render_semaphore_count;
|
||||||
|
PrRhiCommandPool *cmd_pool;
|
||||||
|
PrRhiCommandBufferArray cmd_buffers;
|
||||||
|
|
||||||
|
TextureResourcesArray textures;
|
||||||
|
PrRhiDescriptorSetLayout *desc_set_layout;
|
||||||
|
PrRhiDescriptorPool *desc_pool;
|
||||||
|
PrRhiDescriptorSet *desc_set;
|
||||||
|
|
||||||
|
PrRhiShader *shader;
|
||||||
|
PrRhiPipelineLayout *pipeline_layout;
|
||||||
|
PrRhiPipeline *pipeline;
|
||||||
|
|
||||||
|
u32 selected;
|
||||||
|
u32 frame_index;
|
||||||
|
i32 window_width;
|
||||||
|
i32 window_height;
|
||||||
|
bool update_swapchain;
|
||||||
|
|
||||||
|
SDL_Window *window;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Helpers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Fills rect with the NDC bounds (x0, y0, x1, y1) of the texture content when
|
||||||
|
// contained within the window, preserving aspect ratio and centering.
|
||||||
|
static void computeFitRect(f32 tex_w, f32 tex_h, f32 win_w, f32 win_h, f32 *rect) {
|
||||||
|
if (win_w <= 0.0f || win_h <= 0.0f || tex_w <= 0.0f || tex_h <= 0.0f) {
|
||||||
|
rect[0] = -1.0f;
|
||||||
|
rect[1] = -1.0f;
|
||||||
|
rect[2] = 1.0f;
|
||||||
|
rect[3] = 1.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
f32 scale_x = win_w / tex_w;
|
||||||
|
f32 scale_y = win_h / tex_h;
|
||||||
|
f32 scale = scale_x < scale_y ? scale_x : scale_y;
|
||||||
|
|
||||||
|
f32 content_w = tex_w * scale;
|
||||||
|
f32 content_h = tex_h * scale;
|
||||||
|
|
||||||
|
rect[0] = -content_w / win_w;
|
||||||
|
rect[1] = -content_h / win_h;
|
||||||
|
rect[2] = content_w / win_w;
|
||||||
|
rect[3] = content_h / win_h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Main
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
AppState app = {};
|
||||||
|
WpAllocator arena = wpMemArenaAllocatorInitZero(MiB(128));
|
||||||
|
|
||||||
|
// {{{ Initialisation
|
||||||
|
prRhiInit();
|
||||||
|
|
||||||
|
check(SDL_Init(SDL_INIT_VIDEO), EXIT_CODE_SDL_INIT_FAILED);
|
||||||
|
|
||||||
|
f32 display_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
|
||||||
|
app.window = SDL_CreateWindow("Prism — Texture Viewer", (i32)(display_scale * 1920),
|
||||||
|
(i32)(display_scale * 1080),
|
||||||
|
SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
|
||||||
|
check(app.window != nullptr, EXIT_CODE_WINDOW_CREATION_FAILED);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Instance creation
|
||||||
|
app.inst = prRhiCreateInstance(PrRhiInstanceDesc{});
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Physical device selection
|
||||||
|
PrRhiPhysicalDeviceArray pdevs = prRhiGetPhysicalDevices(app.inst);
|
||||||
|
check(wpArrayCount(pdevs) > 0, EXIT_CODE_NO_PHYSICAL_DEVICES);
|
||||||
|
|
||||||
|
i32 selected = -1;
|
||||||
|
for (u32 i = 0; i < wpArrayCount(pdevs); ++i) {
|
||||||
|
PrRhiPhysicalDeviceProperties props = prRhiGetPhysicalDeviceProperties(pdevs[i]);
|
||||||
|
switch (props.device_type) {
|
||||||
|
case PR_RHI_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
|
||||||
|
selected = (i32)i;
|
||||||
|
break;
|
||||||
|
case PR_RHI_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
|
||||||
|
if (selected == -1)
|
||||||
|
selected = (i32)i;
|
||||||
|
break;
|
||||||
|
default: continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(selected != -1, EXIT_CODE_NO_SUITABLE_PHYSICAL_DEVICE);
|
||||||
|
app.pdev = pdevs[selected];
|
||||||
|
|
||||||
|
// Print device info
|
||||||
|
WpStr8 dev_name = wpStr8Buf(512);
|
||||||
|
WpStr8 driver_info = wpStr8Buf(512);
|
||||||
|
prRhiGetPhysicalDeviceName(app.pdev, &dev_name);
|
||||||
|
prRhiGetPhysicalDeviceDriverInfo(app.pdev, &driver_info);
|
||||||
|
std::cout << "Selected GPU: " << std::string_view((const char *)dev_name.buf, dev_name.size) << '\n'
|
||||||
|
<< "Driver version: " << std::string_view((const char *)driver_info.buf, driver_info.size) << '\n';
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Surface creation
|
||||||
|
check(SDL_GetWindowSizeInPixels(app.window, &app.window_width, &app.window_height),
|
||||||
|
EXIT_CODE_GET_WINDOW_SIZE_FAILED);
|
||||||
|
app.surface = prRhiCreateSurfaceFromWindow(app.inst, app.window);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Device creation
|
||||||
|
PrRhiDeviceDesc dev_desc = {};
|
||||||
|
dev_desc.present_mode = PR_RHI_PRESENT_MODE_FIFO;
|
||||||
|
|
||||||
|
app.device = prRhiCreateDevice(app.pdev, app.surface, dev_desc);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Swapchain creation
|
||||||
|
PrRhiSwapchainDesc swap_desc = {};
|
||||||
|
swap_desc.surface = app.surface;
|
||||||
|
swap_desc.width = (u32)app.window_width;
|
||||||
|
swap_desc.height = (u32)app.window_height;
|
||||||
|
swap_desc.has_depth = true;
|
||||||
|
swap_desc.depth_format = PR_RHI_FORMAT_D24_UNORM_S8_UINT;
|
||||||
|
|
||||||
|
app.swapchain = prRhiCreateSwapchain(app.device, swap_desc);
|
||||||
|
app.swapchain_format = prRhiGetSwapchainFormat(app.swapchain);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Synchronisation objects
|
||||||
|
app.fences = wpArrayAllocCapacity(PrRhiFence *, &arena, AppState::max_frames_in_flight,
|
||||||
|
WP_ARRAY_INIT_FILLED);
|
||||||
|
for (u32 i = 0; i < AppState::max_frames_in_flight; ++i) {
|
||||||
|
PrRhiFenceDesc fd = {};
|
||||||
|
fd.signaled = true;
|
||||||
|
app.fences[i] = prRhiCreateFence(app.device, fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.image_acquired_semaphores = wpArrayAllocCapacity(PrRhiSemaphore *, &arena,
|
||||||
|
AppState::max_frames_in_flight,
|
||||||
|
WP_ARRAY_INIT_FILLED);
|
||||||
|
for (u32 i = 0; i < AppState::max_frames_in_flight; ++i) {
|
||||||
|
app.image_acquired_semaphores[i] = prRhiCreateSemaphore(app.device);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.render_semaphore_count = prRhiGetSwapchainImageCount(app.swapchain);
|
||||||
|
app.render_completed_semaphores = wpArrayAllocCapacity(PrRhiSemaphore *, &arena,
|
||||||
|
app.render_semaphore_count,
|
||||||
|
WP_ARRAY_INIT_FILLED);
|
||||||
|
for (u32 i = 0; i < app.render_semaphore_count; ++i) {
|
||||||
|
app.render_completed_semaphores[i] = prRhiCreateSemaphore(app.device);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Command pool and buffers
|
||||||
|
app.cmd_pool = prRhiCreateCommandPool(app.device);
|
||||||
|
app.cmd_buffers = prRhiAllocateCommandBuffers(app.device, app.cmd_pool,
|
||||||
|
AppState::max_frames_in_flight);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Texture loading
|
||||||
|
app.textures = wpArrayAllocCapacity(TextureResources, &arena, AppState::texture_count,
|
||||||
|
WP_ARRAY_INIT_FILLED);
|
||||||
|
|
||||||
|
PrRhiCommandBufferArray upload_cbs = prRhiAllocateCommandBuffers(app.device, app.cmd_pool, 1);
|
||||||
|
PrRhiCommandBuffer *upload_cb = upload_cbs[0];
|
||||||
|
|
||||||
|
for (u32 i = 0; i < AppState::texture_count; ++i) {
|
||||||
|
PrRhiTexture *tex = prRhiCreateTextureFromKtx(app.device, TEXTURE_PATHS[i], app.cmd_pool, upload_cb);
|
||||||
|
|
||||||
|
PrRhiSamplerDesc samp_desc = {};
|
||||||
|
samp_desc.mag_filter = PR_RHI_FILTER_LINEAR;
|
||||||
|
samp_desc.min_filter = PR_RHI_FILTER_LINEAR;
|
||||||
|
samp_desc.mipmap_mode = PR_RHI_MIPMAP_MODE_LINEAR;
|
||||||
|
samp_desc.max_anisotropy = 8.0f;
|
||||||
|
samp_desc.max_lod = PR_RHI_LOD_CLAMP_NONE;
|
||||||
|
|
||||||
|
PrRhiSampler *sampler = prRhiCreateSampler(app.device, samp_desc);
|
||||||
|
|
||||||
|
app.textures[i].texture = tex;
|
||||||
|
app.textures[i].sampler = sampler;
|
||||||
|
}
|
||||||
|
|
||||||
|
prRhiFreeCommandBuffers(app.device, app.cmd_pool, upload_cbs);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Descriptor set layout, pool, set
|
||||||
|
PrRhiDescriptorSetLayoutBindingArray ds_layouts = wpArray(
|
||||||
|
PrRhiDescriptorSetLayoutBinding,
|
||||||
|
PrRhiDescriptorSetLayoutBinding{
|
||||||
|
PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
|
AppState::texture_count,
|
||||||
|
PR_RHI_SHADER_STAGE_FRAGMENT,
|
||||||
|
PR_RHI_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PrRhiDescriptorSetLayoutDesc layout_desc = { ds_layouts };
|
||||||
|
app.desc_set_layout = prRhiCreateDescriptorSetLayout(app.device, layout_desc);
|
||||||
|
|
||||||
|
PrRhiDescriptorPoolSize pool_size = {};
|
||||||
|
pool_size.type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
pool_size.descriptor_count = AppState::texture_count;
|
||||||
|
|
||||||
|
PrRhiDescriptorPoolDesc ds_pool_desc = {};
|
||||||
|
ds_pool_desc.max_sets = 1;
|
||||||
|
ds_pool_desc.pool_sizes = wpArray(PrRhiDescriptorPoolSize, pool_size);
|
||||||
|
|
||||||
|
app.desc_pool = prRhiCreateDescriptorPool(app.device, ds_pool_desc);
|
||||||
|
|
||||||
|
WpU32Array var_counts = wpArray(u32, AppState::texture_count);
|
||||||
|
app.desc_set = prRhiAllocateDescriptorSet(app.device, app.desc_pool, app.desc_set_layout,
|
||||||
|
var_counts);
|
||||||
|
|
||||||
|
PrRhiDescriptorImageInfoArray img_infos = wpArrayAllocCapacity(PrRhiDescriptorImageInfo,
|
||||||
|
&arena,
|
||||||
|
AppState::texture_count,
|
||||||
|
WP_ARRAY_INIT_NONE);
|
||||||
|
for (u32 i = 0; i < AppState::texture_count; ++i) {
|
||||||
|
PrRhiDescriptorImageInfo info = {};
|
||||||
|
info.texture = app.textures[i].texture;
|
||||||
|
info.sampler = app.textures[i].sampler;
|
||||||
|
info.layout = PR_RHI_LAYOUT_READ_ONLY_OPTIMAL;
|
||||||
|
wpArrayAppendCapped(PrRhiDescriptorImageInfo, img_infos, &info);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrRhiWriteDescriptorSet write = {};
|
||||||
|
write.dst_set = app.desc_set;
|
||||||
|
write.dst_binding = 0;
|
||||||
|
write.type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
write.image_info = img_infos;
|
||||||
|
|
||||||
|
PrRhiWriteDescriptorSetArray writes = wpArray(PrRhiWriteDescriptorSet, write);
|
||||||
|
prRhiUpdateDescriptorSet(app.device, writes);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Load blit shader (pre-compiled SPIR-V)
|
||||||
|
WpStr8RO spirv_path = wpStr8LitRo("build/shaders/blit.spv");
|
||||||
|
WpFile *spirv_file = wpFileOpen(&arena, &spirv_path, WP_ACCESS_READ);
|
||||||
|
check(spirv_file != nullptr, EXIT_CODE_SHADER_LOAD_FAILED);
|
||||||
|
|
||||||
|
i64 spirv_len = wpFileGetLength(spirv_file);
|
||||||
|
void *spirv = wpMemAllocatorAlloc(&arena, (u64)spirv_len);
|
||||||
|
check(spirv != nullptr, EXIT_CODE_ALLOCATION_FAILURE);
|
||||||
|
wpFileRead(spirv, spirv_file, (u64)spirv_len);
|
||||||
|
wpFileClose(spirv_file);
|
||||||
|
|
||||||
|
PrRhiShaderDesc shader_desc = {};
|
||||||
|
shader_desc.spirv_code = spirv;
|
||||||
|
shader_desc.spirv_size = (u64)spirv_len;
|
||||||
|
|
||||||
|
app.shader = prRhiCreateShader(app.device, shader_desc);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Pipeline layout
|
||||||
|
PrRhiPushConstantRange pc_range = {};
|
||||||
|
pc_range.stage_flags = (PrRhiShaderStage)(PR_RHI_SHADER_STAGE_VERTEX |
|
||||||
|
PR_RHI_SHADER_STAGE_FRAGMENT);
|
||||||
|
pc_range.size = sizeof(BlitData);
|
||||||
|
|
||||||
|
PrRhiDescriptorSetLayoutArray pl_layouts = wpArray(PrRhiDescriptorSetLayout *, app.desc_set_layout);
|
||||||
|
|
||||||
|
PrRhiPipelineLayoutDesc pl_desc = {};
|
||||||
|
pl_desc.set_layouts = pl_layouts;
|
||||||
|
pl_desc.push_constant_ranges = wpArray(PrRhiPushConstantRange, pc_range);
|
||||||
|
|
||||||
|
app.pipeline_layout = prRhiCreatePipelineLayout(app.device, pl_desc);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Graphics pipeline
|
||||||
|
PrRhiColorBlendAttachmentArray blend_attachments = wpArray(
|
||||||
|
PrRhiColorBlendAttachment,
|
||||||
|
PrRhiColorBlendAttachment{ 0xf }
|
||||||
|
);
|
||||||
|
|
||||||
|
PrRhiFormatArray color_fmt_array = wpArray(PrRhiFormat, app.swapchain_format);
|
||||||
|
|
||||||
|
PrRhiGraphicsPipelineDesc pipe_desc = {};
|
||||||
|
pipe_desc.vertex_shader = app.shader;
|
||||||
|
pipe_desc.vertex_shader_entry_point = "main";
|
||||||
|
pipe_desc.fragment_shader = app.shader;
|
||||||
|
pipe_desc.fragment_shader_entry_point = "main";
|
||||||
|
pipe_desc.topology = PR_RHI_TOPOLOGY_TRIANGLE_STRIP;
|
||||||
|
pipe_desc.color_attachment_formats = color_fmt_array;
|
||||||
|
pipe_desc.depth_attachment_format = swap_desc.depth_format;
|
||||||
|
pipe_desc.depth_test_enable = false;
|
||||||
|
pipe_desc.depth_write_enable = false;
|
||||||
|
pipe_desc.blend_attachments = blend_attachments;
|
||||||
|
pipe_desc.dynamic_viewport = true;
|
||||||
|
pipe_desc.dynamic_scissor = true;
|
||||||
|
pipe_desc.cull_mode = PR_RHI_CULL_MODE_NONE;
|
||||||
|
pipe_desc.line_width = 1.0f;
|
||||||
|
pipe_desc.multisample_count = PR_RHI_SAMPLE_COUNT_1;
|
||||||
|
pipe_desc.layout = app.pipeline_layout;
|
||||||
|
|
||||||
|
app.pipeline = prRhiCreateGraphicsPipeline(app.device, pipe_desc);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Render loop
|
||||||
|
app.frame_index = 0;
|
||||||
|
app.selected = 0;
|
||||||
|
u32 image_index = 0;
|
||||||
|
PrRhiTextureSize tex_size = prRhiGetTextureSize(app.textures[app.selected].texture);
|
||||||
|
std::cout << "Texture " << app.selected << ": " << TEXTURE_PATHS[app.selected]
|
||||||
|
<< " (" << tex_size.width << 'x' << tex_size.height << ")\n";
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// {{{ Wait on fence
|
||||||
|
PrRhiFence *wait_fence = app.fences[app.frame_index];
|
||||||
|
prRhiWaitForFences(app.device, wpArray(PrRhiFence *, wait_fence), 1, true, UINT64_MAX);
|
||||||
|
prRhiResetFences(app.device, wpArray(PrRhiFence *, wait_fence), 1);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Acquire next image
|
||||||
|
PrRhiSwapchainResult acq = prRhiAcquireNextImage(app.device, app.swapchain,
|
||||||
|
app.image_acquired_semaphores[app.frame_index],
|
||||||
|
&image_index);
|
||||||
|
if (acq == PR_RHI_SWAPCHAIN_OUT_OF_DATE) {
|
||||||
|
app.update_swapchain = true;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
if (app.update_swapchain) {
|
||||||
|
// Skip this frame — will recreate below
|
||||||
|
} else {
|
||||||
|
// {{{ Compute contain-fit rect
|
||||||
|
PrRhiTextureSize tex_size = prRhiGetTextureSize(app.textures[app.selected].texture);
|
||||||
|
f32 fit_rect[4] = {};
|
||||||
|
computeFitRect((f32)tex_size.width, (f32)tex_size.height,
|
||||||
|
(f32)app.window_width, (f32)app.window_height, fit_rect);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Record command buffer
|
||||||
|
PrRhiCommandBuffer *cb = app.cmd_buffers[app.frame_index];
|
||||||
|
prRhiResetCommandBuffer(cb);
|
||||||
|
prRhiBeginCommandBuffer(cb);
|
||||||
|
|
||||||
|
// Transition images to attachment optimal
|
||||||
|
{
|
||||||
|
PrRhiImageMemoryBarrierArray barriers_arr =
|
||||||
|
wpArrayWithCapacity(PrRhiImageMemoryBarrier, 2, WP_ARRAY_INIT_FILLED);
|
||||||
|
|
||||||
|
PrRhiTexture *color_tex = prRhiGetSwapchainTexture(app.swapchain, image_index);
|
||||||
|
barriers_arr[0].texture = color_tex;
|
||||||
|
barriers_arr[0].old_layout = PR_RHI_LAYOUT_UNDEFINED;
|
||||||
|
barriers_arr[0].new_layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL;
|
||||||
|
barriers_arr[0].src_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT;
|
||||||
|
barriers_arr[0].src_access_mask = PR_RHI_ACCESS_NONE;
|
||||||
|
barriers_arr[0].dst_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT;
|
||||||
|
barriers_arr[0].dst_access_mask = (PrRhiAccess)(PR_RHI_ACCESS_COLOR_ATTACHMENT_READ | PR_RHI_ACCESS_COLOR_ATTACHMENT_WRITE);
|
||||||
|
|
||||||
|
PrRhiTexture *depth_tex = prRhiGetSwapchainDepthTexture(app.swapchain);
|
||||||
|
barriers_arr[1].texture = depth_tex;
|
||||||
|
barriers_arr[1].old_layout = PR_RHI_LAYOUT_UNDEFINED;
|
||||||
|
barriers_arr[1].new_layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL;
|
||||||
|
barriers_arr[1].src_stage_mask = PR_RHI_PIPELINE_STAGE_LATE_FRAGMENT_TESTS;
|
||||||
|
barriers_arr[1].src_access_mask = PR_RHI_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE;
|
||||||
|
barriers_arr[1].dst_stage_mask = PR_RHI_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS;
|
||||||
|
barriers_arr[1].dst_access_mask = PR_RHI_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE;
|
||||||
|
|
||||||
|
prRhiCmdPipelineBarrier(cb, barriers_arr, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rendering
|
||||||
|
{
|
||||||
|
PrRhiColorAttachmentArray color_arr =
|
||||||
|
wpArrayWithCapacity(PrRhiColorAttachment, 1, WP_ARRAY_INIT_FILLED);
|
||||||
|
color_arr[0].texture = prRhiGetSwapchainTexture(app.swapchain, image_index);
|
||||||
|
color_arr[0].layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL;
|
||||||
|
color_arr[0].clear = true;
|
||||||
|
color_arr[0].clear_color[0] = 0.0f;
|
||||||
|
color_arr[0].clear_color[1] = 0.0f;
|
||||||
|
color_arr[0].clear_color[2] = 0.0f;
|
||||||
|
color_arr[0].clear_color[3] = 0.0f;
|
||||||
|
|
||||||
|
PrRhiDepthAttachment depth_att = {};
|
||||||
|
depth_att.texture = prRhiGetSwapchainDepthTexture(app.swapchain);
|
||||||
|
depth_att.layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL;
|
||||||
|
depth_att.clear = true;
|
||||||
|
depth_att.clear_depth = 1.0f;
|
||||||
|
|
||||||
|
prRhiCmdBeginRendering(cb, color_arr, &depth_att);
|
||||||
|
}
|
||||||
|
|
||||||
|
prRhiCmdSetViewport(cb, 0.0f, 0.0f, (f32)app.window_width, (f32)app.window_height);
|
||||||
|
prRhiCmdSetScissor(cb, 0, 0, (u32)app.window_width, (u32)app.window_height);
|
||||||
|
|
||||||
|
prRhiCmdBindPipeline(cb, PR_RHI_PIPELINE_BIND_POINT_GRAPHICS, app.pipeline);
|
||||||
|
|
||||||
|
PrRhiDescriptorSetArray sets = wpArray(PrRhiDescriptorSet *, app.desc_set);
|
||||||
|
prRhiCmdBindDescriptorSets(cb, PR_RHI_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
|
app.pipeline_layout, 0, sets);
|
||||||
|
|
||||||
|
// Draw background (fullscreen grey quad)
|
||||||
|
BlitData bg = {};
|
||||||
|
bg.rect[0] = -1.0f;
|
||||||
|
bg.rect[1] = -1.0f;
|
||||||
|
bg.rect[2] = 1.0f;
|
||||||
|
bg.rect[3] = 1.0f;
|
||||||
|
bg.selected = 0;
|
||||||
|
bg.mode = 1;
|
||||||
|
prRhiCmdPushConstants(cb, app.pipeline_layout,
|
||||||
|
(PrRhiShaderStage)(PR_RHI_SHADER_STAGE_VERTEX |
|
||||||
|
PR_RHI_SHADER_STAGE_FRAGMENT),
|
||||||
|
0, sizeof(BlitData), &bg);
|
||||||
|
prRhiCmdDraw(cb, 4, 1, 0, 0);
|
||||||
|
|
||||||
|
// Draw texture (contain-fit)
|
||||||
|
BlitData blit = {};
|
||||||
|
blit.rect[0] = fit_rect[0];
|
||||||
|
blit.rect[1] = fit_rect[1];
|
||||||
|
blit.rect[2] = fit_rect[2];
|
||||||
|
blit.rect[3] = fit_rect[3];
|
||||||
|
blit.selected = app.selected;
|
||||||
|
blit.mode = 0;
|
||||||
|
prRhiCmdPushConstants(cb, app.pipeline_layout,
|
||||||
|
(PrRhiShaderStage)(PR_RHI_SHADER_STAGE_VERTEX |
|
||||||
|
PR_RHI_SHADER_STAGE_FRAGMENT),
|
||||||
|
0, sizeof(BlitData), &blit);
|
||||||
|
|
||||||
|
prRhiCmdDraw(cb, 4, 1, 0, 0);
|
||||||
|
prRhiCmdEndRendering(cb);
|
||||||
|
|
||||||
|
// Transition to present
|
||||||
|
{
|
||||||
|
PrRhiImageMemoryBarrierArray present_barriers =
|
||||||
|
wpArrayWithCapacity(PrRhiImageMemoryBarrier, 1, WP_ARRAY_INIT_FILLED);
|
||||||
|
present_barriers[0].texture = prRhiGetSwapchainTexture(app.swapchain, image_index);
|
||||||
|
present_barriers[0].old_layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL;
|
||||||
|
present_barriers[0].new_layout = PR_RHI_LAYOUT_PRESENT_SRC;
|
||||||
|
present_barriers[0].src_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT;
|
||||||
|
present_barriers[0].src_access_mask = PR_RHI_ACCESS_COLOR_ATTACHMENT_WRITE;
|
||||||
|
present_barriers[0].dst_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT;
|
||||||
|
present_barriers[0].dst_access_mask = PR_RHI_ACCESS_NONE;
|
||||||
|
|
||||||
|
prRhiCmdPipelineBarrier(cb, present_barriers, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
prRhiEndCommandBuffer(cb);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Submit
|
||||||
|
prRhiQueueSubmit(app.device, cb, app.image_acquired_semaphores[app.frame_index],
|
||||||
|
app.render_completed_semaphores[image_index], app.fences[app.frame_index]);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Present
|
||||||
|
PrRhiSwapchainResult pres = prRhiPresent(app.device, app.swapchain,
|
||||||
|
app.render_completed_semaphores[image_index]);
|
||||||
|
if (pres == PR_RHI_SWAPCHAIN_OUT_OF_DATE) {
|
||||||
|
app.update_swapchain = true;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{{ Poll events
|
||||||
|
SDL_Event event = {};
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_EVENT_QUIT:
|
||||||
|
goto done;
|
||||||
|
case SDL_EVENT_KEY_DOWN:
|
||||||
|
if (event.key.key == SDLK_ESCAPE) goto done;
|
||||||
|
if (event.key.key == SDLK_PLUS || event.key.key == SDLK_KP_PLUS || event.key.key == SDLK_EQUALS) {
|
||||||
|
app.selected = (app.selected + 1) % AppState::texture_count;
|
||||||
|
PrRhiTextureSize ts = prRhiGetTextureSize(app.textures[app.selected].texture);
|
||||||
|
std::cout << "Texture " << app.selected << ": " << TEXTURE_PATHS[app.selected]
|
||||||
|
<< " (" << ts.width << 'x' << ts.height << ")\n";
|
||||||
|
}
|
||||||
|
if (event.key.key == SDLK_MINUS || event.key.key == SDLK_KP_MINUS) {
|
||||||
|
app.selected = (app.selected + AppState::texture_count - 1) % AppState::texture_count;
|
||||||
|
PrRhiTextureSize ts = prRhiGetTextureSize(app.textures[app.selected].texture);
|
||||||
|
std::cout << "Texture " << app.selected << ": " << TEXTURE_PATHS[app.selected]
|
||||||
|
<< " (" << ts.width << 'x' << ts.height << ")\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_WINDOW_RESIZED:
|
||||||
|
check(SDL_GetWindowSizeInPixels(app.window, &app.window_width, &app.window_height),
|
||||||
|
EXIT_CODE_GET_WINDOW_SIZE_FAILED);
|
||||||
|
app.update_swapchain = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Swapchain recreate
|
||||||
|
if (app.update_swapchain) {
|
||||||
|
prRhiDeviceWaitIdle(app.device);
|
||||||
|
prRhiRecreateSwapchain(app.device, &app.swapchain,
|
||||||
|
(u32)app.window_width, (u32)app.window_height);
|
||||||
|
|
||||||
|
// Re-create render completed semaphores for the new image count
|
||||||
|
u32 new_count = prRhiGetSwapchainImageCount(app.swapchain);
|
||||||
|
for (u32 i = 0; i < app.render_semaphore_count; ++i) {
|
||||||
|
prRhiDestroySemaphore(app.device, app.render_completed_semaphores[i]);
|
||||||
|
}
|
||||||
|
app.render_semaphore_count = new_count;
|
||||||
|
app.render_completed_semaphores = wpArrayAllocCapacity(PrRhiSemaphore *, &arena,
|
||||||
|
new_count, WP_ARRAY_INIT_FILLED);
|
||||||
|
for (u32 i = 0; i < new_count; ++i) {
|
||||||
|
app.render_completed_semaphores[i] = prRhiCreateSemaphore(app.device);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.update_swapchain = false;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
app.frame_index = (app.frame_index + 1) % AppState::max_frames_in_flight;
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
// {{{ Cleanup
|
||||||
|
prRhiDeviceWaitIdle(app.device);
|
||||||
|
|
||||||
|
prRhiDestroyPipeline(app.device, app.pipeline);
|
||||||
|
prRhiDestroyPipelineLayout(app.device, app.pipeline_layout);
|
||||||
|
prRhiDestroyShader(app.device, app.shader);
|
||||||
|
prRhiDestroyDescriptorPool(app.device, app.desc_pool);
|
||||||
|
prRhiDestroyDescriptorSetLayout(app.device, app.desc_set_layout);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < AppState::texture_count; ++i) {
|
||||||
|
prRhiDestroySampler(app.device, app.textures[i].sampler);
|
||||||
|
prRhiDestroyTexture(app.device, app.textures[i].texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
prRhiFreeCommandBuffers(app.device, app.cmd_pool, app.cmd_buffers);
|
||||||
|
prRhiDestroyCommandPool(app.device, app.cmd_pool);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < app.render_semaphore_count; ++i) {
|
||||||
|
prRhiDestroySemaphore(app.device, app.render_completed_semaphores[i]);
|
||||||
|
}
|
||||||
|
for (u32 i = 0; i < AppState::max_frames_in_flight; ++i) {
|
||||||
|
prRhiDestroySemaphore(app.device, app.image_acquired_semaphores[i]);
|
||||||
|
prRhiDestroyFence(app.device, app.fences[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
prRhiDestroySwapchain(app.device, app.swapchain);
|
||||||
|
prRhiDestroyDevice(app.device);
|
||||||
|
prRhiDestroySurface(app.inst, app.surface);
|
||||||
|
prRhiDestroyInstance(app.inst);
|
||||||
|
|
||||||
|
SDL_DestroyWindow(app.window);
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
prRhiDestroy();
|
||||||
|
wpMemArenaAllocatorDestroy(&arena);
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
return EXIT_CODE_SUCCESS;
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
// vim:fileencoding=utf-8:foldmethod=marker
|
||||||
|
|
||||||
|
#include "pr_pool_allocator.h"
|
||||||
|
#include "../../vendor/wapp/os/mem/mem_os.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Intrusive free list node — reuses the first sizeof(void*) bytes of each slot
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct PrPoolFreeNode PrPoolFreeNode;
|
||||||
|
struct PrPoolFreeNode {
|
||||||
|
PrPoolFreeNode *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Internal: allocate a new block and carve it into the free list
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
wp_intern b8 _prPoolGrow(PrPool *pool) {
|
||||||
|
// Grow blocks array if full
|
||||||
|
if (pool->block_count == pool->block_cap) {
|
||||||
|
u64 new_cap = pool->block_cap ? pool->block_cap * 2 : 4;
|
||||||
|
u64 new_size = sizeof(void *) * new_cap;
|
||||||
|
void **new_arr = (void **)wpOsMemAlloc(NULL, new_size, WP_MEM_ACCESS_READ_WRITE,
|
||||||
|
WP_MEM_ALLOC_RESERVE, WP_MEM_INIT_UNINITIALISED);
|
||||||
|
if (!new_arr) { return false; }
|
||||||
|
|
||||||
|
if (pool->blocks) {
|
||||||
|
memcpy(new_arr, pool->blocks, sizeof(void *) * pool->block_count);
|
||||||
|
wpOsMemFree(pool->blocks, sizeof(void *) * pool->block_cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
pool->blocks = new_arr;
|
||||||
|
pool->block_cap = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the block
|
||||||
|
u64 block_bytes = pool->alloc_size * pool->block_slots;
|
||||||
|
void *block = wpOsMemAlloc(NULL, block_bytes, WP_MEM_ACCESS_READ_WRITE,
|
||||||
|
WP_MEM_ALLOC_RESERVE, WP_MEM_INIT_UNINITIALISED);
|
||||||
|
if (!block) { return false; }
|
||||||
|
|
||||||
|
pool->blocks[pool->block_count++] = block;
|
||||||
|
pool->total += pool->block_slots;
|
||||||
|
|
||||||
|
// Carve into free list (in reverse so the first slot ends up on top)
|
||||||
|
u8 *bytes = (u8 *)block;
|
||||||
|
for (u64 i = pool->block_slots; i > 0; --i) {
|
||||||
|
PrPoolFreeNode *node = (PrPoolFreeNode *)bytes;
|
||||||
|
node->next = (PrPoolFreeNode *)pool->free_list;
|
||||||
|
pool->free_list = node;
|
||||||
|
bytes += pool->alloc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Public API
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void prPoolInit(PrPool *pool, u64 slot_size, u64 initial_slots) {
|
||||||
|
memset(pool, 0, sizeof(*pool));
|
||||||
|
pool->slot_size = slot_size;
|
||||||
|
pool->alloc_size = slot_size < sizeof(PrPoolFreeNode) ? sizeof(PrPoolFreeNode) : slot_size;
|
||||||
|
|
||||||
|
// Enforce minimum block size of 4096 bytes
|
||||||
|
u64 min_slots = (4096 + pool->alloc_size - 1) / pool->alloc_size;
|
||||||
|
pool->block_slots = initial_slots > min_slots ? initial_slots : min_slots;
|
||||||
|
|
||||||
|
_prPoolGrow(pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *prPoolAlloc(PrPool *pool) {
|
||||||
|
// Grow if free list is empty
|
||||||
|
if (!pool->free_list) {
|
||||||
|
if (!_prPoolGrow(pool)) { return NULL; }
|
||||||
|
}
|
||||||
|
|
||||||
|
PrPoolFreeNode *node = (PrPoolFreeNode *)pool->free_list;
|
||||||
|
pool->free_list = node->next;
|
||||||
|
pool->active++;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void prPoolFree(PrPool *pool, void *slot) {
|
||||||
|
if (!slot) { return; }
|
||||||
|
|
||||||
|
PrPoolFreeNode *node = (PrPoolFreeNode *)slot;
|
||||||
|
node->next = (PrPoolFreeNode *)pool->free_list;
|
||||||
|
pool->free_list = node;
|
||||||
|
pool->active--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void prPoolDestroy(PrPool *pool) {
|
||||||
|
u64 block_bytes = pool->alloc_size * pool->block_slots;
|
||||||
|
for (u64 i = 0; i < pool->block_count; ++i) {
|
||||||
|
wpOsMemFree(pool->blocks[i], block_bytes);
|
||||||
|
}
|
||||||
|
if (pool->blocks) {
|
||||||
|
wpOsMemFree(pool->blocks, sizeof(void *) * pool->block_cap);
|
||||||
|
}
|
||||||
|
memset(pool, 0, sizeof(*pool));
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 prPoolTotalSlots(const PrPool *pool) {
|
||||||
|
return pool->total;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 prPoolActiveSlots(const PrPool *pool) {
|
||||||
|
return pool->active;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// vim:fileencoding=utf-8:foldmethod=marker
|
||||||
|
|
||||||
|
#ifndef PR_POOL_ALLOCATOR_H
|
||||||
|
#define PR_POOL_ALLOCATOR_H
|
||||||
|
|
||||||
|
#include "../../vendor/wapp/common/aliases/aliases.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Pool allocator — fixed-size slot allocator with self-managed growth
|
||||||
|
//
|
||||||
|
// Manages fixed-size slots arranged in contiguous blocks. The pool owns its
|
||||||
|
// memory (via wapp OS allocation) and grows on demand when the free list is empty.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// PrPool pool;
|
||||||
|
// prPoolInit(&pool, sizeof(Edge), 64);
|
||||||
|
// Edge *e = prPoolAlloc(&pool);
|
||||||
|
// prPoolFree(&pool, e);
|
||||||
|
// prPoolDestroy(&pool);
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct PrPool PrPool;
|
||||||
|
|
||||||
|
struct PrPool {
|
||||||
|
void **blocks; // array of allocated block pointers (for destroy)
|
||||||
|
u64 block_count; // number of allocated blocks
|
||||||
|
u64 block_cap; // capacity of blocks array
|
||||||
|
void *free_list; // intrusive free list head
|
||||||
|
u64 slot_size; // user-requested slot size
|
||||||
|
u64 alloc_size; // actual slot size used internally (>= slot_size, >= sizeof(void*))
|
||||||
|
u64 block_slots; // slots per block
|
||||||
|
u64 total; // total slots ever allocated (diagnostics)
|
||||||
|
u64 active; // currently in use (diagnostics)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialise a pool.
|
||||||
|
// slot_size: fixed size of each slot
|
||||||
|
// initial_slots: starting capacity in slots (also used as block size)
|
||||||
|
void prPoolInit(PrPool *pool, u64 slot_size, u64 initial_slots);
|
||||||
|
|
||||||
|
// Allocate one slot. Grows by a new block if the free list is empty.
|
||||||
|
// Returns NULL only on allocation failure.
|
||||||
|
void *prPoolAlloc(PrPool *pool);
|
||||||
|
|
||||||
|
// Return a slot to the pool's free list. Safe no-op on NULL.
|
||||||
|
void prPoolFree(PrPool *pool, void *slot);
|
||||||
|
|
||||||
|
// Free all blocks and zero the pool.
|
||||||
|
void prPoolDestroy(PrPool *pool);
|
||||||
|
|
||||||
|
// Diagnostics
|
||||||
|
u64 prPoolTotalSlots(const PrPool *pool);
|
||||||
|
u64 prPoolActiveSlots(const PrPool *pool);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // !PR_POOL_ALLOCATOR_H
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// vim:fileencoding=utf-8:foldmethod=marker
|
||||||
|
//
|
||||||
|
// RHI context — shared initialisation and global state.
|
||||||
|
|
||||||
|
#include "pr_rhi.h"
|
||||||
|
|
||||||
|
PrRhiContext _G_RHI_CONTEXT;
|
||||||
|
|
||||||
|
void prRhiInit(void) {
|
||||||
|
_G_RHI_CONTEXT.allocator = wpMemArenaAllocatorInit(MiB(64));
|
||||||
|
}
|
||||||
|
|
||||||
|
void prRhiDestroy(void) {
|
||||||
|
wpMemArenaAllocatorDestroy(&_G_RHI_CONTEXT.allocator);
|
||||||
|
}
|
||||||
@@ -24,28 +24,39 @@
|
|||||||
#define PR_RHI_H
|
#define PR_RHI_H
|
||||||
|
|
||||||
#include "pr_rhi_types.h"
|
#include "pr_rhi_types.h"
|
||||||
|
#include <SDL3/SDL_video.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
wp_extern PrRhiContext _G_RHI_CONTEXT;
|
||||||
|
|
||||||
|
wp_extern void prRhiInit(void);
|
||||||
|
wp_extern void prRhiDestroy(void);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Instance
|
// Instance
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiInstance *prRhiCreateInstance(PrRhiInstanceDesc desc, WpAllocator *alloc);
|
PrRhiInstance *prRhiCreateInstance(PrRhiInstanceDesc desc);
|
||||||
void prRhiDestroyInstance(PrRhiInstance *inst, WpAllocator *alloc);
|
void prRhiDestroyInstance(PrRhiInstance *inst);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Physical device enumeration
|
// Physical device enumeration
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiPhysicalDeviceArray prRhiGetPhysicalDevices(PrRhiInstance *inst, const WpAllocator *scratch);
|
PrRhiPhysicalDeviceArray prRhiGetPhysicalDevices(PrRhiInstance *inst);
|
||||||
void prRhiGetPhysicalDeviceName(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
void prRhiGetPhysicalDeviceName(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
||||||
void prRhiGetPhysicalDeviceDriverInfo(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
void prRhiGetPhysicalDeviceDriverInfo(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
||||||
|
PrRhiPhysicalDeviceProperties prRhiGetPhysicalDeviceProperties(PrRhiPhysicalDevice *pdev);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Surface (platform-specific)
|
// Surface (platform-specific)
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiSurface *prRhiCreateSurface(PrRhiInstance *inst, void *window_handle, WpAllocator *alloc);
|
PrRhiSurface *prRhiCreateSurfaceFromWindow(PrRhiInstance *inst, SDL_Window *window);
|
||||||
void prRhiDestroySurface(PrRhiInstance *inst, PrRhiSurface *surface, WpAllocator *alloc);
|
void prRhiDestroySurface(PrRhiInstance *inst, PrRhiSurface *surface);
|
||||||
PrRhiSurfaceCapabilities prRhiGetSurfaceCapabilities(PrRhiPhysicalDevice *pdev,
|
PrRhiSurfaceCapabilities prRhiGetSurfaceCapabilities(PrRhiPhysicalDevice *pdev,
|
||||||
PrRhiSurface *surface);
|
PrRhiSurface *surface);
|
||||||
|
|
||||||
@@ -54,19 +65,19 @@ PrRhiSurfaceCapabilities prRhiGetSurfaceCapabilities(PrRhiPhysicalDevice *pdev,
|
|||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface,
|
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface,
|
||||||
PrRhiDeviceDesc desc, WpAllocator *alloc);
|
PrRhiDeviceDesc desc);
|
||||||
void prRhiDestroyDevice(PrRhiDevice *device, WpAllocator *alloc);
|
void prRhiDestroyDevice(PrRhiDevice *device);
|
||||||
void prRhiDeviceWaitIdle(PrRhiDevice *device);
|
void prRhiDeviceWaitIdle(PrRhiDevice *device);
|
||||||
|
u32 prRhiGetQueueFamilyIndex(PrRhiDevice *device);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Swapchain
|
// Swapchain
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiSwapchain *prRhiCreateSwapchain(PrRhiDevice *device, PrRhiSwapchainDesc desc,
|
PrRhiSwapchain *prRhiCreateSwapchain(PrRhiDevice *device, PrRhiSwapchainDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroySwapchain(PrRhiDevice *device, PrRhiSwapchain *swapchain);
|
||||||
void prRhiDestroySwapchain(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
|
u32 prRhiGetSwapchainImageCount(PrRhiSwapchain *swapchain);
|
||||||
PrRhiSwapchainResult prRhiAcquireNextImage(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
PrRhiSwapchainResult prRhiAcquireNextImage(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
||||||
PrRhiSemaphore *signal_semaphore, u32 *out_image_index);
|
PrRhiSemaphore *signal_semaphore, u32 *out_image_index);
|
||||||
|
|
||||||
@@ -74,18 +85,18 @@ PrRhiSwapchainResult prRhiPresent(PrRhiDevice *device, PrRhiSwapchain *swapchain
|
|||||||
PrRhiSemaphore *wait_semaphore);
|
PrRhiSemaphore *wait_semaphore);
|
||||||
|
|
||||||
void prRhiRecreateSwapchain(PrRhiDevice *device, PrRhiSwapchain **swapchain,
|
void prRhiRecreateSwapchain(PrRhiDevice *device, PrRhiSwapchain **swapchain,
|
||||||
u32 width, u32 height, WpAllocator *alloc);
|
u32 width, u32 height);
|
||||||
|
|
||||||
PrRhiTexture *prRhiGetSwapchainTexture(PrRhiSwapchain *swapchain, u32 image_index);
|
PrRhiTexture *prRhiGetSwapchainTexture(PrRhiSwapchain *swapchain, u32 image_index);
|
||||||
PrRhiTexture *prRhiGetSwapchainDepthTexture(PrRhiSwapchain *swapchain);
|
PrRhiTexture *prRhiGetSwapchainDepthTexture(PrRhiSwapchain *swapchain);
|
||||||
|
PrRhiFormat prRhiGetSwapchainFormat(PrRhiSwapchain *swapchain);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Buffers
|
// Buffers
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiBuffer *prRhiCreateBuffer(PrRhiDevice *device, PrRhiBufferDesc desc,
|
PrRhiBuffer *prRhiCreateBuffer(PrRhiDevice *device, PrRhiBufferDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyBuffer(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||||
void prRhiDestroyBuffer(PrRhiDevice *device, PrRhiBuffer *buffer, WpAllocator *alloc);
|
|
||||||
|
|
||||||
void *prRhiBufferMap(PrRhiDevice *device, PrRhiBuffer *buffer);
|
void *prRhiBufferMap(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||||
void prRhiBufferUnmap(PrRhiDevice *device, PrRhiBuffer *buffer);
|
void prRhiBufferUnmap(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||||
@@ -96,73 +107,64 @@ PrRhiDeviceAddress prRhiGetBufferDeviceAddress(PrRhiDevice *device, PrRhiBuffer
|
|||||||
// Textures
|
// Textures
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiTexture *prRhiCreateTexture(PrRhiDevice *device, PrRhiTextureDesc desc,
|
PrRhiTexture *prRhiCreateTexture(PrRhiDevice *device, PrRhiTextureDesc desc);
|
||||||
WpAllocator *alloc);
|
PrRhiTexture *prRhiCreateTextureFromKtx(PrRhiDevice *device, const char *path,
|
||||||
void prRhiDestroyTexture(PrRhiDevice *device, PrRhiTexture *texture,
|
PrRhiCommandPool *pool, PrRhiCommandBuffer *cb);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyTexture(PrRhiDevice *device, PrRhiTexture *texture);
|
||||||
|
PrRhiTextureSize prRhiGetTextureSize(PrRhiTexture *texture);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Samplers
|
// Samplers
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiSampler *prRhiCreateSampler(PrRhiDevice *device, PrRhiSamplerDesc desc,
|
PrRhiSampler *prRhiCreateSampler(PrRhiDevice *device, PrRhiSamplerDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroySampler(PrRhiDevice *device, PrRhiSampler *sampler);
|
||||||
void prRhiDestroySampler(PrRhiDevice *device, PrRhiSampler *sampler,
|
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Shaders (from SPIR-V)
|
// Shaders (from SPIR-V)
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiShader *prRhiCreateShader(PrRhiDevice *device, PrRhiShaderDesc desc,
|
PrRhiShader *prRhiCreateShader(PrRhiDevice *device, PrRhiShaderDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyShader(PrRhiDevice *device, PrRhiShader *shader);
|
||||||
void prRhiDestroyShader(PrRhiDevice *device, PrRhiShader *shader, WpAllocator *alloc);
|
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Pipeline layouts
|
// Pipeline layouts
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiPipelineLayout *prRhiCreatePipelineLayout(PrRhiDevice *device,
|
PrRhiPipelineLayout *prRhiCreatePipelineLayout(PrRhiDevice *device,
|
||||||
PrRhiPipelineLayoutDesc desc,
|
PrRhiPipelineLayoutDesc desc);
|
||||||
WpAllocator *alloc);
|
|
||||||
void prRhiDestroyPipelineLayout(PrRhiDevice *device,
|
void prRhiDestroyPipelineLayout(PrRhiDevice *device,
|
||||||
PrRhiPipelineLayout *layout,
|
PrRhiPipelineLayout *layout);
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Pipelines
|
// Pipelines
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiPipeline *prRhiCreateGraphicsPipeline(PrRhiDevice *device,
|
PrRhiPipeline *prRhiCreateGraphicsPipeline(PrRhiDevice *device,
|
||||||
PrRhiGraphicsPipelineDesc desc,
|
PrRhiGraphicsPipelineDesc desc);
|
||||||
WpAllocator *alloc);
|
|
||||||
PrRhiPipeline *prRhiCreateComputePipeline(PrRhiDevice *device,
|
PrRhiPipeline *prRhiCreateComputePipeline(PrRhiDevice *device,
|
||||||
PrRhiComputePipelineDesc desc,
|
PrRhiComputePipelineDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyPipeline(PrRhiDevice *device, PrRhiPipeline *pipeline);
|
||||||
void prRhiDestroyPipeline(PrRhiDevice *device, PrRhiPipeline *pipeline,
|
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Descriptor set layouts
|
// Descriptor set layouts
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiDescriptorSetLayout *prRhiCreateDescriptorSetLayout(PrRhiDevice *device,
|
PrRhiDescriptorSetLayout *prRhiCreateDescriptorSetLayout(PrRhiDevice *device,
|
||||||
PrRhiDescriptorSetLayoutDesc desc,
|
PrRhiDescriptorSetLayoutDesc desc);
|
||||||
WpAllocator *alloc);
|
|
||||||
void prRhiDestroyDescriptorSetLayout(PrRhiDevice *device,
|
void prRhiDestroyDescriptorSetLayout(PrRhiDevice *device,
|
||||||
PrRhiDescriptorSetLayout *layout,
|
PrRhiDescriptorSetLayout *layout);
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Descriptor pools
|
// Descriptor pools
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiDescriptorPool *prRhiCreateDescriptorPool(PrRhiDevice *device,
|
PrRhiDescriptorPool *prRhiCreateDescriptorPool(PrRhiDevice *device,
|
||||||
PrRhiDescriptorPoolDesc desc,
|
PrRhiDescriptorPoolDesc desc);
|
||||||
WpAllocator *alloc);
|
|
||||||
void prRhiDestroyDescriptorPool(PrRhiDevice *device,
|
void prRhiDestroyDescriptorPool(PrRhiDevice *device,
|
||||||
PrRhiDescriptorPool *pool,
|
PrRhiDescriptorPool *pool);
|
||||||
WpAllocator *alloc);
|
void prRhiResetDescriptorPool(PrRhiDevice *device,
|
||||||
|
PrRhiDescriptorPool *pool);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Descriptor sets
|
// Descriptor sets
|
||||||
@@ -170,40 +172,37 @@ void prRhiDestroyDescriptorPool(PrRhiDevice *device,
|
|||||||
|
|
||||||
PrRhiDescriptorSet *prRhiAllocateDescriptorSet(PrRhiDevice *device, PrRhiDescriptorPool *pool,
|
PrRhiDescriptorSet *prRhiAllocateDescriptorSet(PrRhiDevice *device, PrRhiDescriptorPool *pool,
|
||||||
PrRhiDescriptorSetLayout *layout,
|
PrRhiDescriptorSetLayout *layout,
|
||||||
u32 variable_count, WpAllocator *alloc);
|
WpU32Array variable_descriptor_counts);
|
||||||
void prRhiFreeDescriptorSet(PrRhiDevice *device, PrRhiDescriptorPool *pool,
|
void prRhiFreeDescriptorSet(PrRhiDevice *device, PrRhiDescriptorPool *pool,
|
||||||
PrRhiDescriptorSet *set, WpAllocator *alloc);
|
PrRhiDescriptorSet *set);
|
||||||
void prRhiUpdateDescriptorSet(PrRhiDevice *device, PrRhiWriteDescriptorSetArray writes);
|
void prRhiUpdateDescriptorSet(PrRhiDevice *device, PrRhiWriteDescriptorSetArray writes);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Fences and semaphores
|
// Fences and semaphores
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiFence *prRhiCreateFence(PrRhiDevice *device, PrRhiFenceDesc desc,
|
PrRhiFence *prRhiCreateFence(PrRhiDevice *device, PrRhiFenceDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyFence(PrRhiDevice *device, PrRhiFence *fence);
|
||||||
void prRhiDestroyFence(PrRhiDevice *device, PrRhiFence *fence, WpAllocator *alloc);
|
|
||||||
|
|
||||||
void prRhiWaitForFences(PrRhiDevice *device, PrRhiFenceArray fences, u32 count,
|
void prRhiWaitForFences(PrRhiDevice *device, PrRhiFenceArray fences, u32 count,
|
||||||
b8 wait_all, u64 timeout_ns);
|
b8 wait_all, u64 timeout_ns);
|
||||||
void prRhiResetFences(PrRhiDevice *device, PrRhiFenceArray fences, u32 count);
|
void prRhiResetFences(PrRhiDevice *device, PrRhiFenceArray fences, u32 count);
|
||||||
|
|
||||||
PrRhiSemaphore *prRhiCreateSemaphore(PrRhiDevice *device, WpAllocator *alloc);
|
PrRhiSemaphore *prRhiCreateSemaphore(PrRhiDevice *device);
|
||||||
void prRhiDestroySemaphore(PrRhiDevice *device, PrRhiSemaphore *semaphore,
|
void prRhiDestroySemaphore(PrRhiDevice *device, PrRhiSemaphore *semaphore);
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Command pools and command buffers
|
// Command pools and command buffers
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
|
|
||||||
PrRhiCommandPool *prRhiCreateCommandPool(PrRhiDevice *device, PrRhiCommandPoolDesc desc,
|
PrRhiCommandPool *prRhiCreateCommandPool(PrRhiDevice *device);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyCommandPool(PrRhiDevice *device, PrRhiCommandPool *pool);
|
||||||
void prRhiDestroyCommandPool(PrRhiDevice *device, PrRhiCommandPool *pool,
|
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
PrRhiCommandBufferArray prRhiAllocateCommandBuffers(PrRhiDevice *device, PrRhiCommandPool *pool,
|
PrRhiCommandBufferArray prRhiAllocateCommandBuffers(PrRhiDevice *device, PrRhiCommandPool *pool,
|
||||||
u32 count, WpAllocator *alloc);
|
u32 count);
|
||||||
|
|
||||||
void prRhiFreeCommandBuffers(PrRhiDevice *device, PrRhiCommandPool *pool,
|
void prRhiFreeCommandBuffers(PrRhiDevice *device, PrRhiCommandPool *pool,
|
||||||
u32 count, PrRhiCommandBufferArray buffers);
|
PrRhiCommandBufferArray buffers);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Command buffer recording
|
// Command buffer recording
|
||||||
@@ -244,8 +243,8 @@ void prRhiCmdPushConstants(PrRhiCommandBuffer *cb, PrRhiPipelineLayout *layout,
|
|||||||
|
|
||||||
// --- Vertex / index buffers ---
|
// --- Vertex / index buffers ---
|
||||||
|
|
||||||
void prRhiCmdBindVertexBuffers(PrRhiCommandBuffer *cb, u32 first_binding,
|
void prRhiCmdBindVertexBuffers(PrRhiCommandBuffer *cb, u32 first_binding, PrRhiBufferArray buffers,
|
||||||
PrRhiBufferArray buffers, const u64 *offsets, u32 count);
|
WpU64Array offsets);
|
||||||
void prRhiCmdBindIndexBuffer(PrRhiCommandBuffer *cb, PrRhiBuffer *buffer, u64 offset,
|
void prRhiCmdBindIndexBuffer(PrRhiCommandBuffer *cb, PrRhiBuffer *buffer, u64 offset,
|
||||||
PrRhiIndexType index_type);
|
PrRhiIndexType index_type);
|
||||||
|
|
||||||
@@ -258,7 +257,8 @@ void prRhiCmdDrawIndexed(PrRhiCommandBuffer *cb, u32 index_count, u32 instance_c
|
|||||||
|
|
||||||
// --- Copy ---
|
// --- Copy ---
|
||||||
|
|
||||||
void prRhiCmdCopyBufferToImage(PrRhiCommandBuffer *cb, PrRhiBuffer *src, PrRhiTexture *dst);
|
void prRhiCmdCopyBufferToImage(PrRhiCommandBuffer *cb, PrRhiBuffer *src, PrRhiTexture *dst,
|
||||||
|
PrRhiBufferImageCopyArray copies);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Queue submission
|
// Queue submission
|
||||||
@@ -282,5 +282,9 @@ void prRhiQueueSubmit(PrRhiDevice *device, PrRhiCommandBuffer *cb,
|
|||||||
# error "Define one of: PR_RHI_VULKAN, PR_RHI_D3D12, PR_RHI_METAL"
|
# error "Define one of: PR_RHI_VULKAN, PR_RHI_D3D12, PR_RHI_METAL"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -7,7 +7,9 @@
|
|||||||
#ifndef PR_RHI_TYPES_H
|
#ifndef PR_RHI_TYPES_H
|
||||||
#define PR_RHI_TYPES_H
|
#define PR_RHI_TYPES_H
|
||||||
|
|
||||||
#include "../../src/wapp/wapp.h"
|
#include "../../vendor/wapp/wapp.h"
|
||||||
|
|
||||||
|
#define PR_RHI_LOD_CLAMP_NONE 1000.0f
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Opaque handle types
|
// Opaque handle types
|
||||||
@@ -36,6 +38,14 @@ typedef struct PrRhiSemaphore PrRhiSemaphore;
|
|||||||
// Enums and flags
|
// Enums and flags
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
typedef enum PrRhiPhysicalDeviceType {
|
||||||
|
PR_RHI_PHYSICAL_DEVICE_TYPE_OTHER = 0,
|
||||||
|
PR_RHI_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU = 1,
|
||||||
|
PR_RHI_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU = 2,
|
||||||
|
PR_RHI_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU = 3,
|
||||||
|
PR_RHI_PHYSICAL_DEVICE_TYPE_CPU = 4,
|
||||||
|
} PrRhiPhysicalDeviceType;
|
||||||
|
|
||||||
typedef enum PrRhiSwapchainResult {
|
typedef enum PrRhiSwapchainResult {
|
||||||
PR_RHI_SWAPCHAIN_SUCCESS = 0,
|
PR_RHI_SWAPCHAIN_SUCCESS = 0,
|
||||||
PR_RHI_SWAPCHAIN_OUT_OF_DATE,
|
PR_RHI_SWAPCHAIN_OUT_OF_DATE,
|
||||||
@@ -96,6 +106,33 @@ typedef enum PrRhiShaderStage {
|
|||||||
PR_RHI_SHADER_STAGE_COMPUTE = 1 << 2,
|
PR_RHI_SHADER_STAGE_COMPUTE = 1 << 2,
|
||||||
} PrRhiShaderStage;
|
} PrRhiShaderStage;
|
||||||
|
|
||||||
|
typedef enum PrRhiPipelineStage {
|
||||||
|
PR_RHI_PIPELINE_STAGE_NONE = 0,
|
||||||
|
PR_RHI_PIPELINE_STAGE_TOP_OF_PIPE = 1 << 0,
|
||||||
|
PR_RHI_PIPELINE_STAGE_TRANSFER = 1 << 1,
|
||||||
|
PR_RHI_PIPELINE_STAGE_VERTEX_SHADER = 1 << 2,
|
||||||
|
PR_RHI_PIPELINE_STAGE_FRAGMENT_SHADER = 1 << 3,
|
||||||
|
PR_RHI_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS = 1 << 4,
|
||||||
|
PR_RHI_PIPELINE_STAGE_LATE_FRAGMENT_TESTS = 1 << 5,
|
||||||
|
PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT = 1 << 6,
|
||||||
|
PR_RHI_PIPELINE_STAGE_COMPUTE_SHADER = 1 << 7,
|
||||||
|
PR_RHI_PIPELINE_STAGE_BOTTOM_OF_PIPE = 1 << 8,
|
||||||
|
} PrRhiPipelineStage;
|
||||||
|
|
||||||
|
typedef enum PrRhiAccess {
|
||||||
|
PR_RHI_ACCESS_NONE = 0,
|
||||||
|
PR_RHI_ACCESS_TRANSFER_READ = 1 << 0,
|
||||||
|
PR_RHI_ACCESS_TRANSFER_WRITE = 1 << 1,
|
||||||
|
PR_RHI_ACCESS_SHADER_READ = 1 << 2,
|
||||||
|
PR_RHI_ACCESS_SHADER_WRITE = 1 << 3,
|
||||||
|
PR_RHI_ACCESS_COLOR_ATTACHMENT_READ = 1 << 4,
|
||||||
|
PR_RHI_ACCESS_COLOR_ATTACHMENT_WRITE = 1 << 5,
|
||||||
|
PR_RHI_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ = 1 << 6,
|
||||||
|
PR_RHI_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE = 1 << 7,
|
||||||
|
PR_RHI_ACCESS_MEMORY_READ = 1 << 8,
|
||||||
|
PR_RHI_ACCESS_MEMORY_WRITE = 1 << 9,
|
||||||
|
} PrRhiAccess;
|
||||||
|
|
||||||
typedef enum PrRhiDescriptorType {
|
typedef enum PrRhiDescriptorType {
|
||||||
PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||||
PR_RHI_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
PR_RHI_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||||
@@ -212,14 +249,29 @@ typedef struct PrRhiImageMemoryBarrier {
|
|||||||
PrRhiTexture *texture;
|
PrRhiTexture *texture;
|
||||||
PrRhiImageLayout old_layout;
|
PrRhiImageLayout old_layout;
|
||||||
PrRhiImageLayout new_layout;
|
PrRhiImageLayout new_layout;
|
||||||
|
PrRhiPipelineStage src_stage_mask;
|
||||||
|
PrRhiAccess src_access_mask;
|
||||||
|
PrRhiPipelineStage dst_stage_mask;
|
||||||
|
PrRhiAccess dst_access_mask;
|
||||||
} PrRhiImageMemoryBarrier;
|
} PrRhiImageMemoryBarrier;
|
||||||
|
|
||||||
typedef struct PrRhiBufferMemoryBarrier {
|
typedef struct PrRhiBufferMemoryBarrier {
|
||||||
PrRhiBuffer *buffer;
|
PrRhiBuffer *buffer;
|
||||||
u64 offset;
|
u64 offset;
|
||||||
u64 size;
|
u64 size;
|
||||||
|
PrRhiPipelineStage src_stage_mask;
|
||||||
|
PrRhiAccess src_access_mask;
|
||||||
|
PrRhiPipelineStage dst_stage_mask;
|
||||||
|
PrRhiAccess dst_access_mask;
|
||||||
} PrRhiBufferMemoryBarrier;
|
} PrRhiBufferMemoryBarrier;
|
||||||
|
|
||||||
|
typedef struct PrRhiBufferImageCopy {
|
||||||
|
u64 buffer_offset;
|
||||||
|
u32 mip_level;
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
} PrRhiBufferImageCopy;
|
||||||
|
|
||||||
typedef struct PrRhiColorAttachment {
|
typedef struct PrRhiColorAttachment {
|
||||||
PrRhiTexture *texture;
|
PrRhiTexture *texture;
|
||||||
PrRhiImageLayout layout;
|
PrRhiImageLayout layout;
|
||||||
@@ -240,8 +292,6 @@ typedef PrRhiSemaphore **PrRhiSemaphoreArray;
|
|||||||
typedef PrRhiCommandBuffer **PrRhiCommandBufferArray;
|
typedef PrRhiCommandBuffer **PrRhiCommandBufferArray;
|
||||||
typedef PrRhiDescriptorSetLayout **PrRhiDescriptorSetLayoutArray;
|
typedef PrRhiDescriptorSetLayout **PrRhiDescriptorSetLayoutArray;
|
||||||
typedef PrRhiDescriptorSet **PrRhiDescriptorSetArray;
|
typedef PrRhiDescriptorSet **PrRhiDescriptorSetArray;
|
||||||
typedef const char **PrRhiExtensionArray;
|
|
||||||
|
|
||||||
// Value type arrays (contiguous structs/enums)
|
// Value type arrays (contiguous structs/enums)
|
||||||
typedef PrRhiPushConstantRange *PrRhiPushConstantRangeArray;
|
typedef PrRhiPushConstantRange *PrRhiPushConstantRangeArray;
|
||||||
typedef PrRhiVertexInputBinding *PrRhiVertexInputBindingArray;
|
typedef PrRhiVertexInputBinding *PrRhiVertexInputBindingArray;
|
||||||
@@ -255,6 +305,7 @@ typedef PrRhiDescriptorBufferInfo *PrRhiDescriptorBufferInfoArray;
|
|||||||
typedef PrRhiImageMemoryBarrier *PrRhiImageMemoryBarrierArray;
|
typedef PrRhiImageMemoryBarrier *PrRhiImageMemoryBarrierArray;
|
||||||
typedef PrRhiBufferMemoryBarrier *PrRhiBufferMemoryBarrierArray;
|
typedef PrRhiBufferMemoryBarrier *PrRhiBufferMemoryBarrierArray;
|
||||||
typedef PrRhiColorAttachment *PrRhiColorAttachmentArray;
|
typedef PrRhiColorAttachment *PrRhiColorAttachmentArray;
|
||||||
|
typedef PrRhiBufferImageCopy *PrRhiBufferImageCopyArray;
|
||||||
typedef struct PrRhiWriteDescriptorSet *PrRhiWriteDescriptorSetArray;
|
typedef struct PrRhiWriteDescriptorSet *PrRhiWriteDescriptorSetArray;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -264,7 +315,6 @@ typedef struct PrRhiWriteDescriptorSet *PrRhiWriteDescriptorSetArray;
|
|||||||
typedef struct PrRhiInstanceDesc {
|
typedef struct PrRhiInstanceDesc {
|
||||||
const char *app_name;
|
const char *app_name;
|
||||||
u32 app_version;
|
u32 app_version;
|
||||||
PrRhiExtensionArray extra_extensions;
|
|
||||||
} PrRhiInstanceDesc;
|
} PrRhiInstanceDesc;
|
||||||
|
|
||||||
typedef struct PrRhiDeviceDesc {
|
typedef struct PrRhiDeviceDesc {
|
||||||
@@ -302,14 +352,50 @@ typedef struct PrRhiShaderDesc {
|
|||||||
u64 spirv_size;
|
u64 spirv_size;
|
||||||
} PrRhiShaderDesc;
|
} PrRhiShaderDesc;
|
||||||
|
|
||||||
|
typedef struct PrRhiTextureSize {
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
} PrRhiTextureSize;
|
||||||
|
|
||||||
typedef struct PrRhiPipelineLayoutDesc {
|
typedef struct PrRhiPipelineLayoutDesc {
|
||||||
PrRhiDescriptorSetLayoutArray set_layouts;
|
PrRhiDescriptorSetLayoutArray set_layouts;
|
||||||
PrRhiPushConstantRangeArray push_constant_ranges;
|
PrRhiPushConstantRangeArray push_constant_ranges;
|
||||||
} PrRhiPipelineLayoutDesc;
|
} PrRhiPipelineLayoutDesc;
|
||||||
|
|
||||||
|
typedef enum PrRhiPolygonMode {
|
||||||
|
PR_RHI_POLYGON_MODE_FILL = 0,
|
||||||
|
PR_RHI_POLYGON_MODE_LINE = 1,
|
||||||
|
PR_RHI_POLYGON_MODE_POINT = 2,
|
||||||
|
} PrRhiPolygonMode;
|
||||||
|
|
||||||
|
typedef enum PrRhiCullMode {
|
||||||
|
PR_RHI_CULL_MODE_NONE = 0,
|
||||||
|
PR_RHI_CULL_MODE_FRONT = 0x00000001,
|
||||||
|
PR_RHI_CULL_MODE_BACK = 0x00000002,
|
||||||
|
PR_RHI_CULL_MODE_FRONT_AND_BACK = 0x00000003,
|
||||||
|
} PrRhiCullMode;
|
||||||
|
|
||||||
|
typedef enum PrRhiFrontFace {
|
||||||
|
PR_RHI_FRONT_FACE_COUNTER_CLOCKWISE = 0,
|
||||||
|
PR_RHI_FRONT_FACE_CLOCKWISE = 1,
|
||||||
|
} PrRhiFrontFace;
|
||||||
|
|
||||||
|
typedef enum PrRhiMultisampleCount {
|
||||||
|
PR_RHI_SAMPLE_COUNT_1 = 0x00000001,
|
||||||
|
PR_RHI_SAMPLE_COUNT_2 = 0x00000002,
|
||||||
|
PR_RHI_SAMPLE_COUNT_4 = 0x00000004,
|
||||||
|
PR_RHI_SAMPLE_COUNT_8 = 0x00000008,
|
||||||
|
PR_RHI_SAMPLE_COUNT_16 = 0x00000010,
|
||||||
|
PR_RHI_SAMPLE_COUNT_32 = 0x00000020,
|
||||||
|
PR_RHI_SAMPLE_COUNT_64 = 0x00000040,
|
||||||
|
} PrRhiMultisampleCount;
|
||||||
|
|
||||||
typedef struct PrRhiGraphicsPipelineDesc {
|
typedef struct PrRhiGraphicsPipelineDesc {
|
||||||
PrRhiShader *vertex_shader;
|
PrRhiShader *vertex_shader;
|
||||||
|
const char *vertex_shader_entry_point;
|
||||||
|
|
||||||
PrRhiShader *fragment_shader;
|
PrRhiShader *fragment_shader;
|
||||||
|
const char *fragment_shader_entry_point;
|
||||||
|
|
||||||
PrRhiVertexInputBindingArray vertex_bindings;
|
PrRhiVertexInputBindingArray vertex_bindings;
|
||||||
PrRhiVertexAttributeArray vertex_attributes;
|
PrRhiVertexAttributeArray vertex_attributes;
|
||||||
@@ -328,11 +414,22 @@ typedef struct PrRhiGraphicsPipelineDesc {
|
|||||||
b8 dynamic_viewport;
|
b8 dynamic_viewport;
|
||||||
b8 dynamic_scissor;
|
b8 dynamic_scissor;
|
||||||
|
|
||||||
|
PrRhiPolygonMode polygon_mode;
|
||||||
|
|
||||||
|
PrRhiCullMode cull_mode;
|
||||||
|
|
||||||
|
PrRhiFrontFace front_face;
|
||||||
|
|
||||||
|
f32 line_width;
|
||||||
|
|
||||||
|
PrRhiMultisampleCount multisample_count;
|
||||||
|
|
||||||
PrRhiPipelineLayout *layout;
|
PrRhiPipelineLayout *layout;
|
||||||
} PrRhiGraphicsPipelineDesc;
|
} PrRhiGraphicsPipelineDesc;
|
||||||
|
|
||||||
typedef struct PrRhiComputePipelineDesc {
|
typedef struct PrRhiComputePipelineDesc {
|
||||||
PrRhiShader *shader;
|
PrRhiShader *shader;
|
||||||
|
const char *shader_entry_point;
|
||||||
PrRhiPipelineLayout *layout;
|
PrRhiPipelineLayout *layout;
|
||||||
} PrRhiComputePipelineDesc;
|
} PrRhiComputePipelineDesc;
|
||||||
|
|
||||||
@@ -358,15 +455,12 @@ typedef struct PrRhiFenceDesc {
|
|||||||
b8 signaled;
|
b8 signaled;
|
||||||
} PrRhiFenceDesc;
|
} PrRhiFenceDesc;
|
||||||
|
|
||||||
typedef struct PrRhiCommandPoolDesc {
|
|
||||||
u32 queue_family_index;
|
|
||||||
} PrRhiCommandPoolDesc;
|
|
||||||
|
|
||||||
typedef struct PrRhiSwapchainDesc {
|
typedef struct PrRhiSwapchainDesc {
|
||||||
PrRhiSurface *surface;
|
PrRhiSurface *surface;
|
||||||
u32 width;
|
u32 width;
|
||||||
u32 height;
|
u32 height;
|
||||||
b8 has_depth;
|
b8 has_depth;
|
||||||
|
PrRhiFormat depth_format; // PR_RHI_FORMAT_UNDEFINED = auto-pick
|
||||||
} PrRhiSwapchainDesc;
|
} PrRhiSwapchainDesc;
|
||||||
|
|
||||||
typedef struct PrRhiSurfaceCapabilities {
|
typedef struct PrRhiSurfaceCapabilities {
|
||||||
@@ -380,8 +474,21 @@ typedef struct PrRhiSurfaceCapabilities {
|
|||||||
u32 max_height;
|
u32 max_height;
|
||||||
} PrRhiSurfaceCapabilities;
|
} PrRhiSurfaceCapabilities;
|
||||||
|
|
||||||
|
typedef struct PrRhiPhysicalDeviceProperties {
|
||||||
|
u32 api_version;
|
||||||
|
PrRhiPhysicalDeviceType device_type;
|
||||||
|
} PrRhiPhysicalDeviceProperties;
|
||||||
|
|
||||||
typedef u64 PrRhiDeviceAddress;
|
typedef u64 PrRhiDeviceAddress;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// RHI context (global, owns all RHI object lifetimes)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
typedef struct PrRhiContext {
|
||||||
|
WpAllocator allocator;
|
||||||
|
} PrRhiContext;
|
||||||
|
|
||||||
// --- Command buffer types ---
|
// --- Command buffer types ---
|
||||||
|
|
||||||
typedef struct PrRhiDepthAttachment {
|
typedef struct PrRhiDepthAttachment {
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -8,216 +8,219 @@
|
|||||||
#define PR_RHI_VK_H
|
#define PR_RHI_VK_H
|
||||||
|
|
||||||
#include "../pr_rhi_types.h"
|
#include "../pr_rhi_types.h"
|
||||||
|
#include <SDL3/SDL_video.h>
|
||||||
|
#include <volk/volk.h>
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Opaque struct definitions — visible only to the backend implementation.
|
// Opaque struct definitions — visible only to the backend implementation.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
#define PR_RHI_VK_PHYSICAL_DEVICE_NAME_LENGTH 256
|
||||||
|
#define PR_RHI_VK_PHYSICAL_DRIVER_INFO_LENGTH 256
|
||||||
|
|
||||||
struct PrRhiInstance {
|
struct PrRhiInstance {
|
||||||
void *handle; // VkInstance
|
VkInstance handle; // VkInstance
|
||||||
void *debug_messenger; // VkDebugUtilsMessengerEXT
|
VkDebugUtilsMessengerEXT *debug_messenger; // VkDebugUtilsMessengerEXT
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiPhysicalDevice {
|
struct PrRhiPhysicalDevice {
|
||||||
void *handle; // VkPhysicalDevice
|
VkPhysicalDevice handle;
|
||||||
PrRhiInstance *instance;
|
PrRhiInstance *instance;
|
||||||
|
c8 device_name[PR_RHI_VK_PHYSICAL_DEVICE_NAME_LENGTH];
|
||||||
|
c8 driver_info[PR_RHI_VK_PHYSICAL_DRIVER_INFO_LENGTH];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiDevice {
|
struct PrRhiDevice {
|
||||||
void *handle; // VkDevice
|
VkDevice handle;
|
||||||
void *queue; // VkQueue
|
VkQueue queue;
|
||||||
u32 queue_family_index;
|
u32 queue_family_index;
|
||||||
void *allocator; // VmaAllocator
|
VkPresentModeKHR present_mode;
|
||||||
|
VkPhysicalDevice physical_device;
|
||||||
|
VmaAllocator allocator;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiSurface {
|
struct PrRhiSurface {
|
||||||
void *handle; // VkSurfaceKHR
|
VkSurfaceKHR handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiSwapchain {
|
struct PrRhiSwapchain {
|
||||||
PrRhiDevice *device;
|
PrRhiDevice *device;
|
||||||
void *handle; // VkSwapchainKHR
|
VkSwapchainKHR handle;
|
||||||
|
PrRhiSurface *surface;
|
||||||
u32 image_count;
|
u32 image_count;
|
||||||
PrRhiTexture **images;
|
PrRhiTexture **images;
|
||||||
PrRhiTexture *depth;
|
PrRhiTexture *depth;
|
||||||
u32 format; // VkFormat
|
VkFormat format;
|
||||||
|
VkFormat depth_format;
|
||||||
u32 width;
|
u32 width;
|
||||||
u32 height;
|
u32 height;
|
||||||
|
u32 current_image_index;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiBuffer {
|
struct PrRhiBuffer {
|
||||||
void *handle; // VkBuffer
|
VkBuffer handle;
|
||||||
void *allocation; // VmaAllocation
|
VmaAllocation allocation;
|
||||||
u64 device_address;
|
u64 device_address;
|
||||||
u64 size;
|
u64 size;
|
||||||
void *mapped_data;
|
void *mapped_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiTexture {
|
struct PrRhiTexture {
|
||||||
void *image; // VkImage
|
VkImage image;
|
||||||
void *view; // VkImageView
|
VkImageView view;
|
||||||
void *allocation; // VmaAllocation
|
VmaAllocation allocation;
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
VkFormat format;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiSampler {
|
struct PrRhiSampler {
|
||||||
void *handle; // VkSampler
|
VkSampler handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiShader {
|
struct PrRhiShader {
|
||||||
void *handle; // VkShaderModule
|
VkShaderModule handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiPipelineLayout {
|
struct PrRhiPipelineLayout {
|
||||||
void *handle; // VkPipelineLayout
|
VkPipelineLayout handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiPipeline {
|
struct PrRhiPipeline {
|
||||||
void *handle; // VkPipeline
|
VkPipeline handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiDescriptorSetLayout {
|
struct PrRhiDescriptorSetLayout {
|
||||||
void *handle; // VkDescriptorSetLayout
|
VkDescriptorSetLayout handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiDescriptorPool {
|
struct PrRhiDescriptorPool {
|
||||||
void *handle; // VkDescriptorPool
|
VkDescriptorPool handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiDescriptorSet {
|
struct PrRhiDescriptorSet {
|
||||||
void *handle; // VkDescriptorSet
|
VkDescriptorSet handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiCommandPool {
|
struct PrRhiCommandPool {
|
||||||
void *handle; // VkCommandPool
|
VkCommandPool handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiCommandBuffer {
|
struct PrRhiCommandBuffer {
|
||||||
void *handle; // VkCommandBuffer
|
VkCommandBuffer handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiFence {
|
struct PrRhiFence {
|
||||||
void *handle; // VkFence
|
VkFence handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrRhiSemaphore {
|
struct PrRhiSemaphore {
|
||||||
void *handle; // VkSemaphore
|
VkSemaphore handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Function declarations
|
// Function declarations
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
PrRhiInstance *prRhiCreateInstanceVk(PrRhiInstanceDesc desc, WpAllocator *alloc);
|
PrRhiInstance *prRhiCreateInstanceVk(PrRhiInstanceDesc desc);
|
||||||
void prRhiDestroyInstanceVk(PrRhiInstance *inst, WpAllocator *alloc);
|
void prRhiDestroyInstanceVk(PrRhiInstance *inst);
|
||||||
|
|
||||||
PrRhiPhysicalDeviceArray prRhiGetPhysicalDevicesVk(PrRhiInstance *inst, const WpAllocator *scratch);
|
PrRhiPhysicalDeviceArray prRhiGetPhysicalDevicesVk(PrRhiInstance *inst);
|
||||||
void prRhiGetPhysicalDeviceNameVk(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
void prRhiGetPhysicalDeviceNameVk(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
||||||
void prRhiGetPhysicalDeviceDriverInfoVk(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
void prRhiGetPhysicalDeviceDriverInfoVk(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
||||||
|
PrRhiPhysicalDeviceProperties prRhiGetPhysicalDevicePropertiesVk(PrRhiPhysicalDevice *pdev);
|
||||||
|
|
||||||
PrRhiSurface *prRhiCreateSurfaceVk(PrRhiInstance *inst, void *window_handle, WpAllocator *alloc);
|
PrRhiSurface *prRhiCreateSurfaceFromWindowVk(PrRhiInstance *inst, SDL_Window *window);
|
||||||
void prRhiDestroySurfaceVk(PrRhiInstance *inst, PrRhiSurface *surface, WpAllocator *alloc);
|
void prRhiDestroySurfaceVk(PrRhiInstance *inst, PrRhiSurface *surface);
|
||||||
|
|
||||||
PrRhiSurfaceCapabilities prRhiGetSurfaceCapabilitiesVk(PrRhiPhysicalDevice *pdev,
|
PrRhiSurfaceCapabilities prRhiGetSurfaceCapabilitiesVk(PrRhiPhysicalDevice *pdev,
|
||||||
PrRhiSurface *surface);
|
PrRhiSurface *surface);
|
||||||
|
|
||||||
PrRhiDevice *prRhiCreateDeviceVk(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface,
|
PrRhiDevice *prRhiCreateDeviceVk(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface,
|
||||||
PrRhiDeviceDesc desc, WpAllocator *alloc);
|
PrRhiDeviceDesc desc);
|
||||||
void prRhiDestroyDeviceVk(PrRhiDevice *device, WpAllocator *alloc);
|
void prRhiDestroyDeviceVk(PrRhiDevice *device);
|
||||||
void prRhiDeviceWaitIdleVk(PrRhiDevice *device);
|
void prRhiDeviceWaitIdleVk(PrRhiDevice *device);
|
||||||
|
u32 prRhiGetQueueFamilyIndexVk(PrRhiDevice *device);
|
||||||
|
|
||||||
PrRhiSwapchain *prRhiCreateSwapchainVk(PrRhiDevice *device, PrRhiSwapchainDesc desc,
|
PrRhiSwapchain *prRhiCreateSwapchainVk(PrRhiDevice *device, PrRhiSwapchainDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroySwapchainVk(PrRhiDevice *device, PrRhiSwapchain *swapchain);
|
||||||
void prRhiDestroySwapchainVk(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
u32 prRhiGetSwapchainImageCountVk(PrRhiSwapchain *swapchain);
|
||||||
WpAllocator *alloc);
|
|
||||||
PrRhiSwapchainResult prRhiAcquireNextImageVk(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
PrRhiSwapchainResult prRhiAcquireNextImageVk(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
||||||
PrRhiSemaphore *signal_semaphore, u32 *out_image_index);
|
PrRhiSemaphore *signal_semaphore, u32 *out_image_index);
|
||||||
PrRhiSwapchainResult prRhiPresentVk(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
PrRhiSwapchainResult prRhiPresentVk(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
||||||
PrRhiSemaphore *wait_semaphore);
|
PrRhiSemaphore *wait_semaphore);
|
||||||
void prRhiRecreateSwapchainVk(PrRhiDevice *device, PrRhiSwapchain **swapchain,
|
void prRhiRecreateSwapchainVk(PrRhiDevice *device, PrRhiSwapchain **swapchain,
|
||||||
u32 width, u32 height, WpAllocator *alloc);
|
u32 width, u32 height);
|
||||||
PrRhiTexture *prRhiGetSwapchainTextureVk(PrRhiSwapchain *swapchain, u32 image_index);
|
PrRhiTexture *prRhiGetSwapchainTextureVk(PrRhiSwapchain *swapchain, u32 image_index);
|
||||||
PrRhiTexture *prRhiGetSwapchainDepthTextureVk(PrRhiSwapchain *swapchain);
|
PrRhiTexture *prRhiGetSwapchainDepthTextureVk(PrRhiSwapchain *swapchain);
|
||||||
|
PrRhiFormat prRhiGetSwapchainFormatVk(PrRhiSwapchain *swapchain);
|
||||||
|
|
||||||
PrRhiBuffer *prRhiCreateBufferVk(PrRhiDevice *device, PrRhiBufferDesc desc,
|
PrRhiBuffer *prRhiCreateBufferVk(PrRhiDevice *device, PrRhiBufferDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyBufferVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||||
void prRhiDestroyBufferVk(PrRhiDevice *device, PrRhiBuffer *buffer, WpAllocator *alloc);
|
|
||||||
void *prRhiBufferMapVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
void *prRhiBufferMapVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||||
void prRhiBufferUnmapVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
void prRhiBufferUnmapVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||||
PrRhiDeviceAddress prRhiGetBufferDeviceAddressVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
PrRhiDeviceAddress prRhiGetBufferDeviceAddressVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||||
|
|
||||||
PrRhiTexture *prRhiCreateTextureVk(PrRhiDevice *device, PrRhiTextureDesc desc,
|
PrRhiTexture *prRhiCreateTextureVk(PrRhiDevice *device, PrRhiTextureDesc desc);
|
||||||
WpAllocator *alloc);
|
PrRhiTexture *prRhiCreateTextureFromKtxVk(PrRhiDevice *device, const char *path,
|
||||||
void prRhiDestroyTextureVk(PrRhiDevice *device, PrRhiTexture *texture,
|
PrRhiCommandPool *pool, PrRhiCommandBuffer *cb);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyTextureVk(PrRhiDevice *device, PrRhiTexture *texture);
|
||||||
|
PrRhiTextureSize prRhiGetTextureSizeVk(PrRhiTexture *texture);
|
||||||
|
|
||||||
PrRhiSampler *prRhiCreateSamplerVk(PrRhiDevice *device, PrRhiSamplerDesc desc,
|
PrRhiSampler *prRhiCreateSamplerVk(PrRhiDevice *device, PrRhiSamplerDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroySamplerVk(PrRhiDevice *device, PrRhiSampler *sampler);
|
||||||
void prRhiDestroySamplerVk(PrRhiDevice *device, PrRhiSampler *sampler,
|
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
PrRhiShader *prRhiCreateShaderVk(PrRhiDevice *device, PrRhiShaderDesc desc,
|
PrRhiShader *prRhiCreateShaderVk(PrRhiDevice *device, PrRhiShaderDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyShaderVk(PrRhiDevice *device, PrRhiShader *shader);
|
||||||
void prRhiDestroyShaderVk(PrRhiDevice *device, PrRhiShader *shader, WpAllocator *alloc);
|
|
||||||
|
|
||||||
PrRhiPipelineLayout *prRhiCreatePipelineLayoutVk(PrRhiDevice *device,
|
PrRhiPipelineLayout *prRhiCreatePipelineLayoutVk(PrRhiDevice *device,
|
||||||
PrRhiPipelineLayoutDesc desc,
|
PrRhiPipelineLayoutDesc desc);
|
||||||
WpAllocator *alloc);
|
|
||||||
void prRhiDestroyPipelineLayoutVk(PrRhiDevice *device,
|
void prRhiDestroyPipelineLayoutVk(PrRhiDevice *device,
|
||||||
PrRhiPipelineLayout *layout,
|
PrRhiPipelineLayout *layout);
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
PrRhiPipeline *prRhiCreateGraphicsPipelineVk(PrRhiDevice *device,
|
PrRhiPipeline *prRhiCreateGraphicsPipelineVk(PrRhiDevice *device,
|
||||||
PrRhiGraphicsPipelineDesc desc,
|
PrRhiGraphicsPipelineDesc desc);
|
||||||
WpAllocator *alloc);
|
|
||||||
PrRhiPipeline *prRhiCreateComputePipelineVk(PrRhiDevice *device,
|
PrRhiPipeline *prRhiCreateComputePipelineVk(PrRhiDevice *device,
|
||||||
PrRhiComputePipelineDesc desc,
|
PrRhiComputePipelineDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyPipelineVk(PrRhiDevice *device, PrRhiPipeline *pipeline);
|
||||||
void prRhiDestroyPipelineVk(PrRhiDevice *device, PrRhiPipeline *pipeline,
|
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
PrRhiDescriptorSetLayout *prRhiCreateDescriptorSetLayoutVk(PrRhiDevice *device,
|
PrRhiDescriptorSetLayout *prRhiCreateDescriptorSetLayoutVk(PrRhiDevice *device,
|
||||||
PrRhiDescriptorSetLayoutDesc desc,
|
PrRhiDescriptorSetLayoutDesc desc);
|
||||||
WpAllocator *alloc);
|
|
||||||
void prRhiDestroyDescriptorSetLayoutVk(PrRhiDevice *device,
|
void prRhiDestroyDescriptorSetLayoutVk(PrRhiDevice *device,
|
||||||
PrRhiDescriptorSetLayout *layout,
|
PrRhiDescriptorSetLayout *layout);
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
PrRhiDescriptorPool *prRhiCreateDescriptorPoolVk(PrRhiDevice *device,
|
PrRhiDescriptorPool *prRhiCreateDescriptorPoolVk(PrRhiDevice *device,
|
||||||
PrRhiDescriptorPoolDesc desc,
|
PrRhiDescriptorPoolDesc desc);
|
||||||
WpAllocator *alloc);
|
|
||||||
void prRhiDestroyDescriptorPoolVk(PrRhiDevice *device,
|
void prRhiDestroyDescriptorPoolVk(PrRhiDevice *device,
|
||||||
PrRhiDescriptorPool *pool,
|
PrRhiDescriptorPool *pool);
|
||||||
WpAllocator *alloc);
|
void prRhiResetDescriptorPoolVk(PrRhiDevice *device,
|
||||||
|
PrRhiDescriptorPool *pool);
|
||||||
|
|
||||||
PrRhiDescriptorSet *prRhiAllocateDescriptorSetVk(PrRhiDevice *device,
|
PrRhiDescriptorSet *prRhiAllocateDescriptorSetVk(PrRhiDevice *device,
|
||||||
PrRhiDescriptorPool *pool,
|
PrRhiDescriptorPool *pool,
|
||||||
PrRhiDescriptorSetLayout *layout,
|
PrRhiDescriptorSetLayout *layout,
|
||||||
u32 variable_count, WpAllocator *alloc);
|
WpU32Array variable_descriptor_counts);
|
||||||
void prRhiFreeDescriptorSetVk(PrRhiDevice *device, PrRhiDescriptorPool *pool,
|
void prRhiFreeDescriptorSetVk(PrRhiDevice *device, PrRhiDescriptorPool *pool,
|
||||||
PrRhiDescriptorSet *set, WpAllocator *alloc);
|
PrRhiDescriptorSet *set);
|
||||||
void prRhiUpdateDescriptorSetVk(PrRhiDevice *device, PrRhiWriteDescriptorSetArray writes);
|
void prRhiUpdateDescriptorSetVk(PrRhiDevice *device, PrRhiWriteDescriptorSetArray writes);
|
||||||
|
|
||||||
PrRhiFence *prRhiCreateFenceVk(PrRhiDevice *device, PrRhiFenceDesc desc,
|
PrRhiFence *prRhiCreateFenceVk(PrRhiDevice *device, PrRhiFenceDesc desc);
|
||||||
WpAllocator *alloc);
|
void prRhiDestroyFenceVk(PrRhiDevice *device, PrRhiFence *fence);
|
||||||
void prRhiDestroyFenceVk(PrRhiDevice *device, PrRhiFence *fence, WpAllocator *alloc);
|
|
||||||
|
|
||||||
void prRhiWaitForFencesVk(PrRhiDevice *device, PrRhiFenceArray fences, u32 count,
|
void prRhiWaitForFencesVk(PrRhiDevice *device, PrRhiFenceArray fences, u32 count,
|
||||||
b8 wait_all, u64 timeout_ns);
|
b8 wait_all, u64 timeout_ns);
|
||||||
void prRhiResetFencesVk(PrRhiDevice *device, PrRhiFenceArray fences, u32 count);
|
void prRhiResetFencesVk(PrRhiDevice *device, PrRhiFenceArray fences, u32 count);
|
||||||
|
|
||||||
PrRhiSemaphore *prRhiCreateSemaphoreVk(PrRhiDevice *device, WpAllocator *alloc);
|
PrRhiSemaphore *prRhiCreateSemaphoreVk(PrRhiDevice *device);
|
||||||
void prRhiDestroySemaphoreVk(PrRhiDevice *device, PrRhiSemaphore *semaphore,
|
void prRhiDestroySemaphoreVk(PrRhiDevice *device, PrRhiSemaphore *semaphore);
|
||||||
WpAllocator *alloc);
|
|
||||||
|
|
||||||
PrRhiCommandPool *prRhiCreateCommandPoolVk(PrRhiDevice *device,
|
PrRhiCommandPool *prRhiCreateCommandPoolVk(PrRhiDevice *device);
|
||||||
PrRhiCommandPoolDesc desc,
|
void prRhiDestroyCommandPoolVk(PrRhiDevice *device, PrRhiCommandPool *pool);
|
||||||
WpAllocator *alloc);
|
|
||||||
void prRhiDestroyCommandPoolVk(PrRhiDevice *device, PrRhiCommandPool *pool,
|
|
||||||
WpAllocator *alloc);
|
|
||||||
PrRhiCommandBufferArray prRhiAllocateCommandBuffersVk(PrRhiDevice *device, PrRhiCommandPool *pool,
|
PrRhiCommandBufferArray prRhiAllocateCommandBuffersVk(PrRhiDevice *device, PrRhiCommandPool *pool,
|
||||||
u32 count, WpAllocator *alloc);
|
u32 count);
|
||||||
void prRhiFreeCommandBuffersVk(PrRhiDevice *device, PrRhiCommandPool *pool,
|
|
||||||
u32 count, PrRhiCommandBufferArray buffers);
|
void prRhiFreeCommandBuffersVk(PrRhiDevice *device, PrRhiCommandPool *pool, PrRhiCommandBufferArray buffers);
|
||||||
|
|
||||||
void prRhiBeginCommandBufferVk(PrRhiCommandBuffer *cb);
|
void prRhiBeginCommandBufferVk(PrRhiCommandBuffer *cb);
|
||||||
void prRhiEndCommandBufferVk(PrRhiCommandBuffer *cb);
|
void prRhiEndCommandBufferVk(PrRhiCommandBuffer *cb);
|
||||||
@@ -243,8 +246,8 @@ void prRhiCmdBindDescriptorSetsVk(PrRhiCommandBuffer *cb, PrRhiPipelineBindPoint
|
|||||||
void prRhiCmdPushConstantsVk(PrRhiCommandBuffer *cb, PrRhiPipelineLayout *layout,
|
void prRhiCmdPushConstantsVk(PrRhiCommandBuffer *cb, PrRhiPipelineLayout *layout,
|
||||||
PrRhiShaderStage stage_flags, u32 offset, u32 size,
|
PrRhiShaderStage stage_flags, u32 offset, u32 size,
|
||||||
const void *data);
|
const void *data);
|
||||||
void prRhiCmdBindVertexBuffersVk(PrRhiCommandBuffer *cb, u32 first_binding,
|
void prRhiCmdBindVertexBuffersVk(PrRhiCommandBuffer *cb, u32 first_binding, PrRhiBufferArray buffers,
|
||||||
PrRhiBufferArray buffers, const u64 *offsets, u32 count);
|
WpU64Array offsets);
|
||||||
void prRhiCmdBindIndexBufferVk(PrRhiCommandBuffer *cb, PrRhiBuffer *buffer, u64 offset,
|
void prRhiCmdBindIndexBufferVk(PrRhiCommandBuffer *cb, PrRhiBuffer *buffer, u64 offset,
|
||||||
PrRhiIndexType index_type);
|
PrRhiIndexType index_type);
|
||||||
|
|
||||||
@@ -253,7 +256,8 @@ void prRhiCmdDrawVk(PrRhiCommandBuffer *cb, u32 vertex_count, u32 instance_count
|
|||||||
void prRhiCmdDrawIndexedVk(PrRhiCommandBuffer *cb, u32 index_count, u32 instance_count,
|
void prRhiCmdDrawIndexedVk(PrRhiCommandBuffer *cb, u32 index_count, u32 instance_count,
|
||||||
u32 first_index, i32 vertex_offset, u32 first_instance);
|
u32 first_index, i32 vertex_offset, u32 first_instance);
|
||||||
|
|
||||||
void prRhiCmdCopyBufferToImageVk(PrRhiCommandBuffer *cb, PrRhiBuffer *src, PrRhiTexture *dst);
|
void prRhiCmdCopyBufferToImageVk(PrRhiCommandBuffer *cb, PrRhiBuffer *src, PrRhiTexture *dst,
|
||||||
|
PrRhiBufferImageCopyArray copies);
|
||||||
|
|
||||||
void prRhiQueueSubmitVk(PrRhiDevice *device, PrRhiCommandBuffer *cb,
|
void prRhiQueueSubmitVk(PrRhiDevice *device, PrRhiCommandBuffer *cb,
|
||||||
PrRhiSemaphore *wait_semaphore,
|
PrRhiSemaphore *wait_semaphore,
|
||||||
+9
-1
@@ -10,25 +10,32 @@
|
|||||||
#define prRhiGetPhysicalDevices prRhiGetPhysicalDevicesVk
|
#define prRhiGetPhysicalDevices prRhiGetPhysicalDevicesVk
|
||||||
#define prRhiGetPhysicalDeviceName prRhiGetPhysicalDeviceNameVk
|
#define prRhiGetPhysicalDeviceName prRhiGetPhysicalDeviceNameVk
|
||||||
#define prRhiGetPhysicalDeviceDriverInfo prRhiGetPhysicalDeviceDriverInfoVk
|
#define prRhiGetPhysicalDeviceDriverInfo prRhiGetPhysicalDeviceDriverInfoVk
|
||||||
#define prRhiCreateSurface prRhiCreateSurfaceVk
|
#define prRhiGetPhysicalDeviceProperties prRhiGetPhysicalDevicePropertiesVk
|
||||||
|
#define prRhiCreateSurfaceFromWindow prRhiCreateSurfaceFromWindowVk
|
||||||
#define prRhiDestroySurface prRhiDestroySurfaceVk
|
#define prRhiDestroySurface prRhiDestroySurfaceVk
|
||||||
|
#define prRhiGetSurfaceCapabilities prRhiGetSurfaceCapabilitiesVk
|
||||||
#define prRhiCreateDevice prRhiCreateDeviceVk
|
#define prRhiCreateDevice prRhiCreateDeviceVk
|
||||||
#define prRhiDestroyDevice prRhiDestroyDeviceVk
|
#define prRhiDestroyDevice prRhiDestroyDeviceVk
|
||||||
#define prRhiDeviceWaitIdle prRhiDeviceWaitIdleVk
|
#define prRhiDeviceWaitIdle prRhiDeviceWaitIdleVk
|
||||||
|
#define prRhiGetQueueFamilyIndex prRhiGetQueueFamilyIndexVk
|
||||||
#define prRhiCreateSwapchain prRhiCreateSwapchainVk
|
#define prRhiCreateSwapchain prRhiCreateSwapchainVk
|
||||||
#define prRhiDestroySwapchain prRhiDestroySwapchainVk
|
#define prRhiDestroySwapchain prRhiDestroySwapchainVk
|
||||||
|
#define prRhiGetSwapchainImageCount prRhiGetSwapchainImageCountVk
|
||||||
#define prRhiAcquireNextImage prRhiAcquireNextImageVk
|
#define prRhiAcquireNextImage prRhiAcquireNextImageVk
|
||||||
#define prRhiPresent prRhiPresentVk
|
#define prRhiPresent prRhiPresentVk
|
||||||
#define prRhiRecreateSwapchain prRhiRecreateSwapchainVk
|
#define prRhiRecreateSwapchain prRhiRecreateSwapchainVk
|
||||||
#define prRhiGetSwapchainTexture prRhiGetSwapchainTextureVk
|
#define prRhiGetSwapchainTexture prRhiGetSwapchainTextureVk
|
||||||
#define prRhiGetSwapchainDepthTexture prRhiGetSwapchainDepthTextureVk
|
#define prRhiGetSwapchainDepthTexture prRhiGetSwapchainDepthTextureVk
|
||||||
|
#define prRhiGetSwapchainFormat prRhiGetSwapchainFormatVk
|
||||||
#define prRhiCreateBuffer prRhiCreateBufferVk
|
#define prRhiCreateBuffer prRhiCreateBufferVk
|
||||||
#define prRhiDestroyBuffer prRhiDestroyBufferVk
|
#define prRhiDestroyBuffer prRhiDestroyBufferVk
|
||||||
#define prRhiBufferMap prRhiBufferMapVk
|
#define prRhiBufferMap prRhiBufferMapVk
|
||||||
#define prRhiBufferUnmap prRhiBufferUnmapVk
|
#define prRhiBufferUnmap prRhiBufferUnmapVk
|
||||||
#define prRhiGetBufferDeviceAddress prRhiGetBufferDeviceAddressVk
|
#define prRhiGetBufferDeviceAddress prRhiGetBufferDeviceAddressVk
|
||||||
#define prRhiCreateTexture prRhiCreateTextureVk
|
#define prRhiCreateTexture prRhiCreateTextureVk
|
||||||
|
#define prRhiCreateTextureFromKtx prRhiCreateTextureFromKtxVk
|
||||||
#define prRhiDestroyTexture prRhiDestroyTextureVk
|
#define prRhiDestroyTexture prRhiDestroyTextureVk
|
||||||
|
#define prRhiGetTextureSize prRhiGetTextureSizeVk
|
||||||
#define prRhiCreateSampler prRhiCreateSamplerVk
|
#define prRhiCreateSampler prRhiCreateSamplerVk
|
||||||
#define prRhiDestroySampler prRhiDestroySamplerVk
|
#define prRhiDestroySampler prRhiDestroySamplerVk
|
||||||
#define prRhiCreateShader prRhiCreateShaderVk
|
#define prRhiCreateShader prRhiCreateShaderVk
|
||||||
@@ -42,6 +49,7 @@
|
|||||||
#define prRhiDestroyDescriptorSetLayout prRhiDestroyDescriptorSetLayoutVk
|
#define prRhiDestroyDescriptorSetLayout prRhiDestroyDescriptorSetLayoutVk
|
||||||
#define prRhiCreateDescriptorPool prRhiCreateDescriptorPoolVk
|
#define prRhiCreateDescriptorPool prRhiCreateDescriptorPoolVk
|
||||||
#define prRhiDestroyDescriptorPool prRhiDestroyDescriptorPoolVk
|
#define prRhiDestroyDescriptorPool prRhiDestroyDescriptorPoolVk
|
||||||
|
#define prRhiResetDescriptorPool prRhiResetDescriptorPoolVk
|
||||||
#define prRhiAllocateDescriptorSet prRhiAllocateDescriptorSetVk
|
#define prRhiAllocateDescriptorSet prRhiAllocateDescriptorSetVk
|
||||||
#define prRhiFreeDescriptorSet prRhiFreeDescriptorSetVk
|
#define prRhiFreeDescriptorSet prRhiFreeDescriptorSetVk
|
||||||
#define prRhiUpdateDescriptorSet prRhiUpdateDescriptorSetVk
|
#define prRhiUpdateDescriptorSet prRhiUpdateDescriptorSetVk
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#define VMA_IMPLEMENTATION
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.khronos.org/vulkan/profiles-0.8-latest.json#",
|
||||||
|
"capabilities": {
|
||||||
|
"VP_PRISM_desktop_2026_block": {
|
||||||
|
"extensions": {
|
||||||
|
"VK_KHR_global_priority": 1,
|
||||||
|
"VK_KHR_get_surface_capabilities2": 1,
|
||||||
|
"VK_KHR_swapchain": 70,
|
||||||
|
"VK_KHR_maintenance5": 1
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"VkPhysicalDeviceFeatures": {
|
||||||
|
"robustBufferAccess": true,
|
||||||
|
"fullDrawIndexUint32": true,
|
||||||
|
"imageCubeArray": true,
|
||||||
|
"independentBlend": true,
|
||||||
|
"sampleRateShading": true,
|
||||||
|
"drawIndirectFirstInstance": true,
|
||||||
|
"depthClamp": true,
|
||||||
|
"depthBiasClamp": true,
|
||||||
|
"samplerAnisotropy": true,
|
||||||
|
"occlusionQueryPrecise": true,
|
||||||
|
"fragmentStoresAndAtomics": true,
|
||||||
|
"shaderStorageImageExtendedFormats": true,
|
||||||
|
"shaderUniformBufferArrayDynamicIndexing": true,
|
||||||
|
"shaderSampledImageArrayDynamicIndexing": true,
|
||||||
|
"shaderStorageBufferArrayDynamicIndexing": true,
|
||||||
|
"shaderStorageImageArrayDynamicIndexing": true
|
||||||
|
},
|
||||||
|
"VkPhysicalDeviceVulkan11Features": {
|
||||||
|
"multiview": true,
|
||||||
|
"samplerYcbcrConversion": true
|
||||||
|
},
|
||||||
|
"VkPhysicalDeviceVulkan12Features": {
|
||||||
|
"uniformBufferStandardLayout": true,
|
||||||
|
"subgroupBroadcastDynamicId": true,
|
||||||
|
"imagelessFramebuffer": true,
|
||||||
|
"separateDepthStencilLayouts": true,
|
||||||
|
"hostQueryReset": true,
|
||||||
|
"timelineSemaphore": true,
|
||||||
|
"shaderSubgroupExtendedTypes": true,
|
||||||
|
"samplerMirrorClampToEdge": true,
|
||||||
|
"descriptorIndexing": true,
|
||||||
|
"shaderUniformTexelBufferArrayDynamicIndexing": true,
|
||||||
|
"shaderStorageTexelBufferArrayDynamicIndexing": true,
|
||||||
|
"shaderUniformBufferArrayNonUniformIndexing": true,
|
||||||
|
"shaderSampledImageArrayNonUniformIndexing": true,
|
||||||
|
"shaderStorageBufferArrayNonUniformIndexing": true,
|
||||||
|
"shaderStorageImageArrayNonUniformIndexing": true,
|
||||||
|
"shaderUniformTexelBufferArrayNonUniformIndexing": true,
|
||||||
|
"shaderStorageTexelBufferArrayNonUniformIndexing": true,
|
||||||
|
"descriptorBindingSampledImageUpdateAfterBind": true,
|
||||||
|
"descriptorBindingStorageImageUpdateAfterBind": true,
|
||||||
|
"descriptorBindingStorageBufferUpdateAfterBind": true,
|
||||||
|
"descriptorBindingUniformTexelBufferUpdateAfterBind": true,
|
||||||
|
"descriptorBindingStorageTexelBufferUpdateAfterBind": true,
|
||||||
|
"descriptorBindingUpdateUnusedWhilePending": true,
|
||||||
|
"descriptorBindingPartiallyBound": true,
|
||||||
|
"descriptorBindingVariableDescriptorCount": true,
|
||||||
|
"runtimeDescriptorArray": true,
|
||||||
|
"scalarBlockLayout": true,
|
||||||
|
"vulkanMemoryModel": true,
|
||||||
|
"vulkanMemoryModelDeviceScope": true,
|
||||||
|
"bufferDeviceAddress": true
|
||||||
|
},
|
||||||
|
"VkPhysicalDeviceVulkan13Features": {
|
||||||
|
"robustImageAccess": true,
|
||||||
|
"shaderTerminateInvocation": true,
|
||||||
|
"shaderZeroInitializeWorkgroupMemory": true,
|
||||||
|
"synchronization2": true,
|
||||||
|
"shaderIntegerDotProduct": true,
|
||||||
|
"maintenance4": true,
|
||||||
|
"pipelineCreationCacheControl": true,
|
||||||
|
"subgroupSizeControl": true,
|
||||||
|
"computeFullSubgroups": true,
|
||||||
|
"shaderDemoteToHelperInvocation": true,
|
||||||
|
"inlineUniformBlock": true,
|
||||||
|
"dynamicRendering": true,
|
||||||
|
"descriptorBindingInlineUniformBlockUpdateAfterBind": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"VkPhysicalDeviceProperties": {
|
||||||
|
"limits": {
|
||||||
|
"maxImageDimension1D": 8192,
|
||||||
|
"maxImageDimension2D": 8192,
|
||||||
|
"maxImageDimensionCube": 8192,
|
||||||
|
"maxImageArrayLayers": 2048,
|
||||||
|
"maxUniformBufferRange": 65536,
|
||||||
|
"bufferImageGranularity": 4096,
|
||||||
|
"maxPerStageDescriptorSamplers": 64,
|
||||||
|
"maxPerStageDescriptorUniformBuffers": 15,
|
||||||
|
"maxPerStageDescriptorStorageBuffers": 30,
|
||||||
|
"maxPerStageDescriptorSampledImages": 200,
|
||||||
|
"maxPerStageDescriptorStorageImages": 16,
|
||||||
|
"maxPerStageResources": 200,
|
||||||
|
"maxDescriptorSetSamplers": 576,
|
||||||
|
"maxDescriptorSetUniformBuffers": 90,
|
||||||
|
"maxDescriptorSetStorageBuffers": 96,
|
||||||
|
"maxDescriptorSetSampledImages": 1800,
|
||||||
|
"maxDescriptorSetStorageImages": 144,
|
||||||
|
"maxFragmentCombinedOutputResources": 16,
|
||||||
|
"maxComputeWorkGroupInvocations": 256,
|
||||||
|
"maxComputeWorkGroupSize": [
|
||||||
|
256,
|
||||||
|
256,
|
||||||
|
64
|
||||||
|
],
|
||||||
|
"subTexelPrecisionBits": 8,
|
||||||
|
"mipmapPrecisionBits": 6,
|
||||||
|
"maxSamplerLodBias": 14,
|
||||||
|
"standardSampleLocations": true,
|
||||||
|
"maxColorAttachments": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"VkPhysicalDeviceVulkan11Properties": {
|
||||||
|
"maxMultiviewViewCount": 6,
|
||||||
|
"maxMultiviewInstanceIndex": 134217727,
|
||||||
|
"subgroupSize": 4,
|
||||||
|
"subgroupSupportedStages": [
|
||||||
|
"VK_SHADER_STAGE_COMPUTE_BIT",
|
||||||
|
"VK_SHADER_STAGE_FRAGMENT_BIT"
|
||||||
|
],
|
||||||
|
"subgroupSupportedOperations": [
|
||||||
|
"VK_SUBGROUP_FEATURE_BASIC_BIT",
|
||||||
|
"VK_SUBGROUP_FEATURE_VOTE_BIT",
|
||||||
|
"VK_SUBGROUP_FEATURE_ARITHMETIC_BIT",
|
||||||
|
"VK_SUBGROUP_FEATURE_BALLOT_BIT",
|
||||||
|
"VK_SUBGROUP_FEATURE_SHUFFLE_BIT",
|
||||||
|
"VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT",
|
||||||
|
"VK_SUBGROUP_FEATURE_QUAD_BIT"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"VkPhysicalDeviceVulkan12Properties": {
|
||||||
|
"maxTimelineSemaphoreValueDifference": 2147483647,
|
||||||
|
"shaderSignedZeroInfNanPreserveFloat16": true,
|
||||||
|
"shaderSignedZeroInfNanPreserveFloat32": true,
|
||||||
|
"maxPerStageDescriptorUpdateAfterBindSamplers": 500000,
|
||||||
|
"maxPerStageDescriptorUpdateAfterBindUniformBuffers": 12,
|
||||||
|
"maxPerStageDescriptorUpdateAfterBindStorageBuffers": 500000,
|
||||||
|
"maxPerStageDescriptorUpdateAfterBindSampledImages": 500000,
|
||||||
|
"maxPerStageDescriptorUpdateAfterBindStorageImages": 500000,
|
||||||
|
"maxPerStageDescriptorUpdateAfterBindInputAttachments": 7,
|
||||||
|
"maxPerStageUpdateAfterBindResources": 500000,
|
||||||
|
"maxDescriptorSetUpdateAfterBindSamplers": 500000,
|
||||||
|
"maxDescriptorSetUpdateAfterBindUniformBuffers": 72,
|
||||||
|
"maxDescriptorSetUpdateAfterBindUniformBuffersDynamic": 8,
|
||||||
|
"maxDescriptorSetUpdateAfterBindStorageBuffers": 500000,
|
||||||
|
"maxDescriptorSetUpdateAfterBindStorageBuffersDynamic": 4,
|
||||||
|
"maxDescriptorSetUpdateAfterBindSampledImages": 500000,
|
||||||
|
"maxDescriptorSetUpdateAfterBindStorageImages": 500000,
|
||||||
|
"maxDescriptorSetUpdateAfterBindInputAttachments": 7
|
||||||
|
},
|
||||||
|
"VkPhysicalDeviceVulkan13Properties": {
|
||||||
|
"maxBufferSize": 1073741824,
|
||||||
|
"maxInlineUniformBlockSize": 256,
|
||||||
|
"maxPerStageDescriptorInlineUniformBlocks": 4,
|
||||||
|
"maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks": 4,
|
||||||
|
"maxDescriptorSetInlineUniformBlocks": 4,
|
||||||
|
"maxDescriptorSetUpdateAfterBindInlineUniformBlocks": 4,
|
||||||
|
"maxInlineUniformTotalSize": 256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"VP_PRISM_desktop_2026": {
|
||||||
|
"version": 1,
|
||||||
|
"api-version": "1.3.204",
|
||||||
|
"label": "Prism Desktop 2026",
|
||||||
|
"description": "Generated profile doing an union between profiles: VP_KHR_roadmap_2022",
|
||||||
|
"capabilities": [
|
||||||
|
"VP_PRISM_desktop_2026_block"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contributors": {},
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"revision": 1,
|
||||||
|
"date": "2026-05-25",
|
||||||
|
"author": "LunarG Profiles Merge Script",
|
||||||
|
"comment": "Generated profiles file"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,434 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021-2026 Valve Corporation
|
||||||
|
* Copyright (C) 2021-2026 LunarG, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License")
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* This file is ***GENERATED***. Do Not Edit.
|
||||||
|
* See scripts/gen_profiles_solution.py for modifications.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VULKAN_PROFILES_H_
|
||||||
|
#define VULKAN_PROFILES_H_ 1
|
||||||
|
|
||||||
|
#define VPAPI_ATTR
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <volk/volk.h>
|
||||||
|
|
||||||
|
#if defined(VK_VERSION_1_3) && \
|
||||||
|
defined(VK_KHR_get_surface_capabilities2) && \
|
||||||
|
defined(VK_KHR_global_priority) && \
|
||||||
|
defined(VK_KHR_maintenance5) && \
|
||||||
|
defined(VK_KHR_swapchain)
|
||||||
|
#define VP_PRISM_desktop_2026 1
|
||||||
|
#define VP_PRISM_DESKTOP_2026_NAME "VP_PRISM_desktop_2026"
|
||||||
|
#define VP_PRISM_DESKTOP_2026_SPEC_VERSION 1
|
||||||
|
#define VP_PRISM_DESKTOP_2026_MIN_API_VERSION VK_MAKE_VERSION(1, 3, 204)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define VP_HEADER_VERSION_COMPLETE VK_MAKE_API_VERSION(0, 2, 0, VK_HEADER_VERSION)
|
||||||
|
|
||||||
|
#define VP_MAX_PROFILE_NAME_SIZE 256U
|
||||||
|
|
||||||
|
typedef struct VpProfileProperties {
|
||||||
|
char profileName[VP_MAX_PROFILE_NAME_SIZE];
|
||||||
|
uint32_t specVersion;
|
||||||
|
} VpProfileProperties;
|
||||||
|
|
||||||
|
typedef struct VpBlockProperties {
|
||||||
|
VpProfileProperties profiles;
|
||||||
|
uint32_t apiVersion;
|
||||||
|
char blockName[VP_MAX_PROFILE_NAME_SIZE];
|
||||||
|
} VpBlockProperties;
|
||||||
|
|
||||||
|
typedef struct VpVideoProfileProperties {
|
||||||
|
char name[VP_MAX_PROFILE_NAME_SIZE];
|
||||||
|
} VpVideoProfileProperties;
|
||||||
|
|
||||||
|
typedef enum VpInstanceCreateFlagBits {
|
||||||
|
VP_INSTANCE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
|
||||||
|
} VpInstanceCreateFlagBits;
|
||||||
|
typedef VkFlags VpInstanceCreateFlags;
|
||||||
|
|
||||||
|
typedef struct VpInstanceCreateInfo {
|
||||||
|
const VkInstanceCreateInfo* pCreateInfo;
|
||||||
|
VpInstanceCreateFlags flags;
|
||||||
|
uint32_t enabledFullProfileCount;
|
||||||
|
const VpProfileProperties* pEnabledFullProfiles;
|
||||||
|
uint32_t enabledProfileBlockCount;
|
||||||
|
const VpBlockProperties* pEnabledProfileBlocks;
|
||||||
|
} VpInstanceCreateInfo;
|
||||||
|
|
||||||
|
typedef enum VpDeviceCreateFlagBits {
|
||||||
|
VP_DEVICE_CREATE_DISABLE_ROBUST_BUFFER_ACCESS_BIT = 0x0000001,
|
||||||
|
VP_DEVICE_CREATE_DISABLE_ROBUST_IMAGE_ACCESS_BIT = 0x0000002,
|
||||||
|
VP_DEVICE_CREATE_DISABLE_ROBUST_ACCESS =
|
||||||
|
VP_DEVICE_CREATE_DISABLE_ROBUST_BUFFER_ACCESS_BIT | VP_DEVICE_CREATE_DISABLE_ROBUST_IMAGE_ACCESS_BIT,
|
||||||
|
|
||||||
|
VP_DEVICE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
|
||||||
|
} VpDeviceCreateFlagBits;
|
||||||
|
typedef VkFlags VpDeviceCreateFlags;
|
||||||
|
|
||||||
|
typedef struct VpDeviceCreateInfo {
|
||||||
|
const VkDeviceCreateInfo* pCreateInfo;
|
||||||
|
VpDeviceCreateFlags flags;
|
||||||
|
uint32_t enabledFullProfileCount;
|
||||||
|
const VpProfileProperties* pEnabledFullProfiles;
|
||||||
|
uint32_t enabledProfileBlockCount;
|
||||||
|
const VpBlockProperties* pEnabledProfileBlocks;
|
||||||
|
} VpDeviceCreateInfo;
|
||||||
|
|
||||||
|
VK_DEFINE_HANDLE(VpCapabilities)
|
||||||
|
|
||||||
|
typedef enum VpCapabilitiesCreateFlagBits {
|
||||||
|
VP_PROFILE_CREATE_STATIC_BIT = (1 << 0),
|
||||||
|
//VP_PROFILE_CREATE_DYNAMIC_BIT = (1 << 1),
|
||||||
|
VP_PROFILE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
|
||||||
|
} VpCapabilitiesCreateFlagBits;
|
||||||
|
|
||||||
|
typedef VkFlags VpCapabilitiesCreateFlags;
|
||||||
|
|
||||||
|
// Pointers to some Vulkan functions - a subset used by the library.
|
||||||
|
// Used in VpCapabilitiesCreateInfo::pVulkanFunctions.
|
||||||
|
|
||||||
|
typedef struct VpVulkanFunctions {
|
||||||
|
/// Required when using VP_DYNAMIC_VULKAN_FUNCTIONS.
|
||||||
|
PFN_vkGetInstanceProcAddr GetInstanceProcAddr;
|
||||||
|
/// Required when using VP_DYNAMIC_VULKAN_FUNCTIONS.
|
||||||
|
PFN_vkGetDeviceProcAddr GetDeviceProcAddr;
|
||||||
|
PFN_vkEnumerateInstanceVersion EnumerateInstanceVersion;
|
||||||
|
PFN_vkEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties;
|
||||||
|
PFN_vkEnumerateDeviceExtensionProperties EnumerateDeviceExtensionProperties;
|
||||||
|
PFN_vkGetPhysicalDeviceFeatures2 GetPhysicalDeviceFeatures2;
|
||||||
|
PFN_vkGetPhysicalDeviceProperties2 GetPhysicalDeviceProperties2;
|
||||||
|
PFN_vkGetPhysicalDeviceFormatProperties2 GetPhysicalDeviceFormatProperties2;
|
||||||
|
PFN_vkGetPhysicalDeviceQueueFamilyProperties2 GetPhysicalDeviceQueueFamilyProperties2;
|
||||||
|
PFN_vkCreateInstance CreateInstance;
|
||||||
|
PFN_vkCreateDevice CreateDevice;
|
||||||
|
} VpVulkanFunctions;
|
||||||
|
|
||||||
|
/// Description of a Allocator to be created.
|
||||||
|
typedef struct VpCapabilitiesCreateInfo
|
||||||
|
{
|
||||||
|
/// Flags for created allocator. Use #VpInstanceCreateFlagBits enum.
|
||||||
|
VpCapabilitiesCreateFlags flags;
|
||||||
|
uint32_t apiVersion;
|
||||||
|
const VpVulkanFunctions* pVulkanFunctions;
|
||||||
|
} VpCapabilitiesCreateInfo;
|
||||||
|
|
||||||
|
VPAPI_ATTR VkResult vpCreateCapabilities(
|
||||||
|
const VpCapabilitiesCreateInfo* pCreateInfo,
|
||||||
|
const VkAllocationCallbacks* pAllocator,
|
||||||
|
VpCapabilities* pCapabilities);
|
||||||
|
|
||||||
|
/// Destroys allocator object.
|
||||||
|
VPAPI_ATTR void vpDestroyCapabilities(
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
const VkAllocationCallbacks* pAllocator);
|
||||||
|
|
||||||
|
// Query the list of available profiles in the library
|
||||||
|
VPAPI_ATTR VkResult vpGetProfiles(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
uint32_t* pPropertyCount,
|
||||||
|
VpProfileProperties* pProperties);
|
||||||
|
|
||||||
|
// List the required profiles of a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileRequiredProfiles(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
uint32_t* pPropertyCount,
|
||||||
|
VpProfileProperties* pProperties);
|
||||||
|
|
||||||
|
// Query the profile required Vulkan API version
|
||||||
|
VPAPI_ATTR uint32_t vpGetProfileAPIVersion(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile);
|
||||||
|
|
||||||
|
// List the recommended fallback profiles of a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileFallbacks(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
uint32_t* pPropertyCount,
|
||||||
|
VpProfileProperties* pProperties);
|
||||||
|
|
||||||
|
// Query whether the profile has multiple variants. Profiles with multiple variants can only use vpGetInstanceProfileSupport and vpGetPhysicalDeviceProfileSupport capabilities of the library. Other function will return a VK_ERROR_UNKNOWN error
|
||||||
|
VPAPI_ATTR VkResult vpHasMultipleVariantsProfile(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
VkBool32* pHasMultipleVariants);
|
||||||
|
|
||||||
|
// Check whether a profile is supported at the instance level
|
||||||
|
VPAPI_ATTR VkResult vpGetInstanceProfileSupport(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const char* pLayerName,
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
VkBool32* pSupported);
|
||||||
|
|
||||||
|
// Check whether a variant of a profile is supported at the instance level and report this list of blocks used to validate the profiles
|
||||||
|
VPAPI_ATTR VkResult vpGetInstanceProfileVariantsSupport(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const char* pLayerName,
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
VkBool32* pSupported,
|
||||||
|
uint32_t* pPropertyCount,
|
||||||
|
VpBlockProperties* pProperties);
|
||||||
|
|
||||||
|
// Create a VkInstance with the profile instance extensions enabled
|
||||||
|
VPAPI_ATTR VkResult vpCreateInstance(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpInstanceCreateInfo* pCreateInfo,
|
||||||
|
const VkAllocationCallbacks* pAllocator,
|
||||||
|
VkInstance* pInstance);
|
||||||
|
|
||||||
|
// Check whether a profile is supported by the physical device
|
||||||
|
VPAPI_ATTR VkResult vpGetPhysicalDeviceProfileSupport(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
VkInstance instance,
|
||||||
|
VkPhysicalDevice physicalDevice,
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
VkBool32* pSupported);
|
||||||
|
|
||||||
|
// Check whether a variant of a profile is supported by the physical device and report this list of blocks used to validate the profiles
|
||||||
|
VPAPI_ATTR VkResult vpGetPhysicalDeviceProfileVariantsSupport(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
VkInstance instance,
|
||||||
|
VkPhysicalDevice physicalDevice,
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
VkBool32* pSupported,
|
||||||
|
uint32_t* pPropertyCount,
|
||||||
|
VpBlockProperties* pProperties);
|
||||||
|
|
||||||
|
// Create a VkDevice with the profile features and device extensions enabled
|
||||||
|
VPAPI_ATTR VkResult vpCreateDevice(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
VkPhysicalDevice physicalDevice,
|
||||||
|
const VpDeviceCreateInfo* pCreateInfo,
|
||||||
|
const VkAllocationCallbacks* pAllocator,
|
||||||
|
VkDevice* pDevice);
|
||||||
|
|
||||||
|
// Query the list of instance extensions of a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileInstanceExtensionProperties(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t* pPropertyCount,
|
||||||
|
VkExtensionProperties* pProperties);
|
||||||
|
|
||||||
|
// Query the list of device extensions of a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileDeviceExtensionProperties(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t* pPropertyCount,
|
||||||
|
VkExtensionProperties* pProperties);
|
||||||
|
|
||||||
|
// Fill the feature structures with the requirements of a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileFeatures(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
void* pNext);
|
||||||
|
|
||||||
|
// Query the list of feature structure types specified by the profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileFeatureStructureTypes(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t* pStructureTypeCount,
|
||||||
|
VkStructureType* pStructureTypes);
|
||||||
|
|
||||||
|
// Fill the property structures with the requirements of a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileProperties(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
void* pNext);
|
||||||
|
|
||||||
|
// Query the list of property structure types specified by the profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfilePropertyStructureTypes(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t* pStructureTypeCount,
|
||||||
|
VkStructureType* pStructureTypes);
|
||||||
|
|
||||||
|
// Fill the queue family property structures with the requirements of a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileQueueFamilyProperties(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t* pPropertyCount,
|
||||||
|
VkQueueFamilyProperties2KHR* pProperties);
|
||||||
|
|
||||||
|
// Query the list of queue family property structure types specified by the profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileQueueFamilyStructureTypes(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t* pStructureTypeCount,
|
||||||
|
VkStructureType* pStructureTypes);
|
||||||
|
|
||||||
|
// Query the list of formats with specified requirements by a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileFormats(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t* pFormatCount,
|
||||||
|
VkFormat* pFormats);
|
||||||
|
|
||||||
|
// Query the requirements of a format for a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileFormatProperties(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
VkFormat format,
|
||||||
|
void* pNext);
|
||||||
|
|
||||||
|
// Query the list of format structure types specified by the profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileFormatStructureTypes(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t* pStructureTypeCount,
|
||||||
|
VkStructureType* pStructureTypes);
|
||||||
|
|
||||||
|
#ifdef VK_KHR_video_queue
|
||||||
|
// Query the list of video profiles specified by the profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileVideoProfiles(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t* pVideoProfileCount,
|
||||||
|
VpVideoProfileProperties* pVideoProfiles);
|
||||||
|
|
||||||
|
// Query the video profile info structures for a video profile defined by a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileVideoProfileInfo(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t videoProfileIndex,
|
||||||
|
VkVideoProfileInfoKHR* pVideoProfileInfo);
|
||||||
|
|
||||||
|
// Query the list of video profile info structure types specified by the profile for a video profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileVideoProfileInfoStructureTypes(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t videoProfileIndex,
|
||||||
|
uint32_t* pStructureTypeCount,
|
||||||
|
VkStructureType* pStructureTypes);
|
||||||
|
|
||||||
|
// Query the video capabilities requirements for a video profile defined by a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileVideoCapabilities(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t videoProfileIndex,
|
||||||
|
void* pNext);
|
||||||
|
|
||||||
|
// Query the list of video capability structure types specified by the profile for a video profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileVideoCapabilityStructureTypes(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t videoProfileIndex,
|
||||||
|
uint32_t* pStructureTypeCount,
|
||||||
|
VkStructureType* pStructureTypes);
|
||||||
|
|
||||||
|
// Query the video format property requirements for a video profile defined by a profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileVideoFormatProperties(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t videoProfileIndex,
|
||||||
|
uint32_t* pPropertyCount,
|
||||||
|
VkVideoFormatPropertiesKHR* pProperties);
|
||||||
|
|
||||||
|
// Query the list of video format property structure types specified by the profile for a video profile
|
||||||
|
VPAPI_ATTR VkResult vpGetProfileVideoFormatStructureTypes(
|
||||||
|
#ifdef VP_USE_OBJECT
|
||||||
|
VpCapabilities capabilities,
|
||||||
|
#endif//VP_USE_OBJECT
|
||||||
|
const VpProfileProperties* pProfile,
|
||||||
|
const char* pBlockName,
|
||||||
|
uint32_t videoProfileIndex,
|
||||||
|
uint32_t* pStructureTypeCount,
|
||||||
|
VkStructureType* pStructureTypes);
|
||||||
|
#endif // VK_KHR_video_queue
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // VULKAN_PROFILES_H_
|
||||||
File diff suppressed because it is too large
Load Diff
+1
Submodule src/vendor/ktx added at 4d6fc70eaf
@@ -7,14 +7,14 @@
|
|||||||
#include "../../common/aliases/aliases.h"
|
#include "../../common/aliases/aliases.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#define _array_header(ARRAY) (WpArrayHeader *)(wpMiscUtilsOffsetPointer(ARRAY, (i64)sizeof(WpArrayHeader) * -1))
|
#define _arrayHeader(ARRAY) (WpArrayHeader *)(wpMiscUtilsOffsetPointer(ARRAY, (i64)sizeof(WpArrayHeader) * -1))
|
||||||
|
|
||||||
wp_persist inline void _array_validate(const WpArray array, u64 item_size);
|
wp_persist inline void _arrayValidate(const WpArray array, u64 item_size);
|
||||||
|
|
||||||
u64 _arrayCount(WpArray array) {
|
u64 _arrayCount(WpArray array) {
|
||||||
wpDebugAssert(array != NULL, "`array` should not be NULL");
|
wpDebugAssert(array != NULL, "`array` should not be NULL");
|
||||||
|
|
||||||
WpArrayHeader *header = _array_header(array);
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
wpRuntimeAssert(WP_ARRAY_MAGIC == header->magic, "`array` is not a valid wapp array");
|
wpRuntimeAssert(WP_ARRAY_MAGIC == header->magic, "`array` is not a valid wapp array");
|
||||||
|
|
||||||
return header->count;
|
return header->count;
|
||||||
@@ -23,7 +23,7 @@ u64 _arrayCount(WpArray array) {
|
|||||||
u64 _arrayCapacity(WpArray array) {
|
u64 _arrayCapacity(WpArray array) {
|
||||||
wpDebugAssert(array != NULL, "`array` should not be NULL");
|
wpDebugAssert(array != NULL, "`array` should not be NULL");
|
||||||
|
|
||||||
WpArrayHeader *header = _array_header(array);
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
wpRuntimeAssert(WP_ARRAY_MAGIC == header->magic, "`array` is not a valid wapp array");
|
wpRuntimeAssert(WP_ARRAY_MAGIC == header->magic, "`array` is not a valid wapp array");
|
||||||
|
|
||||||
return header->capacity;
|
return header->capacity;
|
||||||
@@ -32,7 +32,7 @@ u64 _arrayCapacity(WpArray array) {
|
|||||||
u64 _arrayItemSize(WpArray array) {
|
u64 _arrayItemSize(WpArray array) {
|
||||||
wpDebugAssert(array != NULL, "`array` should not be NULL");
|
wpDebugAssert(array != NULL, "`array` should not be NULL");
|
||||||
|
|
||||||
WpArrayHeader *header = _array_header(array);
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
wpRuntimeAssert(WP_ARRAY_MAGIC == header->magic, "`array` is not a valid wapp array");
|
wpRuntimeAssert(WP_ARRAY_MAGIC == header->magic, "`array` is not a valid wapp array");
|
||||||
|
|
||||||
return header->item_size;
|
return header->item_size;
|
||||||
@@ -41,7 +41,7 @@ u64 _arrayItemSize(WpArray array) {
|
|||||||
void _arraySetCount(WpArray array, u64 count) {
|
void _arraySetCount(WpArray array, u64 count) {
|
||||||
wpDebugAssert(array != NULL, "`array` should not be NULL");
|
wpDebugAssert(array != NULL, "`array` should not be NULL");
|
||||||
|
|
||||||
WpArrayHeader *header = _array_header(array);
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
wpRuntimeAssert(WP_ARRAY_MAGIC == header->magic, "`array` is not a valid wapp array");
|
wpRuntimeAssert(WP_ARRAY_MAGIC == header->magic, "`array` is not a valid wapp array");
|
||||||
|
|
||||||
header->count = count;
|
header->count = count;
|
||||||
@@ -49,9 +49,9 @@ void _arraySetCount(WpArray array, u64 count) {
|
|||||||
|
|
||||||
void *_arrayGet(WpArray array, u64 index, u64 item_size) {
|
void *_arrayGet(WpArray array, u64 index, u64 item_size) {
|
||||||
wpRuntimeAssert(array != NULL, "`array` should not be NULL");
|
wpRuntimeAssert(array != NULL, "`array` should not be NULL");
|
||||||
_array_validate(array, item_size);
|
_arrayValidate(array, item_size);
|
||||||
|
|
||||||
WpArrayHeader *header = _array_header(array);
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
wpRuntimeAssert(index < header->count, "`index` is out of bounds");
|
wpRuntimeAssert(index < header->count, "`index` is out of bounds");
|
||||||
|
|
||||||
return wpMiscUtilsOffsetPointer(array, header->item_size * index);
|
return wpMiscUtilsOffsetPointer(array, header->item_size * index);
|
||||||
@@ -60,15 +60,15 @@ void *_arrayGet(WpArray array, u64 index, u64 item_size) {
|
|||||||
void _arraySet(WpArray array, u64 index, void *value, u64 item_size) {
|
void _arraySet(WpArray array, u64 index, void *value, u64 item_size) {
|
||||||
void *item = _arrayGet(array, index, item_size);
|
void *item = _arrayGet(array, index, item_size);
|
||||||
|
|
||||||
WpArrayHeader *header = _array_header(array);
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
memcpy(item, value, header->item_size);
|
memcpy(item, value, header->item_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _arrayAppendCapped(WpArray array, void *value, u64 item_size) {
|
void _arrayAppendCapped(WpArray array, void *value, u64 item_size) {
|
||||||
wpRuntimeAssert(array != NULL, "`array` should not be NULL");
|
wpRuntimeAssert(array != NULL, "`array` should not be NULL");
|
||||||
_array_validate(array, item_size);
|
_arrayValidate(array, item_size);
|
||||||
|
|
||||||
WpArrayHeader *header = _array_header(array);
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
if (header->count >= header->capacity) { return; }
|
if (header->count >= header->capacity) { return; }
|
||||||
|
|
||||||
u64 index = (header->count)++;
|
u64 index = (header->count)++;
|
||||||
@@ -77,11 +77,11 @@ void _arrayAppendCapped(WpArray array, void *value, u64 item_size) {
|
|||||||
|
|
||||||
void _arrayExtendCappend(WpArray dst, const WpArray src, u64 item_size) {
|
void _arrayExtendCappend(WpArray dst, const WpArray src, u64 item_size) {
|
||||||
wpRuntimeAssert(dst != NULL && src != NULL, "`dst` and `src` should not be NULL");
|
wpRuntimeAssert(dst != NULL && src != NULL, "`dst` and `src` should not be NULL");
|
||||||
_array_validate(dst, item_size);
|
_arrayValidate(dst, item_size);
|
||||||
_array_validate(src, item_size);
|
_arrayValidate(src, item_size);
|
||||||
|
|
||||||
WpArrayHeader *src_header = _array_header(src);
|
WpArrayHeader *src_header = _arrayHeader(src);
|
||||||
WpArrayHeader *dst_header = _array_header(dst);
|
WpArrayHeader *dst_header = _arrayHeader(dst);
|
||||||
u64 remaining_capacity = dst_header->capacity - dst_header->count;
|
u64 remaining_capacity = dst_header->capacity - dst_header->count;
|
||||||
|
|
||||||
u64 copy_count = src_header->count < remaining_capacity ? src_header->count : remaining_capacity;
|
u64 copy_count = src_header->count < remaining_capacity ? src_header->count : remaining_capacity;
|
||||||
@@ -92,13 +92,13 @@ void _arrayExtendCappend(WpArray dst, const WpArray src, u64 item_size) {
|
|||||||
|
|
||||||
void _arrayCopyCapped(WpArray dst, const WpArray src, u64 item_size) {
|
void _arrayCopyCapped(WpArray dst, const WpArray src, u64 item_size) {
|
||||||
wpRuntimeAssert(dst != NULL && src != NULL, "`dst` and `src` should not be NULL");
|
wpRuntimeAssert(dst != NULL && src != NULL, "`dst` and `src` should not be NULL");
|
||||||
_array_validate(dst, item_size);
|
_arrayValidate(dst, item_size);
|
||||||
_array_validate(src, item_size);
|
_arrayValidate(src, item_size);
|
||||||
|
|
||||||
_arrayClear(dst, item_size);
|
_arrayClear(dst, item_size);
|
||||||
|
|
||||||
WpArrayHeader *src_header = _array_header(src);
|
WpArrayHeader *src_header = _arrayHeader(src);
|
||||||
WpArrayHeader *dst_header = _array_header(dst);
|
WpArrayHeader *dst_header = _arrayHeader(dst);
|
||||||
u64 copy_count = src_header->count < dst_header->capacity ? src_header->count : dst_header->capacity;
|
u64 copy_count = src_header->count < dst_header->capacity ? src_header->count : dst_header->capacity;
|
||||||
memcpy((void *)dst, (void *)src, copy_count * src_header->item_size);
|
memcpy((void *)dst, (void *)src, copy_count * src_header->item_size);
|
||||||
dst_header->count = copy_count;
|
dst_header->count = copy_count;
|
||||||
@@ -107,11 +107,11 @@ void _arrayCopyCapped(WpArray dst, const WpArray src, u64 item_size) {
|
|||||||
WpArray _arrayAppendAlloc(const WpAllocator *allocator, WpArray array, void *value,
|
WpArray _arrayAppendAlloc(const WpAllocator *allocator, WpArray array, void *value,
|
||||||
WpArrayInitFlags flags, u64 item_size) {
|
WpArrayInitFlags flags, u64 item_size) {
|
||||||
wpRuntimeAssert(allocator != NULL && array != NULL, "`allocator` and `array` should not be NULL");
|
wpRuntimeAssert(allocator != NULL && array != NULL, "`allocator` and `array` should not be NULL");
|
||||||
_array_validate(array, item_size);
|
_arrayValidate(array, item_size);
|
||||||
|
|
||||||
WpArray output = array;
|
WpArray output = array;
|
||||||
|
|
||||||
WpArrayHeader *header = _array_header(array);
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
if (header->count >= header->capacity) {
|
if (header->count >= header->capacity) {
|
||||||
u64 new_capacity = wpMiscUtilsU64RoundUpPow2(header->capacity * 2);
|
u64 new_capacity = wpMiscUtilsU64RoundUpPow2(header->capacity * 2);
|
||||||
output = (WpArray )_arrayAllocCapacity(allocator, new_capacity, flags,
|
output = (WpArray )_arrayAllocCapacity(allocator, new_capacity, flags,
|
||||||
@@ -136,13 +136,13 @@ RETURN_ARRAY_APPEND_ALLOC:
|
|||||||
WpArray _arrayExtendAlloc(const WpAllocator *allocator, WpArray dst, const WpArray src,
|
WpArray _arrayExtendAlloc(const WpAllocator *allocator, WpArray dst, const WpArray src,
|
||||||
WpArrayInitFlags flags, u64 item_size) {
|
WpArrayInitFlags flags, u64 item_size) {
|
||||||
wpRuntimeAssert(allocator != NULL && dst != NULL && src != NULL, "`allocator`, `dst` and `src` should not be NULL");
|
wpRuntimeAssert(allocator != NULL && dst != NULL && src != NULL, "`allocator`, `dst` and `src` should not be NULL");
|
||||||
_array_validate(dst, item_size);
|
_arrayValidate(dst, item_size);
|
||||||
_array_validate(src, item_size);
|
_arrayValidate(src, item_size);
|
||||||
|
|
||||||
WpArray output = dst;
|
WpArray output = dst;
|
||||||
|
|
||||||
WpArrayHeader *src_header = _array_header(src);
|
WpArrayHeader *src_header = _arrayHeader(src);
|
||||||
WpArrayHeader *dst_header = _array_header(dst);
|
WpArrayHeader *dst_header = _arrayHeader(dst);
|
||||||
u64 remaining_capacity = dst_header->capacity - dst_header->count;
|
u64 remaining_capacity = dst_header->capacity - dst_header->count;
|
||||||
if (src_header->count >= remaining_capacity) {
|
if (src_header->count >= remaining_capacity) {
|
||||||
u64 new_capacity = wpMiscUtilsU64RoundUpPow2(dst_header->capacity * 2);
|
u64 new_capacity = wpMiscUtilsU64RoundUpPow2(dst_header->capacity * 2);
|
||||||
@@ -168,13 +168,13 @@ RETURN_ARRAY_EXTEND_ALLOC:
|
|||||||
WpArray _arrayCopyAlloc(const WpAllocator *allocator, WpArray dst, const WpArray src,
|
WpArray _arrayCopyAlloc(const WpAllocator *allocator, WpArray dst, const WpArray src,
|
||||||
WpArrayInitFlags flags, u64 item_size) {
|
WpArrayInitFlags flags, u64 item_size) {
|
||||||
wpRuntimeAssert(allocator != NULL && dst != NULL && src != NULL, "`allocator`, `dst` and `src` should not be NULL");
|
wpRuntimeAssert(allocator != NULL && dst != NULL && src != NULL, "`allocator`, `dst` and `src` should not be NULL");
|
||||||
_array_validate(dst, item_size);
|
_arrayValidate(dst, item_size);
|
||||||
_array_validate(src, item_size);
|
_arrayValidate(src, item_size);
|
||||||
|
|
||||||
WpArray output = dst;
|
WpArray output = dst;
|
||||||
|
|
||||||
WpArrayHeader *src_header = _array_header(src);
|
WpArrayHeader *src_header = _arrayHeader(src);
|
||||||
WpArrayHeader *dst_header = _array_header(dst);
|
WpArrayHeader *dst_header = _arrayHeader(dst);
|
||||||
if (src_header->count >= dst_header->capacity) {
|
if (src_header->count >= dst_header->capacity) {
|
||||||
u64 new_capacity = wpMiscUtilsU64RoundUpPow2(dst_header->capacity * 2);
|
u64 new_capacity = wpMiscUtilsU64RoundUpPow2(dst_header->capacity * 2);
|
||||||
output = (WpArray )_arrayAllocCapacity(allocator, new_capacity,
|
output = (WpArray )_arrayAllocCapacity(allocator, new_capacity,
|
||||||
@@ -197,9 +197,9 @@ RETURN_ARRAY_COPY_ALLOC:
|
|||||||
|
|
||||||
void *_arrayPop(WpArray array, u64 item_size) {
|
void *_arrayPop(WpArray array, u64 item_size) {
|
||||||
wpRuntimeAssert(array != NULL, "`array` should not be NULL");
|
wpRuntimeAssert(array != NULL, "`array` should not be NULL");
|
||||||
_array_validate(array, item_size);
|
_arrayValidate(array, item_size);
|
||||||
|
|
||||||
WpArrayHeader *header = _array_header(array);
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
if (header->count == 0) { return NULL; }
|
if (header->count == 0) { return NULL; }
|
||||||
|
|
||||||
u64 index = header->count - 1;
|
u64 index = header->count - 1;
|
||||||
@@ -210,9 +210,9 @@ void *_arrayPop(WpArray array, u64 item_size) {
|
|||||||
|
|
||||||
void _arrayClear(WpArray array, u64 item_size) {
|
void _arrayClear(WpArray array, u64 item_size) {
|
||||||
wpRuntimeAssert(array != NULL, "`array` should not be NULL");
|
wpRuntimeAssert(array != NULL, "`array` should not be NULL");
|
||||||
_array_validate(array, item_size);
|
_arrayValidate(array, item_size);
|
||||||
|
|
||||||
WpArrayHeader *header = _array_header(array);
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
header->count = 0;
|
header->count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,8 +259,18 @@ WpArray _arrayFromPreallocatedBuffer(void *buffer, u64 buffer_size, WpArrayInitF
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
wp_persist inline void _array_validate(const WpArray array, u64 item_size) {
|
void _arrayDealloc(const WpAllocator *allocator, WpArray *array, u64 item_size) {
|
||||||
WpArrayHeader *header = _array_header(array);
|
wpRuntimeAssert(allocator != NULL, "`allocator` should not be NULL");
|
||||||
|
|
||||||
|
u64 capacity = wpArrayCapacity(*array);
|
||||||
|
u64 allocation_size = _arrayCalcAllocSize(capacity, item_size);
|
||||||
|
void *header = (void *)_arrayHeader(*array);
|
||||||
|
wpMemAllocatorFree(allocator, &header, allocation_size);
|
||||||
|
*array = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_persist inline void _arrayValidate(const WpArray array, u64 item_size) {
|
||||||
|
WpArrayHeader *header = _arrayHeader(array);
|
||||||
wpRuntimeAssert(WP_ARRAY_MAGIC == header->magic, "`array` is not a valid wapp array");
|
wpRuntimeAssert(WP_ARRAY_MAGIC == header->magic, "`array` is not a valid wapp array");
|
||||||
wpRuntimeAssert(item_size == header->item_size, "Invalid item type provided");
|
wpRuntimeAssert(item_size == header->item_size, "Invalid item type provided");
|
||||||
}
|
}
|
||||||
@@ -176,6 +176,10 @@ typedef enum {
|
|||||||
((TYPE *)_arrayAllocCapacity(ALLOCATOR_PTR, CAPACITY, FLAGS, sizeof(TYPE)))
|
((TYPE *)_arrayAllocCapacity(ALLOCATOR_PTR, CAPACITY, FLAGS, sizeof(TYPE)))
|
||||||
#define wpArrayFromPreallcatedBuffer(TYPE, BUFFER, BUFFER_SIZE) \
|
#define wpArrayFromPreallcatedBuffer(TYPE, BUFFER, BUFFER_SIZE) \
|
||||||
((TYPE *)_array_from_preallcated_buffer(BUFFER, BUFFER_SIZE, sizeof(TYPE)))
|
((TYPE *)_array_from_preallcated_buffer(BUFFER, BUFFER_SIZE, sizeof(TYPE)))
|
||||||
|
// Only needed for allocators like malloc where each allocation has to be freed on its own.
|
||||||
|
// No need to use it for allocators like Arena.
|
||||||
|
#define wpArrayDealloc(TYPE, ALLOCATOR_PTR, ARRAY_DPTR) \
|
||||||
|
(_arrayDealloc(ALLOCATOR_PTR, (WpArray *)ARRAY_DPTR, sizeof(TYPE)))
|
||||||
|
|
||||||
|
|
||||||
typedef struct WpArrayHeader WpArrayHeader;
|
typedef struct WpArrayHeader WpArrayHeader;
|
||||||
@@ -208,6 +212,7 @@ WpArray _arrayAllocCapacity(const WpAllocator *allocator, u64 capacity, WpArrayI
|
|||||||
u64 item_size);
|
u64 item_size);
|
||||||
WpArray _arrayFromPreallocatedBuffer(void *buffer, u64 buffer_size, WpArrayInitFlags flags,
|
WpArray _arrayFromPreallocatedBuffer(void *buffer, u64 buffer_size, WpArrayInitFlags flags,
|
||||||
u64 item_size);
|
u64 item_size);
|
||||||
|
void _arrayDealloc(const WpAllocator *allocator, WpArray *array, u64 item_size);
|
||||||
|
|
||||||
#ifdef WP_PLATFORM_CPP
|
#ifdef WP_PLATFORM_CPP
|
||||||
END_C_LINKAGE
|
END_C_LINKAGE
|
||||||
Vendored
+5
-5
@@ -6,7 +6,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
void *wpMemAllocatorAlloc(const WpAllocator *allocator, u64 size) {
|
void *wpMemAllocatorAlloc(const WpAllocator *allocator, u64 size) {
|
||||||
wpDebugAssert(allocator != NULL && (allocator->alloc) != NULL, "`allocator` and `allocator->alloc` should not be NULL");
|
wpDebugAssert(allocator != NULL, "`allocator` should not be NULL");
|
||||||
|
|
||||||
if (!wpMemAllocatorOpSupported(allocator, WP_MEM_OP_ALLOC)) {
|
if (!wpMemAllocatorOpSupported(allocator, WP_MEM_OP_ALLOC)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -16,7 +16,7 @@ void *wpMemAllocatorAlloc(const WpAllocator *allocator, u64 size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void *wpMemAllocatorAllocAligned(const WpAllocator *allocator, u64 size, u64 alignment) {
|
void *wpMemAllocatorAllocAligned(const WpAllocator *allocator, u64 size, u64 alignment) {
|
||||||
wpDebugAssert(allocator != NULL && (allocator->alloc_aligned) != NULL, "`allocator` and `allocator->alloc_aligned` should not be NULL");
|
wpDebugAssert(allocator != NULL, "`allocator` should not be NULL");
|
||||||
|
|
||||||
if (!wpMemAllocatorOpSupported(allocator, WP_MEM_OP_ALLOC_ALIGNED)) {
|
if (!wpMemAllocatorOpSupported(allocator, WP_MEM_OP_ALLOC_ALIGNED)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -26,7 +26,7 @@ void *wpMemAllocatorAllocAligned(const WpAllocator *allocator, u64 size, u64 ali
|
|||||||
}
|
}
|
||||||
|
|
||||||
void *wpMemAllocatorRealloc(const WpAllocator *allocator, void *ptr, u64 old_size, u64 new_size) {
|
void *wpMemAllocatorRealloc(const WpAllocator *allocator, void *ptr, u64 old_size, u64 new_size) {
|
||||||
wpDebugAssert(allocator != NULL && (allocator->realloc) != NULL, "`allocator` and `allocator->realloc` should not be NULL");
|
wpDebugAssert(allocator != NULL, "`allocator` should not be NULL");
|
||||||
|
|
||||||
if (!wpMemAllocatorOpSupported(allocator, WP_MEM_OP_REALLOC)) {
|
if (!wpMemAllocatorOpSupported(allocator, WP_MEM_OP_REALLOC)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -37,7 +37,7 @@ void *wpMemAllocatorRealloc(const WpAllocator *allocator, void *ptr, u64 old_siz
|
|||||||
|
|
||||||
void *wpMemAllocatorReallocAligned(const WpAllocator *allocator, void *ptr, u64 old_size,
|
void *wpMemAllocatorReallocAligned(const WpAllocator *allocator, void *ptr, u64 old_size,
|
||||||
u64 new_size, u64 alignment) {
|
u64 new_size, u64 alignment) {
|
||||||
wpDebugAssert(allocator != NULL && (allocator->realloc_aligned) != NULL, "`allocator` and `allocator->realloc_aligned` should not be NULL");
|
wpDebugAssert(allocator != NULL, "`allocator` should not be NULL");
|
||||||
|
|
||||||
if (!wpMemAllocatorOpSupported(allocator, WP_MEM_OP_REALLOC_ALIGNED)) {
|
if (!wpMemAllocatorOpSupported(allocator, WP_MEM_OP_REALLOC_ALIGNED)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -47,7 +47,7 @@ void *wpMemAllocatorReallocAligned(const WpAllocator *allocator, void *ptr, u64
|
|||||||
}
|
}
|
||||||
|
|
||||||
void wpMemAllocatorFree(const WpAllocator *allocator, void **ptr, u64 size) {
|
void wpMemAllocatorFree(const WpAllocator *allocator, void **ptr, u64 size) {
|
||||||
wpDebugAssert(allocator != NULL && (allocator->free) != NULL, "`allocator` and `allocator->free` should not be NULL");
|
wpDebugAssert(allocator != NULL, "`allocator` should not be NULL");
|
||||||
|
|
||||||
if (!wpMemAllocatorOpSupported(allocator, WP_MEM_OP_FREE)) {
|
if (!wpMemAllocatorOpSupported(allocator, WP_MEM_OP_FREE)) {
|
||||||
return;
|
return;
|
||||||
Vendored
@@ -88,6 +88,11 @@ typedef WpQueue WpStr8Queue;
|
|||||||
#define wpQueuePop(TYPE, QUEUE_PTR) ( \
|
#define wpQueuePop(TYPE, QUEUE_PTR) ( \
|
||||||
(TYPE *)_queuePop(QUEUE_PTR, sizeof(TYPE)) \
|
(TYPE *)_queuePop(QUEUE_PTR, sizeof(TYPE)) \
|
||||||
)
|
)
|
||||||
|
#define wpQueueDealloc(TYPE, ALLOCATOR_PTR, QUEUE_PTR) \
|
||||||
|
(wpArrayDealloc(TYPE, ALLOCATOR_PTR, &((QUEUE_PTR)->items)), \
|
||||||
|
(QUEUE_PTR)->front = 0, \
|
||||||
|
(QUEUE_PTR)->back = 0, \
|
||||||
|
(QUEUE_PTR)->count = 0)
|
||||||
|
|
||||||
void _queuePush(WpQueue *queue, void *item, u64 item_size);
|
void _queuePush(WpQueue *queue, void *item, u64 item_size);
|
||||||
WpQueue *_queuePushAlloc(const WpAllocator *allocator, WpQueue *queue, void *item, u64 item_size);
|
WpQueue *_queuePushAlloc(const WpAllocator *allocator, WpQueue *queue, void *item, u64 item_size);
|
||||||
Vendored
Vendored
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user