gprim.h
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 EXT_RMANPKG_24_0_PLUGIN_RENDERMAN_PLUGIN_HD_PRMAN_GPRIM_H
25 #define EXT_RMANPKG_24_0_PLUGIN_RENDERMAN_PLUGIN_HD_PRMAN_GPRIM_H
26 
27 #include "pxr/pxr.h"
28 #include "pxr/imaging/hd/enums.h"
29 #include "pxr/usd/sdf/types.h"
30 #include "pxr/base/gf/matrix4d.h"
31 
32 #include "hdPrman/gprimbase.h"
33 #include "hdPrman/renderParam.h"
34 #include "hdPrman/instancer.h"
35 #include "hdPrman/material.h"
36 #include "hdPrman/rixStrings.h"
37 
38 #include "Riley.h"
39 #include "RixShadingUtils.h"
40 #include "RixPredefinedStrings.hpp"
41 
42 PXR_NAMESPACE_OPEN_SCOPE
43 
46 template <typename BASE>
47 class HdPrman_Gprim : public BASE, public HdPrman_GprimBase
48 {
49 public:
50  using BaseType = BASE;
51 
52  HdPrman_Gprim(SdfPath const& id)
53  : BaseType(id)
54  {
55  }
56 
57  ~HdPrman_Gprim() override = default;
58 
59  void
60  Finalize(HdRenderParam *renderParam) override
61  {
62  HdPrman_RenderParam *param =
63  static_cast<HdPrman_RenderParam*>(renderParam);
64 
65  riley::Riley *riley = param->AcquireRiley();
66 
67  // Release retained conversions of coordSys bindings.
68  param->ReleaseCoordSysBindings(BASE::GetId());
69 
70  // Delete instances before deleting the prototypes they use.
71  for (const auto &id: _instanceIds) {
72  if (id != riley::GeometryInstanceId::InvalidId()) {
73  riley->DeleteGeometryInstance(
74  riley::GeometryPrototypeId::InvalidId(), id);
75  }
76  }
77  _instanceIds.clear();
78  for (const auto &id: _prototypeIds) {
79  if (id != riley::GeometryPrototypeId::InvalidId()) {
80  riley->DeleteGeometryPrototype(id);
81  }
82  }
83  _prototypeIds.clear();
84  }
85 
86  void Sync(HdSceneDelegate* sceneDelegate,
87  HdRenderParam* renderParam,
88  HdDirtyBits* dirtyBits,
89  TfToken const &reprToken) override;
90 
91 protected:
92  HdDirtyBits GetInitialDirtyBitsMask() const override = 0;
93 
94  HdDirtyBits
95  _PropagateDirtyBits(HdDirtyBits bits) const override
96  {
97  // XXX This is not ideal. Currently Riley requires us to provide
98  // all the values anytime we edit a volume. To make sure the values
99  // exist in the value cache, we propagte the dirty bits.value cache,
100  // we propagte the dirty bits.value cache, we propagte the dirty
101  // bits.value cache, we propagte the dirty bits.
102  return bits ? (bits | GetInitialDirtyBitsMask()) : bits;
103  }
104 
105  void
106  _InitRepr(TfToken const &reprToken,
107  HdDirtyBits *dirtyBits) override
108  {
109  TF_UNUSED(reprToken);
110  TF_UNUSED(dirtyBits);
111  // No-op
112  }
113 
114  // Provide a fallback material. Default grabs _fallbackMaterial
115  // from the context.
116  virtual riley::MaterialId
117  _GetFallbackMaterial(HdPrman_RenderParam *renderParam)
118  {
119  return renderParam->GetFallbackMaterialId();
120  }
121 
122  // Populate primType and primvars.
123  virtual RtPrimVarList
124  _ConvertGeometry(HdPrman_RenderParam *renderParam,
125  HdSceneDelegate *sceneDelegate,
126  const SdfPath &id,
127  RtUString *primType,
128  std::vector<HdGeomSubset> *geomSubsets) = 0;
129 
130  // This class does not support copying.
131  HdPrman_Gprim(const HdPrman_Gprim&) = delete;
132  HdPrman_Gprim &operator =(const HdPrman_Gprim&) = delete;
133 
134 };
135 
136 template <typename BASE>
137 void
139  HdRenderParam* renderParam,
140  HdDirtyBits* dirtyBits,
141  TfToken const &reprToken)
142 {
143  HD_TRACE_FUNCTION();
144  HF_MALLOC_TAG_FUNCTION();
145  TF_UNUSED(reprToken);
146 
147  HdPrman_RenderParam *param =
148  static_cast<HdPrman_RenderParam*>(renderParam);
149 
150  // Riley API.
151  riley::Riley *riley = param->AcquireRiley();
152 
153  // Update instance bindings.
154  BASE::_UpdateInstancer(sceneDelegate, dirtyBits);
155 
156  // Prim id
157  SdfPath const& id = BASE::GetId();
158  SdfPath const& instancerId = BASE::GetInstancerId();
159  const bool isHdInstance = !instancerId.IsEmpty();
160  SdfPath primPath = sceneDelegate->GetScenePrimPath(id, 0, nullptr);
161 
162  // Prman has a default value for identifier:id of 0 (in case of ray miss),
163  // while Hydra treats id -1 as the clear value. We map Prman primId as
164  // (Hydra primId + 1) to get around this, here and in
165  // hdPrman/framebuffer.cpp.
166  const int32_t primId = BASE::GetPrimId() + 1;
167 
168  // Sample transform
170  sceneDelegate->SampleTransform(id, &xf);
171 
172  // Update visibility so thet rprim->IsVisible() will work in render pass
173  if (HdChangeTracker::IsVisibilityDirty(*dirtyBits, id)) {
174  BASE::_UpdateVisibility(sceneDelegate, dirtyBits);
175  }
176 
177  // Resolve material binding. Default to fallbackGprimMaterial.
178  if (*dirtyBits & HdChangeTracker::DirtyMaterialId) {
179 #if HD_API_VERSION < 37
180  BASE::_SetMaterialId(sceneDelegate->GetRenderIndex().GetChangeTracker(),
181  sceneDelegate->GetMaterialId(id));
182 #else
183  BASE::SetMaterialId(sceneDelegate->GetMaterialId(id));
184 #endif
185  }
186  riley::MaterialId materialId = _GetFallbackMaterial(param);
187  riley::DisplacementId dispId = riley::DisplacementId::InvalidId();
188  const SdfPath & hdMaterialId = BASE::GetMaterialId();
189  HdPrman_ResolveMaterial(sceneDelegate, hdMaterialId, &materialId, &dispId);
190 
191  // Convert (and cache) coordinate systems.
192  riley::CoordinateSystemList coordSysList = {0, nullptr};
193  if (HdPrman_RenderParam::RileyCoordSysIdVecRefPtr convertedCoordSys =
194  param->ConvertAndRetainCoordSysBindings(sceneDelegate, id)) {
195  coordSysList.count = convertedCoordSys->size();
196  coordSysList.ids = convertedCoordSys->data();
197  }
198 
199  // Hydra dirty bits corresponding to PRMan prototype primvars
200  // and instance attributes.
201  const int prmanPrimvarBits =
202  HdChangeTracker::DirtyPrimvar;
203  const int prmanAttrBits =
204  HdChangeTracker::DirtyVisibility |
205  HdChangeTracker::DirtyTransform;
206 
207  //
208  // Create or modify Riley geometry prototype(s).
209  //
210  std::vector<riley::MaterialId> subsetMaterialIds;
211  std::vector<SdfPath> subsetPaths;
212  {
213  RtUString primType;
214  HdGeomSubsets geomSubsets;
215  RtPrimVarList primvars = _ConvertGeometry(param, sceneDelegate, id,
216  &primType, &geomSubsets);
217 
218  // Transfer material opinions of primvars.
219  HdPrman_TransferMaterialPrimvarOpinions(sceneDelegate, hdMaterialId,
220  primvars);
221 
222  // Adjust _prototypeIds array.
223  const size_t oldCount = _prototypeIds.size();
224  const size_t newCount = std::max((size_t) 1, geomSubsets.size());
225  if (newCount != oldCount) {
226  for (const auto &oldPrototypeId: _prototypeIds) {
227  if (oldPrototypeId != riley::GeometryPrototypeId::InvalidId()) {
228  riley->DeleteGeometryPrototype(oldPrototypeId);
229  }
230  }
231  _prototypeIds.resize(newCount,
232  riley::GeometryPrototypeId::InvalidId());
233  }
234 
235  // Update Riley geom prototypes.
236  if (geomSubsets.empty()) {
237  // Common case: no subsets.
238  TF_VERIFY(newCount == 1);
239  TF_VERIFY(_prototypeIds.size() == 1);
240  if (_prototypeIds[0] == riley::GeometryPrototypeId::InvalidId()) {
241  _prototypeIds[0] = riley->CreateGeometryPrototype(
242  riley::UserId(
243  stats::AddDataLocation(primPath.GetText()).GetValue()),
244  primType, dispId, primvars);
245  } else if (*dirtyBits & prmanPrimvarBits) {
246  riley->ModifyGeometryPrototype(primType, _prototypeIds[0],
247  &dispId, &primvars);
248  }
249  } else {
250  // Subsets case.
251  // We resolve materials here, and hold them in subsetMaterialIds:
252  // Displacement networks are passed to the geom prototype;
253  // material networks are passed to the instances.
254  subsetMaterialIds.reserve(geomSubsets.size());
255 
256  // We also cache the subset paths for re-use when creating the instances
257  subsetPaths.reserve(geomSubsets.size());
258 
259  for (size_t j=0; j < geomSubsets.size(); ++j) {
260  auto& prototypeId = _prototypeIds[j];
261  HdGeomSubset &subset = geomSubsets[j];
262 
263  // Convert indices to int32_t and set as k_shade_faceset.
264  std::vector<int32_t> int32Indices(subset.indices.begin(),
265  subset.indices.end());
266  primvars.SetIntegerArray(RixStr.k_shade_faceset,
267  int32Indices.data(),
268  int32Indices.size());
269  // Look up material override for the subset (if any)
270  riley::MaterialId subsetMaterialId = materialId;
271  riley::DisplacementId subsetDispId = dispId;
272  if (subset.materialId.IsEmpty()) {
273  subset.materialId = hdMaterialId;
274  }
275  HdPrman_ResolveMaterial(sceneDelegate, subset.materialId,
276  &subsetMaterialId, &subsetDispId);
277  subsetMaterialIds.push_back(subsetMaterialId);
278 
279  // Look up the path for the subset
280  SdfPath subsetPath = sceneDelegate->GetScenePrimPath(subset.id, 0, nullptr);
281  subsetPaths.push_back(subsetPath);
282 
283  if (prototypeId == riley::GeometryPrototypeId::InvalidId()) {
284  prototypeId =
285  riley->CreateGeometryPrototype(
286  riley::UserId(
287  stats::AddDataLocation(subsetPath.GetText()).GetValue()),
288  primType, dispId, primvars);
289  } else if (*dirtyBits & prmanPrimvarBits) {
290  riley->ModifyGeometryPrototype(primType, prototypeId,
291  &dispId, &primvars);
292  }
293  }
294  }
295  }
296 
297  //
298  // Create or modify Riley geometry instances.
299  //
300  // Resolve attributes.
301  RtParamList attrs = param->ConvertAttributes(sceneDelegate, id, true);
302  if (!isHdInstance) {
303  // Simple case: Singleton instance.
304  // Convert transform.
306  for (size_t i=0; i < xf.count; ++i) {
307  xf_rt[i] = HdPrman_GfMatrixToRtMatrix(xf.values[i]);
308  }
309  const riley::Transform xform = {
310  unsigned(xf.count),
311  xf_rt.data(),
312  xf.times.data()};
313 
314  // Add "identifier:id" with the hydra prim id, and "identifier:id2"
315  // with the instance number.
316  // XXX Do we want to distinguish facesets here?
317  attrs.SetInteger(RixStr.k_identifier_id, primId);
318  attrs.SetInteger(RixStr.k_identifier_id2, 0);
319  // Adjust _instanceIds array.
320  const size_t newNumHdInstances = 1u;
321  const size_t oldCount = _instanceIds.size();
322  const size_t newCount = newNumHdInstances * _prototypeIds.size();
323  if (newCount != oldCount) {
324  for (const auto &oldInstanceId: _instanceIds) {
325  if (oldInstanceId != riley::GeometryInstanceId::InvalidId()) {
326  riley->DeleteGeometryInstance(
327  riley::GeometryPrototypeId::InvalidId(), oldInstanceId);
328  }
329  }
330  _instanceIds.resize(
331  newCount,
332  riley::GeometryInstanceId::InvalidId());
333  }
334 
335  // Create or modify Riley instances corresponding to a
336  // singleton Hydra instance.
337  TF_VERIFY(_instanceIds.size() == _prototypeIds.size());
338  for (size_t j=0; j < _prototypeIds.size(); ++j) {
339  auto const& prototypeId = _prototypeIds[j];
340  auto& instanceId = _instanceIds[j];
341  auto instanceMaterialId = materialId;
342 
343  // If a subset path was cached, use it. If not, use the prim path.
344  SdfPath* subsetPath(&primPath);
345  if (!subsetPaths.empty()) {
346  subsetPath = &subsetPaths[j];
347  }
348 
349  // If a valid subset material was bound, use it.
350  if (!subsetMaterialIds.empty()) {
351  TF_VERIFY(j < subsetMaterialIds.size());
352  instanceMaterialId = subsetMaterialIds[j];
353  }
354 
355  if (instanceId == riley::GeometryInstanceId::InvalidId()) {
356  instanceId = riley->CreateGeometryInstance(
357  riley::UserId(
358  stats::AddDataLocation(subsetPath->GetText()).GetValue()),
359  riley::GeometryPrototypeId::InvalidId(),
360  prototypeId, instanceMaterialId, coordSysList,
361  xform, attrs);
362  } else if (*dirtyBits & prmanAttrBits) {
363  riley->ModifyGeometryInstance(
364  riley::GeometryPrototypeId::InvalidId(),
365  instanceId, &instanceMaterialId, &coordSysList,
366  &xform, &attrs);
367  }
368  }
369  } else {
370  // Hydra Instancer case.
371  HdRenderIndex &renderIndex = sceneDelegate->GetRenderIndex();
372 
373  // Sync the hydra instancer (note: this is transitional code, it should
374  // be done by the render index...)
375  HdInstancer::_SyncInstancerAndParents(renderIndex, instancerId);
376 
377  HdPrmanInstancer *instancer = static_cast<HdPrmanInstancer*>(
378  renderIndex.GetInstancer(instancerId));
379  VtIntArray instanceIndices =
380  sceneDelegate->GetInstanceIndices(instancerId, id);
381 
382  // Sample per-instance transforms.
384  instancer->SampleInstanceTransforms(id, instanceIndices, &ixf);
385 
386  // Adjust _instanceIds array.
387  // Each Hydra instance produces a Riley instance for each
388  // geometry prototype. The number of geometry prototypes is
389  // based on the number of geometry subsets.
390  const size_t newNumHdInstances =
391  (ixf.count > 0) ? ixf.values[0].size() : 0;
392  const size_t oldCount = _instanceIds.size();
393  const size_t newCount = newNumHdInstances * _prototypeIds.size();
394  if (newCount != oldCount) {
395  for (const auto &oldInstanceId: _instanceIds) {
396  riley->DeleteGeometryInstance(
397  riley::GeometryPrototypeId::InvalidId(),
398  oldInstanceId);
399  }
400  // Clear before resize, because above we just deleted
401  // all the instances, so require re-creating them in riley.
402  _instanceIds.clear();
403  _instanceIds.resize(newCount,
404  riley::GeometryInstanceId::InvalidId());
405  }
406 
407  // Add "identifier:id" with the hydra prim id.
408  attrs.SetInteger(RixStr.k_identifier_id, primId);
409 
410  // Retrieve instance categories.
411  std::vector<VtArray<TfToken>> instanceCategories =
412  sceneDelegate->GetInstanceCategories(instancerId);
413 
414  // Process each Hydra instance.
415  for (size_t i=0; i < newNumHdInstances; ++i) {
416  // XXX: Add support for nested instancing instance primvars.
417  size_t instanceIndex = 0;
418  if (i < instanceIndices.size()) {
419  instanceIndex = instanceIndices[i];
420  }
421 
422  // Create a copy of the instancer attrs.
423  RtParamList instanceAttrs = attrs;
424  instancer->GetInstancePrimvars(id, instanceIndex, instanceAttrs);
425  // Add "identifier:id2" with the instance number.
426  instanceAttrs.SetInteger(RixStr.k_identifier_id2, i);
427 
428  // Convert categories.
429  if (instanceIndex < instanceCategories.size()) {
430  param->ConvertCategoriesToAttributes(
431  id, instanceCategories[instanceIndex], instanceAttrs);
432  }
433 
434  // Convert transform.
435  // PRMan does not allow transforms on geometry prototypes,
436  // so we apply that transform (xf) to all the instances, here.
438  rt_xf(ixf.count);
439 
440  if (xf.count == 0 ||
441  (xf.count == 1 && (xf.values[0] == GfMatrix4d(1)))) {
442  // Expected case: prototype xf is constant & exactly identity.
443  for (size_t j=0; j < ixf.count; ++j) {
444  rt_xf[j] = HdPrman_GfMatrixToRtMatrix(ixf.values[j][i]);
445  }
446  } else {
447  // Multiply resampled prototype xf against instance xforms.
448  for (size_t j=0; j < ixf.count; ++j) {
449  GfMatrix4d xf_j = xf.Resample(ixf.times[j]);
450  rt_xf[j] =
451  HdPrman_GfMatrixToRtMatrix(xf_j * ixf.values[j][i]);
452  }
453  }
454  const riley::Transform xform =
455  { unsigned(ixf.count), rt_xf.data(), ixf.times.data() };
456 
457  // Create or modify Riley instances corresponding to this
458  // Hydra instance.
459  for (size_t j=0; j < _prototypeIds.size(); ++j) {
460  auto const& prototypeId = _prototypeIds[j];
461  auto& instanceId = _instanceIds[i*_prototypeIds.size() + j];
462  auto instanceMaterialId = materialId;
463 
464  // If a subset path was cached, use it. If not, use the prim path.
465  SdfPath* subsetPath(&primPath);
466  if (!subsetPaths.empty()) {
467  subsetPath = &subsetPaths[j];
468  }
469 
470  // If a valid subset material was bound, use it.
471  if (!subsetMaterialIds.empty()) {
472  TF_VERIFY(j < subsetMaterialIds.size());
473  instanceMaterialId = subsetMaterialIds[j];
474  }
475 
476  if (instanceId == riley::GeometryInstanceId::InvalidId()) {
477  // Generate an instance path using the subset path (or prim
478  // path as appropriate) as a base
479  SdfPath instancePath =
480  sceneDelegate->GetScenePrimPath(*subsetPath, i, nullptr);
481  instanceId = riley->CreateGeometryInstance(
482  riley::UserId(
483  stats::AddDataLocation(instancePath.GetText())
484  .GetValue()),
485  riley::GeometryPrototypeId::InvalidId(), prototypeId,
486  instanceMaterialId, coordSysList, xform, instanceAttrs);
487  } else if (*dirtyBits & prmanAttrBits) {
488  riley->ModifyGeometryInstance(
489  riley::GeometryPrototypeId::InvalidId(),
490  instanceId, &instanceMaterialId, &coordSysList,
491  &xform, &instanceAttrs);
492  }
493  }
494  }
495  }
496  *dirtyBits &= ~HdChangeTracker::AllSceneDirtyBits;
497 }
498 
499 PXR_NAMESPACE_CLOSE_SCOPE
500 
501 #endif // EXT_RMANPKG_24_0_PLUGIN_RENDERMAN_PLUGIN_HD_PRMAN_GPRIM_H
The Hydra render index is a flattened representation of the client scene graph, which may be composed...
Definition: renderIndex.h:120
Tracks changes from the HdSceneDelegate, providing invalidation cues to the render engine.
Definition: changeTracker.h:51
This is a small-vector class with local storage optimization, the local storage can be specified via ...
Definition: smallVector.h:177
value_type * data()
Direct access to the underlying array.
Definition: smallVector.h:768
virtual HD_API SdfPath GetMaterialId(SdfPath const &rprimId)
Returns the material ID bound to the rprim rprimId.
virtual HD_API std::vector< VtArray< TfToken > > GetInstanceCategories(SdfPath const &instancerId)
Returns the categories for all instances in the instancer.
The HdRenderParam is an opaque (to core Hydra) handle, to an object that is obtained from the render ...
SDF_API const char * GetText() const
Returns the string representation of this path as a c string.
#define TF_UNUSED(x)
Stops compiler from producing unused argument or variable warnings.
Definition: tf.h:185
Basic Sdf data types.
A common base class for HdPrman_Gprim types.
Definition: gprimbase.h:37
Token for efficient comparison, assignment, and hashing of known strings.
Definition: token.h:87
HD_API HdInstancer * GetInstancer(SdfPath const &id) const
Returns the instancer of id.
virtual HD_API size_t SampleTransform(SdfPath const &id, size_t maxSampleCount, float *sampleTimes, GfMatrix4d *sampleValues)
Store up to maxSampleCount transform samples in *sampleValues.
Stores a 4x4 matrix of double elements.
Definition: matrix4d.h:88
#define TF_VERIFY(cond, format,...)
Checks a condition and reports an error if it evaluates false.
Definition: diagnostic.h:283
Adapter class providing data exchange with the client scene graph.
A mix-in template that adds shared gprim behavior to support various HdRprim types.
Definition: gprim.h:47
TYPE Resample(float u) const
Convience method for invoking HdResampleRawTimeSamples on this HdTimeSampleArray.
A path value used to locate objects in layers or scenegraphs.
Definition: path.h:290
VtIntArray indices
The list of element indices contained in the subset.
Definition: geomSubset.h:56
HD_API bool IsVisibilityDirty(SdfPath const &id)
Returns true if the rprim identified by id has dirty visibility.
SdfPath materialId
The path used to identify this material bound to the subset.
Definition: geomSubset.h:54
virtual HD_API VtIntArray GetInstanceIndices(SdfPath const &instancerId, SdfPath const &prototypeId)
Gets the extracted indices array of the prototype id used in the instancer.
SdfPath id
The path used to identify this subset in the scene.
Definition: geomSubset.h:52
bool IsEmpty() const noexcept
Returns true if this is the empty path (SdfPath::EmptyPath()).
Definition: path.h:419
HdRenderIndex & GetRenderIndex()
Returns the RenderIndex owned by this delegate.
Describes a subset of a piece of geometry as a set of indices.
Definition: geomSubset.h:40
virtual HD_API SdfPath GetScenePrimPath(SdfPath const &rprimId, int instanceIndex, HdInstancerContext *instancerContext=nullptr)
Returns the scene address of the prim corresponding to the given rprim/instance index.