1 //
2 // Copyright 2019 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #ifndef PXR_IMAGING_HD_EXT_COMPUTATION_UTILS_H
25 #define PXR_IMAGING_HD_EXT_COMPUTATION_UTILS_H
26 
27 #include "pxr/pxr.h"
28 #include "pxr/imaging/hd/api.h"
29 #include "pxr/imaging/hd/extComputation.h"
30 #include "pxr/imaging/hd/sceneDelegate.h"
31 
32 #include "pxr/base/tf/span.h"
33 #include "pxr/base/tf/token.h"
34 #include "pxr/base/vt/value.h"
35 
36 #include <unordered_map>
37 
38 PXR_NAMESPACE_OPEN_SCOPE
39 
40 using HdExtComputationConstPtr = HdExtComputation const *;
41 using HdExtComputationConstPtrVector = std::vector<HdExtComputationConstPtr>;
42 
43 // This class contains utility methods to allow any Hydra backend to execute
44 // CPU computations via the Hydra ExtComputation framework.
45 //
46 // Note:
47 // The computation execution happens during Rprim sync. This precludes the
48 // use of computations shared by multiple Rprims, since the chain of
49 // computations for a computation primvar is executed for each Rprim.
50 class HdExtComputationUtils {
51 public:
52     using ValueStore =
53         std::unordered_map<TfToken, VtValue, TfToken::HashFunctor>;
54 
55     // Returns a map containing the (token, value) pairs for each "computation
56     // primvar".
57     // The participating computations are ordered based on their dependency
58     // and then, the CPU kernel is executed for each computation.
59     HD_API
60     static ValueStore
61     GetComputedPrimvarValues(
62         HdExtComputationPrimvarDescriptorVector const& compPrimvars,
63         HdSceneDelegate* sceneDelegate);
64 
65     template <unsigned int CAPACITY>
66     using SampledValueStore =
67         std::unordered_map<TfToken, HdTimeSampleArray<VtValue, CAPACITY>,
68                            TfToken::HashFunctor>;
69 
70     /// Returns a map containing the (token, samples) pairs for each
71     /// computation primvar, with up to \a maxSampleCount samples.
72     /// The participating computations are ordered based on their dependency
73     /// and then, the CPU kernel is executed for each computation.
74     template <unsigned int CAPACITY>
75     static void
76     SampleComputedPrimvarValues(
77         HdExtComputationPrimvarDescriptorVector const& compPrimvars,
78         HdSceneDelegate* sceneDelegate,
79         size_t maxSampleCount,
80         SampledValueStore<CAPACITY> *computedPrimvarValueStore);
81 
82     // Helper methods (these are public for testing purposes)
83     using ComputationDependencyMap =
84         std::unordered_map<HdExtComputation const *,
85                            HdExtComputationConstPtrVector>;
86     // Returns true if an ordering of the computations wherein any dependencies
87     // of a given computation come before it is possible, and fills
88     // sortedComps with the ordering.
89     // Returns false otherwise.
90     // The directed graph of a computation (vertex) and its dependencies (edges)
91     // is represented via the ComputationDependencyMap.
92     HD_API
93     static bool
94     DependencySort(ComputationDependencyMap cdm,
95                    HdExtComputationConstPtrVector* sortedComps);
96 
97     HD_API
98     static void
99     PrintDependencyMap(ComputationDependencyMap const& cdm);
100 
101 private:
102     HD_API
103     static ComputationDependencyMap
104     _GenerateDependencyMap(
105         HdExtComputationPrimvarDescriptorVector const& compPrimvars,
106         HdSceneDelegate* sceneDelegate);
107 
108     template <unsigned int CAPACITY>
109     static void
110     _ExecuteSampledComputations(
111         HdExtComputationConstPtrVector computations,
112         HdSceneDelegate* sceneDelegate,
113         size_t maxSampleCount,
114         SampledValueStore<CAPACITY>* valueStore);
115 
116     // Limits the list of the computation input time samples to the specified
117     // maximum number of (unique) samples.
118     HD_API
119     static void
120     _LimitTimeSamples(size_t maxSampleCount, std::vector<double>* times);
121 
122     // Internal method to invoke the computation with the specified input
123     // values, storing the output values in the provided buffer. The value
124     // arrays correspond to GetSceneInputNames(), GetComputationInputs(), and
125     // GetComputationOutputs() from the HdExtComputation, respectively, and are
126     // required to have the same lengths.
127     HD_API
128     static bool
129     _InvokeComputation(
130         HdSceneDelegate& sceneDelegate,
131         HdExtComputation const& computation,
132         TfSpan<const VtValue> sceneInputValues,
133         TfSpan<const VtValue> compInputValues,
134         TfSpan<VtValue> compOutputValues);
135 };
136 
137 template <unsigned int CAPACITY>
138 /*static*/ void
SampleComputedPrimvarValues(HdExtComputationPrimvarDescriptorVector const & compPrimvars,HdSceneDelegate * sceneDelegate,size_t maxSampleCount,SampledValueStore<CAPACITY> * computedPrimvarValueStore)139 HdExtComputationUtils::SampleComputedPrimvarValues(
140     HdExtComputationPrimvarDescriptorVector const& compPrimvars,
141     HdSceneDelegate* sceneDelegate,
142     size_t maxSampleCount,
143     SampledValueStore<CAPACITY> *computedPrimvarValueStore
144 )
145 {
146     HD_TRACE_FUNCTION();
147 
148     // Directed graph representation of the participating computations
149     ComputationDependencyMap cdm =
150         _GenerateDependencyMap(compPrimvars, sceneDelegate);
151 
152     // Topological ordering of the computations
153     HdExtComputationConstPtrVector sortedComputations;
154     bool success = DependencySort(cdm, &sortedComputations);
155     if (!success) {
156         return;
157     }
158 
159     // Execution
160     SampledValueStore<CAPACITY> valueStore;
161     _ExecuteSampledComputations<CAPACITY>(sortedComputations, sceneDelegate,
162                                           maxSampleCount, &valueStore);
163 
164     // Output extraction
165     for (auto const& pv : compPrimvars) {
166         TfToken const& compOutputName = pv.sourceComputationOutputName;
167         (*computedPrimvarValueStore)[pv.name] = valueStore[compOutputName];
168     }
169 }
170 
171 template <unsigned int CAPACITY>
172 /*static*/ void
_ExecuteSampledComputations(HdExtComputationConstPtrVector computations,HdSceneDelegate * sceneDelegate,size_t maxSampleCount,SampledValueStore<CAPACITY> * valueStore)173 HdExtComputationUtils::_ExecuteSampledComputations(
174     HdExtComputationConstPtrVector computations,
175     HdSceneDelegate* sceneDelegate,
176     size_t maxSampleCount,
177     SampledValueStore<CAPACITY> *valueStore
178 )
179 {
180     HD_TRACE_FUNCTION();
181 
182     for (auto const& comp : computations) {
183         SdfPath const& compId = comp->GetId();
184 
185         TfTokenVector const& sceneInputNames = comp->GetSceneInputNames();
186         HdExtComputationInputDescriptorVector const& compInputs =
187             comp->GetComputationInputs();
188         HdExtComputationOutputDescriptorVector const& compOutputs =
189             comp->GetComputationOutputs();
190 
191         // Add all the scene inputs to the value store
192         std::vector<double> times;
193         for (TfToken const& input : sceneInputNames) {
194             auto &samples = (*valueStore)[input];
195             sceneDelegate->SampleExtComputationInput(compId, input, &samples);
196 
197             for (size_t i = 0; i < samples.count; ++i)
198                 times.push_back(samples.times[i]);
199         }
200 
201         if (comp->IsInputAggregation()) {
202             // An aggregator computation produces no output, and thus
203             // doesn't need to be executed.
204             continue;
205         }
206 
207         // Also find all the time samples from the computed inputs.
208         for (auto const& computedInput : compInputs) {
209             auto const& samples =
210                 valueStore->at(computedInput.sourceComputationOutputName);
211             for (size_t i = 0; i < samples.count; ++i) {
212                 times.push_back(samples.times[i]);
213             }
214         }
215 
216         // Determine the time samples to evaluate the computation at.
217         _LimitTimeSamples(maxSampleCount, &times);
218 
219         // Allocate enough space for the evaluated outputs.
220         for (const TfToken &name : comp->GetOutputNames())
221         {
222             auto &output_samples = (*valueStore)[name];
223             output_samples.Resize(times.size());
224             output_samples.count = 0;
225         }
226 
227         TfSmallVector<VtValue, CAPACITY> sceneInputValues;
228         sceneInputValues.reserve(sceneInputNames.size());
229 
230         TfSmallVector<VtValue, CAPACITY> compInputValues;
231         compInputValues.reserve(compInputs.size());
232 
233         TfSmallVector<VtValue, CAPACITY> compOutputValues;
234 
235         // Evaluate the computation for each time sample.
236         for (double t : times) {
237 
238             // Retrieve all the inputs (scene, computed) from the value store,
239             // resampled to the required time.
240             sceneInputValues.clear();
241             for (auto const& sceneInput : comp->GetSceneInputNames()) {
242                 auto const& samples = valueStore->at(sceneInput);
243                 sceneInputValues.push_back(samples.Resample(t));
244             }
245 
246             compInputValues.clear();
247             for (auto const& computedInput : compInputs) {
248                 auto const& samples =
249                     valueStore->at(computedInput.sourceComputationOutputName);
250                 compInputValues.push_back(samples.Resample(t));
251             }
252 
253             compOutputValues.resize(compOutputs.size());
254             if (!_InvokeComputation(*sceneDelegate, *comp,
255                                     TfMakeSpan(sceneInputValues),
256                                     TfMakeSpan(compInputValues),
257                                     TfMakeSpan(compOutputValues))) {
258                 // We could bail here, or choose to execute other computations.
259                 // Choose the latter.
260                 continue;
261             }
262 
263             // Add outputs to the value store (subsequent computations may need
264             // them as computation inputs)
265             for (size_t i = 0; i < compOutputValues.size(); ++i) {
266                 auto &output_samples = (*valueStore)[compOutputs[i].name];
267 
268                 output_samples.times[output_samples.count] = t;
269                 output_samples.values[output_samples.count] =
270                     std::move(compOutputValues[i]);
271                 ++output_samples.count;
272             }
273         }
274 
275     } // for each computation
276 }
277 
278 PXR_NAMESPACE_CLOSE_SCOPE
279 
280 #endif // PXR_IMAGING_HD_EXT_COMPUTATION_UTILS_H
281