Skip to content

Commit 88b61f0

Browse files
jloypixar-oss
authored andcommitted
exec: provide type-erased construction of VdfVector from VtValue
(Internal change: 2366613)
1 parent fc75eb4 commit 88b61f0

File tree

4 files changed

+301
-8
lines changed

4 files changed

+301
-8
lines changed

pxr/exec/exec/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,20 @@ pxr_register_test(testExecInputResolver
115115
EXPECTED_RETURN_CODE 0
116116
)
117117

118+
pxr_build_test(testExecTypeRegistration
119+
LIBRARIES
120+
gf
121+
tf
122+
exec
123+
CPPFILES
124+
testenv/testExecTypeRegistration.cpp
125+
)
126+
127+
pxr_register_test(testExecTypeRegistration
128+
COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testExecTypeRegistration"
129+
EXPECTED_RETURN_CODE 0
130+
)
131+
118132
pxr_build_test(testExecUncompilationTable
119133
LIBRARIES
120134
exec
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//
2+
// Copyright 2025 Pixar
3+
//
4+
// Licensed under the terms set forth in the LICENSE.txt file available at
5+
// https://openusd.org/license.
6+
//
7+
#include "pxr/pxr.h"
8+
9+
#include "pxr/exec/exec/typeRegistry.h"
10+
11+
#include "pxr/usd/sdf/timeCode.h"
12+
#include "pxr/usd/sdf/types.h"
13+
14+
#include "pxr/base/gf/vec3d.h"
15+
#include "pxr/base/tf/diagnostic.h"
16+
#include "pxr/base/tf/stringUtils.h"
17+
#include "pxr/base/vt/types.h"
18+
19+
PXR_NAMESPACE_USING_DIRECTIVE
20+
21+
#define ASSERT_EQ(expr, expected) \
22+
[&] { \
23+
auto&& expr_ = expr; \
24+
if (expr_ != expected) { \
25+
TF_FATAL_ERROR( \
26+
"Expected " TF_PP_STRINGIZE(expr) " == '%s'; got '%s'", \
27+
TfStringify(expected).c_str(), \
28+
TfStringify(expr_).c_str()); \
29+
} \
30+
}()
31+
32+
// A type that supports the minimum requirements to be an input value from
33+
// external clients, a computation result type and a result value returned to
34+
// external clients.
35+
struct TestExecTypeRegistrationValue
36+
{
37+
bool operator==(const TestExecTypeRegistrationValue &) const {
38+
return true;
39+
}
40+
};
41+
42+
static void
43+
TestBasicRegistration()
44+
{
45+
auto &reg = ExecTypeRegistry::GetInstance();
46+
47+
reg.RegisterType(TestExecTypeRegistrationValue{});
48+
reg.CheckForRegistration<TestExecTypeRegistrationValue>();
49+
50+
reg.RegisterType(VtArray<TestExecTypeRegistrationValue>{});
51+
reg.CheckForRegistration<VtArray<TestExecTypeRegistrationValue>>();
52+
}
53+
54+
static void
55+
TestCreateVector()
56+
{
57+
auto &reg = ExecTypeRegistry::GetInstance();
58+
59+
// This tests conversion of VtValue to VdfVector with a variety of types.
60+
// The are two special categories of types:
61+
//
62+
// 1. Vt known value types, for which VtValue has optimizations
63+
// related to type checking.
64+
// 2. Sdf value types, which comprise the types of attribute and
65+
// metadata in Usd.
66+
//
67+
// The following test cases include types that cover all combinations of
68+
// these categories, including a type that does not belong to either.
69+
// Additionally, for VtArray<T> types, test that CreateVector yields a
70+
// vectorized VdfVector of T rather than a VdfVector holding a single
71+
// VtArray<T>.
72+
73+
// GfVec3d is both a Vt known value type and an Sdf value type.
74+
{
75+
static_assert(VtIsKnownValueType<GfVec3d>());
76+
static_assert(SdfValueTypeTraits<GfVec3d>::IsValueType);
77+
GfVec3d point(1, 2, 3);
78+
const VdfVector vec = reg.CreateVector(VtValue(point));
79+
TF_AXIOM(vec.Holds<GfVec3d>());
80+
const auto accessor = vec.GetReadAccessor<GfVec3d>();
81+
ASSERT_EQ(accessor.GetNumValues(), 1);
82+
ASSERT_EQ(accessor[0], point);
83+
}
84+
85+
// VtArray<GfVec3d> is both a Vt known value type and an Sdf value type.
86+
{
87+
static_assert(VtIsKnownValueType<VtArray<GfVec3d>>());
88+
static_assert(SdfValueTypeTraits<VtArray<GfVec3d>>::IsValueType);
89+
const VtArray<GfVec3d> points = {
90+
{ 0., 0., 0. },
91+
{ 1., 0., 0. },
92+
{ 2., 0., 0. },
93+
{ 3., 0., 0. },
94+
{ 4., 0., 0. },
95+
{ 5., 0., 0. },
96+
};
97+
const VdfVector vec = reg.CreateVector(VtValue(points));
98+
TF_AXIOM(vec.Holds<GfVec3d>());
99+
const auto accessor = vec.GetReadAccessor<GfVec3d>();
100+
ASSERT_EQ(accessor.GetNumValues(), 6);
101+
ASSERT_EQ(accessor[0], points[0]);
102+
ASSERT_EQ(accessor[1], points[1]);
103+
ASSERT_EQ(accessor[2], points[2]);
104+
ASSERT_EQ(accessor[3], points[3]);
105+
ASSERT_EQ(accessor[4], points[4]);
106+
ASSERT_EQ(accessor[5], points[5]);
107+
}
108+
109+
// short is known to Vt but is not an Sdf value type.
110+
{
111+
static_assert(VtIsKnownValueType<short>());
112+
static_assert(!SdfValueTypeTraits<short>::IsValueType);
113+
const short x = 3;
114+
const VdfVector vec = reg.CreateVector(VtValue(x));
115+
TF_AXIOM(vec.Holds<short>());
116+
const auto accessor = vec.GetReadAccessor<short>();
117+
ASSERT_EQ(accessor.GetNumValues(), 1);
118+
ASSERT_EQ(accessor[0], x);
119+
}
120+
121+
// VtArray<short> is known to Vt but is not an Sdf value type.
122+
{
123+
static_assert(VtIsKnownValueType<VtArray<short>>());
124+
static_assert(!SdfValueTypeTraits<VtArray<short>>::IsValueType);
125+
const VtArray<short> arr = { 0, 1 };
126+
const VdfVector vec = reg.CreateVector(VtValue(arr));
127+
TF_AXIOM(vec.Holds<short>());
128+
const auto accessor = vec.GetReadAccessor<short>();
129+
ASSERT_EQ(accessor.GetNumValues(), 2);
130+
ASSERT_EQ(accessor[0], arr[0]);
131+
ASSERT_EQ(accessor[1], arr[1]);
132+
}
133+
134+
// SdfTimeCode is not known to Vt but is an Sdf value type.
135+
{
136+
static_assert(!VtIsKnownValueType<SdfTimeCode>());
137+
static_assert(SdfValueTypeTraits<SdfTimeCode>::IsValueType);
138+
const SdfTimeCode t = 1.0;
139+
const VdfVector vec = reg.CreateVector(VtValue(t));
140+
TF_AXIOM(vec.Holds<SdfTimeCode>());
141+
const auto accessor = vec.GetReadAccessor<SdfTimeCode>();
142+
ASSERT_EQ(accessor.GetNumValues(), 1);
143+
ASSERT_EQ(accessor[0], t);
144+
}
145+
146+
// VtArray<SdfTimeCode> is not known to Vt but is an Sdf value type.
147+
{
148+
static_assert(!VtIsKnownValueType<VtArray<SdfTimeCode>>());
149+
static_assert(SdfValueTypeTraits<VtArray<SdfTimeCode>>::IsValueType);
150+
const VtArray<SdfTimeCode> ts = { 0., 1. };
151+
const VdfVector vec = reg.CreateVector(VtValue(ts));
152+
TF_AXIOM(vec.Holds<SdfTimeCode>());
153+
const auto accessor = vec.GetReadAccessor<SdfTimeCode>();
154+
ASSERT_EQ(accessor.GetNumValues(), 2);
155+
ASSERT_EQ(accessor[0], ts[0]);
156+
ASSERT_EQ(accessor[1], ts[1]);
157+
}
158+
159+
// TestExecTypeRegistrationValue is not known to Vt and is not an Sdf
160+
// value type.
161+
{
162+
static_assert(!VtIsKnownValueType<TestExecTypeRegistrationValue>());
163+
static_assert(!SdfValueTypeTraits<
164+
TestExecTypeRegistrationValue>::IsValueType);
165+
TestExecTypeRegistrationValue val{};
166+
const VdfVector vec = reg.CreateVector(VtValue(val));
167+
TF_AXIOM(vec.Holds<TestExecTypeRegistrationValue>());
168+
const auto accessor = vec.GetReadAccessor<
169+
TestExecTypeRegistrationValue>();
170+
ASSERT_EQ(accessor.GetNumValues(), 1);
171+
// ASSERT_EQ is not used here because it requires that the type have a
172+
// TfStringify or ostream operator<< overload and this type should
173+
// define only what is necessary for VtValue & VdfVector.
174+
TF_AXIOM(accessor[0] == TestExecTypeRegistrationValue{});
175+
}
176+
177+
// VtArray<TestExecTypeRegistrationValue> is not known to Vt and is not an
178+
// Sdf value type.
179+
{
180+
static_assert(!VtIsKnownValueType<
181+
VtArray<TestExecTypeRegistrationValue>>());
182+
static_assert(!SdfValueTypeTraits<
183+
VtArray<TestExecTypeRegistrationValue>>::IsValueType);
184+
const VtArray<TestExecTypeRegistrationValue> arr = { {}, {} };
185+
const VdfVector vec = reg.CreateVector(VtValue(arr));
186+
TF_AXIOM(vec.Holds<TestExecTypeRegistrationValue>());
187+
const auto accessor = vec.GetReadAccessor<
188+
TestExecTypeRegistrationValue>();
189+
ASSERT_EQ(accessor.GetNumValues(), 2);
190+
TF_AXIOM(accessor[0] == arr[0]);
191+
TF_AXIOM(accessor[1] == arr[1]);
192+
}
193+
}
194+
195+
int
196+
main()
197+
{
198+
TestBasicRegistration();
199+
TestCreateVector();
200+
201+
return 0;
202+
}
203+

pxr/exec/exec/typeRegistry.cpp

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
#include "pxr/base/tf/preprocessorUtilsLite.h"
2121
#include "pxr/base/tf/type.h"
2222
#include "pxr/base/trace/trace.h"
23+
#include "pxr/base/vt/typeHeaders.h"
2324
#include "pxr/base/vt/value.h"
25+
#include "pxr/base/vt/visitValue.h"
26+
27+
#include <type_traits>
2428

2529
PXR_NAMESPACE_OPEN_SCOPE
2630

@@ -46,7 +50,7 @@ ExecTypeRegistry::ExecTypeRegistry()
4650
{
4751
TRACE_FUNCTION();
4852

49-
SdfSchema const& schema = SdfSchema::GetInstance();
53+
const SdfSchema& schema = SdfSchema::GetInstance();
5054

5155
// Ensure that USD value types are registered before subscribing to our
5256
// registry function so that plugin type registration cannot override the
@@ -55,12 +59,13 @@ ExecTypeRegistry::ExecTypeRegistry()
5559
#define _EXEC_REGISTER_VALUE_TYPE(unused, elem) \
5660
{ \
5761
using ValueType = SDF_VALUE_CPP_TYPE(elem); \
58-
TfType const type = TfType::Find<ValueType>(); \
59-
SdfValueTypeName const name = schema.FindType(type); \
60-
VtValue const &def = name.GetDefaultValue(); \
62+
const TfType type = TfType::Find<ValueType>(); \
63+
const SdfValueTypeName name = schema.FindType(type); \
64+
const VtValue &def = name.GetDefaultValue(); \
6165
if (TF_VERIFY(def.IsHolding<ValueType>())) { \
62-
ValueType const &value = def.UncheckedGet<ValueType>(); \
63-
_RegisterType(value); \
66+
const ValueType &value = def.UncheckedGet<ValueType>(); \
67+
_RegisterType(value); \
68+
_RegisterType(SDF_VALUE_CPP_ARRAY_TYPE(elem)()); \
6469
} \
6570
}
6671

@@ -69,6 +74,7 @@ ExecTypeRegistry::ExecTypeRegistry()
6974

7075
_RegisterType(EfTime());
7176
_RegisterType(SdfPath());
77+
_RegisterType(VtArray<SdfPath>());
7278

7379
TfSingleton<ExecTypeRegistry>::SetInstanceConstructed(*this);
7480
TfRegistryManager::GetInstance().SubscribeTo<ExecTypeRegistry>();
@@ -77,4 +83,23 @@ ExecTypeRegistry::ExecTypeRegistry()
7783

7884
ExecTypeRegistry::~ExecTypeRegistry() = default;
7985

86+
VdfVector
87+
ExecTypeRegistry::CreateVector(const VtValue &value) const
88+
{
89+
return VtVisitValue(value, [this](const auto &value) {
90+
using T = std::remove_cv_t<std::remove_reference_t<decltype(value)>>;
91+
// Visitors must accept a VtValue argument to handle types that aren't
92+
// known to VtValue. This is exactly the purpose of the type dispatch
93+
// table.
94+
if constexpr (std::is_same_v<VtValue, T>) {
95+
return _createVector.Call<VdfVector>(value.GetType(), value);
96+
}
97+
// Handle Vt's known value types. We don't need to explicitly
98+
// enumerate them here as VtVisitValue will do so.
99+
else {
100+
return _CreateVector<T>::Create(value);
101+
}
102+
});
103+
}
104+
80105
PXR_NAMESPACE_CLOSE_SCOPE

pxr/exec/exec/typeRegistry.h

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313

1414
#include "pxr/exec/exec/api.h"
1515
#include "pxr/exec/vdf/executionTypeRegistry.h"
16+
#include "pxr/exec/vdf/typeDispatchTable.h"
1617
#include "pxr/exec/vdf/vector.h"
1718

1819
#include "pxr/base/tf/singleton.h"
1920
#include "pxr/base/tf/type.h"
21+
#include "pxr/base/vt/traits.h"
22+
#include "pxr/base/vt/types.h"
23+
#include "pxr/base/vt/value.h"
2024

21-
#include <tbb/concurrent_unordered_map.h>
22-
25+
#include <algorithm>
2326
#include <memory>
2427

2528
PXR_NAMESPACE_OPEN_SCOPE
@@ -81,6 +84,10 @@ class ExecTypeRegistry
8184
return VdfExecutionTypeRegistry::CheckForRegistration<ValueType>();
8285
}
8386

87+
/// Construct a VdfVector whose value is copied from \p value.
88+
EXEC_API
89+
VdfVector CreateVector(const VtValue &value) const;
90+
8491
private:
8592
// Only TfSingleton can create instances.
8693
friend class TfSingleton<ExecTypeRegistry>;
@@ -94,15 +101,59 @@ class ExecTypeRegistry
94101
template <typename ValueType>
95102
void _RegisterType(ValueType const &fallback);
96103

104+
template <typename T>
105+
struct _CreateVector {
106+
// Interface for VdfTypeDispatchTable.
107+
static VdfVector Call(const VtValue &value) {
108+
return Create(value.UncheckedGet<T>());
109+
}
110+
// Typed implementation of CreateVector.
111+
//
112+
// This is separate from Call so that it can be shared with the
113+
// Vt known type optimization in CreateVector.
114+
static VdfVector Create(const T &value);
115+
};
116+
97117
private:
98118
std::unique_ptr<Exec_RegistrationBarrier> _registrationBarrier;
119+
120+
VdfTypeDispatchTable<_CreateVector> _createVector;
99121
};
100122

101123
template <typename ValueType>
102124
void
103125
ExecTypeRegistry::_RegisterType(ValueType const &fallback)
104126
{
105127
VdfExecutionTypeRegistry::Define(fallback);
128+
129+
// CreateVector has internal handling for value types known to Vt so we do
130+
// not need to register them here.
131+
if constexpr (!VtIsKnownValueType<ValueType>()) {
132+
_createVector.RegisterType<ValueType>();
133+
}
134+
}
135+
136+
template <typename T>
137+
VdfVector
138+
ExecTypeRegistry::_CreateVector<T>::Create(const T &value)
139+
{
140+
if constexpr (!VtIsArray<T>::value) {
141+
VdfVector v = VdfTypedVector<T>();
142+
v.Set(value);
143+
return v;
144+
}
145+
else {
146+
using ElementType = typename T::value_type;
147+
148+
const size_t size = value.size();
149+
150+
Vdf_BoxedContainer<ElementType> execValue(size);
151+
std::copy_n(value.cdata(), size, execValue.data());
152+
153+
VdfVector v = VdfTypedVector<ElementType>();
154+
v.Set(std::move(execValue));
155+
return v;
156+
}
106157
}
107158

108159
PXR_NAMESPACE_CLOSE_SCOPE

0 commit comments

Comments
 (0)