Files

151 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 01 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.