Created
June 30, 2026 14:05
-
-
Save shadeslayer/28f82bb7c22bc68ab23faccb6ad3a820 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| Bindless Sets in LayaAir — Potential Gain by Backend | |
| TL;DR | |
| The gain is highly backend-dependent. The LayaX (Vulkan/Metal) native path is where bindless pays off; the WebGL browser path gets nothing. | |
| --- | |
| Backend-by-Backend Breakdown | |
| WebGL (main browser path) — 0% gain | |
| Bindless doesn't exist in WebGL. The current hot path in WebGLRenderElement3D.ts:205 does up to 24 gl.bindTexture() calls per draw (17–24 textures across scene/shadow/camera/material slots). The only optimization available here is draw-call sorting and the existing _activeTextures[] cache. Nothing to do. | |
| LayaX (Vulkan/Metal) — 15–45% render-thread CPU reduction | |
| This is where bindless has real impact. The current model (LayaXBindGroupHelper.ts:79) uses 4 explicit descriptor sets: | |
| - Set 0: Scene/Shadow uniforms | |
| - Set 1: Camera uniforms | |
| - Set 2: Node/Sprite uniforms | |
| - Set 3: Material uniforms + textures ← changes every draw for unique materials | |
| Set 3 today requires a full descriptor write (12 VkWriteDescriptorSet entries for a PBR material) + vkCmdBindDescriptorSets per unique material per draw. In a scene with 200 draw calls and 70% unique materials, that's ~140 descriptor updates per frame. | |
| With bindless (VK_EXT_descriptor_indexing), all textures live in one large heap indexed at load time. Per-draw cost drops to one push-constant or UBO update with texture indices. Sets 0–2 are already UBO-friendly. | |
| ┌─────────────────────────────────────────────────────────────────────┬────────────────────────────────┐ | |
| │ Scene type │ Estimated gain │ | |
| ├─────────────────────────────────────────────────────────────────────┼────────────────────────────────┤ | |
| │ GPU-bound (heavy shaders, few objects) │ ~0% (CPU isn't the bottleneck) │ | |
| ├─────────────────────────────────────────────────────────────────────┼────────────────────────────────┤ | |
| │ Mixed, typical game scene (100–500 draws, ~50–70% unique materials) │ 15–25% render-thread CPU │ | |
| ├─────────────────────────────────────────────────────────────────────┼────────────────────────────────┤ | |
| │ CPU-bound, material-heavy (many unique draws) │ 30–45% render-thread CPU │ | |
| └─────────────────────────────────────────────────────────────────────┴────────────────────────────────┘ | |
| WebGPU (not yet implemented) — design-in from day one | |
| The WebGPU backend directory exists but is empty. If implemented naively, the createBindGroup per material problem that plagues early WebGPU engines would apply. Designing it with bindless (texture arrays + non-uniform indexing) from the start avoids that entirely — estimated 10–20% improvement over a naive WebGPU implementation. | |
| OpenGLES — conditional | |
| All binding is in C++ via Conch FFI. GL_EXT_bindless_texture is available on Adreno 630+, Mali G76+, but not universal on Android. Worth supporting as a capability path, not a baseline. | |
| --- | |
| Where to Actually Implement | |
| The work is contained to the LayaX driver layer: | |
| ┌─────────────────────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────┐ | |
| │ File │ What changes │ | |
| ├─────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────┤ | |
| │ LayaXDriver/RenderDevice/LayaXBindGroupHelper.ts:79–155 │ Add bindless descriptor layout variant — one large VkDescriptorSetLayout with PARTIALLY_BOUND │ | |
| │ │ + UPDATE_AFTER_BIND flags │ | |
| ├─────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────┤ | |
| │ LayaXDriver/RenderDevice/ShaderGenerate/LayaX_GLSLForVulkanGenerator.ts │ Rewrite texture bindings from layout(set=3, binding=N) to index lookups into bindless heap │ | |
| ├─────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────┤ | |
| │ LayaXDriver/RenderModuleData/LayaXShaderPass.ts:141 │ Parse and communicate texture indices instead of per-set texture entries │ | |
| ├─────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────┤ | |
| │ Rust/C++ backend │ Allocate and manage the descriptor heap; expose texture handle registration API to TS │ | |
| └─────────────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────┘ | |
| The TS-side descriptor set structure ("0:Scene3D,Global;1:BaseCamera;2:Sprite3D;3:PBRStandard") already cleanly separates material from scene/camera, so Set 3 can be replaced with a bindless index UBO without touching Sets 0–2. | |
| --- | |
| Bottom Line | |
| If your primary target is the LayaX native path on Vulkan, implementing bindless is worth it for material-diverse scenes — expect a 20–30% real-world render-thread CPU gain in typical 3D game workloads. If the scene is GPU-bound or most objects share a small set of materials (because GPU instancing already batches them), the gain drops significantly. WebGL gets nothing from this work. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment