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