The layer carries what the driver used to
In older graphics APIs — Direct3D 9 is the clearest example — the driver is stateful. You call SetRenderState(D3DRS_ZENABLE, TRUE) and depth testing is on. It stays on. Every subsequent draw call inherits that state unless something explicitly changes it. The API remembers, and the program leans on that memory constantly.
Vulkan and Metal do not work this way. Both are built around pipeline state objects: immutable bundles that bake together the blend mode, depth function, rasteriser configuration and shader pair into a single compiled object before the first draw call is issued. Nothing persists between pipeline objects. Nothing is assumed. If you want depth testing on, it must be explicit in the pipeline object you recorded it into, every time.
What changes between API generations
FROM THIS ENTRY| Stateful API | driver retains all render state between calls; program sets values and they persist |
|---|---|
| Pipeline state object | immutable, precompiled bundle of all state; nothing inherited between objects |
| State flush | the moment the layer snapshots its CPU-side state struct and resolves it into a pipeline object |
The compatibility layer sits in the gap between these two contracts. When a Direct3D 9 program calls SetRenderState, the layer cannot forward that call anywhere — there is no equivalent receptacle. Instead it keeps its own copy of the entire render state in CPU-side memory: a struct that holds every flag, every blend factor, every stencil operation. Every state-setting call updates that struct. Every draw call inspects it, hashes the result, and either retrieves a previously compiled pipeline object from a cache or compiles a new one on the spot.
That hash-and-lookup step is where one API's model is translated into another's. It is also where the cost lives. A hash over dozens of state fields is cheap in isolation, but it happens once per draw call, and a game submitting tens of thousands of draw calls per frame accumulates those microseconds. The cache hit rate matters: a miss means handing a new pipeline description to the driver, which means shader compilation stutter if the underlying pipeline has not been seen before.

State tracking also creates correctness hazards that are harder to see than the performance ones. The older API allows setting render target, then setting blend state, then drawing — in any order within a frame, with the driver sorting out the dependency. The layer must impose that same apparent order onto a command buffer that has no notion of stateful dependency at all. Get the ordering of state flushes wrong and the draw call executes against a pipeline object that was built from a snapshot of state taken one call too early. The visual result is usually wrong blending or missing geometry, and the cause is an ordering bug in the layer's own bookkeeping rather than anything the program did incorrectly.
The older API remembered so the programmer did not have to. The layer now carries that memory instead, and it pays for the privilege on every frame.
Where the cost appears
FROM THIS ENTRY| Per-draw-call hashing | small per call, significant at tens of thousands of draw calls per frame |
|---|---|
| Cache miss penalty | triggers driver-level pipeline compilation; the source of compilation stutter mid-frame |
| Ordering hazards | incorrect flush timing produces wrong blending or missing geometry, not a crash |

