Add computeStructuralHash for shader graphs#2976
Conversation
Introduce computeStructuralHash in MaterialXGenShader, a free function that produces a size_t hash of a finalized ShaderGraph capturing its topology and port types while ignoring instance-specific data (node/port names, parameter values, and texture paths). Two graphs that wire up the same node implementations in the same pattern hash identically even when their constants differ, enabling shader deduplication/caching and distinct-structure reporting across large material libraries. The function is an external visitor over the public API of ShaderGraph, ShaderNode, and ShaderPort: it never mutates the graph and folds node implementation hashes, classifications, port structure (type, semantic, colorspace, unit, geomprop, uniform/bind-input flags), and connection topology into a Boost/Hydra-style hash combiner. The hash is deterministic within a build but session-local: it relies on std::hash and implementation hashes, so it must not be persisted or compared across builds/versions, and it is a fast pre-filter rather than a collision-proof equivalence proof. Also adds: - Python binding (computeStructuralHash) in PyMaterialXGenShader. - A --dumpHash flag to generateshader.py. - A GLSL structural-hash test exercising generation and determinism.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new structural hashing utility for MaterialXGenShader shader graphs to support shader deduplication/caching by hashing graph topology and port structure while ignoring instance-specific details (names, values, paths).
Changes:
- Added
computeStructuralHash(const ShaderGraph&)inMaterialXGenShaderto hash finalized shader graph structure/topology. - Exposed the hash via Python (
PyMaterialXGenShader.computeStructuralHash(shader)), and added a--dumpHashCLI flag togenerateshader.py. - Added a GLSL-side Catch2 test to exercise structural hash determinism.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| source/PyMaterialX/PyMaterialXGenShader/PyUtil.cpp | Adds Python binding for structural hashing via a Shader wrapper function. |
| source/MaterialXTest/MaterialXGenGlsl/GenGlsl.cpp | Adds a Catch2 test that computes/prints structural hashes and checks determinism. |
| source/MaterialXGenShader/ShaderGraphHash.h | Declares the new public computeStructuralHash API. |
| source/MaterialXGenShader/ShaderGraphHash.cpp | Implements the structural hash over graph sockets, nodes, ports, and connections. |
| python/Scripts/generateshader.py | Adds --dumpHash to print the structural hash for generated shaders. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| std::string message; | ||
| bool docValid = doc->validate(&message); | ||
| if (!docValid) | ||
| { | ||
| continue; | ||
| } |
| mx::ShaderPtr shader; | ||
| try | ||
| { | ||
| shader = context.getShaderGenerator().generate(element->getName(), element, context); | ||
| } | ||
| catch (const std::exception&) | ||
| { | ||
| continue; | ||
| } |
| mx::ShaderPtr shader2; | ||
| try | ||
| { | ||
| shader2 = context.getShaderGenerator().generate(element->getName(), element, context); | ||
| } | ||
| catch (const std::exception&) | ||
| { | ||
| continue; | ||
| } |
| // Output to Catch2 INFO so it appears with -s flag | ||
| INFO(hashLog.str()); | ||
| SUCCEED(); | ||
| } |
kwokcb
left a comment
There was a problem hiding this comment.
@jstone-lucasfilm suggested adding enough API exposure for my PR for building graph diagrams from a Shader as seen in #2790.
Basically expose enough to allow you to build your own utilities.
I think this could be an approach to consider here as well vs an opaque non-customizable hash function. I think this could work:
- Add a "hash" class and provide your current implementation as one case.
- Expose it in the API so you can create custom ones in C++, Python or JS.
- e.g. in
generateshader.pyyou could even add a "hasher" option like the validator option for custom hashes.
This follows the approach for customizable entry points such as generators and loaders.
Thoughts ?
Introduce computeStructuralHash in MaterialXGenShader, a free function that produces a size_t hash of a finalized ShaderGraph capturing its topology and port types while ignoring instance-specific data (node/port names, parameter values, and texture paths). Two graphs that wire up the same node implementations in the same pattern hash identically even when their constants differ, enabling shader deduplication/caching and distinct-structure reporting across large material libraries.
The function is an external visitor over the public API of ShaderGraph, ShaderNode, and ShaderPort: it never mutates the graph and folds node implementation hashes, classifications, port structure (type, semantic, colorspace, unit, geomprop, uniform/bind-input flags), and connection topology into a Boost/Hydra-style hash combiner.
The hash is deterministic within a build but session-local: it relies on std::hash and implementation hashes, so it must not be persisted or compared across builds/versions, and it is a fast pre-filter rather than a collision-proof equivalence proof.
Also adds: