Context
The engine currently uses a single global bindless TextureArray (set=1, binding=0) shared by all consumers: scene textures, the ImGui font atlas, the viewport frame color, the fallback texture, and future content-browser thumbnails. All consumers draw from a single pool capped at `MaxGlobalTexture` (currently 1024), clamped at runtime to `maxPerStageDescriptorUpdateAfterBindSampledImages - 1`.
For a single scene this is adequate. For a large project with multiple scenes, hundreds of materials, and a populated content browser, the pool will be exhausted.
The problem at scale
| Consumer |
Slots (estimate) |
| Single large scene (Bistro) |
~500 |
| Content browser thumbnails (200 assets) |
~200 |
| Additional loaded scene |
~200 |
| Engine (font, frame, fallback) |
~5 |
| Total |
~905 — at 1024 limit |
Adding a second large scene, streaming multiple regions, or generating thumbnails for a 500-asset project pushes past 1024.
Design options to evaluate
Option A — Per-scene descriptor sets
Each scene gets its own texture descriptor set. The G-buffer pass switches sets per draw call or uses a push constant index. Requires VK_EXT_descriptor_indexing dynamic indexing across multiple sets.
Option B — Streaming descriptors (descriptor heap)
A fixed-size descriptor heap with LRU eviction. Textures are assigned slots at upload time and evicted when no draw command references them. Requires a reference-counting layer on top of GlobalTextures.
Option C — Separate pools per consumer type
Scene textures: one pool. UI/thumbnails: a second smaller pool (set=2). Each pool is independently sized. Requires shader changes to access two separate arrays.
Option D — Grow the pool dynamically
Query the device's actual limit (often 500,000+) and use a larger initial cap. Simplest short-term fix — avoids architectural change at the cost of descriptor pool memory.
Prerequisites
Notes
This is a design-only issue. No implementation until the right architecture is chosen. Should be discussed alongside the virtual geometry streaming design.
Context
The engine currently uses a single global bindless
TextureArray(set=1, binding=0) shared by all consumers: scene textures, the ImGui font atlas, the viewport frame color, the fallback texture, and future content-browser thumbnails. All consumers draw from a single pool capped at `MaxGlobalTexture` (currently 1024), clamped at runtime to `maxPerStageDescriptorUpdateAfterBindSampledImages - 1`.For a single scene this is adequate. For a large project with multiple scenes, hundreds of materials, and a populated content browser, the pool will be exhausted.
The problem at scale
Adding a second large scene, streaming multiple regions, or generating thumbnails for a 500-asset project pushes past 1024.
Design options to evaluate
Option A — Per-scene descriptor sets
Each scene gets its own texture descriptor set. The G-buffer pass switches sets per draw call or uses a push constant index. Requires
VK_EXT_descriptor_indexingdynamic indexing across multiple sets.Option B — Streaming descriptors (descriptor heap)
A fixed-size descriptor heap with LRU eviction. Textures are assigned slots at upload time and evicted when no draw command references them. Requires a reference-counting layer on top of
GlobalTextures.Option C — Separate pools per consumer type
Scene textures: one pool. UI/thumbnails: a second smaller pool (set=2). Each pool is independently sized. Requires shader changes to access two separate arrays.
Option D — Grow the pool dynamically
Query the device's actual limit (often 500,000+) and use a larger initial cap. Simplest short-term fix — avoids architectural change at the cost of descriptor pool memory.
Prerequisites
MaxGlobalTexture = 1024withstd::minruntime clamp buys time for typical projectsNotes
This is a design-only issue. No implementation until the right architecture is chosen. Should be discussed alongside the virtual geometry streaming design.