Compare commits
24 Commits
a073dec6a0
...
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 |
@@ -1,6 +1,8 @@
|
||||
build
|
||||
compile_commands.json
|
||||
.vscode
|
||||
*.dSYM
|
||||
assets/shaders
|
||||
scratchpad/**
|
||||
!scratchpad/**/
|
||||
!scratchpad/**/*.h
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "src/vendor/ktx"]
|
||||
path = src/vendor/ktx
|
||||
url = https://github.com/KhronosGroup/KTX-Software
|
||||
@@ -16,6 +16,21 @@ Use this when working on any file in `src/prism/rhi/`, or when creating a new ba
|
||||
|
||||
## Conventions
|
||||
|
||||
### Global context
|
||||
|
||||
RHI functions do **not** take allocator parameters. A global `PrRhiContext` provides two allocators:
|
||||
- `allocator` — for user-facing objects (buffers, textures, pipelines, etc.)
|
||||
- `tmp` — for short-lived internal temporaries
|
||||
|
||||
```c
|
||||
extern PrRhiContext _G_RHI_CONTEXT;
|
||||
|
||||
void prRhiInit(void); // sets up both allocators
|
||||
void prRhiDestroy(void); // tears down context
|
||||
```
|
||||
|
||||
All RHI functions access `_G_RHI_CONTEXT` directly. Do not pass allocators to RHI API calls.
|
||||
|
||||
### Backend dispatch
|
||||
|
||||
Backend selection is compile-time via `-D PR_RHI_VULKAN` / `-D PR_RHI_D3D12` / `-D PR_RHI_METAL`. The umbrella header `pr_rhi.h` includes the appropriate alias file:
|
||||
@@ -49,22 +64,95 @@ All descriptor structs are passed **by value**, not `const *`:
|
||||
|
||||
```c
|
||||
// correct
|
||||
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface,
|
||||
PrRhiDeviceDesc desc, WpAllocator *alloc);
|
||||
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface, PrRhiDeviceDesc desc);
|
||||
|
||||
// wrong
|
||||
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface,
|
||||
const PrRhiDeviceDesc *desc, WpAllocator *alloc);
|
||||
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface, const PrRhiDeviceDesc *desc);
|
||||
```
|
||||
|
||||
### Frame-by-frame command batching
|
||||
|
||||
Commands that run every frame must avoid arena allocation. Use stack arrays with a while-loop to batch operations:
|
||||
|
||||
```c
|
||||
// correct — stack array, batched submission
|
||||
void prRhiCmdBindDescriptorSetsVk(PrRhiCommandBuffer *cb, PrRhiPipelineBindPoint bind_point,
|
||||
PrRhiPipelineLayout *layout, u32 first_set,
|
||||
PrRhiDescriptorSetArray sets) {
|
||||
u32 set_count = sets ? (u32)wpArrayCount(sets) : 0;
|
||||
while (set_count > 0) {
|
||||
VkDescriptorSetArray vk_sets = wpArrayWithCapacity(VkDescriptorSet, 16, WP_ARRAY_INIT_FILLED);
|
||||
u32 total_capacity = (u32)wpArrayCapacity(vk_sets);
|
||||
u32 real_count = set_count < total_capacity ? set_count : total_capacity;
|
||||
for (u32 i = 0; i < real_count; ++i) {
|
||||
vk_sets[i] = sets[i]->handle;
|
||||
}
|
||||
vkCmdBindDescriptorSets(cb->handle, vk_bp, vk_layout, first_set, real_count, vk_sets, 0, NULL);
|
||||
set_count -= real_count;
|
||||
first_set += real_count;
|
||||
}
|
||||
}
|
||||
|
||||
// wrong — allocates from arena on every call
|
||||
void prRhiCmdBindDescriptorSetsBad(PrRhiCommandBuffer *cb, ...) {
|
||||
VkDescriptorSetArray vk_sets = wpArrayAllocCapacity(VkDescriptorSet, &_G_RHI_CONTEXT.allocator, count, ...);
|
||||
// ... this leaks every frame
|
||||
}
|
||||
```
|
||||
|
||||
Apply this pattern to: `prRhiCmdBindDescriptorSets`, `prRhiCmdBindVertexBuffers`, `prRhiCmdCopyBufferToImage`, and any other command that processes user-provided arrays.
|
||||
|
||||
### Opaque struct handles — no casts
|
||||
|
||||
Handle types in opaque structs are already the correct Vulkan type. Do not cast:
|
||||
|
||||
```c
|
||||
// correct
|
||||
vk_device = device->handle;
|
||||
vk_buffer = buffer->handle;
|
||||
|
||||
// wrong
|
||||
vk_device = (VkDevice)device->handle;
|
||||
vk_buffer = (VkBuffer)buffer->handle;
|
||||
```
|
||||
|
||||
### Vulkan struct initialisation — designated initializers
|
||||
|
||||
Always use C99 designated initializers for Vulkan info structs:
|
||||
|
||||
```c
|
||||
// correct
|
||||
VkBufferCreateInfo buf_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = desc.size,
|
||||
.usage = _toVkBufferUsage(desc.usage),
|
||||
};
|
||||
|
||||
// wrong
|
||||
VkBufferCreateInfo buf_info = {};
|
||||
buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
buf_info.size = desc.size;
|
||||
buf_info.usage = _toVkBufferUsage(desc.usage);
|
||||
```
|
||||
|
||||
### API patterns
|
||||
|
||||
- **`prRhiCreateCommandPool`**: Takes only `PrRhiDevice *device` (uses `device->queue_family_index` internally)
|
||||
- **`prRhiFreeCommandBuffers`**: Takes `PrRhiCommandBufferArray buffers` (count derived from `wpArrayCount`)
|
||||
- **`prRhiAllocateDescriptorSet`**: Takes `WpU32Array variable_descriptor_counts` for variable descriptor support
|
||||
- **`prRhiCmdBindVertexBuffers`**: Takes `WpU64Array offsets` (count matched to buffers internally)
|
||||
- **Shader entry points**: Configurable via `vertex_shader_entry_point` / `fragment_shader_entry_point` in pipeline desc (not hardcoded to "main")
|
||||
|
||||
### File layout
|
||||
|
||||
```
|
||||
src/prism/rhi/
|
||||
├── pr_rhi.h ← umbrella header (API declarations + backend dispatch)
|
||||
├── pr_rhi.c ← global context definition (prRhiInit, prRhiDestroy)
|
||||
├── pr_rhi_types.h ← shared types (enums, element types, array aliases, desc structs, opaque handles)
|
||||
├── vulkan/
|
||||
│ ├── pr_rhi_vk.h ← Vulkan backend header (opaque struct defs + Vk-suffixed decls)
|
||||
│ ├── pr_rhi_vk.c ← Vulkan backend implementation
|
||||
│ ├── pr_rhi_vk_aliases.h ← #define alias mapping
|
||||
│ └── profiles/ ← generated Vulkan Profiles library
|
||||
├── d3d12/
|
||||
|
||||
@@ -36,6 +36,16 @@ All code follows the patterns established in `src/wapp/`. The project prefix is
|
||||
|
||||
- **Tabs for indentation**, 8-column tab width.
|
||||
- **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.
|
||||
@@ -84,6 +94,12 @@ void prNodeDestroy(PrNode *n, PrAllocator *alloc);
|
||||
Use wapp allocators (`WpAllocator`, arena-based). Stack-allocate where
|
||||
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
|
||||
PrGraph *prGraphCreate(PrAllocator *alloc);
|
||||
void prGraphDestroy(PrGraph *g, PrAllocator *alloc);
|
||||
@@ -117,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)
|
||||
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
|
||||
|
||||
Adjacency list nodes must be **separately allocated from the vertex array**.
|
||||
@@ -200,7 +234,7 @@ WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(16));
|
||||
|
||||
## Documentation
|
||||
|
||||
Save research notes and implementation plans as markdown in `documents/`:
|
||||
Save research notes, implementation plans and session logs as markdown in `documents/`:
|
||||
|
||||
```
|
||||
documents/
|
||||
@@ -214,6 +248,9 @@ documents/
|
||||
└── 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
|
||||
|
||||
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
|
||||
|
||||
# Build the project
|
||||
build:
|
||||
@echo "TODO: implement build"
|
||||
CC := "clang"
|
||||
CXX := "clang++"
|
||||
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:
|
||||
@echo "TODO: implement linter"
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// vim:fileencoding=utf-8:foldmethod=marker
|
||||
|
||||
#include "../src/wapp/wapp.h"
|
||||
#include "../src/vendor/wapp/wapp.h"
|
||||
#include <inttypes.h>
|
||||
#include <stdio.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);
|
||||
}
|
||||
+67
-63
@@ -24,28 +24,39 @@
|
||||
#define PR_RHI_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
|
||||
// ======================================================================
|
||||
|
||||
PrRhiInstance *prRhiCreateInstance(PrRhiInstanceDesc desc, WpAllocator *alloc);
|
||||
void prRhiDestroyInstance(PrRhiInstance *inst, WpAllocator *alloc);
|
||||
PrRhiInstance *prRhiCreateInstance(PrRhiInstanceDesc desc);
|
||||
void prRhiDestroyInstance(PrRhiInstance *inst);
|
||||
|
||||
// ======================================================================
|
||||
// Physical device enumeration
|
||||
// ======================================================================
|
||||
|
||||
PrRhiPhysicalDeviceArray prRhiGetPhysicalDevices(PrRhiInstance *inst, const WpAllocator *scratch);
|
||||
void prRhiGetPhysicalDeviceName(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
||||
void prRhiGetPhysicalDeviceDriverInfo(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
||||
PrRhiPhysicalDeviceArray prRhiGetPhysicalDevices(PrRhiInstance *inst);
|
||||
void prRhiGetPhysicalDeviceName(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
||||
void prRhiGetPhysicalDeviceDriverInfo(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
||||
PrRhiPhysicalDeviceProperties prRhiGetPhysicalDeviceProperties(PrRhiPhysicalDevice *pdev);
|
||||
|
||||
// ======================================================================
|
||||
// Surface (platform-specific)
|
||||
// ======================================================================
|
||||
|
||||
PrRhiSurface *prRhiCreateSurface(PrRhiInstance *inst, void *window_handle, WpAllocator *alloc);
|
||||
void prRhiDestroySurface(PrRhiInstance *inst, PrRhiSurface *surface, WpAllocator *alloc);
|
||||
PrRhiSurface *prRhiCreateSurfaceFromWindow(PrRhiInstance *inst, SDL_Window *window);
|
||||
void prRhiDestroySurface(PrRhiInstance *inst, PrRhiSurface *surface);
|
||||
PrRhiSurfaceCapabilities prRhiGetSurfaceCapabilities(PrRhiPhysicalDevice *pdev,
|
||||
PrRhiSurface *surface);
|
||||
|
||||
@@ -54,19 +65,19 @@ PrRhiSurfaceCapabilities prRhiGetSurfaceCapabilities(PrRhiPhysicalDevice *pdev,
|
||||
// ======================================================================
|
||||
|
||||
PrRhiDevice *prRhiCreateDevice(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface,
|
||||
PrRhiDeviceDesc desc, WpAllocator *alloc);
|
||||
void prRhiDestroyDevice(PrRhiDevice *device, WpAllocator *alloc);
|
||||
PrRhiDeviceDesc desc);
|
||||
void prRhiDestroyDevice(PrRhiDevice *device);
|
||||
void prRhiDeviceWaitIdle(PrRhiDevice *device);
|
||||
u32 prRhiGetQueueFamilyIndex(PrRhiDevice *device);
|
||||
|
||||
// ======================================================================
|
||||
// Swapchain
|
||||
// ======================================================================
|
||||
|
||||
PrRhiSwapchain *prRhiCreateSwapchain(PrRhiDevice *device, PrRhiSwapchainDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroySwapchain(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
||||
WpAllocator *alloc);
|
||||
PrRhiSwapchain *prRhiCreateSwapchain(PrRhiDevice *device, PrRhiSwapchainDesc desc);
|
||||
void prRhiDestroySwapchain(PrRhiDevice *device, PrRhiSwapchain *swapchain);
|
||||
|
||||
u32 prRhiGetSwapchainImageCount(PrRhiSwapchain *swapchain);
|
||||
PrRhiSwapchainResult prRhiAcquireNextImage(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
||||
PrRhiSemaphore *signal_semaphore, u32 *out_image_index);
|
||||
|
||||
@@ -74,18 +85,18 @@ PrRhiSwapchainResult prRhiPresent(PrRhiDevice *device, PrRhiSwapchain *swapchain
|
||||
PrRhiSemaphore *wait_semaphore);
|
||||
|
||||
void prRhiRecreateSwapchain(PrRhiDevice *device, PrRhiSwapchain **swapchain,
|
||||
u32 width, u32 height, WpAllocator *alloc);
|
||||
u32 width, u32 height);
|
||||
|
||||
PrRhiTexture *prRhiGetSwapchainTexture(PrRhiSwapchain *swapchain, u32 image_index);
|
||||
PrRhiTexture *prRhiGetSwapchainDepthTexture(PrRhiSwapchain *swapchain);
|
||||
PrRhiFormat prRhiGetSwapchainFormat(PrRhiSwapchain *swapchain);
|
||||
|
||||
// ======================================================================
|
||||
// Buffers
|
||||
// ======================================================================
|
||||
|
||||
PrRhiBuffer *prRhiCreateBuffer(PrRhiDevice *device, PrRhiBufferDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyBuffer(PrRhiDevice *device, PrRhiBuffer *buffer, WpAllocator *alloc);
|
||||
PrRhiBuffer *prRhiCreateBuffer(PrRhiDevice *device, PrRhiBufferDesc desc);
|
||||
void prRhiDestroyBuffer(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||
|
||||
void *prRhiBufferMap(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||
void prRhiBufferUnmap(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||
@@ -96,73 +107,64 @@ PrRhiDeviceAddress prRhiGetBufferDeviceAddress(PrRhiDevice *device, PrRhiBuffer
|
||||
// Textures
|
||||
// ======================================================================
|
||||
|
||||
PrRhiTexture *prRhiCreateTexture(PrRhiDevice *device, PrRhiTextureDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyTexture(PrRhiDevice *device, PrRhiTexture *texture,
|
||||
WpAllocator *alloc);
|
||||
PrRhiTexture *prRhiCreateTexture(PrRhiDevice *device, PrRhiTextureDesc desc);
|
||||
PrRhiTexture *prRhiCreateTextureFromKtx(PrRhiDevice *device, const char *path,
|
||||
PrRhiCommandPool *pool, PrRhiCommandBuffer *cb);
|
||||
void prRhiDestroyTexture(PrRhiDevice *device, PrRhiTexture *texture);
|
||||
PrRhiTextureSize prRhiGetTextureSize(PrRhiTexture *texture);
|
||||
|
||||
// ======================================================================
|
||||
// Samplers
|
||||
// ======================================================================
|
||||
|
||||
PrRhiSampler *prRhiCreateSampler(PrRhiDevice *device, PrRhiSamplerDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroySampler(PrRhiDevice *device, PrRhiSampler *sampler,
|
||||
WpAllocator *alloc);
|
||||
PrRhiSampler *prRhiCreateSampler(PrRhiDevice *device, PrRhiSamplerDesc desc);
|
||||
void prRhiDestroySampler(PrRhiDevice *device, PrRhiSampler *sampler);
|
||||
|
||||
// ======================================================================
|
||||
// Shaders (from SPIR-V)
|
||||
// ======================================================================
|
||||
|
||||
PrRhiShader *prRhiCreateShader(PrRhiDevice *device, PrRhiShaderDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyShader(PrRhiDevice *device, PrRhiShader *shader, WpAllocator *alloc);
|
||||
PrRhiShader *prRhiCreateShader(PrRhiDevice *device, PrRhiShaderDesc desc);
|
||||
void prRhiDestroyShader(PrRhiDevice *device, PrRhiShader *shader);
|
||||
|
||||
// ======================================================================
|
||||
// Pipeline layouts
|
||||
// ======================================================================
|
||||
|
||||
PrRhiPipelineLayout *prRhiCreatePipelineLayout(PrRhiDevice *device,
|
||||
PrRhiPipelineLayoutDesc desc,
|
||||
WpAllocator *alloc);
|
||||
PrRhiPipelineLayoutDesc desc);
|
||||
void prRhiDestroyPipelineLayout(PrRhiDevice *device,
|
||||
PrRhiPipelineLayout *layout,
|
||||
WpAllocator *alloc);
|
||||
PrRhiPipelineLayout *layout);
|
||||
|
||||
// ======================================================================
|
||||
// Pipelines
|
||||
// ======================================================================
|
||||
|
||||
PrRhiPipeline *prRhiCreateGraphicsPipeline(PrRhiDevice *device,
|
||||
PrRhiGraphicsPipelineDesc desc,
|
||||
WpAllocator *alloc);
|
||||
PrRhiGraphicsPipelineDesc desc);
|
||||
PrRhiPipeline *prRhiCreateComputePipeline(PrRhiDevice *device,
|
||||
PrRhiComputePipelineDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyPipeline(PrRhiDevice *device, PrRhiPipeline *pipeline,
|
||||
WpAllocator *alloc);
|
||||
PrRhiComputePipelineDesc desc);
|
||||
void prRhiDestroyPipeline(PrRhiDevice *device, PrRhiPipeline *pipeline);
|
||||
|
||||
// ======================================================================
|
||||
// Descriptor set layouts
|
||||
// ======================================================================
|
||||
|
||||
PrRhiDescriptorSetLayout *prRhiCreateDescriptorSetLayout(PrRhiDevice *device,
|
||||
PrRhiDescriptorSetLayoutDesc desc,
|
||||
WpAllocator *alloc);
|
||||
PrRhiDescriptorSetLayoutDesc desc);
|
||||
void prRhiDestroyDescriptorSetLayout(PrRhiDevice *device,
|
||||
PrRhiDescriptorSetLayout *layout,
|
||||
WpAllocator *alloc);
|
||||
PrRhiDescriptorSetLayout *layout);
|
||||
|
||||
// ======================================================================
|
||||
// Descriptor pools
|
||||
// ======================================================================
|
||||
|
||||
PrRhiDescriptorPool *prRhiCreateDescriptorPool(PrRhiDevice *device,
|
||||
PrRhiDescriptorPoolDesc desc,
|
||||
WpAllocator *alloc);
|
||||
PrRhiDescriptorPoolDesc desc);
|
||||
void prRhiDestroyDescriptorPool(PrRhiDevice *device,
|
||||
PrRhiDescriptorPool *pool,
|
||||
WpAllocator *alloc);
|
||||
PrRhiDescriptorPool *pool);
|
||||
void prRhiResetDescriptorPool(PrRhiDevice *device,
|
||||
PrRhiDescriptorPool *pool);
|
||||
|
||||
// ======================================================================
|
||||
// Descriptor sets
|
||||
@@ -170,40 +172,37 @@ void prRhiDestroyDescriptorPool(PrRhiDevice *device,
|
||||
|
||||
PrRhiDescriptorSet *prRhiAllocateDescriptorSet(PrRhiDevice *device, PrRhiDescriptorPool *pool,
|
||||
PrRhiDescriptorSetLayout *layout,
|
||||
u32 variable_count, WpAllocator *alloc);
|
||||
WpU32Array variable_descriptor_counts);
|
||||
void prRhiFreeDescriptorSet(PrRhiDevice *device, PrRhiDescriptorPool *pool,
|
||||
PrRhiDescriptorSet *set, WpAllocator *alloc);
|
||||
PrRhiDescriptorSet *set);
|
||||
void prRhiUpdateDescriptorSet(PrRhiDevice *device, PrRhiWriteDescriptorSetArray writes);
|
||||
|
||||
// ======================================================================
|
||||
// Fences and semaphores
|
||||
// ======================================================================
|
||||
|
||||
PrRhiFence *prRhiCreateFence(PrRhiDevice *device, PrRhiFenceDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyFence(PrRhiDevice *device, PrRhiFence *fence, WpAllocator *alloc);
|
||||
PrRhiFence *prRhiCreateFence(PrRhiDevice *device, PrRhiFenceDesc desc);
|
||||
void prRhiDestroyFence(PrRhiDevice *device, PrRhiFence *fence);
|
||||
|
||||
void prRhiWaitForFences(PrRhiDevice *device, PrRhiFenceArray fences, u32 count,
|
||||
b8 wait_all, u64 timeout_ns);
|
||||
void prRhiResetFences(PrRhiDevice *device, PrRhiFenceArray fences, u32 count);
|
||||
|
||||
PrRhiSemaphore *prRhiCreateSemaphore(PrRhiDevice *device, WpAllocator *alloc);
|
||||
void prRhiDestroySemaphore(PrRhiDevice *device, PrRhiSemaphore *semaphore,
|
||||
WpAllocator *alloc);
|
||||
PrRhiSemaphore *prRhiCreateSemaphore(PrRhiDevice *device);
|
||||
void prRhiDestroySemaphore(PrRhiDevice *device, PrRhiSemaphore *semaphore);
|
||||
|
||||
// ======================================================================
|
||||
// Command pools and command buffers
|
||||
// ======================================================================
|
||||
|
||||
PrRhiCommandPool *prRhiCreateCommandPool(PrRhiDevice *device, PrRhiCommandPoolDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyCommandPool(PrRhiDevice *device, PrRhiCommandPool *pool,
|
||||
WpAllocator *alloc);
|
||||
PrRhiCommandPool *prRhiCreateCommandPool(PrRhiDevice *device);
|
||||
void prRhiDestroyCommandPool(PrRhiDevice *device, PrRhiCommandPool *pool);
|
||||
|
||||
PrRhiCommandBufferArray prRhiAllocateCommandBuffers(PrRhiDevice *device, PrRhiCommandPool *pool,
|
||||
u32 count, WpAllocator *alloc);
|
||||
u32 count);
|
||||
|
||||
void prRhiFreeCommandBuffers(PrRhiDevice *device, PrRhiCommandPool *pool,
|
||||
u32 count, PrRhiCommandBufferArray buffers);
|
||||
PrRhiCommandBufferArray buffers);
|
||||
|
||||
// ======================================================================
|
||||
// Command buffer recording
|
||||
@@ -244,8 +243,8 @@ void prRhiCmdPushConstants(PrRhiCommandBuffer *cb, PrRhiPipelineLayout *layout,
|
||||
|
||||
// --- Vertex / index buffers ---
|
||||
|
||||
void prRhiCmdBindVertexBuffers(PrRhiCommandBuffer *cb, u32 first_binding,
|
||||
PrRhiBufferArray buffers, const u64 *offsets, u32 count);
|
||||
void prRhiCmdBindVertexBuffers(PrRhiCommandBuffer *cb, u32 first_binding, PrRhiBufferArray buffers,
|
||||
WpU64Array offsets);
|
||||
void prRhiCmdBindIndexBuffer(PrRhiCommandBuffer *cb, PrRhiBuffer *buffer, u64 offset,
|
||||
PrRhiIndexType index_type);
|
||||
|
||||
@@ -258,7 +257,8 @@ void prRhiCmdDrawIndexed(PrRhiCommandBuffer *cb, u32 index_count, u32 instance_c
|
||||
|
||||
// --- Copy ---
|
||||
|
||||
void prRhiCmdCopyBufferToImage(PrRhiCommandBuffer *cb, PrRhiBuffer *src, PrRhiTexture *dst);
|
||||
void prRhiCmdCopyBufferToImage(PrRhiCommandBuffer *cb, PrRhiBuffer *src, PrRhiTexture *dst,
|
||||
PrRhiBufferImageCopyArray copies);
|
||||
|
||||
// ======================================================================
|
||||
// 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"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
+120
-13
@@ -7,7 +7,9 @@
|
||||
#ifndef PR_RHI_TYPES_H
|
||||
#define PR_RHI_TYPES_H
|
||||
|
||||
#include "../../wapp/wapp.h"
|
||||
#include "../../vendor/wapp/wapp.h"
|
||||
|
||||
#define PR_RHI_LOD_CLAMP_NONE 1000.0f
|
||||
|
||||
// ============================================================================
|
||||
// Opaque handle types
|
||||
@@ -36,6 +38,14 @@ typedef struct PrRhiSemaphore PrRhiSemaphore;
|
||||
// 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 {
|
||||
PR_RHI_SWAPCHAIN_SUCCESS = 0,
|
||||
PR_RHI_SWAPCHAIN_OUT_OF_DATE,
|
||||
@@ -96,6 +106,33 @@ typedef enum PrRhiShaderStage {
|
||||
PR_RHI_SHADER_STAGE_COMPUTE = 1 << 2,
|
||||
} 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 {
|
||||
PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
PR_RHI_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||
@@ -212,14 +249,29 @@ typedef struct PrRhiImageMemoryBarrier {
|
||||
PrRhiTexture *texture;
|
||||
PrRhiImageLayout old_layout;
|
||||
PrRhiImageLayout new_layout;
|
||||
PrRhiPipelineStage src_stage_mask;
|
||||
PrRhiAccess src_access_mask;
|
||||
PrRhiPipelineStage dst_stage_mask;
|
||||
PrRhiAccess dst_access_mask;
|
||||
} PrRhiImageMemoryBarrier;
|
||||
|
||||
typedef struct PrRhiBufferMemoryBarrier {
|
||||
PrRhiBuffer *buffer;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
PrRhiBuffer *buffer;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
PrRhiPipelineStage src_stage_mask;
|
||||
PrRhiAccess src_access_mask;
|
||||
PrRhiPipelineStage dst_stage_mask;
|
||||
PrRhiAccess dst_access_mask;
|
||||
} PrRhiBufferMemoryBarrier;
|
||||
|
||||
typedef struct PrRhiBufferImageCopy {
|
||||
u64 buffer_offset;
|
||||
u32 mip_level;
|
||||
u32 width;
|
||||
u32 height;
|
||||
} PrRhiBufferImageCopy;
|
||||
|
||||
typedef struct PrRhiColorAttachment {
|
||||
PrRhiTexture *texture;
|
||||
PrRhiImageLayout layout;
|
||||
@@ -240,8 +292,6 @@ typedef PrRhiSemaphore **PrRhiSemaphoreArray;
|
||||
typedef PrRhiCommandBuffer **PrRhiCommandBufferArray;
|
||||
typedef PrRhiDescriptorSetLayout **PrRhiDescriptorSetLayoutArray;
|
||||
typedef PrRhiDescriptorSet **PrRhiDescriptorSetArray;
|
||||
typedef const char **PrRhiExtensionArray;
|
||||
|
||||
// Value type arrays (contiguous structs/enums)
|
||||
typedef PrRhiPushConstantRange *PrRhiPushConstantRangeArray;
|
||||
typedef PrRhiVertexInputBinding *PrRhiVertexInputBindingArray;
|
||||
@@ -255,6 +305,7 @@ typedef PrRhiDescriptorBufferInfo *PrRhiDescriptorBufferInfoArray;
|
||||
typedef PrRhiImageMemoryBarrier *PrRhiImageMemoryBarrierArray;
|
||||
typedef PrRhiBufferMemoryBarrier *PrRhiBufferMemoryBarrierArray;
|
||||
typedef PrRhiColorAttachment *PrRhiColorAttachmentArray;
|
||||
typedef PrRhiBufferImageCopy *PrRhiBufferImageCopyArray;
|
||||
typedef struct PrRhiWriteDescriptorSet *PrRhiWriteDescriptorSetArray;
|
||||
|
||||
// ============================================================================
|
||||
@@ -262,9 +313,8 @@ typedef struct PrRhiWriteDescriptorSet *PrRhiWriteDescriptorSetArray;
|
||||
// ============================================================================
|
||||
|
||||
typedef struct PrRhiInstanceDesc {
|
||||
const char *app_name;
|
||||
u32 app_version;
|
||||
PrRhiExtensionArray extra_extensions;
|
||||
const char *app_name;
|
||||
u32 app_version;
|
||||
} PrRhiInstanceDesc;
|
||||
|
||||
typedef struct PrRhiDeviceDesc {
|
||||
@@ -302,14 +352,50 @@ typedef struct PrRhiShaderDesc {
|
||||
u64 spirv_size;
|
||||
} PrRhiShaderDesc;
|
||||
|
||||
typedef struct PrRhiTextureSize {
|
||||
u32 width;
|
||||
u32 height;
|
||||
} PrRhiTextureSize;
|
||||
|
||||
typedef struct PrRhiPipelineLayoutDesc {
|
||||
PrRhiDescriptorSetLayoutArray set_layouts;
|
||||
PrRhiPushConstantRangeArray push_constant_ranges;
|
||||
} 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 {
|
||||
PrRhiShader *vertex_shader;
|
||||
const char *vertex_shader_entry_point;
|
||||
|
||||
PrRhiShader *fragment_shader;
|
||||
const char *fragment_shader_entry_point;
|
||||
|
||||
PrRhiVertexInputBindingArray vertex_bindings;
|
||||
PrRhiVertexAttributeArray vertex_attributes;
|
||||
@@ -328,11 +414,22 @@ typedef struct PrRhiGraphicsPipelineDesc {
|
||||
b8 dynamic_viewport;
|
||||
b8 dynamic_scissor;
|
||||
|
||||
PrRhiPolygonMode polygon_mode;
|
||||
|
||||
PrRhiCullMode cull_mode;
|
||||
|
||||
PrRhiFrontFace front_face;
|
||||
|
||||
f32 line_width;
|
||||
|
||||
PrRhiMultisampleCount multisample_count;
|
||||
|
||||
PrRhiPipelineLayout *layout;
|
||||
} PrRhiGraphicsPipelineDesc;
|
||||
|
||||
typedef struct PrRhiComputePipelineDesc {
|
||||
PrRhiShader *shader;
|
||||
const char *shader_entry_point;
|
||||
PrRhiPipelineLayout *layout;
|
||||
} PrRhiComputePipelineDesc;
|
||||
|
||||
@@ -358,15 +455,12 @@ typedef struct PrRhiFenceDesc {
|
||||
b8 signaled;
|
||||
} PrRhiFenceDesc;
|
||||
|
||||
typedef struct PrRhiCommandPoolDesc {
|
||||
u32 queue_family_index;
|
||||
} PrRhiCommandPoolDesc;
|
||||
|
||||
typedef struct PrRhiSwapchainDesc {
|
||||
PrRhiSurface *surface;
|
||||
u32 width;
|
||||
u32 height;
|
||||
b8 has_depth;
|
||||
PrRhiFormat depth_format; // PR_RHI_FORMAT_UNDEFINED = auto-pick
|
||||
} PrRhiSwapchainDesc;
|
||||
|
||||
typedef struct PrRhiSurfaceCapabilities {
|
||||
@@ -380,8 +474,21 @@ typedef struct PrRhiSurfaceCapabilities {
|
||||
u32 max_height;
|
||||
} PrRhiSurfaceCapabilities;
|
||||
|
||||
typedef struct PrRhiPhysicalDeviceProperties {
|
||||
u32 api_version;
|
||||
PrRhiPhysicalDeviceType device_type;
|
||||
} PrRhiPhysicalDeviceProperties;
|
||||
|
||||
typedef u64 PrRhiDeviceAddress;
|
||||
|
||||
// ============================================================================
|
||||
// RHI context (global, owns all RHI object lifetimes)
|
||||
// ============================================================================
|
||||
|
||||
typedef struct PrRhiContext {
|
||||
WpAllocator allocator;
|
||||
} PrRhiContext;
|
||||
|
||||
// --- Command buffer types ---
|
||||
|
||||
typedef struct PrRhiDepthAttachment {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+104
-100
@@ -8,216 +8,219 @@
|
||||
#define PR_RHI_VK_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.
|
||||
// ============================================================================
|
||||
|
||||
#define PR_RHI_VK_PHYSICAL_DEVICE_NAME_LENGTH 256
|
||||
#define PR_RHI_VK_PHYSICAL_DRIVER_INFO_LENGTH 256
|
||||
|
||||
struct PrRhiInstance {
|
||||
void *handle; // VkInstance
|
||||
void *debug_messenger; // VkDebugUtilsMessengerEXT
|
||||
VkInstance handle; // VkInstance
|
||||
VkDebugUtilsMessengerEXT *debug_messenger; // VkDebugUtilsMessengerEXT
|
||||
};
|
||||
|
||||
struct PrRhiPhysicalDevice {
|
||||
void *handle; // VkPhysicalDevice
|
||||
VkPhysicalDevice handle;
|
||||
PrRhiInstance *instance;
|
||||
c8 device_name[PR_RHI_VK_PHYSICAL_DEVICE_NAME_LENGTH];
|
||||
c8 driver_info[PR_RHI_VK_PHYSICAL_DRIVER_INFO_LENGTH];
|
||||
};
|
||||
|
||||
struct PrRhiDevice {
|
||||
void *handle; // VkDevice
|
||||
void *queue; // VkQueue
|
||||
u32 queue_family_index;
|
||||
void *allocator; // VmaAllocator
|
||||
VkDevice handle;
|
||||
VkQueue queue;
|
||||
u32 queue_family_index;
|
||||
VkPresentModeKHR present_mode;
|
||||
VkPhysicalDevice physical_device;
|
||||
VmaAllocator allocator;
|
||||
};
|
||||
|
||||
struct PrRhiSurface {
|
||||
void *handle; // VkSurfaceKHR
|
||||
VkSurfaceKHR handle;
|
||||
};
|
||||
|
||||
struct PrRhiSwapchain {
|
||||
PrRhiDevice *device;
|
||||
void *handle; // VkSwapchainKHR
|
||||
u32 image_count;
|
||||
PrRhiTexture **images;
|
||||
PrRhiTexture *depth;
|
||||
u32 format; // VkFormat
|
||||
u32 width;
|
||||
u32 height;
|
||||
PrRhiDevice *device;
|
||||
VkSwapchainKHR handle;
|
||||
PrRhiSurface *surface;
|
||||
u32 image_count;
|
||||
PrRhiTexture **images;
|
||||
PrRhiTexture *depth;
|
||||
VkFormat format;
|
||||
VkFormat depth_format;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 current_image_index;
|
||||
};
|
||||
|
||||
struct PrRhiBuffer {
|
||||
void *handle; // VkBuffer
|
||||
void *allocation; // VmaAllocation
|
||||
u64 device_address;
|
||||
u64 size;
|
||||
void *mapped_data;
|
||||
VkBuffer handle;
|
||||
VmaAllocation allocation;
|
||||
u64 device_address;
|
||||
u64 size;
|
||||
void *mapped_data;
|
||||
};
|
||||
|
||||
struct PrRhiTexture {
|
||||
void *image; // VkImage
|
||||
void *view; // VkImageView
|
||||
void *allocation; // VmaAllocation
|
||||
VkImage image;
|
||||
VkImageView view;
|
||||
VmaAllocation allocation;
|
||||
u32 width;
|
||||
u32 height;
|
||||
VkFormat format;
|
||||
};
|
||||
|
||||
struct PrRhiSampler {
|
||||
void *handle; // VkSampler
|
||||
VkSampler handle;
|
||||
};
|
||||
|
||||
struct PrRhiShader {
|
||||
void *handle; // VkShaderModule
|
||||
VkShaderModule handle;
|
||||
};
|
||||
|
||||
struct PrRhiPipelineLayout {
|
||||
void *handle; // VkPipelineLayout
|
||||
VkPipelineLayout handle;
|
||||
};
|
||||
|
||||
struct PrRhiPipeline {
|
||||
void *handle; // VkPipeline
|
||||
VkPipeline handle;
|
||||
};
|
||||
|
||||
struct PrRhiDescriptorSetLayout {
|
||||
void *handle; // VkDescriptorSetLayout
|
||||
VkDescriptorSetLayout handle;
|
||||
};
|
||||
|
||||
struct PrRhiDescriptorPool {
|
||||
void *handle; // VkDescriptorPool
|
||||
VkDescriptorPool handle;
|
||||
};
|
||||
|
||||
struct PrRhiDescriptorSet {
|
||||
void *handle; // VkDescriptorSet
|
||||
VkDescriptorSet handle;
|
||||
};
|
||||
|
||||
struct PrRhiCommandPool {
|
||||
void *handle; // VkCommandPool
|
||||
VkCommandPool handle;
|
||||
};
|
||||
|
||||
struct PrRhiCommandBuffer {
|
||||
void *handle; // VkCommandBuffer
|
||||
VkCommandBuffer handle;
|
||||
};
|
||||
|
||||
struct PrRhiFence {
|
||||
void *handle; // VkFence
|
||||
VkFence handle;
|
||||
};
|
||||
|
||||
struct PrRhiSemaphore {
|
||||
void *handle; // VkSemaphore
|
||||
VkSemaphore handle;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations
|
||||
// ============================================================================
|
||||
|
||||
PrRhiInstance *prRhiCreateInstanceVk(PrRhiInstanceDesc desc, WpAllocator *alloc);
|
||||
void prRhiDestroyInstanceVk(PrRhiInstance *inst, WpAllocator *alloc);
|
||||
PrRhiInstance *prRhiCreateInstanceVk(PrRhiInstanceDesc desc);
|
||||
void prRhiDestroyInstanceVk(PrRhiInstance *inst);
|
||||
|
||||
PrRhiPhysicalDeviceArray prRhiGetPhysicalDevicesVk(PrRhiInstance *inst, const WpAllocator *scratch);
|
||||
PrRhiPhysicalDeviceArray prRhiGetPhysicalDevicesVk(PrRhiInstance *inst);
|
||||
void prRhiGetPhysicalDeviceNameVk(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
||||
void prRhiGetPhysicalDeviceDriverInfoVk(PrRhiPhysicalDevice *pdev, WpStr8 *out);
|
||||
PrRhiPhysicalDeviceProperties prRhiGetPhysicalDevicePropertiesVk(PrRhiPhysicalDevice *pdev);
|
||||
|
||||
PrRhiSurface *prRhiCreateSurfaceVk(PrRhiInstance *inst, void *window_handle, WpAllocator *alloc);
|
||||
void prRhiDestroySurfaceVk(PrRhiInstance *inst, PrRhiSurface *surface, WpAllocator *alloc);
|
||||
PrRhiSurface *prRhiCreateSurfaceFromWindowVk(PrRhiInstance *inst, SDL_Window *window);
|
||||
void prRhiDestroySurfaceVk(PrRhiInstance *inst, PrRhiSurface *surface);
|
||||
|
||||
PrRhiSurfaceCapabilities prRhiGetSurfaceCapabilitiesVk(PrRhiPhysicalDevice *pdev,
|
||||
PrRhiSurface *surface);
|
||||
|
||||
PrRhiDevice *prRhiCreateDeviceVk(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface,
|
||||
PrRhiDeviceDesc desc, WpAllocator *alloc);
|
||||
void prRhiDestroyDeviceVk(PrRhiDevice *device, WpAllocator *alloc);
|
||||
PrRhiDeviceDesc desc);
|
||||
void prRhiDestroyDeviceVk(PrRhiDevice *device);
|
||||
void prRhiDeviceWaitIdleVk(PrRhiDevice *device);
|
||||
u32 prRhiGetQueueFamilyIndexVk(PrRhiDevice *device);
|
||||
|
||||
PrRhiSwapchain *prRhiCreateSwapchainVk(PrRhiDevice *device, PrRhiSwapchainDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroySwapchainVk(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
||||
WpAllocator *alloc);
|
||||
PrRhiSwapchain *prRhiCreateSwapchainVk(PrRhiDevice *device, PrRhiSwapchainDesc desc);
|
||||
void prRhiDestroySwapchainVk(PrRhiDevice *device, PrRhiSwapchain *swapchain);
|
||||
u32 prRhiGetSwapchainImageCountVk(PrRhiSwapchain *swapchain);
|
||||
PrRhiSwapchainResult prRhiAcquireNextImageVk(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
||||
PrRhiSemaphore *signal_semaphore, u32 *out_image_index);
|
||||
PrRhiSwapchainResult prRhiPresentVk(PrRhiDevice *device, PrRhiSwapchain *swapchain,
|
||||
PrRhiSemaphore *wait_semaphore);
|
||||
void prRhiRecreateSwapchainVk(PrRhiDevice *device, PrRhiSwapchain **swapchain,
|
||||
u32 width, u32 height, WpAllocator *alloc);
|
||||
u32 width, u32 height);
|
||||
PrRhiTexture *prRhiGetSwapchainTextureVk(PrRhiSwapchain *swapchain, u32 image_index);
|
||||
PrRhiTexture *prRhiGetSwapchainDepthTextureVk(PrRhiSwapchain *swapchain);
|
||||
PrRhiFormat prRhiGetSwapchainFormatVk(PrRhiSwapchain *swapchain);
|
||||
|
||||
PrRhiBuffer *prRhiCreateBufferVk(PrRhiDevice *device, PrRhiBufferDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyBufferVk(PrRhiDevice *device, PrRhiBuffer *buffer, WpAllocator *alloc);
|
||||
PrRhiBuffer *prRhiCreateBufferVk(PrRhiDevice *device, PrRhiBufferDesc desc);
|
||||
void prRhiDestroyBufferVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||
void *prRhiBufferMapVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||
void prRhiBufferUnmapVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||
PrRhiDeviceAddress prRhiGetBufferDeviceAddressVk(PrRhiDevice *device, PrRhiBuffer *buffer);
|
||||
|
||||
PrRhiTexture *prRhiCreateTextureVk(PrRhiDevice *device, PrRhiTextureDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyTextureVk(PrRhiDevice *device, PrRhiTexture *texture,
|
||||
WpAllocator *alloc);
|
||||
PrRhiTexture *prRhiCreateTextureVk(PrRhiDevice *device, PrRhiTextureDesc desc);
|
||||
PrRhiTexture *prRhiCreateTextureFromKtxVk(PrRhiDevice *device, const char *path,
|
||||
PrRhiCommandPool *pool, PrRhiCommandBuffer *cb);
|
||||
void prRhiDestroyTextureVk(PrRhiDevice *device, PrRhiTexture *texture);
|
||||
PrRhiTextureSize prRhiGetTextureSizeVk(PrRhiTexture *texture);
|
||||
|
||||
PrRhiSampler *prRhiCreateSamplerVk(PrRhiDevice *device, PrRhiSamplerDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroySamplerVk(PrRhiDevice *device, PrRhiSampler *sampler,
|
||||
WpAllocator *alloc);
|
||||
PrRhiSampler *prRhiCreateSamplerVk(PrRhiDevice *device, PrRhiSamplerDesc desc);
|
||||
void prRhiDestroySamplerVk(PrRhiDevice *device, PrRhiSampler *sampler);
|
||||
|
||||
PrRhiShader *prRhiCreateShaderVk(PrRhiDevice *device, PrRhiShaderDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyShaderVk(PrRhiDevice *device, PrRhiShader *shader, WpAllocator *alloc);
|
||||
PrRhiShader *prRhiCreateShaderVk(PrRhiDevice *device, PrRhiShaderDesc desc);
|
||||
void prRhiDestroyShaderVk(PrRhiDevice *device, PrRhiShader *shader);
|
||||
|
||||
PrRhiPipelineLayout *prRhiCreatePipelineLayoutVk(PrRhiDevice *device,
|
||||
PrRhiPipelineLayoutDesc desc,
|
||||
WpAllocator *alloc);
|
||||
PrRhiPipelineLayoutDesc desc);
|
||||
void prRhiDestroyPipelineLayoutVk(PrRhiDevice *device,
|
||||
PrRhiPipelineLayout *layout,
|
||||
WpAllocator *alloc);
|
||||
PrRhiPipelineLayout *layout);
|
||||
|
||||
PrRhiPipeline *prRhiCreateGraphicsPipelineVk(PrRhiDevice *device,
|
||||
PrRhiGraphicsPipelineDesc desc,
|
||||
WpAllocator *alloc);
|
||||
PrRhiGraphicsPipelineDesc desc);
|
||||
PrRhiPipeline *prRhiCreateComputePipelineVk(PrRhiDevice *device,
|
||||
PrRhiComputePipelineDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyPipelineVk(PrRhiDevice *device, PrRhiPipeline *pipeline,
|
||||
WpAllocator *alloc);
|
||||
PrRhiComputePipelineDesc desc);
|
||||
void prRhiDestroyPipelineVk(PrRhiDevice *device, PrRhiPipeline *pipeline);
|
||||
|
||||
PrRhiDescriptorSetLayout *prRhiCreateDescriptorSetLayoutVk(PrRhiDevice *device,
|
||||
PrRhiDescriptorSetLayoutDesc desc,
|
||||
WpAllocator *alloc);
|
||||
PrRhiDescriptorSetLayoutDesc desc);
|
||||
void prRhiDestroyDescriptorSetLayoutVk(PrRhiDevice *device,
|
||||
PrRhiDescriptorSetLayout *layout,
|
||||
WpAllocator *alloc);
|
||||
PrRhiDescriptorSetLayout *layout);
|
||||
|
||||
PrRhiDescriptorPool *prRhiCreateDescriptorPoolVk(PrRhiDevice *device,
|
||||
PrRhiDescriptorPoolDesc desc,
|
||||
WpAllocator *alloc);
|
||||
PrRhiDescriptorPoolDesc desc);
|
||||
void prRhiDestroyDescriptorPoolVk(PrRhiDevice *device,
|
||||
PrRhiDescriptorPool *pool,
|
||||
WpAllocator *alloc);
|
||||
PrRhiDescriptorPool *pool);
|
||||
void prRhiResetDescriptorPoolVk(PrRhiDevice *device,
|
||||
PrRhiDescriptorPool *pool);
|
||||
|
||||
PrRhiDescriptorSet *prRhiAllocateDescriptorSetVk(PrRhiDevice *device,
|
||||
PrRhiDescriptorPool *pool,
|
||||
PrRhiDescriptorSetLayout *layout,
|
||||
u32 variable_count, WpAllocator *alloc);
|
||||
PrRhiDescriptorPool *pool,
|
||||
PrRhiDescriptorSetLayout *layout,
|
||||
WpU32Array variable_descriptor_counts);
|
||||
void prRhiFreeDescriptorSetVk(PrRhiDevice *device, PrRhiDescriptorPool *pool,
|
||||
PrRhiDescriptorSet *set, WpAllocator *alloc);
|
||||
PrRhiDescriptorSet *set);
|
||||
void prRhiUpdateDescriptorSetVk(PrRhiDevice *device, PrRhiWriteDescriptorSetArray writes);
|
||||
|
||||
PrRhiFence *prRhiCreateFenceVk(PrRhiDevice *device, PrRhiFenceDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyFenceVk(PrRhiDevice *device, PrRhiFence *fence, WpAllocator *alloc);
|
||||
PrRhiFence *prRhiCreateFenceVk(PrRhiDevice *device, PrRhiFenceDesc desc);
|
||||
void prRhiDestroyFenceVk(PrRhiDevice *device, PrRhiFence *fence);
|
||||
|
||||
void prRhiWaitForFencesVk(PrRhiDevice *device, PrRhiFenceArray fences, u32 count,
|
||||
b8 wait_all, u64 timeout_ns);
|
||||
void prRhiResetFencesVk(PrRhiDevice *device, PrRhiFenceArray fences, u32 count);
|
||||
|
||||
PrRhiSemaphore *prRhiCreateSemaphoreVk(PrRhiDevice *device, WpAllocator *alloc);
|
||||
void prRhiDestroySemaphoreVk(PrRhiDevice *device, PrRhiSemaphore *semaphore,
|
||||
WpAllocator *alloc);
|
||||
PrRhiSemaphore *prRhiCreateSemaphoreVk(PrRhiDevice *device);
|
||||
void prRhiDestroySemaphoreVk(PrRhiDevice *device, PrRhiSemaphore *semaphore);
|
||||
|
||||
PrRhiCommandPool *prRhiCreateCommandPoolVk(PrRhiDevice *device,
|
||||
PrRhiCommandPoolDesc desc,
|
||||
WpAllocator *alloc);
|
||||
void prRhiDestroyCommandPoolVk(PrRhiDevice *device, PrRhiCommandPool *pool,
|
||||
WpAllocator *alloc);
|
||||
PrRhiCommandPool *prRhiCreateCommandPoolVk(PrRhiDevice *device);
|
||||
void prRhiDestroyCommandPoolVk(PrRhiDevice *device, PrRhiCommandPool *pool);
|
||||
PrRhiCommandBufferArray prRhiAllocateCommandBuffersVk(PrRhiDevice *device, PrRhiCommandPool *pool,
|
||||
u32 count, WpAllocator *alloc);
|
||||
void prRhiFreeCommandBuffersVk(PrRhiDevice *device, PrRhiCommandPool *pool,
|
||||
u32 count, PrRhiCommandBufferArray buffers);
|
||||
u32 count);
|
||||
|
||||
void prRhiFreeCommandBuffersVk(PrRhiDevice *device, PrRhiCommandPool *pool, PrRhiCommandBufferArray buffers);
|
||||
|
||||
void prRhiBeginCommandBufferVk(PrRhiCommandBuffer *cb);
|
||||
void prRhiEndCommandBufferVk(PrRhiCommandBuffer *cb);
|
||||
@@ -243,8 +246,8 @@ void prRhiCmdBindDescriptorSetsVk(PrRhiCommandBuffer *cb, PrRhiPipelineBindPoint
|
||||
void prRhiCmdPushConstantsVk(PrRhiCommandBuffer *cb, PrRhiPipelineLayout *layout,
|
||||
PrRhiShaderStage stage_flags, u32 offset, u32 size,
|
||||
const void *data);
|
||||
void prRhiCmdBindVertexBuffersVk(PrRhiCommandBuffer *cb, u32 first_binding,
|
||||
PrRhiBufferArray buffers, const u64 *offsets, u32 count);
|
||||
void prRhiCmdBindVertexBuffersVk(PrRhiCommandBuffer *cb, u32 first_binding, PrRhiBufferArray buffers,
|
||||
WpU64Array offsets);
|
||||
void prRhiCmdBindIndexBufferVk(PrRhiCommandBuffer *cb, PrRhiBuffer *buffer, u64 offset,
|
||||
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,
|
||||
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,
|
||||
PrRhiSemaphore *wait_semaphore,
|
||||
|
||||
@@ -10,25 +10,32 @@
|
||||
#define prRhiGetPhysicalDevices prRhiGetPhysicalDevicesVk
|
||||
#define prRhiGetPhysicalDeviceName prRhiGetPhysicalDeviceNameVk
|
||||
#define prRhiGetPhysicalDeviceDriverInfo prRhiGetPhysicalDeviceDriverInfoVk
|
||||
#define prRhiCreateSurface prRhiCreateSurfaceVk
|
||||
#define prRhiGetPhysicalDeviceProperties prRhiGetPhysicalDevicePropertiesVk
|
||||
#define prRhiCreateSurfaceFromWindow prRhiCreateSurfaceFromWindowVk
|
||||
#define prRhiDestroySurface prRhiDestroySurfaceVk
|
||||
#define prRhiGetSurfaceCapabilities prRhiGetSurfaceCapabilitiesVk
|
||||
#define prRhiCreateDevice prRhiCreateDeviceVk
|
||||
#define prRhiDestroyDevice prRhiDestroyDeviceVk
|
||||
#define prRhiDeviceWaitIdle prRhiDeviceWaitIdleVk
|
||||
#define prRhiGetQueueFamilyIndex prRhiGetQueueFamilyIndexVk
|
||||
#define prRhiCreateSwapchain prRhiCreateSwapchainVk
|
||||
#define prRhiDestroySwapchain prRhiDestroySwapchainVk
|
||||
#define prRhiGetSwapchainImageCount prRhiGetSwapchainImageCountVk
|
||||
#define prRhiAcquireNextImage prRhiAcquireNextImageVk
|
||||
#define prRhiPresent prRhiPresentVk
|
||||
#define prRhiRecreateSwapchain prRhiRecreateSwapchainVk
|
||||
#define prRhiGetSwapchainTexture prRhiGetSwapchainTextureVk
|
||||
#define prRhiGetSwapchainDepthTexture prRhiGetSwapchainDepthTextureVk
|
||||
#define prRhiGetSwapchainFormat prRhiGetSwapchainFormatVk
|
||||
#define prRhiCreateBuffer prRhiCreateBufferVk
|
||||
#define prRhiDestroyBuffer prRhiDestroyBufferVk
|
||||
#define prRhiBufferMap prRhiBufferMapVk
|
||||
#define prRhiBufferUnmap prRhiBufferUnmapVk
|
||||
#define prRhiGetBufferDeviceAddress prRhiGetBufferDeviceAddressVk
|
||||
#define prRhiCreateTexture prRhiCreateTextureVk
|
||||
#define prRhiCreateTextureFromKtx prRhiCreateTextureFromKtxVk
|
||||
#define prRhiDestroyTexture prRhiDestroyTextureVk
|
||||
#define prRhiGetTextureSize prRhiGetTextureSizeVk
|
||||
#define prRhiCreateSampler prRhiCreateSamplerVk
|
||||
#define prRhiDestroySampler prRhiDestroySamplerVk
|
||||
#define prRhiCreateShader prRhiCreateShaderVk
|
||||
@@ -42,6 +49,7 @@
|
||||
#define prRhiDestroyDescriptorSetLayout prRhiDestroyDescriptorSetLayoutVk
|
||||
#define prRhiCreateDescriptorPool prRhiCreateDescriptorPoolVk
|
||||
#define prRhiDestroyDescriptorPool prRhiDestroyDescriptorPoolVk
|
||||
#define prRhiResetDescriptorPool prRhiResetDescriptorPoolVk
|
||||
#define prRhiAllocateDescriptorSet prRhiAllocateDescriptorSetVk
|
||||
#define prRhiFreeDescriptorSet prRhiFreeDescriptorSetVk
|
||||
#define prRhiUpdateDescriptorSet prRhiUpdateDescriptorSetVk
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include <vk_mem_alloc.h>
|
||||
+1
Submodule src/vendor/ktx added at 4d6fc70eaf
Vendored
Vendored
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