mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
Optimize traversal and reduce string allocations in Stage and Tydra APIs
Performance optimizations for path traversal and prim lookup: Stage (stage.cc): - Replace recursive GetPrimAtPathRec with iterative GetPrimAtPathIterative - Avoid string concatenation by using compare() with indices - Direct path component parsing without intermediate allocations Scene Access (scene-access.cc): - Replace recursive TraverseRec with iterative TraverseIterative - Use explicit stack with StackVector to avoid recursion overhead - Reuse path buffer across traversal to minimize string allocations - Reserve capacity for path buffer (256) and stack (64) Additional optimizations: - layer.cc: Improved path handling - prim-types.cc: Optimized type operations - usd-to-json.cc: Better JSON serialization - usdGeom.cc, usdMtlx.cc: Enhanced prim reconstruction 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
282
src/layer.cc
282
src/layer.cc
@@ -5,6 +5,7 @@
|
||||
#include "prim-types.hh" // For PrimSpec, LayerMetas, etc.
|
||||
#include "path-util.hh" // For Path
|
||||
#include "str-util.hh" // For split function
|
||||
#include "tiny-container.hh"
|
||||
#include "common-macros.inc"
|
||||
#include "tiny-format.hh"
|
||||
|
||||
@@ -16,169 +17,163 @@ namespace tinyusdz {
|
||||
|
||||
namespace {
|
||||
|
||||
// Forward declare helper functions
|
||||
bool HasReferencesRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
|
||||
bool HasPayloadRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
|
||||
bool HasVariantRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
|
||||
bool HasInheritsRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
|
||||
bool HasSpecializesRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
|
||||
bool HasOverRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
|
||||
nonstd::optional<const PrimSpec *> GetPrimSpecAtPathRec(
|
||||
const PrimSpec *parent, const std::string &parent_path, const Path &path,
|
||||
uint32_t depth);
|
||||
|
||||
bool HasReferencesRec(uint32_t depth, const PrimSpec &primspec,
|
||||
const uint32_t max_depth = 1024 * 128) {
|
||||
if (depth > max_depth) {
|
||||
// too deep
|
||||
return false;
|
||||
}
|
||||
|
||||
if (primspec.metas().references) {
|
||||
// Generic iterative tree search using explicit stack
|
||||
// Returns true if any PrimSpec satisfies the predicate
|
||||
template <typename Predicate>
|
||||
bool HasPrimSpecWithCondition(const PrimSpec &root, Predicate pred) {
|
||||
// Check root first
|
||||
if (pred(root)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto &child : primspec.children()) {
|
||||
if (HasReferencesRec(depth + 1, child, max_depth)) {
|
||||
// Use explicit stack for DFS traversal (StackVector for stack allocation)
|
||||
StackVector<std::pair<const PrimSpec *, size_t>, 4> stack;
|
||||
stack.reserve(64);
|
||||
|
||||
if (!root.children().empty()) {
|
||||
stack.emplace_back(&root, 0);
|
||||
}
|
||||
|
||||
while (!stack.empty()) {
|
||||
auto &top = stack.back();
|
||||
const PrimSpec *current = top.first;
|
||||
size_t &child_idx = top.second;
|
||||
|
||||
if (child_idx >= current->children().size()) {
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
const PrimSpec &child = current->children()[child_idx];
|
||||
++child_idx;
|
||||
|
||||
if (pred(child)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!child.children().empty()) {
|
||||
stack.emplace_back(&child, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasPayloadRec(uint32_t depth, const PrimSpec &primspec,
|
||||
const uint32_t max_depth = 1024 * 128) {
|
||||
if (depth > max_depth) {
|
||||
// too deep
|
||||
return false;
|
||||
}
|
||||
|
||||
if (primspec.metas().payload) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto &child : primspec.children()) {
|
||||
if (HasPayloadRec(depth + 1, child, max_depth)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
// Iterative predicates for each Has* function
|
||||
bool HasReferencesIterative(const PrimSpec &primspec) {
|
||||
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
|
||||
return ps.metas().references.has_value();
|
||||
});
|
||||
}
|
||||
|
||||
bool HasVariantRec(uint32_t depth, const PrimSpec &primspec,
|
||||
const uint32_t max_depth = 1024 * 128) {
|
||||
if (depth > max_depth) {
|
||||
// too deep
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Also check if PrimSpec::variantSets is empty?
|
||||
if (primspec.metas().variants && primspec.metas().variantSets) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto &child : primspec.children()) {
|
||||
if (HasVariantRec(depth + 1, child, max_depth)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
bool HasPayloadIterative(const PrimSpec &primspec) {
|
||||
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
|
||||
return ps.metas().payload.has_value();
|
||||
});
|
||||
}
|
||||
|
||||
bool HasInheritsRec(uint32_t depth, const PrimSpec &primspec,
|
||||
const uint32_t max_depth = 1024 * 128) {
|
||||
if (depth > max_depth) {
|
||||
// too deep
|
||||
return false;
|
||||
}
|
||||
|
||||
if (primspec.metas().inherits) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto &child : primspec.children()) {
|
||||
if (HasInheritsRec(depth + 1, child, max_depth)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
bool HasVariantIterative(const PrimSpec &primspec) {
|
||||
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
|
||||
// TODO: Also check if PrimSpec::variantSets is empty?
|
||||
return ps.metas().variants.has_value() && ps.metas().variantSets.has_value();
|
||||
});
|
||||
}
|
||||
|
||||
bool HasSpecializesRec(uint32_t depth, const PrimSpec &primspec,
|
||||
const uint32_t max_depth = 1024 * 128) {
|
||||
if (depth > max_depth) {
|
||||
// too deep
|
||||
return false;
|
||||
}
|
||||
|
||||
if (primspec.metas().specializes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto &child : primspec.children()) {
|
||||
if (HasSpecializesRec(depth + 1, child, max_depth)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
bool HasInheritsIterative(const PrimSpec &primspec) {
|
||||
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
|
||||
return ps.metas().inherits.has_value();
|
||||
});
|
||||
}
|
||||
|
||||
bool HasOverRec(uint32_t depth, const PrimSpec &primspec,
|
||||
const uint32_t max_depth = 1024 * 128) {
|
||||
if (depth > max_depth) {
|
||||
// too deep
|
||||
return false;
|
||||
}
|
||||
|
||||
if (primspec.specifier() == Specifier::Over) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto &child : primspec.children()) {
|
||||
if (HasOverRec(depth + 1, child, max_depth)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
bool HasSpecializesIterative(const PrimSpec &primspec) {
|
||||
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
|
||||
return ps.metas().specializes.has_value();
|
||||
});
|
||||
}
|
||||
|
||||
nonstd::optional<const PrimSpec *> GetPrimSpecAtPathRec(
|
||||
const PrimSpec *parent, const std::string &parent_path, const Path &path,
|
||||
uint32_t depth) {
|
||||
if (depth > (1024 * 1024 * 128)) {
|
||||
// Too deep.
|
||||
bool HasOverIterative(const PrimSpec &primspec) {
|
||||
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
|
||||
return ps.specifier() == Specifier::Over;
|
||||
});
|
||||
}
|
||||
|
||||
// Optimized iterative path lookup starting from a single root PrimSpec
|
||||
// Uses direct path component matching without string allocation
|
||||
nonstd::optional<const PrimSpec *> GetPrimSpecAtPathFromRoot(
|
||||
const PrimSpec &root, const Path &path) {
|
||||
const std::string &target_path = path.full_path_name();
|
||||
|
||||
// Must be absolute path starting with '/'
|
||||
if (target_path.empty() || target_path[0] != '/') {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
if (!parent) {
|
||||
// Root path "/" has no primspec
|
||||
if (target_path.size() == 1) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
std::string abs_path;
|
||||
{
|
||||
std::string elementName = parent->name();
|
||||
// Parse first component to check if it matches root
|
||||
size_t start = 1; // skip leading '/'
|
||||
const size_t len = target_path.size();
|
||||
|
||||
abs_path = parent_path + "/" + elementName;
|
||||
|
||||
if (abs_path == path.full_path_name()) {
|
||||
return parent;
|
||||
}
|
||||
size_t end = start;
|
||||
while (end < len && target_path[end] != '/') {
|
||||
++end;
|
||||
}
|
||||
|
||||
for (const auto &child : parent->children()) {
|
||||
if (auto pv = GetPrimSpecAtPathRec(&child, abs_path, path, depth + 1)) {
|
||||
return pv.value();
|
||||
}
|
||||
// Check if first component matches root name
|
||||
const size_t first_len = end - start;
|
||||
const std::string &root_name = root.name();
|
||||
if (root_name.size() != first_len ||
|
||||
target_path.compare(start, first_len, root_name) != 0) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
// not found
|
||||
return nonstd::nullopt;
|
||||
// If path is just the root, return it
|
||||
if (end >= len) {
|
||||
return &root;
|
||||
}
|
||||
|
||||
// Navigate down the tree
|
||||
const PrimSpec *current = &root;
|
||||
start = end + 1;
|
||||
|
||||
while (start < len) {
|
||||
// Find end of current component
|
||||
end = start;
|
||||
while (end < len && target_path[end] != '/') {
|
||||
++end;
|
||||
}
|
||||
|
||||
if (end == start) {
|
||||
// Empty component (double slash), skip
|
||||
start = end + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Search for matching child
|
||||
const PrimSpec *found = nullptr;
|
||||
const size_t component_len = end - start;
|
||||
|
||||
for (const auto &child : current->children()) {
|
||||
const std::string &name = child.name();
|
||||
if (name.size() == component_len &&
|
||||
target_path.compare(start, component_len, name) == 0) {
|
||||
found = &child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
current = found;
|
||||
start = end + 1;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
// Helper function to estimate PrimSpec memory usage
|
||||
@@ -468,10 +463,9 @@ bool Layer::find_primspec_at(const Path &path, const PrimSpec **ps,
|
||||
}
|
||||
}
|
||||
|
||||
// Brute-force search.
|
||||
// Direct path-based lookup (iterative, no string allocation)
|
||||
for (const auto &parent : _impl->_prim_specs) {
|
||||
if (auto pv = GetPrimSpecAtPathRec(&parent.second, /* parent_path */ "",
|
||||
path, /* depth */ 0)) {
|
||||
if (auto pv = GetPrimSpecAtPathFromRoot(parent.second, path)) {
|
||||
(*ps) = pv.value();
|
||||
|
||||
// Add to cache.
|
||||
@@ -487,10 +481,11 @@ bool Layer::find_primspec_at(const Path &path, const PrimSpec **ps,
|
||||
}
|
||||
|
||||
bool Layer::check_unresolved_references(const uint32_t max_depth) const {
|
||||
(void)max_depth; // Not needed for iterative version
|
||||
bool ret = false;
|
||||
|
||||
for (const auto &item : _impl->_prim_specs) {
|
||||
if (HasReferencesRec(/* depth */ 0, item.second, max_depth)) {
|
||||
if (HasReferencesIterative(item.second)) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
@@ -501,10 +496,11 @@ bool Layer::check_unresolved_references(const uint32_t max_depth) const {
|
||||
}
|
||||
|
||||
bool Layer::check_unresolved_payload(const uint32_t max_depth) const {
|
||||
(void)max_depth; // Not needed for iterative version
|
||||
bool ret = false;
|
||||
|
||||
for (const auto &item : _impl->_prim_specs) {
|
||||
if (HasPayloadRec(/* depth */ 0, item.second, max_depth)) {
|
||||
if (HasPayloadIterative(item.second)) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
@@ -515,10 +511,11 @@ bool Layer::check_unresolved_payload(const uint32_t max_depth) const {
|
||||
}
|
||||
|
||||
bool Layer::check_unresolved_variant(const uint32_t max_depth) const {
|
||||
(void)max_depth; // Not needed for iterative version
|
||||
bool ret = false;
|
||||
|
||||
for (const auto &item : _impl->_prim_specs) {
|
||||
if (HasVariantRec(/* depth */ 0, item.second, max_depth)) {
|
||||
if (HasVariantIterative(item.second)) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
@@ -529,10 +526,11 @@ bool Layer::check_unresolved_variant(const uint32_t max_depth) const {
|
||||
}
|
||||
|
||||
bool Layer::check_unresolved_inherits(const uint32_t max_depth) const {
|
||||
(void)max_depth; // Not needed for iterative version
|
||||
bool ret = false;
|
||||
|
||||
for (const auto &item : _impl->_prim_specs) {
|
||||
if (HasInheritsRec(/* depth */ 0, item.second, max_depth)) {
|
||||
if (HasInheritsIterative(item.second)) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
@@ -543,10 +541,11 @@ bool Layer::check_unresolved_inherits(const uint32_t max_depth) const {
|
||||
}
|
||||
|
||||
bool Layer::check_unresolved_specializes(const uint32_t max_depth) const {
|
||||
(void)max_depth; // Not needed for iterative version
|
||||
bool ret = false;
|
||||
|
||||
for (const auto &item : _impl->_prim_specs) {
|
||||
if (HasSpecializesRec(/* depth */ 0, item.second, max_depth)) {
|
||||
if (HasSpecializesIterative(item.second)) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
@@ -557,10 +556,11 @@ bool Layer::check_unresolved_specializes(const uint32_t max_depth) const {
|
||||
}
|
||||
|
||||
bool Layer::check_over_primspec(const uint32_t max_depth) const {
|
||||
(void)max_depth; // Not needed for iterative version
|
||||
bool ret = false;
|
||||
|
||||
for (const auto &item : _impl->_prim_specs) {
|
||||
if (HasOverRec(/* depth */ 0, item.second, max_depth)) {
|
||||
if (HasOverIterative(item.second)) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
#include "prim-types.hh"
|
||||
#include "str-util.hh"
|
||||
#include "tiny-container.hh"
|
||||
#include "tiny-format.hh"
|
||||
//
|
||||
#include "usdGeom.hh"
|
||||
@@ -1547,48 +1548,52 @@ bool GetCustomDataByKey(const CustomDataType &custom, const std::string &key,
|
||||
|
||||
namespace {
|
||||
|
||||
bool OverrideCustomDataRec(uint32_t depth, CustomDataType &dst,
|
||||
const CustomDataType &src, const bool override_existing) {
|
||||
if (depth > (1024 * 1024 * 128)) {
|
||||
// too deep
|
||||
return false;
|
||||
}
|
||||
// Iterative version of dictionary override using explicit stack
|
||||
// Avoids recursion for deeply nested dictionary structures
|
||||
void OverrideCustomDataIterative(CustomDataType &dst, const CustomDataType &src,
|
||||
const bool override_existing) {
|
||||
// Stack of pairs: (dst_dict pointer, src_dict pointer)
|
||||
StackVector<std::pair<CustomDataType *, const CustomDataType *>, 4> stack;
|
||||
stack.reserve(16);
|
||||
|
||||
for (const auto &item : src) {
|
||||
if (dst.count(item.first)) {
|
||||
if (override_existing) {
|
||||
CustomDataType *dst_dict =
|
||||
dst.at(item.first).get_raw_value().as<CustomDataType>();
|
||||
// Start with the root dictionaries
|
||||
stack.emplace_back(&dst, &src);
|
||||
|
||||
const value::Value &src_data = item.second.get_raw_value();
|
||||
const CustomDataType *src_dict = src_data.as<CustomDataType>();
|
||||
while (!stack.empty()) {
|
||||
auto current = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
//
|
||||
// Recursively apply override op both types are dict.
|
||||
//
|
||||
if (src_dict && dst_dict) {
|
||||
// recursively override dict
|
||||
if (!OverrideCustomDataRec(depth + 1, (*dst_dict), (*src_dict), override_existing)) {
|
||||
return false;
|
||||
CustomDataType *current_dst = current.first;
|
||||
const CustomDataType *current_src = current.second;
|
||||
|
||||
for (const auto &item : *current_src) {
|
||||
if (current_dst->count(item.first)) {
|
||||
if (override_existing) {
|
||||
CustomDataType *dst_dict =
|
||||
current_dst->at(item.first).get_raw_value().as<CustomDataType>();
|
||||
|
||||
const value::Value &src_data = item.second.get_raw_value();
|
||||
const CustomDataType *src_dict = src_data.as<CustomDataType>();
|
||||
|
||||
// If both are dicts, push to stack for later processing
|
||||
if (src_dict && dst_dict) {
|
||||
stack.emplace_back(dst_dict, src_dict);
|
||||
} else {
|
||||
(*current_dst)[item.first] = item.second;
|
||||
}
|
||||
|
||||
} else {
|
||||
dst[item.first] = item.second;
|
||||
}
|
||||
} else {
|
||||
// add dict value
|
||||
current_dst->emplace(item.first, item.second);
|
||||
}
|
||||
} else {
|
||||
// add dict value
|
||||
dst.emplace(item.first, item.second);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void OverrideDictionary(CustomDataType &dst, const CustomDataType &src, const bool override_existing) {
|
||||
OverrideCustomDataRec(0, dst, src, override_existing);
|
||||
OverrideCustomDataIterative(dst, src, override_existing);
|
||||
}
|
||||
|
||||
AssetInfo PrimMeta::get_assetInfo(bool *is_authored) const {
|
||||
|
||||
369
src/stage.cc
369
src/stage.cc
@@ -27,6 +27,7 @@
|
||||
#include "prim-pprint.hh"
|
||||
#include "prim-pprint-parallel.hh"
|
||||
#include "str-util.hh"
|
||||
#include "tiny-container.hh"
|
||||
#include "tiny-format.hh"
|
||||
#include "tinyusdz.hh"
|
||||
#include "usdLux.hh"
|
||||
@@ -46,44 +47,69 @@ namespace tinyusdz {
|
||||
|
||||
namespace {
|
||||
|
||||
nonstd::optional<const Prim *> GetPrimAtPathRec(const Prim *parent,
|
||||
const std::string &parent_path,
|
||||
const Path &path,
|
||||
const uint32_t depth) {
|
||||
// Optimized version: iterative traversal using path components
|
||||
// Avoids string concatenation and recursion by directly navigating to target
|
||||
nonstd::optional<const Prim *> GetPrimAtPathIterative(
|
||||
const std::vector<Prim> &root_nodes,
|
||||
const Path &path) {
|
||||
|
||||
if (!parent) {
|
||||
const std::string &target_path = path.full_path_name();
|
||||
|
||||
// Must be absolute path starting with '/'
|
||||
if (target_path.empty() || target_path[0] != '/') {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
std::string abs_path;
|
||||
// if (auto pv = GetPrimElementName(parent->data())) {
|
||||
{
|
||||
std::string elementName = parent->element_path().prim_part();
|
||||
// DCOUT(pprint::Indent(depth) << "Prim elementName = " << elementName);
|
||||
// DCOUT(pprint::Indent(depth) << "Given Path = " << path);
|
||||
// fully absolute path
|
||||
abs_path = parent_path + "/" + elementName;
|
||||
// DCOUT(pprint::Indent(depth) << "abs_path = " << abs_path);
|
||||
// DCOUT(pprint::Indent(depth)
|
||||
// << "queriying path = " << path.full_path_name());
|
||||
if (abs_path == path.full_path_name()) {
|
||||
// DCOUT(pprint::Indent(depth)
|
||||
// << "Got it! Found Prim at Path = " << abs_path);
|
||||
return parent;
|
||||
}
|
||||
// For paths like "/" only (root path), we don't have a prim
|
||||
if (target_path.size() == 1) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
// DCOUT(pprint::Indent(depth)
|
||||
// << "# of children : " << parent->children().size());
|
||||
for (const auto &child : parent->children()) {
|
||||
// const std::string &p = parent->elementPath.full_path_name();
|
||||
// DCOUT(pprint::Indent(depth + 1) << "Parent path : " << abs_path);
|
||||
if (auto pv = GetPrimAtPathRec(&child, abs_path, path, depth + 1)) {
|
||||
return pv.value();
|
||||
// Iteratively parse and traverse path components
|
||||
// No string allocations - uses compare() with indices
|
||||
const Prim *current = nullptr;
|
||||
const std::vector<Prim> *current_children = &root_nodes;
|
||||
|
||||
size_t start = 1; // skip leading '/'
|
||||
const size_t len = target_path.size();
|
||||
|
||||
while (start < len) {
|
||||
// Find end of current component
|
||||
size_t end = start;
|
||||
while (end < len && target_path[end] != '/') {
|
||||
++end;
|
||||
}
|
||||
|
||||
if (end == start) {
|
||||
// Empty component (double slash), skip
|
||||
start = end + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Search for matching child using direct string comparison
|
||||
// No string allocation - compare against substring
|
||||
const Prim *found = nullptr;
|
||||
const size_t component_len = end - start;
|
||||
|
||||
for (const auto &child : *current_children) {
|
||||
const std::string &name = child.element_name();
|
||||
if (name.size() == component_len &&
|
||||
target_path.compare(start, component_len, name) == 0) {
|
||||
found = &child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
current = found;
|
||||
current_children = ¤t->children();
|
||||
start = end + 1;
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
return current;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -130,15 +156,12 @@ nonstd::expected<const Prim *, std::string> Stage::GetPrimAtPath(
|
||||
}
|
||||
|
||||
|
||||
// Brute-force search.
|
||||
for (const auto &parent : _root_nodes) {
|
||||
if (auto pv =
|
||||
GetPrimAtPathRec(&parent, /* root */ "", path, /* depth */ 0)) {
|
||||
// Add to cache.
|
||||
// Assume pointer address does not change unless dirty state.
|
||||
_prim_path_cache[path.prim_part()] = pv.value();
|
||||
return pv.value();
|
||||
}
|
||||
// Direct path-based lookup (no brute-force search)
|
||||
if (auto pv = GetPrimAtPathIterative(_root_nodes, path)) {
|
||||
// Add to cache.
|
||||
// Assume pointer address does not change unless dirty state.
|
||||
_prim_path_cache[path.prim_part()] = pv.value();
|
||||
return pv.value();
|
||||
}
|
||||
|
||||
DCOUT("Not found.");
|
||||
@@ -181,36 +204,61 @@ bool Stage::find_prim_at_path(const Path &path, int64_t *prim_id,
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool FindPrimByPrimIdRec(uint64_t prim_id, const Prim *root,
|
||||
const Prim **primFound, int level, std::string *err) {
|
||||
if (level > 1024 * 1024 * 128) {
|
||||
// too deep node.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optimized iterative version using explicit stack
|
||||
// Avoids recursion to save stack memory for deep hierarchies
|
||||
static bool FindPrimByPrimIdIterative(uint64_t prim_id,
|
||||
const std::vector<Prim> &root_nodes,
|
||||
const Prim **primFound) {
|
||||
if (!primFound) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (root->prim_id() == int64_t(prim_id)) {
|
||||
(*primFound) = root;
|
||||
return true;
|
||||
// Use explicit stack for DFS traversal (StackVector for stack allocation)
|
||||
// Store pointer to prim and current child index
|
||||
StackVector<std::pair<const Prim *, size_t>, 4> stack;
|
||||
stack.reserve(64); // Pre-allocate for typical depth
|
||||
|
||||
// Initialize stack with root nodes
|
||||
for (const auto &root : root_nodes) {
|
||||
if (root.prim_id() == int64_t(prim_id)) {
|
||||
(*primFound) = &root;
|
||||
return true;
|
||||
}
|
||||
if (!root.children().empty()) {
|
||||
stack.emplace_back(&root, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Brute-force search.
|
||||
for (const auto &child : root->children()) {
|
||||
if (FindPrimByPrimIdRec(prim_id, &child, primFound, level + 1, err)) {
|
||||
// Iterative DFS
|
||||
while (!stack.empty()) {
|
||||
auto &top = stack.back();
|
||||
const Prim *current = top.first;
|
||||
size_t &child_idx = top.second;
|
||||
|
||||
if (child_idx >= current->children().size()) {
|
||||
// All children processed, backtrack
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
const Prim &child = current->children()[child_idx];
|
||||
++child_idx; // Move to next child for when we return
|
||||
|
||||
// Check if this is the target
|
||||
if (child.prim_id() == int64_t(prim_id)) {
|
||||
(*primFound) = &child;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Push child to stack if it has children
|
||||
if (!child.children().empty()) {
|
||||
stack.emplace_back(&child, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Stage::find_prim_by_prim_id(const uint64_t prim_id, const Prim *&prim,
|
||||
std::string *err) const {
|
||||
if (prim_id < 1) {
|
||||
@@ -237,12 +285,10 @@ bool Stage::find_prim_by_prim_id(const uint64_t prim_id, const Prim *&prim,
|
||||
}
|
||||
|
||||
const Prim *p{nullptr};
|
||||
for (const auto &root : root_prims()) {
|
||||
if (FindPrimByPrimIdRec(prim_id, &root, &p, 0, err)) {
|
||||
_prim_id_cache[prim_id] = p;
|
||||
prim = p;
|
||||
return true;
|
||||
}
|
||||
if (FindPrimByPrimIdIterative(prim_id, _root_nodes, &p)) {
|
||||
_prim_id_cache[prim_id] = p;
|
||||
prim = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -577,49 +623,98 @@ bool Stage::has_prim_id(const uint64_t prim_id) const {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ComputeAbsPathAndAssignPrimIdRec(const Stage &stage, Prim &prim,
|
||||
const Path &parentPath, uint32_t depth,
|
||||
bool assign_prim_id,
|
||||
bool force_assign_prim_id = true,
|
||||
std::string *err = nullptr) {
|
||||
if (depth > 1024 * 1024 * 128) {
|
||||
// too deep node.
|
||||
if (err) {
|
||||
(*err) += "Prim hierarchy too deep.\n";
|
||||
// Iterative version of ComputeAbsPathAndAssignPrimIdRec
|
||||
// Uses explicit stack to avoid recursion
|
||||
bool ComputeAbsPathAndAssignPrimIdIterative(const Stage &stage,
|
||||
std::vector<Prim> &root_prims,
|
||||
bool assign_prim_id,
|
||||
bool force_assign_prim_id,
|
||||
std::string *err) {
|
||||
// Stack entry: (prim pointer, parent path, child index)
|
||||
struct StackEntry {
|
||||
Prim *prim;
|
||||
Path parent_path;
|
||||
size_t child_idx;
|
||||
|
||||
StackEntry(Prim *p, Path pp) : prim(p), parent_path(std::move(pp)), child_idx(0) {}
|
||||
};
|
||||
|
||||
StackVector<StackEntry, 4> stack;
|
||||
stack.reserve(64);
|
||||
|
||||
Path root_path("/", "");
|
||||
|
||||
// Process each root prim
|
||||
for (Prim &root : root_prims) {
|
||||
if (root.element_name().empty()) {
|
||||
if (err) {
|
||||
(*err) += "Prim's elementName is empty. Prim's parent Path = /\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prim.element_name().empty()) {
|
||||
// Prim's elementName must not be empty.
|
||||
if (err) {
|
||||
(*err) += "Prim's elementName is empty. Prim's parent Path = " +
|
||||
parentPath.full_path_name() + "\n";
|
||||
// Compute path and assign ID for root
|
||||
Path abs_path = root_path.AppendPrim(root.element_name());
|
||||
root.absolute_path() = abs_path;
|
||||
|
||||
if (assign_prim_id) {
|
||||
if (force_assign_prim_id || (root.prim_id() < 1)) {
|
||||
uint64_t prim_id{0};
|
||||
if (!stage.allocate_prim_id(&prim_id)) {
|
||||
if (err) {
|
||||
(*err) += "Failed to assign unique Prim ID.\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
root.prim_id() = int64_t(prim_id);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Path abs_path = parentPath.AppendPrim(prim.element_name());
|
||||
if (!root.children().empty()) {
|
||||
stack.emplace_back(&root, abs_path);
|
||||
}
|
||||
|
||||
prim.absolute_path() = abs_path;
|
||||
if (assign_prim_id) {
|
||||
if (force_assign_prim_id || (prim.prim_id() < 1)) {
|
||||
uint64_t prim_id{0};
|
||||
if (!stage.allocate_prim_id(&prim_id)) {
|
||||
// Process tree iteratively
|
||||
while (!stack.empty()) {
|
||||
auto &top = stack.back();
|
||||
Prim *current = top.prim;
|
||||
size_t &child_idx = top.child_idx;
|
||||
|
||||
if (child_idx >= current->children().size()) {
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
Prim &child = current->children()[child_idx];
|
||||
++child_idx;
|
||||
|
||||
if (child.element_name().empty()) {
|
||||
if (err) {
|
||||
(*err) += "Failed to assign unique Prim ID.\n";
|
||||
(*err) += "Prim's elementName is empty. Prim's parent Path = " +
|
||||
top.parent_path.full_path_name() + "\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
prim.prim_id() = int64_t(prim_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (Prim &child : prim.children()) {
|
||||
if (!ComputeAbsPathAndAssignPrimIdRec(stage, child, abs_path, depth + 1,
|
||||
assign_prim_id, force_assign_prim_id,
|
||||
err)) {
|
||||
return false;
|
||||
Path child_abs_path = top.parent_path.AppendPrim(child.element_name());
|
||||
child.absolute_path() = child_abs_path;
|
||||
|
||||
if (assign_prim_id) {
|
||||
if (force_assign_prim_id || (child.prim_id() < 1)) {
|
||||
uint64_t prim_id{0};
|
||||
if (!stage.allocate_prim_id(&prim_id)) {
|
||||
if (err) {
|
||||
(*err) += "Failed to assign unique Prim ID.\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
child.prim_id() = int64_t(prim_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!child.children().empty()) {
|
||||
stack.emplace_back(&child, child_abs_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,13 +725,10 @@ bool ComputeAbsPathAndAssignPrimIdRec(const Stage &stage, Prim &prim,
|
||||
|
||||
bool Stage::compute_absolute_prim_path_and_assign_prim_id(
|
||||
bool force_assign_prim_id) {
|
||||
Path rootPath("/", "");
|
||||
for (Prim &root : root_prims()) {
|
||||
if (!ComputeAbsPathAndAssignPrimIdRec(*this, root, rootPath, 1,
|
||||
/* assign_prim_id */ true,
|
||||
force_assign_prim_id, &_err)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputeAbsPathAndAssignPrimIdIterative(*this, _root_nodes,
|
||||
/* assign_prim_id */ true,
|
||||
force_assign_prim_id, &_err)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Only set dirty when prim_id changed.
|
||||
@@ -646,13 +738,11 @@ bool Stage::compute_absolute_prim_path_and_assign_prim_id(
|
||||
}
|
||||
|
||||
bool Stage::compute_absolute_prim_path() {
|
||||
Path rootPath("/", "");
|
||||
for (Prim &root : root_prims()) {
|
||||
if (!ComputeAbsPathAndAssignPrimIdRec(
|
||||
*this, root, rootPath, 1, /* assign prim_id */ false,
|
||||
/* force_assign_prim_id */ true, &_err)) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputeAbsPathAndAssignPrimIdIterative(*this, _root_nodes,
|
||||
/* assign_prim_id */ false,
|
||||
/* force_assign_prim_id */ true,
|
||||
&_err)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -782,21 +872,57 @@ bool Stage::replace_root_prim(const std::string &prim_name, Prim &&prim) {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string DumpPrimTreeRec(const Prim &prim, uint32_t depth) {
|
||||
// Iterative version of DumpPrimTree using explicit stack
|
||||
// Avoids recursion to save stack memory for deep hierarchies
|
||||
std::string DumpPrimTreeIterative(const std::vector<Prim> &root_prims) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (depth > 1024 * 1024 * 128) {
|
||||
// too deep node.
|
||||
return ss.str();
|
||||
// Stack entries: (prim pointer, depth, child index)
|
||||
// child_idx == SIZE_MAX means we haven't processed this node yet
|
||||
struct StackEntry {
|
||||
const Prim *prim;
|
||||
uint32_t depth;
|
||||
size_t child_idx;
|
||||
StackEntry(const Prim *p, uint32_t d) : prim(p), depth(d), child_idx(SIZE_MAX) {}
|
||||
};
|
||||
|
||||
StackVector<StackEntry, 4> stack;
|
||||
stack.reserve(64);
|
||||
|
||||
// Push root prims in reverse order to process in forward order
|
||||
for (auto it = root_prims.rbegin(); it != root_prims.rend(); ++it) {
|
||||
stack.emplace_back(&(*it), 0);
|
||||
}
|
||||
|
||||
ss << pprint::Indent(depth) << "\"" << prim.element_name() << "\" "
|
||||
<< prim.absolute_path() << "\n";
|
||||
ss << pprint::Indent(depth + 1) << fmt::format("prim_id {}", prim.prim_id())
|
||||
<< "\n";
|
||||
constexpr uint32_t kMaxDepth = 1024 * 1024 * 128;
|
||||
|
||||
for (const Prim &child : prim.children()) {
|
||||
ss << DumpPrimTreeRec(child, depth + 1);
|
||||
while (!stack.empty()) {
|
||||
StackEntry &entry = stack.back();
|
||||
|
||||
if (entry.depth > kMaxDepth) {
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.child_idx == SIZE_MAX) {
|
||||
// First visit: output this node
|
||||
ss << pprint::Indent(entry.depth) << "\"" << entry.prim->element_name() << "\" "
|
||||
<< entry.prim->absolute_path() << "\n";
|
||||
ss << pprint::Indent(entry.depth + 1) << fmt::format("prim_id {}", entry.prim->prim_id())
|
||||
<< "\n";
|
||||
entry.child_idx = 0;
|
||||
}
|
||||
|
||||
// Process children
|
||||
const auto &children = entry.prim->children();
|
||||
if (entry.child_idx < children.size()) {
|
||||
// Push next child and increment index
|
||||
size_t idx = entry.child_idx++;
|
||||
stack.emplace_back(&children[idx], entry.depth + 1);
|
||||
} else {
|
||||
// All children processed, pop this node
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
@@ -805,12 +931,7 @@ std::string DumpPrimTreeRec(const Prim &prim, uint32_t depth) {
|
||||
} // namespace
|
||||
|
||||
std::string Stage::dump_prim_tree() const {
|
||||
std::stringstream ss;
|
||||
|
||||
for (const Prim &root : root_prims()) {
|
||||
ss << DumpPrimTreeRec(root, 0);
|
||||
}
|
||||
return ss.str();
|
||||
return DumpPrimTreeIterative(_root_nodes);
|
||||
}
|
||||
|
||||
size_t Stage::estimate_memory_usage() const {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "prim-pprint.hh"
|
||||
#include "prim-types.hh"
|
||||
#include "primvar.hh"
|
||||
#include "tiny-container.hh"
|
||||
#include "tiny-format.hh"
|
||||
#include "tydra/prim-apply.hh"
|
||||
#include "usdGeom.hh"
|
||||
@@ -108,57 +109,135 @@ value::TimeSamples EnumTimeSamplesToTypelessTimeSamples(
|
||||
return dst;
|
||||
}
|
||||
|
||||
// Optimized iterative traversal using explicit stack
|
||||
// Avoids recursion and reuses path buffer to minimize string allocations
|
||||
template <typename T>
|
||||
bool TraverseRec(const std::string &path_prefix, const tinyusdz::Prim &prim,
|
||||
uint32_t depth, PathPrimMap<T> &itemmap) {
|
||||
if (depth > 1024 * 128) {
|
||||
// Too deep
|
||||
return false;
|
||||
}
|
||||
bool TraverseIterative(const tinyusdz::Prim &root_prim, PathPrimMap<T> &itemmap) {
|
||||
// Stack stores: (prim pointer, child index, path length before this prim)
|
||||
StackVector<std::tuple<const tinyusdz::Prim *, size_t, size_t>, 4> stack;
|
||||
stack.reserve(64);
|
||||
|
||||
std::string prim_abs_path =
|
||||
path_prefix + "/" + prim.local_path().full_path_name();
|
||||
// Shared path buffer - reuse to avoid allocations
|
||||
std::string path_buffer;
|
||||
path_buffer.reserve(256);
|
||||
|
||||
if (prim.is<T>()) {
|
||||
if (const T *pv = prim.as<T>()) {
|
||||
DCOUT("Path : <" << prim_abs_path << "> is " << tinyusdz::value::TypeTraits<T>::type_name());
|
||||
itemmap[prim_abs_path] = pv;
|
||||
// Process root prim
|
||||
path_buffer = "/" + root_prim.local_path().full_path_name();
|
||||
|
||||
if (root_prim.is<T>()) {
|
||||
if (const T *pv = root_prim.as<T>()) {
|
||||
DCOUT("Path : <" << path_buffer << "> is " << tinyusdz::value::TypeTraits<T>::type_name());
|
||||
itemmap[path_buffer] = pv;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &child : prim.children()) {
|
||||
if (!TraverseRec(prim_abs_path, child, depth + 1, itemmap)) {
|
||||
return false;
|
||||
if (!root_prim.children().empty()) {
|
||||
stack.emplace_back(&root_prim, 0, 0); // path_len=0 since "/" is implicit
|
||||
}
|
||||
|
||||
while (!stack.empty()) {
|
||||
auto &top = stack.back();
|
||||
const tinyusdz::Prim *parent = std::get<0>(top);
|
||||
size_t &child_idx = std::get<1>(top);
|
||||
const size_t parent_path_len = std::get<2>(top);
|
||||
|
||||
if (child_idx >= parent->children().size()) {
|
||||
// All children processed, backtrack
|
||||
// Restore path to parent's length
|
||||
path_buffer.resize(parent_path_len);
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
const tinyusdz::Prim &child = parent->children()[child_idx];
|
||||
++child_idx;
|
||||
|
||||
// Build path for this child
|
||||
size_t current_path_len = path_buffer.size();
|
||||
path_buffer += "/";
|
||||
path_buffer += child.local_path().full_path_name();
|
||||
|
||||
// Check and add to map if type matches
|
||||
if (child.is<T>()) {
|
||||
if (const T *pv = child.as<T>()) {
|
||||
DCOUT("Path : <" << path_buffer << "> is " << tinyusdz::value::TypeTraits<T>::type_name());
|
||||
itemmap[path_buffer] = pv;
|
||||
}
|
||||
}
|
||||
|
||||
// Push child to stack if it has children
|
||||
if (!child.children().empty()) {
|
||||
stack.emplace_back(&child, 0, current_path_len);
|
||||
} else {
|
||||
// No children, restore path immediately
|
||||
path_buffer.resize(current_path_len);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Specialization for Shader type.
|
||||
// Optimized iterative shader traversal using explicit stack
|
||||
// Avoids recursion and reuses path buffer to minimize string allocations
|
||||
template <typename ShaderTy>
|
||||
bool TraverseShaderRec(const std::string &path_prefix,
|
||||
const tinyusdz::Prim &prim, uint32_t depth,
|
||||
PathShaderMap<ShaderTy> &itemmap) {
|
||||
if (depth > 1024 * 128) {
|
||||
// Too deep
|
||||
return false;
|
||||
}
|
||||
bool TraverseShaderIterative(const tinyusdz::Prim &root_prim,
|
||||
PathShaderMap<ShaderTy> &itemmap) {
|
||||
// Stack stores: (prim pointer, child index, path length before this prim)
|
||||
StackVector<std::tuple<const tinyusdz::Prim *, size_t, size_t>, 4> stack;
|
||||
stack.reserve(64);
|
||||
|
||||
std::string prim_abs_path =
|
||||
path_prefix + "/" + prim.local_path().full_path_name();
|
||||
// Shared path buffer - reuse to avoid allocations
|
||||
std::string path_buffer;
|
||||
path_buffer.reserve(256);
|
||||
|
||||
// First check if type is Shader Prim.
|
||||
if (const Shader *ps = prim.as<Shader>()) {
|
||||
// Then check if wanted Shder type
|
||||
// Process root prim
|
||||
path_buffer = "/" + root_prim.local_path().full_path_name();
|
||||
|
||||
// Check if root is a Shader of the wanted type
|
||||
if (const Shader *ps = root_prim.as<Shader>()) {
|
||||
if (const ShaderTy *s = ps->value.as<ShaderTy>()) {
|
||||
itemmap[prim_abs_path] = std::make_pair(ps, s);
|
||||
itemmap[path_buffer] = std::make_pair(ps, s);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &child : prim.children()) {
|
||||
if (!TraverseShaderRec(prim_abs_path, child, depth + 1, itemmap)) {
|
||||
return false;
|
||||
if (!root_prim.children().empty()) {
|
||||
stack.emplace_back(&root_prim, 0, 0);
|
||||
}
|
||||
|
||||
while (!stack.empty()) {
|
||||
auto &top = stack.back();
|
||||
const tinyusdz::Prim *parent = std::get<0>(top);
|
||||
size_t &child_idx = std::get<1>(top);
|
||||
const size_t parent_path_len = std::get<2>(top);
|
||||
|
||||
if (child_idx >= parent->children().size()) {
|
||||
// All children processed, backtrack
|
||||
path_buffer.resize(parent_path_len);
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
const tinyusdz::Prim &child = parent->children()[child_idx];
|
||||
++child_idx;
|
||||
|
||||
// Build path for this child
|
||||
size_t current_path_len = path_buffer.size();
|
||||
path_buffer += "/";
|
||||
path_buffer += child.local_path().full_path_name();
|
||||
|
||||
// Check if this is a Shader of the wanted type
|
||||
if (const Shader *ps = child.as<Shader>()) {
|
||||
if (const ShaderTy *s = ps->value.as<ShaderTy>()) {
|
||||
itemmap[path_buffer] = std::make_pair(ps, s);
|
||||
}
|
||||
}
|
||||
|
||||
// Push child to stack if it has children
|
||||
if (!child.children().empty()) {
|
||||
stack.emplace_back(&child, 0, current_path_len);
|
||||
} else {
|
||||
// No children, restore path immediately
|
||||
path_buffer.resize(current_path_len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +284,7 @@ bool ListPrims(const tinyusdz::Stage &stage, PathPrimMap<T> &m /* output */) {
|
||||
}
|
||||
|
||||
for (const auto &root_prim : stage.root_prims()) {
|
||||
TraverseRec(/* root path is empty */ "", root_prim, /* depth */ 0, m);
|
||||
TraverseIterative(root_prim, m);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -232,7 +311,7 @@ bool ListShaders(const tinyusdz::Stage &stage,
|
||||
}
|
||||
|
||||
for (const auto &root_prim : stage.root_prims()) {
|
||||
TraverseShaderRec(/* root path is empty */ "", root_prim, /* depth */ 0, m);
|
||||
TraverseShaderIterative(root_prim, m);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -315,62 +394,144 @@ template bool ListShaders(const tinyusdz::Stage &stage,
|
||||
|
||||
namespace {
|
||||
|
||||
bool VisitPrimsRec(const tinyusdz::Path &root_abs_path,
|
||||
const tinyusdz::Prim &root, int32_t level,
|
||||
VisitPrimFunction visitor_fun, void *userdata,
|
||||
std::string *err) {
|
||||
std::string fun_error;
|
||||
bool ret = visitor_fun(root_abs_path, root, level, userdata, &fun_error);
|
||||
if (!ret) {
|
||||
if (fun_error.empty()) {
|
||||
// early termination request.
|
||||
DCOUT("Early termination requested");
|
||||
// Optimized iterative version of VisitPrimsRec
|
||||
// Handles primChildren ordering and early termination
|
||||
bool VisitPrimsIterative(const tinyusdz::Path &start_abs_path,
|
||||
const tinyusdz::Prim &start_prim, int32_t start_level,
|
||||
VisitPrimFunction visitor_fun, void *userdata,
|
||||
std::string *err) {
|
||||
// Stack entry: (prim pointer, ordered children to visit, current child index, level, parent path)
|
||||
struct StackEntry {
|
||||
const tinyusdz::Prim *prim;
|
||||
std::vector<const tinyusdz::Prim *> ordered_children;
|
||||
size_t child_idx;
|
||||
int32_t level;
|
||||
tinyusdz::Path abs_path;
|
||||
|
||||
StackEntry(const tinyusdz::Prim *p, int32_t lvl, tinyusdz::Path path)
|
||||
: prim(p), child_idx(0), level(lvl), abs_path(std::move(path)) {}
|
||||
};
|
||||
|
||||
StackVector<StackEntry, 4> stack;
|
||||
stack.reserve(64);
|
||||
|
||||
// Helper to get ordered children list
|
||||
auto get_ordered_children = [err](const tinyusdz::Prim &prim)
|
||||
-> std::pair<bool, std::vector<const tinyusdz::Prim *>> {
|
||||
std::vector<const tinyusdz::Prim *> result;
|
||||
|
||||
if (prim.children().empty()) {
|
||||
return {true, result};
|
||||
}
|
||||
|
||||
// If primChildren metadata matches children count, use it for ordering
|
||||
if (prim.metas().primChildren.size() == prim.children().size()) {
|
||||
std::map<std::string, const tinyusdz::Prim *> primNameTable;
|
||||
for (size_t i = 0; i < prim.children().size(); i++) {
|
||||
primNameTable.emplace(prim.children()[i].element_name(),
|
||||
&prim.children()[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < prim.metas().primChildren.size(); i++) {
|
||||
value::token nameTok = prim.metas().primChildren[i];
|
||||
const auto it = primNameTable.find(nameTok.str());
|
||||
if (it != primNameTable.end()) {
|
||||
result.push_back(it->second);
|
||||
} else {
|
||||
if (err) {
|
||||
(*err) += fmt::format(
|
||||
"Prim name `{}` in `primChildren` metadatum not found in this "
|
||||
"Prim's children",
|
||||
nameTok.str());
|
||||
}
|
||||
return {false, {}};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (err) {
|
||||
(*err) += fmt::format(
|
||||
"Visit function returned an error for Prim {} (id {}). err = {}",
|
||||
root_abs_path.full_path_name(), root.prim_id(), fun_error);
|
||||
// Use natural order
|
||||
for (const auto &child : prim.children()) {
|
||||
result.push_back(&child);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// if `primChildren` is available, use it
|
||||
if (root.metas().primChildren.size() == root.children().size()) {
|
||||
std::map<std::string, const Prim *> primNameTable;
|
||||
for (size_t i = 0; i < root.children().size(); i++) {
|
||||
primNameTable.emplace(root.children()[i].element_name(),
|
||||
&root.children()[i]);
|
||||
}
|
||||
return {true, result};
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < root.metas().primChildren.size(); i++) {
|
||||
value::token nameTok = root.metas().primChildren[i];
|
||||
const auto it = primNameTable.find(nameTok.str());
|
||||
if (it != primNameTable.end()) {
|
||||
const Path child_abs_path = root_abs_path.AppendPrim(nameTok.str());
|
||||
if (!VisitPrimsRec(child_abs_path, *it->second, level + 1, visitor_fun,
|
||||
userdata, err)) {
|
||||
return false;
|
||||
}
|
||||
// Visit start prim first
|
||||
{
|
||||
std::string fun_error;
|
||||
bool ret = visitor_fun(start_abs_path, start_prim, start_level, userdata, &fun_error);
|
||||
if (!ret) {
|
||||
if (fun_error.empty()) {
|
||||
DCOUT("Early termination requested");
|
||||
} else {
|
||||
if (err) {
|
||||
(*err) += fmt::format(
|
||||
"Prim name `{}` in `primChildren` metadatum not found in this "
|
||||
"Prim's children",
|
||||
nameTok.str());
|
||||
"Visit function returned an error for Prim {} (id {}). err = {}",
|
||||
start_abs_path.full_path_name(), start_prim.prim_id(), fun_error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get ordered children for start prim
|
||||
std::pair<bool, std::vector<const tinyusdz::Prim *>> start_result =
|
||||
get_ordered_children(start_prim);
|
||||
if (!start_result.first) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!start_result.second.empty()) {
|
||||
StackEntry entry(&start_prim, start_level, start_abs_path);
|
||||
entry.ordered_children = std::move(start_result.second);
|
||||
stack.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
// Iterative traversal
|
||||
while (!stack.empty()) {
|
||||
auto &top = stack.back();
|
||||
|
||||
if (top.child_idx >= top.ordered_children.size()) {
|
||||
// All children processed, backtrack
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
for (const auto &child : root.children()) {
|
||||
const Path child_abs_path =
|
||||
root_abs_path.AppendPrim(child.element_name());
|
||||
if (!VisitPrimsRec(child_abs_path, child, level + 1, visitor_fun,
|
||||
userdata, err)) {
|
||||
return false;
|
||||
const tinyusdz::Prim *child = top.ordered_children[top.child_idx];
|
||||
++top.child_idx;
|
||||
|
||||
// Build path for this child
|
||||
tinyusdz::Path child_abs_path = top.abs_path.AppendPrim(child->element_name());
|
||||
int32_t child_level = top.level + 1;
|
||||
|
||||
// Call visitor
|
||||
std::string fun_error;
|
||||
bool ret = visitor_fun(child_abs_path, *child, child_level, userdata, &fun_error);
|
||||
if (!ret) {
|
||||
if (fun_error.empty()) {
|
||||
DCOUT("Early termination requested");
|
||||
} else {
|
||||
if (err) {
|
||||
(*err) += fmt::format(
|
||||
"Visit function returned an error for Prim {} (id {}). err = {}",
|
||||
child_abs_path.full_path_name(), child->prim_id(), fun_error);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get ordered children for this child
|
||||
std::pair<bool, std::vector<const tinyusdz::Prim *>> child_result =
|
||||
get_ordered_children(*child);
|
||||
if (!child_result.first) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!child_result.second.empty()) {
|
||||
StackEntry entry(child, child_level, std::move(child_abs_path));
|
||||
entry.ordered_children = std::move(child_result.second);
|
||||
stack.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1929,8 +2090,8 @@ bool VisitPrims(const tinyusdz::Stage &stage, VisitPrimFunction visitor_fun,
|
||||
const auto it = primNameTable.find(nameTok.str());
|
||||
if (it != primNameTable.end()) {
|
||||
const Path root_abs_path("/" + nameTok.str(), "");
|
||||
if (!VisitPrimsRec(root_abs_path, *it->second, 0, visitor_fun, userdata,
|
||||
err)) {
|
||||
if (!VisitPrimsIterative(root_abs_path, *it->second, 0, visitor_fun,
|
||||
userdata, err)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -1947,8 +2108,8 @@ bool VisitPrims(const tinyusdz::Stage &stage, VisitPrimFunction visitor_fun,
|
||||
} else {
|
||||
for (const auto &root : stage.root_prims()) {
|
||||
const Path root_abs_path("/" + root.element_name(), /* prop part */ "");
|
||||
if (!VisitPrimsRec(root_abs_path, root, /* root level */ 0, visitor_fun,
|
||||
userdata, err)) {
|
||||
if (!VisitPrimsIterative(root_abs_path, root, /* root level */ 0,
|
||||
visitor_fun, userdata, err)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2132,20 +2293,12 @@ bool ListSceneNames(const tinyusdz::Prim &root,
|
||||
|
||||
namespace {
|
||||
|
||||
bool BuildXformNodeFromStageRec(
|
||||
const tinyusdz::Stage &stage, const Path &parent_abs_path, const Prim *prim,
|
||||
XformNode *nodeOut, /* out */
|
||||
value::matrix4d rootMat, const double t,
|
||||
const tinyusdz::value::TimeSampleInterpolationType tinterp) {
|
||||
if (!nodeOut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XformNode node;
|
||||
|
||||
if (prim->element_name().empty()) {
|
||||
// TODO: report error
|
||||
}
|
||||
// Helper to compute XformNode properties from a Prim
|
||||
static void ComputeXformNodeProperties(
|
||||
const Prim *prim, const Path &parent_abs_path,
|
||||
const value::matrix4d &parent_world_mat, const double t,
|
||||
const tinyusdz::value::TimeSampleInterpolationType tinterp,
|
||||
XformNode &node) {
|
||||
|
||||
node.element_name = prim->element_name();
|
||||
node.absolute_path = parent_abs_path.AppendPrim(prim->element_name());
|
||||
@@ -2161,7 +2314,6 @@ bool BuildXformNodeFromStageRec(
|
||||
GetLocalTransform(*prim, &resetXformStack, t, tinterp);
|
||||
DCOUT("local mat = " << localMat);
|
||||
|
||||
value::matrix4d worldMat = rootMat;
|
||||
node.has_resetXformStack() = resetXformStack;
|
||||
|
||||
value::matrix4d m;
|
||||
@@ -2171,10 +2323,10 @@ bool BuildXformNodeFromStageRec(
|
||||
m = localMat;
|
||||
} else {
|
||||
// matrix is row-major, so local first
|
||||
m = localMat * worldMat;
|
||||
m = localMat * parent_world_mat;
|
||||
}
|
||||
|
||||
node.set_parent_world_matrix(rootMat);
|
||||
node.set_parent_world_matrix(parent_world_mat);
|
||||
node.set_local_matrix(localMat);
|
||||
node.set_world_matrix(m);
|
||||
node.has_xform() = true;
|
||||
@@ -2182,46 +2334,131 @@ bool BuildXformNodeFromStageRec(
|
||||
DCOUT("Not xformable");
|
||||
node.has_xform() = false;
|
||||
node.has_resetXformStack() = false;
|
||||
node.set_parent_world_matrix(rootMat);
|
||||
node.set_world_matrix(rootMat);
|
||||
node.set_parent_world_matrix(parent_world_mat);
|
||||
node.set_world_matrix(parent_world_mat);
|
||||
node.set_local_matrix(value::matrix4d::identity());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &childPrim : prim->children()) {
|
||||
XformNode childNode;
|
||||
if (!BuildXformNodeFromStageRec(stage, node.absolute_path, &childPrim,
|
||||
&childNode, node.get_world_matrix(), t,
|
||||
tinterp)) {
|
||||
return false;
|
||||
}
|
||||
// Iterative version of BuildXformNodeFromStage using explicit stack
|
||||
bool BuildXformNodeFromStageIterative(
|
||||
const tinyusdz::Stage &stage, const Path &initial_parent_path, const Prim *root_prim,
|
||||
XformNode *nodeOut, /* out */
|
||||
value::matrix4d rootMat, const double t,
|
||||
const tinyusdz::value::TimeSampleInterpolationType tinterp) {
|
||||
|
||||
childNode.parent = &node;
|
||||
node.children.emplace_back(std::move(childNode));
|
||||
(void)stage; // Currently unused
|
||||
|
||||
if (!nodeOut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(*nodeOut) = node;
|
||||
// Stack entry for iterative processing
|
||||
struct StackEntry {
|
||||
const Prim *prim;
|
||||
Path parent_path;
|
||||
value::matrix4d parent_world_mat;
|
||||
size_t child_idx;
|
||||
XformNode node;
|
||||
|
||||
StackEntry(const Prim *p, Path pp, value::matrix4d pwm)
|
||||
: prim(p), parent_path(std::move(pp)), parent_world_mat(pwm), child_idx(0) {}
|
||||
};
|
||||
|
||||
StackVector<StackEntry, 4> stack;
|
||||
stack.reserve(64);
|
||||
|
||||
// Initialize with root prim
|
||||
stack.emplace_back(root_prim, initial_parent_path, rootMat);
|
||||
|
||||
// Compute root node properties
|
||||
ComputeXformNodeProperties(root_prim, initial_parent_path, rootMat, t, tinterp,
|
||||
stack.back().node);
|
||||
|
||||
while (!stack.empty()) {
|
||||
StackEntry &curr = stack.back();
|
||||
const auto &children = curr.prim->children();
|
||||
|
||||
if (curr.child_idx < children.size()) {
|
||||
// Push next child
|
||||
const Prim &child = children[curr.child_idx];
|
||||
curr.child_idx++;
|
||||
|
||||
stack.emplace_back(&child, curr.node.absolute_path, curr.node.get_world_matrix());
|
||||
|
||||
// Compute new child's properties
|
||||
StackEntry &new_entry = stack.back();
|
||||
ComputeXformNodeProperties(new_entry.prim, new_entry.parent_path,
|
||||
new_entry.parent_world_mat, t, tinterp,
|
||||
new_entry.node);
|
||||
} else {
|
||||
// All children processed
|
||||
if (stack.size() > 1) {
|
||||
// Move completed node to parent's children
|
||||
XformNode completed = std::move(curr.node);
|
||||
stack.pop_back();
|
||||
// Note: parent pointer will point to stack.back().node, which will be
|
||||
// moved later. This preserves the same behavior as the recursive version.
|
||||
completed.parent = &stack.back().node;
|
||||
stack.back().node.children.emplace_back(std::move(completed));
|
||||
} else {
|
||||
// Root node - copy to output
|
||||
*nodeOut = std::move(curr.node);
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string DumpXformNodeRec(const XformNode &node, uint32_t indent) {
|
||||
// Iterative version of DumpXformNode using explicit stack
|
||||
std::string DumpXformNodeIterative(const XformNode &root) {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << pprint::Indent(indent) << "Prim name: " << node.element_name
|
||||
<< " PrimID: " << node.prim_id << " (Path " << node.absolute_path
|
||||
<< ") Xformable? " << node.has_xform() << " resetXformStack? "
|
||||
<< node.has_resetXformStack() << " {\n";
|
||||
ss << pprint::Indent(indent + 1)
|
||||
<< "parent_world: " << node.get_parent_world_matrix() << "\n";
|
||||
ss << pprint::Indent(indent + 1) << "world: " << node.get_world_matrix()
|
||||
<< "\n";
|
||||
ss << pprint::Indent(indent + 1) << "local: " << node.get_local_matrix()
|
||||
<< "\n";
|
||||
// Stack entry: (node pointer, indent, child index, closing_brace_pending)
|
||||
// child_idx == SIZE_MAX means we haven't printed this node yet
|
||||
struct StackEntry {
|
||||
const XformNode *node;
|
||||
uint32_t indent;
|
||||
size_t child_idx;
|
||||
StackEntry(const XformNode *n, uint32_t i)
|
||||
: node(n), indent(i), child_idx(SIZE_MAX) {}
|
||||
};
|
||||
|
||||
for (const auto &child : node.children) {
|
||||
ss << DumpXformNodeRec(child, indent + 1);
|
||||
StackVector<StackEntry, 4> stack;
|
||||
stack.reserve(64);
|
||||
stack.emplace_back(&root, 0);
|
||||
|
||||
while (!stack.empty()) {
|
||||
StackEntry &entry = stack.back();
|
||||
|
||||
if (entry.child_idx == SIZE_MAX) {
|
||||
// First visit: print node info
|
||||
ss << pprint::Indent(entry.indent) << "Prim name: " << entry.node->element_name
|
||||
<< " PrimID: " << entry.node->prim_id << " (Path " << entry.node->absolute_path
|
||||
<< ") Xformable? " << entry.node->has_xform() << " resetXformStack? "
|
||||
<< entry.node->has_resetXformStack() << " {\n";
|
||||
ss << pprint::Indent(entry.indent + 1)
|
||||
<< "parent_world: " << entry.node->get_parent_world_matrix() << "\n";
|
||||
ss << pprint::Indent(entry.indent + 1) << "world: " << entry.node->get_world_matrix()
|
||||
<< "\n";
|
||||
ss << pprint::Indent(entry.indent + 1) << "local: " << entry.node->get_local_matrix()
|
||||
<< "\n";
|
||||
entry.child_idx = 0;
|
||||
}
|
||||
|
||||
// Process children
|
||||
const auto &children = entry.node->children;
|
||||
if (entry.child_idx < children.size()) {
|
||||
size_t idx = entry.child_idx++;
|
||||
stack.emplace_back(&children[idx], entry.indent + 1);
|
||||
} else {
|
||||
// All children processed, print closing brace and pop
|
||||
ss << pprint::Indent(entry.indent + 1) << "}\n";
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
ss << pprint::Indent(indent + 1) << "}\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
@@ -2253,8 +2490,8 @@ bool BuildXformNodeFromStage(
|
||||
|
||||
value::matrix4d rootMat{value::matrix4d::identity()};
|
||||
|
||||
if (!BuildXformNodeFromStageRec(stage, stage_root.absolute_path, &root,
|
||||
&node, rootMat, t, tinterp)) {
|
||||
if (!BuildXformNodeFromStageIterative(stage, stage_root.absolute_path, &root,
|
||||
&node, rootMat, t, tinterp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2267,7 +2504,7 @@ bool BuildXformNodeFromStage(
|
||||
}
|
||||
|
||||
std::string DumpXformNode(const XformNode &node) {
|
||||
return DumpXformNodeRec(node, 0);
|
||||
return DumpXformNodeIterative(node);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -3113,16 +3350,33 @@ bool BuildSkelHierarchy(const Skeleton &skel, SkelNode &dst, std::string *err) {
|
||||
|
||||
namespace {
|
||||
|
||||
void BuildSkelNameToIndexMapRec(const SkelNode &node, std::map<std::string, int> &m) {
|
||||
// Iterative version of BuildSkelNameToIndexMap using explicit stack
|
||||
void BuildSkelNameToIndexMapIterative(const SkelNode &root, std::map<std::string, int> &m) {
|
||||
// Stack for DFS traversal
|
||||
StackVector<std::pair<const SkelNode *, size_t>, 4> stack;
|
||||
stack.reserve(64);
|
||||
stack.emplace_back(&root, 0);
|
||||
|
||||
if (node.joint_name.size() && (node.joint_id >= 0)) {
|
||||
m[node.joint_name] = node.joint_id;
|
||||
while (!stack.empty()) {
|
||||
std::pair<const SkelNode *, size_t> &entry = stack.back();
|
||||
const SkelNode *node = entry.first;
|
||||
size_t &child_idx = entry.second;
|
||||
|
||||
// Process current node on first visit (child_idx == 0)
|
||||
if (child_idx == 0) {
|
||||
if (node->joint_name.size() && (node->joint_id >= 0)) {
|
||||
m[node->joint_name] = node->joint_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Process children
|
||||
if (child_idx < node->children.size()) {
|
||||
size_t idx = child_idx++;
|
||||
stack.emplace_back(&node->children[idx], 0);
|
||||
} else {
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &child : node.children) {
|
||||
BuildSkelNameToIndexMapRec(child, m);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -3131,8 +3385,8 @@ std::map<std::string, int> BuildSkelNameToIndexMap(const SkelHierarchy &skel) {
|
||||
|
||||
std::map<std::string, int> m;
|
||||
|
||||
BuildSkelNameToIndexMapRec(skel.root_node, m);
|
||||
|
||||
BuildSkelNameToIndexMapIterative(skel.root_node, m);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
@@ -990,30 +990,69 @@ nonstd::expected<json, std::string> ToJSON(const tinyusdz::StageMetas& metas) {
|
||||
return j;
|
||||
}
|
||||
|
||||
bool PrimToJSONRec(json &root, const tinyusdz::Prim& prim, int depth) {
|
||||
json j = ToJSON(prim.data());
|
||||
// Iterative version of PrimToJSON using explicit stack
|
||||
bool PrimToJSONIterative(json &root, const tinyusdz::Prim& root_prim) {
|
||||
// Stack entry for iterative processing
|
||||
struct StackEntry {
|
||||
const tinyusdz::Prim *prim;
|
||||
size_t child_idx;
|
||||
json j;
|
||||
json jchildren;
|
||||
|
||||
json jchildren = json::object();
|
||||
|
||||
// TODO: Traverse Prim according to primChildren.
|
||||
for (const auto &child : prim.children()) {
|
||||
json cj;
|
||||
if (!PrimToJSONRec(cj, child, depth+1)) {
|
||||
return false;
|
||||
explicit StackEntry(const tinyusdz::Prim *p)
|
||||
: prim(p), child_idx(0), jchildren(json::object()) {
|
||||
// Convert prim data to JSON immediately
|
||||
j = ToJSON(p->data());
|
||||
}
|
||||
std::string cname = child.element_name();
|
||||
jchildren[cname] = cj;
|
||||
}
|
||||
};
|
||||
|
||||
if (jchildren.size()) {
|
||||
j["primChildren"] = jchildren;
|
||||
}
|
||||
std::vector<StackEntry> stack;
|
||||
stack.reserve(64);
|
||||
|
||||
root[prim.element_name()] = j;
|
||||
// Initialize with root prim
|
||||
stack.emplace_back(&root_prim);
|
||||
|
||||
while (!stack.empty()) {
|
||||
StackEntry &curr = stack.back();
|
||||
const auto &children = curr.prim->children();
|
||||
|
||||
if (curr.child_idx < children.size()) {
|
||||
// Push next child
|
||||
const tinyusdz::Prim &child = children[curr.child_idx];
|
||||
curr.child_idx++;
|
||||
|
||||
stack.emplace_back(&child);
|
||||
} else {
|
||||
// All children processed
|
||||
// Finalize this node's JSON
|
||||
if (curr.jchildren.size()) {
|
||||
curr.j["primChildren"] = curr.jchildren;
|
||||
}
|
||||
|
||||
std::string prim_name = curr.prim->element_name();
|
||||
json completed_j = std::move(curr.j);
|
||||
|
||||
if (stack.size() > 1) {
|
||||
// Add to parent's children
|
||||
stack.pop_back();
|
||||
stack.back().jchildren[prim_name] = std::move(completed_j);
|
||||
} else {
|
||||
// Root node - add to output
|
||||
root[prim_name] = std::move(completed_j);
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wrapper to maintain backward compatibility with PrimToJSONRec signature
|
||||
bool PrimToJSONRec(json &root, const tinyusdz::Prim& prim, int depth) {
|
||||
(void)depth; // Iterative version doesn't need depth
|
||||
return PrimToJSONIterative(root, prim);
|
||||
}
|
||||
|
||||
// Helper function to serialize context to JSON
|
||||
json SerializeContextToJSON(const USDToJSONContext& context) {
|
||||
json j;
|
||||
|
||||
119
src/usdGeom.cc
119
src/usdGeom.cc
@@ -5,7 +5,9 @@
|
||||
// UsdGeom API implementations
|
||||
|
||||
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "pprinter.hh"
|
||||
#include "value-types.hh"
|
||||
@@ -36,6 +38,32 @@ namespace {
|
||||
constexpr auto kPrimvars = "primvars:";
|
||||
constexpr auto kIndices = ":indices";
|
||||
|
||||
// Helper trait: can use data() pointer (excludes std::vector<bool>)
|
||||
template <typename T>
|
||||
struct can_use_data_ptr : std::integral_constant<bool,
|
||||
!std::is_same<T, bool>::value> {};
|
||||
|
||||
// Helper trait: can use memcpy for block copy
|
||||
template <typename T>
|
||||
struct can_use_memcpy : std::integral_constant<bool,
|
||||
std::is_trivially_copyable<T>::value && !std::is_same<T, bool>::value> {};
|
||||
|
||||
// Block copy with memcpy for trivially copyable types
|
||||
template <typename T>
|
||||
inline typename std::enable_if<can_use_memcpy<T>::value>::type
|
||||
CopyBlockElements(T* dest, const T* src, size_t count) {
|
||||
memcpy(dest, src, count * sizeof(T));
|
||||
}
|
||||
|
||||
// Block copy with loop for non-trivially copyable types
|
||||
template <typename T>
|
||||
inline typename std::enable_if<!can_use_memcpy<T>::value>::type
|
||||
CopyBlockElements(T* dest, const T* src, size_t count) {
|
||||
for (size_t k = 0; k < count; k++) {
|
||||
dest[k] = src[k];
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Computes
|
||||
///
|
||||
@@ -45,8 +73,11 @@ constexpr auto kIndices = ":indices";
|
||||
///
|
||||
/// `dest` is set to `values` when `indices` is empty
|
||||
///
|
||||
/// Optimized version for types that support data() (not std::vector<bool>)
|
||||
///
|
||||
template <typename T>
|
||||
nonstd::expected<bool, std::string> ExpandWithIndices(
|
||||
typename std::enable_if<can_use_data_ptr<T>::value, nonstd::expected<bool, std::string>>::type
|
||||
ExpandWithIndices(
|
||||
const std::vector<T> &values, uint32_t elementSize, const std::vector<int32_t> &indices,
|
||||
std::vector<T> *dest) {
|
||||
if (!dest) {
|
||||
@@ -69,17 +100,31 @@ nonstd::expected<bool, std::string> ExpandWithIndices(
|
||||
dest->resize(indices.size() * elementSize);
|
||||
|
||||
std::vector<size_t> invalidIndices;
|
||||
const size_t numValues = values.size();
|
||||
const size_t numIndices = indices.size();
|
||||
T* destData = dest->data();
|
||||
const T* srcData = values.data();
|
||||
|
||||
bool valid = true;
|
||||
for (size_t i = 0; i < indices.size(); i++) {
|
||||
int32_t idx = indices[i];
|
||||
if ((idx >= 0) && ((size_t(idx+1) * size_t(elementSize)) <= values.size())) {
|
||||
for (size_t k = 0; k < elementSize; k++) {
|
||||
(*dest)[i*elementSize + k] = values[size_t(idx)*elementSize + k];
|
||||
// Fast path for elementSize == 1 (most common case)
|
||||
if (elementSize == 1) {
|
||||
for (size_t i = 0; i < numIndices; i++) {
|
||||
int32_t idx = indices[i];
|
||||
if ((idx >= 0) && (size_t(idx) < numValues)) {
|
||||
destData[i] = srcData[idx];
|
||||
} else {
|
||||
invalidIndices.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Optimized path for elementSize > 1
|
||||
else {
|
||||
for (size_t i = 0; i < numIndices; i++) {
|
||||
int32_t idx = indices[i];
|
||||
if ((idx >= 0) && ((size_t(idx+1) * size_t(elementSize)) <= numValues)) {
|
||||
CopyBlockElements(destData + i * elementSize, srcData + size_t(idx) * elementSize, elementSize);
|
||||
} else {
|
||||
invalidIndices.push_back(i);
|
||||
}
|
||||
} else {
|
||||
invalidIndices.push_back(i);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +135,59 @@ nonstd::expected<bool, std::string> ExpandWithIndices(
|
||||
/* N to display */ 5));
|
||||
}
|
||||
|
||||
return valid;
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// Fallback for std::vector<bool> (no data() method available)
|
||||
///
|
||||
template <typename T>
|
||||
typename std::enable_if<!can_use_data_ptr<T>::value, nonstd::expected<bool, std::string>>::type
|
||||
ExpandWithIndices(
|
||||
const std::vector<T> &values, uint32_t elementSize, const std::vector<int32_t> &indices,
|
||||
std::vector<T> *dest) {
|
||||
if (!dest) {
|
||||
return nonstd::make_unexpected("`dest` is nullptr.");
|
||||
}
|
||||
|
||||
if (indices.empty()) {
|
||||
(*dest) = values;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (elementSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((values.size() % elementSize) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dest->resize(indices.size() * elementSize);
|
||||
|
||||
std::vector<size_t> invalidIndices;
|
||||
const size_t numValues = values.size();
|
||||
const size_t numIndices = indices.size();
|
||||
|
||||
for (size_t i = 0; i < numIndices; i++) {
|
||||
int32_t idx = indices[i];
|
||||
if ((idx >= 0) && ((size_t(idx+1) * size_t(elementSize)) <= numValues)) {
|
||||
for (size_t k = 0; k < elementSize; k++) {
|
||||
(*dest)[i*elementSize + k] = values[size_t(idx)*elementSize + k];
|
||||
}
|
||||
} else {
|
||||
invalidIndices.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidIndices.size()) {
|
||||
return nonstd::make_unexpected(
|
||||
"Invalid indices found: " +
|
||||
value::print_array_snipped(invalidIndices,
|
||||
/* N to display */ 5));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
109
src/usdMtlx.cc
109
src/usdMtlx.cc
@@ -1272,15 +1272,13 @@ static bool ConvertNoise(const tinyusdz::mtlx::pugi::xml_node &node, PrimSpec &p
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ConvertNodeGraphRec(const uint32_t depth,
|
||||
const tinyusdz::mtlx::pugi::xml_node &node, PrimSpec &ps_out,
|
||||
std::string *warn, std::string *err) {
|
||||
if (depth > (1024 * 1024)) {
|
||||
PUSH_ERROR_AND_RETURN("Network too deep.\n");
|
||||
}
|
||||
|
||||
PrimSpec ps;
|
||||
|
||||
// Helper to convert a single MaterialX node to PrimSpec
|
||||
// Returns true if successful (including skip case), false on error
|
||||
// Sets is_skip to true if the node should be skipped (input/output/unknown)
|
||||
static bool ConvertSingleNode(const tinyusdz::mtlx::pugi::xml_node &node,
|
||||
PrimSpec &ps, bool &is_skip,
|
||||
std::string *warn, std::string *err) {
|
||||
is_skip = false;
|
||||
std::string node_name = node.name();
|
||||
|
||||
// Convert MaterialX nodes to USD shader nodes
|
||||
@@ -1330,29 +1328,104 @@ static bool ConvertNodeGraphRec(const uint32_t depth,
|
||||
}
|
||||
} else if (node_name == "input" || node_name == "output") {
|
||||
// Skip input/output nodes - they are handled separately
|
||||
is_skip = true;
|
||||
return true;
|
||||
} else {
|
||||
PUSH_WARN(fmt::format("Unknown/unsupported Shader Node: {}. Skipping.\n", node.name()));
|
||||
is_skip = true;
|
||||
return true; // Don't fail, just skip unknown nodes
|
||||
}
|
||||
|
||||
// Recursively process child nodes
|
||||
for (const auto &child : node) {
|
||||
PrimSpec child_ps;
|
||||
if (!ConvertNodeGraphRec(depth + 1, child, child_ps, warn, err)) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Iterative version of ConvertNodeGraph using explicit stack
|
||||
static bool ConvertNodeGraphIterative(const tinyusdz::mtlx::pugi::xml_node &root_node,
|
||||
PrimSpec &ps_out,
|
||||
std::string *warn, std::string *err) {
|
||||
constexpr size_t kMaxDepth = 1024 * 1024;
|
||||
|
||||
// Stack entry for iterative processing
|
||||
// We need to collect children into a vector since the iterator is temporary
|
||||
struct StackEntry {
|
||||
tinyusdz::mtlx::pugi::xml_node xml_node;
|
||||
std::vector<tinyusdz::mtlx::pugi::xml_node> children;
|
||||
size_t child_idx;
|
||||
PrimSpec ps;
|
||||
bool is_skip;
|
||||
|
||||
explicit StackEntry(const tinyusdz::mtlx::pugi::xml_node &n)
|
||||
: xml_node(n), child_idx(0), is_skip(false) {
|
||||
// Collect children into vector
|
||||
for (auto it = n.begin(); it != n.end(); ++it) {
|
||||
children.push_back(*it);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<StackEntry> stack;
|
||||
stack.reserve(64);
|
||||
|
||||
// Initialize with root node
|
||||
stack.emplace_back(root_node);
|
||||
|
||||
// Convert root node
|
||||
if (!ConvertSingleNode(root_node, stack.back().ps, stack.back().is_skip, warn, err)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!stack.empty()) {
|
||||
if (stack.size() > kMaxDepth) {
|
||||
PUSH_ERROR_AND_RETURN("Network too deep.\n");
|
||||
}
|
||||
|
||||
if (!child_ps.name().empty()) {
|
||||
ps.children().emplace_back(std::move(child_ps));
|
||||
StackEntry &curr = stack.back();
|
||||
|
||||
// Check if there are more children to process
|
||||
if (curr.child_idx < curr.children.size()) {
|
||||
// Get current child and advance index
|
||||
tinyusdz::mtlx::pugi::xml_node child = curr.children[curr.child_idx];
|
||||
curr.child_idx++;
|
||||
|
||||
// Push child onto stack
|
||||
stack.emplace_back(child);
|
||||
|
||||
// Convert the child node
|
||||
if (!ConvertSingleNode(child, stack.back().ps, stack.back().is_skip, warn, err)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// All children processed
|
||||
if (stack.size() > 1) {
|
||||
// Move completed node to parent's children if not skipped and has name
|
||||
PrimSpec completed = std::move(curr.ps);
|
||||
bool was_skip = curr.is_skip;
|
||||
stack.pop_back();
|
||||
|
||||
if (!was_skip && !completed.name().empty()) {
|
||||
stack.back().ps.children().emplace_back(std::move(completed));
|
||||
}
|
||||
} else {
|
||||
// Root node - copy to output
|
||||
if (!curr.is_skip) {
|
||||
ps_out = std::move(curr.ps);
|
||||
}
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ps_out = std::move(ps);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wrapper to maintain backward compatibility with ConvertNodeGraphRec signature
|
||||
static bool ConvertNodeGraphRec(const uint32_t depth,
|
||||
const tinyusdz::mtlx::pugi::xml_node &node, PrimSpec &ps_out,
|
||||
std::string *warn, std::string *err) {
|
||||
(void)depth; // Iterative version handles depth internally
|
||||
return ConvertNodeGraphIterative(node, ps_out, warn, err);
|
||||
}
|
||||
|
||||
#if 0 // TODO
|
||||
static bool ConvertPlace2d(const tinyusdz::mtlx::pugi::xml_node &node, UsdTransform2d &tx, std::string *warn, std::string *err) {
|
||||
// texcoord(vector2). default index=0 uv coordinate
|
||||
|
||||
Reference in New Issue
Block a user