diff --git a/include/Inventor/actions/SoIRRenderAction.h b/include/Inventor/actions/SoIRRenderAction.h index b9f1facf782..5b6cb7e76bd 100644 --- a/include/Inventor/actions/SoIRRenderAction.h +++ b/include/Inventor/actions/SoIRRenderAction.h @@ -69,6 +69,13 @@ class COIN_DLL_API SoIRRenderAction : public SoAction { */ class PrimitiveCollector { public: + //! Primitive attributes needed by the retained renderer. + struct VertexData { + SbVec3f point; + SbVec3f normal; + SbVec4f texcoord = SbVec4f(0.0f, 0.0f, 0.0f, 1.0f); + int materialIndex = 0; + }; virtual ~PrimitiveCollector() {} virtual void onTriangle(const SoPrimitiveVertex * v1, const SoPrimitiveVertex * v2, @@ -76,6 +83,32 @@ class COIN_DLL_API SoIRRenderAction : public SoAction { virtual void onLine(const SoPrimitiveVertex * v1, const SoPrimitiveVertex * v2) = 0; virtual void onPoint(const SoPrimitiveVertex * v) = 0; + //! Receive an already resolved triangle and its picking identity. + virtual void onTriangleData(const VertexData & v1, + const VertexData & v2, + const VertexData & v3, + int faceIndex) = 0; + //! Receive an already resolved line segment and its picking identity. + virtual void onLineData(const VertexData & v1, + const VertexData & v2, + int lineIndex) = 0; + //! Reuse or register an explicit non-textured triangle source. + virtual SbBool beginRetainedTriangles(uint64_t sourceKey, + uint64_t revision, + int faceCount) + { return FALSE; } + //! Reuse a previously registered triangle source without rescanning it. + virtual SbBool reuseRetainedTriangles(uint64_t sourceId, + uint64_t revision) + { return FALSE; } + //! Reuse or register an explicit non-textured line-segment source. + virtual SbBool beginRetainedLines(uint64_t sourceId, + uint64_t revision, + int segmentCount) + { return FALSE; } + virtual SbBool reuseRetainedLines(uint64_t sourceId, + uint64_t revision) + { return FALSE; } }; static void initClass(void); @@ -166,6 +199,9 @@ class COIN_DLL_API SoIRRenderAction : public SoAction { void popPrimitiveCollector(PrimitiveCollector * collector); //! Return the currently active primitive collector, or NULL. PrimitiveCollector * getActivePrimitiveCollector(void) const; + //! Find a geometry resource with the same producer identity this frame. + SoGeometryHandle findGeometrySource(uint64_t sourceKey, + uint64_t revision) const; //! Enable intrusive construction attribution for benchmark diagnostics. void setConstructionTimingEnabled(SbBool enabled); SbBool isConstructionTimingEnabled() const; diff --git a/include/Inventor/elements/SoMultiTextureCoordinateElement.h b/include/Inventor/elements/SoMultiTextureCoordinateElement.h index d8f743c9eda..2723e8add9a 100644 --- a/include/Inventor/elements/SoMultiTextureCoordinateElement.h +++ b/include/Inventor/elements/SoMultiTextureCoordinateElement.h @@ -83,6 +83,8 @@ class COIN_DLL_API SoMultiTextureCoordinateElement : public SoElement { const int32_t numCoords, const SbVec4f * const coords); static CoordType getType(SoState * const state, const int unit = 0); + //! Return the source node revision recorded for this coordinate unit. + static SbUniqueId getNodeId(SoState * const state, const int unit = 0); virtual CoordType getType(const int unit = 0) const; static const SoMultiTextureCoordinateElement * getInstance(SoState * const state); diff --git a/include/Inventor/elements/SoMultiTextureImageElement.h b/include/Inventor/elements/SoMultiTextureImageElement.h index f4e8f59760e..d47c200c371 100644 --- a/include/Inventor/elements/SoMultiTextureImageElement.h +++ b/include/Inventor/elements/SoMultiTextureImageElement.h @@ -122,6 +122,8 @@ class COIN_DLL_API SoMultiTextureImageElement : public SoElement { static Wrap getWrapS(SoState * const state, const int unit = 0); static Wrap getWrapT(SoState * const state, const int unit = 0); static Wrap getWrapR(SoState * const state, const int unit = 0); + //! Return the source node revision recorded for this texture unit. + static SbUniqueId getNodeId(SoState * const state, const int unit = 0); static SbBool containsTransparency(SoState * const state); diff --git a/include/Inventor/nodes/SoFaceSet.h b/include/Inventor/nodes/SoFaceSet.h index 8dae06e8959..164318ba60f 100644 --- a/include/Inventor/nodes/SoFaceSet.h +++ b/include/Inventor/nodes/SoFaceSet.h @@ -59,6 +59,7 @@ class COIN_DLL_API SoFaceSet : public SoNonIndexedShape { protected: virtual ~SoFaceSet(); + SbBool generateRetainedPrimitives(SoIRRenderAction * action) override; void generatePrimitives(SoAction * action) override; void computeBBox(SoAction * action, SbBox3f & box, SbVec3f & center) override; SbBool generateDefaultNormals(SoState *, SoNormalCache *) override; diff --git a/include/Inventor/nodes/SoIndexedFaceSet.h b/include/Inventor/nodes/SoIndexedFaceSet.h index 3e3a23b2c55..e060d629195 100644 --- a/include/Inventor/nodes/SoIndexedFaceSet.h +++ b/include/Inventor/nodes/SoIndexedFaceSet.h @@ -41,6 +41,7 @@ #endif // !SO_END_FACE_INDEX class SoIndexedFaceSetP; +class SoIRRenderAction; class COIN_DLL_API SoIndexedFaceSet : public SoIndexedShape { typedef SoIndexedShape inherited; @@ -64,6 +65,7 @@ class COIN_DLL_API SoIndexedFaceSet : public SoIndexedShape { protected: virtual ~SoIndexedFaceSet(); + SbBool generateRetainedPrimitives(SoIRRenderAction * action) override; void generatePrimitives(SoAction * action) override; private: diff --git a/include/Inventor/nodes/SoIndexedLineSet.h b/include/Inventor/nodes/SoIndexedLineSet.h index 0235887af6c..c28b85e1a8f 100644 --- a/include/Inventor/nodes/SoIndexedLineSet.h +++ b/include/Inventor/nodes/SoIndexedLineSet.h @@ -41,6 +41,7 @@ #endif // !SO_END_LINE_INDEX class SoIndexedLineSetP; +class SoIRRenderAction; class COIN_DLL_API SoIndexedLineSet : public SoIndexedShape { typedef SoIndexedShape inherited; @@ -60,6 +61,7 @@ class COIN_DLL_API SoIndexedLineSet : public SoIndexedShape { protected: virtual ~SoIndexedLineSet(); void notify(SoNotList * list) override; + SbBool generateRetainedPrimitives(SoIRRenderAction * action) override; private: void generatePrimitives(SoAction * action) override; diff --git a/include/Inventor/nodes/SoShape.h b/include/Inventor/nodes/SoShape.h index f3fed82323d..43b6eccc83a 100644 --- a/include/Inventor/nodes/SoShape.h +++ b/include/Inventor/nodes/SoShape.h @@ -89,6 +89,8 @@ class COIN_DLL_API SoShape : public SoNode { float getComplexityValue(SoAction * action); virtual void generatePrimitives(SoAction * action) = 0; + //! Emit directly to a retained primitive collector when supported. + virtual SbBool generateRetainedPrimitives(SoIRRenderAction * action); #if COIN_HAVE_LEGACY_GL_RENDERER virtual SbBool shouldGLRender(SoGLRenderAction * action); void beginSolidShape(SoGLRenderAction * action); diff --git a/include/Inventor/rendering/SoRenderIR.h b/include/Inventor/rendering/SoRenderIR.h index 4d92f98832d..a1ad377d7f7 100644 --- a/include/Inventor/rendering/SoRenderIR.h +++ b/include/Inventor/rendering/SoRenderIR.h @@ -100,6 +100,41 @@ struct SoGeometryDesc { }; +//! Stable, draw-list-local reference to a geometry resource. +using SoGeometryHandle = uint32_t; +static constexpr SoGeometryHandle SO_INVALID_GEOMETRY_HANDLE = 0; + +//! Backend-neutral identity kinds retained for picking. +enum SoPickElementType : uint8_t { + SO_PICK_OBJECT = 0, + SO_PICK_FACE, + SO_PICK_EDGE, + SO_PICK_VERTEX +}; + +//! Maps one logical subelement to a geometry draw range. +struct SoRenderElementRange { + SoPickElementType type = SO_PICK_OBJECT; + int elementIndex = -1; + uint32_t drawStart = 0; + uint32_t drawCount = 0; +}; + +/*! + \struct SoGeometryResource + \brief Draw-list-owned geometry descriptor with producer identity. + + Handles are one-based and remain stable until SoDrawList::clear(). The + descriptor keeps the existing frame-lifetime pointer contract; the resource + table separates shared geometry identity from individual draw commands. +*/ +struct SoGeometryResource { + SoGeometryDesc geometry; + uint64_t sourceKey = 0; + uint64_t revision = 0; + std::vector elementRanges; +}; + /*! \enum SoShadingModel \brief Effective shading contract carried by a render command. @@ -517,31 +552,12 @@ struct COIN_DLL_API SoIRRenderContext { \enum SoPickElementType \brief Backend-neutral identity kinds retained for picking. */ -enum SoPickElementType : uint8_t { - SO_PICK_OBJECT = 0, - SO_PICK_FACE, - SO_PICK_EDGE, - SO_PICK_VERTEX -}; - -/*! \struct SoRenderElementRange - \brief Maps one logical subelement to a geometry draw range. - - For indexed geometry, drawStart/drawCount refer to indices. For - non-indexed geometry, they refer to vertices. -*/ -struct SoRenderElementRange { - SoPickElementType type = SO_PICK_OBJECT; - int elementIndex = -1; - uint32_t drawStart = 0; - uint32_t drawCount = 0; -}; - /*! \struct SoPickData \brief Backend-neutral pickability and optional subelement ranges. */ struct SoPickData { bool pickable = true; + bool useResourceElementRanges = false; std::vector elementRanges; }; @@ -637,6 +653,7 @@ struct SoRenderCommand { // Geometry, texture pixels, and other pointer-valued fields are borrowed; // see the lifetime contract on SoGeometryDesc and SoTextureData. SoGeometryDesc geometry; + SoGeometryHandle geometryHandle = SO_INVALID_GEOMETRY_HANDLE; SoMaterialData material; SoRenderState state; @@ -685,6 +702,20 @@ class COIN_DLL_API SoDrawList { void addCommand(const SoRenderCommand & cmd); SoRenderCommand & emplaceCommand(); + //! Append a geometry resource and return its stable one-based handle. + SoGeometryHandle addGeometryResource(const SoGeometryResource & resource); + //! Return NULL for an invalid handle or a handle outside this draw list. + SoGeometryResource * getGeometryResource(SoGeometryHandle handle); + const SoGeometryResource * getGeometryResource( + SoGeometryHandle handle) const; + int getNumGeometryResources() const; + //! Resolve a command resource, falling back to its embedded descriptor. + const SoGeometryDesc & getCommandGeometry( + const SoRenderCommand & command) const; + //! Resolve command-local or shared geometry subelement ranges. + const std::vector & getCommandElementRanges( + const SoRenderCommand & command) const; + int getNumCommands() const; //! Remove commands beyond index count without reordering remaining commands. void truncate(int count); @@ -730,6 +761,7 @@ class COIN_DLL_API SoDrawList { private: std::vector commands; + std::vector geometryResources; std::vector lightingSetups; std::vector depthClearEvents; SoSelectionState selection; diff --git a/src/actions/SoIRRenderAction.cpp b/src/actions/SoIRRenderAction.cpp index e7114a8186f..ff33b4e41d8 100644 --- a/src/actions/SoIRRenderAction.cpp +++ b/src/actions/SoIRRenderAction.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include SO_ACTION_SOURCE(SoIRRenderAction); @@ -89,6 +90,7 @@ class SoIRRenderActionP { SoIRBuffer geometryPool; std::vector textureStorage; SbList collectorStack; + std::unordered_multimap geometrySources; bool constructionTimingEnabled = false; SoIRRenderAction::ConstructionStatistics constructionStatistics; }; @@ -239,6 +241,40 @@ SoIRRenderAction::addCommand(const SoRenderCommand & command) state, this->drawlist); } + if (retained.geometry.cacheKey != 0) { + retained.geometryHandle = this->findGeometrySource( + retained.geometry.cacheKey, retained.geometry.revision); + if (retained.geometryHandle == SO_INVALID_GEOMETRY_HANDLE) { + SoGeometryResource resource; + resource.geometry = retained.geometry; + resource.sourceKey = retained.geometry.cacheKey; + resource.revision = retained.geometry.revision; + resource.elementRanges = retained.pick.elementRanges; + retained.geometryHandle = this->drawlist.addGeometryResource(resource); + retained.pick.elementRanges.clear(); + retained.pick.useResourceElementRanges = true; + PRIVATE(this)->geometrySources.emplace( + retained.geometry.cacheKey, retained.geometryHandle); + } + else { + const SoGeometryResource * resource = + this->drawlist.getGeometryResource(retained.geometryHandle); + const auto rangesEqual = [](const SoRenderElementRange & lhs, + const SoRenderElementRange & rhs) { + return lhs.type == rhs.type && lhs.elementIndex == rhs.elementIndex && + lhs.drawStart == rhs.drawStart && lhs.drawCount == rhs.drawCount; + }; + if (resource && resource->elementRanges.size() == + retained.pick.elementRanges.size() && + std::equal(resource->elementRanges.begin(), + resource->elementRanges.end(), + retained.pick.elementRanges.begin(), rangesEqual)) { + retained.pick.elementRanges.clear(); + retained.pick.useResourceElementRanges = true; + } + } + } + const int commandIndex = this->drawlist.getNumCommands(); this->drawlist.addCommand(retained); @@ -418,6 +454,20 @@ SoIRRenderAction::getActivePrimitiveCollector(void) const return PRIVATE(this)->collectorStack[count - 1]; } +SoGeometryHandle +SoIRRenderAction::findGeometrySource(const uint64_t sourceKey, + const uint64_t revision) const +{ + const auto candidates = PRIVATE(this)->geometrySources.equal_range(sourceKey); + for (auto candidate = candidates.first; candidate != candidates.second; + ++candidate) { + const SoGeometryResource * resource = + this->drawlist.getGeometryResource(candidate->second); + if (resource && resource->revision == revision) return candidate->second; + } + return SO_INVALID_GEOMETRY_HANDLE; +} + void SoIRRenderAction::setConstructionTimingEnabled(const SbBool enabled) { @@ -572,6 +622,7 @@ SoIRRenderAction::resetFrameResources() PRIVATE(this)->geometryPool.clear(); PRIVATE(this)->textureStorage.clear(); PRIVATE(this)->collectorStack.truncate(0); + PRIVATE(this)->geometrySources.clear(); PRIVATE(this)->constructionStatistics = ConstructionStatistics(); } diff --git a/src/elements/SoMultiTextureCoordinateElement.cpp b/src/elements/SoMultiTextureCoordinateElement.cpp index 9b83bf28f10..394573509de 100644 --- a/src/elements/SoMultiTextureCoordinateElement.cpp +++ b/src/elements/SoMultiTextureCoordinateElement.cpp @@ -426,6 +426,15 @@ SoMultiTextureCoordinateElement::getType(SoState * const state, const int unit) return element->getType(unit); } +SbUniqueId +SoMultiTextureCoordinateElement::getNodeId(SoState * const state, + const int unit) +{ + const SoMultiTextureCoordinateElement * elem = getInstance(state); + PRIVATE(elem)->ensureCapacity(unit); + return PRIVATE(elem)->unitdata[unit].nodeid; +} + //! FIXME: write doc. // side effect, will increase array size diff --git a/src/elements/SoMultiTextureImageElement.cpp b/src/elements/SoMultiTextureImageElement.cpp index 31be43b7ee2..4584f0c81d8 100644 --- a/src/elements/SoMultiTextureImageElement.cpp +++ b/src/elements/SoMultiTextureImageElement.cpp @@ -193,6 +193,16 @@ SoMultiTextureImageElement::get(SoState * const state, return getImage(state, unit, size, numComponents); } +SbUniqueId +SoMultiTextureImageElement::getNodeId(SoState * const state, const int unit) +{ + const SoMultiTextureImageElement * elem = + coin_assert_cast( + getConstElement(state, classStackIndex)); + PRIVATE(elem)->ensureCapacity(unit); + return PRIVATE(elem)->unitdata[unit].nodeid; +} + /*! FIXME: write doc. diff --git a/src/rendering/SoGLRenderBackend.cpp b/src/rendering/SoGLRenderBackend.cpp index d1c640d5795..e5e8c4e9dc5 100644 --- a/src/rendering/SoGLRenderBackend.cpp +++ b/src/rendering/SoGLRenderBackend.cpp @@ -801,14 +801,14 @@ SoGLRenderBackend::discard() this->setInitialized(FALSE); } SoGLRenderBackend::CachedCommand & -SoGLRenderBackend::getOrCreateCache(const SoRenderCommand * command) +SoGLRenderBackend::getOrCreateCache(const SoRenderCommand * command, + const SoGeometryDesc & geometry) { const auto found = this->commandToCache.find(command); if (found != this->commandToCache.end()) { return this->gpuCache[found->second]; } - const SoGeometryDesc & geometry = command->geometry; const SoTextureData & texture = command->material.texture; const bool hasTexture = (texture.cacheKey != 0 || texture.pixels != nullptr) && @@ -1042,9 +1042,9 @@ SoGLRenderBackend::uploadLineDistanceBuffer(CachedCommand & entry, void SoGLRenderBackend::updateCacheDescription(CachedCommand & entry, const SoRenderCommand & command, + const SoGeometryDesc & geometry, const GLsizei vertexStride) { - const SoGeometryDesc & geometry = command.geometry; const SoTextureData & texture = command.material.texture; const bool hasTexture = texture.pixels && texture.width > 0 && texture.height > 0 && texture.numComponents >= 1 && @@ -1080,9 +1080,9 @@ SoGLRenderBackend::updateCacheDescription(CachedCommand & entry, void SoGLRenderBackend::uploadGeometry(CachedCommand & entry, - const SoRenderCommand & command) + const SoRenderCommand & command, + const SoGeometryDesc & geometry) { - const SoGeometryDesc & geometry = command.geometry; const GLsizei vertexStride = static_cast( geometry.vertexStride ? geometry.vertexStride : sizeof(float) * 3); @@ -1097,7 +1097,7 @@ SoGLRenderBackend::uploadGeometry(CachedCommand & entry, cc_glglue_glBindBuffer(this->glue, GL_ARRAY_BUFFER, 0); cc_glglue_glBindBuffer(this->glue, GL_ELEMENT_ARRAY_BUFFER, 0); - this->updateCacheDescription(entry, command, vertexStride); + this->updateCacheDescription(entry, command, geometry, vertexStride); } void @@ -1494,12 +1494,12 @@ SoGLRenderBackend::updateGeometryCache(const SoDrawList & drawlist) for (int i = 0; i < drawlist.getNumCommands(); ++i) { const SoRenderCommand & command = drawlist.getCommand(i); - const SoGeometryDesc & geometry = command.geometry; + const SoGeometryDesc & geometry = drawlist.getCommandGeometry(command); if ((!geometry.positions && geometry.cacheKey == 0) || geometry.vertexCount == 0 || geometry.vertexCount > MAX_VERTEX_COUNT) continue; - CachedCommand & entry = this->getOrCreateCache(&command); + CachedCommand & entry = this->getOrCreateCache(&command, geometry); const uint32_t vertexStride = geometry.vertexStride ? geometry.vertexStride : sizeof(float) * 3; const bool lineGeometry = geometry.topology == SO_TOPOLOGY_LINES || @@ -1525,7 +1525,7 @@ SoGLRenderBackend::updateGeometryCache(const SoDrawList & drawlist) lineDistanceMatches && this->textureDescriptionMatches(entry, command); if (!geometryMatches) { if (!geometry.positions) continue; - this->uploadGeometry(entry, command); + this->uploadGeometry(entry, command, geometry); this->setupVisualVAO(entry); } } @@ -3462,7 +3462,8 @@ SoGLRenderBackend::renderSelection(const SoDrawList & drawlist, return; } - for (const SoRenderElementRange & range : command.pick.elementRanges) { + for (const SoRenderElementRange & range : + drawlist.getCommandElementRanges(command)) { if (range.type != target.type || range.elementIndex != target.elementIndex) continue; SoPickLUTEntry entry; diff --git a/src/rendering/SoGLRenderBackend.h b/src/rendering/SoGLRenderBackend.h index 6bc5a75b83e..0f7731068fe 100644 --- a/src/rendering/SoGLRenderBackend.h +++ b/src/rendering/SoGLRenderBackend.h @@ -394,9 +394,11 @@ class SoGLRenderBackend : public SoRenderBackend { const SoRenderCommand & command, const SurfaceUniforms & uniforms); - CachedCommand & getOrCreateCache(const SoRenderCommand * command); + CachedCommand & getOrCreateCache(const SoRenderCommand * command, + const SoGeometryDesc & geometry); void uploadGeometry(CachedCommand & entry, - const SoRenderCommand & command); + const SoRenderCommand & command, + const SoGeometryDesc & geometry); void uploadVertexBuffers(CachedCommand & entry, const SoGeometryDesc & geometry); void uploadTexture(CachedCommand & entry, @@ -408,6 +410,7 @@ class SoGLRenderBackend : public SoRenderBackend { const SoGeometryDesc & geometry); void updateCacheDescription(CachedCommand & entry, const SoRenderCommand & command, + const SoGeometryDesc & geometry, GLsizei vertexStride); void setupVisualVAO(CachedCommand & entry); void destroyCacheEntry(CachedCommand & entry); diff --git a/src/rendering/SoRenderBackend.cpp b/src/rendering/SoRenderBackend.cpp index 6c631c76032..d52f79d80fb 100644 --- a/src/rendering/SoRenderBackend.cpp +++ b/src/rendering/SoRenderBackend.cpp @@ -131,24 +131,22 @@ SoRenderBackend::debugValidateDrawList(const SoDrawList & drawlist) const const int num = drawlist.getNumCommands(); for (int i = 0; i < num; ++i) { const SoRenderCommand & command = drawlist.getCommand(i); - if (command.geometry.topology >= SO_TOPOLOGY_COUNT) { + const SoGeometryDesc & geometry = drawlist.getCommandGeometry(command); + if (geometry.topology >= SO_TOPOLOGY_COUNT) { SoDebugError::post("SoRenderBackend", "Command %d has invalid topology (%d)", - i, static_cast(command.geometry.topology)); + i, static_cast(geometry.topology)); } - if (command.geometry.vertexCount == 0 && - command.geometry.indexCount == 0) { + if (geometry.vertexCount == 0 && geometry.indexCount == 0) { SoDebugError::post("SoRenderBackend", "Command %d has no vertices or indices", i); } - if (command.geometry.vertexCount > 0 && - command.geometry.positions == nullptr && - command.geometry.cacheKey == 0) { + if (geometry.vertexCount > 0 && geometry.positions == nullptr && + geometry.cacheKey == 0) { SoDebugError::post("SoRenderBackend", "Command %d is missing its position buffer", i); } - if (command.geometry.indexCount > 0 && - command.geometry.indices == nullptr) { + if (geometry.indexCount > 0 && geometry.indices == nullptr) { SoDebugError::post("SoRenderBackend", "Command %d is missing its index buffer", i); } diff --git a/src/rendering/SoRenderIR.cpp b/src/rendering/SoRenderIR.cpp index 5c4149f451c..b9fe966be7e 100644 --- a/src/rendering/SoRenderIR.cpp +++ b/src/rendering/SoRenderIR.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -282,6 +283,7 @@ void SoDrawList::clear() { this->commands.clear(); + this->geometryResources.clear(); this->lightingSetups.clear(); this->depthClearEvents.clear(); this->selection = SoSelectionState(); @@ -339,6 +341,58 @@ SoDrawList::emplaceCommand() return this->commands.back(); } +SoGeometryHandle +SoDrawList::addGeometryResource(const SoGeometryResource & resource) +{ + this->geometryResources.push_back(resource); + return static_cast(this->geometryResources.size()); +} + +SoGeometryResource * +SoDrawList::getGeometryResource(SoGeometryHandle handle) +{ + if (handle == SO_INVALID_GEOMETRY_HANDLE || + static_cast(handle) > this->geometryResources.size()) { + return nullptr; + } + return &this->geometryResources[static_cast(handle - 1)]; +} + +const SoGeometryResource * +SoDrawList::getGeometryResource(SoGeometryHandle handle) const +{ + if (handle == SO_INVALID_GEOMETRY_HANDLE || + static_cast(handle) > this->geometryResources.size()) { + return nullptr; + } + return &this->geometryResources[static_cast(handle - 1)]; +} + +int +SoDrawList::getNumGeometryResources() const +{ + return static_cast(this->geometryResources.size()); +} + +const SoGeometryDesc & +SoDrawList::getCommandGeometry(const SoRenderCommand & command) const +{ + const SoGeometryResource * resource = + this->getGeometryResource(command.geometryHandle); + return resource ? resource->geometry : command.geometry; +} + +const std::vector & +SoDrawList::getCommandElementRanges(const SoRenderCommand & command) const +{ + if (command.pick.useResourceElementRanges) { + const SoGeometryResource * resource = + this->getGeometryResource(command.geometryHandle); + if (resource) return resource->elementRanges; + } + return command.pick.elementRanges; +} + void SoDrawList::addDepthClearEvent(const SoDepthClearEvent & event) { @@ -409,7 +463,9 @@ SoDrawList::buildPickLUT() const : command.geometry.vertexCount; if (drawLimit == 0) continue; - if (command.pick.elementRanges.empty()) { + const std::vector & elementRanges = + this->getCommandElementRanges(command); + if (elementRanges.empty()) { SoPickLUTEntry entry; entry.commandIndex = commandIndex; entry.objectId = command.objectId; @@ -421,7 +477,7 @@ SoDrawList::buildPickLUT() const continue; } - for (const SoRenderElementRange & range : command.pick.elementRanges) { + for (const SoRenderElementRange & range : elementRanges) { SoPickLUTEntry entry; entry.commandIndex = commandIndex; entry.objectId = command.objectId; @@ -754,6 +810,13 @@ fillTextureFromState(SoState * state, SoIRRenderAction * action, bytes, byteCount, size[0], size[1], numComponents, hasTransparency); material.texture.pixels = copy; + // The image element owns the source bytes and changes its node id whenever + // the producing texture node changes. Preserve both pieces so backends can + // retain one upload even though the IR owns a separate frame-local copy. + material.texture.cacheKey = static_cast( + reinterpret_cast(bytes)); + material.texture.revision = static_cast( + SoMultiTextureImageElement::getNodeId(state, 0)); material.texture.width = size[0]; material.texture.height = size[1]; material.texture.numComponents = numComponents; diff --git a/src/rendering/SoRenderPlan.cpp b/src/rendering/SoRenderPlan.cpp index 596dd223650..71011e9831d 100644 --- a/src/rendering/SoRenderPlan.cpp +++ b/src/rendering/SoRenderPlan.cpp @@ -21,8 +21,9 @@ SoRenderPlanner::build(const SoDrawList & drawlist, const auto depthOf = [&drawlist](const uint32_t commandIndex) { const SoRenderCommand & command = drawlist.getCommand( static_cast(commandIndex)); - const SbVec3f localCenter = command.geometry.hasBounds - ? command.geometry.boundsCenter : SbVec3f(0.0f, 0.0f, 0.0f); + const SoGeometryDesc & geometry = drawlist.getCommandGeometry(command); + const SbVec3f localCenter = geometry.hasBounds + ? geometry.boundsCenter : SbVec3f(0.0f, 0.0f, 0.0f); SbVec3f worldCenter; SbVec3f eyeCenter; command.modelMatrix.multVecMatrix(localCenter, worldCenter); diff --git a/src/shapenodes/SoFaceSet.cpp b/src/shapenodes/SoFaceSet.cpp index 66e71666532..7b7ab56b169 100644 --- a/src/shapenodes/SoFaceSet.cpp +++ b/src/shapenodes/SoFaceSet.cpp @@ -79,12 +79,15 @@ class SoVBO; #include #include #include +#include #include #if COIN_BUILD_LEGACY_GL_RENDERER #include #endif #include #include +#include +#include #include #include #include @@ -100,6 +103,8 @@ class SoVBO; #include #include #include +#include +#include #if COIN_BUILD_LEGACY_GL_RENDERER #include #endif @@ -793,6 +798,153 @@ SoFaceSet::getPrimitiveCount(SoGetPrimitiveCountAction *action) } } +SbBool +SoFaceSet::generateRetainedPrimitives(SoIRRenderAction * action) +{ + // Triangles can be emitted from resolved vertex state directly. Other face + // sizes retain the established tessellation path in generatePrimitives(). + const int faceCount = this->numVertices.getNum(); + if (faceCount == 0 || (faceCount == 1 && this->numVertices[0] == 0)) { + return TRUE; + } + const int32_t * faceSizes = this->numVertices.getValues(0); + for (int face = 0; face < faceCount; ++face) { + if (faceSizes[face] != 3) return FALSE; + } + if (this->startIndex.getValue() < 0) return FALSE; + + SoIRRenderAction::PrimitiveCollector * collector = + action->getActivePrimitiveCollector(); + if (!collector) return FALSE; + SoState * state = action->getState(); + const SoCoordinateElement * coordinates; + const SbVec3f * normals; + SbBool needNormals = TRUE; + SoVertexShape::getVertexData(state, coordinates, normals, needNormals); + const Binding materialBinding = this->findMaterialBinding(state); + const Binding normalBinding = this->findNormalBinding(state); + + SoNormalCache * normalCache = NULL; + if (needNormals && normals == NULL) { + normalCache = this->generateAndReadLockNormalCache(state); + normals = normalCache->getNormals(); + } + if (!normals) { + if (normalCache) this->readUnlockNormalCache(); + return FALSE; + } + + SoTextureCoordinateBundle textureBundle(action, FALSE, FALSE); + const SbBool useTextures = textureBundle.needCoordinates(); + const SoLazyElement * lazy = SoLazyElement::getInstance(state); + const bool explicitVertexColors = materialBinding == PER_VERTEX && + !lazy->isPacked() && lazy->getNumDiffuse() >= faceCount * 3; + if ((materialBinding == OVERALL || explicitVertexColors) && + (!useTextures || !textureBundle.isFunction())) { + const auto mixIdentity = [](uint64_t hash, uint64_t value) { + hash ^= value + 0x9e3779b97f4a7c15ULL + (hash << 6) + (hash >> 2); + return hash; + }; + const void * coordinateSource = coordinates->is3D() + ? static_cast(coordinates->getArrayPtr3()) + : static_cast(coordinates->getArrayPtr4()); + uint64_t sourceKey = mixIdentity( + static_cast(reinterpret_cast(this)), + static_cast(reinterpret_cast(coordinateSource))); + sourceKey = mixIdentity(sourceKey, + static_cast(reinterpret_cast(normals))); + sourceKey = mixIdentity(sourceKey, static_cast(faceCount)); + sourceKey = mixIdentity(sourceKey, + static_cast(this->startIndex.getValue())); + sourceKey = mixIdentity(sourceKey, static_cast(normalBinding)); + sourceKey = mixIdentity(sourceKey, static_cast(materialBinding)); + if (explicitVertexColors) { + sourceKey = mixIdentity(sourceKey, static_cast( + reinterpret_cast(lazy->getDiffusePointer()))); + sourceKey = mixIdentity(sourceKey, static_cast( + reinterpret_cast(lazy->getTransparencyPointer()))); + sourceKey = mixIdentity(sourceKey, + static_cast(lazy->getNumDiffuse())); + sourceKey = mixIdentity(sourceKey, + static_cast(lazy->getNumTransparencies())); + } + if (useTextures) { + const SoMultiTextureCoordinateElement * textureCoordinates = + SoMultiTextureCoordinateElement::getInstance(state); + const int dimension = textureCoordinates->getDimension(0); + const void * textureCoordinateSource = dimension == 2 + ? static_cast(textureCoordinates->getArrayPtr2(0)) + : (dimension == 3 + ? static_cast(textureCoordinates->getArrayPtr3(0)) + : static_cast(textureCoordinates->getArrayPtr4(0))); + sourceKey = mixIdentity(sourceKey, static_cast( + reinterpret_cast(textureCoordinateSource))); + sourceKey = mixIdentity(sourceKey, + static_cast(textureCoordinates->getNum(0))); + sourceKey = mixIdentity(sourceKey, static_cast(dimension)); + } + if (sourceKey == 0) sourceKey = 1; + uint64_t revision = mixIdentity( + static_cast(this->getNodeId()), + static_cast(coordinates->getNodeId())); + revision = mixIdentity(revision, + static_cast(SoNormalElement::getInstance(state)->getNodeId())); + if (explicitVertexColors) { + const int vertexCount = faceCount * 3; + for (int vertex = 0; vertex < vertexCount; ++vertex) { + const SbColor & color = SoLazyElement::getDiffuse(state, vertex); + for (int channel = 0; channel < 3; ++channel) { + uint32_t bits = 0; + const float value = color[channel]; + std::memcpy(&bits, &value, sizeof(bits)); + revision = mixIdentity(revision, bits); + } + uint32_t transparencyBits = 0; + const float transparency = + SoLazyElement::getTransparency(state, vertex); + std::memcpy(&transparencyBits, &transparency, + sizeof(transparencyBits)); + revision = mixIdentity(revision, transparencyBits); + } + } + if (useTextures) { + revision = mixIdentity(revision, static_cast( + SoMultiTextureCoordinateElement::getNodeId(state, 0))); + } + if (revision == 0) revision = 1; + if (collector->beginRetainedTriangles(sourceKey, revision, faceCount)) { + if (normalCache) this->readUnlockNormalCache(); + return TRUE; + } + } + int coordinateIndex = this->startIndex.getValue(); + int normalIndex = 0; + int materialIndex = 0; + int textureIndex = 0; + for (int face = 0; face < faceCount; ++face) { + SoIRRenderAction::PrimitiveCollector::VertexData vertices[3]; + for (int vertex = 0; vertex < 3; ++vertex, ++coordinateIndex) { + vertices[vertex].point = coordinates->get3(coordinateIndex); + vertices[vertex].normal = normals[ + normalBinding == OVERALL ? 0 : normalIndex]; + vertices[vertex].materialIndex = + materialBinding == OVERALL ? 0 : materialIndex; + if (useTextures) { + vertices[vertex].texcoord = textureBundle.isFunction() + ? textureBundle.get(vertices[vertex].point, vertices[vertex].normal) + : textureBundle.get(textureIndex++); + } + if (normalBinding == PER_VERTEX) ++normalIndex; + if (materialBinding == PER_VERTEX) ++materialIndex; + } + collector->onTriangleData(vertices[0], vertices[1], vertices[2], face); + if (normalBinding == PER_FACE) ++normalIndex; + if (materialBinding == PER_FACE) ++materialIndex; + } + if (normalCache) this->readUnlockNormalCache(); + return TRUE; +} + // doc from parent void SoFaceSet::generatePrimitives(SoAction *action) diff --git a/src/shapenodes/SoIndexedFaceSet.cpp b/src/shapenodes/SoIndexedFaceSet.cpp index 9d4e1daa9a8..c9beaef0d5b 100644 --- a/src/shapenodes/SoIndexedFaceSet.cpp +++ b/src/shapenodes/SoIndexedFaceSet.cpp @@ -194,6 +194,7 @@ class SoVBO; #include #include #include +#include #include #include #include @@ -714,6 +715,129 @@ SoIndexedFaceSet::GLRender(SoGLRenderAction * action) pointDetail.setCoordinateIndex(idx); \ this->shapeVertex(&vertex); +// doc from parent +SbBool +SoIndexedFaceSet::generateRetainedPrimitives(SoIRRenderAction * action) +{ + const int numIndices = this->coordIndex.getNum(); + if (numIndices < 3) return TRUE; + + SoIRRenderAction::PrimitiveCollector * collector = + action->getActivePrimitiveCollector(); + if (!collector) return FALSE; + + SoState * state = action->getState(); + const SbBool hasVertexProperty = this->vertexProperty.getValue() != NULL; + if (hasVertexProperty) { + state->push(); + this->vertexProperty.getValue()->doAction(action); + } + + const Binding materialBinding = this->findMaterialBinding(state); + Binding normalBinding = this->findNormalBinding(state); + const SoCoordinateElement * coordinates; + const SbVec3f * normals; + const int32_t * coordinateIndices; + const int32_t * normalIndices; + const int32_t * textureIndices; + const int32_t * materialIndices; + int actualNumIndices; + SbBool sendNormals = TRUE; + SbBool normalCacheUsed; + this->getVertexData(state, coordinates, normals, coordinateIndices, + normalIndices, textureIndices, materialIndices, + actualNumIndices, sendNormals, normalCacheUsed); + + SoTextureCoordinateBundle textureBundle(action, FALSE, FALSE); + const SbBool supported = + materialBinding == OVERALL && + !textureBundle.needCoordinates() && + !normalCacheUsed && normals != NULL && + (normalBinding == OVERALL || normalBinding == PER_VERTEX_INDEXED); + if (!supported) { + if (normalCacheUsed) this->readUnlockNormalCache(); + if (hasVertexProperty) state->pop(); + return FALSE; + } + if (normalBinding == PER_VERTEX_INDEXED && normalIndices == NULL) { + normalIndices = coordinateIndices; + } + + const auto mixIdentity = [](uint64_t hash, uint64_t value) { + hash ^= value + 0x9e3779b97f4a7c15ULL + (hash << 6) + (hash >> 2); + return hash; + }; + uint64_t sourceId = mixIdentity( + static_cast(reinterpret_cast(this)), + static_cast(reinterpret_cast(coordinateIndices))); + sourceId = mixIdentity(sourceId, + static_cast(reinterpret_cast(normalIndices))); + sourceId = mixIdentity(sourceId, + static_cast(reinterpret_cast(normals))); + sourceId = mixIdentity(sourceId, static_cast(actualNumIndices)); + sourceId = mixIdentity(sourceId, static_cast(normalBinding)); + if (sourceId == 0) sourceId = 1; + + uint64_t revision = mixIdentity( + static_cast(this->getNodeId()), + static_cast(coordinates->getNodeId())); + revision = mixIdentity(revision, + static_cast(SoNormalElement::getInstance(state)->getNodeId())); + if (revision == 0) revision = 1; + + // Probe the frame-local source table before inspecting individual indices. + // A hit proves that this exact source revision was validated earlier. + if (collector->reuseRetainedTriangles(sourceId, revision)) { + if (hasVertexProperty) state->pop(); + return TRUE; + } + + // This path deliberately handles only already-triangulated input. More + // general faces must retain generatePrimitives()' tessellation semantics. + int faceCount = 0; + int cursor = 0; + while (cursor < actualNumIndices) { + if (cursor + 2 >= actualNumIndices || + coordinateIndices[cursor] < 0 || + coordinateIndices[cursor + 1] < 0 || + coordinateIndices[cursor + 2] < 0) { + if (hasVertexProperty) state->pop(); + return FALSE; + } + cursor += 3; + ++faceCount; + if (cursor < actualNumIndices) { + if (coordinateIndices[cursor] >= 0) { + if (hasVertexProperty) state->pop(); + return FALSE; + } + ++cursor; + } + } + + if (collector->beginRetainedTriangles(sourceId, revision, faceCount)) { + if (hasVertexProperty) state->pop(); + return TRUE; + } + + cursor = 0; + for (int face = 0; face < faceCount; ++face) { + SoIRRenderAction::PrimitiveCollector::VertexData vertices[3]; + for (int vertex = 0; vertex < 3; ++vertex, ++cursor) { + const int32_t coordinateIndex = coordinateIndices[cursor]; + vertices[vertex].point = coordinates->get3(coordinateIndex); + vertices[vertex].normal = normals[ + normalBinding == OVERALL ? 0 : normalIndices[cursor]]; + vertices[vertex].materialIndex = 0; + } + collector->onTriangleData(vertices[0], vertices[1], vertices[2], face); + if (cursor < actualNumIndices && coordinateIndices[cursor] < 0) ++cursor; + } + + if (hasVertexProperty) state->pop(); + return TRUE; +} + // doc from parent void SoIndexedFaceSet::generatePrimitives(SoAction *action) diff --git a/src/shapenodes/SoIndexedLineSet.cpp b/src/shapenodes/SoIndexedLineSet.cpp index 80f0027d3d3..23a35781e9f 100644 --- a/src/shapenodes/SoIndexedLineSet.cpp +++ b/src/shapenodes/SoIndexedLineSet.cpp @@ -77,9 +77,11 @@ class SoVBO; #include #include #include +#include #include #include #include +#include #include #include #if COIN_BUILD_LEGACY_GL_RENDERER @@ -492,6 +494,125 @@ SoIndexedLineSet::getPrimitiveCount(SoGetPrimitiveCountAction *action) } } +// doc from parent +SbBool +SoIndexedLineSet::generateRetainedPrimitives(SoIRRenderAction * action) +{ + if (this->coordIndex.getNum() < 2) return TRUE; + SoIRRenderAction::PrimitiveCollector * collector = + action->getActivePrimitiveCollector(); + if (!collector) return FALSE; + + SoState * state = action->getState(); + const SbBool hasVertexProperty = this->vertexProperty.getValue() != NULL; + if (hasVertexProperty) { + state->push(); + this->vertexProperty.getValue()->doAction(action); + } + + const Binding materialBinding = this->findMaterialBinding(state); + Binding normalBinding = this->findNormalBinding(state); + const SoCoordinateElement * coordinates; + const SbVec3f * normals; + const int32_t * coordinateIndices; + const int32_t * normalIndices; + const int32_t * textureIndices; + const int32_t * materialIndices; + int numIndices; + SbBool sendNormals = TRUE; + SbBool normalCacheUsed; + this->getVertexData(state, coordinates, normals, coordinateIndices, + normalIndices, textureIndices, materialIndices, + numIndices, sendNormals, normalCacheUsed); + if (!normals) normalBinding = OVERALL; + + SoTextureCoordinateBundle textureBundle(action, FALSE, FALSE); + const SbBool supported = + materialBinding == OVERALL && + !textureBundle.needCoordinates() && + !normalCacheUsed && + (normalBinding == OVERALL || normalBinding == PER_VERTEX_INDEXED); + if (!supported) { + if (normalCacheUsed) this->readUnlockNormalCache(); + if (hasVertexProperty) state->pop(); + return FALSE; + } + if (normalBinding == PER_VERTEX_INDEXED && normalIndices == NULL) { + normalIndices = coordinateIndices; + } + + const auto mixIdentity = [](uint64_t hash, uint64_t value) { + hash ^= value + 0x9e3779b97f4a7c15ULL + (hash << 6) + (hash >> 2); + return hash; + }; + uint64_t sourceId = mixIdentity( + static_cast(reinterpret_cast(this)), + static_cast(reinterpret_cast(coordinateIndices))); + sourceId = mixIdentity(sourceId, + static_cast(reinterpret_cast(normalIndices))); + sourceId = mixIdentity(sourceId, + static_cast(reinterpret_cast(normals))); + sourceId = mixIdentity(sourceId, static_cast(numIndices)); + sourceId = mixIdentity(sourceId, static_cast(normalBinding)); + if (sourceId == 0) sourceId = 1; + + uint64_t revision = mixIdentity( + static_cast(this->getNodeId()), + static_cast(coordinates->getNodeId())); + revision = mixIdentity(revision, + static_cast(SoNormalElement::getInstance(state)->getNodeId())); + if (revision == 0) revision = 1; + + if (collector->reuseRetainedLines(sourceId, revision)) { + if (hasVertexProperty) state->pop(); + return TRUE; + } + + // Independent segments have unambiguous sequential edge identities. Line + // strips retain generatePrimitives()' established expansion semantics. + int segmentCount = 0; + int cursor = 0; + while (cursor < numIndices) { + if (cursor + 1 >= numIndices || coordinateIndices[cursor] < 0 || + coordinateIndices[cursor + 1] < 0) { + if (hasVertexProperty) state->pop(); + return FALSE; + } + cursor += 2; + ++segmentCount; + if (cursor < numIndices) { + if (coordinateIndices[cursor] >= 0) { + if (hasVertexProperty) state->pop(); + return FALSE; + } + ++cursor; + } + } + + if (collector->beginRetainedLines(sourceId, revision, segmentCount)) { + if (hasVertexProperty) state->pop(); + return TRUE; + } + + const SbVec3f defaultNormal(0.0f, 0.0f, 1.0f); + cursor = 0; + for (int segment = 0; segment < segmentCount; ++segment) { + SoIRRenderAction::PrimitiveCollector::VertexData vertices[2]; + for (int vertex = 0; vertex < 2; ++vertex, ++cursor) { + vertices[vertex].point = coordinates->get3(coordinateIndices[cursor]); + vertices[vertex].normal = normals + ? normals[normalBinding == OVERALL ? 0 : normalIndices[cursor]] + : defaultNormal; + vertices[vertex].materialIndex = 0; + } + collector->onLineData(vertices[0], vertices[1], segment); + if (cursor < numIndices && coordinateIndices[cursor] < 0) ++cursor; + } + + if (hasVertexProperty) state->pop(); + return TRUE; +} + // doc from parent void SoIndexedLineSet::generatePrimitives(SoAction *action) diff --git a/src/shapenodes/SoShape.cpp b/src/shapenodes/SoShape.cpp index 9b04457d7f7..12ca89e5ff8 100644 --- a/src/shapenodes/SoShape.cpp +++ b/src/shapenodes/SoShape.cpp @@ -222,12 +222,107 @@ class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { this->appendRange(first, 1, SO_PICK_VERTEX, this->pointIndex(v)); } + void onTriangleData(const VertexData & v1, const VertexData & v2, + const VertexData & v3, int faceIndex) override + { + this->setTopology(SO_TOPOLOGY_TRIANGLES); + const size_t first = this->vertices.size(); + this->append(v1); + this->append(v2); + this->append(v3); + this->appendRange(first, 3, SO_PICK_FACE, faceIndex); + } + + void onLineData(const VertexData & v1, const VertexData & v2, + int lineIndex) override + { + this->setTopology(SO_TOPOLOGY_LINES); + const size_t first = this->vertices.size(); + this->append(v1); + this->append(v2); + this->appendRange(first, 2, SO_PICK_EDGE, lineIndex); + } + + SbBool beginRetainedTriangles(uint64_t sourceKey, uint64_t revision, + int faceCount) override + { + this->geometryCacheKey = sourceKey; + this->geometryRevision = revision; + return this->reuseGeometry(sourceKey, revision, faceCount, + SO_TOPOLOGY_TRIANGLES, 3); + } + + SbBool reuseRetainedTriangles(uint64_t sourceKey, + uint64_t revision) override + { + return this->reuseGeometry(sourceKey, revision, 0, + SO_TOPOLOGY_TRIANGLES, 3); + } + + SbBool beginRetainedLines(uint64_t sourceKey, uint64_t revision, + int segmentCount) override + { + this->geometryCacheKey = sourceKey; + this->geometryRevision = revision; + return this->reuseGeometry(sourceKey, revision, segmentCount, + SO_TOPOLOGY_LINES, 2); + } + + SbBool reuseRetainedLines(uint64_t sourceKey, uint64_t revision) override + { + return this->reuseGeometry(sourceKey, revision, 0, + SO_TOPOLOGY_LINES, 2); + } + void finalize() { + if (this->reusedVertexCount != 0) { + std::vector batches; + batches.push_back(SoIRBatch(0, this->reusedVertexCount, 0)); + const std::chrono::steady_clock::time_point start = + this->action->isConstructionTimingEnabled() + ? std::chrono::steady_clock::now() + : std::chrono::steady_clock::time_point(); + this->emitCommands(this->action->getState(), + this->reusedGeometry, batches); + if (this->action->isConstructionTimingEnabled()) { + this->action->recordCommandEmissionNanoseconds( + static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - start).count())); + } + return; + } this->flushRun(); } private: + SbBool reuseGeometry(uint64_t sourceKey, uint64_t revision, + int expectedPrimitiveCount, + SoPrimitiveTopology topology, + int verticesPerPrimitive) + { + const SoGeometryHandle handle = + this->action->findGeometrySource(sourceKey, revision); + if (handle == SO_INVALID_GEOMETRY_HANDLE) return FALSE; + const SoGeometryResource * resource = + this->action->getDrawList().getGeometryResource(handle); + if (!resource || resource->geometry.topology != topology || + resource->geometry.vertexCount == 0 || + resource->geometry.vertexCount % verticesPerPrimitive != 0 || + (expectedPrimitiveCount > 0 && resource->geometry.vertexCount != + static_cast(expectedPrimitiveCount * verticesPerPrimitive))) { + return FALSE; + } + this->geometryCacheKey = sourceKey; + this->geometryRevision = revision; + this->topology = topology; + // Reused commands resolve the canonical ranges owned by the resource. + this->reusedPrimitiveWidth = verticesPerPrimitive; + this->reusedGeometry = resource->geometry; + this->reusedVertexCount = resource->geometry.vertexCount; + return TRUE; + } + void flushRun() { if (this->vertices.empty()) return; @@ -315,6 +410,8 @@ class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { geometry.normalCount = geometry.vertexCount; geometry.vertexStride = sizeof(float) * 3; geometry.texcoordStride = sizeof(float) * 4; + geometry.cacheKey = this->geometryCacheKey; + geometry.revision = this->geometryRevision; float * positions = static_cast( this->action->allocateGeometryStorage(sizeof(float) * 3 * count)); @@ -392,25 +489,31 @@ class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { const size_t batchEnd = batch.first + batch.count; const size_t primitiveWidth = this->topology == SO_TOPOLOGY_TRIANGLES ? 3 : (this->topology == SO_TOPOLOGY_LINES ? 2 : 1); - std::vector pickRanges; - bool completePickRanges = true; - for (const SoIRPrimitiveRange & range : this->primitiveRanges) { - if (range.first < batch.first || range.first >= batchEnd) continue; - if (!range.valid || range.first + range.count > batchEnd) { - completePickRanges = false; - break; - } - SoRenderElementRange pickRange; - pickRange.type = range.type; - pickRange.elementIndex = range.elementIndex; - pickRange.drawStart = static_cast(range.first - batch.first); - pickRange.drawCount = static_cast(range.count); - pickRanges.push_back(pickRange); - } const size_t expectedRanges = primitiveWidth == 0 ? 0 : batch.count / primitiveWidth; - if (completePickRanges && pickRanges.size() == expectedRanges) { - command.pick.elementRanges = pickRanges; + if (this->reusedPrimitiveWidth != 0) { + command.pick.useResourceElementRanges = true; + } + else { + command.pick.elementRanges.reserve(expectedRanges); + bool completePickRanges = true; + for (const SoIRPrimitiveRange & range : this->primitiveRanges) { + if (range.first < batch.first || range.first >= batchEnd) continue; + if (!range.valid || range.first + range.count > batchEnd) { + completePickRanges = false; + break; + } + SoRenderElementRange pickRange; + pickRange.type = range.type; + pickRange.elementIndex = range.elementIndex; + pickRange.drawStart = static_cast(range.first - batch.first); + pickRange.drawCount = static_cast(range.count); + command.pick.elementRanges.push_back(pickRange); + } + if (!completePickRanges || + command.pick.elementRanges.size() != expectedRanges) { + command.pick.elementRanges.clear(); + } } this->action->applyRenderStage(command); command.userData = this->shape; @@ -437,6 +540,16 @@ class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { this->vertices.push_back(copy); } + void append(const VertexData & vertex) + { + SoIRVertex copy; + copy.position = vertex.point; + copy.normal = vertex.normal; + copy.texcoord = vertex.texcoord; + copy.materialIndex = vertex.materialIndex; + this->vertices.push_back(copy); + } + void appendRange(size_t first, size_t count, SoPickElementType type, int elementIndex) { @@ -492,6 +605,11 @@ class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { SoIRRenderAction * action; SoShape * shape; SoPrimitiveTopology topology; + uint64_t geometryCacheKey = 0; + uint64_t geometryRevision = 0; + SoGeometryDesc reusedGeometry; + size_t reusedVertexCount = 0; + int reusedPrimitiveWidth = 0; std::vector vertices; std::vector primitiveRanges; }; @@ -832,8 +950,15 @@ SoShape::GLRender(SoGLRenderAction * action) if (vp) action->getState()->pop(); } + #endif +SbBool +SoShape::generateRetainedPrimitives(SoIRRenderAction *) +{ + return FALSE; +} + void SoShape::IRRender(SoIRRenderAction * action) { @@ -855,7 +980,9 @@ SoShape::IRRender(SoIRRenderAction * action) const std::chrono::steady_clock::time_point primitiveStart = measure ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point(); - this->generatePrimitives(action); + if (!this->generateRetainedPrimitives(action)) { + this->generatePrimitives(action); + } if (measure) { action->recordPrimitiveGenerationNanoseconds( static_cast(std::chrono::duration_cast( diff --git a/testsuite/DrawListGLTest.cpp b/testsuite/DrawListGLTest.cpp index 6c90f3ef107..673e4684d46 100644 --- a/testsuite/DrawListGLTest.cpp +++ b/testsuite/DrawListGLTest.cpp @@ -251,6 +251,36 @@ runTest() } } + // A keyed geometry resource remains usable after the producer replaces its + // frame storage. The second frame intentionally provides no CPU positions; + // the backend must use the resource retained from the first frame. + SoDrawList persistentDrawlist; + SoRenderCommand persistentCommand = unindexed; + persistentCommand.geometry.cacheKey = 0x42u; + persistentCommand.geometry.revision = 1; + persistentDrawlist.addCommand(persistentCommand); + if (!renderWithPlan(backend, persistentDrawlist, params)) { + std::cerr << "FAIL: keyed geometry first-frame execution failed" << std::endl; + result = 1; + } + persistentDrawlist.clear(); + persistentCommand.geometry.positions = nullptr; + persistentDrawlist.addCommand(persistentCommand); + if (!renderWithPlan(backend, persistentDrawlist, params)) { + std::cerr << "FAIL: keyed geometry was not retained across frames" + << std::endl; + result = 1; + } + else { + glFinish(); + const std::vector pixels = readPixels(context); + if (!nearColor(pixelAt(pixels, 16, 16), 0, 255, 0)) { + std::cerr << "FAIL: retained keyed geometry did not render from GPU data" + << std::endl; + result = 1; + } + } + // clear() changes the generation. Replacing the command must not reuse the // previous frame's GPU data. const float replacement[] = { diff --git a/testsuite/RetainedIRTest.cpp b/testsuite/RetainedIRTest.cpp index 88c4d4bd902..0579cb041fc 100644 --- a/testsuite/RetainedIRTest.cpp +++ b/testsuite/RetainedIRTest.cpp @@ -1,9 +1,20 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include #include static int @@ -14,14 +25,241 @@ runTest() root->ref(); int result = 0; + SoDrawList resourceDrawList; + SoGeometryResource resource; + resource.geometry.vertexCount = 3; + resource.sourceKey = 17; + resource.revision = 4; + const SoGeometryHandle handle = + resourceDrawList.addGeometryResource(resource); + SoRenderCommand resourceCommand; + resourceCommand.geometryHandle = handle; + resourceDrawList.addCommand(resourceCommand); + const SoGeometryResource * resolved = + resourceDrawList.getGeometryResource(handle); + if (handle == SO_INVALID_GEOMETRY_HANDLE || + resourceDrawList.getNumGeometryResources() != 1 || + !resolved || resolved->geometry.vertexCount != 3 || + resolved->sourceKey != 17 || resolved->revision != 4 || + resourceDrawList.getCommand(0).geometryHandle != handle || + resourceDrawList.getCommandGeometry( + resourceDrawList.getCommand(0)).vertexCount != 3 || + resourceDrawList.getGeometryResource(SO_INVALID_GEOMETRY_HANDLE) || + resourceDrawList.getGeometryResource(handle + 1)) { + std::cerr << "FAIL: draw-list geometry resource handle was unstable" + << std::endl; + result = 1; + } + resourceDrawList.clear(); + if (resourceDrawList.getNumGeometryResources() != 0 || + resourceDrawList.getGeometryResource(handle)) { + std::cerr << "FAIL: draw-list clear retained a stale geometry resource" + << std::endl; + result = 1; + } + action.apply(root); if (action.getDrawList().getNumCommands() != 0) { std::cerr << "FAIL: empty scene emitted retained commands" << std::endl; result = 1; } + const SbVec3f triangle[] = { + SbVec3f(-1.0f, -1.0f, 0.0f), + SbVec3f(1.0f, -1.0f, 0.0f), + SbVec3f(0.0f, 1.0f, 0.0f) + }; + SoSeparator * sharedSourceRoot = new SoSeparator; + sharedSourceRoot->ref(); + SoCoordinate3 * sharedCoordinates = new SoCoordinate3; + sharedCoordinates->point.setValues(0, 3, triangle); + SoFaceSet * sharedSourceFace = new SoFaceSet; + sharedSourceFace->numVertices.set1Value(0, 3); + sharedSourceRoot->addChild(sharedCoordinates); + sharedSourceRoot->addChild(sharedSourceFace); + sharedSourceRoot->addChild(sharedSourceFace); + action.apply(sharedSourceRoot); + if (action.getDrawList().getNumCommands() != 2 || + action.getDrawList().getNumGeometryResources() != 1 || + action.getDrawList().getCommand(0).geometryHandle == + SO_INVALID_GEOMETRY_HANDLE || + action.getDrawList().getCommand(0).geometryHandle != + action.getDrawList().getCommand(1).geometryHandle) { + std::cerr << "FAIL: repeated face-set source did not share geometry" + << std::endl; + result = 1; + } + const uint64_t originalRevision = + action.getDrawList().getCommandGeometry( + action.getDrawList().getCommand(0)).revision; + sharedCoordinates->point.set1Value(0, SbVec3f(-1.5f, -1.0f, 0.0f)); + action.apply(sharedSourceRoot); + if (action.getDrawList().getNumGeometryResources() != 1 || + action.getDrawList().getCommandGeometry( + action.getDrawList().getCommand(0)).revision == originalRevision || + action.getDrawList().getCommand(0).geometryHandle != + action.getDrawList().getCommand(1).geometryHandle) { + std::cerr << "FAIL: shared face-set source mutation was stale" + << std::endl; + result = 1; + } + const SbVec2f textureCoordinates[] = { + SbVec2f(0.0f, 0.0f), SbVec2f(1.0f, 0.0f), SbVec2f(0.5f, 1.0f) + }; + SoTextureCoordinate2 * sharedTexcoords = new SoTextureCoordinate2; + sharedTexcoords->point.setValues(0, 3, textureCoordinates); + const unsigned char texel[] = { 255, 255, 255, 255 }; + SoTexture2 * sharedTexture = new SoTexture2; + sharedTexture->image.setValue(SbVec2s(1, 1), 4, texel); + sharedSourceRoot->insertChild(sharedTexture, 1); + sharedSourceRoot->insertChild(sharedTexcoords, 2); + action.apply(sharedSourceRoot); + if (action.getDrawList().getNumGeometryResources() != 1 || + action.getDrawList().getCommand(0).geometryHandle != + action.getDrawList().getCommand(1).geometryHandle || + action.getDrawList().getCommand(0).material.texture.cacheKey == 0) { + std::cerr << "FAIL: textured face-set source did not share geometry" + << std::endl; + result = 1; + } + const uint64_t texturedRevision = action.getDrawList().getCommandGeometry( + action.getDrawList().getCommand(0)).revision; + sharedTexcoords->point.set1Value(0, SbVec2f(0.25f, 0.0f)); + action.apply(sharedSourceRoot); + if (action.getDrawList().getCommandGeometry( + action.getDrawList().getCommand(0)).revision == texturedRevision || + action.getDrawList().getCommand(0).geometryHandle != + action.getDrawList().getCommand(1).geometryHandle) { + std::cerr << "FAIL: textured face-set coordinate mutation was stale" + << std::endl; + result = 1; + } + const SbColor vertexColors[] = { + SbColor(1.0f, 0.0f, 0.0f), SbColor(0.0f, 1.0f, 0.0f), + SbColor(0.0f, 0.0f, 1.0f) + }; + SoMaterial * vertexMaterial = new SoMaterial; + vertexMaterial->diffuseColor.setValues(0, 3, vertexColors); + SoMaterialBinding * vertexBinding = new SoMaterialBinding; + vertexBinding->value = SoMaterialBinding::PER_VERTEX; + sharedSourceRoot->insertChild(vertexMaterial, 3); + sharedSourceRoot->insertChild(vertexBinding, 4); + action.apply(sharedSourceRoot); + if (action.getDrawList().getNumGeometryResources() != 1 || + action.getDrawList().getCommand(0).geometryHandle != + action.getDrawList().getCommand(1).geometryHandle) { + std::cerr << "FAIL: vertex-colored face-set source did not share geometry" + << std::endl; + result = 1; + } + const uint64_t coloredRevision = action.getDrawList().getCommandGeometry( + action.getDrawList().getCommand(0)).revision; + vertexMaterial->diffuseColor.set1Value(0, SbColor(0.5f, 0.25f, 0.75f)); + action.apply(sharedSourceRoot); + if (action.getDrawList().getCommandGeometry( + action.getDrawList().getCommand(0)).revision == coloredRevision || + action.getDrawList().getCommand(0).geometryHandle != + action.getDrawList().getCommand(1).geometryHandle) { + std::cerr << "FAIL: vertex-color source mutation was stale" << std::endl; + result = 1; + } + sharedSourceRoot->unref(); + + SoSeparator * indexedSourceRoot = new SoSeparator; + indexedSourceRoot->ref(); + SoCoordinate3 * indexedCoordinates = new SoCoordinate3; + indexedCoordinates->point.setValues(0, 3, triangle); + SoNormal * indexedNormals = new SoNormal; + indexedNormals->vector.set1Value(0, SbVec3f(0.0f, 0.0f, 1.0f)); + SoNormalBinding * indexedNormalBinding = new SoNormalBinding; + indexedNormalBinding->value = SoNormalBinding::OVERALL; + SoIndexedFaceSet * indexedFace = new SoIndexedFaceSet; + const int32_t indexedTriangle[] = { 0, 1, 2, -1 }; + indexedFace->coordIndex.setValues(0, 4, indexedTriangle); + indexedSourceRoot->addChild(indexedCoordinates); + indexedSourceRoot->addChild(indexedNormals); + indexedSourceRoot->addChild(indexedNormalBinding); + indexedSourceRoot->addChild(indexedFace); + indexedSourceRoot->addChild(indexedFace); + action.apply(indexedSourceRoot); + if (action.getDrawList().getNumCommands() != 2 || + action.getDrawList().getNumGeometryResources() != 1 || + action.getDrawList().getCommand(0).geometryHandle != + action.getDrawList().getCommand(1).geometryHandle) { + std::cerr << "FAIL: repeated indexed geometry did not share one resource" + << std::endl; + result = 1; + } + const uint64_t indexedRevision = action.getDrawList().getCommandGeometry( + action.getDrawList().getCommand(0)).revision; + indexedCoordinates->point.set1Value(0, SbVec3f(-1.25f, -1.0f, 0.0f)); + action.apply(indexedSourceRoot); + if (action.getDrawList().getNumGeometryResources() != 1 || + action.getDrawList().getCommandGeometry( + action.getDrawList().getCommand(0)).revision == indexedRevision || + action.getDrawList().getCommand(0).geometryHandle != + action.getDrawList().getCommand(1).geometryHandle) { + std::cerr << "FAIL: retained indexed geometry source mutation was stale" + << std::endl; + result = 1; + } + indexedSourceRoot->unref(); + + SoSeparator * indexedLineRoot = new SoSeparator; + indexedLineRoot->ref(); + SoCoordinate3 * indexedLineCoordinates = new SoCoordinate3; + indexedLineCoordinates->point.setValues(0, 3, triangle); + SoIndexedLineSet * indexedLine = new SoIndexedLineSet; + const int32_t indexedSegments[] = { 0, 1, -1, 1, 2, -1 }; + indexedLine->coordIndex.setValues(0, 6, indexedSegments); + indexedLineRoot->addChild(indexedLineCoordinates); + indexedLineRoot->addChild(indexedLine); + indexedLineRoot->addChild(indexedLine); + action.apply(indexedLineRoot); + SbBool validIndexedLineReuse = + action.getDrawList().getNumCommands() == 2 && + action.getDrawList().getNumGeometryResources() == 1; + if (validIndexedLineReuse) { + const SoRenderCommand & repeatedLineCommand = + action.getDrawList().getCommand(1); + const std::vector & repeatedLineRanges = + action.getDrawList().getCommandElementRanges(repeatedLineCommand); + const SoGeometryResource * repeatedLineResource = + action.getDrawList().getGeometryResource( + repeatedLineCommand.geometryHandle); + validIndexedLineReuse = + action.getDrawList().getCommand(0).geometryHandle == + repeatedLineCommand.geometryHandle && + repeatedLineRanges.size() == 2 && + repeatedLineRanges[0].type == SO_PICK_EDGE && + repeatedLineRanges[0].elementIndex == 0 && + repeatedLineRanges[1].elementIndex == 1 && + repeatedLineCommand.pick.useResourceElementRanges && + repeatedLineCommand.pick.elementRanges.empty() && + repeatedLineResource && repeatedLineResource->elementRanges.size() == 2; + } + if (!validIndexedLineReuse) { + std::cerr << "FAIL: repeated indexed lines did not share one resource" + << std::endl; + result = 1; + } + const uint64_t indexedLineRevision = action.getDrawList().getCommandGeometry( + action.getDrawList().getCommand(0)).revision; + indexedLineCoordinates->point.set1Value(0, SbVec3f(-1.1f, -1.0f, 0.0f)); + action.apply(indexedLineRoot); + if (action.getDrawList().getNumGeometryResources() != 1 || + action.getDrawList().getCommandGeometry( + action.getDrawList().getCommand(0)).revision == indexedLineRevision || + action.getDrawList().getCommand(0).geometryHandle != + action.getDrawList().getCommand(1).geometryHandle) { + std::cerr << "FAIL: retained indexed line source mutation was stale" + << std::endl; + result = 1; + } + indexedLineRoot->unref(); + action.apply(root); + SoRenderCommand command; - command.objectId = 42; command.geometry.topology = SO_TOPOLOGY_TRIANGLES; command.geometry.vertexCount = 3; command.geometry.vertexStride = sizeof(float) * 3; @@ -33,6 +271,60 @@ runTest() result = 1; } + SoSeparator * coloredRoot = new SoSeparator; + coloredRoot->ref(); + SoMaterial * material = new SoMaterial; + material->diffuseColor.setNum(3); + material->diffuseColor.set1Value(0, SbColor(1.0f, 0.0f, 0.0f)); + material->diffuseColor.set1Value(1, SbColor(0.0f, 1.0f, 0.0f)); + material->diffuseColor.set1Value(2, SbColor(0.0f, 0.0f, 1.0f)); + SoMaterialBinding * binding = new SoMaterialBinding; + binding->value = SoMaterialBinding::PER_VERTEX_INDEXED; + SoCoordinate3 * coordinates = new SoCoordinate3; + coordinates->point.setNum(3); + coordinates->point.set1Value(0, SbVec3f(-1.0f, -1.0f, 0.0f)); + coordinates->point.set1Value(1, SbVec3f(1.0f, -1.0f, 0.0f)); + coordinates->point.set1Value(2, SbVec3f(0.0f, 1.0f, 0.0f)); + SoFaceSet * faceSet = new SoFaceSet; + faceSet->numVertices.set1Value(0, 3); + coloredRoot->addChild(material); + coloredRoot->addChild(binding); + coloredRoot->addChild(coordinates); + coloredRoot->addChild(faceSet); + + action.apply(coloredRoot); + if (action.getDrawList().getNumCommands() != 1) { + std::cerr << "FAIL: per-vertex material scene emitted an unexpected " + << "number of commands" << std::endl; + result = 1; + } + else { + const SoGeometryDesc & geometry = + action.getDrawList().getCommand(0).geometry; + const float expected[] = { + 1.0f, 0.0f, 0.0f, 1.0f, + 0.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, 1.0f + }; + if (!geometry.colors || geometry.vertexCount != 3) { + std::cerr << "FAIL: per-vertex material colors were not captured" + << std::endl; + result = 1; + } + else { + for (int i = 0; i < 12; ++i) { + if (std::fabs(geometry.colors[i] - expected[i]) > 0.0001f) { + std::cerr << "FAIL: captured vertex color differs at component " + << i << std::endl; + result = 1; + break; + } + } + } + } + + coloredRoot->unref(); + action.getMutableDrawList().buildPickLUT(); if (action.getDrawList().getPickLUT().size() != 1 || action.getDrawList().resolvePickId(1) == NULL) { diff --git a/testsuite/RetainedMaterialLightingTest.cpp b/testsuite/RetainedMaterialLightingTest.cpp index fe7bf058c09..cd2dd358e9a 100644 --- a/testsuite/RetainedMaterialLightingTest.cpp +++ b/testsuite/RetainedMaterialLightingTest.cpp @@ -320,13 +320,38 @@ runTest() textureRoot->addChild(new SoCube); SoIRRenderAction textureAction(SbViewportRegion(32, 32)); textureAction.apply(textureRoot); - if (textureAction.getDrawList().getNumCommands() != 2 || - textureAction.getDrawList().getCommand(0).material.texture.pixels != - textureAction.getDrawList().getCommand(1).material.texture.pixels) { - std::cerr << "FAIL: frame texture payload was copied once per command" + if (textureAction.getDrawList().getNumCommands() != 2) { + std::cerr << "FAIL: shared texture scene did not emit two commands" << std::endl; result = 1; } + else { + const SoTextureData & first = + textureAction.getDrawList().getCommand(0).material.texture; + const SoTextureData & second = + textureAction.getDrawList().getCommand(1).material.texture; + if (first.pixels != second.pixels || first.cacheKey == 0 || + first.cacheKey != second.cacheKey || + first.revision != second.revision) { + std::cerr << "FAIL: shared scene texture lost its retained identity" + << std::endl; + result = 1; + } + const uint64_t originalKey = first.cacheKey; + const uint64_t originalRevision = first.revision; + const unsigned char replacement[] = { 32, 64, 128, 255 }; + sharedTexture->image.setValue(SbVec2s(1, 1), 4, replacement); + textureAction.apply(textureRoot); + const SoTextureData & updated = + textureAction.getDrawList().getCommand(0).material.texture; + if ((updated.cacheKey == originalKey && + updated.revision == originalRevision) || + updated.pixels[0] != replacement[0]) { + std::cerr << "FAIL: changed scene texture retained stale identity" + << std::endl; + result = 1; + } + } textureRoot->unref(); } diff --git a/testsuite/RetainedPickingIdentityTest.cpp b/testsuite/RetainedPickingIdentityTest.cpp index e55ffcd0cd2..e3c11b66993 100644 --- a/testsuite/RetainedPickingIdentityTest.cpp +++ b/testsuite/RetainedPickingIdentityTest.cpp @@ -37,13 +37,15 @@ const SoRenderCommand * commandFor(const SoIRRenderAction & action, return NULL; } -bool hasRange(const SoRenderCommand & command, +bool hasRange(const SoDrawList & drawlist, + const SoRenderCommand & command, SoPickElementType type, int elementIndex, uint32_t drawStart, uint32_t drawCount) { - for (const SoRenderElementRange & range : command.pick.elementRanges) { + for (const SoRenderElementRange & range : + drawlist.getCommandElementRanges(command)) { if (range.type == type && range.elementIndex == elementIndex && range.drawStart == drawStart && range.drawCount == drawCount) { return true; @@ -126,8 +128,10 @@ main() const SoRenderCommand * faceCommand = commandFor(action, faces, commandIndex); check(faceCommand != NULL, "indexed faces did not produce a retained command", result); if (faceCommand) { - check(hasRange(*faceCommand, SO_PICK_FACE, 0, 0, 3) && - hasRange(*faceCommand, SO_PICK_FACE, 1, 3, 3), + check(hasRange(action.getDrawList(), *faceCommand, + SO_PICK_FACE, 0, 0, 3) && + hasRange(action.getDrawList(), *faceCommand, + SO_PICK_FACE, 1, 3, 3), "indexed faces did not retain face ranges", result); check(action.getCommandPath(commandIndex) != NULL && action.getCommandPath(commandIndex)->getTail() == faces, @@ -139,25 +143,32 @@ main() check(indexedLineCommand != NULL, "indexed lines did not produce a retained command", result); if (indexedLineCommand) { - check(hasRange(*indexedLineCommand, SO_PICK_EDGE, 0, 0, 2) && - hasRange(*indexedLineCommand, SO_PICK_EDGE, 1, 2, 2), + check(hasRange(action.getDrawList(), *indexedLineCommand, + SO_PICK_EDGE, 0, 0, 2) && + hasRange(action.getDrawList(), *indexedLineCommand, + SO_PICK_EDGE, 1, 2, 2), "indexed lines did not retain edge ranges", result); } const SoRenderCommand * lineCommand = commandFor(action, lines, commandIndex); check(lineCommand != NULL, "line set did not produce a retained command", result); if (lineCommand) { - check(hasRange(*lineCommand, SO_PICK_EDGE, 0, 0, 2) && - hasRange(*lineCommand, SO_PICK_EDGE, 1, 2, 2), + check(hasRange(action.getDrawList(), *lineCommand, + SO_PICK_EDGE, 0, 0, 2) && + hasRange(action.getDrawList(), *lineCommand, + SO_PICK_EDGE, 1, 2, 2), "line set did not retain edge ranges", result); } const SoRenderCommand * pointCommand = commandFor(action, points, commandIndex); check(pointCommand != NULL, "point set did not produce a retained command", result); if (pointCommand) { - check(hasRange(*pointCommand, SO_PICK_VERTEX, 0, 0, 1) && - hasRange(*pointCommand, SO_PICK_VERTEX, 1, 1, 1) && - hasRange(*pointCommand, SO_PICK_VERTEX, 2, 2, 1), + check(hasRange(action.getDrawList(), *pointCommand, + SO_PICK_VERTEX, 0, 0, 1) && + hasRange(action.getDrawList(), *pointCommand, + SO_PICK_VERTEX, 1, 1, 1) && + hasRange(action.getDrawList(), *pointCommand, + SO_PICK_VERTEX, 2, 2, 1), "point set did not retain vertex ranges", result); }