Resource Aliasing & Memory
Transient resources with non-overlapping lifetimes share GPU memory. The render graph works this out automatically. The point is to keep the VRAM footprint bounded as the pipeline grows.
The VRAM Problem
A real pipeline uses fifteen-plus intermediate textures: shadow maps, SSAO buffers, bloom mip chains, SSR buffers, selection masks, and so on. A single Rgba16Float texture at 1080p is about 16 MB. At 4K it is 64 MB. Without aliasing, every one of those textures sits in VRAM whether it is in use or not.
Resource aliasing is the GPU equivalent of stack allocation. The same memory region gets reused by different variables (textures) whose lifetimes do not overlap. The graph's execution order gives a total ordering of passes, which is exactly what is needed to compute precise lifetimes and find aliasing opportunities.
The technique comes from the Frostbite frame graph (GDC 2017) and is standard practice in production engines. The savings run 30 to 50 percent of transient VRAM depending on the pipeline.
How It Works
After the topological sort, the graph knows the execution order. For every transient it computes two pass indices.
first_useis the pass where the resource is first written.last_useis the pass where the resource is last read.
If resource A's last_use is before resource B's first_use, and they have compatible descriptors, they can share the same GPU texture.
Pass 0: writes A
Pass 1: reads A, writes B
Pass 2: reads B, writes C <- A's memory can be reused for C
Pass 3: reads C, writes output
A's lifetime is [0, 1]. C's lifetime is [2, 3]. They do not overlap. If the descriptors match, the graph assigns them to the same pool slot.
Compatibility Requirements
Two textures alias when every one of the following matches.
- Format
- Width and height
- Sample count
- Mip level count
- The reuser's usage flags are a subset of the pool's usage flags
Two buffers alias when the pool's size is at least the reuser's size and the usage flags match exactly.
If a reuser needs usage flags the pool texture does not have, the pool texture is recreated with the combined flags. The reuser still gets to share the slot.
Pool-Based Allocation
The allocator is a pool keyed by a BinaryHeap ordered by lifetime_end. The heap gives constant-time access to the slot that frees up earliest.
- Sort transient resources by
first_use. - For each resource, check whether any pool slot has a
lifetime_endstrictly before this resource'sfirst_use. If a compatible slot is found, reuse it. Otherwise allocate a new slot. - Each pool slot holds at most one GPU texture or buffer at any moment.
Min-heap on lifetime_end means the earliest-expiring slot is the one checked first, which is the right order for greedy reuse.
Bind Group Invalidation
When a transient gets a new GPU texture (the pool slot was reallocated), every pass that references that resource needs to rebuild its bind groups.
The graph keeps a version number per resource. When a resource's version changes between frames:
- Find every pass that reads, writes, or
reads_writesthe resource. - Call
invalidate_bind_groups()on those passes. - Update the stored version.
This is what keeps passes from accidentally binding the old GPU handle after an aliasing change or a window resize.
Memory Savings
A scene with fifteen-plus transient textures can shrink dramatically once aliasing is applied. Some concrete examples:
ssao_rawandssgi_raware usually never live at the same time.- Shadow depth maps are only live during shadow passes. After that, their memory is free.
- Intermediate blur textures from bloom can share memory with SSR blur textures.
The exact savings depend on the pass ordering and the resource sizes.
External Resources
External resources are never aliased. They are owned outside the graph and provided fresh each frame. The swapchain texture is the obvious case. Editor viewport outputs are the other.