feat(vulkan): run the compute/UI path without VK_EXT_descriptor_heap
Device::Initialize required twelve device extensions with no capability check. Mesa Turnip (Adreno) lacks six of them: VK_EXT_descriptor_heap, VK_KHR_shader_untyped_pointers, VK_EXT_device_fault and the three ray tracing extensions. vkCreateDevice failed there, so not even a UI-only program could start, although the UI path is compute-only and needs nothing else that Turnip is missing. The descriptor heap, ray tracing and device fault are now optional groups, detected at start-up from the extension list and the feature bits (Device::descriptorHeapSupported, rayTracingSupported, deviceFaultSupported). Ray tracing needs the heap; on a device without it the ray tracing entry points throw a message naming the cause. Without the heap the same bindless slots live in one update-after-bind descriptor set per frame, owned by DescriptorHeapVulkan: binding 0 storage images, 1 sampled images, 2 samplers, 3 storage buffers. Slot allocation, the slot handles, UIDispatchHeader and the UIRenderer API are unchanged; buffer slots simply stop being offset. Window::Render binds that frame's set where it binds the heap, every compute pipeline shares the heap object's pipeline layout, and push data travels as push constants. Each UI kernel is built twice from one source. ui-shared.glsl declares its resources through UI_HEAP_* macros that expand to descriptor_heap or, with CRAFTER_GRAPHICS_DESCRIPTOR_SETS defined, to set/binding. <name>.comp.spv and <name>.comp.sets.spv ship side by side and ComputeShader::Load picks one. The heap build of every shader is byte-identical to before. Breaking: - ComputeShader::Load takes the heap as a second argument. - DescriptorHeapVulkan is no longer copyable or movable. - Custom UI shaders declare heap resources with the UI_HEAP_* macros and are registered twice in project.cpp (see the README). - project.cpp uses Shader::defines and Shader::variant, so it needs a crafter-build that has them (Crafter.Build master, fe38cc1 or later). Also: - maintenance5 is enabled as a feature, not only as an extension: every VulkanBuffer already relied on VkBufferUsageFlags2CreateInfo. - Barrier stage masks drop RAY_TRACING_SHADER on devices without ray tracing, and ImageVulkan::Create resolves its default consumer stage to COMPUTE_SHADER there. - Window refuses a swapchain whose image count is not numFrames instead of writing past images[]. The limitation itself is #160. - CRAFTER_GRAPHICS_NO_DESCRIPTOR_HEAP forces the fallback on a driver that has the heap, so it can be exercised on a desktop GPU. Verified on an RTX 4090 in both modes under the validation layer, and on a Fairphone 6 (Turnip, Adreno 810, postmarketOS), where the fallback is the only path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
6aa0338c0f
commit
48aa9be5ce
22 changed files with 912 additions and 110 deletions
63
README.md
63
README.md
|
|
@ -3,7 +3,9 @@
|
|||
Vulkan + WebGPU graphics library built around C++20 modules and
|
||||
bindless heaps. Provides window management, ray tracing, and a
|
||||
compute-shader-driven UI on a single, opinionated stack. Native
|
||||
builds use Vulkan with `VK_EXT_descriptor_heap`; `wasm32-*` builds
|
||||
builds use Vulkan with `VK_EXT_descriptor_heap` (the compute/UI path
|
||||
also runs without it, see
|
||||
[below](#drivers-without-vk_ext_descriptor_heap)); `wasm32-*` builds
|
||||
target the browser via WebGPU and a DOM window backend.
|
||||
|
||||
## Backends
|
||||
|
|
@ -79,7 +81,10 @@ shaded through an intersection shader with an any-hit cut-out.
|
|||
`VK_EXT_descriptor_heap`; pipelines are created with
|
||||
`VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT` so there are no
|
||||
descriptor-set layouts and push constants travel via
|
||||
`vkCmdPushDataEXT`.
|
||||
`vkCmdPushDataEXT`. The heap, ray tracing and device-fault extensions
|
||||
are optional groups, detected at start-up
|
||||
(`Device::descriptorHeapSupported`, `rayTracingSupported`,
|
||||
`deviceFaultSupported`).
|
||||
- **DescriptorHeapVulkan / DescriptorHeapWebGPU** — bindless slot
|
||||
allocators. Vulkan side allocates image/buffer/sampler slots in a
|
||||
`VK_EXT_descriptor_heap`; WebGPU side resolves slots to JS-side
|
||||
|
|
@ -108,12 +113,13 @@ shaded through an intersection shader with an any-hit cut-out.
|
|||
[WAVEFRONT-DESIGN.md](WAVEFRONT-DESIGN.md).
|
||||
- **ComputeShader / WebGPUComputeShader** — Tier 1 wrapper used by the
|
||||
UI system. Vulkan loads a `.spv` and dispatches with
|
||||
`vkCmdPushDataEXT`; WebGPU loads a user-supplied `.wgsl` blob at
|
||||
`vkCmdPushDataEXT` (or the `.sets.spv` build and `vkCmdPushConstants`
|
||||
without the heap); WebGPU loads a user-supplied `.wgsl` blob at
|
||||
runtime via `wgpuLoadCustomShader`. Use it directly for any custom
|
||||
compute.
|
||||
- **UI** — three-tier UI system; see below. The standard shaders ship
|
||||
as four `.spv` blobs on native and four WGSL strings baked into the
|
||||
WebGPU dispatcher.
|
||||
- **UI** — three-tier UI system; see below. The standard shaders (four
|
||||
kernels plus the fused one) ship as `.spv` / `.sets.spv` pairs on
|
||||
native and as WGSL strings baked into the WebGPU dispatcher.
|
||||
- **FontAtlas** — single-channel SDF atlas (1024×1024, 32pt base,
|
||||
shelf-packed, lazy `Ensure` per codepoint, dirty-flush via `Update`).
|
||||
Backend-agnostic.
|
||||
|
|
@ -168,6 +174,51 @@ overhead. If you need to render thousands of UI items, you want a
|
|||
different shader (tile binning, per-item-list resolve), not more
|
||||
dispatches.
|
||||
|
||||
### Drivers without `VK_EXT_descriptor_heap`
|
||||
|
||||
The compute/UI path also runs on Vulkan drivers that lack
|
||||
`VK_EXT_descriptor_heap` (Mesa Turnip on Qualcomm Adreno, for one).
|
||||
`Device::Initialize` detects this and sets
|
||||
`Device::descriptorHeapSupported = false`. The same slots then live in
|
||||
one bindless descriptor set per frame instead of the heap, and push
|
||||
constants go through `vkCmdPushConstants`. Slot handles, item structs,
|
||||
`UIDispatchHeader` and the `UIRenderer` API are unchanged, so
|
||||
application code does not branch on it. Ray tracing is heap-only: on
|
||||
such a driver `Device::rayTracingSupported` is false and the RT entry
|
||||
points throw.
|
||||
|
||||
Each compute shader is shipped in two builds of the same source:
|
||||
`<name>.spv` for the heap and `<name>.sets.spv` for descriptor sets.
|
||||
`ComputeShader::Load(path, heap)` takes the `.spv` path and picks the
|
||||
right file itself. For a custom shader that means three things:
|
||||
|
||||
- Declare bindless resources with the `UI_HEAP_*` macros from
|
||||
[shaders/ui-shared.glsl](shaders/ui-shared.glsl), not with a literal
|
||||
`descriptor_heap`:
|
||||
|
||||
```glsl
|
||||
layout(UI_HEAP_BUFFERS, std430) readonly buffer MyBuf { MyItem items[]; } myHeap[];
|
||||
```
|
||||
|
||||
- Register the shader twice in `project.cpp`, the second time with the
|
||||
define and the variant name:
|
||||
|
||||
```cpp
|
||||
cfg.shaders.emplace_back(fs::path("my.comp.glsl"), std::string("main"), ShaderType::Compute);
|
||||
Shader& sets = cfg.shaders.emplace_back(fs::path("my.comp.glsl"), std::string("main"), ShaderType::Compute);
|
||||
sets.defines = {"CRAFTER_GRAPHICS_DESCRIPTOR_SETS"};
|
||||
sets.variant = "sets";
|
||||
```
|
||||
|
||||
- Keep the push-constant block within the device's
|
||||
`maxPushConstantsSize` (256 bytes on common drivers). Without the
|
||||
heap a larger payload throws in `ComputeShader::Dispatch`.
|
||||
|
||||
Set the environment variable `CRAFTER_GRAPHICS_NO_DESCRIPTOR_HEAP` to
|
||||
force the descriptor-set path on a driver that does have the heap; the
|
||||
`UIFallbackParity` test uses it to compare the two paths frame for
|
||||
frame.
|
||||
|
||||
## Build
|
||||
|
||||
The repository is built with `crafter-build` (a project-config based
|
||||
|
|
|
|||
Loading…
Reference in a new issue