Blender Dependency Graph Architecture: Design, Boundaries, and Checks
Learn how Blender’s depsgraph separates construction from evaluation, enforces script sandboxing, and uses watchdogs to keep updates incremental—plus how to verify it in practice.
16 Feb 2026, 09:21 UTC

Blender Dependency Graph Architecture Note
Problem: When a scene contains many drivers or complex modifiers, you may notice occasional frame‑rate spikes because the entire dependency graph (depsgraph) is rebuilt each frame. Useful takeaway: By understanding the depsgraph’s two‑phase design, its trust boundaries, and the operational checks that guard incremental updates, you can isolate costly changes and use built‑in debug tools to confirm that only the affected data blocks are re‑evaluated.
Requirements
The depsgraph must satisfy three core requirements each frame:
- Correctness: All data blocks that depend on changed inputs reflect the new state before rendering.
- Efficiency: Only the minimal subset of IDs whose actual data changed should be re‑evaluated.
- Safety: User‑provided Python scripts and drivers cannot corrupt Blender’s internal memory or bypass API restrictions.
Minimal Suitable Design
The smallest design that meets these requirements separates work into two distinct phases:
- Graph Construction Phase: A topological sort of all ID datablocks (objects, meshes, materials, etc.) is performed, producing a directed acyclic graph where edges represent dependencies (e.g., an object’s location depends on its transform node). This phase is cheap because it works on lightweight ID references, not the heavy data they point to.
- Evaluation Phase: Each node in the graph executes its callback (modifier evaluation, driver expression, constraint solve) only if its
update_tagindicates that one of its inputs changed since the last frame. Nodes with no tag are skipped, achieving incremental updates.
Because construction and evaluation are decoupled, Blender can rebuild only the sub‑graph that is tagged for change, leaving the rest of the scene untouched.
Trust and Data Boundaries
All user‑supplied code—drivers, custom property getters/setters, and Python‑based modifiers—is treated as untrusted. The depsgraph enforces this by:
- Running driver expressions in a sandboxed interpreter that exposes only a whitelisted subset of the bpy API (e.g.,
mathutils,bpy.typesread‑only properties). - Preventing direct memory access or calls to unsafe C extensions; any attempt to import external modules raises an exception.
- Isolating the evaluation context per frame so that a driver cannot retain state across frames beyond what Blender explicitly stores in ID properties.
These boundaries ensure that a malicious or buggy script cannot crash the host or corrupt unrelated data blocks.
Operational Checks
To keep the incremental model reliable, the depsgraph runs several checks each frame:
- Validation Tags: Specific flags such as
OBJECT_TRANSFORM,MODIFIER_DATA, orDRIVER_VARIABLEare set on IDs when a relevant property changes. The evaluation phase consults these tags to decide whether to run a node’s callback. - Watchdog for Cycles: During construction, a depth‑first search detects back‑edges that would create a cycle (e.g., Object A drives B’s location, B drives A’s rotation). If a cycle is found, the watchdog logs a warning and forces a full graph rebuild for that frame to break the infinite loop.
- Rebuild Threshold: If the number of tagged IDs exceeds an internal heuristic (roughly a few hundred in typical scenes), the depsgraph opts for a full rebuild to avoid the overhead of managing many small incremental updates.
Failure Modes
Even with the above safeguards, certain conditions can degrade performance or correctness:
- Excessive Tagging: A driver that writes to many unrelated properties each frame can tag a large portion of the scene, pushing the rebuild threshold and causing a full‑graph reevaluation.
- Complex Node‑Based Modifiers: Geometry node trees that contain loops or heavy attribute transfers may produce expensive callbacks, making even incremental updates costly.
- Driver‑Induced Thrashing: A driver that reads a property it also writes (directly or indirectly) can cause continuous re‑tagging, leading to repeated partial rebuilds and jitter.
These failure modes manifest as spikes in the System Console’s “Dependency Graph” debug output or as visible frame‑rate drops in the viewport.
Conditions That Would Trigger a Redesign
The current two‑phase design would be reconsidered if any of the following became prevalent:
- Workflows where the majority of IDs are tagged each frame (e.g., real‑time procedural generation touching thousands of objects), making the incremental benefit negligible.
- Widespread use of unrestricted Python modules that require direct memory access, breaking the sandbox assumption.
- Hardware shifts where the cost of graph construction overtakes evaluation, suggesting a merge of phases or a move to a just‑in‑time compilation model.
Until such shifts occur, the separated construction/evaluation model remains the most suitable balance of correctness, efficiency, and safety.
Verification and Practical Checks
You can confirm the behavior described above using Blender’s built‑in debug tools and the Python API.
Enable Dependency Graph Debug Output
- Open Edit → Preferences → Editing → Debug.
- Check Show Dependency Graph.
- Open the Window → Toggle System Console (or start Blender from a terminal).
- Play the animation or scrub the timeline; watch the console for lines like:
depsgraph: rebuilt 3 IDs, tags: OBJECT_TRANSFORM|MODIFIER_DATA
Each line shows how many IDs were re‑evaluated and which validation tags triggered the rebuild. A low count indicates incremental updates; a count near the total number of IDs signals a full rebuild.
Inspect the Graph via Python
Run the following script in Blender’s Scripting → Python Console (no special permissions needed; it runs as the current user):
import bpy
# Get the evaluated depsgraph for the current scene
dg = bpy.context.evaluated_depsgraph_get()
print(f"Graph nodes: {len(dg.nodes)}")
for node in dg.nodes:
# node.update_tag is a bitmask; zero means no change since last frame
if node.update_tag:
print(f"{node.name}: tag {node.update_tag:#x}")
After making a change (e.g., moving an object), re‑run the script. Only the nodes whose tags changed should print non‑zero values. If many nodes print tags despite a minimal edit, you may be hitting the rebuild threshold or a cyclic‑dependency watchdog trigger.
Introduce a Deliberate Cycle to Observe the Watchdog
- Select Object A, add a driver to its Location X that uses Object B’s Rotation Y.
- Select Object B, add a driver to its Rotation Y that uses Object A’s Location X.
- Play the animation. In the System Console you should see a warning similar to:
depsgraph: cycle detected between Object A and Object B; forcing full rebuild
After the warning, the rebuild count will jump to the total number of IDs, confirming the watchdog’s fallback behavior.
Limitations
The verification methods above have constraints:
- The debug output can become verbose in heavy scenes; filter the console or pipe it to a file for easier review.
- The Python snapshot (
evaluated_depsgraph_get()) reflects the graph *after* evaluation for the current frame; to compare frames you must store the tag values yourself. - Introducing cycles for testing may cause temporary slowdowns; remove the test drivers after verification to avoid unintended side effects.
Despite these limits, the combination of debug tags, console logs, and API inspection provides a reliable way to confirm whether your scene is benefiting from incremental depsgraph updates or falling back to full rebuilds.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.