Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save shadeslayer/28f82bb7c22bc68ab23faccb6ad3a820 to your computer and use it in GitHub Desktop.

Select an option

Save shadeslayer/28f82bb7c22bc68ab23faccb6ad3a820 to your computer and use it in GitHub Desktop.
---
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