Compare commits
8 Commits
618f09689d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e6f051955 | |||
| e59865bff5 | |||
| e206e4647b | |||
| a0b7c0672a | |||
| 3d4f34c531 | |||
| ebd1801883 | |||
| f5ff6c70ea | |||
| 30408ff244 |
@@ -234,7 +234,7 @@ WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(16));
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Save research notes and implementation plans as markdown in `documents/`:
|
Save research notes, implementation plans and session logs as markdown in `documents/`:
|
||||||
|
|
||||||
```
|
```
|
||||||
documents/
|
documents/
|
||||||
@@ -248,6 +248,9 @@ documents/
|
|||||||
└── YYYY-MM-DD.md
|
└── YYYY-MM-DD.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
At the start of each new session, read the previous session logs to understand what
|
||||||
|
we've implemented so far
|
||||||
|
|
||||||
## Skills
|
## Skills
|
||||||
|
|
||||||
Domain-specific conventions are stored as skills in `.opencode/skills/<name>/SKILL.md`.
|
Domain-specific conventions are stored as skills in `.opencode/skills/<name>/SKILL.md`.
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Prism fullscreen texture blit shader.
|
||||||
|
//
|
||||||
|
// Draws a selected texture from the bindless array as a fullscreen quad that
|
||||||
|
// is letterboxed/pillarboxed to preserve aspect ratio (contain-fit). The NDC
|
||||||
|
// content rect is supplied via push constants so the texture is never
|
||||||
|
// stretched, squashed, or cropped.
|
||||||
|
|
||||||
|
struct BlitData {
|
||||||
|
float4 rect; // NDC fit rect: x0, y0, x1, y1
|
||||||
|
uint selected;
|
||||||
|
uint mode; // 0 = sample texture, 1 = solid background
|
||||||
|
uint pad[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
[[vk::push_constant]]
|
||||||
|
BlitData blit;
|
||||||
|
|
||||||
|
Sampler2D textures[];
|
||||||
|
|
||||||
|
struct VSOutput {
|
||||||
|
float4 Pos : SV_POSITION;
|
||||||
|
float2 UV;
|
||||||
|
};
|
||||||
|
|
||||||
|
[shader("vertex")]
|
||||||
|
VSOutput main(uint vertexIndex : SV_VertexID) {
|
||||||
|
VSOutput output;
|
||||||
|
float2 uv = float2(float(vertexIndex & 1), float((vertexIndex >> 1) & 1));
|
||||||
|
output.UV = uv;
|
||||||
|
float2 pos = lerp(blit.rect.xy, blit.rect.zw, uv);
|
||||||
|
output.Pos = float4(pos, 0.0, 1.0);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
float4 main(VSOutput input) {
|
||||||
|
if (blit.mode == 1) {
|
||||||
|
return float4(0.18, 0.18, 0.18, 1.0);
|
||||||
|
}
|
||||||
|
return textures[NonUniformResourceIndex(blit.selected)].Sample(input.UV);
|
||||||
|
}
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/* Copyright (c) 2025-2026, Sascha Willems
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct VSInput {
|
|
||||||
float3 Pos;
|
|
||||||
float3 Normal;
|
|
||||||
float2 UV;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sampler2D textures[];
|
|
||||||
|
|
||||||
struct ShaderData {
|
|
||||||
float4x4 projection;
|
|
||||||
float4x4 view;
|
|
||||||
float4x4 model[3];
|
|
||||||
float4 lightPos;
|
|
||||||
uint32_t selected;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VSOutput {
|
|
||||||
float4 Pos : SV_POSITION;
|
|
||||||
float3 Normal;
|
|
||||||
float2 UV;
|
|
||||||
float3 Factor;
|
|
||||||
float3 LightVec;
|
|
||||||
float3 ViewVec;
|
|
||||||
uint32_t InstanceIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
[shader("vertex")]
|
|
||||||
VSOutput main(VSInput input, uniform ShaderData *shaderData, uint instanceIndex : SV_VulkanInstanceID) {
|
|
||||||
VSOutput output;
|
|
||||||
float4x4 modelMat = shaderData->model[instanceIndex];
|
|
||||||
output.Normal = mul((float3x3)mul(shaderData->view, modelMat), input.Normal);
|
|
||||||
output.UV = input.UV;
|
|
||||||
output.Pos = mul(shaderData->projection, mul(shaderData->view, mul(modelMat, float4(input.Pos.xyz, 1.0))));
|
|
||||||
output.Factor = (shaderData->selected == instanceIndex ? 3.0f : 1.0f);
|
|
||||||
output.InstanceIndex = instanceIndex;
|
|
||||||
// Calculate view vectors required for lighting
|
|
||||||
float4 fragPos = mul(mul(shaderData->view, modelMat), float4(input.Pos.xyz, 1.0));
|
|
||||||
output.LightVec = shaderData->lightPos.xyz - fragPos.xyz;
|
|
||||||
output.ViewVec = -fragPos.xyz;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
[shader("fragment")]
|
|
||||||
float4 main(VSOutput input) {
|
|
||||||
// Phong lighting
|
|
||||||
float3 N = normalize(input.Normal);
|
|
||||||
float3 L = normalize(input.LightVec);
|
|
||||||
float3 V = normalize(input.ViewVec);
|
|
||||||
float3 R = reflect(-L, N);
|
|
||||||
float3 diffuse = max(dot(N, L), 0.0025);
|
|
||||||
float3 specular = pow(max(dot(R, V), 0.0), 16.0) * 0.75;
|
|
||||||
// Sample from texture
|
|
||||||
float3 color = textures[NonUniformResourceIndex(input.InstanceIndex)].Sample(input.UV).rgb * input.Factor;
|
|
||||||
return float4(diffuse * color.rgb + specular, 1.0);
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# Blender 5.0.1 MTL File: 'suzanne4.blend'
|
|
||||||
# www.blender.org
|
|
||||||
|
|
||||||
newmtl Material
|
|
||||||
Ns 250.000000
|
|
||||||
Ka 1.000000 1.000000 1.000000
|
|
||||||
Kd 0.800000 0.800000 0.800000
|
|
||||||
Ks 0.500000 0.500000 0.500000
|
|
||||||
Ke 0.000000 0.000000 0.000000
|
|
||||||
Ni 1.500000
|
|
||||||
d 1.000000
|
|
||||||
illum 2
|
|
||||||
|
|
||||||
newmtl Material.001
|
|
||||||
Ns 250.000000
|
|
||||||
Ka 1.000000 1.000000 1.000000
|
|
||||||
Kd 0.127605 0.127605 0.127605
|
|
||||||
Ks 0.500000 0.500000 0.500000
|
|
||||||
Ke 0.000000 0.000000 0.000000
|
|
||||||
Ni 1.500000
|
|
||||||
d 1.000000
|
|
||||||
illum 2
|
|
||||||
|
|
||||||
newmtl Material.002
|
|
||||||
Ns 250.000000
|
|
||||||
Ka 1.000000 1.000000 1.000000
|
|
||||||
Kd 0.000000 0.000000 0.000000
|
|
||||||
Ks 0.500000 0.500000 0.500000
|
|
||||||
Ke 0.000000 0.000000 0.000000
|
|
||||||
Ni 1.500000
|
|
||||||
d 1.000000
|
|
||||||
illum 2
|
|
||||||
|
|
||||||
newmtl Material.003
|
|
||||||
Ns 250.000000
|
|
||||||
Ka 1.000000 1.000000 1.000000
|
|
||||||
Kd 0.800000 0.800000 0.800000
|
|
||||||
Ks 0.500000 0.500000 0.500000
|
|
||||||
Ke 0.000000 0.000000 0.000000
|
|
||||||
Ni 1.500000
|
|
||||||
d 1.000000
|
|
||||||
illum 2
|
|
||||||
|
|
||||||
newmtl Material.004
|
|
||||||
Ns 250.000000
|
|
||||||
Ka 1.000000 1.000000 1.000000
|
|
||||||
Kd 0.800007 0.200800 0.327761
|
|
||||||
Ks 0.500000 0.500000 0.500000
|
|
||||||
Ke 0.000000 0.000000 0.000000
|
|
||||||
Ni 1.500000
|
|
||||||
d 1.000000
|
|
||||||
illum 2
|
|
||||||
|
|
||||||
newmtl Material.005
|
|
||||||
Ns 250.000000
|
|
||||||
Ka 1.000000 1.000000 1.000000
|
|
||||||
Kd 1.000000 1.000000 1.000000
|
|
||||||
Ks 0.500000 0.500000 0.500000
|
|
||||||
Ke 0.000000 0.000000 0.000000
|
|
||||||
Ni 1.500000
|
|
||||||
d 1.000000
|
|
||||||
illum 2
|
|
||||||
-8150
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||||
@@ -5,7 +5,6 @@ default: build
|
|||||||
|
|
||||||
CC := "clang"
|
CC := "clang"
|
||||||
CXX := "clang++"
|
CXX := "clang++"
|
||||||
SLANGC := "slangc"
|
|
||||||
BUILDDIR := "build"
|
BUILDDIR := "build"
|
||||||
|
|
||||||
# Resolve VULKAN_SDK once via backtick
|
# Resolve VULKAN_SDK once via backtick
|
||||||
@@ -14,7 +13,7 @@ VENDOR_INC := BUILDDIR + "/include"
|
|||||||
VENDOR_LIB := BUILDDIR + "/lib"
|
VENDOR_LIB := BUILDDIR + "/lib"
|
||||||
|
|
||||||
VK_FLAGS := "-DPR_RHI_VULKAN -DVK_NO_PROTOTYPES -I" + VK_SDK + "/include -I" + VK_SDK + "/include/vma -I" + VENDOR_INC
|
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" + VK_SDK + "/include/slang -I" + VENDOR_INC + " -Isrc"
|
APP_INC := "-I" + VK_SDK + "/include -I" + VK_SDK + "/include/vma -I" + VENDOR_INC + " -Isrc"
|
||||||
|
|
||||||
# Build KTX from source
|
# Build KTX from source
|
||||||
vendor:
|
vendor:
|
||||||
@@ -31,16 +30,19 @@ vendor:
|
|||||||
cmake --build {{BUILDDIR}}/ktx --config Release
|
cmake --build {{BUILDDIR}}/ktx --config Release
|
||||||
cmake --install {{BUILDDIR}}/ktx
|
cmake --install {{BUILDDIR}}/ktx
|
||||||
|
|
||||||
# Compile Slang shaders to SPIR-V
|
# Compile shaders from .slang to SPIR-V
|
||||||
shaders:
|
shaders:
|
||||||
mkdir -p assets/shaders
|
mkdir -p {{BUILDDIR}}/shaders
|
||||||
{{SLANGC}} -target spirv -stage vertex -entry main src/shaders/blit.vert.slang -o assets/shaders/blit.vert.spv
|
{{VK_SDK}}/bin/slangc -target spirv \
|
||||||
{{SLANGC}} -target spirv -stage vertex -entry main src/shaders/blit_to_swap.vert.slang -o assets/shaders/blit_to_swap.vert.spv
|
-profile spirv_1_6+\
|
||||||
{{SLANGC}} -target spirv -stage fragment -entry main src/shaders/read.frag.slang -o assets/shaders/read.frag.spv
|
SPV_GOOGLE_user_type+\
|
||||||
{{SLANGC}} -target spirv -stage fragment -entry main src/shaders/blur.frag.slang -o assets/shaders/blur.frag.spv
|
spvFragmentFullyCoveredEXT+\
|
||||||
{{SLANGC}} -target spirv -stage fragment -entry main src/shaders/grade.frag.slang -o assets/shaders/grade.frag.spv
|
spvDerivativeControl+\
|
||||||
{{SLANGC}} -target spirv -stage fragment -entry main src/shaders/blend.frag.slang -o assets/shaders/blend.frag.spv
|
spvImageQuery+\
|
||||||
{{SLANGC}} -target spirv -stage fragment -entry main src/shaders/blit_to_swap.frag.slang -o assets/shaders/blit_to_swap.frag.spv
|
spvImageGatherExtended+\
|
||||||
|
spvSparseResidency+\
|
||||||
|
spvMinLod \
|
||||||
|
-o {{BUILDDIR}}/shaders/blit.spv assets/blit.slang
|
||||||
|
|
||||||
# Build all objects, then link
|
# Build all objects, then link
|
||||||
build: vendor shaders
|
build: vendor shaders
|
||||||
@@ -55,11 +57,6 @@ build: vendor shaders
|
|||||||
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/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 {{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 -- {{CC}} -g -c src/vendor/wapp/wapp.c -o {{BUILDDIR}}/wapp.o
|
||||||
bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/allocators/pr_pool_allocator.c -o {{BUILDDIR}}/pr_pool_allocator.o
|
|
||||||
bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/core/pr_graph.c -o {{BUILDDIR}}/pr_graph.o
|
|
||||||
bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/core/pr_node.c -o {{BUILDDIR}}/pr_node.o
|
|
||||||
bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/core/pr_node_eval.c -o {{BUILDDIR}}/pr_node_eval.o
|
|
||||||
bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/core/pr_texture_pool.c -o {{BUILDDIR}}/pr_texture_pool.o
|
|
||||||
bear -a -- {{CXX}} -g -c {{VK_FLAGS}} -Wno-nullability-completeness -DVK_NO_PROTOTYPES \
|
bear -a -- {{CXX}} -g -c {{VK_FLAGS}} -Wno-nullability-completeness -DVK_NO_PROTOTYPES \
|
||||||
{{APP_INC}} \
|
{{APP_INC}} \
|
||||||
src/main.cpp \
|
src/main.cpp \
|
||||||
|
|||||||
+554
-375
File diff suppressed because it is too large
Load Diff
@@ -1,212 +0,0 @@
|
|||||||
// vim:fileencoding=utf-8:foldmethod=marker
|
|
||||||
|
|
||||||
#include "pr_graph.h"
|
|
||||||
#include "../../vendor/wapp/wapp.h"
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Internal: unlink edge helpers
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
static void _unlinkForward(PrGraph *graph, u64 from_idx, PrGraphEdge *edge) {
|
|
||||||
PrGraphVertex *vtx = &graph->vertices[from_idx];
|
|
||||||
PrGraphEdge *curr = vtx->next_forward;
|
|
||||||
PrGraphEdge *prev = NULL;
|
|
||||||
while (curr) {
|
|
||||||
if (curr == edge) {
|
|
||||||
if (prev) {
|
|
||||||
prev->next_forward = curr->next_forward;
|
|
||||||
} else {
|
|
||||||
vtx->next_forward = curr->next_forward;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prev = curr;
|
|
||||||
curr = curr->next_forward;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _unlinkBackward(PrGraph *graph, u64 to_idx, PrGraphEdge *edge) {
|
|
||||||
PrGraphVertex *vtx = &graph->vertices[to_idx];
|
|
||||||
PrGraphEdge *curr = vtx->next_backward;
|
|
||||||
PrGraphEdge *prev = NULL;
|
|
||||||
while (curr) {
|
|
||||||
if (curr == edge) {
|
|
||||||
if (prev) {
|
|
||||||
prev->next_backward = curr->next_backward;
|
|
||||||
} else {
|
|
||||||
vtx->next_backward = curr->next_backward;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prev = curr;
|
|
||||||
curr = curr->next_backward;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Graph lifecycle
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
wp_extern void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity) {
|
|
||||||
memset(graph, 0, sizeof(*graph));
|
|
||||||
graph->capacity = capacity;
|
|
||||||
|
|
||||||
graph->vertices = wpArrayAllocCapacity(PrGraphVertex, allocator, capacity, WP_ARRAY_INIT_FILLED);
|
|
||||||
if (!graph->vertices) {
|
|
||||||
graph->capacity = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
prPoolInit(&graph->edge_pool, sizeof(PrGraphEdge), 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern void prGraphDestroy(PrGraph *graph) {
|
|
||||||
prPoolDestroy(&graph->edge_pool);
|
|
||||||
// vertices are owned by the wapp allocator passed to prGraphInit
|
|
||||||
memset(graph, 0, sizeof(*graph));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Vertex lifecycle
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
wp_extern void prGraphAddVertex(PrGraph *graph, u64 idx) {
|
|
||||||
graph->vertices[idx].active = true;
|
|
||||||
graph->vertex_count++;
|
|
||||||
if (idx >= graph->max_vertex_ever) {
|
|
||||||
graph->max_vertex_ever = idx + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern void prGraphRemoveVertex(PrGraph *graph, u64 idx) {
|
|
||||||
PrGraphVertex *vtx = &graph->vertices[idx];
|
|
||||||
if (!vtx->active) { return; }
|
|
||||||
|
|
||||||
// Free outgoing edges: unlink from each target's backward list
|
|
||||||
PrGraphEdge *curr = vtx->next_forward;
|
|
||||||
while (curr) {
|
|
||||||
PrGraphEdge *next = curr->next_forward;
|
|
||||||
_unlinkBackward(graph, curr->target_idx, curr);
|
|
||||||
prPoolFree(&graph->edge_pool, curr);
|
|
||||||
curr = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free incoming edges: unlink from each source's forward list
|
|
||||||
curr = vtx->next_backward;
|
|
||||||
while (curr) {
|
|
||||||
PrGraphEdge *next = curr->next_backward;
|
|
||||||
_unlinkForward(graph, curr->source_idx, curr);
|
|
||||||
prPoolFree(&graph->edge_pool, curr);
|
|
||||||
curr = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
vtx->next_forward = NULL;
|
|
||||||
vtx->next_backward = NULL;
|
|
||||||
vtx->active = false;
|
|
||||||
graph->vertex_count--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Edge management
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
wp_extern b8 prGraphEdgeExists(const PrGraph *graph, u64 from_idx, u64 to_idx) {
|
|
||||||
PrGraphVertex *vtx = &graph->vertices[from_idx];
|
|
||||||
PrGraphEdge *curr = vtx->next_forward;
|
|
||||||
while (curr) {
|
|
||||||
if (curr->target_idx == to_idx) { return true; }
|
|
||||||
curr = curr->next_forward;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern b8 prGraphAddEdge(PrGraph *graph, u64 from_idx, u64 to_idx) {
|
|
||||||
PrGraphEdge *edge = (PrGraphEdge *)prPoolAlloc(&graph->edge_pool);
|
|
||||||
if (!edge) { return false; }
|
|
||||||
|
|
||||||
edge->source_idx = from_idx;
|
|
||||||
edge->target_idx = to_idx;
|
|
||||||
|
|
||||||
// Link into adjacency chains
|
|
||||||
PrGraphVertex *src = &graph->vertices[from_idx];
|
|
||||||
PrGraphVertex *dst = &graph->vertices[to_idx];
|
|
||||||
edge->next_forward = src->next_forward;
|
|
||||||
edge->next_backward = dst->next_backward;
|
|
||||||
src->next_forward = edge;
|
|
||||||
dst->next_backward = edge;
|
|
||||||
|
|
||||||
// Check whether the new edge created a cycle
|
|
||||||
WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(16));
|
|
||||||
WpU64Array sorted = prGraphTopologicalSort(graph, &scratch);
|
|
||||||
u64 sorted_n = sorted ? wpArrayCount(sorted) : 0;
|
|
||||||
if (sorted_n < graph->vertex_count) {
|
|
||||||
_unlinkForward(graph, from_idx, edge);
|
|
||||||
_unlinkBackward(graph, to_idx, edge);
|
|
||||||
prPoolFree(&graph->edge_pool, edge);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern u64 prGraphVertexCount(const PrGraph *graph) {
|
|
||||||
return graph->vertex_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Kahn's algorithm — topological sort / cycle detection
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
wp_extern WpU64Array prGraphTopologicalSort(const PrGraph *graph, const WpAllocator *allocator) {
|
|
||||||
if (graph->vertex_count == 0) { return NULL; }
|
|
||||||
if (!graph->vertices || graph->capacity == 0) { return NULL; }
|
|
||||||
|
|
||||||
WpU64Array result = wpArrayAllocCapacity(u64, allocator, graph->vertex_count, WP_ARRAY_INIT_NONE);
|
|
||||||
if (!result) { return NULL; }
|
|
||||||
|
|
||||||
WpAllocator local_arena = wpMemArenaAllocatorInitZero(KiB(16));
|
|
||||||
|
|
||||||
WpU64Array in_degree = wpArrayAllocCapacity(u64, &local_arena, graph->capacity, WP_ARRAY_INIT_FILLED);
|
|
||||||
if (!in_degree) { return result; }
|
|
||||||
memset(in_degree, 0, wpArrayCapacity(in_degree) * sizeof(u64));
|
|
||||||
|
|
||||||
for (u64 i = 0; i < graph->max_vertex_ever; i++) {
|
|
||||||
if (!graph->vertices[i].active) { continue; }
|
|
||||||
|
|
||||||
PrGraphEdge *curr = graph->vertices[i].next_forward;
|
|
||||||
while (curr) {
|
|
||||||
in_degree[curr->target_idx]++;
|
|
||||||
curr = curr->next_forward;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WpQueue queue = wpQueueAlloc(u64, &local_arena, graph->vertex_count);
|
|
||||||
|
|
||||||
for (u64 i = 0; i < graph->max_vertex_ever; i++) {
|
|
||||||
if (!graph->vertices[i].active) { continue; }
|
|
||||||
if (in_degree[i] == 0) {
|
|
||||||
wpQueuePush(u64, &queue, &i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (queue.count > 0) {
|
|
||||||
u64 *node_idx = wpQueuePop(u64, &queue);
|
|
||||||
if (!node_idx) { break; }
|
|
||||||
|
|
||||||
wpArrayAppendCapped(u64, result, node_idx);
|
|
||||||
|
|
||||||
PrGraphEdge *curr = graph->vertices[*node_idx].next_forward;
|
|
||||||
while (curr) {
|
|
||||||
u64 target_idx = curr->target_idx;
|
|
||||||
if (in_degree[target_idx] > 0) {
|
|
||||||
in_degree[target_idx]--;
|
|
||||||
if (in_degree[target_idx] == 0) {
|
|
||||||
wpQueuePush(u64, &queue, &target_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
curr = curr->next_forward;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
// vim:fileencoding=utf-8:foldmethod=marker
|
|
||||||
|
|
||||||
#ifndef PR_GRAPH_H
|
|
||||||
#define PR_GRAPH_H
|
|
||||||
|
|
||||||
#include "../../vendor/wapp/common/aliases/aliases.h"
|
|
||||||
#include "../../vendor/wapp/base/mem/allocator/mem_allocator.h"
|
|
||||||
#include "../../vendor/wapp/base/wapp_base.h"
|
|
||||||
#include "../allocators/pr_pool_allocator.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// PrGraphEdge — separately allocated adjacency list node
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct PrGraphEdge PrGraphEdge;
|
|
||||||
struct PrGraphEdge {
|
|
||||||
PrGraphEdge *next_forward;
|
|
||||||
PrGraphEdge *next_backward;
|
|
||||||
u64 source_idx;
|
|
||||||
u64 target_idx;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// PrGraphVertex — compact adjacency head
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct PrGraphVertex PrGraphVertex;
|
|
||||||
struct PrGraphVertex {
|
|
||||||
PrGraphEdge *next_forward;
|
|
||||||
PrGraphEdge *next_backward;
|
|
||||||
b8 active;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// PrGraph — owns topology (edges + adjacency heads)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PrPool edge_pool;
|
|
||||||
PrGraphVertex *vertices;
|
|
||||||
u64 capacity;
|
|
||||||
u64 max_vertex_ever;
|
|
||||||
u64 vertex_count;
|
|
||||||
} PrGraph;
|
|
||||||
|
|
||||||
void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity);
|
|
||||||
void prGraphDestroy(PrGraph *graph);
|
|
||||||
void prGraphAddVertex(PrGraph *graph, u64 idx);
|
|
||||||
void prGraphRemoveVertex(PrGraph *graph, u64 idx);
|
|
||||||
b8 prGraphAddEdge(PrGraph *graph, u64 from_idx, u64 to_idx);
|
|
||||||
b8 prGraphEdgeExists(const PrGraph *graph, u64 from_idx, u64 to_idx);
|
|
||||||
u64 prGraphVertexCount(const PrGraph *graph);
|
|
||||||
WpU64Array prGraphTopologicalSort(const PrGraph *graph, const WpAllocator *allocator);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // !PR_GRAPH_H
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
// vim:fileencoding=utf-8:foldmethod=marker
|
|
||||||
|
|
||||||
#include "pr_node.h"
|
|
||||||
#include "../../vendor/wapp/wapp.h"
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Node manager lifecycle
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
wp_extern void prNodeManagerInit(PrNodeManager *mgr, WpAllocator *allocator, u64 capacity) {
|
|
||||||
memset(mgr, 0, sizeof(*mgr));
|
|
||||||
mgr->capacity = capacity;
|
|
||||||
|
|
||||||
mgr->nodes = wpArrayAllocCapacity(PrNode, allocator, capacity, WP_ARRAY_INIT_FILLED);
|
|
||||||
if (!mgr->nodes) {
|
|
||||||
mgr->capacity = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mgr->free_head = 0;
|
|
||||||
for (u64 i = 0; i < capacity; ++i) {
|
|
||||||
mgr->nodes[i].next_free = i < capacity - 1 ? i + 1 : INVALID_NODE_INDEX;
|
|
||||||
}
|
|
||||||
|
|
||||||
prGraphInit(&mgr->graph, allocator, capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern void prNodeManagerDestroy(PrNodeManager *mgr) {
|
|
||||||
prGraphDestroy(&mgr->graph);
|
|
||||||
// nodes are owned by the wapp allocator passed to prNodeManagerInit
|
|
||||||
memset(mgr, 0, sizeof(*mgr));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Handle queries
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
wp_extern b8 prNodeManagerIsStaleNode(const PrNodeManager *mgr, PrNodeId id) {
|
|
||||||
u64 generation = mgr->nodes[id.index].generation;
|
|
||||||
return id.generation != generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern b8 prNodeManagerIsActiveNode(const PrNodeManager *mgr, PrNodeId id) {
|
|
||||||
u64 next_free = mgr->nodes[id.index].next_free;
|
|
||||||
return !prNodeManagerIsStaleNode(mgr, id) && next_free == INVALID_NODE_INDEX;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern PrNodeId prNodeManagerGetNode(const PrNodeManager *mgr, u64 index) {
|
|
||||||
return (PrNodeId){ .index = index, .generation = mgr->nodes[index].generation };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Node lifecycle
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
wp_extern PrNodeId prNodeManagerAddNode(PrNodeManager *mgr, PrNodeType type) {
|
|
||||||
u64 idx = mgr->free_head;
|
|
||||||
if (idx == INVALID_NODE_INDEX) { return INVALID_NODE_ID; }
|
|
||||||
|
|
||||||
PrNode *node = &mgr->nodes[idx];
|
|
||||||
|
|
||||||
mgr->free_head = node->next_free;
|
|
||||||
node->next_free = INVALID_NODE_INDEX;
|
|
||||||
node->type = type;
|
|
||||||
memset(&node->params, 0, sizeof(node->params));
|
|
||||||
|
|
||||||
mgr->count++;
|
|
||||||
if (idx + 1 > mgr->max_count_ever) { mgr->max_count_ever = idx + 1; }
|
|
||||||
|
|
||||||
prGraphAddVertex(&mgr->graph, idx);
|
|
||||||
|
|
||||||
return (PrNodeId){ .index = idx, .generation = node->generation };
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern void prNodeManagerRemoveNode(PrNodeManager *mgr, PrNodeId id) {
|
|
||||||
if (!prNodeManagerIsActiveNode(mgr, id)) { return; }
|
|
||||||
|
|
||||||
// Tear down all edges incident to this node and mark vertex inactive
|
|
||||||
prGraphRemoveVertex(&mgr->graph, id.index);
|
|
||||||
|
|
||||||
// Return node slot to free list with bumped generation
|
|
||||||
PrNode *node = &mgr->nodes[id.index];
|
|
||||||
node->generation++;
|
|
||||||
node->next_free = mgr->free_head;
|
|
||||||
mgr->free_head = id.index;
|
|
||||||
mgr->count--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Edge management
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
wp_extern void prNodeManagerAddEdge(PrNodeManager *mgr, PrNodeId from, PrNodeId to) {
|
|
||||||
if (!prNodeManagerIsActiveNode(mgr, from) || !prNodeManagerIsActiveNode(mgr, to)) { return; }
|
|
||||||
if (from.index == to.index) { return; }
|
|
||||||
if (prGraphEdgeExists(&mgr->graph, from.index, to.index)) { return; }
|
|
||||||
prGraphAddEdge(&mgr->graph, from.index, to.index);
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
// vim:fileencoding=utf-8:foldmethod=marker
|
|
||||||
|
|
||||||
#ifndef PR_NODE_H
|
|
||||||
#define PR_NODE_H
|
|
||||||
|
|
||||||
#include "../../vendor/wapp/common/aliases/aliases.h"
|
|
||||||
#include "pr_graph.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Forward declarations
|
|
||||||
typedef struct PrRhiTexture PrRhiTexture;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Constants
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#define INVALID_NODE_INDEX (u64)-1
|
|
||||||
#define INVALID_NODE_ID ((PrNodeId){ .index = INVALID_NODE_INDEX, .generation = INVALID_NODE_INDEX })
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// PrNodeType
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
PR_NODE_TYPE_NONE,
|
|
||||||
PR_NODE_TYPE_READ,
|
|
||||||
PR_NODE_TYPE_BLUR,
|
|
||||||
PR_NODE_TYPE_GRADE,
|
|
||||||
PR_NODE_TYPE_BLEND,
|
|
||||||
|
|
||||||
COUNT_NODE_TYPES
|
|
||||||
} PrNodeType;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// PrNodeId — generational handle
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
u64 index;
|
|
||||||
u64 generation;
|
|
||||||
} PrNodeId;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// PrNode — compositor node data
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
union {
|
|
||||||
//WpStr8 path; // READ: texture path
|
|
||||||
f32 radius; // BLUR: radius
|
|
||||||
struct { // GRADE: colour grading
|
|
||||||
f32 gain;
|
|
||||||
f32 offset;
|
|
||||||
f32 power;
|
|
||||||
} grade;
|
|
||||||
u32 mode; // BLEND: 0=over, 1=under, 2=add
|
|
||||||
} params;
|
|
||||||
PrRhiTexture *texture; // READ: persistent KTX texture
|
|
||||||
PrNodeType type;
|
|
||||||
u64 generation;
|
|
||||||
u64 next_free;
|
|
||||||
} PrNode;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// PrNodeManager — owns node data + handle lifecycle + topology
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PrNode *nodes;
|
|
||||||
PrGraph graph;
|
|
||||||
u64 capacity;
|
|
||||||
u64 max_count_ever;
|
|
||||||
u64 count;
|
|
||||||
u64 free_head;
|
|
||||||
} PrNodeManager;
|
|
||||||
|
|
||||||
void prNodeManagerInit(PrNodeManager *mgr, WpAllocator *allocator, u64 capacity);
|
|
||||||
void prNodeManagerDestroy(PrNodeManager *mgr);
|
|
||||||
b8 prNodeManagerIsStaleNode(const PrNodeManager *mgr, PrNodeId id);
|
|
||||||
b8 prNodeManagerIsActiveNode(const PrNodeManager *mgr, PrNodeId id);
|
|
||||||
PrNodeId prNodeManagerGetNode(const PrNodeManager *mgr, u64 index);
|
|
||||||
PrNodeId prNodeManagerAddNode(PrNodeManager *mgr, PrNodeType type);
|
|
||||||
void prNodeManagerRemoveNode(PrNodeManager *mgr, PrNodeId id);
|
|
||||||
void prNodeManagerAddEdge(PrNodeManager *mgr, PrNodeId from, PrNodeId to);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // !PR_NODE_H
|
|
||||||
@@ -1,358 +0,0 @@
|
|||||||
// vim:fileencoding=utf-8:foldmethod=marker
|
|
||||||
|
|
||||||
#include "pr_node_eval.h"
|
|
||||||
#include "../rhi/pr_rhi.h"
|
|
||||||
#include "../../vendor/wapp/wapp.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
wp_intern PrNodeTypeEntry _node_type_table_data[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 = 1,
|
|
||||||
.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),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
PrNodeTypeEntry pr_node_type_table[COUNT_NODE_TYPES];
|
|
||||||
|
|
||||||
wp_persist PrRhiDescriptorPool *_eval_desc_pool;
|
|
||||||
|
|
||||||
wp_intern void *_loadSpirv(const WpAllocator *alloc, const char *path, u64 *out_size) {
|
|
||||||
u64 path_len = strlen(path);
|
|
||||||
WpStr8RO filepath = { path_len, path_len, (c8 *)path };
|
|
||||||
WpFile *f = wpFileOpen(alloc, &filepath, WP_ACCESS_READ);
|
|
||||||
if (!f) {
|
|
||||||
fprintf(stderr, "failed to open SPIR-V: %s\n", path);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
i64 file_size = wpFileGetLength(f);
|
|
||||||
if (file_size <= 0) {
|
|
||||||
wpFileClose(f);
|
|
||||||
fprintf(stderr, "empty SPIR-V file: %s\n", path);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
void *code = wpMemAllocatorAlloc(alloc, (u64)file_size);
|
|
||||||
if (!code) { wpFileClose(f); abort(); }
|
|
||||||
u64 bytes_read = wpFileRead(code, f, (u64)file_size);
|
|
||||||
wpFileClose(f);
|
|
||||||
if (bytes_read != (u64)file_size) {
|
|
||||||
fprintf(stderr, "short read on SPIR-V: %s\n", path);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
*out_size = (u64)file_size;
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern void prNodeEvalInit(PrRhiDevice *device, PrRhiFormat output_format) {
|
|
||||||
(void)output_format;
|
|
||||||
memcpy(pr_node_type_table, _node_type_table_data, sizeof(_node_type_table_data));
|
|
||||||
|
|
||||||
for (u32 i = 0; i < COUNT_NODE_TYPES; ++i) {
|
|
||||||
PrNodeTypeEntry *entry = &pr_node_type_table[i];
|
|
||||||
if (entry->shader_type != PR_SHADER_TYPE_FRAGMENT) { continue; }
|
|
||||||
if (!entry->vertex_shader_path || !entry->fragment_shader_path) { continue; }
|
|
||||||
|
|
||||||
// load SPIR-V
|
|
||||||
u64 vert_size = 0, frag_size = 0;
|
|
||||||
void *vert_code = _loadSpirv(&_G_RHI_CONTEXT.allocator, entry->vertex_shader_path, &vert_size);
|
|
||||||
void *frag_code = _loadSpirv(&_G_RHI_CONTEXT.allocator, entry->fragment_shader_path, &frag_size);
|
|
||||||
|
|
||||||
// create shaders
|
|
||||||
entry->vertex_shader = prRhiCreateShader(device, (PrRhiShaderDesc){
|
|
||||||
.spirv_code = vert_code,
|
|
||||||
.spirv_size = vert_size,
|
|
||||||
});
|
|
||||||
entry->fragment_shader = prRhiCreateShader(device, (PrRhiShaderDesc){
|
|
||||||
.spirv_code = frag_code,
|
|
||||||
.spirv_size = frag_size,
|
|
||||||
});
|
|
||||||
|
|
||||||
// free SPIR-V (now owned by Vulkan)
|
|
||||||
wpMemAllocatorFree(&_G_RHI_CONTEXT.allocator, &vert_code, vert_size);
|
|
||||||
wpMemAllocatorFree(&_G_RHI_CONTEXT.allocator, &frag_code, frag_size);
|
|
||||||
|
|
||||||
// create descriptor set layout
|
|
||||||
PrRhiDescriptorSetLayoutBindingArray bindings = NULL;
|
|
||||||
if (entry->input_count > 0) {
|
|
||||||
bindings = wpArrayWithCapacity(PrRhiDescriptorSetLayoutBinding, 4, WP_ARRAY_INIT_NONE);
|
|
||||||
for (u32 b = 0; b < entry->input_count; ++b) {
|
|
||||||
PrRhiDescriptorSetLayoutBinding binding = {
|
|
||||||
.type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
||||||
.descriptor_count = 1,
|
|
||||||
.stage_flags = PR_RHI_SHADER_STAGE_FRAGMENT,
|
|
||||||
.binding_flags = (PrRhiDescriptorBindingFlag)0,
|
|
||||||
};
|
|
||||||
wpArrayAppendCapped(PrRhiDescriptorSetLayoutBinding, bindings, &binding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry->set_layout = prRhiCreateDescriptorSetLayout(device, (PrRhiDescriptorSetLayoutDesc){
|
|
||||||
.bindings = bindings,
|
|
||||||
});
|
|
||||||
|
|
||||||
// create pipeline layout
|
|
||||||
PrRhiPushConstantRange pc_range = {
|
|
||||||
.stage_flags = PR_RHI_SHADER_STAGE_FRAGMENT,
|
|
||||||
.offset = 0,
|
|
||||||
.size = entry->push_constant_size,
|
|
||||||
};
|
|
||||||
PrRhiDescriptorSetLayoutArray set_layouts = entry->set_layout
|
|
||||||
? wpArray(PrRhiDescriptorSetLayout *, entry->set_layout)
|
|
||||||
: NULL;
|
|
||||||
PrRhiPushConstantRangeArray pc_ranges = entry->push_constant_size > 0
|
|
||||||
? wpArray(PrRhiPushConstantRange, pc_range)
|
|
||||||
: NULL;
|
|
||||||
entry->pipeline_layout = prRhiCreatePipelineLayout(device, (PrRhiPipelineLayoutDesc){
|
|
||||||
.set_layouts = set_layouts,
|
|
||||||
.push_constant_ranges = pc_ranges,
|
|
||||||
});
|
|
||||||
|
|
||||||
// create graphics pipeline
|
|
||||||
// pool textures are always RGBA16F
|
|
||||||
PrRhiFormatArray color_formats = wpArray(PrRhiFormat, PR_RHI_FORMAT_R16G16B16A16_SFLOAT);
|
|
||||||
PrRhiColorBlendAttachmentArray blend_attachments = wpArray(PrRhiColorBlendAttachment,
|
|
||||||
((PrRhiColorBlendAttachment){ .color_write_mask = 0xF }));
|
|
||||||
entry->pipeline = prRhiCreateGraphicsPipeline(device, (PrRhiGraphicsPipelineDesc){
|
|
||||||
.vertex_shader = entry->vertex_shader,
|
|
||||||
.vertex_shader_entry_point = "main",
|
|
||||||
.fragment_shader = entry->fragment_shader,
|
|
||||||
.fragment_shader_entry_point = "main",
|
|
||||||
.vertex_bindings = NULL,
|
|
||||||
.vertex_attributes = NULL,
|
|
||||||
.topology = PR_RHI_TOPOLOGY_TRIANGLE_LIST,
|
|
||||||
.color_attachment_formats = color_formats,
|
|
||||||
.depth_attachment_format = PR_RHI_FORMAT_UNDEFINED,
|
|
||||||
.depth_test_enable = false,
|
|
||||||
.depth_write_enable = false,
|
|
||||||
.depth_compare_op = PR_RHI_COMPARE_OP_ALWAYS,
|
|
||||||
.blend_attachments = blend_attachments,
|
|
||||||
.dynamic_viewport = true,
|
|
||||||
.dynamic_scissor = true,
|
|
||||||
.polygon_mode = PR_RHI_POLYGON_MODE_FILL,
|
|
||||||
.cull_mode = PR_RHI_CULL_MODE_NONE,
|
|
||||||
.front_face = PR_RHI_FRONT_FACE_COUNTER_CLOCKWISE,
|
|
||||||
.line_width = 1.0,
|
|
||||||
.multisample_count = PR_RHI_SAMPLE_COUNT_1,
|
|
||||||
.layout = entry->pipeline_layout,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// create per-frame descriptor pool (reset each frame)
|
|
||||||
PrRhiDescriptorPoolSizeArray pool_sizes = wpArray(PrRhiDescriptorPoolSize,
|
|
||||||
((PrRhiDescriptorPoolSize){ .type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptor_count = 64 }));
|
|
||||||
_eval_desc_pool = prRhiCreateDescriptorPool(device, (PrRhiDescriptorPoolDesc){
|
|
||||||
.max_sets = 64,
|
|
||||||
.pool_sizes = pool_sizes,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern void prNodeEvalDestroy(PrRhiDevice *device) {
|
|
||||||
if (_eval_desc_pool) {
|
|
||||||
prRhiDestroyDescriptorPool(device, _eval_desc_pool);
|
|
||||||
_eval_desc_pool = NULL;
|
|
||||||
}
|
|
||||||
for (u32 i = 0; i < COUNT_NODE_TYPES; ++i) {
|
|
||||||
PrNodeTypeEntry *entry = &pr_node_type_table[i];
|
|
||||||
if (entry->pipeline) {
|
|
||||||
prRhiDestroyPipeline(device, entry->pipeline);
|
|
||||||
entry->pipeline = NULL;
|
|
||||||
}
|
|
||||||
if (entry->pipeline_layout) {
|
|
||||||
prRhiDestroyPipelineLayout(device, entry->pipeline_layout);
|
|
||||||
entry->pipeline_layout = NULL;
|
|
||||||
}
|
|
||||||
if (entry->set_layout) {
|
|
||||||
prRhiDestroyDescriptorSetLayout(device, entry->set_layout);
|
|
||||||
entry->set_layout = NULL;
|
|
||||||
}
|
|
||||||
if (entry->fragment_shader) {
|
|
||||||
prRhiDestroyShader(device, entry->fragment_shader);
|
|
||||||
entry->fragment_shader = NULL;
|
|
||||||
}
|
|
||||||
if (entry->vertex_shader) {
|
|
||||||
prRhiDestroyShader(device, entry->vertex_shader);
|
|
||||||
entry->vertex_shader = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Per-frame evaluation
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
wp_extern void prGraphEvaluate(PrNodeManager *mgr, PrRhiDevice *device,
|
|
||||||
PrTexturePool *pool, PrRhiCommandBuffer *cb,
|
|
||||||
PrRhiSampler *shared_sampler,
|
|
||||||
PrTextureSlot **out_output) {
|
|
||||||
PrGraph *graph = &mgr->graph;
|
|
||||||
u64 vertex_count = prGraphVertexCount(graph);
|
|
||||||
|
|
||||||
if (out_output) { *out_output = NULL; }
|
|
||||||
|
|
||||||
// 1. topological sort
|
|
||||||
WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(64));
|
|
||||||
WpU64Array topo = prGraphTopologicalSort(graph, &scratch);
|
|
||||||
u64 topo_count = topo ? wpArrayCount(topo) : 0;
|
|
||||||
|
|
||||||
// 2. compute initial refcounts (out-degree per node)
|
|
||||||
u32 *refcounts = wpMemAllocatorAlloc(&scratch, vertex_count * sizeof(u32));
|
|
||||||
memset(refcounts, 0, vertex_count * sizeof(u32));
|
|
||||||
for (u64 i = 0; i < graph->max_vertex_ever; ++i) {
|
|
||||||
if (!graph->vertices[i].active) { continue; }
|
|
||||||
PrGraphEdge *edge = graph->vertices[i].next_forward;
|
|
||||||
while (edge) {
|
|
||||||
refcounts[i]++;
|
|
||||||
edge = edge->next_forward;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. reset texture pool and descriptor pool
|
|
||||||
prTexturePoolReset(pool);
|
|
||||||
if (_eval_desc_pool) {
|
|
||||||
prRhiResetDescriptorPool(device, _eval_desc_pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
// output slot per node (transient, lives in scratch arena)
|
|
||||||
PrTextureSlot **output_slots = wpMemAllocatorAlloc(&scratch, vertex_count * sizeof(PrTextureSlot *));
|
|
||||||
memset(output_slots, 0, vertex_count * sizeof(PrTextureSlot *));
|
|
||||||
|
|
||||||
// 4. for each node in topological order
|
|
||||||
for (u64 t = 0; t < topo_count; ++t) {
|
|
||||||
u64 node_idx = topo[t];
|
|
||||||
PrNode *node = &mgr->nodes[node_idx];
|
|
||||||
if (node->generation == 0) { continue; } // inactive node
|
|
||||||
|
|
||||||
PrNodeTypeEntry *entry = &pr_node_type_table[node->type];
|
|
||||||
|
|
||||||
// acquire output texture
|
|
||||||
PrTextureSlot *output_slot = prTexturePoolAcquire(pool, device);
|
|
||||||
if (!output_slot) { abort(); }
|
|
||||||
|
|
||||||
// gather input textures (predecessors via backward edges)
|
|
||||||
PrTextureSlot *input_slots[4];
|
|
||||||
u32 input_count = 0;
|
|
||||||
PrGraphEdge *edge = graph->vertices[node_idx].next_backward;
|
|
||||||
while (edge && input_count < 4) {
|
|
||||||
u64 src_idx = edge->source_idx;
|
|
||||||
if (output_slots[src_idx]) {
|
|
||||||
input_slots[input_count++] = output_slots[src_idx];
|
|
||||||
}
|
|
||||||
edge = edge->next_backward;
|
|
||||||
}
|
|
||||||
// READ nodes: use the node's own persistent texture if no edges
|
|
||||||
if (input_count == 0 && node->texture) {
|
|
||||||
input_slots[0] = NULL;
|
|
||||||
input_count = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// allocate descriptor set (skip for nodes with no inputs)
|
|
||||||
PrRhiDescriptorSet *desc_set = NULL;
|
|
||||||
if (entry->input_count > 0 && input_count > 0) {
|
|
||||||
desc_set = prRhiAllocateDescriptorSet(device, _eval_desc_pool, entry->set_layout, NULL);
|
|
||||||
|
|
||||||
// build write descriptors
|
|
||||||
PrRhiDescriptorImageInfo image_infos[4];
|
|
||||||
PrRhiWriteDescriptorSet writes[4];
|
|
||||||
for (u32 i = 0; i < input_count; ++i) {
|
|
||||||
PrRhiTexture *tex = input_slots[i] ? input_slots[i]->texture : node->texture;
|
|
||||||
image_infos[i] = (PrRhiDescriptorImageInfo){
|
|
||||||
.texture = tex,
|
|
||||||
.sampler = shared_sampler,
|
|
||||||
.layout = PR_RHI_LAYOUT_READ_ONLY_OPTIMAL,
|
|
||||||
};
|
|
||||||
writes[i] = (PrRhiWriteDescriptorSet){
|
|
||||||
.dst_set = desc_set,
|
|
||||||
.dst_binding = i,
|
|
||||||
.dst_array_element= 0,
|
|
||||||
.type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
||||||
.image_info = &image_infos[i],
|
|
||||||
.buffer_info = NULL,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
prRhiUpdateDescriptorSet(device, writes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// record commands
|
|
||||||
PrRhiColorAttachment color_att = {
|
|
||||||
.texture = output_slot->texture,
|
|
||||||
.layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL,
|
|
||||||
.clear = true,
|
|
||||||
.clear_color = {0.0f, 0.0f, 0.0f, 0.0f},
|
|
||||||
};
|
|
||||||
prRhiCmdBeginRendering(cb, &color_att, NULL);
|
|
||||||
prRhiCmdBindPipeline(cb, PR_RHI_PIPELINE_BIND_POINT_GRAPHICS, entry->pipeline);
|
|
||||||
prRhiCmdSetViewport(cb, 0.0f, 0.0f, (f32)pool->width, (f32)pool->height);
|
|
||||||
prRhiCmdSetScissor(cb, 0, 0, pool->width, pool->height);
|
|
||||||
|
|
||||||
if (desc_set) {
|
|
||||||
PrRhiDescriptorSet *sets_arr[1] = { desc_set };
|
|
||||||
prRhiCmdBindDescriptorSets(cb, PR_RHI_PIPELINE_BIND_POINT_GRAPHICS,
|
|
||||||
entry->pipeline_layout, 0, sets_arr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// push constants
|
|
||||||
if (entry->push_constant_size > 0) {
|
|
||||||
prRhiCmdPushConstants(cb, entry->pipeline_layout,
|
|
||||||
PR_RHI_SHADER_STAGE_FRAGMENT, 0,
|
|
||||||
entry->push_constant_size, &node->params);
|
|
||||||
}
|
|
||||||
|
|
||||||
prRhiCmdDraw(cb, 3, 1, 0, 0);
|
|
||||||
prRhiCmdEndRendering(cb);
|
|
||||||
|
|
||||||
// release input textures whose refcount hit 0
|
|
||||||
edge = graph->vertices[node_idx].next_backward;
|
|
||||||
u32 input_idx = 0;
|
|
||||||
while (edge && input_idx < input_count) {
|
|
||||||
u64 src_idx = edge->source_idx;
|
|
||||||
if (refcounts[src_idx] > 0) {
|
|
||||||
refcounts[src_idx]--;
|
|
||||||
if (refcounts[src_idx] == 0) {
|
|
||||||
prTexturePoolRelease(pool, output_slots[src_idx]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edge = edge->next_backward;
|
|
||||||
input_idx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
output_slots[node_idx] = output_slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the last node's output as the compositor output
|
|
||||||
if (out_output && topo_count > 0) {
|
|
||||||
u64 last_idx = topo[topo_count - 1];
|
|
||||||
*out_output = output_slots[last_idx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
// vim:fileencoding=utf-8:foldmethod=marker
|
|
||||||
|
|
||||||
#ifndef PR_NODE_EVAL_H
|
|
||||||
#define PR_NODE_EVAL_H
|
|
||||||
|
|
||||||
#include "../../vendor/wapp/common/aliases/aliases.h"
|
|
||||||
#include "../rhi/pr_rhi_types.h"
|
|
||||||
#include "pr_node.h"
|
|
||||||
#include "pr_texture_pool.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Shader type
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef enum PrShaderType {
|
|
||||||
PR_SHADER_TYPE_FRAGMENT, // fullscreen triangle, per-pixel
|
|
||||||
PR_SHADER_TYPE_COMPUTE, // dispatch, shared memory
|
|
||||||
} PrShaderType;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Push constant structs (one per node type)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
f32 radius;
|
|
||||||
} PrBlurPushConstants;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
f32 gain;
|
|
||||||
f32 offset;
|
|
||||||
f32 power;
|
|
||||||
} PrGradePushConstants;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
u32 mode; // 0=over, 1=under, 2=add
|
|
||||||
} PrBlendPushConstants;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Node type entry — maps a node type to its resource signature
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct PrNodeTypeEntry {
|
|
||||||
PrNodeType type;
|
|
||||||
PrShaderType shader_type;
|
|
||||||
|
|
||||||
// shader paths (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
|
|
||||||
|
|
||||||
// resource signature
|
|
||||||
u32 input_count; // number of texture inputs
|
|
||||||
u32 output_count; // always 1 for V1
|
|
||||||
|
|
||||||
// push constant size (bytes)
|
|
||||||
u32 push_constant_size;
|
|
||||||
|
|
||||||
// created at init, cached here
|
|
||||||
PrRhiShader *vertex_shader;
|
|
||||||
PrRhiShader *fragment_shader;
|
|
||||||
PrRhiDescriptorSetLayout *set_layout;
|
|
||||||
PrRhiPipelineLayout *pipeline_layout;
|
|
||||||
PrRhiPipeline *pipeline;
|
|
||||||
} PrNodeTypeEntry;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Global registry
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
extern PrNodeTypeEntry pr_node_type_table[COUNT_NODE_TYPES];
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Evaluation
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
wp_extern void prNodeEvalInit(PrRhiDevice *device, PrRhiFormat output_format);
|
|
||||||
wp_extern void prNodeEvalDestroy(PrRhiDevice *device);
|
|
||||||
|
|
||||||
// Per-frame graph evaluation. Records commands into cb.
|
|
||||||
// pool is reset each frame. desc_pool is reset each frame.
|
|
||||||
// out_output receives the final compositor output slot (last node in topo order).
|
|
||||||
wp_extern void prGraphEvaluate(PrNodeManager *mgr, PrRhiDevice *device,
|
|
||||||
PrTexturePool *pool, PrRhiCommandBuffer *cb,
|
|
||||||
PrRhiSampler *shared_sampler,
|
|
||||||
PrTextureSlot **out_output);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // !PR_NODE_EVAL_H
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
#include "pr_texture_pool.h"
|
|
||||||
#include "../rhi/pr_rhi.h"
|
|
||||||
#include "../../vendor/wapp/wapp.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
wp_intern b8 _growPool(PrTexturePool *pool, PrRhiDevice *device) {
|
|
||||||
u32 old_count = pool->count;
|
|
||||||
u32 new_count = old_count + PR_TEXTURE_POOL_GROWTH_BATCH;
|
|
||||||
if (new_count > pool->max) { new_count = pool->max; }
|
|
||||||
if (old_count >= pool->max) {
|
|
||||||
fprintf(stderr, "texture pool exhausted: %u in use, max %u\n", pool->in_use, pool->max);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
PrTextureSlot *new_slots = wpArrayAllocCapacity(PrTextureSlot, pool->alloc, new_count, WP_ARRAY_INIT_FILLED);
|
|
||||||
if (!new_slots) { return false; }
|
|
||||||
if (pool->slots) {
|
|
||||||
memcpy(new_slots, pool->slots, old_count * sizeof(PrTextureSlot));
|
|
||||||
wpArrayDealloc(PrTextureSlot, pool->alloc, &pool->slots);
|
|
||||||
}
|
|
||||||
pool->slots = new_slots;
|
|
||||||
|
|
||||||
for (u32 i = old_count; i < new_count; ++i) {
|
|
||||||
PrRhiTextureDesc desc = {
|
|
||||||
.format = PR_RHI_FORMAT_R16G16B16A16_SFLOAT,
|
|
||||||
.width = pool->width,
|
|
||||||
.height = pool->height,
|
|
||||||
.mip_levels = 1,
|
|
||||||
.usage = PR_RHI_TEXTURE_USAGE_SAMPLED | PR_RHI_TEXTURE_USAGE_COLOR_ATTACHMENT,
|
|
||||||
};
|
|
||||||
PrRhiTexture *tex = prRhiCreateTexture(device, desc);
|
|
||||||
if (!tex) { return false; }
|
|
||||||
pool->slots[i].texture = tex;
|
|
||||||
pool->slots[i].refcount = 0;
|
|
||||||
pool->slots[i].in_use = false;
|
|
||||||
}
|
|
||||||
pool->count = new_count;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern void prTexturePoolInit(PrTexturePool *pool, u32 initial_capacity, u32 max, u32 width, u32 height, const WpAllocator *alloc) {
|
|
||||||
pool->alloc = alloc;
|
|
||||||
pool->slots = NULL;
|
|
||||||
pool->count = 0;
|
|
||||||
pool->in_use = 0;
|
|
||||||
pool->max = max;
|
|
||||||
pool->width = width;
|
|
||||||
pool->height = height;
|
|
||||||
if (initial_capacity > 0) {
|
|
||||||
pool->slots = wpArrayAllocCapacity(PrTextureSlot, alloc, initial_capacity, WP_ARRAY_INIT_FILLED);
|
|
||||||
if (!pool->slots) {
|
|
||||||
fprintf(stderr, "texture pool initial allocation failed\n");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
pool->count = initial_capacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern void prTexturePoolReset(PrTexturePool *pool) {
|
|
||||||
for (u32 i = 0; i < pool->count; ++i) {
|
|
||||||
pool->slots[i].refcount = 0;
|
|
||||||
pool->slots[i].in_use = false;
|
|
||||||
}
|
|
||||||
pool->in_use = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern PrTextureSlot *prTexturePoolAcquire(PrTexturePool *pool, PrRhiDevice *device) {
|
|
||||||
for (u32 i = 0; i < pool->count; ++i) {
|
|
||||||
if (!pool->slots[i].in_use) {
|
|
||||||
pool->slots[i].in_use = true;
|
|
||||||
pool->in_use += 1;
|
|
||||||
return &pool->slots[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!_growPool(pool, device)) { return NULL; }
|
|
||||||
PrTextureSlot *slot = &pool->slots[pool->count - 1];
|
|
||||||
slot->in_use = true;
|
|
||||||
pool->in_use += 1;
|
|
||||||
return slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern void prTexturePoolRelease(PrTexturePool *pool, PrTextureSlot *slot) {
|
|
||||||
(void)pool;
|
|
||||||
slot->refcount = 0;
|
|
||||||
slot->in_use = false;
|
|
||||||
pool->in_use -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_extern void prTexturePoolDestroy(PrTexturePool *pool, PrRhiDevice *device) {
|
|
||||||
for (u32 i = 0; i < pool->count; ++i) {
|
|
||||||
if (pool->slots[i].texture) {
|
|
||||||
prRhiDestroyTexture(device, pool->slots[i].texture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pool->slots) { wpArrayDealloc(PrTextureSlot, pool->alloc, &pool->slots); }
|
|
||||||
pool->slots = NULL;
|
|
||||||
pool->count = 0;
|
|
||||||
pool->in_use = 0;
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#ifndef PR_TEXTURE_POOL_H
|
|
||||||
#define PR_TEXTURE_POOL_H
|
|
||||||
|
|
||||||
#include "../rhi/pr_rhi_types.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define PR_TEXTURE_POOL_GROWTH_BATCH 8
|
|
||||||
|
|
||||||
typedef struct PrTextureSlot {
|
|
||||||
PrRhiTexture *texture;
|
|
||||||
u32 refcount;
|
|
||||||
b8 in_use;
|
|
||||||
} PrTextureSlot;
|
|
||||||
|
|
||||||
typedef struct PrTexturePool {
|
|
||||||
PrTextureSlot *slots;
|
|
||||||
const WpAllocator *alloc;
|
|
||||||
u32 count;
|
|
||||||
u32 in_use;
|
|
||||||
u32 max;
|
|
||||||
u32 width;
|
|
||||||
u32 height;
|
|
||||||
} PrTexturePool;
|
|
||||||
|
|
||||||
wp_extern void prTexturePoolInit(PrTexturePool *pool, u32 initial_capacity, u32 max, u32 width, u32 height, const WpAllocator *alloc);
|
|
||||||
wp_extern void prTexturePoolReset(PrTexturePool *pool);
|
|
||||||
wp_extern PrTextureSlot *prTexturePoolAcquire(PrTexturePool *pool, PrRhiDevice *device);
|
|
||||||
wp_extern void prTexturePoolRelease(PrTexturePool *pool, PrTextureSlot *slot);
|
|
||||||
wp_extern void prTexturePoolDestroy(PrTexturePool *pool, PrRhiDevice *device);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -111,6 +111,7 @@ PrRhiTexture *prRhiCreateTexture(PrRhiDevice *device, PrRhiTextureDesc desc);
|
|||||||
PrRhiTexture *prRhiCreateTextureFromKtx(PrRhiDevice *device, const char *path,
|
PrRhiTexture *prRhiCreateTextureFromKtx(PrRhiDevice *device, const char *path,
|
||||||
PrRhiCommandPool *pool, PrRhiCommandBuffer *cb);
|
PrRhiCommandPool *pool, PrRhiCommandBuffer *cb);
|
||||||
void prRhiDestroyTexture(PrRhiDevice *device, PrRhiTexture *texture);
|
void prRhiDestroyTexture(PrRhiDevice *device, PrRhiTexture *texture);
|
||||||
|
PrRhiTextureSize prRhiGetTextureSize(PrRhiTexture *texture);
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
// Samplers
|
// Samplers
|
||||||
|
|||||||
@@ -352,6 +352,11 @@ typedef struct PrRhiShaderDesc {
|
|||||||
u64 spirv_size;
|
u64 spirv_size;
|
||||||
} PrRhiShaderDesc;
|
} PrRhiShaderDesc;
|
||||||
|
|
||||||
|
typedef struct PrRhiTextureSize {
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
} PrRhiTextureSize;
|
||||||
|
|
||||||
typedef struct PrRhiPipelineLayoutDesc {
|
typedef struct PrRhiPipelineLayoutDesc {
|
||||||
PrRhiDescriptorSetLayoutArray set_layouts;
|
PrRhiDescriptorSetLayoutArray set_layouts;
|
||||||
PrRhiPushConstantRangeArray push_constant_ranges;
|
PrRhiPushConstantRangeArray push_constant_ranges;
|
||||||
|
|||||||
@@ -834,7 +834,9 @@ PrRhiSwapchainResult prRhiAcquireNextImageVk(PrRhiDevice *device, PrRhiSwapchain
|
|||||||
VkSemaphore vk_semaphore = signal_semaphore ? signal_semaphore->handle : VK_NULL_HANDLE;
|
VkSemaphore vk_semaphore = signal_semaphore ? signal_semaphore->handle : VK_NULL_HANDLE;
|
||||||
VkResult res = vkAcquireNextImageKHR(device->handle, swapchain->handle, UINT64_MAX, vk_semaphore,
|
VkResult res = vkAcquireNextImageKHR(device->handle, swapchain->handle, UINT64_MAX, vk_semaphore,
|
||||||
VK_NULL_HANDLE, &swapchain->current_image_index);
|
VK_NULL_HANDLE, &swapchain->current_image_index);
|
||||||
if (res == VK_ERROR_OUT_OF_DATE_KHR) { return PR_RHI_SWAPCHAIN_OUT_OF_DATE; }
|
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) {
|
||||||
|
return PR_RHI_SWAPCHAIN_OUT_OF_DATE;
|
||||||
|
}
|
||||||
_checkVk(res, "vkAcquireNextImageKHR");
|
_checkVk(res, "vkAcquireNextImageKHR");
|
||||||
if (out_image_index) { *out_image_index = swapchain->current_image_index; }
|
if (out_image_index) { *out_image_index = swapchain->current_image_index; }
|
||||||
return PR_RHI_SWAPCHAIN_SUCCESS;
|
return PR_RHI_SWAPCHAIN_SUCCESS;
|
||||||
@@ -854,7 +856,9 @@ PrRhiSwapchainResult prRhiPresentVk(PrRhiDevice *device, PrRhiSwapchain *swapcha
|
|||||||
};
|
};
|
||||||
|
|
||||||
VkResult res = vkQueuePresentKHR(device->queue, &present_info);
|
VkResult res = vkQueuePresentKHR(device->queue, &present_info);
|
||||||
if (res == VK_ERROR_OUT_OF_DATE_KHR) { return PR_RHI_SWAPCHAIN_OUT_OF_DATE; }
|
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) {
|
||||||
|
return PR_RHI_SWAPCHAIN_OUT_OF_DATE;
|
||||||
|
}
|
||||||
_checkVk(res, "vkQueuePresentKHR");
|
_checkVk(res, "vkQueuePresentKHR");
|
||||||
return PR_RHI_SWAPCHAIN_SUCCESS;
|
return PR_RHI_SWAPCHAIN_SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -866,13 +870,22 @@ void prRhiRecreateSwapchainVk(PrRhiDevice *device, PrRhiSwapchain **swapchain,
|
|||||||
|
|
||||||
VkDevice vk_device = device->handle;
|
VkDevice vk_device = device->handle;
|
||||||
VkPhysicalDevice vk_pdev = device->physical_device;
|
VkPhysicalDevice vk_pdev = device->physical_device;
|
||||||
|
VkSurfaceKHR vk_surface = ((PrRhiSurface *)old->surface)->handle;
|
||||||
|
|
||||||
VkExtent2D extent = { width, height };
|
VkSurfaceCapabilitiesKHR caps = {};
|
||||||
|
_checkVk(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk_pdev, vk_surface, &caps),
|
||||||
|
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
|
||||||
|
|
||||||
|
VkExtent2D extent = caps.currentExtent;
|
||||||
|
if (extent.width == 0xffffffff) {
|
||||||
|
extent.width = width;
|
||||||
|
extent.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
VkSwapchainCreateInfoKHR swapchain_info = {
|
VkSwapchainCreateInfoKHR swapchain_info = {
|
||||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||||
.oldSwapchain = old->handle,
|
.oldSwapchain = old->handle,
|
||||||
.surface = ((PrRhiSurface *)old->surface)->handle,
|
.surface = vk_surface,
|
||||||
.minImageCount = old->image_count,
|
.minImageCount = old->image_count,
|
||||||
.imageFormat = (VkFormat)old->format,
|
.imageFormat = (VkFormat)old->format,
|
||||||
.imageColorSpace = _default_colorspace,
|
.imageColorSpace = _default_colorspace,
|
||||||
@@ -1315,6 +1328,14 @@ PrRhiTexture *prRhiCreateTextureFromKtxVk(PrRhiDevice *device, const char *path,
|
|||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PrRhiTextureSize prRhiGetTextureSizeVk(PrRhiTexture *texture) {
|
||||||
|
PrRhiTextureSize size = {};
|
||||||
|
if (!texture) { return size; }
|
||||||
|
size.width = texture->width;
|
||||||
|
size.height = texture->height;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
void prRhiDestroyTextureVk(PrRhiDevice *device, PrRhiTexture *texture) {
|
void prRhiDestroyTextureVk(PrRhiDevice *device, PrRhiTexture *texture) {
|
||||||
if (!texture) { return; }
|
if (!texture) { return; }
|
||||||
VkDevice vk_device = device->handle;
|
VkDevice vk_device = device->handle;
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ PrRhiTexture *prRhiCreateTextureVk(PrRhiDevice *device, PrRhiTextureDesc desc);
|
|||||||
PrRhiTexture *prRhiCreateTextureFromKtxVk(PrRhiDevice *device, const char *path,
|
PrRhiTexture *prRhiCreateTextureFromKtxVk(PrRhiDevice *device, const char *path,
|
||||||
PrRhiCommandPool *pool, PrRhiCommandBuffer *cb);
|
PrRhiCommandPool *pool, PrRhiCommandBuffer *cb);
|
||||||
void prRhiDestroyTextureVk(PrRhiDevice *device, PrRhiTexture *texture);
|
void prRhiDestroyTextureVk(PrRhiDevice *device, PrRhiTexture *texture);
|
||||||
|
PrRhiTextureSize prRhiGetTextureSizeVk(PrRhiTexture *texture);
|
||||||
|
|
||||||
PrRhiSampler *prRhiCreateSamplerVk(PrRhiDevice *device, PrRhiSamplerDesc desc);
|
PrRhiSampler *prRhiCreateSamplerVk(PrRhiDevice *device, PrRhiSamplerDesc desc);
|
||||||
void prRhiDestroySamplerVk(PrRhiDevice *device, PrRhiSampler *sampler);
|
void prRhiDestroySamplerVk(PrRhiDevice *device, PrRhiSampler *sampler);
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#define prRhiCreateTexture prRhiCreateTextureVk
|
#define prRhiCreateTexture prRhiCreateTextureVk
|
||||||
#define prRhiCreateTextureFromKtx prRhiCreateTextureFromKtxVk
|
#define prRhiCreateTextureFromKtx prRhiCreateTextureFromKtxVk
|
||||||
#define prRhiDestroyTexture prRhiDestroyTextureVk
|
#define prRhiDestroyTexture prRhiDestroyTextureVk
|
||||||
|
#define prRhiGetTextureSize prRhiGetTextureSizeVk
|
||||||
#define prRhiCreateSampler prRhiCreateSamplerVk
|
#define prRhiCreateSampler prRhiCreateSamplerVk
|
||||||
#define prRhiDestroySampler prRhiDestroySamplerVk
|
#define prRhiDestroySampler prRhiDestroySamplerVk
|
||||||
#define prRhiCreateShader prRhiCreateShaderVk
|
#define prRhiCreateShader prRhiCreateShaderVk
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
// BLEND node — composites two textures.
|
|
||||||
// 2 input textures, push constant: u32 mode (0=over, 1=under, 2=add).
|
|
||||||
|
|
||||||
[[vk::binding(0, 0)]]
|
|
||||||
Texture2D<float4> background : register(t0);
|
|
||||||
[[vk::binding(1, 0)]]
|
|
||||||
Texture2D<float4> foreground : register(t1);
|
|
||||||
[[vk::binding(2, 0)]]
|
|
||||||
SamplerState input_sampler : register(s0);
|
|
||||||
|
|
||||||
struct PushConstants {
|
|
||||||
uint mode;
|
|
||||||
};
|
|
||||||
|
|
||||||
[[vk::push_constant]]
|
|
||||||
PushConstants pc;
|
|
||||||
|
|
||||||
struct VSOutput {
|
|
||||||
float4 position : SV_Position;
|
|
||||||
float2 uv : TEXCOORD0;
|
|
||||||
};
|
|
||||||
|
|
||||||
float4 main(VSOutput input) : SV_Target {
|
|
||||||
float4 bg = background.Sample(input_sampler, input.uv);
|
|
||||||
float4 fg = foreground.Sample(input_sampler, input.uv);
|
|
||||||
|
|
||||||
float4 result;
|
|
||||||
switch (pc.mode) {
|
|
||||||
case 0: // over: foreground over background
|
|
||||||
result = fg.a * fg + (1.0 - fg.a) * bg;
|
|
||||||
break;
|
|
||||||
case 1: // under: background over foreground
|
|
||||||
result = bg.a * bg + (1.0 - bg.a) * fg;
|
|
||||||
break;
|
|
||||||
case 2: // add: additive blend
|
|
||||||
result = bg + fg;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
result = fg;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// Fullscreen triangle — no vertex buffer needed.
|
|
||||||
// Uses gl_VertexIndex to generate a single triangle that covers the viewport.
|
|
||||||
|
|
||||||
struct VSOutput {
|
|
||||||
float4 position : SV_Position;
|
|
||||||
float2 uv : TEXCOORD0;
|
|
||||||
};
|
|
||||||
|
|
||||||
VSOutput main(uint vertex_id : SV_VertexID) {
|
|
||||||
VSOutput output;
|
|
||||||
// Generate UV from vertex ID (0, 1, 2)
|
|
||||||
output.uv = float2((vertex_id << 1) & 2, vertex_id & 2);
|
|
||||||
// Generate clip-space position
|
|
||||||
output.position = float4(output.uv * 2.0 - 1.0, 0.0, 1.0);
|
|
||||||
// Flip Y for Vulkan
|
|
||||||
output.position.y = -output.position.y;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// Blit-to-swapchain fragment shader.
|
|
||||||
// Samples the compositor output (RGBA16F pool texture) and writes it
|
|
||||||
// to the swapchain color attachment.
|
|
||||||
|
|
||||||
[vk::binding(0, 0)] Texture2D<float4> tex : register(t0);
|
|
||||||
[vk::binding(1, 0)] SamplerState smp : register(s0);
|
|
||||||
|
|
||||||
float4 main(float4 position : SV_Position, float2 uv : SV_Target0) : SV_Target0 {
|
|
||||||
return tex.Sample(smp, uv);
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Fullscreen triangle — no vertex buffer, no inputs.
|
|
||||||
// Draws a single triangle that covers the entire viewport.
|
|
||||||
// Reused for all blit / composit passes.
|
|
||||||
|
|
||||||
struct VsOut {
|
|
||||||
float4 position : SV_Position;
|
|
||||||
float2 uv : SV_Target0;
|
|
||||||
};
|
|
||||||
|
|
||||||
VsOut main(uint vertex_id : SV_VertexID) {
|
|
||||||
// Generate fullscreen triangle from vertex ID.
|
|
||||||
// vertex_id 0 → (-1,-1), 1 → (-1,3), 2 → (3,-1)
|
|
||||||
// UV flips Y so image top maps to screen top.
|
|
||||||
float2 positions[3] = {
|
|
||||||
float2(-1.0, -1.0),
|
|
||||||
float2(-1.0, 3.0),
|
|
||||||
float2( 3.0, -1.0)
|
|
||||||
};
|
|
||||||
float2 uvs[3] = {
|
|
||||||
float2(0.0, 0.0),
|
|
||||||
float2(0.0, 2.0),
|
|
||||||
float2(2.0, 0.0)
|
|
||||||
};
|
|
||||||
|
|
||||||
VsOut output;
|
|
||||||
output.position = float4(positions[vertex_id], 0.0, 1.0);
|
|
||||||
output.uv = uvs[vertex_id];
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// BLUR node — Gaussian blur with configurable radius.
|
|
||||||
// 1 input texture, push constant: f32 radius.
|
|
||||||
|
|
||||||
[[vk::binding(0, 0)]]
|
|
||||||
Texture2D<float4> input_texture : register(t0);
|
|
||||||
[[vk::binding(1, 0)]]
|
|
||||||
SamplerState input_sampler : register(s0);
|
|
||||||
|
|
||||||
struct PushConstants {
|
|
||||||
float radius;
|
|
||||||
};
|
|
||||||
|
|
||||||
[[vk::push_constant]]
|
|
||||||
PushConstants pc;
|
|
||||||
|
|
||||||
struct VSOutput {
|
|
||||||
float4 position : SV_Position;
|
|
||||||
float2 uv : TEXCOORD0;
|
|
||||||
};
|
|
||||||
|
|
||||||
float4 main(VSOutput input) : SV_Target {
|
|
||||||
uint width, height;
|
|
||||||
input_texture.GetDimensions(width, height);
|
|
||||||
float2 texel_size = 1.0 / float2(width, height);
|
|
||||||
float4 result = float4(0.0, 0.0, 0.0, 0.0);
|
|
||||||
|
|
||||||
int radius = int(pc.radius);
|
|
||||||
float weight_sum = 0.0;
|
|
||||||
|
|
||||||
for (int x = -radius; x <= radius; ++x) {
|
|
||||||
for (int y = -radius; y <= radius; ++y) {
|
|
||||||
float2 offset = float2(x, y) * texel_size;
|
|
||||||
float weight = 1.0 / (1.0 + float(x * x + y * y));
|
|
||||||
result += input_texture.Sample(input_sampler, input.uv + offset) * weight;
|
|
||||||
weight_sum += weight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result / weight_sum;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// GRADE node — colour grading with gain, offset, power.
|
|
||||||
// 1 input texture, push constants: f32 gain, f32 offset, f32 power.
|
|
||||||
|
|
||||||
[[vk::binding(0, 0)]]
|
|
||||||
Texture2D<float4> input_texture : register(t0);
|
|
||||||
[[vk::binding(1, 0)]]
|
|
||||||
SamplerState input_sampler : register(s0);
|
|
||||||
|
|
||||||
struct PushConstants {
|
|
||||||
float gain;
|
|
||||||
float offset;
|
|
||||||
float power;
|
|
||||||
};
|
|
||||||
|
|
||||||
[[vk::push_constant]]
|
|
||||||
PushConstants pc;
|
|
||||||
|
|
||||||
struct VSOutput {
|
|
||||||
float4 position : SV_Position;
|
|
||||||
float2 uv : TEXCOORD0;
|
|
||||||
};
|
|
||||||
|
|
||||||
float4 main(VSOutput input) : SV_Target {
|
|
||||||
float4 color = input_texture.Sample(input_sampler, input.uv);
|
|
||||||
|
|
||||||
// Apply gain, offset, power per channel
|
|
||||||
color.rgb = color.rgb * pc.gain + pc.offset;
|
|
||||||
color.rgb = pow(max(color.rgb, float3(0.0, 0.0, 0.0)), pc.power);
|
|
||||||
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// READ node — samples from a persistent KTX texture.
|
|
||||||
// No push constants, no inputs (texture loaded separately).
|
|
||||||
|
|
||||||
[[vk::binding(0, 0)]]
|
|
||||||
Texture2D<float4> input_texture : register(t0);
|
|
||||||
[[vk::binding(1, 0)]]
|
|
||||||
SamplerState input_sampler : register(s0);
|
|
||||||
|
|
||||||
struct VSOutput {
|
|
||||||
float4 position : SV_Position;
|
|
||||||
float2 uv : TEXCOORD0;
|
|
||||||
};
|
|
||||||
|
|
||||||
float4 main(VSOutput input) : SV_Target {
|
|
||||||
return input_texture.Sample(input_sampler, input.uv);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user