diff --git a/pxr/imaging/hdx/pickTask.cpp b/pxr/imaging/hdx/pickTask.cpp index 8ed2d6e89e..6923db74bd 100644 --- a/pxr/imaging/hdx/pickTask.cpp +++ b/pxr/imaging/hdx/pickTask.cpp @@ -34,6 +34,7 @@ #include "pxr/imaging/hd/renderIndex.h" #include "pxr/imaging/hd/renderPass.h" #include "pxr/imaging/hd/types.h" +#include "pxr/imaging/hd/vtBufferSource.h" #include "pxr/imaging/hdSt/renderBuffer.h" #include "pxr/imaging/hdSt/renderDelegate.h" @@ -56,6 +57,17 @@ PXR_NAMESPACE_OPEN_SCOPE TF_DEFINE_PUBLIC_TOKENS(HdxPickTokens, HDX_PICK_TOKENS); +TF_DEFINE_PRIVATE_TOKENS( + _HdxPickTokens, + (PickBuffer) + (PickBufferBinding) + (Picking) +); + +static const int PICK_BUFFER_HEADER_SIZE = 8; +static const int PICK_BUFFER_SUBBUFFER_CAPACITY = 32; +static const int PICK_BUFFER_ENTRY_SIZE = 3; + static HdRenderPassStateSharedPtr _InitIdRenderPassState(HdRenderIndex *index) { @@ -117,6 +129,22 @@ HdxPickTask::~HdxPickTask() void HdxPickTask::_InitIfNeeded() { + // Init pick buffer + if (!_pickBuffer) { + HdStResourceRegistrySharedPtr const& hdStResourceRegistry = + std::dynamic_pointer_cast( + _index->GetResourceRegistry()); + + HdBufferSpecVector bufferSpecs { + { _HdxPickTokens->PickBuffer, HdTupleType{ HdTypeInt32, 1 } } + }; + + _pickBuffer = hdStResourceRegistry->AllocateSingleBufferArrayRange( + _HdxPickTokens->Picking, + bufferSpecs, + HdBufferArrayUsageHint()); + } + if (_pickableAovBuffers.empty()) { _CreateAovBindings(); } @@ -496,12 +524,18 @@ HdxPickTask::Sync(HdSceneDelegate* delegate, state->SetStencilEnabled(false); } + // disable depth write for the main pass when resolving 'deep' + bool enableDepthWrite = + (state == _occluderRenderPassState) || + (_contextParams.resolveMode != HdxPickTokens->resolveDeep); + state->SetEnableDepthTest(true); - state->SetEnableDepthMask(true); + state->SetEnableDepthMask(enableDepthWrite); state->SetDepthFunc(HdCmpFuncLEqual); - // Make sure translucent pixels can be picked by not discarding them - state->SetAlphaThreshold(0.0f); + // Set alpha threshold, to potentially discard translucent pixels. + // The default value of 0 makes all the pixels pickable + state->SetAlphaThreshold(_contextParams.alphaThreshold); state->SetAlphaToCoverageEnabled(false); state->SetBlendEnabled(false); state->SetCullStyle(_params.cullStyle); @@ -585,9 +619,94 @@ HdxPickTask::Prepare(HdTaskContext* ctx, _pickableRenderPassState->Prepare(renderIndex->GetResourceRegistry()); if (_UseWidgetPass()) { _widgetRenderPassState->Prepare(renderIndex->GetResourceRegistry()); + } + + _ClearPickBuffer(); + + // Prepare pick buffer binding + HdStRenderPassState* extendedState = + dynamic_cast(_pickableRenderPassState.get()); + + HdStRenderPassShaderSharedPtr renderPassShader = + extendedState ? extendedState->GetRenderPassShader() : nullptr; + + if (renderPassShader) { + if (_pickBuffer) { + renderPassShader->AddBufferBinding( + HdBindingRequest( + HdBinding::SSBO, + _HdxPickTokens->PickBufferBinding, + _pickBuffer, + false)); + } + else { + renderPassShader->RemoveBufferBinding( + _HdxPickTokens->PickBufferBinding); + } } } +void +HdxPickTask::_ClearPickBuffer() +{ + if (!_pickBuffer) { + return; + } + + // populate pick buffer source array + VtIntArray pickBufferInit; + if (_contextParams.resolveMode == HdxPickTokens->resolveDeep) + { + const int numSubBuffers = + _contextParams.maxNumDeepEntries / PICK_BUFFER_SUBBUFFER_CAPACITY; + const int entryStorageOffset = + PICK_BUFFER_HEADER_SIZE + numSubBuffers; + const int entryStorageSize = + numSubBuffers * PICK_BUFFER_SUBBUFFER_CAPACITY * PICK_BUFFER_ENTRY_SIZE; + + pickBufferInit.reserve(entryStorageOffset + entryStorageSize); + + // populate pick buffer header + pickBufferInit.push_back(numSubBuffers); + pickBufferInit.push_back(PICK_BUFFER_SUBBUFFER_CAPACITY); + pickBufferInit.push_back(PICK_BUFFER_HEADER_SIZE); + pickBufferInit.push_back(entryStorageOffset); + + pickBufferInit.push_back( + _contextParams.pickTarget == HdxPickTokens->pickFaces ? 1 : 0); + pickBufferInit.push_back( + _contextParams.pickTarget == HdxPickTokens->pickEdges ? 1 : 0); + pickBufferInit.push_back( + _contextParams.pickTarget == HdxPickTokens->pickPoints ? 1 : 0); + pickBufferInit.push_back(0); + + // populate pick buffer's sub-buffer size table with zeros + pickBufferInit.resize(pickBufferInit.size() + numSubBuffers, + [](int* b, int* e) { std::uninitialized_fill(b, e, 0); }); + + // populate pick buffer's entry storage with -9s, meaning uninitialized + pickBufferInit.resize(pickBufferInit.size() + entryStorageSize, + [](int* b, int* e) { std::uninitialized_fill(b, e, -9); }); + } + else + { + // set pick buffer to invalid state + pickBufferInit.push_back(0); + } + + // set the source to the pick buffer + HdBufferSourceSharedPtr bufferSource = + std::make_shared( + _HdxPickTokens->PickBuffer, + VtValue(pickBufferInit)); + + HdStResourceRegistrySharedPtr const& hdStResourceRegistry = + std::dynamic_pointer_cast( + _index->GetResourceRegistry()); + + hdStResourceRegistry->AddSource(_pickBuffer, bufferSource); +} + void HdxPickTask::Execute(HdTaskContext* ctx) { @@ -642,6 +761,12 @@ HdxPickTask::Execute(HdTaskContext* ctx) {HdxRenderTagTokens->widget}); } + // For 'resolveDeep' mode, read hits from the pick buffer. + if (_contextParams.resolveMode == HdxPickTokens->resolveDeep) { + _ResolveDeep(); + return; + } + // Capture the result buffers and cast to the appropriate types. HdStTextureUtils::AlignedBuffer primIds = _ReadAovBuffer(HdAovTokens->primId); @@ -691,6 +816,78 @@ HdxPickTask::Execute(HdTaskContext* ctx) } } +void HdxPickTask::_ResolveDeep() +{ + if (!_pickBuffer) { + return; + } + + VtValue pickData = _pickBuffer->ReadData(_HdxPickTokens->PickBuffer); + if (pickData.IsEmpty()) { + return; + } + + const auto& data = pickData.Get(); + + const int numSubBuffers = + _contextParams.maxNumDeepEntries / PICK_BUFFER_SUBBUFFER_CAPACITY; + const int entryStorageOffset = + PICK_BUFFER_HEADER_SIZE + numSubBuffers; + + // loop through all the sub-buffers, populating outHits + for (int subBuffer = 0; subBuffer < numSubBuffers; ++subBuffer) + { + const int sizeOffset = PICK_BUFFER_HEADER_SIZE + subBuffer; + const int numEntries = data[sizeOffset]; + const int subBufferOffset = + entryStorageOffset + + subBuffer * PICK_BUFFER_SUBBUFFER_CAPACITY * PICK_BUFFER_ENTRY_SIZE; + + // loop through sub-buffer entries + for (int j = 0; j < numEntries; ++j) + { + int entryOffset = subBufferOffset + (j * PICK_BUFFER_ENTRY_SIZE); + + HdxPickHit hit; + + int primId = data[entryOffset]; + hit.objectId = _index->GetRprimPathFromPrimId(primId); + + if (!hit.IsValid()) { + continue; + } + + bool rprimValid = _index->GetSceneDelegateAndInstancerIds( + hit.objectId, + &(hit.delegateId), + &(hit.instancerId)); + + if (!TF_VERIFY(rprimValid, "%s\n", hit.objectId.GetText())) { + continue; + } + + int partIndex = data[entryOffset + 2]; + hit.instanceIndex = data[entryOffset + 1]; + hit.elementIndex = + _contextParams.pickTarget == HdxPickTokens->pickFaces ? + partIndex : -1; + hit.edgeIndex = + _contextParams.pickTarget == HdxPickTokens->pickEdges ? + partIndex : -1; + hit.pointIndex = + _contextParams.pickTarget == HdxPickTokens->pickPoints ? + partIndex : -1; + + // the following data is skipped in deep selection + hit.worldSpaceHitPoint = GfVec3f(0.f, 0.f, 0.f); + hit.worldSpaceHitNormal = GfVec3f(0.f, 0.f, 0.f); + hit.normalizedDepth = 0.f; + + _contextParams.outHits->push_back(hit); + } + } +} + const TfTokenVector & HdxPickTask::GetRenderTags() const { diff --git a/pxr/imaging/hdx/pickTask.h b/pxr/imaging/hdx/pickTask.h index fefb4a6b7e..375fe87568 100644 --- a/pxr/imaging/hdx/pickTask.h +++ b/pxr/imaging/hdx/pickTask.h @@ -62,7 +62,8 @@ PXR_NAMESPACE_OPEN_SCOPE (resolveNearestToCamera) \ (resolveNearestToCenter) \ (resolveUnique) \ - (resolveAll) + (resolveAll) \ + (resolveDeep) TF_DECLARE_PUBLIC_TOKENS(HdxPickTokens, HDX_API, HDX_PICK_TOKENS); @@ -138,6 +139,10 @@ using HdxPickHitVector = std::vector; /// 4. HdxPickTokens->resolveAll: Returns all the hits for the pick location /// or region. The number of hits returned depends on the resolution /// used and may have duplicates. +/// 5. HdxPickTokens->resolveDeep: Returns the unique hits not only of visible +/// geometry but also of all the geometry hiding behind. The 'pickTarget' +/// influences this operation. For e.g., the subprim indices are ignored +/// when the pickTarget is pickPrimsAndInstances. /// struct HdxPickTaskContextParams { @@ -145,6 +150,7 @@ struct HdxPickTaskContextParams HdxPickTaskContextParams() : resolution(128, 128) + , maxNumDeepEntries(32000) , pickTarget(HdxPickTokens->pickPrimsAndInstances) , resolveMode(HdxPickTokens->resolveNearestToCamera) , doUnpickablesOcclude(false) @@ -153,10 +159,12 @@ struct HdxPickTaskContextParams , clipPlanes() , depthMaskCallback(nullptr) , collection() + , alphaThreshold(0.f) , outHits(nullptr) {} GfVec2i resolution; + int maxNumDeepEntries; TfToken pickTarget; TfToken resolveMode; bool doUnpickablesOcclude; @@ -165,6 +173,7 @@ struct HdxPickTaskContextParams std::vector clipPlanes; DepthMaskCallback depthMaskCallback; HdRprimCollection collection; + float alphaThreshold; HdxPickHitVector *outHits; }; @@ -238,6 +247,9 @@ class HdxPickTask : public HdTask bool _UseOcclusionPass() const; bool _UseWidgetPass() const; + void _ClearPickBuffer(); + void _ResolveDeep(); + template HdStTextureUtils::AlignedBuffer _ReadAovBuffer(TfToken const & aovName) const; @@ -266,6 +278,9 @@ class HdxPickTask : public HdTask std::unique_ptr _widgetDepthStencilBuffer; HdRenderPassAovBindingVector _widgetAovBindings; + // pick buffer used for deep selection + HdBufferArrayRangeSharedPtr _pickBuffer; + HdxPickTask() = delete; HdxPickTask(const HdxPickTask &) = delete; HdxPickTask &operator =(const HdxPickTask &) = delete; diff --git a/pxr/imaging/hdx/shaders/renderPass.glslfx b/pxr/imaging/hdx/shaders/renderPass.glslfx index 160fa42cb8..5bd541a50d 100644 --- a/pxr/imaging/hdx/shaders/renderPass.glslfx +++ b/pxr/imaging/hdx/shaders/renderPass.glslfx @@ -159,6 +159,77 @@ vec4 IntToVec4(int id) ((id >> 24) & 0xff) / 255.0); } +// calculate an integer hash value based on integer key and offset +// this is used for the pick buffer hash map +int fnv1Hash(int key, int offset) +{ + const int prime = 16777619; + + int ret = offset; + + int b0 = (key & 255); + int b1 = (key & 65280) >> 8; + int b2 = (key & 16711680) >> 16; + int b3 = (key & -2130706432) >> 24; + + ret *= prime; + ret ^= b0; + ret *= prime; + ret ^= b1; + ret *= prime; + ret ^= b2; + ret *= prime; + ret ^= b3; + + return ret; +} + +#if defined(HD_HAS_PickBuffer) +bool +compareOrSet( + const int valueOffset, + const int primId, + const int instanceId, + const int partId) +{ + int primIdOffset = valueOffset; + int instanceIdOffset = valueOffset + 1; + int partIdOffset = valueOffset + 2; + + int primIdValue = PickBuffer[primIdOffset]; + int instanceIdValue = PickBuffer[instanceIdOffset]; + int partIdValue = PickBuffer[partIdOffset]; + + // if all the values are initialized, just compare + if ((primIdValue != -9) && + (instanceIdValue != -9) && + (partIdValue != -9)) { + return (primId == primIdValue) && + (instanceId == instanceIdValue) && + (partId == partIdValue); + } + + // try to initialize the values and check if succeeded + + primIdValue = atomicCompSwap(PickBuffer[primIdOffset], -9, primId); + if ((primIdValue != primId) && (primIdValue != -9)) { + return false; + } + + instanceIdValue = atomicCompSwap(PickBuffer[instanceIdOffset], -9, instanceId); + if ((instanceIdValue != instanceId) && (instanceIdValue != -9)) { + return false; + } + + partIdValue = atomicCompSwap(PickBuffer[partIdOffset], -9, partId); + if ((partIdValue != partId) && (partIdValue != -9)) { + return false; + } + + return true; +} +#endif // HD_HAS_PickBuffer + // Fwd declare necessary methods to determine the subprim id of a fragment. FORWARD_DECL(int GetElementID()); // generated via codeGen FORWARD_DECL(int GetPrimitiveEdgeId()); // defined in edgeId.glslfx, or generated via codeGen @@ -175,11 +246,67 @@ void RenderOutput(vec4 Peye, vec3 Neye, vec4 color, vec4 patchCoord) int instanceId = GetDrawingCoord().instanceIndex[0]; instanceIdOut = IntToVec4(instanceId); - elementIdOut = IntToVec4(GetElementID()); - edgeIdOut = IntToVec4(GetPrimitiveEdgeId()); - pointIdOut = IntToVec4(GetPointId()); + int elementId = GetElementID(); + int edgeId = GetPrimitiveEdgeId(); + int pointId = GetPointId(); + + elementIdOut = IntToVec4(elementId); + edgeIdOut = IntToVec4(edgeId); + pointIdOut = IntToVec4(pointId); neyeOut = IntToVec4(hd_vec4_2_10_10_10_set(vec4(Neye,0))); + + // add the item to the pick buffer, but only unique +#if defined(HD_HAS_PickBuffer) + + // if the pick buffer is not initialized, we are done + if (PickBuffer[0] == 0) + return; + + // prepare some constants + const int entrySize = 3; + const int numSubBuffers = PickBuffer[0]; + const int subBufferCapacity = PickBuffer[1]; + const int tableOffset = PickBuffer[2]; + const int storageOffset = PickBuffer[3]; + const int partId = PickBuffer[4] * elementId + + PickBuffer[5] * edgeId + + PickBuffer[6] * pointId; + + // calculate the item's hash + const int fnv1Seed = -2128831035; + int fnv1Value = fnv1Hash(primId, fnv1Seed); + fnv1Value = fnv1Hash(instanceId, fnv1Value); + fnv1Value = fnv1Hash(partId, fnv1Value); + + // more constants + const int subBufferNumber = fnv1Value % numSubBuffers; + const int sizeOffset = tableOffset + subBufferNumber; + const int subBufferOffset = storageOffset + + subBufferNumber * subBufferCapacity * entrySize; + + // loop through the item's sub-buffer to see if the item is already there + // if not, add it in atomic fashion + int entryNumber = 0; + do + { + // see if we need to add a new entry + if (entryNumber == PickBuffer[sizeOffset]) { + atomicCompSwap(PickBuffer[sizeOffset], entryNumber, entryNumber + 1); + } + + // if either the item equals to the current entry or we managed to + // initialize the entry with the item's data, we are done + if (compareOrSet( + subBufferOffset + entryNumber * entrySize, + primId, instanceId, partId)) { + break; + } + } // we exit out if we reach the capacity of the sub-buffer + while(++entryNumber != subBufferCapacity); + + +#endif // HD_HAS_PickBuffer } -- layout HdxRenderPass.RenderColorAndSelection diff --git a/pxr/usdImaging/usdImagingGL/engine.cpp b/pxr/usdImaging/usdImagingGL/engine.cpp index fcb9a746cb..2c5ed386c4 100644 --- a/pxr/usdImaging/usdImagingGL/engine.cpp +++ b/pxr/usdImaging/usdImagingGL/engine.cpp @@ -741,6 +741,66 @@ UsdImagingGLEngine::TestIntersection( return true; } +bool +UsdImagingGLEngine::TestIntersection( + const TfToken& resolveMode, + const GfMatrix4d& viewMatrix, + const GfMatrix4d& projectionMatrix, + const SdfPathVector& paths, + const UsdImagingGLRenderParams& params, + IntersectionResultVector& outResults, + bool wantsInstancerContext) +{ + TF_VERIFY(_sceneDelegate); + TF_VERIFY(_taskController); + + TF_VERIFY(_taskController); + + _UpdateHydraCollection(&_renderCollection, paths, params); + _taskController->SetCollection(_renderCollection); + + _PrepareRender(params); + + HdxPickHitVector allHits; + HdxPickTaskContextParams pickParams; + pickParams.resolveMode = resolveMode; + pickParams.viewMatrix = viewMatrix; + pickParams.projectionMatrix = projectionMatrix; + pickParams.clipPlanes = params.clipPlanes; + pickParams.collection = _intersectCollection; + pickParams.outHits = &allHits; + const VtValue vtPickParams(pickParams); + + _engine->SetTaskContextData(HdxPickTokens->pickParams, vtPickParams); + _Execute(params, _taskController->GetPickingTasks()); + + // return false if there were no hits + if (allHits.size() == 0) { + return false; + } + + for(HdxPickHit& hit : allHits) + { + IntersectionResult res; + + hit.objectId = _sceneDelegate->GetScenePrimPath( + hit.objectId, hit.instanceIndex, + wantsInstancerContext ? &res.instancerContext : nullptr); + + hit.instancerId = _sceneDelegate->ConvertIndexPathToCachePath( + hit.instancerId).GetAbsoluteRootOrPrimPath(); + + res.hitPoint = hit.worldSpaceHitPoint; + res.hitNormal = hit.worldSpaceHitNormal; + res.hitPrimPath = hit.objectId; + res.hitInstanceIndex = hit.instanceIndex; + res.hitInstancerPath = hit.instancerId; + outResults.push_back(res); + } + + return true; +} + bool UsdImagingGLEngine::DecodeIntersection( unsigned char const primIdColor[4], diff --git a/pxr/usdImaging/usdImagingGL/engine.h b/pxr/usdImaging/usdImagingGL/engine.h index 058a3179d0..26923d36e5 100644 --- a/pxr/usdImaging/usdImagingGL/engine.h +++ b/pxr/usdImaging/usdImagingGL/engine.h @@ -306,6 +306,47 @@ class UsdImagingGLEngine int *outHitInstanceIndex = NULL, HdInstancerContext *outInstancerContext = NULL); + struct IntersectionResult + { + GfVec3d hitPoint; + GfVec3d hitNormal; + SdfPath hitPrimPath; + SdfPath hitInstancerPath; + int hitInstanceIndex = 0; + HdInstancerContext instancerContext; + }; + + typedef std::vector IntersectionResultVector; + + /// Perform picking by finding the intersection of objects in the scene with a renderered frustum. + /// Depending on the resolve mode it may find all objects intersecting the frustum or the closest + /// point of intersection within the frustum. + /// + /// If resolve mode is set to resolveDeep it uses Deep Selection to gather all paths within + /// the frustum even if obscured by other visible objects. + /// If resolve mode is set to resolveNearestToCenter it uses a PickRender and + /// a customized depth buffer to find all approximate points of intersection by rendering. + /// This is less accurate than implicit methods or rendering with GL_SELECT, but leverages any + /// data already cached in the renderer. + /// + /// Returns whether a hit occurred and if so, \p outResults will point to all the + /// gprims selected by the pick as determined by the resolve mode. + /// \p outHitPoint will contain the intersection point in world space + /// (i.e. \p projectionMatrix and \p viewMatrix factored back out of the result) + /// \p outHitNormal will contain the world space normal at that point. + /// \p hitPrimPath will point to the gprim selected by the pick. + /// \p hitInstancerPath will point to the point instancer (if applicable) of each gprim. + /// + USDIMAGINGGL_API + bool TestIntersection( + const TfToken& resolveMode, + const GfMatrix4d& viewMatrix, + const GfMatrix4d& projectionMatrix, + const SdfPathVector& paths, + const UsdImagingGLRenderParams& params, + IntersectionResultVector& outResults, + bool wantsInstancerContext = false); + /// Decodes a pick result given hydra prim ID/instance ID (like you'd get /// from an ID render). USDIMAGINGGL_API diff --git a/pxr/usdImaging/usdImagingGL/testenv/testUsdImagingGLPickAndHighlight.cpp b/pxr/usdImaging/usdImagingGL/testenv/testUsdImagingGLPickAndHighlight.cpp index 131e9b9350..ef88ad8004 100644 --- a/pxr/usdImaging/usdImagingGL/testenv/testUsdImagingGLPickAndHighlight.cpp +++ b/pxr/usdImaging/usdImagingGL/testenv/testUsdImagingGLPickAndHighlight.cpp @@ -40,6 +40,7 @@ #include "pxr/imaging/garch/glDebugWindow.h" #include "pxr/imaging/hd/mesh.h" #include "pxr/imaging/hd/renderIndex.h" +#include "pxr/imaging/hdx/pickTask.h" #include "pxr/usd/usd/stage.h" #include "pxr/usd/usdGeom/bboxCache.h" #include "pxr/usd/usdGeom/metrics.h" @@ -59,30 +60,43 @@ PXR_NAMESPACE_USING_DIRECTIVE using UsdImagingGLEngineSharedPtr = std::shared_ptr; -struct OutHit { - GfVec3d outHitPoint; - GfVec3d outHitNormal; - SdfPath outHitPrimPath; - SdfPath outHitInstancerPath; - int outHitInstanceIndex; -}; - +using OutHit = UsdImagingGLEngine::IntersectionResult; static bool _CompareOutHit(OutHit const & lhs, OutHit const & rhs) { double const epsilon = 1e-6; - return GfIsClose(lhs.outHitPoint[0], rhs.outHitPoint[0], epsilon) && - GfIsClose(lhs.outHitPoint[1], rhs.outHitPoint[1], epsilon) && - GfIsClose(lhs.outHitPoint[2], rhs.outHitPoint[2], epsilon) && - GfIsClose(lhs.outHitNormal[0], rhs.outHitNormal[0], epsilon) && - GfIsClose(lhs.outHitNormal[1], rhs.outHitNormal[1], epsilon) && - GfIsClose(lhs.outHitNormal[2], rhs.outHitNormal[2], epsilon) && - lhs.outHitPrimPath == rhs.outHitPrimPath && - lhs.outHitInstancerPath == rhs.outHitInstancerPath && - lhs.outHitInstanceIndex == rhs.outHitInstanceIndex; + return GfIsClose(lhs.hitPoint[0], rhs.hitPoint[0], epsilon) && + GfIsClose(lhs.hitPoint[1], rhs.hitPoint[1], epsilon) && + GfIsClose(lhs.hitPoint[2], rhs.hitPoint[2], epsilon) && + GfIsClose(lhs.hitNormal[0], rhs.hitNormal[0], epsilon) && + GfIsClose(lhs.hitNormal[1], rhs.hitNormal[1], epsilon) && + GfIsClose(lhs.hitNormal[2], rhs.hitNormal[2], epsilon) && + lhs.hitPrimPath == rhs.hitPrimPath && + lhs.hitInstancerPath == rhs.hitInstancerPath && + lhs.hitInstanceIndex == rhs.hitInstanceIndex; +} + + +static bool +_CompareDeepSelectionResults(UsdImagingGLEngine::IntersectionResultVector const& lhsVector, + UsdImagingGLEngine::IntersectionResultVector const& rhsVector) +{ + bool res = lhsVector.size() == rhsVector.size(); + if (!res) return false; + + for (size_t i = 0; i < lhsVector.size(); ++i) + { + auto lhs = lhsVector[i]; + auto rhs = rhsVector[i]; + res &= (lhs.hitPrimPath == rhs.hitPrimPath && + lhs.hitInstancerPath == rhs.hitInstancerPath && + lhs.hitInstanceIndex == rhs.hitInstanceIndex); + } + return res; } + class My_TestGLDrawing : public UsdImagingGL_UnitTestGLDrawing { public: My_TestGLDrawing() { @@ -104,6 +118,7 @@ class My_TestGLDrawing : public UsdImagingGL_UnitTestGLDrawing { void Draw(bool render=true); void Pick(GfVec2i const &startPos, GfVec2i const &endPos); void Pick(GfVec2i const &startPos, GfVec2i const &endPos, OutHit* out); + void DeepSelect(GfVec2i const& startPos, GfVec2i const& endPos, UsdImagingGLEngine::IntersectionResultVector& out); private: UsdStageRefPtr _stage; @@ -208,7 +223,8 @@ My_TestGLDrawing::DrawTest(bool offscreen) SdfPath("/Group/GI1/I1/Mesh1/Plane1"), _stage->GetPrimAtPath(SdfPath("/Group/GI1/I1")).GetPrototype(). GetPath().AppendPath(SdfPath("Mesh1")), - 2 } + 2, + {} } }, { TfToken("HdEmbreeRendererPlugin"), @@ -216,7 +232,65 @@ My_TestGLDrawing::DrawTest(bool offscreen) GfVec3d(0, 0, 0), SdfPath("/Instance/I1/Mesh1/Plane1"), SdfPath::EmptyPath(), - 0 } + 0, + {} } + } + }; + + const UsdImagingGLEngine::IntersectionResultVector expectedOutputVector = { + { + GfVec3d(0.0, 0.0, 0.0), + GfVec3d(0.0, 0.0, 0.0), + SdfPath("/Group/GI1/I1/Mesh1/Plane1"), + _stage->GetPrimAtPath(SdfPath("/Group/GI1/I1")).GetPrototype(). + GetPath().AppendPath(SdfPath("Mesh1")), + 2, + {} + }, + { + GfVec3d(0.0, 0.0, 0.0), + GfVec3d(0.0, 0.0, 0.0), + SdfPath("/Group/GI2/I1/Mesh1/Plane2"), + _stage->GetPrimAtPath(SdfPath("/Group/GI2/I1")).GetPrototype(). + GetPath().AppendPath(SdfPath("Mesh1")), + 3, + {} + }, + { + GfVec3d(0.0, 0.0, 0.0), + GfVec3d(0.0, 0.0, 0.0), + SdfPath("/Instance/I1/Mesh1/Plane1"), + _stage->GetPrimAtPath(SdfPath("/Instance/I1")).GetPrototype(). + GetPath().AppendPath(SdfPath("Mesh1")), + 0, + {} + }, + { + GfVec3d(0.0, 0.0, 0.0), + GfVec3d(0.0, 0.0, 0.0), + SdfPath("/Group/GI2/I1/Mesh1/Plane1"), + _stage->GetPrimAtPath(SdfPath("/Group/GI2/I1")).GetPrototype(). + GetPath().AppendPath(SdfPath("Mesh1")), + 3, + {} + }, + { + GfVec3d(0.0, 0.0, 0.0), + GfVec3d(0.0, 0.0, 0.0), + SdfPath("/Group/GI1/I1/Mesh1/Plane2"), + _stage->GetPrimAtPath(SdfPath("/Group/GI1/I1")).GetPrototype(). + GetPath().AppendPath(SdfPath("Mesh1")), + 2, + {} + }, + { + GfVec3d(0.0, 0.0, 0.0), + GfVec3d(0.0, 0.0, 0.0), + SdfPath("/Instance/I1/Mesh1/Plane2"), + _stage->GetPrimAtPath(SdfPath("/Instance/I1")).GetPrototype(). + GetPath().AppendPath(SdfPath("Mesh1")), + 0, + {} } }; @@ -239,6 +313,22 @@ My_TestGLDrawing::DrawTest(bool offscreen) } else { Draw(); } + + // currently only HdStorm supports deep selection + static const TfToken _stormRendererPluginName("HdStormRendererPlugin"); + if (_engine->GetCurrentRendererId() == _stormRendererPluginName) + { + // Test windowed deep selection + UsdImagingGLEngine::IntersectionResultVector testOutVector; + DeepSelect(GfVec2i(320, 130), GfVec2i(171, 131), testOutVector); + TF_VERIFY(_CompareDeepSelectionResults(testOutVector, expectedOutputVector)); + Draw(); + + // Single pick deep selection + testOutVector.clear(); + DeepSelect(GfVec2i(170, 130), GfVec2i(171, 131), testOutVector); + Draw(); + } } void @@ -369,6 +459,54 @@ My_TestGLDrawing::Pick(GfVec2i const &startPos, GfVec2i const &endPos) { Pick(startPos, endPos, nullptr); } +void +My_TestGLDrawing::DeepSelect(GfVec2i const& startPos, GfVec2i const& endPos, + UsdImagingGLEngine::IntersectionResultVector& outResults) +{ + GfFrustum frustum = _frustum; + float width = GetWidth(), height = GetHeight(); + + GfVec2d min(2 * startPos[0] / width - 1, 1 - 2 * startPos[1] / height); + GfVec2d max(2 * (endPos[0] + 1) / width - 1, 1 - 2 * (endPos[1] + 1) / height); + // scale window + GfVec2d origin = frustum.GetWindow().GetMin(); + GfVec2d scale = frustum.GetWindow().GetMax() - frustum.GetWindow().GetMin(); + min = origin + GfCompMult(scale, 0.5 * (GfVec2d(1.0, 1.0) + min)); + max = origin + GfCompMult(scale, 0.5 * (GfVec2d(1.0, 1.0) + max)); + + frustum.SetWindow(GfRange2d(min, max)); + + // XXX: For a timevarying test need to set timecode for frame param + UsdImagingGLRenderParams params; + params.enableIdRender = true; + + SdfPathVector selection; + SdfPathVector roots(1, SdfPath::AbsoluteRootPath()); + + if (_engine->TestIntersection( + HdxPickTokens->resolveDeep, + _viewMatrix, + frustum.ComputeProjectionMatrix(), + roots, + params, + outResults)) { + + for (size_t i = 0; i < outResults.size(); ++i) + { + UsdImagingGLEngine::IntersectionResult outHit = outResults[i]; + std::cout << "Hit " + << i << ": " + << outHit.hitPrimPath << ", " + << outHit.hitInstancerPath << ", " + << outHit.hitInstanceIndex << "\n"; + + selection.push_back(outHit.hitPrimPath); + } + } + + _engine->SetSelected(selection); +} + void My_TestGLDrawing::Pick(GfVec2i const &startPos, GfVec2i const &endPos, OutHit* out) @@ -390,36 +528,39 @@ My_TestGLDrawing::Pick(GfVec2i const &startPos, GfVec2i const &endPos, UsdImagingGLRenderParams params; params.enableIdRender = true; - OutHit outHit; + UsdImagingGLEngine::IntersectionResultVector outResults; SdfPathVector selection; + SdfPathVector roots(1, SdfPath::AbsoluteRootPath()); if (_engine->TestIntersection( + HdxPickTokens->resolveNearestToCenter, _viewMatrix, frustum.ComputeProjectionMatrix(), - _stage->GetPseudoRoot(), + roots, params, - &outHit.outHitPoint, - &outHit.outHitNormal, - &outHit.outHitPrimPath, - &outHit.outHitInstancerPath, - &outHit.outHitInstanceIndex)) { - - std::cout << "Hit " - << outHit.outHitPoint << ", " - << outHit.outHitNormal << ", " - << outHit.outHitPrimPath << ", " - << outHit.outHitInstancerPath << ", " - << outHit.outHitInstanceIndex << "\n"; - - _engine->SetSelectionColor(GfVec4f(1, 1, 0, 1)); - selection.push_back(outHit.outHitPrimPath); - } + outResults)) { - if (out) { - *out = outHit; - } else { - _engine->SetSelected(selection); + if (outResults.size() == 1) + { + std::cout << "Hit " + << outResults[0].hitPoint << ", " + << outResults[0].hitNormal << ", " + << outResults[0].hitPrimPath << ", " + << outResults[0].hitInstancerPath << ", " + << outResults[0].hitInstanceIndex << "\n"; + + _engine->SetSelectionColor(GfVec4f(1, 1, 0, 1)); + selection.push_back(outResults[0].hitPrimPath); + + if (out) { + *out = outResults[0]; + } + } + + if (!out) { + _engine->SetSelected(selection); + } } } diff --git a/pxr/usdImaging/usdImagingGL/testenv/testUsdImagingGLPickAndHighlight/baseline/testUsdImagingGLPickAndHighlight_005.png b/pxr/usdImaging/usdImagingGL/testenv/testUsdImagingGLPickAndHighlight/baseline/testUsdImagingGLPickAndHighlight_005.png new file mode 100644 index 0000000000..d1931d873d Binary files /dev/null and b/pxr/usdImaging/usdImagingGL/testenv/testUsdImagingGLPickAndHighlight/baseline/testUsdImagingGLPickAndHighlight_005.png differ diff --git a/pxr/usdImaging/usdImagingGL/testenv/testUsdImagingGLPickAndHighlight/baseline/testUsdImagingGLPickAndHighlight_006.png b/pxr/usdImaging/usdImagingGL/testenv/testUsdImagingGLPickAndHighlight/baseline/testUsdImagingGLPickAndHighlight_006.png new file mode 100644 index 0000000000..12524236f1 Binary files /dev/null and b/pxr/usdImaging/usdImagingGL/testenv/testUsdImagingGLPickAndHighlight/baseline/testUsdImagingGLPickAndHighlight_006.png differ diff --git a/pxr/usdImaging/usdImagingGL/version.h b/pxr/usdImaging/usdImagingGL/version.h index 0af206071a..c584b8aa73 100644 --- a/pxr/usdImaging/usdImagingGL/version.h +++ b/pxr/usdImaging/usdImagingGL/version.h @@ -32,7 +32,8 @@ // 5 -> 6: Use UsdImagingGLEngine::_GetHdEngine() instead of _engine. // 6 -> 7: Added UsdImagingGLEngine::_GetTaskController() and _IsUsingLegacyImpl() // 7 -> 8: Added outHitNormal parameter to UsdImagingGLEngine::TestIntersection() -#define USDIMAGINGGL_API_VERSION 8 +// 8 -> 9: Added new UsdImagingGLEngine::TestIntersection() method with resolve mode +#define USDIMAGINGGL_API_VERSION 9 #endif // PXR_USD_IMAGING_USD_IMAGING_GL_VERSION_H