Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removed old viewport draw in favour or VP2 GeometryOverride with Parallel Eval and Viewport Caching compatibility #26

Merged
merged 4 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ find_package(Maya REQUIRED)
find_package(OpenGL REQUIRED)

set(SOURCE_FILES
"src/drawOverride.h"
"src/twistSpline.h"
"src/twistSpline_maya.h"
"src/twistSplineData.h"
Expand All @@ -26,14 +27,15 @@ set(SOURCE_FILES
"src/twistTangentNode.h"

"src/pluginMain.cpp"
"src/drawOverride.cpp"
"src/twistSplineData.cpp"
"src/twistSplineNode.cpp"
"src/riderConstraint.cpp"
"src/twistTangentNode.cpp"
)

add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})
target_include_directories(${PROJECT_NAME}
target_include_directories(${PROJECT_NAME}
PRIVATE Maya::Maya
PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}"
)
Expand Down
185 changes: 185 additions & 0 deletions src/drawOverride.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#include "drawOverride.h"

TwistSplineGeometryOverride::TwistSplineGeometryOverride(const MObject& obj)
: MPxGeometryOverride(obj), mTwistSplineNode(nullptr) {
MStatus status;
MFnDependencyNode dependNode(obj, &status);
if (status != MStatus::kSuccess) return;

MPxNode* twistSplineNode = dependNode.userNode(&status);
if (status != MStatus::kSuccess) twistSplineNode = nullptr;

mTwistSplineNode = dynamic_cast<TwistSplineNode*>(twistSplineNode);
}

TwistSplineGeometryOverride::~TwistSplineGeometryOverride() {}

void TwistSplineGeometryOverride::updateDG() {
/*
Pull (evaluate) all attributes in TwistSplineNodeNode node we will be
using. Here is the list of attributes we are pulling :
- geometryChanging: Needed by requiresGeometryUpdate() to check if we
needs to update the vertex and index buffer;
- debugDisplay: Needed by addUIDrawables() to draw debug mode;
- debugScale: Needed by addUIDrawables() to draw debug mode scale;
- outputSpline: Needed by addUIDrawables() to draw the main spline;

It is very important that all the attributes pulled from this method are
cached (with Technique 1) otherwise Evaluation Cache will not work. In
fact, this method is not needed by EM Evaluation modes.
*/
mTwistSplineNode->updateRenderAttributes();
}

bool TwistSplineGeometryOverride::requiresUpdateRenderItems(
const MDagPath& path) const {
/*
Override this function if you need a more complicated animated-material
behavior For example, you will need to change this to `return true` if
object's associated shader is changing at every frame. (This could be
very expensive).

Note 1: this method have to return 'false' in most case, otherwise
VP2 caching may not work.

Note 2: for rendering simple animated-material like animated-color or
animated-texture-uv check FootPrintGeometryOverrideAnimatedMaterial
example.
*/
return false;
}

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Geometry update and VP2 cache implementations
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

/*
Return true when the aOutputSpline changes, which requires us to re-generate
the geometry.

Note: this method must return the exact same value in a cache-restoration
frame as it was in the corresponding cache-store frame.
*/
bool TwistSplineGeometryOverride::requiresGeometryUpdate() const {
/*
Checking the "TwistSplineNodeNode::geometryChanging" attribute if any
attribute affecting the geometry is changing,
"TwistSplineNodeNode::geometryChanging" should be affected and dirtied.
Also check TwistSplineNodeNodometry(). Warning: this method may be
called outside of regular { update() : cleanUp() } scope. Thus, we must
invoke node-local evaluation to ensure the correctness.
*/
return mTwistSplineNode->isGeometryChanging();
}

// Generate the geometry(vertex / index) from TwistSplineNodeNode's parameter
// data
// [[ensure : requiresGeometryUpdate() == false]]
void TwistSplineGeometryOverride::populateGeometry(
const MGeometryRequirements& requirements,
const MRenderItemList& renderItems, MGeometry& data) {
// This call will ensure the post-condition that requiresGeometryUpdate() is
// false
mTwistSplineNode->updatingGeometry();
}

void TwistSplineGeometryOverride::addUIDrawables(
const MDagPath& path, MHWRender::MUIDrawManager& drawManager,
const MHWRender::MFrameContext& frameContext) {
// Retrieve the spline data to draw elements;
auto splineData = mTwistSplineNode->getSplineData();
auto splinePoints = splineData->getPoints();

bool debugDraw;
double debugScale;
mTwistSplineNode->getDebugDraw(debugDraw, debugScale);

MFnDependencyNode node(path.node());
bool draw2D = false; // ALWAYS false

drawManager.beginDrawable(MUIDrawManager::kSelectable);

// Get correct color based on the state of object, e.g. active or
// dormant.
MHWRender::DisplayStatus displayStatus =
MHWRender::MGeometryUtilities::displayStatus(path);
MColor color = MHWRender::MGeometryUtilities::wireframeColor(path);
drawManager.setColor(color);
drawManager.lineStrip(splinePoints, draw2D);

if (debugDraw) {
MPointArray tangents;
MVectorArray tans = splineData->getTangents();
tangents.setLength(tans.length() * 2);
for (size_t i = 0; i < tans.length(); ++i) {
MPoint& spi = splinePoints[i];
tangents[2 * i] = spi;
tangents[(2 * i) + 1] = debugScale * tans[i] + spi;
}
drawManager.setColor(MColor(.5, 0, 0));
drawManager.lineList(tangents, draw2D);

MPointArray normals;
MVectorArray norms = splineData->getNormals();
normals.setLength(norms.length() * 2);
for (size_t i = 0; i < norms.length(); ++i) {
MPoint& spi = splinePoints[i];
MVector& nn = norms[i];
normals[2 * i] = spi;
normals[(2 * i) + 1] = debugScale * nn + spi;
}
drawManager.setColor(MColor(0, .5, 0));
drawManager.lineList(normals, draw2D);

MPointArray binormals;
MVectorArray binorms = splineData->getBinormals();
binormals.setLength(binorms.length() * 2);
for (size_t i = 0; i < binorms.length(); ++i) {
MPoint& spi = splinePoints[i];
binormals[2 * i] = spi;
binormals[(2 * i) + 1] = debugScale * binorms[i] + spi;
}
drawManager.setColor(MColor(0, 0, .5));
drawManager.lineList(binormals, draw2D);
}
drawManager.endDrawable();
}

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Debug functions
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

// Return true if internal tracing is desired.
bool TwistSplineGeometryOverride::traceCallSequence() const {
/*
Tracing will look something like the following when in shaded mode (on
selection change):
- TwistSplineGeometryOverride: Geometry override DG update: twistSpline1
- TwistSplineGeometryOverride: Start geometry override render item
update: |transform1|twistSpline1
- TwistSplineGeometryOverride: - Call API to update render items
- TwistSplineGeometryOverride: End geometry override render item update:
|transform1|twistSpline1
- TwistSplineGeometryOverride: End geometry override clean up:
twistSpline1

This is based on the existing stream and indexing dirty flags being used
which attempts to minimize the amount of render item, vertex buffer and
indexing update.
*/
return false;
}

inline void TwistSplineGeometryOverride::handleTraceMessage(
const MString& message) const {
MGlobal::displayInfo(gPluginNodeMessagePrefix + message);

// Some simple custom message formatting.
fputs(gPluginNodeMessagePrefix, stderr);
fputs(message.asChar(), stderr);
fputs("\n", stderr);
}
142 changes: 142 additions & 0 deletions src/drawOverride.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#ifndef DRAW_OVERRIDE_H
#define DRAW_OVERRIDE_H

#include <maya/M3dView.h>
#include <maya/MDrawRegistry.h>
#include <maya/MHWGeometry.h>
#include <maya/MHWGeometryUtilities.h>
#include <maya/MPxGeometryOverride.h>
#include <maya/MShaderManager.h>
#include <maya/MSharedPtr.h>
#include <maya/MUserData.h>
#include <maya/MViewport2Renderer.h>

#include "twistSplineNode.h"

static const MString WireframeItemName = "twistSplineWires";

/*
Customized rendering logic of TwistSplineNodeNode

This class is responsible for generating the geometry (vertex buffer) from
the procedure geometry. Beside rendering, the functions for generating
geometry is also a crucial part in Viewport Caching (in contrast with
Evaluation Caching).
*/
class TwistSplineGeometryOverride : public MPxGeometryOverride {
public:
static MPxGeometryOverride* Creator(const MObject& obj) {
return new TwistSplineGeometryOverride(obj);
}
~TwistSplineGeometryOverride() override;

// Support configuration functions :
MHWRender::DrawAPI supportedDrawAPIs() const override {
return (MHWRender::DrawAPI::kOpenGL | MHWRender::DrawAPI::kDirectX11 |
MHWRender::DrawAPI::kOpenGLCoreProfile);
}
bool supportsEvaluationManagerParallelUpdate() const override {
return true;
}
/*
Supporting Viewport Caching (VP2 Custom Caching)

Viewport Cache for customize rendering nodes is caching the geometry
data generated by MPxGeometryOverride::populateGeometry(). Like the
graphics API, the geometry data is usually expressed in three different
objects:
- Vertex Buffer : Viewport Cache
- Index Buffer : No cache, must be time-independent
- Uniform Buffer : Evaluation Cache
Currently, *only* Vertex Buffer can be cached in Viewport Cache and all
the data in Uniform Buffer (shader parameters such as Color), must be
stored in Evaluation Cache.

Viewport Caching is helpful if you are generating the geometry data in
CPU. For GPU generated geometry, the benefit is limited. To make the
Viewport caching works properly the following constraints must be meet:
- requiresUpdateRenderItems(path) always return 'false' in
background-dg-context.
- requiresGeometryUpdate() always return 'true' when restored from
cache (if geometry is animated).
- populateGeometry() should be context-safe
*/
bool supportsVP2CustomCaching() const override { return true; }

/*
Interaction with TwistSplineNodeNode
- This is the only method where you can call MPlug::getValue() or
Mdatablock::inputValue()
- This method can be empty (in EM modes), if you have setup the node
correctly with Technique 1.
*/
void updateDG() override;
// For the best practice, this method should contain no status data
void cleanUp() override{};

// Render item functions, only involved in foreground rendering, should
// not affect VP2 Caching at all
bool requiresUpdateRenderItems(const MDagPath& path) const override;
void updateRenderItems(const MDagPath& path,
MRenderItemList& list) override{};
bool hasUIDrawables() const override { return true; };
void addUIDrawables(const MDagPath& path, MUIDrawManager& drawManager,
const MFrameContext& frameContext) override;

// Geometry update functions, major entry for support VP2 Custom Caching
bool requiresGeometryUpdate() const override;
bool isIndexingDirty(const MRenderItem& item) override {
return false;
} // Viewport Caching does not support index buffer change (animated
// topology)
void populateGeometry(const MGeometryRequirements& requirements,
const MRenderItemList& renderItems,
MGeometry& data) override;

// Debugging functions
bool traceCallSequence() const override;
void handleTraceMessage(const MString& message) const override;

private:
TwistSplineGeometryOverride(const MObject& obj);
/*
To ensure the plugin works well with Viewport and Evaluation Caching
we recommend to use a state-less MpxGeometryOverride:

- Store all time-dependent data on the attributes of corresponding Maya
node, instead of a data member in MpxGeometryOverride.
- updateDG() pulls (evaluates) all the time-dependent data on node by
"MDataBlock::inputValue()", but *not* store them in a data member of
MPxGeometryOverride
- Always access the time-dependent data by "MDataBlock::outputValue()"
in the renderer

Backgrounds of the recommendation:

In this plugin example, we have 3 different pipeline stages involved:
1. Node Evaluation
2. Geometry Update (MPxGeometryOverride invocation)
3. Rendering (Shader-pre-draw-callback invocation)

With (Evaluation or Viewport) Caching, some surprising mixture of
stages happen concurrently:

- Foreground thread :
MPxGeometryOverride -> Shader Callback -> (Rendering on frame 10)
- Background thread :
Node Evaluation -> MPxGeometryOverride -> (Viewport cache fill on frame 20)

Accessing the same data directly cross stage boundaries can cause
incorrect result or even crash Maya. Thus we must use different storage
for each data in each DG context (frame). And a depend node's attribute
are always isolated for different context.

Beside, within the MPxGeometryOverride invocation stage, it is not
always safe to access its data member : During Viewport Cache
Restoration, requiresGeometryUpdate() is the only method get called.
(No updateDG() call) Thus the data must be stored in the node.
*/
TwistSplineNode* mTwistSplineNode; // The node we are rendering
};

#endif
Loading