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