Loading...
Searching...
No Matches
instancer.h
1//
2// Copyright 2023 Pixar
3//
4// Licensed under the terms set forth in the LICENSE.txt file available at
5// https://openusd.org/license.
6//
7#ifndef EXT_RMANPKG_25_0_PLUGIN_RENDERMAN_PLUGIN_HD_PRMAN_INSTANCER_H
8#define EXT_RMANPKG_25_0_PLUGIN_RENDERMAN_PLUGIN_HD_PRMAN_INSTANCER_H
9
10#include "pxr/pxr.h"
11
12#include "hdPrman/renderParam.h"
13#include "hdPrman/rixStrings.h"
14
15#include "pxr/imaging/hd/instancer.h"
16#include "pxr/imaging/hd/sceneDelegate.h"
17#include "pxr/imaging/hd/timeSampleArray.h"
18#include "pxr/base/tf/hashmap.h"
19#include "pxr/base/tf/token.h"
20#include "pxr/base/vt/types.h"
21#include "pxr/base/vt/value.h"
22
23#include "tbb/concurrent_unordered_map.h"
24#include "tbb/spin_rw_mutex.h"
25
26PXR_NAMESPACE_OPEN_SCOPE
27
28class HdPrmanInstancer : public HdInstancer
29{
30
31public:
32
33 HdPrmanInstancer(HdSceneDelegate* delegate, SdfPath const& id);
34
36 ~HdPrmanInstancer();
37
38 void Sync(HdSceneDelegate *sceneDelegate,
39 HdRenderParam *renderParam,
40 HdDirtyBits *dirtyBits) override;
41
42 void Finalize(HdRenderParam *renderParam) override;
43
44 HdDirtyBits GetInitialDirtyBitsMask() const override;
45
93 void Populate(
94 HdRenderParam* renderParam,
95 HdDirtyBits* dirtyBits,
96 const SdfPath& hydraPrototypeId,
97 const std::vector<riley::GeometryPrototypeId>& rileyPrototypeIds,
98 const riley::CoordinateSystemList& coordSysList,
99 const RtParamList protoParams,
101 const std::vector<riley::MaterialId>& rileyMaterialIds,
102 const SdfPathVector& prototypePaths,
103 const riley::LightShaderId& lightShaderId = riley::LightShaderId::InvalidId());
104
119 void Depopulate(
120 HdRenderParam* renderParam,
121 const SdfPath& prototypePrimPath,
122 const std::vector<riley::GeometryPrototypeId>& excludedPrototypeIds = {});
123
124private:
125
126 // **********************************************
127 // ** Private Types **
128 // **********************************************
129
132
133 struct _RtParamListHashFunctor
134 {
135 size_t operator()(const RtParamList& params) const noexcept
136 {
137 // Wow this sucks, but RtParamList::Hash() is not const!
138 RtParamList copy = params;
139 return std::hash<uint32_t>()(copy.Hash());
140 }
141 };
142
143 struct _RtParamListEqualToFunctor
144 {
145 bool operator()(const RtParamList& lhs, const RtParamList& rhs) const noexcept
146 {
147 return _RtParamListHashFunctor()(lhs) == _RtParamListHashFunctor()(rhs);
148 }
149 };
150
151 struct _PrimvarValue
152 {
154 VtValue value;
155 };
156
157 struct _FlattenData
158 {
159
160 // The set of light linking categories
161 std::unordered_set<TfToken, TfToken::HashFunctor> categories;
162
163 // We store visibility in an RtParamList to take advantage of that
164 // structure's Inherit and Update methods, and because simply storing
165 // a single boolean would clobber any renderer-specific params that might
166 // have been authored on a given (native) instance.
167 RtParamList params;
168
169 _FlattenData() { }
170 _FlattenData(const VtTokenArray& cats)
171 : categories(cats.begin(), cats.end()) { }
172 _FlattenData(const VtTokenArray& cats, bool vis)
173 : categories(cats.begin(), cats.end())
174 {
175 SetVisibility(vis);
176 }
177 // Copy constructor
178 _FlattenData(const _FlattenData& other)
179 : categories(other.categories.cbegin(), other.categories.cend())
180 {
181 params.Update(other.params);
182 }
183
184 // Params that already exist here will not be changed;
185 // categories will be merged
186 void Inherit(const _FlattenData& rhs)
187 {
188 categories.insert(rhs.categories.cbegin(), rhs.categories.cend());
189 params.Inherit(rhs.params);
190 }
191
192 // Params that already exist here will be changed;
193 // categories will be merged
194 void Update(const _FlattenData& rhs)
195 {
196 categories.insert(rhs.categories.cbegin(), rhs.categories.cend());
197 params.Update(rhs.params);
198 }
199
200 // Update this FlattenData's visibility from an RtParamList. Visibility
201 // params that already exist here will be changed; visibility and
202 // light linking params on the RtParamList will be removed from it.
203 void UpdateVisAndFilterParamList(RtParamList& other) {
204 // Move visibility params from the RtParamList to the FlattenData
205 for (const RtUString& param : _GetVisibilityParams()) {
206 int val;
207 if (other.GetInteger(param, val)) {
208 if (val == 1) {
209 params.Remove(param);
210 } else {
211 params.SetInteger(param, val);
212 }
213 other.Remove(param);
214 }
215 }
216
217 // Copy any existing value for grouping:membership into the
218 // flatten data. For lights, this gets a value during light sync,
219 // and ConvertCategoriesToAttributes specifically handles preserving
220 // it. We need to capture the value from light sync here so we can
221 // and flatten against it. It won't be captured by the categories
222 // because the value set in light sync comes from a different
223 // source. It has to be handled separately from categories.
224 RtUString groupingMembership;
225 if (other.GetString(RixStr.k_grouping_membership, groupingMembership)) {
226 params.SetString(RixStr.k_grouping_membership, groupingMembership);
227 }
228
229 // Remove the light linking params from the RtParamList. Not going
230 // to parse them back out to individual tokens to add to
231 // the FlattenData categories, as they will be captured elsewhere.
232 for (const RtUString& param : _GetLightLinkParams()) {
233 other.Remove(param);
234 }
235 }
236
237 // Sets all visibility params, overwriting current values.)
238 void SetVisibility(bool visible) {
239 if (visible) {
240 for (const RtUString& param : _GetVisibilityParams()) {
241 params.Remove(param);
242 }
243 } else {
244 for (const RtUString& param : _GetVisibilityParams()) {
245 params.SetInteger(param, 0);
246 }
247 }
248 }
249
250 // equals operator
251 bool operator==(const _FlattenData& rhs) const noexcept
252 {
253 return categories == rhs.categories &&
254 _RtParamListEqualToFunctor()(params, rhs.params);
255 }
256
257 struct HashFunctor {
258 size_t operator()(const _FlattenData& fd) const noexcept
259 {
260 size_t hash = 0ul;
261
262 // simple order-independent XOR hash aggregation
263 for (const TfToken& tok : fd.categories) {
264 hash ^= tok.Hash();
265 }
266 return hash ^ _RtParamListHashFunctor()(fd.params);
267 }
268 };
269
270 private:
271 static std::vector<RtUString> _GetLightLinkParams()
272 {
273 // List of riley instance params pertaining to light-linking that are
274 // not supported on instances inside geometry prototype groups
275 static const std::vector<RtUString> LightLinkParams = {
276 RixStr.k_lightfilter_subset,
277 RixStr.k_lighting_subset,
278 RixStr.k_grouping_membership,
279 RixStr.k_lighting_excludesubset
280 };
281 return LightLinkParams;
282 }
283
284 static std::vector<RtUString> _GetVisibilityParams()
285 {
286 // List of rile instance params pertaining to visibility that are
287 // not supported on instances inside geometry prototype groups
288 static const std::vector<RtUString> VisParams = {
289 RixStr.k_visibility_camera,
290 RixStr.k_visibility_indirect,
291 RixStr.k_visibility_transmission
292 };
293 return VisParams;
294 }
295
296 };
297
298 struct _InstanceData
299 {
300 _FlattenData flattenData;
301 RtParamList params;
302 _GfMatrixSA transform;
303
304 _InstanceData() { }
305 _InstanceData(
306 const VtTokenArray& cats,
307 bool vis,
308 const RtParamList& p,
309 _GfMatrixSA& xform)
310 : transform(xform)
311 {
312 params.Inherit(p);
313 }
314 };
315
316 // A simple concurrent hashmap built from tbb::concurrent_unordered_map but
317 // with a simpler interface. Thread-safe operations (insertion, retrieval,
318 // const iteration) happen under a shared_lock, while unsafe operations
319 // (erase, clear, non-const iteration) use an exclusive lock. This way, the
320 // thread-safe operations can all run concurrently with one another, relying
321 // on tbb::concurrent_unordered_map's thread safety, but will never run
322 // while an unsafe operation is in progress, nor will the unsafe operations
323 // start while a safe one is running.
324 template<
325 typename Key,
326 typename T,
327 typename Hash = std::hash<Key>,
328 typename KeyEqual = std::equal_to<Key>>
329 class _LockingMap
330 {
331 public:
332 // Check whether the map contains the given key; check this call before
333 // calling get() if you want to avoid get's auto-insertion.
334 bool has(const Key& key) const
335 {
336 tbb::spin_rw_mutex::scoped_lock lock(_mutex, false);
337 if (_map.size() == 0) { return false; }
338 return _map.find(key) != _map.end();
339 }
340
341 // Retrieve the value for the given key. If the key is not present in
342 // the map, a default-constructed value will be inserted and returned.
343 // T must have default constructor
344 T& get(const Key& key)
345 {
346 static_assert(std::is_default_constructible<T>::value,
347 "T must be default constructible");
348
349 tbb::spin_rw_mutex::scoped_lock lock(_mutex, false);
350 auto it = _map.find(key);
351 if (it == _map.end()) {
352 it = _map.emplace(
353 std::piecewise_construct,
354 std::forward_as_tuple(key),
355 std::tuple<>{}).first;
356 }
357 return it->second;
358 }
359
360 // Set key to value, returns true if the key was newly inserted
361 // T must have copy assignment operator
362 bool set(const Key& key, T& val)
363 {
364 static_assert(std::is_copy_assignable<T>::value,
365 "T must be copy-assignable");
366
367 tbb::spin_rw_mutex::scoped_lock lock(_mutex, false);
368 if (_map.size() > 0) {
369 auto it = _map.find(key);
370 if (it != _map.end()) {
371 it->second = val;
372 return false;
373 }
374 }
375 _map.insert({key, val});
376 return true;
377 }
378
379 // Iterate the map with a non-const value reference under exclusive lock
380 void iterate(std::function<void(const Key&, T&)> fn)
381 {
382 // exclusive lock
383 tbb::spin_rw_mutex::scoped_lock lock(_mutex, true);
384 for (std::pair<const Key, T>& p : _map) {
385 fn(p.first, p.second);
386 }
387 }
388
389 // Iterate the map with a const value reference under shared lock
390 void citerate(std::function<void(const Key&, const T&)> fn) const
391 {
392 tbb::spin_rw_mutex::scoped_lock lock(_mutex, false);
393 for (const std::pair<const Key, const T>& p : _map) {
394 fn(p.first, p.second);
395 }
396 }
397
398 // Gives the count of keys currently in the map
399 size_t size() const
400 {
401 tbb::spin_rw_mutex::scoped_lock lock(_mutex, false);
402 return _map.size();
403 }
404
405 // Erase the given key from the map under exclusive lock
406 void erase(const Key& key)
407 {
408 // exclusive lock
409 tbb::spin_rw_mutex::scoped_lock lock(_mutex, true);
410 _map.unsafe_erase(key);
411 }
412
413 // Clear all map entries under exclusive lock
414 void clear()
415 {
416 // exclusive lock
417 tbb::spin_rw_mutex::scoped_lock lock(_mutex, true);
418 _map.clear();
419 }
420 private:
421 tbb::concurrent_unordered_map<Key, T, Hash, KeyEqual> _map;
422 mutable tbb::spin_rw_mutex _mutex;
423 };
424
425 using _LockingFlattenGroupMap = _LockingMap<
426 _FlattenData,
427 riley::GeometryPrototypeId,
428 _FlattenData::HashFunctor>;
429
430 struct _RileyInstanceId
431 {
432 riley::GeometryPrototypeId groupId;
433 riley::GeometryInstanceId geoInstanceId;
434 riley::LightInstanceId lightInstanceId;
435 };
436
437 using _InstanceIdVec = std::vector<_RileyInstanceId>;
438
439 struct _ProtoIdHash
440 {
441 size_t operator()(const riley::GeometryPrototypeId& id) const noexcept
442 {
443 return std::hash<uint32_t>()(id.AsUInt32());
444 }
445 };
446
447 using _ProtoInstMap = std::unordered_map<
448 riley::GeometryPrototypeId,
449 _InstanceIdVec,
450 _ProtoIdHash>;
451
452 using _LockingProtoGroupCounterMap = _LockingMap<
453 riley::GeometryPrototypeId,
454 std::atomic<int>,
455 _ProtoIdHash>;
456
457 struct _ProtoMapEntry
458 {
459 _ProtoInstMap map;
460 bool dirty;
461 };
462
463 using _LockingProtoMap = _LockingMap<SdfPath, _ProtoMapEntry, SdfPath::Hash>;
464
465 // **********************************************
466 // ** Private Methods **
467 // **********************************************
468
469 // Sync helper; caches instance-rate primvars
470 void _SyncPrimvars(HdDirtyBits* dirtyBits);
471
472 // Sync helper; caches the instancer and instance transforms
473 void _SyncTransforms(HdDirtyBits* dirtyBits);
474
475 // Sync helper; caches instance or instancer categories as appropriate
476 void _SyncCategories(HdDirtyBits* dirtyBits);
477
478 // Sync helper; caches instancer visibility
479 void _SyncVisibility(HdDirtyBits* dirtyBits);
480
481 // Generates InstanceData structures for this instancer's instances;
482 // will multiply those by any supplied subInstances
483 void _ComposeInstances(
484 const SdfPath& protoId,
485 const std::vector<_InstanceData>& subInstances,
486 std::vector<_InstanceData>& instances);
487
488 // Generates FlattenData from a set of instance params by looking for
489 // incompatible params and moving them from the RtParamList to the
490 // FlattenData. Called by _ComposeInstances().
491 void _ComposeInstanceFlattenData(
492 const size_t instanceId,
493 RtParamList& instanceParams,
494 _FlattenData& fd,
495 const _FlattenData& fromBelow = _FlattenData());
496
497 // Generates param sets and flatten data for the given prototype
498 // prim(s). Starts with copies of the prototype params provided to
499 // Populate, and additionally captures constant/uniform params inherited
500 // by the prototype, prototype- and subset-level light linking, and subset
501 // visibility.
502 void _ComposePrototypeData(
503 const SdfPath& protoPath,
504 const RtParamList& globalProtoParams,
505 const bool isLight,
506 const std::vector<riley::GeometryPrototypeId>& protoIds,
507 const SdfPathVector& subProtoPaths,
508 const std::vector<_FlattenData>& subProtoFlats,
509 std::vector<RtParamList>& protoParams,
510 std::vector<_FlattenData>& protoFlats,
511 std::vector<TfToken>& protoRenderTags);
512
513 // Deletes riley instances owned by this instancer that are of riley
514 // geometry prototypes that are no longer associated with the given
515 // prototype prim. Returns true if there are any new riley geometry
516 // prototype ids to associate with this prototype prim path.
517 bool _RemoveDeadInstances(
518 riley::Riley* riley,
519 const SdfPath& prototypePrimPath,
520 const std::vector<riley::GeometryPrototypeId>& protoIds);
521
522 // Flags all previously seen prototype prim paths as needing their instances
523 // updated the next time they show up in a Populate call.
524 void _SetPrototypesDirty();
525
526 // Generates instances of the given prototypes according to the instancer's
527 // instancing configuration. If the instancer is too deep, they get passed
528 // up to this method on the parent instancer. The given prototypes may be
529 // prims (when called through the public Populate method) or may be
530 // child instancers represented by riley geometry prototype groups. In
531 // either case, the caller owns the riley prototypes.
532 void _PopulateInstances(
533 HdRenderParam* renderParam,
534 HdDirtyBits* dirtyBits,
535 const SdfPath& hydraPrototypeId,
536 const SdfPath& prototypePrimPath,
537 const std::vector<riley::GeometryPrototypeId>& rileyPrototypeIds,
538 const riley::CoordinateSystemList& coordSysList,
539 const RtParamList protoParams,
541 const std::vector<riley::MaterialId>& rileyMaterialIds,
542 const SdfPathVector& prototypePaths,
543 const riley::LightShaderId& lightShaderId,
544 const std::vector<_InstanceData>& subInstances,
545 const std::vector<_FlattenData>& prototypeFlats);
546
547 // Locks before calling _PopulateInstances() to prevent duplicated Riley
548 // calls that may arise when Populate() has been called from multiple
549 // threads producing identical population requests from the same child
550 // instancer. Locks are segregated by prototypePrimPath to avoid
551 // over-locking.
552 void _PopulateInstancesFromChild(
553 HdRenderParam* renderParam,
554 HdDirtyBits* dirtyBits,
555 const SdfPath& hydraPrototypeId,
556 const SdfPath& prototypePrimPath,
557 const std::vector<riley::GeometryPrototypeId>& rileyPrototypeIds,
558 const riley::CoordinateSystemList& coordSysList,
559 const RtParamList protoParams,
561 const std::vector<riley::MaterialId>& rileyMaterialIds,
562 const SdfPathVector& prototypePaths,
563 const riley::LightShaderId& lightShaderId,
564 const std::vector<_InstanceData>& subInstances,
565 const std::vector<_FlattenData>& prototypeFlats);
566
567 // Get pointer to parent instancer, if one exists
568 HdPrmanInstancer* _GetParentInstancer();
569
570 // Resize the instancer's interal state store for tracking riley instances.
571 // Shrinking the number of instances for a given prototype path and id will
572 // delete excess instances from riley. Call with newSize = 0 to kill 'em all.
573 void _ResizeProtoMap(
574 riley::Riley* riley,
575 const SdfPath& prototypePrimPath,
576 const std::vector<riley::GeometryPrototypeId>& rileyPrototypeIds,
577 const size_t newSize);
578
579 // Deletes any riley geometry prototype groups that are no longer needed.
580 // Returns true if any groups were deleted.
581 bool _CleanDisusedGroupIds(HdPrman_RenderParam* param);
582
583 // Obtain the riley geometry prototype group id for a given FlattenData.
584 // Returns true if the group had to be created. Gives InvalidId when this
585 // instancer has no parent instancer.
586 bool _AcquireGroupId(
587 HdPrman_RenderParam* param,
588 const _FlattenData& flattenGroup,
589 riley::GeometryPrototypeId& groupId);
590
591 // Retrieves instance-rate params for the given instance index from
592 // the instancer's cache.
593 void _GetInstanceParams(
594 const size_t instanceIndex,
595 RtParamList& params);
596
597 // Gets constant and uniform params for the prototype
598 void _GetPrototypeParams(
599 const SdfPath& protoPath,
600 RtParamList& params
601 );
602
603 // Retrieves the instance transform for the given index from the
604 // instancer's cache.
605 void _GetInstanceTransform(
606 const size_t instanceIndex,
607 _GfMatrixSA& xform,
608 const _GfMatrixSA& left = _GfMatrixSA());
609
610 // Calculates this instancer's depth in the nested instancing hierarchy.
611 // An uninstanced instancer has depth 0. Instancers with depth > 4 cannot
612 // use riley nested instancing and must flatten their instances into their
613 // parents.
614 int _Depth();
615
616
617 // **********************************************
618 // ** Private Members **
619 // **********************************************
620
621 // This instancer's cached instance transforms
623
624 // This instancer's cached coordinate system list
625 riley::CoordinateSystemList _coordSysList = { 0, nullptr };
626
627 // This instancer's cached instance categories; will be empty under point
628 // instancing, so all indexing must be bounds-checked!
629 std::vector<VtTokenArray> _instanceCategories;
630
631 // This instancer's cached visibility and categories
632 _FlattenData _instancerFlat;
633
634 // This instancer's cached USD primvars
635 TfHashMap<TfToken, _PrimvarValue, TfToken::HashFunctor> _primvarMap;
636
637 // Map of FlattenData to GeometryProtoypeId
638 // We use this map to put instances that share values for instance params
639 // that are incompatible with riley nesting into shared prototype groups so
640 // that the incompatible params may be set on the outermost riley
641 // instances of those groups where they are supported. This map may be
642 // written to during Populate, so access must be gated behind a mutex
643 // lock (built into LockingMap).
644 _LockingFlattenGroupMap _groupMap;
645
646 // Counters for tracking number of instances in each prototype group. Used
647 // to speed up empty prototype group removal.
648 _LockingProtoGroupCounterMap _groupCounters;
649
650 // riley geometry prototype groups are created during Populate; these must
651 // be serialized to prevent creating two different groups for the same set
652 // of flatten data.
653 tbb::spin_rw_mutex _groupIdAcquisitionLock;
654
655 // Main storage for tracking riley instances owned by this instancer.
656 // Instance ids are paired with their containing group id (RileyInstanceId),
657 // then grouped by their riley geometry prototype id (ProtoInstMap). These
658 // are then grouped by id of the prototype prim they represent (which may be
659 // the invalid id in the case of analytic lights). The top level of this
660 // nested structure may be written to during Populate, therefore access to
661 // the top level is gated behind a mutex lock (built into LockingMap).
662 // Deeper levels are only ever written to from within a single call to
663 // Populate, so they do not have gated access.
664 _LockingProtoMap _protoMap;
665
666 // Locks used by _PopulateInstancesFromChild() to serialize (and dedupe)
667 // parallel calls from the same child instancer, which occur when the child
668 // has multiple prototype prims and would otherwise lead to duplicated
669 // Riley calls to Create or Remove instances, both of which are problematic.
670 _LockingMap<SdfPath, tbb::spin_rw_mutex, SdfPath::Hash> _childPopulateLocks;
671};
672
673PXR_NAMESPACE_CLOSE_SCOPE
674
675#endif // EXT_RMANPKG_25_0_PLUGIN_RENDERMAN_PLUGIN_HD_PRMAN_INSTANCER_H
Defines all the types "TYPED" for which Vt creates a VtTYPEDArray typedef.
This class exists to facilitate point cloud style instancing.
Definition: instancer.h:108
The HdRenderParam is an opaque (to core Hydra) handle, to an object that is obtained from the render ...
Adapter class providing data exchange with the client scene graph.
A path value used to locate objects in layers or scenegraphs.
Definition: path.h:274
Token for efficient comparison, assignment, and hashing of known strings.
Definition: token.h:71
size_t Hash() const
Return a size_t hash for this token.
Definition: token.h:415
Provides a container which may hold any type, and provides introspection and iteration over array typ...
Definition: value.h:147
Describes a primvar.
An array of a value sampled over time, in struct-of-arrays layout.
TfToken class for efficient string referencing and hashing, plus conversions to and from stl string c...