diff --git a/include/Inventor/actions/SoActions.h b/include/Inventor/actions/SoActions.h index e664c0db8af..451b5cdba12 100644 --- a/include/Inventor/actions/SoActions.h +++ b/include/Inventor/actions/SoActions.h @@ -52,6 +52,7 @@ #endif #include #include +#include #include #include #include diff --git a/include/Inventor/actions/SoIRRenderAction.h b/include/Inventor/actions/SoIRRenderAction.h new file mode 100644 index 00000000000..bbaa3fe5008 --- /dev/null +++ b/include/Inventor/actions/SoIRRenderAction.h @@ -0,0 +1,127 @@ +// include/Inventor/actions/SoIRRenderAction.h + +#ifndef COIN_SOIRRENDERACTION_H +#define COIN_SOIRRENDERACTION_H + +#include +#include +#include +#include + +#include + +#include +class SoPrimitiveVertex; +class SoPath; +class SoPathList; +class SoNode; +class SoIRRenderActionP; + +/*! + \class SoIRRenderAction SoIRRenderAction.h + \brief Render action that traverses a scene graph into a backend-neutral draw list. + + \ingroup coin_actions + + SoIRRenderAction is the traversal front-end for Coin's render-backend path. + Unlike SoGLRenderAction, it does not issue OpenGL commands directly during + traversal. Instead it records geometry, material state, and render state + into a SoDrawList that can later be consumed by a concrete backend. + + The action owns transient per-frame storage for generated geometry. + + A frame begins with beginFrame() (also performed by the normal apply + entry points), records commands through addCommand(), and ends when the + caller replaces or clears the action's frame. Geometry and other borrowed + command data must not be retained beyond that frame lifetime. + + \ingroup coin_retained_rendering +*/ +class COIN_DLL_API SoIRRenderAction : public SoAction { + typedef SoAction inherited; + SO_ACTION_HEADER(SoIRRenderAction); + +public: + /*! + \class SoIRRenderAction::PrimitiveCollector + \brief Callback interface for receiving primitives generated during traversal. + + Shapes that fall back to generatePrimitives() can stream their output + through a PrimitiveCollector instead of building temporary Coin-specific + callback structures. The active collector is managed as a stack so helper + code can install a collector for a limited traversal scope. + */ + class PrimitiveCollector { + public: + virtual ~PrimitiveCollector() {} + virtual void onTriangle(const SoPrimitiveVertex * v1, + const SoPrimitiveVertex * v2, + const SoPrimitiveVertex * v3) = 0; + virtual void onLine(const SoPrimitiveVertex * v1, + const SoPrimitiveVertex * v2) = 0; + virtual void onPoint(const SoPrimitiveVertex * v) = 0; + }; + + static void initClass(void); + + SoIRRenderAction(const SbViewportRegion & vp); + virtual ~SoIRRenderAction(); + + //! Clear the current draw list and begin a new retained frame. + void beginFrame(); + + void setViewportRegion(const SbViewportRegion & vp); + const SbViewportRegion & getViewportRegion(void) const { return this->vpRegion; } + + // Standard entry points, mirroring SoGLRenderAction + virtual void apply(SoNode * root) override; + virtual void apply(SoPath * path) override; + virtual void apply(const SoPathList & pathlist, SbBool obeysrules = FALSE) override; + + //! Append a retained command produced during the current traversal. + void addCommand(const SoRenderCommand & command); + + //! Mark the current frame as unsupported by the retained renderer. + void markUnsupported(const SoNode * node, const char * reason); + //! Return whether traversal encountered semantics not represented by IR. + SbBool hasUnsupportedRendering() const { return this->unsupportedRendering; } + //! Return the first node that made this frame unsupported, if any. + const SoNode * getUnsupportedNode() const { return this->unsupportedNode; } + //! Return a static or otherwise frame-stable explanation for the status. + const char * getUnsupportedReason() const { return this->unsupportedReason; } + + //! Return the generated draw list for the current frame. + const SoDrawList & getDrawList(void) const { return this->drawlist; } + //! Mutable access to the generated draw list for the current frame. + SoDrawList & getMutableDrawList() { return this->drawlist; } + + /*! + \brief Allocate per-frame geometry storage owned by the action. + + The returned memory remains valid until the frame resources are cleared or + the geometry pool is rewound to an earlier save point. + */ + void * allocateGeometryStorage(size_t bytes, size_t alignment = alignof(float)); + + //! Push a primitive collector for subsequent fallback primitive generation. + void pushPrimitiveCollector(PrimitiveCollector * collector); + //! Pop the current primitive collector. The caller must pop in stack order. + void popPrimitiveCollector(PrimitiveCollector * collector); + //! Return the currently active primitive collector, or NULL. + PrimitiveCollector * getActivePrimitiveCollector(void) const; + +protected: + virtual void beginTraversal(SoNode * node) override; + +private: + void resetFrameResources(); + + SbViewportRegion vpRegion; + SoDrawList drawlist; + SoIRRenderActionP * pimpl; + bool unsupportedRendering = false; + const SoNode * unsupportedNode = nullptr; + const char * unsupportedReason = nullptr; +}; + +#endif // COIN_SOIRRENDERACTION_H diff --git a/include/Inventor/nodes/SoNode.h b/include/Inventor/nodes/SoNode.h index 94c6a88d45c..870fbfa249e 100644 --- a/include/Inventor/nodes/SoNode.h +++ b/include/Inventor/nodes/SoNode.h @@ -51,6 +51,7 @@ class SoRayPickAction; class SoSearchAction; class SoWriteAction; class SoAudioRenderAction; +class SoIRRenderAction; class SbDict; class COIN_DLL_API SoNode : public SoFieldContainer { @@ -95,6 +96,7 @@ class COIN_DLL_API SoNode : public SoFieldContainer { virtual SbBool affectsState(void) const; virtual void doAction(SoAction * action); + virtual void IRRender(SoIRRenderAction * action); virtual void GLRender(SoGLRenderAction * action); virtual void GLRenderBelowPath(SoGLRenderAction * action); virtual void GLRenderInPath(SoGLRenderAction * action); @@ -137,6 +139,7 @@ class COIN_DLL_API SoNode : public SoFieldContainer { static int getActionMethodIndex(const SoType type); static void getBoundingBoxS(SoAction * action, SoNode * node); + static void IRRenderS(SoAction * action, SoNode * node); static void GLRenderS(SoAction * action, SoNode * node); static void callbackS(SoAction * action, SoNode * node); static void getMatrixS(SoAction * action, SoNode * node); diff --git a/include/Inventor/nodes/SoShaderProgram.h b/include/Inventor/nodes/SoShaderProgram.h index 8717b881208..b33d417e126 100644 --- a/include/Inventor/nodes/SoShaderProgram.h +++ b/include/Inventor/nodes/SoShaderProgram.h @@ -38,6 +38,7 @@ #include class SoState; +class SoIRRenderAction; #if COIN_HAVE_LEGACY_GL_RENDERER class SoGLRenderAction; #endif @@ -62,6 +63,7 @@ class COIN_DLL_API SoShaderProgram : public SoNode { void * closure); SoEXTENDER public: + void IRRender(SoIRRenderAction * action) override; #if COIN_HAVE_LEGACY_GL_RENDERER void GLRender(SoGLRenderAction * action) override; #endif diff --git a/include/Inventor/nodes/SoShape.h b/include/Inventor/nodes/SoShape.h index 3e0aec1ece8..75d1b372012 100644 --- a/include/Inventor/nodes/SoShape.h +++ b/include/Inventor/nodes/SoShape.h @@ -48,6 +48,7 @@ class SoCoordinateElement; class SbVec2f; class SoMaterialBundle; class SoBoundingBoxCache; +class SoIRRenderAction; class COIN_DLL_API SoShape : public SoNode { typedef SoNode inherited; @@ -70,6 +71,7 @@ class COIN_DLL_API SoShape : public SoNode { #if COIN_HAVE_LEGACY_GL_RENDERER void GLRender(SoGLRenderAction * action) override; #endif + void IRRender(SoIRRenderAction * action) override; void rayPick(SoRayPickAction * action) override; void callback(SoCallbackAction * action) override; virtual void computeBBox(SoAction * action, SbBox3f & box, diff --git a/include/Inventor/rendering/SoRenderIR.h b/include/Inventor/rendering/SoRenderIR.h new file mode 100644 index 00000000000..a6015819ae6 --- /dev/null +++ b/include/Inventor/rendering/SoRenderIR.h @@ -0,0 +1,454 @@ +// include/Inventor/rendering/SoRenderIR.h + +#ifndef COIN_SORENDERIR_H +#define COIN_SORENDERIR_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +/*! + \file SoRenderIR.h + \brief Backend-neutral intermediate representation for retained rendering. + + SoIRRenderAction produces a SoDrawList while traversing a scene graph. A + renderer backend consumes that list to produce pixels or another + backend-specific result. The types in this file deliberately use + semantic values instead of OpenGL enums so the intermediate representation + does not require a particular graphics API. + + Geometry and embedded texture pointers are borrowed from the producer. They + normally refer to storage owned by the current SoIRRenderAction frame and + must not be retained after that frame is cleared, rewound, or replaced. + Device objects, caches, and other implementation resources belong to the + consumer, not to the intermediate representation. +*/ + +/*! + \defgroup coin_retained_rendering Retained Rendering + \brief Backend-neutral retained rendering and execution interfaces. + + The retained path records scene semantics in an intermediate representation, + then lets a manager and a concrete backend decide when and how to execute + the recorded frame. +*/ + +/*! + \enum SoPrimitiveTopology + \brief Enumerates how primitives referenced by a geometry buffer should be interpreted. +*/ +enum SoPrimitiveTopology : uint8_t { + SO_TOPOLOGY_TRIANGLES = 0, + SO_TOPOLOGY_LINES, + SO_TOPOLOGY_POINTS, + SO_TOPOLOGY_TRIANGLE_STRIP, + SO_TOPOLOGY_LINE_STRIP, + SO_TOPOLOGY_COUNT +}; + +/*! + \struct SoGeometryDesc + \brief Describes vertex/index data for a single draw call. + + All pointers remain owned by the producer (typically SoIRRenderAction). + They must remain valid while the backend consumes the frame. They may point + into the action's frame geometry pool and must not be retained after that + storage is cleared or rewound. Backends are free to copy the data into + backend-owned buffers. + + Strides are byte distances between successive entries. A zero position or + normal stride means three tightly packed floats; a zero texture-coordinate + stride means four tightly packed floats. normalCount may be smaller than + vertexCount when only part of a geometry has normals. +*/ +struct SoGeometryDesc { + SoPrimitiveTopology topology = SO_TOPOLOGY_TRIANGLES; + uint32_t vertexCount = 0; + uint32_t normalCount = 0; + uint32_t indexCount = 0; + + const float * positions = nullptr; + const float * normals = nullptr; + const float * texcoords = nullptr; + const float * colors = nullptr; + const uint32_t * indices = nullptr; + + uint32_t vertexStride = 0; //!< Position/normal stride in bytes. + uint32_t texcoordStride = 0; //!< Texture-coordinate stride in bytes. + + // A nonzero key identifies the source geometry across retained frames. + // Revision changes invalidate the corresponding backend resource. A zero + // key keeps the existing frame-local lifetime contract. + uint64_t cacheKey = 0; + uint64_t revision = 0; + + // Cheap local-space bounds retained for planning. Producers may leave this + // unset when the backend should use its conservative origin fallback. + SbVec3f boundsCenter = SbVec3f(0.0f, 0.0f, 0.0f); + SbBool hasBounds = FALSE; + +}; + +/*! + \enum SoShadingModel + \brief Effective shading contract carried by a render command. + + The legacy-compatible model is the current default. It preserves the + fixed-function Coin/GL behavior while the DrawList backend is migrated to + an explicit shading model. +*/ +enum SoShadingModel : uint8_t { + SO_SHADING_UNLIT = 0, + SO_SHADING_LEGACY_GOURAUD +}; + +// --- Texture sampler state --- +// These are semantic sampler modes rather than OpenGL enum values so the IR +// can be consumed by non-OpenGL backends as well. +enum SoTextureFilter : uint8_t { + SO_TEXTURE_FILTER_NEAREST = 0, + SO_TEXTURE_FILTER_LINEAR, + SO_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST, + SO_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST, + SO_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR, + SO_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR +}; + +enum SoTextureWrap : uint8_t { + SO_TEXTURE_WRAP_CLAMP_TO_EDGE = 0, + SO_TEXTURE_WRAP_REPEAT, + SO_TEXTURE_WRAP_CLAMP_TO_BORDER +}; + +// --- Depth state --------------------------------------------------------- + +// Semantic comparison functions. These deliberately do not use GL enum +// values: the IR is also consumed by backends which do not share GL's enum +// space. +enum SoDepthFunction : uint8_t { + SO_DEPTH_NEVER = 0, + SO_DEPTH_ALWAYS, + SO_DEPTH_LESS, + SO_DEPTH_LEQUAL, + SO_DEPTH_EQUAL, + SO_DEPTH_GEQUAL, + SO_DEPTH_GREATER, + SO_DEPTH_NOTEQUAL +}; + +// --- Blend state --------------------------------------------------------- + +enum SoBlendFactor : uint8_t { + SO_BLEND_FACTOR_ZERO = 0, + SO_BLEND_FACTOR_ONE, + SO_BLEND_FACTOR_SRC_COLOR, + SO_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, + SO_BLEND_FACTOR_DST_COLOR, + SO_BLEND_FACTOR_ONE_MINUS_DST_COLOR, + SO_BLEND_FACTOR_SRC_ALPHA, + SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + SO_BLEND_FACTOR_DST_ALPHA, + SO_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, + SO_BLEND_FACTOR_CONSTANT_COLOR, + SO_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR, + SO_BLEND_FACTOR_CONSTANT_ALPHA, + SO_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA, + SO_BLEND_FACTOR_SRC_ALPHA_SATURATE +}; + +enum SoBlendEquation : uint8_t { + SO_BLEND_EQUATION_ADD = 0, + SO_BLEND_EQUATION_SUBTRACT, + SO_BLEND_EQUATION_REVERSE_SUBTRACT, + SO_BLEND_EQUATION_MIN, + SO_BLEND_EQUATION_MAX +}; + +// --- Alpha-test policy -------------------------------------------------- + +enum SoAlphaTestFunction : uint8_t { + SO_ALPHA_TEST_NONE = 0, + SO_ALPHA_TEST_NEVER, + SO_ALPHA_TEST_ALWAYS, + SO_ALPHA_TEST_LESS, + SO_ALPHA_TEST_LEQUAL, + SO_ALPHA_TEST_EQUAL, + SO_ALPHA_TEST_GEQUAL, + SO_ALPHA_TEST_GREATER, + SO_ALPHA_TEST_NOTEQUAL +}; + +enum SoAlphaTestPolicy : uint8_t { + SO_ALPHA_TEST_POLICY_NONE = 0, + SO_ALPHA_TEST_POLICY_EXPLICIT, + SO_ALPHA_TEST_POLICY_LEGACY_THRESHOLD, + SO_ALPHA_TEST_POLICY_PRESERVE_EDGES +}; + +// --- Render param flags (SoRenderParams::flags) --- +static constexpr uint32_t SO_PARAM_CLEAR_WINDOW = 1u; +static constexpr uint32_t SO_PARAM_CLEAR_DEPTH = 4u; //!< Clear depth buffer before rendering + +/*! + \struct SoTextureData + \brief Embedded texture payload carried directly by a render command. + + This is used for commands that provide their own embedded image data. + The memory is owned by the producer of the draw list and must remain valid + until the backend finishes consuming the frame. +*/ +struct SoTextureData { + const unsigned char * pixels = nullptr; + int width = 0; + int height = 0; + int numComponents = 0; // 1=L, 2=LA, 3=RGB, 4=RGBA + + SoTextureFilter minFilter = SO_TEXTURE_FILTER_NEAREST; + SoTextureFilter magFilter = SO_TEXTURE_FILTER_NEAREST; + SoTextureWrap wrapS = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + SoTextureWrap wrapT = SO_TEXTURE_WRAP_CLAMP_TO_EDGE; + + // A nonzero key permits a backend to retain the texture resource across + // frame lifetimes. revision changes require the resource contents to be + // refreshed; zero remains transient. + uint64_t cacheKey = 0; + uint64_t revision = 0; +}; + +/*! + \struct SoMaterialData + \brief Snapshot of the logical Inventor material state for one draw call. + + Texture pixels are embedded in the IR as borrowed data; the producer owns the + storage and keeps it alive until the backend finishes consuming the frame. +*/ +struct SoMaterialData { + SbVec4f diffuse = {0.8f, 0.8f, 0.8f, 1.0f}; + SbVec4f ambient = {0.2f, 0.2f, 0.2f, 1.0f}; + SbVec4f specular = {0.0f, 0.0f, 0.0f, 1.0f}; + SbVec4f emissive = {0.0f, 0.0f, 0.0f, 1.0f}; + SoShadingModel shadingModel = SO_SHADING_LEGACY_GOURAUD; + float shininess = 0.2f; + float opacity = 1.0f; + + SoTextureData texture; //!< Embedded texture data. + + // Some CPU-rasterized textures already multiply their texel alpha by + // material opacity. Consumers use this to avoid multiplying that opacity a + // second time while still composing vertex and texture alpha. + bool textureAlphaIncludesOpacity = false; + + // Material-derived per-vertex colors can already carry the effective + // material transparency (for example SoMaterial PER_FACE colors). Packed + // SoVertexProperty colors carry independent vertex alpha instead. + bool vertexColorAlphaIncludesOpacity = false; + + bool twoSidedLighting = false; +}; + +/*! + \struct SoDepthState + \brief Depth-test configuration for a draw call. +*/ +struct SoDepthState { + SbBool enabled = TRUE; + SbBool writeEnabled = TRUE; + SoDepthFunction func = SO_DEPTH_LEQUAL; + SbVec2f range = SbVec2f(0.0f, 1.0f); +}; + +/*! + \struct SoBlendState + \brief Backend-neutral blending configuration. +*/ +struct SoBlendState { + SbBool enabled = FALSE; + SoBlendFactor srcRGBFactor = SO_BLEND_FACTOR_ONE; + SoBlendFactor dstRGBFactor = SO_BLEND_FACTOR_ZERO; + SoBlendFactor srcAlphaFactor = SO_BLEND_FACTOR_ONE; + SoBlendFactor dstAlphaFactor = SO_BLEND_FACTOR_ZERO; + + // Coin's current LegacyGL state API exposes blend factors but not blend + // equations. ADD is therefore the only equation that can be captured + // from traversal today; separate fields keep the IR ready for a future + // state source without pretending that it is currently preserved. + SoBlendEquation rgbEquation = SO_BLEND_EQUATION_ADD; + SoBlendEquation alphaEquation = SO_BLEND_EQUATION_ADD; +}; + +/*! + \struct SoAlphaTestState + \brief Explicit fragment alpha policy for a render command. +*/ +struct SoAlphaTestState { + SoAlphaTestPolicy policy = SO_ALPHA_TEST_POLICY_NONE; + SoAlphaTestFunction function = SO_ALPHA_TEST_NONE; + float reference = 0.5f; +}; + +/*! + \enum SoRasterFillMode + \brief Backend-neutral polygon fill mode retained from traversal. +*/ +enum SoRasterFillMode : uint8_t { + SO_RASTER_FILL = 0, + SO_RASTER_LINES, + SO_RASTER_POINTS +}; + +/*! + \struct SoRasterState + \brief Rasterizer properties (fill mode, culling, polygon offset). +*/ +struct SoRasterState { + SoRasterFillMode fillMode = SO_RASTER_FILL; + uint8_t cullMode = 0; + SbBool scissorEnabled = FALSE; + SbBool viewportEnabled = FALSE; + int viewportX = 0; + int viewportY = 0; + int viewportWidth = 0; + int viewportHeight = 0; + float lineWidth = 1.0f; + float pointSize = 1.0f; + float polygonOffsetFactor = 0.0f; + float polygonOffsetUnits = 0.0f; +}; + +/*! + \struct SoRenderState + \brief Aggregates depth, blend, alpha-test, and raster state. +*/ +struct SoRenderState { + SoDepthState depth; + SoBlendState blend; + SoAlphaTestState alphaTest; + SoRasterState raster; +}; + +/*! + \typedef SoLightingHandle + \brief Stable 1-based handle into the draw list's deduplicated lighting table. +*/ +typedef uint32_t SoLightingHandle; + +/*! + \enum SoLightType + \brief Light kinds captured in render-backend lighting setups. +*/ +enum SoLightType : uint8_t { + SO_LIGHT_DIRECTIONAL = 0, + SO_LIGHT_POINT, + SO_LIGHT_SPOT +}; + +/*! + \struct SoLightData + \brief View-space light description used by the render backend. +*/ +struct SoLightData { + SoLightType type = SO_LIGHT_DIRECTIONAL; + SbVec3f color = SbVec3f(1.0f, 1.0f, 1.0f); + SbVec3f direction = SbVec3f(0.0f, 0.0f, 1.0f); + SbVec3f position = SbVec3f(0.0f, 0.0f, 1.0f); + SbVec3f attenuation = SbVec3f(0.0f, 0.0f, 1.0f); + float spotCutoffCos = -1.0f; + float spotExponent = 0.0f; +}; + +/*! + \struct SoLightingData + \brief Shared lighting setup referenced by render commands. +*/ +struct SoLightingData { + SbVec3f ambient = SbVec3f(0.2f, 0.2f, 0.2f); + std::vector lights; +}; + +/*! + \struct SoRenderCommand + \brief Backend-neutral retained rendering command. + + A command contains the geometry, material, raster state, and transforms + needed to execute one retained draw operation. + Pointer-valued data is borrowed from storage owned by the producing + SoDrawList/SoIRRenderAction frame and must not outlive that frame. + + \ingroup coin_retained_rendering +*/ +struct SoRenderCommand { + // Geometry, texture pixels, and other pointer-valued fields are borrowed; + // see the lifetime contract on SoGeometryDesc and SoTextureData. + SoGeometryDesc geometry; + SoMaterialData material; + SoRenderState state; + + SbMatrix modelMatrix; // default-constructed to identity + SbMatrix viewMatrix; + SbMatrix projMatrix; + + SoLightingHandle lightingHandle = 0; + // Stable scene identity. Zero means that the producer did not provide one. + uint64_t objectId = 0; + void * userData = nullptr; //!< Opaque, non-owned producer data. +}; + +/*! + \class SoDrawList + \brief Container holding the commands and auxiliary tables for one frame. + + Commands retain their insertion order. The draw list never imposes + execution ordering on a backend. clear() starts a new frame and invalidates pointers + into producer-owned frame storage. + + Command indices are therefore stable until the list is truncated or + cleared. Derived lookup tables are frame-local and must be rebuilt after + their source commands or frame generation changes. + + \ingroup coin_retained_rendering +*/ +class COIN_DLL_API SoDrawList { +public: + SoDrawList(); + + //! Clear commands and per-frame tables, beginning a new frame generation. + void clear(); + void reserve(int count); + + //! Return the generation number incremented when clear() starts a new frame. + uint32_t getGeneration() const { return generation; } + + void addCommand(const SoRenderCommand & cmd); + SoRenderCommand & emplaceCommand(); + + int getNumCommands() const; + //! Remove commands beyond index count without reordering remaining commands. + void truncate(int count); + SoRenderCommand & getCommand(int i); + const SoRenderCommand & getCommand(int i) const; + + //! Add or reuse a lighting setup and return its stable 1-based handle. + SoLightingHandle addLightingSetup(const SoLightingData & lighting); + + //! Resolve a lighting handle previously returned by addLightingSetup(). + //! Returns NULL for handle 0 or an invalid handle. + const SoLightingData * getLighting(SoLightingHandle handle) const; + + SoRenderCommand * begin(); + SoRenderCommand * end(); + const SoRenderCommand * begin() const; + const SoRenderCommand * end() const; + +private: + std::vector commands; + std::vector lightingSetups; + uint32_t generation = 0; +}; + +#endif // COIN_SORENDERIR_H diff --git a/src/actions/CMakeLists.txt b/src/actions/CMakeLists.txt index 60217f3592b..ad879d98d58 100644 --- a/src/actions/CMakeLists.txt +++ b/src/actions/CMakeLists.txt @@ -15,6 +15,7 @@ set(COIN_ACTIONS_FILES SoToVRML2Action.cpp SoWriteAction.cpp SoAudioRenderAction.cpp + SoIRRenderAction.cpp ) if(COIN_BUILD_LEGACY_GL_RENDERER) diff --git a/src/actions/SoAction.cpp b/src/actions/SoAction.cpp index 2df0741408b..14601a5806c 100644 --- a/src/actions/SoAction.cpp +++ b/src/actions/SoAction.cpp @@ -1319,6 +1319,10 @@ SoAction::shouldCompactPathList(void) const void SoAction::switchToPathTraversal(SoPath * path) { + if (!path || path->getLength() == 0 || !path->getNode(0)) { + return; + } + // Store current state. SoActionP::AppliedData storeddata = PRIVATE(this)->applieddata; AppliedCode storedcode = PRIVATE(this)->appliedcode; @@ -1327,10 +1331,14 @@ SoAction::switchToPathTraversal(SoPath * path) // Start path traversal. Don't use beginTraversal() (the user might // have overridden it). + path->ref(); PRIVATE(this)->appliedcode = SoAction::PATH; PRIVATE(this)->applieddata.path = path; - this->currentpathcode = SoAction::IN_PATH; + this->currentpathcode = path->getFullLength() > 1 + ? SoAction::IN_PATH : SoAction::BELOW_PATH; + this->currentpath.setHead(path->getNode(0)); this->traverse(path->getNode(0)); + path->unrefNoDelete(); // Restore previous state. this->currentpath = storedpath; diff --git a/src/actions/SoIRRenderAction.cpp b/src/actions/SoIRRenderAction.cpp new file mode 100644 index 00000000000..50df9563898 --- /dev/null +++ b/src/actions/SoIRRenderAction.cpp @@ -0,0 +1,276 @@ +// src/actions/SoIRRenderAction.cpp + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "actions/SoSubActionP.h" +#include "rendering/SoRenderIRP.h" + +#include +#include +#include + +SO_ACTION_SOURCE(SoIRRenderAction); + +class SoIRRenderActionP { +public: + SoIRRenderActionP() = default; + + SoIRBuffer geometryPool; + SbList collectorStack; +}; + +#define PRIVATE(obj) (obj->pimpl) + +void +SoIRRenderAction::initClass(void) +{ + SO_ACTION_INTERNAL_INIT_CLASS(SoIRRenderAction, SoAction); + + if (SoCacheElement::getClassTypeId() == SoType::badType()) { + SoCacheElement::initClass(); + } + SO_ACTION_ADD_METHOD_INTERNAL(SoNode, SoNode::IRRenderS); + + SO_ENABLE(SoIRRenderAction, SoViewportRegionElement); + SO_ENABLE(SoIRRenderAction, SoViewVolumeElement); + SO_ENABLE(SoIRRenderAction, SoViewingMatrixElement); + SO_ENABLE(SoIRRenderAction, SoProjectionMatrixElement); + SO_ENABLE(SoIRRenderAction, SoMultiTextureImageElement); + SO_ENABLE(SoIRRenderAction, SoMultiTextureMatrixElement); + SO_ENABLE(SoIRRenderAction, SoOverrideElement); + SO_ENABLE(SoIRRenderAction, SoModelMatrixElement); + SO_ENABLE(SoIRRenderAction, SoLazyElement); + SO_ENABLE(SoIRRenderAction, SoDepthBufferElement); + SO_ENABLE(SoIRRenderAction, SoDrawStyleElement); + SO_ENABLE(SoIRRenderAction, SoLineWidthElement); + SO_ENABLE(SoIRRenderAction, SoPolygonOffsetElement); + SO_ENABLE(SoIRRenderAction, SoShapeStyleElement); + SO_ENABLE(SoIRRenderAction, SoLightModelElement); + SO_ENABLE(SoIRRenderAction, SoLightElement); + SO_ENABLE(SoIRRenderAction, SoEnvironmentElement); + SO_ENABLE(SoIRRenderAction, SoLightAttenuationElement); + SO_ENABLE(SoIRRenderAction, SoMaterialBindingElement); + SO_ENABLE(SoIRRenderAction, SoNormalBindingElement); + SO_ENABLE(SoIRRenderAction, SoCacheElement); + SO_ENABLE(SoIRRenderAction, SoBumpMapCoordinateElement); + SO_ENABLE(SoIRRenderAction, SoMultiTextureEnabledElement); + + // Elements needed by generatePrimitives() fallback in SoShape::render() + SO_ENABLE(SoIRRenderAction, SoCoordinateElement); + SO_ENABLE(SoIRRenderAction, SoNormalElement); + SO_ENABLE(SoIRRenderAction, SoCreaseAngleElement); + SO_ENABLE(SoIRRenderAction, SoComplexityElement); + SO_ENABLE(SoIRRenderAction, SoComplexityTypeElement); + SO_ENABLE(SoIRRenderAction, SoMultiTextureCoordinateElement); + SO_ENABLE(SoIRRenderAction, SoProfileElement); + SO_ENABLE(SoIRRenderAction, SoProfileCoordinateElement); + SO_ENABLE(SoIRRenderAction, SoTextureQualityElement); + SO_ENABLE(SoIRRenderAction, SoTextureUnitElement); + SO_ENABLE(SoIRRenderAction, SoSwitchElement); + SO_ENABLE(SoIRRenderAction, SoUnitsElement); + + // Scene state elements needed by standard nodes during traversal + SO_ENABLE(SoIRRenderAction, SoShapeHintsElement); + SO_ENABLE(SoIRRenderAction, SoFocalDistanceElement); + SO_ENABLE(SoIRRenderAction, SoFontNameElement); + SO_ENABLE(SoIRRenderAction, SoFontSizeElement); + SO_ENABLE(SoIRRenderAction, SoPointSizeElement); + SO_ENABLE(SoIRRenderAction, SoDecimationPercentageElement); + SO_ENABLE(SoIRRenderAction, SoDecimationTypeElement); + SO_ENABLE(SoIRRenderAction, SoTextureOverrideElement); +} + +SoIRRenderAction::SoIRRenderAction(const SbViewportRegion & vp) + : SoAction(), vpRegion(vp), pimpl(new SoIRRenderActionP) +{ + SO_ACTION_CONSTRUCTOR(SoIRRenderAction); +} + +SoIRRenderAction::~SoIRRenderAction() +{ + delete PRIVATE(this); + PRIVATE(this) = NULL; +} + +void +SoIRRenderAction::setViewportRegion(const SbViewportRegion & vp) +{ + this->vpRegion = vp; +} + +void +SoIRRenderAction::beginFrame() +{ + this->drawlist.clear(); + this->resetFrameResources(); + this->unsupportedRendering = false; + this->unsupportedNode = nullptr; + this->unsupportedReason = nullptr; +} + +void +SoIRRenderAction::apply(SoNode * root) +{ + this->beginFrame(); + inherited::apply(root); +} + +void +SoIRRenderAction::apply(SoPath * path) +{ + this->beginFrame(); + inherited::apply(path); +} + +void +SoIRRenderAction::apply(const SoPathList & pathlist, SbBool obeysrules) +{ + this->beginFrame(); + inherited::apply(pathlist, obeysrules); +} + +void +SoIRRenderAction::addCommand(const SoRenderCommand & command) +{ + SoRenderCommand retained = command; + if (retained.objectId == 0) { + const SoPath * currentPath = this->getCurPath(); + SoNode * tail = currentPath ? currentPath->getTail() : nullptr; + if (tail) retained.objectId = tail->getNodeId(); + } + + if (!retained.geometry.hasBounds && retained.geometry.positions && + retained.geometry.vertexCount > 0) { + const size_t stride = retained.geometry.vertexStride + ? retained.geometry.vertexStride : sizeof(float) * 3; + const char * position = reinterpret_cast( + retained.geometry.positions); + SbVec3f minimum(std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max()); + SbVec3f maximum(-std::numeric_limits::max(), + -std::numeric_limits::max(), + -std::numeric_limits::max()); + for (uint32_t i = 0; i < retained.geometry.vertexCount; ++i) { + const float * vertex = reinterpret_cast( + position + static_cast(i) * stride); + for (int axis = 0; axis < 3; ++axis) { + minimum[axis] = std::min(minimum[axis], vertex[axis]); + maximum[axis] = std::max(maximum[axis], vertex[axis]); + } + } + retained.geometry.boundsCenter.setValue( + (minimum[0] + maximum[0]) * 0.5f, + (minimum[1] + maximum[1]) * 0.5f, + (minimum[2] + maximum[2]) * 0.5f); + retained.geometry.hasBounds = TRUE; + } + this->drawlist.addCommand(retained); +} + +void +SoIRRenderAction::markUnsupported(const SoNode * node, const char * reason) +{ + if (this->unsupportedRendering) return; + this->unsupportedRendering = true; + this->unsupportedNode = node; + this->unsupportedReason = reason ? reason : + "unsupported retained rendering semantics"; +} + +void +SoIRRenderAction::beginTraversal(SoNode * node) +{ + SoViewportRegionElement::set(this->state, this->vpRegion); + inherited::beginTraversal(node); +} + +void +SoIRRenderAction::pushPrimitiveCollector(PrimitiveCollector * collector) +{ + assert(collector != NULL); + PRIVATE(this)->collectorStack.append(collector); +} + +void +SoIRRenderAction::popPrimitiveCollector(PrimitiveCollector * collector) +{ + const int count = PRIVATE(this)->collectorStack.getLength(); + assert(count > 0); + assert(PRIVATE(this)->collectorStack[count - 1] == collector); + PRIVATE(this)->collectorStack.remove(count - 1); +} + +SoIRRenderAction::PrimitiveCollector * +SoIRRenderAction::getActivePrimitiveCollector(void) const +{ + const int count = PRIVATE(this)->collectorStack.getLength(); + if (count == 0) return NULL; + return PRIVATE(this)->collectorStack[count - 1]; +} + +void * +SoIRRenderAction::allocateGeometryStorage(size_t bytes, size_t alignment) +{ + return PRIVATE(this)->geometryPool.allocate(bytes, alignment); +} + +void +SoIRRenderAction::resetFrameResources() +{ + PRIVATE(this)->geometryPool.clear(); + PRIVATE(this)->collectorStack.truncate(0); +} diff --git a/src/actions/all-actions-cpp.cpp b/src/actions/all-actions-cpp.cpp index 8706c253c02..16b8e86e3af 100644 --- a/src/actions/all-actions-cpp.cpp +++ b/src/actions/all-actions-cpp.cpp @@ -59,5 +59,6 @@ #include "SoToVRMLAction.cpp" #include "SoWriteAction.cpp" #include "SoAudioRenderAction.cpp" +#include "SoIRRenderAction.cpp" #include "SoToVRML2Action.cpp" // #include "SoIntersectionDetectionAction.cpp" diff --git a/src/misc/SoDB.cpp b/src/misc/SoDB.cpp index e9f8fda7ff0..b1ce8c5020c 100644 --- a/src/misc/SoDB.cpp +++ b/src/misc/SoDB.cpp @@ -83,6 +83,7 @@ class SoVBO; #include #include #include +#include #include #include #include @@ -335,6 +336,7 @@ SoDB::init(void) // Actions must be initialized before nodes (because of SO_ENABLE) SoAction::initClass(); SoNode::initClass(); + SoIRRenderAction::initClass(); SoEngine::initClass(); SoEvent::initClass(); SoSensor::initClass(); diff --git a/src/nodes/SoNode.cpp b/src/nodes/SoNode.cpp index 747c9f47d2f..bf416cae1e4 100644 --- a/src/nodes/SoNode.cpp +++ b/src/nodes/SoNode.cpp @@ -205,6 +205,7 @@ SbUniqueId is not really a class, just a \c typedef. #include #include #include +#include #include #include #include @@ -861,6 +862,20 @@ SoNode::doAction(SoAction * COIN_UNUSED_ARG(action)) { } +void +SoNode::IRRender(SoIRRenderAction * action) +{ + this->doAction(action); +} + +void +SoNode::IRRenderS(SoAction * action, SoNode * node) +{ + assert(action != NULL); + assert(node != NULL); + node->IRRender(static_cast(action)); +} + // Note that this documentation will also be used for all subclasses // which reimplements the method, so keep the doc "generic enough". /*! diff --git a/src/rendering/CMakeLists.txt b/src/rendering/CMakeLists.txt index f0c774aadf4..4f105c1cadc 100644 --- a/src/rendering/CMakeLists.txt +++ b/src/rendering/CMakeLists.txt @@ -6,6 +6,7 @@ set(COIN_RENDERING_FILES CoinOffscreenGLCanvas.cpp SoRenderManager.cpp SoRenderManagerP.cpp + SoRenderIR.cpp ) if(COIN_BUILD_LEGACY_GL_RENDERER) @@ -47,6 +48,7 @@ set(COIN_RENDERING_INTERNAL_FILES CoinOffscreenGLCanvas.h CoinOffscreenGLCanvas.cpp CoinGLReadback.h + SoRenderIRP.h ) # build library diff --git a/src/rendering/SoRenderIR.cpp b/src/rendering/SoRenderIR.cpp new file mode 100644 index 00000000000..76a37180d1e --- /dev/null +++ b/src/rendering/SoRenderIR.cpp @@ -0,0 +1,641 @@ +// src/rendering/SoRenderIR.cpp + +#include "rendering/SoRenderIRP.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +bool +lightingEqual(const SoLightData & lhs, const SoLightData & rhs) +{ + return lhs.type == rhs.type && + lhs.color == rhs.color && + lhs.direction == rhs.direction && + lhs.position == rhs.position && + lhs.attenuation == rhs.attenuation && + lhs.spotCutoffCos == rhs.spotCutoffCos && + lhs.spotExponent == rhs.spotExponent; +} + +bool +lightingEqual(const SoLightingData & lhs, const SoLightingData & rhs) +{ + if (lhs.ambient != rhs.ambient || lhs.lights.size() != rhs.lights.size()) { + return false; + } + for (size_t i = 0; i < lhs.lights.size(); ++i) { + if (!lightingEqual(lhs.lights[i], rhs.lights[i])) { + return false; + } + } + return true; +} + +enum LegacyGLBlendFactorValue { + LEGACY_GL_ZERO = 0x0000, + LEGACY_GL_ONE = 0x0001, + LEGACY_GL_SRC_COLOR = 0x0300, + LEGACY_GL_ONE_MINUS_SRC_COLOR = 0x0301, + LEGACY_GL_SRC_ALPHA = 0x0302, + LEGACY_GL_ONE_MINUS_SRC_ALPHA = 0x0303, + LEGACY_GL_DST_ALPHA = 0x0304, + LEGACY_GL_ONE_MINUS_DST_ALPHA = 0x0305, + LEGACY_GL_DST_COLOR = 0x0306, + LEGACY_GL_ONE_MINUS_DST_COLOR = 0x0307, + LEGACY_GL_SRC_ALPHA_SATURATE = 0x0308, + LEGACY_GL_CONSTANT_COLOR = 0x8001, + LEGACY_GL_ONE_MINUS_CONSTANT_COLOR = 0x8002, + LEGACY_GL_CONSTANT_ALPHA = 0x8003, + LEGACY_GL_ONE_MINUS_CONSTANT_ALPHA = 0x8004, + LEGACY_GL_SRC1_ALPHA = 0x8589, + LEGACY_GL_SRC1_COLOR = 0x88F9, + LEGACY_GL_ONE_MINUS_SRC1_COLOR = 0x88FA, + LEGACY_GL_ONE_MINUS_SRC1_ALPHA = 0x88FB +}; + +enum LegacyGLAlphaTestFunctionValue { + LEGACY_GL_NEVER = 0x0200, + LEGACY_GL_LESS = 0x0201, + LEGACY_GL_EQUAL = 0x0202, + LEGACY_GL_LEQUAL = 0x0203, + LEGACY_GL_GREATER = 0x0204, + LEGACY_GL_NOTEQUAL = 0x0205, + LEGACY_GL_GEQUAL = 0x0206, + LEGACY_GL_ALWAYS = 0x0207 +}; + +SoBlendFactor +blendFactorFromLegacyGL(const int value) +{ + // Keep the legacy GL values local to this conversion boundary. No GL enum + // is stored in the public IR. + switch (value) { + case LEGACY_GL_ZERO: return SO_BLEND_FACTOR_ZERO; + case LEGACY_GL_ONE: return SO_BLEND_FACTOR_ONE; + case LEGACY_GL_SRC_COLOR: return SO_BLEND_FACTOR_SRC_COLOR; + case LEGACY_GL_ONE_MINUS_SRC_COLOR: return SO_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + case LEGACY_GL_SRC_ALPHA: return SO_BLEND_FACTOR_SRC_ALPHA; + case LEGACY_GL_ONE_MINUS_SRC_ALPHA: return SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + case LEGACY_GL_DST_ALPHA: return SO_BLEND_FACTOR_DST_ALPHA; + case LEGACY_GL_ONE_MINUS_DST_ALPHA: return SO_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + case LEGACY_GL_DST_COLOR: return SO_BLEND_FACTOR_DST_COLOR; + case LEGACY_GL_ONE_MINUS_DST_COLOR: return SO_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + case LEGACY_GL_SRC_ALPHA_SATURATE: return SO_BLEND_FACTOR_SRC_ALPHA_SATURATE; + case LEGACY_GL_CONSTANT_COLOR: return SO_BLEND_FACTOR_CONSTANT_COLOR; + case LEGACY_GL_ONE_MINUS_CONSTANT_COLOR: return SO_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; + case LEGACY_GL_CONSTANT_ALPHA: return SO_BLEND_FACTOR_CONSTANT_ALPHA; + case LEGACY_GL_ONE_MINUS_CONSTANT_ALPHA: return SO_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA; + // Dual-source factors are represented by the later material/lighting + // layer. The backend-neutral base has no corresponding IR vocabulary yet, + // so retain the historical primary-source approximation here. + case LEGACY_GL_SRC1_ALPHA: return SO_BLEND_FACTOR_SRC_ALPHA; + case LEGACY_GL_SRC1_COLOR: return SO_BLEND_FACTOR_SRC_COLOR; + case LEGACY_GL_ONE_MINUS_SRC1_COLOR: return SO_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + case LEGACY_GL_ONE_MINUS_SRC1_ALPHA: return SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + default: return SO_BLEND_FACTOR_ONE; + } +} + +SoAlphaTestFunction +alphaTestFunctionFromLegacyGL(const int value) +{ + switch (value) { + case LEGACY_GL_NEVER: return SO_ALPHA_TEST_NEVER; + case LEGACY_GL_ALWAYS: return SO_ALPHA_TEST_ALWAYS; + case LEGACY_GL_LESS: return SO_ALPHA_TEST_LESS; + case LEGACY_GL_LEQUAL: return SO_ALPHA_TEST_LEQUAL; + case LEGACY_GL_EQUAL: return SO_ALPHA_TEST_EQUAL; + case LEGACY_GL_GEQUAL: return SO_ALPHA_TEST_GEQUAL; + case LEGACY_GL_GREATER: return SO_ALPHA_TEST_GREATER; + case LEGACY_GL_NOTEQUAL: return SO_ALPHA_TEST_NOTEQUAL; + default: return SO_ALPHA_TEST_NONE; + } +} + +} // namespace + +SbBool +coin_render_ir_trace_enabled() +{ + static int initialized = 0; + static SbBool enabled = FALSE; + if (!initialized) { + enabled = coin_getenv("COIN_DEBUG_RENDER_IR") ? TRUE : FALSE; + initialized = 1; + } + return enabled; +} + +SoIRBuffer::SoIRBuffer() +{ +} + +constexpr size_t SoIRBuffer::MIN_CHUNK_SIZE; + +void +SoIRBuffer::clear() +{ + // Track high-water mark so we can pre-size on next frame + if (this->totalAllocated > this->highWaterMark) { + this->highWaterMark = this->totalAllocated; + } + // Reset cursors but keep chunks allocated + for (auto & chunk : this->chunks) { + chunk->cursor = 0; + } + this->totalAllocated = 0; +} + +void +SoIRBuffer::reserve(size_t bytes) +{ + // Ensure the first chunk is at least this large + if (this->chunks.empty()) { + std::unique_ptr c(new Chunk); + c->data.resize(std::max(bytes, MIN_CHUNK_SIZE)); + this->chunks.push_back(std::move(c)); + } else if (bytes > this->chunks[0]->data.size()) { + // Only resize the first chunk if it hasn't been used yet + if (this->chunks[0]->cursor == 0) { + this->chunks[0]->data.resize(bytes); + } + } +} + +void * +SoIRBuffer::allocate(size_t bytes, size_t alignment) +{ + if (alignment == 0) alignment = 1; + + // Try to allocate from an existing chunk + for (auto & chunk : this->chunks) { + size_t aligned = (chunk->cursor + alignment - 1) & ~(alignment - 1); + if (aligned + bytes <= chunk->data.size()) { + void * ptr = chunk->data.data() + aligned; + chunk->cursor = aligned + bytes; + this->totalAllocated += bytes; + return ptr; + } + } + + // Need a new chunk — size it to at least fit this allocation + // and to avoid many small chunks + size_t chunkSize = std::max({bytes, MIN_CHUNK_SIZE, this->highWaterMark / 2}); + std::unique_ptr c(new Chunk); + c->data.resize(chunkSize); + c->cursor = bytes; + void * ptr = c->data.data(); + this->chunks.push_back(std::move(c)); + this->totalAllocated += bytes; + return ptr; +} + +SoDrawList::SoDrawList() +{ +} + +void +SoDrawList::clear() +{ + this->commands.clear(); + this->lightingSetups.clear(); + this->generation++; +} + +void +SoDrawList::truncate(int count) +{ + if (count < static_cast(this->commands.size())) { + this->commands.resize(static_cast(count)); + } +} + +void +SoDrawList::reserve(int count) +{ + this->commands.reserve(static_cast(count)); +} + +void +SoDrawList::addCommand(const SoRenderCommand & cmd) +{ + this->commands.push_back(cmd); +} + +SoRenderCommand & +SoDrawList::emplaceCommand() +{ + this->commands.emplace_back(); + return this->commands.back(); +} + +int +SoDrawList::getNumCommands() const +{ + return static_cast(this->commands.size()); +} + +SoRenderCommand & +SoDrawList::getCommand(int i) +{ + return this->commands[static_cast(i)]; +} + +const SoRenderCommand & +SoDrawList::getCommand(int i) const +{ + return this->commands[static_cast(i)]; +} + +SoLightingHandle +SoDrawList::addLightingSetup(const SoLightingData & lighting) +{ + for (size_t i = 0; i < this->lightingSetups.size(); ++i) { + if (lightingEqual(this->lightingSetups[i], lighting)) { + return static_cast(i + 1); + } + } + this->lightingSetups.push_back(lighting); + return static_cast(this->lightingSetups.size()); +} + +const SoLightingData * +SoDrawList::getLighting(SoLightingHandle handle) const +{ + if (handle == 0) { + return nullptr; + } + const size_t index = static_cast(handle - 1); + if (index >= this->lightingSetups.size()) { + return nullptr; + } + return &this->lightingSetups[index]; +} + +SoRenderCommand * +SoDrawList::begin() +{ + return this->commands.empty() ? nullptr : this->commands.data(); +} + +SoRenderCommand * +SoDrawList::end() +{ + return this->commands.empty() ? nullptr : this->commands.data() + this->commands.size(); +} + +const SoRenderCommand * +SoDrawList::begin() const +{ + return this->commands.empty() ? nullptr : this->commands.data(); +} + +const SoRenderCommand * +SoDrawList::end() const +{ + return this->commands.empty() ? nullptr : this->commands.data() + this->commands.size(); +} + +void +SoIRDumpSummary(const SoDrawList & drawlist) +{ + if (!coin_render_ir_trace_enabled()) { + return; + } + + uint32_t minVerts = UINT32_MAX; + uint32_t maxVerts = 0; + const int num = drawlist.getNumCommands(); + for (int i = 0; i < num; ++i) { + const SoRenderCommand & cmd = drawlist.getCommand(i); + const uint32_t vc = cmd.geometry.vertexCount; + minVerts = std::min(minVerts, vc); + maxVerts = std::max(maxVerts, vc); + } + + SoDebugError::postInfo("SoDrawList", + "commands=%d minVerts=%u maxVerts=%u", + num, + minVerts == UINT32_MAX ? 0 : minVerts, + maxVerts); +} + +void +SoIRDumpFirstN(const SoDrawList & drawlist, int count) +{ + if (!coin_render_ir_trace_enabled()) { + return; + } + + const int num = drawlist.getNumCommands(); + const int limit = std::min(num, count); + for (int i = 0; i < limit; ++i) { + const SoRenderCommand & cmd = drawlist.getCommand(i); + const SbVec4f & diffuse = cmd.material.diffuse; + const SoLightingData * lighting = drawlist.getLighting(cmd.lightingHandle); + int numlights = lighting ? static_cast(lighting->lights.size()) : -1; + SbVec3f ambient(0.0f, 0.0f, 0.0f); + if (lighting) { + ambient = lighting->ambient; + } + SoDebugError::postInfo("SoDrawList", + "[%d] depth=%d topo=%d verts=%u idx=%u colors=%p diffuse=(%.3f, %.3f, %.3f, %.3f) lights=%d ambient=(%.3f, %.3f, %.3f)", + i, + cmd.state.depth.enabled, + static_cast(cmd.geometry.topology), + cmd.geometry.vertexCount, + cmd.geometry.indexCount, + cmd.geometry.colors, + diffuse[0], + diffuse[1], + diffuse[2], + diffuse[3], + numlights, + ambient[0], + ambient[1], + ambient[2]); + } +} + +namespace SoRenderIR { + +void +fillCommandStateFromState(SoState * state, SoDrawList & drawlist, + SoRenderCommand & command) +{ + command.modelMatrix = SoModelMatrixElement::get(state); + command.viewMatrix = SoViewingMatrixElement::get(state); + command.projMatrix = SoProjectionMatrixElement::get(state); + fillMaterialFromState(state, command.material); + fillRenderStateFromState(state, command.state); + ensureMaterialBlendState(command.state, command.material); + command.lightingHandle = fillLightingFromState(state, drawlist); +} + +void +fillMaterialFromState(SoState * state, SoMaterialData & material) +{ + SoState * mutableState = state; + const SbColor & diffuse = SoLazyElement::getDiffuse(mutableState, 0); + const SbColor & ambient = SoLazyElement::getAmbient(mutableState); + const SbColor & specular = SoLazyElement::getSpecular(mutableState); + const SbColor & emissive = SoLazyElement::getEmissive(mutableState); + const float transparency = SoLazyElement::getTransparency(mutableState, 0); + + material.diffuse.setValue(diffuse[0], diffuse[1], diffuse[2], + 1.0f - transparency); + + // Capture the effective shading contract explicitly. Coin's traditional + // PHONG light model currently maps to the legacy-compatible Gouraud path; + // a true per-fragment PHONG path can be introduced without changing the + // material/light payload carried by the IR. + const int lightModel = SoLightModelElement::get(mutableState); + const bool baseColor = lightModel == SoLightModelElement::BASE_COLOR; + material.shadingModel = baseColor + ? SO_SHADING_UNLIT + : SO_SHADING_LEGACY_GOURAUD; + material.twoSidedLighting = SoLazyElement::getTwoSidedLighting(mutableState) != FALSE; + material.ambient.setValue(ambient[0], ambient[1], ambient[2], 1.0f); + material.specular.setValue(specular[0], specular[1], specular[2], 1.0f); + material.emissive.setValue(emissive[0], emissive[1], emissive[2], 1.0f); + material.shininess = SoLazyElement::getShininess(mutableState); + material.opacity = 1.0f - transparency; + + material.textureAlphaIncludesOpacity = false; + material.vertexColorAlphaIncludesOpacity = false; +} + +void +fillRenderStateFromState(SoState * state, SoRenderState & rs) +{ + SoState * mutableState = state; + SbBool depthtest = TRUE; + SbBool depthwrite = TRUE; + SoDepthBufferElement::DepthWriteFunction depthfunc = + SoDepthBufferElement::LEQUAL; + SbVec2f range; + SoDepthBufferElement::get(mutableState, depthtest, depthwrite, depthfunc, range); + + rs.depth.enabled = depthtest; + rs.depth.writeEnabled = depthwrite; + rs.depth.func = static_cast(depthfunc); + rs.depth.range = range; + + int srcfactor = 0; + int dstfactor = 0; + rs.blend.enabled = SoLazyElement::getBlending(mutableState, srcfactor, dstfactor); + rs.blend.srcRGBFactor = blendFactorFromLegacyGL(srcfactor); + rs.blend.dstRGBFactor = blendFactorFromLegacyGL(dstfactor); + + // Ordinary LegacyGL blending applies the RGB factors to alpha as well. + // Only explicit separate-alpha state supplies different alpha factors. + int srcAlphaFactor = 0; + int dstAlphaFactor = 0; + if (SoLazyElement::getAlphaBlending(mutableState, + srcAlphaFactor, dstAlphaFactor)) { + rs.blend.srcAlphaFactor = blendFactorFromLegacyGL(srcAlphaFactor); + rs.blend.dstAlphaFactor = blendFactorFromLegacyGL(dstAlphaFactor); + } else { + rs.blend.srcAlphaFactor = rs.blend.srcRGBFactor; + rs.blend.dstAlphaFactor = rs.blend.dstRGBFactor; + } + + // LegacyGL does not expose a Coin state element for blend equations. ADD + // is its effective equation and is the only value that can be captured + // deterministically from traversal. + rs.blend.rgbEquation = SO_BLEND_EQUATION_ADD; + rs.blend.alphaEquation = SO_BLEND_EQUATION_ADD; + + float alphaTestValue = 0.5f; + const int alphaTestFunction = SoLazyElement::getAlphaTest(mutableState, + alphaTestValue); + rs.alphaTest.function = alphaTestFunctionFromLegacyGL(alphaTestFunction); + rs.alphaTest.reference = alphaTestValue; + rs.alphaTest.policy = rs.alphaTest.function == SO_ALPHA_TEST_NONE + ? SO_ALPHA_TEST_POLICY_NONE + : SO_ALPHA_TEST_POLICY_EXPLICIT; + + SoDrawStyleElement::Style style = SoDrawStyleElement::get(mutableState); + SoRasterFillMode fillmode = SO_RASTER_FILL; + switch (style) { + case SoDrawStyleElement::LINES: + fillmode = SO_RASTER_LINES; + break; + case SoDrawStyleElement::POINTS: + fillmode = SO_RASTER_POINTS; + break; + default: + fillmode = SO_RASTER_FILL; + break; + } + rs.raster.fillMode = fillmode; + // Native GL_POINTS are square unless point smoothing is enabled. Keep the + // primitive shape explicit in the IR so backends do not choose independently. + + // Backface culling from SoShapeHintsElement: + // vertexOrdering == COUNTERCLOCKWISE + shapeType == SOLID → cull back faces + { + SoShapeHintsElement::VertexOrdering vo; + SoShapeHintsElement::ShapeType st; + SoShapeHintsElement::FaceType ft; + SoShapeHintsElement::get(mutableState, vo, st, ft); + rs.raster.cullMode = (vo == SoShapeHintsElement::COUNTERCLOCKWISE + && st == SoShapeHintsElement::SOLID) ? 1 : 0; + } + rs.raster.scissorEnabled = FALSE; + rs.raster.lineWidth = SoLineWidthElement::get(mutableState); + rs.raster.pointSize = SoPointSizeElement::get(mutableState); + + const SbViewportRegion & viewport = SoViewportRegionElement::get(mutableState); + const SbVec2s & viewportOrigin = viewport.getViewportOriginPixels(); + const SbVec2s & viewportSize = viewport.getViewportSizePixels(); + rs.raster.viewportEnabled = viewportSize[0] > 0 && viewportSize[1] > 0; + rs.raster.viewportX = viewportOrigin[0]; + rs.raster.viewportY = viewportOrigin[1]; + rs.raster.viewportWidth = viewportSize[0]; + rs.raster.viewportHeight = viewportSize[1]; + + float offsetfactor = 0.0f; + float offsetunits = 0.0f; + SoPolygonOffsetElement::Style offsetstyle = SoPolygonOffsetElement::FILLED; + SbBool offseton = FALSE; + SoPolygonOffsetElement::get(mutableState, offsetfactor, offsetunits, + offsetstyle, offseton); + if (!offseton) { + offsetfactor = 0.0f; + offsetunits = 0.0f; + } + rs.raster.polygonOffsetFactor = offsetfactor; + rs.raster.polygonOffsetUnits = offsetunits; + +} + +SoLightingHandle +fillLightingFromState(SoState * state, SoDrawList & drawlist) +{ + SoLightingData lighting; + + const SbColor & ambientColor = SoEnvironmentElement::getAmbientColor(state); + const float ambientIntensity = SoEnvironmentElement::getAmbientIntensity(state); + lighting.ambient.setValue(ambientColor[0] * ambientIntensity, + ambientColor[1] * ambientIntensity, + ambientColor[2] * ambientIntensity); + + const SbVec3f & attenuation = SoLightAttenuationElement::get(state); + const SoNodeList & lights = SoLightElement::getLights(state); + const int numLights = lights.getLength(); + lighting.lights.reserve(numLights); + + for (int i = 0; i < numLights; ++i) { + SoLight * light = static_cast(lights[i]); + if (!light || !light->on.getValue()) { + continue; + } + + const SbColor lightColor = light->color.getValue(); + SoLightData lightData; + lightData.color.setValue(lightColor[0] * light->intensity.getValue(), + lightColor[1] * light->intensity.getValue(), + lightColor[2] * light->intensity.getValue()); + + const SbMatrix & lightMatrix = SoLightElement::getMatrix(state, i); + + if (light->isOfType(SoDirectionalLight::getClassTypeId())) { + SoDirectionalLight * directional = static_cast(light); + lightData.type = SO_LIGHT_DIRECTIONAL; + lightMatrix.multDirMatrix(-(directional->direction.getValue()), lightData.direction); + if (lightData.direction.normalize() == 0.0f) { + lightData.direction.setValue(0.0f, 0.0f, 1.0f); + } + } + else if (light->isOfType(SoPointLight::getClassTypeId())) { + SoPointLight * point = static_cast(light); + lightData.type = SO_LIGHT_POINT; + lightData.attenuation = attenuation; + lightMatrix.multVecMatrix(point->location.getValue(), lightData.position); + } + else if (light->isOfType(SoSpotLight::getClassTypeId())) { + SoSpotLight * spot = static_cast(light); + lightData.type = SO_LIGHT_SPOT; + lightData.attenuation = attenuation; + lightMatrix.multVecMatrix(spot->location.getValue(), lightData.position); + lightMatrix.multDirMatrix(spot->direction.getValue(), lightData.direction); + if (lightData.direction.normalize() == 0.0f) { + lightData.direction.setValue(0.0f, 0.0f, -1.0f); + } + float cutoff = spot->cutOffAngle.getValue(); + if (cutoff < 0.0f) cutoff = 0.0f; + if (cutoff > float(M_PI) * 0.5f) cutoff = float(M_PI) * 0.5f; + lightData.spotCutoffCos = std::cos(cutoff); + float dropoff = spot->dropOffRate.getValue(); + if (dropoff < 0.0f) dropoff = 0.0f; + if (dropoff > 1.0f) dropoff = 1.0f; + lightData.spotExponent = dropoff * 128.0f; + } + else { + continue; + } + + lighting.lights.push_back(lightData); + } + + return drawlist.addLightingSetup(lighting); +} + +bool +isMaterialTransparent(const SoMaterialData & material) +{ + return material.opacity < 0.999f; +} + +void +ensureMaterialBlendState(SoRenderState & renderState, + const SoMaterialData & material) +{ + // SoIRRenderAction captures Coin's logical material state, while the + // legacy GL action enables the conventional blend function as part of its + // transparency setup. Make that implicit IR contract explicit without + // replacing an actual non-standard blend state. + if (renderState.blend.enabled || + !isMaterialTransparent(material)) { + return; + } + + renderState.blend.enabled = TRUE; + renderState.blend.srcRGBFactor = SO_BLEND_FACTOR_SRC_ALPHA; + renderState.blend.dstRGBFactor = SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + renderState.blend.srcAlphaFactor = SO_BLEND_FACTOR_SRC_ALPHA; + renderState.blend.dstAlphaFactor = SO_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + renderState.blend.rgbEquation = SO_BLEND_EQUATION_ADD; + renderState.blend.alphaEquation = SO_BLEND_EQUATION_ADD; +} + +} // namespace SoRenderIR diff --git a/src/rendering/SoRenderIRP.h b/src/rendering/SoRenderIRP.h new file mode 100644 index 00000000000..774472c6920 --- /dev/null +++ b/src/rendering/SoRenderIRP.h @@ -0,0 +1,76 @@ +// src/rendering/SoRenderIRP.h + +#ifndef COIN_SORENDERIRP_H +#define COIN_SORENDERIRP_H + +#include +#include + +#include +#include + +class SoState; + +/*! + \class SoIRBuffer + \brief Chunk-based CPU scratch allocator for per-frame geometry data. + + Allocations are stable: pointers remain valid until clear() is called. + Growth allocates new chunks without moving old data. +*/ +class SoIRBuffer { +public: + SoIRBuffer(); + ~SoIRBuffer() = default; + + void clear(); + void reserve(size_t bytes); + void * allocate(size_t bytes, size_t alignment = alignof(float)); + + template + T * allocateArray(size_t count, size_t alignment = alignof(T)) { + return static_cast(this->allocate(count * sizeof(T), alignment)); + } + + size_t size() const { return this->totalAllocated; } + +private: + static constexpr size_t MIN_CHUNK_SIZE = 1024 * 1024; // 1 MB + struct Chunk { + std::vector data; + size_t cursor = 0; + }; + std::vector> chunks; + size_t totalAllocated = 0; + size_t highWaterMark = 0; // largest total allocation seen across frames +}; + +//! Dump a compact summary of the draw list to Coin's debug output. +void SoIRDumpSummary(const SoDrawList & drawlist); +//! Dump the first \a count render commands to Coin's debug output. +void SoIRDumpFirstN(const SoDrawList & drawlist, int count); +//! Return whether render-backend trace logging is enabled. +SbBool coin_render_ir_trace_enabled(); + +/*! + \namespace SoRenderIR + \brief Helper functions for converting Coin state and caches into render IR. +*/ +namespace SoRenderIR { +//! Capture the ordinary traversal state shared by retained shape producers. +void fillCommandStateFromState(SoState * state, SoDrawList & drawlist, + SoRenderCommand & command); +//! Fill a material snapshot from the current Inventor traversal state. +void fillMaterialFromState(SoState * state, SoMaterialData & material); +//! Fill render-state fields from the current Inventor traversal state. +void fillRenderStateFromState(SoState * state, SoRenderState & renderState); +//! Complete blend state after material opacity has been captured. +void ensureMaterialBlendState(SoRenderState & renderState, + const SoMaterialData & material); +//! Extract the current lighting setup, append/deduplicate it, and return its handle. +SoLightingHandle fillLightingFromState(SoState * state, SoDrawList & drawlist); +//! Return whether the material should be treated as translucent. +bool isMaterialTransparent(const SoMaterialData & material); +} + +#endif // COIN_SORENDERIRP_H diff --git a/src/rendering/all-rendering-cpp.cpp b/src/rendering/all-rendering-cpp.cpp index 7bd4c2cc1cb..04551fbafdb 100644 --- a/src/rendering/all-rendering-cpp.cpp +++ b/src/rendering/all-rendering-cpp.cpp @@ -48,3 +48,4 @@ #include "SoRenderManager.cpp" #include "SoRenderManagerP.cpp" #include "SoGLDriverDatabase.cpp" +#include "SoRenderIR.cpp" diff --git a/src/shaders/SoShaderProgram.cpp b/src/shaders/SoShaderProgram.cpp index fbf565fb030..128ece4a9ed 100644 --- a/src/shaders/SoShaderProgram.cpp +++ b/src/shaders/SoShaderProgram.cpp @@ -202,6 +202,7 @@ */ #include +#include #include "coindefs.h" #include @@ -286,6 +287,15 @@ SoShaderProgram::~SoShaderProgram() delete PRIVATE(this); } +void +SoShaderProgram::IRRender(SoIRRenderAction * action) +{ + if (action) { + action->markUnsupported(this, + "SoShaderProgram is only supported by the LegacyGL renderer"); + } +} + // doc from parent #if COIN_BUILD_LEGACY_GL_RENDERER void diff --git a/src/shapenodes/SoShape.cpp b/src/shapenodes/SoShape.cpp index 5014d03a1c5..a425e443566 100644 --- a/src/shapenodes/SoShape.cpp +++ b/src/shapenodes/SoShape.cpp @@ -52,6 +52,7 @@ class SoVBO; #include #include +#include #ifdef HAVE_CONFIG_H #include @@ -69,6 +70,7 @@ class SoVBO; #if COIN_BUILD_LEGACY_GL_RENDERER #include #endif +#include #include #include #include @@ -141,6 +143,7 @@ class SoVBO; #include "nodes/SoSubNodeP.h" #include "rendering/SoGL.h" +#include "rendering/SoRenderIRP.h" #include "glue/glp.h" #include "threads/threadsutilp.h" #include "tidbitsp.h" @@ -149,6 +152,123 @@ class SoVBO; #endif #include "coindefs.h" // COIN_OBSOLETED() +namespace { + +struct SoIRVertex { + SbVec3f position; + SbVec3f normal; + SbVec4f texcoord; +}; + +class SoIRPrimitiveAssembler : public SoIRRenderAction::PrimitiveCollector { +public: + SoIRPrimitiveAssembler(SoIRRenderAction * action, SoShape * shape) + : action(action), shape(shape), topology(SO_TOPOLOGY_COUNT) {} + + void onTriangle(const SoPrimitiveVertex * v1, + const SoPrimitiveVertex * v2, + const SoPrimitiveVertex * v3) override + { + this->setTopology(SO_TOPOLOGY_TRIANGLES); + this->append(v1); + this->append(v2); + this->append(v3); + } + + void onLine(const SoPrimitiveVertex * v1, + const SoPrimitiveVertex * v2) override + { + this->setTopology(SO_TOPOLOGY_LINES); + this->append(v1); + this->append(v2); + } + + void onPoint(const SoPrimitiveVertex * v) override + { + this->setTopology(SO_TOPOLOGY_POINTS); + this->append(v); + } + + void finalize() + { + this->flushRun(); + } + +private: + void flushRun() + { + if (this->vertices.empty()) return; + + SoRenderCommand command = {}; + this->fillGeometry(command.geometry); + SoRenderIR::fillCommandStateFromState( + this->action->getState(), this->action->getMutableDrawList(), command); + command.userData = this->shape; + this->action->addCommand(command); + + this->vertices.clear(); + } + + void fillGeometry(SoGeometryDesc & geometry) + { + const size_t count = this->vertices.size(); + geometry.topology = this->topology; + geometry.vertexCount = static_cast(count); + geometry.normalCount = geometry.vertexCount; + geometry.vertexStride = sizeof(float) * 3; + geometry.texcoordStride = sizeof(float) * 4; + + float * positions = static_cast( + this->action->allocateGeometryStorage(sizeof(float) * 3 * count)); + float * normals = static_cast( + this->action->allocateGeometryStorage(sizeof(float) * 3 * count)); + float * texcoords = static_cast( + this->action->allocateGeometryStorage(sizeof(float) * 4 * count)); + + for (size_t i = 0; i < count; ++i) { + const SoIRVertex & vertex = this->vertices[i]; + positions[i * 3 + 0] = vertex.position[0]; + positions[i * 3 + 1] = vertex.position[1]; + positions[i * 3 + 2] = vertex.position[2]; + normals[i * 3 + 0] = vertex.normal[0]; + normals[i * 3 + 1] = vertex.normal[1]; + normals[i * 3 + 2] = vertex.normal[2]; + texcoords[i * 4 + 0] = vertex.texcoord[0]; + texcoords[i * 4 + 1] = vertex.texcoord[1]; + texcoords[i * 4 + 2] = vertex.texcoord[2]; + texcoords[i * 4 + 3] = vertex.texcoord[3]; + } + + geometry.positions = positions; + geometry.normals = normals; + geometry.texcoords = texcoords; + } + + void setTopology(SoPrimitiveTopology candidate) + { + if (this->topology == candidate) return; + + if (this->topology != SO_TOPOLOGY_COUNT) this->flushRun(); + this->topology = candidate; + } + + void append(const SoPrimitiveVertex * vertex) + { + SoIRVertex copy; + copy.position = vertex->getPoint(); + copy.normal = vertex->getNormal(); + copy.texcoord = vertex->getTextureCoords(); + this->vertices.push_back(copy); + } + + SoIRRenderAction * action; + SoShape * shape; + SoPrimitiveTopology topology; + std::vector vertices; +}; + +} + // SoShape.cpp grew too big, so I had to move some code into new // files. pederb, 2001-07-18 #include "soshape_primdata.h" @@ -485,6 +605,30 @@ SoShape::GLRender(SoGLRenderAction * action) } #endif +void +SoShape::IRRender(SoIRRenderAction * action) +{ + if (!action) return; + + SoState * state = action->getState(); + SoVertexProperty * vertexProperty = + this->isOfType(SoVertexShape::getClassTypeId()) + ? (SoVertexProperty *) static_cast(this)->vertexProperty.getValue() + : NULL; + if (vertexProperty) { + state->push(); + vertexProperty->doAction(action); + } + + SoIRPrimitiveAssembler assembler(action, this); + action->pushPrimitiveCollector(&assembler); + this->generatePrimitives(action); + action->popPrimitiveCollector(&assembler); + assembler.finalize(); + + if (vertexProperty) state->pop(); +} + // Doc in parent. void SoShape::callback(SoCallbackAction * action) @@ -1116,6 +1260,12 @@ SoShape::invokeTriangleCallbacks(SoAction * const action, SoGetPrimitiveCountAction * ga = (SoGetPrimitiveCountAction *) action; ga->incNumTriangles(); } + else if (action->getTypeId().isDerivedFrom(SoIRRenderAction::getClassTypeId())) { + SoIRRenderAction * ir = static_cast(action); + SoIRRenderAction::PrimitiveCollector * collector = + ir->getActivePrimitiveCollector(); + if (collector) collector->onTriangle(v1, v2, v3); + } #if COIN_BUILD_LEGACY_GL_RENDERER else if (action->getTypeId().isDerivedFrom(SoGLRenderAction::getClassTypeId())) { soshape_staticdata * shapedata = soshape_get_staticdata(); @@ -1211,6 +1361,12 @@ SoShape::invokeLineSegmentCallbacks(SoAction * const action, SoGetPrimitiveCountAction * ga = (SoGetPrimitiveCountAction *) action; ga->incNumLines(); } + else if (action->getTypeId().isDerivedFrom(SoIRRenderAction::getClassTypeId())) { + SoIRRenderAction * ir = static_cast(action); + SoIRRenderAction::PrimitiveCollector * collector = + ir->getActivePrimitiveCollector(); + if (collector) collector->onLine(v1, v2); + } #if COIN_BUILD_LEGACY_GL_RENDERER else if (action->getTypeId().isDerivedFrom(SoGLRenderAction::getClassTypeId())) { soshape_staticdata * shapedata = soshape_get_staticdata(); @@ -1267,6 +1423,12 @@ SoShape::invokePointCallbacks(SoAction * const action, SoGetPrimitiveCountAction * ga = (SoGetPrimitiveCountAction *) action; ga->incNumPoints(); } + else if (action->getTypeId().isDerivedFrom(SoIRRenderAction::getClassTypeId())) { + SoIRRenderAction * ir = static_cast(action); + SoIRRenderAction::PrimitiveCollector * collector = + ir->getActivePrimitiveCollector(); + if (collector) collector->onPoint(v); + } #if COIN_BUILD_LEGACY_GL_RENDERER else if (action->getTypeId().isDerivedFrom(SoGLRenderAction::getClassTypeId())) { soshape_staticdata * shapedata = soshape_get_staticdata(); @@ -1779,8 +1941,8 @@ void SoShape::finishVertexArray(SoGLRenderAction * action, const SbBool vbo, const SbBool normpervertex, - const SbBool texpervertex, - const SbBool colorpervertex) + const SbBool texpervertex, + const SbBool colorpervertex) { SoState * state = action->getState(); const cc_glglue * glue = sogl_glue_instance(state); diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index 290abf51fc0..2eef1c29309 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -176,6 +176,33 @@ target_include_directories(LegacyBlendingTest PRIVATE ) add_test(NAME LegacyBlendingTest COMMAND LegacyBlendingTest) +add_executable(RetainedIRTest RetainedIRTest.cpp) +target_link_libraries(RetainedIRTest Coin ${COIN_TARGET_LINK_LIBRARIES}) +target_include_directories(RetainedIRTest PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${COIN_TARGET_INCLUDE_DIRECTORIES} +) +add_test(NAME RetainedIRTest COMMAND RetainedIRTest) + +add_executable(RetainedNodeTest RetainedNodeTest.cpp) +target_link_libraries(RetainedNodeTest Coin ${COIN_TARGET_LINK_LIBRARIES}) +target_include_directories(RetainedNodeTest PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${COIN_TARGET_INCLUDE_DIRECTORIES} +) +add_test(NAME RetainedNodeTest COMMAND RetainedNodeTest) + +add_executable(RetainedMixedTopologyTest RetainedMixedTopologyTest.cpp) +target_link_libraries(RetainedMixedTopologyTest Coin ${COIN_TARGET_LINK_LIBRARIES}) +target_include_directories(RetainedMixedTopologyTest PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${COIN_TARGET_INCLUDE_DIRECTORIES} +) +add_test(NAME RetainedMixedTopologyTest COMMAND RetainedMixedTopologyTest) + if(HAVE_EGL) add_executable(EGLBindingTest EGLBindingTest.cpp) target_link_libraries(EGLBindingTest Coin ${COIN_TARGET_LINK_LIBRARIES}) diff --git a/testsuite/RetainedIRTest.cpp b/testsuite/RetainedIRTest.cpp new file mode 100644 index 00000000000..ed1c3766bea --- /dev/null +++ b/testsuite/RetainedIRTest.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +#include + +static int +runTest() +{ + SoIRRenderAction action(SbViewportRegion(64, 64)); + SoSeparator * root = new SoSeparator; + root->ref(); + + int result = 0; + action.apply(root); + if (action.getDrawList().getNumCommands() != 0) { + std::cerr << "FAIL: empty scene emitted retained commands" << std::endl; + result = 1; + } + + SoRenderCommand command; + command.geometry.topology = SO_TOPOLOGY_TRIANGLES; + command.geometry.vertexCount = 3; + command.geometry.vertexStride = sizeof(float) * 3; + command.material.diffuse.setValue(1.0f, 0.0f, 0.0f, 1.0f); + action.getMutableDrawList().addCommand(command); + if (action.getDrawList().getNumCommands() != 1 || + action.getDrawList().getCommand(0).geometry.vertexCount != 3) { + std::cerr << "FAIL: retained command was not stored" << std::endl; + result = 1; + } + + SoShaderProgram * shader = new SoShaderProgram; + shader->ref(); + action.apply(shader); + if (!action.hasUnsupportedRendering() || + action.getUnsupportedNode() != shader || + action.getUnsupportedReason() == NULL) { + std::cerr << "FAIL: unsupported retained shader semantics were silently ignored" + << std::endl; + result = 1; + } + shader->unref(); + + root->unref(); + return result; +} + +int +main() +{ + SoDB::init(); + const int result = runTest(); + SoDB::finish(); + return result; +} diff --git a/testsuite/RetainedMixedTopologyTest.cpp b/testsuite/RetainedMixedTopologyTest.cpp new file mode 100644 index 00000000000..b3e39637f3e --- /dev/null +++ b/testsuite/RetainedMixedTopologyTest.cpp @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class MixedPrimitiveShape : public SoShape { + SO_NODE_HEADER(MixedPrimitiveShape); + +public: + static void initClass(void); + MixedPrimitiveShape(void); + +protected: + ~MixedPrimitiveShape() override {} + void generatePrimitives(SoAction * action) override; + void computeBBox(SoAction * action, SbBox3f & box, + SbVec3f & center) override; +}; + +SO_NODE_SOURCE(MixedPrimitiveShape); + +void +MixedPrimitiveShape::initClass(void) +{ + SO_NODE_INIT_CLASS(MixedPrimitiveShape, SoShape, "SoShape"); +} + +MixedPrimitiveShape::MixedPrimitiveShape(void) +{ + SO_NODE_CONSTRUCTOR(MixedPrimitiveShape); +} + +void +MixedPrimitiveShape::generatePrimitives(SoAction * action) +{ + SoPrimitiveVertex a; + SoPrimitiveVertex b; + SoPrimitiveVertex c; + a.setPoint(-1.0f, -1.0f, 0.0f); + b.setPoint(1.0f, -1.0f, 0.0f); + c.setPoint(0.0f, 1.0f, 0.0f); + a.setNormal(0.0f, 0.0f, 1.0f); + b.setNormal(0.0f, 0.0f, 1.0f); + c.setNormal(0.0f, 0.0f, 1.0f); + + this->beginShape(action, TRIANGLES); + this->shapeVertex(&a); + this->shapeVertex(&b); + this->shapeVertex(&c); + this->endShape(); + + a.setPoint(-1.0f, 0.0f, 0.0f); + b.setPoint(1.0f, 0.0f, 0.0f); + this->beginShape(action, LINES); + this->shapeVertex(&a); + this->shapeVertex(&b); + this->endShape(); + + c.setPoint(0.0f, 0.0f, 0.0f); + this->beginShape(action, POINTS); + this->shapeVertex(&c); + this->endShape(); +} + +void +MixedPrimitiveShape::computeBBox(SoAction *, SbBox3f & box, SbVec3f & center) +{ + box.setBounds(-1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f); + center.setValue(0.0f, 0.0f, 0.0f); +} + +static int +runTest() +{ + MixedPrimitiveShape::initClass(); + + SoSeparator * root = new SoSeparator; + root->ref(); + MixedPrimitiveShape * shape = new MixedPrimitiveShape; + root->addChild(shape); + + SoIRRenderAction action(SbViewportRegion(64, 64)); + action.apply(root); + + int result = 0; + const SoDrawList & drawlist = action.getDrawList(); + if (drawlist.getNumCommands() != 3) { + std::cerr << "FAIL: mixed-topology shape did not emit three commands" + << std::endl; + result = 1; + } + else { + const SoPrimitiveTopology expected[] = { + SO_TOPOLOGY_TRIANGLES, SO_TOPOLOGY_LINES, SO_TOPOLOGY_POINTS + }; + const uint32_t counts[] = { 3, 2, 1 }; + for (int i = 0; i < 3; ++i) { + const SoRenderCommand & command = drawlist.getCommand(i); + if (command.geometry.topology != expected[i] || + command.geometry.vertexCount != counts[i] || + command.userData != shape) { + std::cerr << "FAIL: mixed-topology command " << i + << " is incomplete" << std::endl; + result = 1; + } + } + } + + root->unref(); + return result; +} + +int +main() +{ + SoDB::init(); + const int result = runTest(); + SoDB::finish(); + return result; +} diff --git a/testsuite/RetainedNodeTest.cpp b/testsuite/RetainedNodeTest.cpp new file mode 100644 index 00000000000..e4c31602949 --- /dev/null +++ b/testsuite/RetainedNodeTest.cpp @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class PathProbe : public SoNode { + SO_NODE_HEADER(PathProbe); + +public: + static void initClass(void); + PathProbe(void); + + bool sawBelowPath = false; + + void IRRender(SoIRRenderAction * action) override + { + this->sawBelowPath = action->getCurPathCode() == SoAction::BELOW_PATH; + } + +protected: + ~PathProbe() override {} +}; + +SO_NODE_SOURCE(PathProbe); + +void +PathProbe::initClass(void) +{ + SO_NODE_INIT_CLASS(PathProbe, SoNode, "SoNode"); +} + +PathProbe::PathProbe(void) +{ + SO_NODE_CONSTRUCTOR(PathProbe); +} + +static void +check(bool condition, const char * message, int & result) +{ + if (!condition) { + std::cerr << "FAIL: " << message << std::endl; + result = 1; + } +} + +static int +runTest() +{ + PathProbe::initClass(); + + SoSeparator * root = new SoSeparator; + root->ref(); + SoCube * firstCube = new SoCube; + SoCube * secondCube = new SoCube; + root->addChild(firstCube); + root->addChild(secondCube); + + PathProbe * probe = new PathProbe; + probe->ref(); + + SoIRRenderAction action(SbViewportRegion(64, 64)); + int result = 0; + + action.apply(static_cast(root)); + check(action.getDrawList().getNumCommands() == 2, + "apply(SoNode*) did not traverse both cubes", result); + + SoPath * firstPath = new SoPath(root); + firstPath->append(firstCube); + firstPath->ref(); + action.apply(firstPath); + check(action.getDrawList().getNumCommands() == 1 && + action.getDrawList().getCommand(0).userData == firstCube, + "apply(SoPath*) did not traverse the selected cube", result); + firstPath->unref(); + + SoPath * oneNodePath = new SoPath(probe); + oneNodePath->ref(); + action.apply(oneNodePath); + check(probe->sawBelowPath, + "one-node SoPath did not use BELOW_PATH traversal", result); + check(action.getDrawList().getNumCommands() == 0, + "one-node probe left commands in the retained frame", result); + oneNodePath->unref(); + + SoPath * firstListPath = new SoPath(root); + firstListPath->append(firstCube); + firstListPath->ref(); + SoPath * secondListPath = new SoPath(root); + secondListPath->append(secondCube); + secondListPath->ref(); + { + SoPathList pathList; + pathList.append(firstListPath); + pathList.append(secondListPath); + action.apply(pathList); + check(action.getDrawList().getNumCommands() == 2, + "apply(SoPathList) did not traverse both paths", result); + } + firstListPath->unref(); + secondListPath->unref(); + + action.getMutableDrawList().addCommand(SoRenderCommand()); + action.apply(static_cast(probe)); + check(action.getDrawList().getNumCommands() == 0, + "repeated apply() did not clear the previous frame", result); + + probe->unref(); + root->unref(); + return result; +} + +int +main() +{ + SoDB::init(); + const int result = runTest(); + SoDB::finish(); + return result; +}