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