diff --git a/libs/common/shape_utils.cpp b/libs/common/shape_utils.cpp index 14b88bb67..be723616b 100644 --- a/libs/common/shape_utils.cpp +++ b/libs/common/shape_utils.cpp @@ -275,9 +275,9 @@ AtArray* GenerateVertexIdxs(const VtIntArray& indices, AtArray* vidxs) return AiArrayCopy(vidxs); const auto numIdxs = static_cast(AiArrayGetNumElements(vidxs)); - auto* array = AiArrayAllocate(numIdxs, 1, AI_TYPE_UINT); - auto* out = static_cast(AiArrayMap(array)); - auto* in = static_cast(AiArrayMap(vidxs)); + AtArray* array = AiArrayAllocate(numIdxs, 1, AI_TYPE_UINT); + uint32_t* out = static_cast(AiArrayMap(array)); + const uint32_t* in = static_cast(AiArrayMapConst(vidxs)); for (unsigned int i = 0; i < numIdxs; ++i) { if (in[i] >= indices.size()) { @@ -288,7 +288,7 @@ AtArray* GenerateVertexIdxs(const VtIntArray& indices, AtArray* vidxs) } AiArrayUnmap(array); - AiArrayUnmap(vidxs); + AiArrayUnmapConst(vidxs); return array; } diff --git a/libs/render_delegate/mesh.cpp b/libs/render_delegate/mesh.cpp index f4a3cb0bb..b45f8333f 100644 --- a/libs/render_delegate/mesh.cpp +++ b/libs/render_delegate/mesh.cpp @@ -174,6 +174,229 @@ inline void _ConvertFaceVaryingPrimvarToBuiltin( } } + + + +// Compile time mapping of USD type to Arnold types +template inline uint32_t GetArnoldTypeFor(const T &) {return AI_TYPE_UNDEFINED;} +template<> inline uint32_t GetArnoldTypeFor(const GfVec3f &) {return AI_TYPE_VECTOR;} +template<> inline uint32_t GetArnoldTypeFor(const VtArray &) {return AI_TYPE_VECTOR;} +template<> inline uint32_t GetArnoldTypeFor(const std::vector &) {return AI_TYPE_VECTOR;} +template<> inline uint32_t GetArnoldTypeFor(const VtArray &) {return AI_TYPE_INT;} +template<> inline uint32_t GetArnoldTypeFor(const VtArray &) {return AI_TYPE_UINT;} + + +// A structure to manage and hold the shared buffers. It allows to benefit from USD deduplication passing the same +// AtArray to arnold when it is presented with the same USD buffer. +struct BufferHolder { + struct HeldArray { + HeldArray(uint32_t nref_, const VtValue& val_) : nref(nref_), val(val_) {} + uint32_t nref; + VtValue val; + }; + + static std::unordered_map _bufferMap; + static std::unordered_map _arrayMap; + static std::mutex _bufferHolderMutex; + + inline static size_t hashBuffers(uint32_t nkeys, const void** buffers) + { + size_t seed = reinterpret_cast(buffers[0]); + std::hash hasher; + for (uint32_t i = 1; i < nkeys; ++i) { + const void* ptr = buffers[i]; + seed ^= hasher(ptr) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; + } + + inline static AtArray* GetCachedArray(size_t buffersHash) + { + const std::lock_guard lock(_bufferHolderMutex); + auto arrayIt = _arrayMap.find(buffersHash); + if (arrayIt != _arrayMap.end()) { + // returns a shallow copy of the array as we know arrayIt->second is a shared array + return AiArrayCopy(arrayIt->second); + } + return nullptr; + } + + // TODO we could store the created AtArray and reuse it to benefit from usd deduplication + template + static AtArray* CreateAtArrayFromTimeSamples(const HdArnoldSampledPrimvarType& timeSamples) + { + if (timeSamples.count == 0) + return nullptr; + + // Unbox + HdArnoldSampledType unboxed; + unboxed.UnboxFrom(timeSamples); + + std::vector ptrsToSamples; // use SmallVector ?? + for (int i = 0; i < unboxed.count; ++i) { + const auto& val = unboxed.values[i]; + ptrsToSamples.push_back(val.data()); + } + const uint32_t nelements = unboxed.values[0].size(); // TODO make sure that values has something + const uint32_t type = GetArnoldTypeFor(unboxed.values[0]); + const uint32_t nkeys = ptrsToSamples.size(); + const void** samples = ptrsToSamples.data(); + const size_t buffersHash = hashBuffers(nkeys, samples); + + AtArray* atArray = GetCachedArray(buffersHash); + if (!atArray) { + atArray = AiArrayMakeShared(nelements, nkeys, type, samples, ReleaseArrayCallback, nullptr); + if (atArray) { + const std::lock_guard lock(_bufferHolderMutex); + for (int i = 0; i < timeSamples.count; ++i) { + const VtValue& val = timeSamples.values[i]; + if (val.template IsHolding()) { + const T& arr = val.template UncheckedGet(); + const void* ptr = static_cast(arr.cdata()); + auto bufferIt = _bufferMap.find(ptr); + if (bufferIt == _bufferMap.end()) { + _bufferMap.emplace(ptr, HeldArray{1, val}); + } else { + HeldArray& held = bufferIt->second; + held.nref++; + } + } + } + _arrayMap.emplace(buffersHash, atArray); + } + } + return atArray; + } + + template + static AtArray* CreateAtArrayFromBuffer( + const T& vtArray, int32_t forcedType = -1, std::string param = std::string()) + { + const void* arr = static_cast(vtArray.cdata()); + // Look for at AtArray stored with the same buffer + if (!arr) { + return nullptr; + } + const uint32_t nelements = vtArray.size(); + const uint32_t type = forcedType == -1 ? GetArnoldTypeFor(vtArray) : forcedType; + const size_t buffersHash = hashBuffers(1, &arr); + + AtArray* atArray = GetCachedArray(buffersHash); + + if (!atArray) { + atArray = AiArrayMakeShared(nelements, type, arr, ReleaseArrayCallback, nullptr); + if (atArray) { + const std::lock_guard lock(_bufferHolderMutex); + auto it = _bufferMap.find(arr); + if (it == _bufferMap.end()) { + _bufferMap.emplace(arr, HeldArray{1, VtValue(vtArray)}); + } else { + // This should rarely happen, only when a single buffer is shared inside keys + HeldArray& held = it->second; + held.nref++; + } + _arrayMap.emplace(buffersHash, atArray); + } + } + return atArray; + } + + static void ReleaseArrayCallback(uint8_t nkeys, const void** buffers, const void* userData) + { + const std::lock_guard lock(_bufferHolderMutex); + const size_t buffersHash = hashBuffers(nkeys, buffers); + auto arrayIt = _arrayMap.find(buffersHash); + if (arrayIt != _arrayMap.end()) { + _arrayMap.erase(arrayIt); + } else { + assert(false); // this should never happen, catch it in debug mode + } + + for (int i = 0; i < nkeys; ++i) { + const void* arr = buffers[i]; + if (arr) { + auto it = _bufferMap.find(arr); + if (it != _bufferMap.end()) { + HeldArray& arr = it->second; + arr.nref--; + if (arr.nref == 0) { + _bufferMap.erase(it); + } + } else { + assert(false); // this should never happen, catch it in debug mode + } + } + } + } +}; +std::unordered_map BufferHolder::_bufferMap; +std::mutex BufferHolder::_bufferHolderMutex; +std::unordered_map BufferHolder::_arrayMap; + +int HdArnoldSharePositionFromPrimvar(AtNode* node, const SdfPath& id, HdSceneDelegate* sceneDelegate, const AtString& paramName, + const HdArnoldRenderParam* param, int deformKeys = HD_ARNOLD_DEFAULT_PRIMVAR_SAMPLES, + const HdArnoldPrimvarMap* primvars = nullptr, HdArnoldSampledPrimvarType *pointsSample = nullptr, HdMesh *mesh=nullptr) +{ + // HdArnoldSampledPrimvarType sample; + if (pointsSample != nullptr) { + + // If pointsSamples has counts it means that the points are computed (skinned) + if (pointsSample->count == 0) { + sceneDelegate->SamplePrimvar(id, HdTokens->points, pointsSample); + } + + // Check if we can/should extrapolate positions based on velocities/accelerations. + HdArnoldSampledType xf; + HdArnoldUnboxSample(*pointsSample, xf); + const auto extrapolatedCount = ExtrapolatePositions(node, paramName, xf, param, deformKeys, primvars); + if (extrapolatedCount != 0) { + // TODO If the points were extrapolated, we used an arnold array and we don't need the pointsSamples anymore, + // we need to delete it + return extrapolatedCount; + } + + // Check if we have varying topology + bool varyingTopology = false; + const auto& v0 = xf.values[0]; + for (const auto &value : xf.values) { + if (value.size() != v0.size()) { + varyingTopology = true; + break; + } + } + + if (varyingTopology) { + // Varying topology, and no velocity. Let's choose which time sample to pick. + // Ideally we'd want time = 0, as this is what will correspond to the amount of + // expected vertices in other static arrays (like vertex indices). But we might + // not always have this time in our list, so we'll use the first positive time + int timeIndex = GetReferenceTimeIndex(xf); + + // Just export a single key since the number of vertices change along the shutter range, + // and we don't have any velocity / acceleration data + auto value = xf.values[timeIndex]; + auto time = xf.times[timeIndex]; + pointsSample->Resize(1); + pointsSample->values[0] = VtValue(value); + pointsSample->times[0] = time; + } else { + // Arnold needs equaly spaced samples, we want to make sure the pointsamples are correct + TfSmallVector timeSamples; + GetShutterTimeSamples(param->GetShutterRange(), xf.count, timeSamples); + for (size_t index = 0; index < xf.count; index++) { + pointsSample->values[index] = xf.Resample(timeSamples[index]); + pointsSample->times[index] = timeSamples[index]; + } + } + + + AiNodeSetArray(node, paramName, BufferHolder::CreateAtArrayFromTimeSamples(*pointsSample)); + return pointsSample->count; + } + + return 1; +} + /** If normals have a different amount of keys than the vertex positions, Arnold will return an error. This function will handle the remapping, @@ -252,9 +475,10 @@ void HdArnoldMesh::Sync( TF_UNUSED(reprToken); HdArnoldRenderParamInterrupt param(renderParam); const auto& id = GetId(); - - HdArnoldSampledPrimvarType pointsSample; - bool dirtyPrimvars = HdArnoldGetComputedPrimvars(sceneDelegate, id, *dirtyBits, _primvars, nullptr, &pointsSample) || + HdArnoldSampledPrimvarType _pointsSample; + // VtValue _vertexCountsVtValue; ///< Vertex nsides + // VtValue _vertexIndicesVtValue; + const auto dirtyPrimvars = HdArnoldGetComputedPrimvars(sceneDelegate, id, *dirtyBits, _primvars, nullptr, &_pointsSample) || (*dirtyBits & HdChangeTracker::DirtyPrimvar); // We need to set the deform keys first if it is specified @@ -277,67 +501,63 @@ void HdArnoldMesh::Sync( _numberOfPositionKeys = 1; } else if (HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, HdTokens->points)) { param.Interrupt(); - _numberOfPositionKeys = HdArnoldSetPositionFromPrimvar(node, - id, sceneDelegate, str::vlist, param(), GetDeformKeys(), &_primvars, &pointsSample); - - // If this mesh has subdivision, and the positions have changed, we must ensure the - // non-constant primvars are updated again. - if (!dirtyPrimvars && AiNodeGetByte(node, str::subdiv_iterations) > 0) { - dirtyPrimvars = true; - positionsChanged = true; - } + _numberOfPositionKeys = HdArnoldSharePositionFromPrimvar(node, id, sceneDelegate, str::vlist, param(), GetDeformKeys(), &_primvars, &_pointsSample, this); + } + bool isLeftHanded = false; + const auto dirtyTopology = HdChangeTracker::IsTopologyDirty(*dirtyBits, id); if (dirtyTopology) { param.Interrupt(); const auto topology = GetMeshTopology(sceneDelegate); // We have to flip the orientation if it's left handed. - const auto isLeftHanded = topology.GetOrientation() == PxOsdOpenSubdivTokens->leftHanded; - _vertexCounts = topology.GetFaceVertexCounts(); - const auto& vertexIndices = topology.GetFaceVertexIndices(); + isLeftHanded = topology.GetOrientation() == PxOsdOpenSubdivTokens->leftHanded; + + // Keep a reference on the vertex buffers as long as this object is live + // We try to keep the buffer consts as otherwise usd will duplicate them (COW) + const VtIntArray &vertexCounts = topology.GetFaceVertexCounts(); + const VtIntArray &vertexIndices = topology.GetFaceVertexIndices(); + const auto numFaces = topology.GetNumFaces(); - auto* nsidesArray = AiArrayAllocate(numFaces, 1, AI_TYPE_UINT); - auto* vidxsArray = AiArrayAllocate(vertexIndices.size(), 1, AI_TYPE_UINT); - auto* nsides = static_cast(AiArrayMap(nsidesArray)); - auto* vidxs = static_cast(AiArrayMap(vidxsArray)); + // Check if the vertex count buffer contains negative value + const bool hasNegativeValues = std::any_of(vertexCounts.cbegin(), vertexCounts.cend(), [](int i) {return i < 0;}); _vertexCountSum = 0; - // We are manually calculating the sum of the vertex counts here, because we are filtering for negative values - // from the vertex indices array. - if (isLeftHanded) { - for (auto i = decltype(numFaces){0}; i < numFaces; ++i) { - const auto vertexCount = _vertexCounts[i]; - if (Ai_unlikely(_vertexCounts[i] <= 0)) { - nsides[i] = 0; - continue; - } - nsides[i] = vertexCount; - for (auto vertex = decltype(vertexCount){0}; vertex < vertexCount; vertex += 1) { - vidxs[_vertexCountSum + vertexCount - vertex - 1] = - static_cast(vertexIndices[_vertexCountSum + vertex]); + // If the buffer is left handed or has negative values, we must allocate a new one to make it work with arnold + if (isLeftHanded || hasNegativeValues) { + VtIntArray vertexCountsTmp = topology.GetFaceVertexCounts(); + VtIntArray vertexIndicesTmp = topology.GetFaceVertexIndices(); + assert(vertexCountsTmp.size() == numFaces); + if (Ai_unlikely(hasNegativeValues)) { + std::transform(vertexCountsTmp.cbegin(), vertexCountsTmp.cend(), vertexCountsTmp.begin(), [] (const int i){return i < 0 ? 0 : i;}); + } + if (isLeftHanded) { + for (int i = 0; i < numFaces; ++i) { + const int vertexCount = vertexCountsTmp[i]; + for (int vertexIdx = 0; vertexIdx < vertexCount; vertexIdx += 1) { + vertexIndicesTmp[_vertexCountSum + vertexCount - vertexIdx - 1] = vertexIndices[_vertexCountSum + vertexIdx]; + } + _vertexCountSum += vertexCount; } - _vertexCountSum += vertexCount; + } else { + _vertexCountSum = std::accumulate(vertexCounts.cbegin(), vertexCounts.cend(), 0); } + // Keep the buffers alive + _vertexCountsVtValue = VtValue(vertexCountsTmp); + _vertexIndicesVtValue = VtValue(vertexIndicesTmp); + AiNodeSetArray(GetArnoldNode(), str::nsides, BufferHolder::CreateAtArrayFromBuffer(vertexCountsTmp, AI_TYPE_UINT)); + AiNodeSetArray(GetArnoldNode(), str::vidxs, BufferHolder::CreateAtArrayFromBuffer(vertexIndicesTmp, AI_TYPE_UINT) ); + } else { - // We still need _vertexCountSum as it is used to validate primvars. - // We are manually calculating the sum of the vertex counts here, because we are filtering for negative - // values from the vertex indices array. - std::transform(_vertexCounts.begin(), _vertexCounts.end(), nsides, [&](const int i) -> uint32_t { - if (Ai_unlikely(i <= 0)) { - return 0; - } - const auto vertexCount = static_cast(i); - _vertexCountSum += vertexCount; - return vertexCount; - }); - std::transform(vertexIndices.begin(), vertexIndices.end(), vidxs, [](const int i) -> uint32_t { - return Ai_unlikely(i <= 0) ? 0 : static_cast(i); - }); - _vertexCounts = {}; // We don't need this anymore. + _vertexCountSum = std::accumulate(vertexCounts.cbegin(), vertexCounts.cend(), 0); + // Keep the buffers alive + _vertexCountsVtValue = VtValue(vertexCounts); + _vertexIndicesVtValue = VtValue(vertexIndices); + AiNodeSetArray(GetArnoldNode(), str::nsides, BufferHolder::CreateAtArrayFromBuffer(vertexCounts, AI_TYPE_UINT)); + AiNodeSetArray(GetArnoldNode(), str::vidxs, BufferHolder::CreateAtArrayFromBuffer(vertexIndices, AI_TYPE_UINT) ); } - AiNodeSetArray(node, str::nsides, nsidesArray); - AiNodeSetArray(node, str::vidxs, vidxsArray); + const auto scheme = topology.GetScheme(); if (scheme == PxOsdOpenSubdivTokens->catmullClark || scheme == _tokens->catmark) { AiNodeSetStr(node, str::subdiv_type, str::catclark); @@ -432,7 +652,7 @@ void HdArnoldMesh::Sync( param.Interrupt(); const auto isVolume = _IsVolume(); AtNode *meshLight = _GetMeshLight(sceneDelegate, id); - + const VtIntArray *leftHandedVertexCounts = isLeftHanded ? &_vertexCountsVtValue.UncheckedGet() : nullptr; for (auto& primvar : _primvars) { auto& desc = primvar.second; // If the positions have changed, then all non-constant primvars must be updated @@ -516,7 +736,7 @@ void HdArnoldMesh::Sync( } else if (desc.interpolation == HdInterpolationFaceVarying) { if (primvar.first == _tokens->st || primvar.first == _tokens->uv) { _ConvertFaceVaryingPrimvarToBuiltin( - node, desc.value, desc.valueIndices, str::uvlist, str::uvidxs, &_vertexCounts, + node, desc.value, desc.valueIndices, str::uvlist, str::uvidxs, leftHandedVertexCounts, desc.valueIndices.empty() ? &_vertexCountSum : nullptr); } else if (primvar.first == HdTokens->normals) { // The number of motion keys has to be matched between points and normals, so if there are multiple @@ -531,16 +751,17 @@ void HdArnoldMesh::Sync( } _ConvertFaceVaryingPrimvarToBuiltin( node, sample, sample.indices.empty() ? VtIntArray{} : sample.indices[0], - str::nlist, str::nidxs, &_vertexCounts, &_vertexCountSum); + str::nlist, str::nidxs, leftHandedVertexCounts, &_vertexCountSum); } else { _ConvertFaceVaryingPrimvarToBuiltin( - node, desc.value, desc.valueIndices, str::nlist, str::nidxs, &_vertexCounts, + node, desc.value, desc.valueIndices, str::nlist, str::nidxs, leftHandedVertexCounts, desc.valueIndices.empty() ? &_vertexCountSum : nullptr); } } else { HdArnoldSetFaceVaryingPrimvar( - node, primvar.first, desc.role, desc.value, GetRenderDelegate(), desc.valueIndices, &_vertexCounts, + // TODO check leftHandedVertexCounts + node, primvar.first, desc.role, desc.value, GetRenderDelegate(), desc.valueIndices, leftHandedVertexCounts, &_vertexCountSum); } } diff --git a/libs/render_delegate/mesh.h b/libs/render_delegate/mesh.h index f52208247..66bb262df 100755 --- a/libs/render_delegate/mesh.h +++ b/libs/render_delegate/mesh.h @@ -93,7 +93,8 @@ class HdArnoldMesh : public HdArnoldRprim { HdArnoldPrimvarMap _primvars; ///< Precomputed list of primvars. HdArnoldSubsets _subsets; ///< Material ids from subsets. - VtIntArray _vertexCounts; ///< Vertex Counts array for reversing vertex and primvar polygon order. + VtValue _vertexCountsVtValue; ///< Vertex nsides + VtValue _vertexIndicesVtValue; size_t _vertexCountSum = 0; ///< Sum of the vertex counts array. size_t _numberOfPositionKeys = 1; ///< Number of vertex position keys for the mesh. AtNode *_geometryLight = nullptr; ///< Eventual mesh light for this polymesh diff --git a/libs/render_delegate/utils.cpp b/libs/render_delegate/utils.cpp index 206655ade..6540a0e67 100644 --- a/libs/render_delegate/utils.cpp +++ b/libs/render_delegate/utils.cpp @@ -77,19 +77,6 @@ inline bool _TokenStartsWithToken(const TfToken& t0, const TfToken& t1) return strncmp(t0.GetText(), t1.GetText(), t1.size()) == 0; } -// Find the index of the reference time (time == 0) or above -template -int GetReferenceTimeIndex(const SampledTyped &xf) { - int timeIndex = 0; - for (size_t i = 0; i < xf.times.size(); ++i) { - if (xf.times[i] >= 0) { - timeIndex = i; - break; - } - } - return timeIndex; -} - inline size_t _ExtrapolatePositions( AtNode* node, const AtString& paramName, HdArnoldSampledType& xf, const HdArnoldRenderParam* param, int deformKeys, const HdArnoldPrimvarMap* primvars) @@ -187,7 +174,6 @@ inline size_t _ExtrapolatePositions( } // namespace - void HdArnoldSetTransform(AtNode* node, HdSceneDelegate* sceneDelegate, const SdfPath& id) { HdArnoldSampledMatrixType xf{}; @@ -564,7 +550,7 @@ size_t HdArnoldSetPositionFromPrimvar( return 0; } // Check if we can/should extrapolate positions based on velocities/accelerations. - const auto extrapolatedCount = _ExtrapolatePositions(node, paramName, xf, param, deformKeys, primvars); + const auto extrapolatedCount = ExtrapolatePositions(node, paramName, xf, param, deformKeys, primvars); if (extrapolatedCount != 0) { return extrapolatedCount; } @@ -793,4 +779,109 @@ AtArray* HdArnoldGetShidxs(const HdGeomSubsets& subsets, int numFaces, HdArnoldS return shidxsArray; } +size_t ExtrapolatePositions( + AtNode* node, const AtString& paramName, HdArnoldSampledType& xf, const HdArnoldRenderParam* param, + int deformKeys, const HdArnoldPrimvarMap* primvars) +{ + // If velocity or acceleration primvars are present, we want to use them to extrapolate + // the positions for motion blur, instead of relying on positions at different time samples. + // This allow to support varying topologies with motion blur + if (primvars == nullptr || Ai_unlikely(param == nullptr) || param->InstananeousShutter()) { + return 0; + } + + // Check if primvars or positions exists. These arrays are COW. + VtVec3fArray emptyVelocities; + VtVec3fArray emptyAccelerations; + auto primvarIt = primvars->find(HdTokens->velocities); + const VtVec3fArray& velocities = (primvarIt != primvars->end() && primvarIt->second.value.IsHolding()) + ? primvarIt->second.value.UncheckedGet() + : emptyVelocities; + + primvarIt = primvars->find(HdTokens->accelerations); + const VtVec3fArray& accelerations = + (primvarIt != primvars->end() && primvarIt->second.value.IsHolding()) + ? primvarIt->second.value.UncheckedGet() + : emptyAccelerations; + + // The positions in xf contain several several time samples, but the amount of vertices + // can change in each sample. We want to consider the positions at the proper time, so + // that we can apply the velocities/accelerations + // First, let's check if one of the times is 0 (current frame) + int timeIndex = -1; + for (size_t i = 0; i < xf.times.size(); ++i) { + if (xf.times[i] == 0) { + timeIndex = i; + break; + } + } + // If no proper time was found, let's pick the first sample that has the same + // size as the velocities + size_t velocitiesSize = velocities.size(); + if (timeIndex < 0) { + for (size_t i = 0; i < xf.values.size(); ++i) { + if (velocitiesSize > 0 && xf.values[i].size() == velocitiesSize) { + timeIndex = i; + break; + } + } + } + // If we still couldn't find a proper time, let's pick the first sample that has the same + // size as the accelerations + size_t accelerationsSize = accelerations.size(); + if (timeIndex < 0) { + for (size_t i = 0; i < xf.values.size(); ++i) { + if (accelerationsSize > 0 && xf.values[i].size() == accelerationsSize) { + timeIndex = i; + break; + } + } + } + + if (timeIndex < 0) + return 0; // We couldn't find a proper time sample to read positions + + const auto& positions = xf.values[timeIndex]; + const auto numPositions = positions.size(); + const auto hasVelocity = !velocities.empty() && numPositions == velocities.size(); + const auto hasAcceleration = !accelerations.empty() && numPositions == accelerations.size(); + + if (!hasVelocity && !hasAcceleration) { + // No velocity or acceleration, or incorrect sizes for both. + return 0; + } + const auto& t0 = xf.times[timeIndex]; + auto shutter = param->GetShutterRange(); + const auto numKeys = hasAcceleration ? deformKeys : std::min(2, deformKeys); + TfSmallVector times; + times.resize(numKeys); + if (numKeys == 1) { + times[0] = 0.0; + } else { + times[0] = shutter[0]; + for (auto i = decltype(numKeys){1}; i < numKeys - 1; i += 1) { + times[i] = AiLerp(static_cast(i) / static_cast(numKeys - 1), shutter[0], shutter[1]); + } + times[numKeys - 1] = shutter[1]; + } + const auto fps = 1.0f / param->GetFPS(); + const auto fps2 = fps * fps; + auto* array = AiArrayAllocate(numPositions, numKeys, AI_TYPE_VECTOR); + if (numPositions > 0 && numKeys > 0) { + auto* data = reinterpret_cast(AiArrayMap(array)); + for (auto pid = decltype(numPositions){0}; pid < numPositions; pid += 1) { + const auto p = positions[pid]; + const auto v = hasVelocity ? velocities[pid] * fps : GfVec3f{0.0f}; + const auto a = hasAcceleration ? accelerations[pid] * fps2 : GfVec3f{0.0f}; + for (auto tid = decltype(numKeys){0}; tid < numKeys; tid += 1) { + const auto t = t0 + times[tid]; + data[pid + tid * numPositions] = p + (v + a * t * 0.5f) * t; + } + } + AiArrayUnmap(array); + } + AiNodeSetArray(node, paramName, array); + return numKeys; +} + PXR_NAMESPACE_CLOSE_SCOPE diff --git a/libs/render_delegate/utils.h b/libs/render_delegate/utils.h index 544df8db3..ca1cddc25 100644 --- a/libs/render_delegate/utils.h +++ b/libs/render_delegate/utils.h @@ -518,4 +518,26 @@ void HdArnoldGetPrimvars( HDARNOLD_API AtArray* HdArnoldGetShidxs(const HdGeomSubsets& subsets, int numFaces, HdArnoldSubsets& arnoldSubsets); + +/// Use the velocities and accelerations primvars to extrapolate the positions. +/// +/// +size_t ExtrapolatePositions(AtNode* node, const AtString& paramName, HdArnoldSampledType& xf, const HdArnoldRenderParam* param, int deformKeys, const HdArnoldPrimvarMap* primvars); + + +// Find the index of the reference time (time == 0) or above + +template +inline +int GetReferenceTimeIndex(const SampledTyped &xf) { + int timeIndex = 0; + for (size_t i = 0; i < xf.times.size(); ++i) { + if (xf.times[i] >= 0) { + timeIndex = i; + break; + } + } + return timeIndex; +} + PXR_NAMESPACE_CLOSE_SCOPE diff --git a/libs/translator/writer/prim_writer.cpp b/libs/translator/writer/prim_writer.cpp index 4e2f64a71..1e123fd0d 100644 --- a/libs/translator/writer/prim_writer.cpp +++ b/libs/translator/writer/prim_writer.cpp @@ -718,7 +718,7 @@ static inline bool convertArnoldAttribute( switch (arrayType) { case AI_TYPE_BYTE: { std::vector > vtMotionArray(numKeys); - unsigned char* arrayMap = (unsigned char*)AiArrayMap(array); + const unsigned char* arrayMap = (const unsigned char*)AiArrayMapConst(array); for (unsigned int j = 0; j < numKeys; ++j) { VtArray& vtArr = vtMotionArray[j]; vtArr.resize(numElements); @@ -731,7 +731,7 @@ static inline bool convertArnoldAttribute( } case AI_TYPE_INT: { std::vector > vtMotionArray(numKeys); - int* arrayMap = (int*)AiArrayMap(array); + const int* arrayMap = (const int*)AiArrayMapConst(array); for (unsigned int j = 0; j < numKeys; ++j) { VtArray& vtArr = vtMotionArray[j]; vtArr.resize(numElements); @@ -739,12 +739,12 @@ static inline bool convertArnoldAttribute( } typeName = SdfValueTypeNames->IntArray; attrWriter.ProcessAttributeKeys(writer, typeName, vtMotionArray, motionStart, motionEnd); - AiArrayUnmap(array); + AiArrayUnmapConst(array); break; } case AI_TYPE_UINT: { std::vector > vtMotionArray(numKeys); - unsigned int* arrayMap = (unsigned int*)AiArrayMap(array); + const unsigned int* arrayMap = (const unsigned int*)AiArrayMapConst(array); for (unsigned int j = 0; j < numKeys; ++j) { VtArray& vtArr = vtMotionArray[j]; vtArr.resize(numElements); @@ -752,12 +752,12 @@ static inline bool convertArnoldAttribute( } typeName = SdfValueTypeNames->UIntArray; attrWriter.ProcessAttributeKeys(writer, typeName, vtMotionArray, motionStart, motionEnd); - AiArrayUnmap(array); + AiArrayUnmapConst(array); break; } case AI_TYPE_BOOLEAN: { std::vector > vtMotionArray(numKeys); - bool* arrayMap = (bool*)AiArrayMap(array); + const bool* arrayMap = (const bool*)AiArrayMapConst(array); for (unsigned int j = 0; j < numKeys; ++j) { VtArray& vtArr = vtMotionArray[j]; vtArr.resize(numElements); @@ -770,7 +770,7 @@ static inline bool convertArnoldAttribute( } case AI_TYPE_FLOAT: { std::vector > vtMotionArray(numKeys); - float* arrayMap = (float*)AiArrayMap(array); + const float* arrayMap = (const float*)AiArrayMapConst(array); for (unsigned int j = 0; j < numKeys; ++j) { VtArray& vtArr = vtMotionArray[j]; vtArr.resize(numElements); @@ -778,12 +778,12 @@ static inline bool convertArnoldAttribute( } typeName = SdfValueTypeNames->FloatArray; attrWriter.ProcessAttributeKeys(writer, typeName, vtMotionArray, motionStart, motionEnd); - AiArrayUnmap(array); + AiArrayUnmapConst(array); break; } case AI_TYPE_RGB: { std::vector > vtMotionArray(numKeys); - GfVec3f* arrayMap = (GfVec3f*)AiArrayMap(array); + const GfVec3f* arrayMap = (GfVec3f*)AiArrayMapConst(array); for (unsigned int j = 0; j < numKeys; ++j) { VtArray& vtArr = vtMotionArray[j]; vtArr.resize(numElements); @@ -796,7 +796,7 @@ static inline bool convertArnoldAttribute( } case AI_TYPE_VECTOR: { std::vector > vtMotionArray(numKeys); - GfVec3f* arrayMap = (GfVec3f*)AiArrayMap(array); + const GfVec3f* arrayMap = (GfVec3f*)AiArrayMapConst(array); for (unsigned int j = 0; j < numKeys; ++j) { VtArray& vtArr = vtMotionArray[j]; vtArr.resize(numElements); @@ -809,7 +809,7 @@ static inline bool convertArnoldAttribute( } case AI_TYPE_RGBA: { std::vector > vtMotionArray(numKeys); - GfVec4f* arrayMap = (GfVec4f*)AiArrayMap(array); + const GfVec4f* arrayMap = (GfVec4f*)AiArrayMapConst(array); for (unsigned int j = 0; j < numKeys; ++j) { VtArray& vtArr = vtMotionArray[j]; vtArr.resize(numElements); @@ -822,7 +822,7 @@ static inline bool convertArnoldAttribute( } case AI_TYPE_VECTOR2: { std::vector > vtMotionArray(numKeys); - GfVec2f* arrayMap = (GfVec2f*)AiArrayMap(array); + const GfVec2f* arrayMap = (GfVec2f*)AiArrayMapConst(array); for (unsigned int j = 0; j < numKeys; ++j) { VtArray& vtArr = vtMotionArray[j]; vtArr.resize(numElements); @@ -846,7 +846,7 @@ static inline bool convertArnoldAttribute( } case AI_TYPE_MATRIX: { std::vector > vtMotionArray(numKeys); - AtMatrix* arrayMap = (AtMatrix*)AiArrayMap(array); + const AtMatrix* arrayMap = (AtMatrix*)AiArrayMapConst(array); if (arrayMap) { int index = 0; for (unsigned int j = 0; j < numKeys; ++j) { @@ -1124,7 +1124,7 @@ void UsdArnoldPrimWriter::_WriteMatrix(UsdGeomXformable& xformable, const AtNode unsigned int numKeys = AiArrayGetNumKeys(array); - AtMatrix* matrices = (AtMatrix*)AiArrayMap(array); + const AtMatrix* matrices = (const AtMatrix*)AiArrayMapConst(array); if (matrices == nullptr) return; @@ -1165,7 +1165,7 @@ void UsdArnoldPrimWriter::_WriteMatrix(UsdGeomXformable& xformable, const AtNode float time = _motionStart; for (unsigned int k = 0; k < numKeys; ++k) { - AtMatrix& mtx = matrices[k]; + const AtMatrix& mtx = matrices[k]; for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { m[i][j] = mtx[i][j]; @@ -1174,7 +1174,7 @@ void UsdArnoldPrimWriter::_WriteMatrix(UsdGeomXformable& xformable, const AtNode writer.SetAttribute(attr, GfMatrix4d(m), (hasMotion) ? &time : nullptr); time += timeDelta; } - AiArrayUnmap(array); + AiArrayUnmapConst(array); } static void processMaterialBinding(AtNode* shader, AtNode* displacement, UsdPrim& prim, UsdArnoldWriter& writer) diff --git a/libs/translator/writer/write_geometry.cpp b/libs/translator/writer/write_geometry.cpp index 1c32101ec..78755a0d3 100644 --- a/libs/translator/writer/write_geometry.cpp +++ b/libs/translator/writer/write_geometry.cpp @@ -94,7 +94,7 @@ void UsdArnoldWriteMesh::Write(const AtNode *node, UsdArnoldWriter &writer) UsdGeomPrimvar uvPrimVar = primvarAPI.CreatePrimvar(uvToken, SdfValueTypeNames->Float2Array, UsdGeomTokens->faceVarying, uvlistNumElems); VtArray uvValues(uvlistNumElems); - AtVector2 *uvArrayValues = static_cast(AiArrayMap(uvlist)); + const AtVector2 *uvArrayValues = static_cast(AiArrayMapConst(uvlist)); for (unsigned int i = 0; i < uvlistNumElems; ++i) { uvValues[i] = GfVec2f(uvArrayValues[i].x, uvArrayValues[i].y); } @@ -105,7 +105,7 @@ void UsdArnoldWriteMesh::Write(const AtNode *node, UsdArnoldWriter &writer) AtArray *uvidxsArray = AiNodeGetArray(node, AtString("uvidxs")); unsigned int uvidxsSize = (uvidxsArray) ? AiArrayGetNumElements(uvidxsArray) : 0; if (uvidxsSize > 0) { - uint32_t *uvidxs = static_cast(AiArrayMap(uvidxsArray)); + const uint32_t *uvidxs = static_cast(AiArrayMapConst(uvidxsArray)); VtIntArray vtIndices(uvidxsSize); for (unsigned int i = 0; i < uvidxsSize; ++i) { @@ -125,7 +125,7 @@ void UsdArnoldWriteMesh::Write(const AtNode *node, UsdArnoldWriter &writer) VtArray normalsValues(nlistNumElems); unsigned int nlistNumKeys = AiArrayGetNumKeys(nlist); - AtVector *nlistArrayValues = static_cast(AiArrayMap(nlist)); + const AtVector *nlistArrayValues = static_cast(AiArrayMapConst(nlist)); if (nlistNumKeys > 1 && _motionStart < _motionEnd) { float timeDelta = (_motionEnd - _motionStart) / (int)(nlistNumKeys - 1); @@ -148,7 +148,7 @@ void UsdArnoldWriteMesh::Write(const AtNode *node, UsdArnoldWriter &writer) AtArray *nidxsArray = AiNodeGetArray(node, AtString("nidxs")); unsigned int nidxsSize = (nidxsArray) ? AiArrayGetNumElements(nidxsArray) : 0; if (nidxsSize > 0) { - uint32_t *nidxs = static_cast(AiArrayMap(nidxsArray)); + const uint32_t *nidxs = static_cast(AiArrayMapConst(nidxsArray)); VtIntArray vtIndices(nidxsSize); for (unsigned int i = 0; i < nidxsSize; ++i) { vtIndices[i] = nidxs[i]; @@ -237,7 +237,7 @@ void UsdArnoldWriteCurves::Write(const AtNode *node, UsdArnoldWriter &writer) unsigned int numPointsCount = (numPointsArray) ? AiArrayGetNumElements(numPointsArray) : 0; if (numPointsCount > 0) { VtArray vertexCountArray(numPointsCount); - unsigned int *in = static_cast(AiArrayMap(numPointsArray)); + const unsigned int *in = static_cast(AiArrayMapConst(numPointsArray)); for (unsigned int i = 0; i < numPointsCount; ++i) { vertexCountArray[i] = (int)in[i]; } @@ -251,7 +251,7 @@ void UsdArnoldWriteCurves::Write(const AtNode *node, UsdArnoldWriter &writer) unsigned int radiusCount = (radiusArray) ? AiArrayGetNumElements(radiusArray) : 0; if (radiusCount > 0) { VtArray widthArray(radiusCount); - float *in = static_cast(AiArrayMap(radiusArray)); + const float *in = static_cast(AiArrayMapConst(radiusArray)); for (unsigned int i = 0; i < radiusCount; ++i) { widthArray[i] = in[i] * 2.f; } @@ -291,7 +291,7 @@ void UsdArnoldWritePoints::Write(const AtNode *node, UsdArnoldWriter &writer) unsigned int radiusCount = (radiusArray) ? AiArrayGetNumElements(radiusArray) : 0; if (radiusCount > 0) { VtArray widthArray(radiusCount); - float *in = static_cast(AiArrayMap(radiusArray)); + const float *in = static_cast(AiArrayMapConst(radiusArray)); for (unsigned int i = 0; i < radiusCount; ++i) { widthArray[i] = in[i] * 2.f; }