Skip to content

Commit 8f27fe6

Browse files
adamrwoodburypixar-oss
authored andcommitted
OpenExec tutorial: Computing Values in OpenExec
(Internal change: 2374257)
1 parent 568c4f5 commit 8f27fe6

File tree

3 files changed

+217
-8
lines changed

3 files changed

+217
-8
lines changed

pxr/exec/execUsd/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pxr_library(execUsd
3232
DOXYGEN_FILES
3333
README.md
3434
docs/overview.md
35+
docs/tutorial1ComputingValues.md
3536
)
3637

3738
pxr_build_test(testExecUsdBasicCompilation

pxr/exec/execUsd/docs/overview.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,19 @@ provide a `computeValue` builtin computation that yields the value of the
4949
attribute at the current evaluation time.
5050

5151
See the [builtin computation documentation](#group_Exec_Builtin_Computations)
52-
for a desciption of the builin computations that are available.
53-
54-
TODO:
55-
- ExecUsdSystem & ExecUsdRequest?
52+
for a desciption of the builtin computations that are available.
5653

5754

5855
## Tutorials {#section_Tutorials}
5956

60-
TODO:
61-
- Title/brief description of each tutorial, with a link to the document that
62-
contains it.
57+
- [Tutorial 1: Computing Values](tutorial1ComputingValues.md)
58+
- [Tutorial 2: Invalidation](tutorial2Invalidation.md)
59+
- [Tutorial 3: Defining Schema Computations](tutorial3DefiningComputations.md)
6360

6461

6562
## Advanced Topics {#section_AdvancedTopics}
6663

6764
TODO:
6865
- Exec architecture/phases of execution: compilation, scheduling, evaluation
6966
- Dispatched computations
70-
- Invalidation callbacks
7167
- Anything else?
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# Tutorial 1: Computing values in OpenExec
2+
3+
## Overview
4+
5+
The purpose of this tutorial is to demonstrate how to use OpenExec APIs to
6+
request values to be computed.
7+
8+
One thing to note is that the API that is presented here is the lowest-level
9+
API, designed for performance-intensive clients, such as an imaging
10+
system. OpenExec will eventually include convenience API, layered on top of the
11+
API shown here, for use cases that don't require maximum performance and for use
12+
cases that adhere to certain patterns that allow for more convenient API while
13+
still maintaining maxiumum performance.
14+
15+
For this tutorial, we will make use of the `computeLocalToWorldTransform`
16+
computations provided by UsdGeomXformable prims. The result of this computation
17+
on a given Xform is a 4x4 matrix that transforms points local to that Xform into
18+
points in world-space. The result of this computation depends on two values:
19+
1. The authored value of the `transform` attribute on the given Xform.
20+
2. The computed result of `computeLocalToWorldTransform` recursively invoked
21+
on the Xform's parent Xform.
22+
23+
## Create a UsdStage
24+
25+
The first step is to create a UsdStage from a scene that contains
26+
UsdGeomXformable prims, such as the scene described by this usda file:
27+
28+
```
29+
#usda 1.0
30+
31+
def Xform "Root" (
32+
kind = "component"
33+
)
34+
{
35+
uniform token[] xformOpOrder = [ "xformOp:transform" ]
36+
matrix4d xformOps:transform = (
37+
(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (1, 0, 0, 1) )
38+
39+
def Xform "A1"
40+
{
41+
uniform token[] xformOpOrder = [ "xformOp:transform" ]
42+
matrix4d xformOps:transform = (
43+
(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2, 0, 1) )
44+
}
45+
46+
def Xform "A2"
47+
{
48+
uniform token[] xformOpOrder = [ "xformOp:transform" ]
49+
matrix4d xformOps:transform = (
50+
(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 3, 1) )
51+
}
52+
}
53+
```
54+
55+
Assuming this layer is in a file named "xformPrims.usda" we can open the layer
56+
on a UsdStage as follows:
57+
58+
```cpp
59+
UsdStageRefPtr stage = UsdStage::Open("xformPrims.usda");
60+
```
61+
62+
## Create an ExecUsdSystem
63+
64+
In order to compute values from a UsdStage, we first need to create an
65+
ExecUsdSystem object from the stage:
66+
67+
```cpp
68+
ExecUsdSystem execSystem(stage);
69+
```
70+
71+
The system maintains the internal state needed to compute values that is common
72+
across different requests for values from a given UsdStage. In particular, it
73+
holds onto the compiled data flow **network** that is used for evaluation.
74+
75+
## Build an ExecUsdRequest
76+
77+
A set of values to be computed is specified by building an ExecUsdRequest. Each
78+
requested value is identified by an ExecUsdValueKey, which contains a
79+
**provider**--a UsdObject that provides a computation--and a TfToken that gives
80+
the name of the requested computation.
81+
82+
To build a request containing a collection of value keys, we call
83+
ExecUsdSystem::BuildRequest:
84+
85+
```cpp
86+
std::vector<ExecUsdValueKey> valueKeys {
87+
{stage->GetPrimAtPath(SdfPath("/Root/A1")),
88+
ExecGeomXformableTokens->computeLocalToWorldTransform},
89+
{stage->GetPrimAtPath(SdfPath("/Root/A2")),
90+
ExecGeomXformableTokens->computeLocalToWorldTransform},
91+
};
92+
93+
const ExecUsdRequest request = execSystem.BuildRequest(std::move(valueKeys));
94+
```
95+
96+
A request maintains state that is required to effiently compute the particular
97+
set of requested values that it represents. In particular, it holds onto a
98+
**schedule** that is used to accelerate evaluation, by amortizing across
99+
multiple rounds of computation for a given request.
100+
101+
## Prepare the request
102+
103+
Preparing the request, by calling ExecUsdSystem::PrepareRequest, does two
104+
things:
105+
1. It ensures that the network held by the system is compiled for the
106+
request. I.e., it makes sure that all data flow nodes and connections that
107+
are required to compute the requested values are in present the network and
108+
that their structure is up-to-date with respect to the current authored state
109+
of the UsdStage.
110+
2. It ensures that the request's schedule is up-to-date, and will re-schedule
111+
(including scheduling for the first time) if necessary.
112+
113+
```cpp
114+
execSystem.PrepareRequest(request);
115+
```
116+
117+
Note that explicitly preparing the request is optional; if we call `Compute`
118+
without first calling `PrepareRequest`, the call to `Compute` will prepare the
119+
request before computing. However, it is often desirable for client code to
120+
have explicit control over when the request is prepared, because of the cost of
121+
doing so. I.e., compiling and scheduling tend to be more expensive than
122+
computing, so it often makes sense to ensure these happen first, before multiple
123+
rounds of computation.
124+
125+
## Compute values
126+
127+
To compute the set of requested values, we simply call ExecUsdSystem::Compute:
128+
129+
```cpp
130+
ExecUsdCacheView cache = execSystem.Compute(request);
131+
```
132+
133+
The computed results are now ready, and can be acessed via the returned
134+
ExecUsdCacheView object.
135+
136+
## Extract computed values
137+
138+
To access the computed values, we call ExecUsdCacheView::Get to extract them,
139+
using indices that correspond to order of value keys in the vector used to build
140+
the request:
141+
142+
```cpp
143+
VtValue value = cache.Get(0);
144+
const GfMatrix4d a1LocalToWorld = value.Get<GfMatrix4d>();
145+
value = cache.Get(1);
146+
const GfMatrix4d a2LocalToWorld = value.Get<GfMatrix4d>();
147+
```
148+
149+
## Putting it all together
150+
151+
Bringing this all together into a single block of example code:
152+
153+
```cpp
154+
#include "pxr/base/gf/matrix4d.h"
155+
#include "pxr/base/vt/value.h"
156+
#include "pxr/exec/execGeom/tokens.h"
157+
#include "pxr/exec/execUsd/request.h"
158+
#include "pxr/exec/execUsd/system.h"
159+
#include "pxr/exec/execUsd/valueKey.h"
160+
#include "pxr/usd/sdf/path.h"
161+
#include "pxr/usd/usd/stage.h"
162+
163+
#include <utility>
164+
#include <vector>
165+
166+
void Example()
167+
{
168+
// Open the layer that contains our scene on a UsdStage.
169+
const UsdStageRefPtr stage = UsdStage::Open("xformPrims.usda");
170+
171+
// Create an ExecUsdSystem, which we will use to evaluate computations
172+
// on the stage.
173+
ExecUsdSystem execSystem(stage);
174+
175+
// Create a vector of value keys that indicate which computed values we
176+
// are requesting for evaluation.
177+
std::vector<ExecUsdValueKey> valueKeys {
178+
{stage->GetPrimAtPath(SdfPath("/Root/A1")),
179+
ExecGeomXformableTokens->computeLocalToWorldTransform},
180+
{stage->GetPrimAtPath(SdfPath("/Root/A2")),
181+
ExecGeomXformableTokens->computeLocalToWorldTransform},
182+
};
183+
184+
// Build the request.
185+
const ExecUsdRequest request =
186+
execSystem.BuildRequest(std::move(valueKeys));
187+
188+
// Prepare the request, ensuring the data flow graph is compiled and the
189+
// schedule is created.
190+
execSystem.PrepareRequest(request);
191+
192+
// Evaluate the data flow graph according to the schedule, to yield the
193+
// requested computed values.
194+
ExecUsdCacheView cache = execSystem.Compute(request);
195+
196+
// Extract the values.
197+
VtValue value = cache.Get(0);
198+
const GfMatrix4d a1LocalToWorld = value.Get<GfMatrix4d>();
199+
value = cache.Get(1);
200+
const GfMatrix4d a2LocalToWorld = value.Get<GfMatrix4d>();
201+
202+
// The resulting matrices are the concatenation of the transforms
203+
// authored on A1 and Root and A2 and Root, respectively. Here, we
204+
// extract the translations from the resulting matrices, demonstrating
205+
// that we end up with the expected net translations necessary to
206+
// translate points in each local space into world space.
207+
TF_AXIOM(GfIsClose(
208+
a1LocalToWorld.ExtractTranslation(), GfVec3d(1, 2, 0), 1e-6));
209+
TF_AXIOM(GfIsClose(
210+
a2LocalToWorld.ExtractTranslation(), GfVec3d(1, 0, 3), 1e-6));
211+
}
212+
```

0 commit comments

Comments
 (0)