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

DYN-3896 Add support for geometry instancing in render pipeline #11914

Merged
merged 33 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
987381f
Add instancing support to DynamoMesh sharder
saintentropy Jul 19, 2021
0824814
Add instancing interfaces to GraphicsDataInterfaces for instance support
saintentropy Jul 19, 2021
37ceb00
Add IRenderInstances implementation to HelixRenderPackage
saintentropy Jul 19, 2021
3aecdc4
Add support to HelixWatch3DViewModel to add instance data when pressent
saintentropy Jul 19, 2021
3276e67
Handle adding instance data when object is IInstanceableItem
saintentropy Jul 19, 2021
ca899f7
Update interface to add tessellation params
saintentropy Aug 1, 2021
0de95b0
add line instancing to render package
saintentropy Aug 1, 2021
f901cb6
Support rendering of line instances
saintentropy Aug 1, 2021
774d66e
add todo for labels
saintentropy Aug 1, 2021
c69a6cc
Merge branch 'master' into Instancing
saintentropy Aug 2, 2021
c379ed4
Add bool to control instance availablity
saintentropy Aug 19, 2021
c2f34d2
Merge branch 'master' into Instancing
saintentropy Aug 19, 2021
6a08c8a
Add parameter to support labeling within instance set
saintentropy Aug 19, 2021
a95a881
Merge branch 'master' into Instancing
saintentropy Oct 6, 2021
628392b
Merge branch 'master' into Instancing
saintentropy Nov 4, 2021
0a5b8f3
add simple renderpackage tests
mjkkirschner Nov 11, 2021
a0302cd
add instancing image comparison test 1
mjkkirschner Nov 11, 2021
e7170fe
add image comparison test for instance class with no data
mjkkirschner Nov 11, 2021
f60c9b6
Merge branch 'master' of https://github.com/DynamoDS/Dynamo into inst…
mjkkirschner May 11, 2022
f407a71
review comments to HelixWatch3dViewModel and GraphicsDataInterface files
mjkkirschner May 11, 2022
60aabb8
builds and moved to value tuple
mjkkirschner May 11, 2022
a8caa7b
it builds
mjkkirschner May 12, 2022
c05c323
fix some comments
mjkkirschner May 12, 2022
0da5120
Merge branch 'instancetests' of https://github.com/mjkkirschner/Dynam…
mjkkirschner May 12, 2022
cfb521c
initialize internal collection properties
mjkkirschner May 13, 2022
fec13a9
new test for transformable and instancing
mjkkirschner May 13, 2022
e38a40b
control instancing with feature flag
mjkkirschner May 13, 2022
af38da1
update
mjkkirschner May 13, 2022
1850bc0
test mode should enable instancing flag
mjkkirschner May 13, 2022
df92121
update shaders
mjkkirschner May 13, 2022
2c9551f
fix test, add 1 more
mjkkirschner May 13, 2022
52331e1
handle show edges
mjkkirschner May 17, 2022
b4b2fe7
TODO
mjkkirschner May 18, 2022
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
37 changes: 37 additions & 0 deletions src/DynamoCore/Scheduler/UpdateRenderPackageAsyncTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Autodesk.DesignScript.Interfaces;
using Dynamo.Engine;
using Dynamo.Graph.Nodes;
using Dynamo.Models;
using Dynamo.Visualization;
using ProtoCore.Mirror;

Expand Down Expand Up @@ -210,6 +211,7 @@ private void GetRenderPackagesFromMirrorDataImp(
private void GetTessellationDataFromGraphicItem(Guid outputPortId, IGraphicItem graphicItem, string labelKey, ref IRenderPackage package)
{
var packageWithTransform = package as ITransformable;
mjkkirschner marked this conversation as resolved.
Show resolved Hide resolved
var packageWithInstances = package as IInstancingRenderPackage;

try
{
Expand All @@ -218,10 +220,45 @@ private void GetTessellationDataFromGraphicItem(Guid outputPortId, IGraphicItem
var previousMeshVertexCount = package.MeshVertexCount;

//Todo Plane tessellation needs to be handled here vs in LibG currently
bool instancingEnabled = DynamoModel.FeatureFlags.CheckFeatureFlag<bool>("graphics-primitive-instancing", false);
if (graphicItem is Plane plane)
{
CreatePlaneTessellation(package, plane);
}
else if (graphicItem is IInstanceableGraphicItem instanceableItem &&
instanceableItem.InstanceInfoAvailable
&& packageWithInstances != null
&& instancingEnabled)
{
//if we have not generated the base tessellation for this type yet, generate it
if (!packageWithInstances.ContainsTessellationId(instanceableItem.BaseTessellationGuid))

{
instanceableItem.AddBaseTessellation(packageWithInstances, factory.TessellationParameters);
var prevLineIndex = package.LineVertexCount;
//if edges is on, then also add edges to base tessellation.
if (factory.TessellationParameters.ShowEdges)
{
//TODO if we start to instance more types, expand this edge generation.
if (graphicItem is Topology topology)
{
var edges = topology.Edges;
foreach (var geom in edges.Select(edge => edge.CurveGeometry))
{
geom.Tessellate(package, factory.TessellationParameters);
geom.Dispose();
}

edges.ForEach(x => x.Dispose());
packageWithInstances.AddInstanceGuidForLineVertexRange(prevLineIndex, package.LineVertexCount - 1, instanceableItem.BaseTessellationGuid);
}
}
}

instanceableItem.AddInstance(packageWithInstances, factory.TessellationParameters, labelKey);

return;
}
else
{
try
Expand Down
173 changes: 155 additions & 18 deletions src/DynamoCoreWpf/Rendering/HelixRenderPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public IRenderPackage CreateRenderPackage()
/// A Helix-specifc IRenderPackage implementation.
/// </summary>
[Obsolete("Do not use! This will be moved to a new project in a future version of Dynamo.")]
public class HelixRenderPackage : IRenderPackage, ITransformable, IRenderLabels, IRenderPackageSupplement
public class HelixRenderPackage : IRenderPackage, ITransformable, IRenderLabels, IRenderPackageSupplement, IInstancingRenderPackage
{
#region private members

Expand All @@ -50,7 +50,7 @@ public class HelixRenderPackage : IRenderPackage, ITransformable, IRenderLabels,
private int colorStride;
private List< byte[]> colorsList = new List<byte[]>();
private List<int> colorStrideList = new List<int>();
private List<Tuple<int, int>> colorsMeshVerticesRange = new List<Tuple<int, int>>();
internal Dictionary<Guid, List<Matrix>> instanceTransforms = new Dictionary<Guid, List<Matrix>>();

#endregion

Expand Down Expand Up @@ -215,7 +215,7 @@ public void Clear()

colorsList.Clear();
colorStrideList.Clear();
colorsMeshVerticesRange.Clear();
MeshVerticesRangesAssociatedWithTextureMaps.Clear();
}

/// <summary>
Expand Down Expand Up @@ -379,12 +379,12 @@ public void ApplyMeshVertexColors(byte[] colors)
mesh.Colors = colors.ToColor4Collection();
}

private bool HasValidStartEnd(int startIndex, int endIndex, Geometry3D geom, out string message)
private bool ValidateRange<T>(int startIndex, int endIndex, ICollection<T> geomDataCollection, out string message)
{
message = string.Empty;

if (startIndex > geom.Colors.Count ||
endIndex > geom.Colors.Count - 1)
if (startIndex > geomDataCollection.Count ||
endIndex > geomDataCollection.Count - 1)
{
message = "The start and end indices must be within the bounds of the array.";
mjkkirschner marked this conversation as resolved.
Show resolved Hide resolved
return false;
Expand Down Expand Up @@ -644,7 +644,7 @@ public void ClearLabels()
/// <param name="alpha">byte for the alpha RGB value</param>
public void UpdatePointVertexColorForRange(int startIndex, int endIndex, byte red, byte green, byte blue, byte alpha)
{
if (!HasValidStartEnd(startIndex, endIndex, points, out var message))
if (!ValidateRange(startIndex, endIndex, points.Colors, out var message))
{
throw new Exception(message);
}
Expand Down Expand Up @@ -680,7 +680,7 @@ public void AppendPointVertexColorRange(byte[] colors)
/// <param name="alpha">byte for the alpha RGB value</param>
public void UpdateLineVertexColorForRange(int startIndex, int endIndex, byte red, byte green, byte blue, byte alpha)
{
if (!HasValidStartEnd(startIndex, endIndex, lines, out var message))
if (!ValidateRange(startIndex, endIndex, lines.Colors, out var message))
{
throw new Exception(message);
}
Expand Down Expand Up @@ -716,7 +716,7 @@ public void AppendLineVertexColorRange(byte[] colors)
/// <param name="alpha">byte for the alpha RGB value</param>
public void UpdateMeshVertexColorForRange(int startIndex, int endIndex, byte red, byte green, byte blue, byte alpha)
{
if (!HasValidStartEnd(startIndex, endIndex, mesh, out var message))
if (!ValidateRange(startIndex, endIndex, mesh.Colors, out var message))
{
throw new Exception(message);
}
Expand Down Expand Up @@ -761,10 +761,7 @@ public List<int> TextureMapsStrideList
/// <summary>
/// A list of mesh vertices ranges that have associated texture maps
/// </summary>
public List<Tuple<int, int>> MeshVerticesRangesAssociatedWithTextureMaps
{
get { return colorsMeshVerticesRange; }
}
public List<Tuple<int, int>> MeshVerticesRangesAssociatedWithTextureMaps { get; } = new List<Tuple<int, int>>();

/// <summary>
/// Set a color texture map for a specific range of mesh vertices
Expand All @@ -775,34 +772,174 @@ public List<Tuple<int, int>> MeshVerticesRangesAssociatedWithTextureMaps
/// <param name="stride">The size of one dimension of the colors array</param>
public void AddTextureMapForMeshVerticesRange(int startIndex, int endIndex, byte[] textureMap, int stride)
{
if (!HasValidStartEnd(startIndex, endIndex, mesh, out var message))
if (!ValidateRange(startIndex, endIndex, mesh.Colors, out var message))
{
throw new Exception(message);
}

foreach (var r in colorsMeshVerticesRange)
foreach (var r in MeshVerticesRangesAssociatedWithTextureMaps)
{
if (startIndex >= r.Item1 && startIndex <= r.Item2 || endIndex >= r.Item1 && endIndex <= r.Item2)
{
throw new Exception("The start and end indices must not overlap previously defined ranges.");
}
}
colorsMeshVerticesRange.Add(new Tuple<int, int>(startIndex, endIndex));

MeshVerticesRangesAssociatedWithTextureMaps.Add(new Tuple<int, int>(startIndex, endIndex));
colorsList.Add(textureMap);
colorStrideList.Add(stride);
}

/// <summary>
/// Allow legacy usage of the color methods in IRenderPackage
/// This flag is used by the UpdateRenderPackageAsyncTask implementation to flag
/// any third party usage of deprecated color methods in IRenderPackage API
/// any third party usage of deprecated color methods in IRenderPackageSupplement.MeshVerticesRangesAssociatedWithTextureMaps
/// </summary>
[Obsolete("Do not use! This will be removed in Dynamo 3.0")]
public bool AllowLegacyColorOperations { get; set; } = true;

#endregion

/// <summary>
/// A list of mesh vertices ranges that have associated instance references
/// </summary>
internal Dictionary<Guid, (int start, int end)> MeshVertexRangesAssociatedWithInstancing { get; } = new Dictionary<Guid, (int start, int end)>();

/// <summary>
/// Set an instance reference for a specific range of mesh vertices
/// </summary>
/// <param name="startIndex">The index associated with the first vertex in MeshVertices we want to associate with the instance matrices
/// <param name="endIndex">The index associated with the last vertex in MeshVertices we want to associate with the instance matrices
/// <param name="id">A unique id associated with this tessellation geometry for instancing</param>
public void AddInstanceGuidForMeshVertexRange(int startIndex, int endIndex, Guid id)
{
if (!ValidateRange(startIndex, endIndex, mesh.Positions, out var message))
{
throw new Exception(message);
mjkkirschner marked this conversation as resolved.
Show resolved Hide resolved
}

if(MeshVertexRangesAssociatedWithInstancing.ContainsKey(id))
{
throw new Exception("The reference to the mesh range for this ID already exists.");
mjkkirschner marked this conversation as resolved.
Show resolved Hide resolved
}

MeshVertexRangesAssociatedWithInstancing.Add(id, (startIndex, endIndex));
}

/// <summary>
/// A list of line vertices ranges that have associated instance references
/// </summary>
internal Dictionary<Guid, (int start, int end)> LineVertexRangesAssociatedWithInstancing { get; } = new Dictionary<Guid, (int start, int end)>();

/// <summary>
/// Set an instance reference for a specific range of line vertices
/// </summary>
/// <param name="startIndex">The index associated with the first vertex in LineVertices we want to associate with the instance matrices
/// <param name="endIndex">The index associated with the last vertex in LineVertices we want to associate with the instance matrices
/// <param name="id">A unique id associated with this tessellation geometry for instancing</param>
public void AddInstanceGuidForLineVertexRange(int startIndex, int endIndex, Guid id)
{
if (!ValidateRange(startIndex, endIndex, lines.Positions, out var message))
{
throw new Exception(message);
}

if (LineVertexRangesAssociatedWithInstancing.ContainsKey(id))
{
throw new Exception("The reference to the line range for this ID already exists.");
mjkkirschner marked this conversation as resolved.
Show resolved Hide resolved
}

LineVertexRangesAssociatedWithInstancing.Add(id, (startIndex, endIndex));
}

/// <summary>
/// Set the transform using a series of doubles. The resulting transform is applied to all geometry in the renderPackage.
/// Following conventional matrix notation, m11 is the value of the first row and first column, and m12
/// is the value of the first row and second column.
/// NOTE: This method will set the matrix exactly as described by the caller.
/// </summary>
/// <param name="m11"></param>
/// <param name="m12"></param>
/// <param name="m13"></param>
/// <param name="m14"></param>
/// <param name="m21"></param>
/// <param name="m22"></param>
/// <param name="m23"></param>
/// <param name="m24"></param>
/// <param name="m31"></param>
/// <param name="m32"></param>
/// <param name="m33"></param>
/// <param name="m34"></param>
/// <param name="m41"></param>
/// <param name="m42"></param>
/// <param name="m43"></param>
/// <param name="m44"></param>
public void AddInstanceMatrix(float m11, float m12, float m13, float m14,
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44, Guid id)
{
if (!ContainsTessellationId(id))
{
throw new Exception("The reference to the graphics range(mesh or line) for this ID does not exists.");
}

var transform = new Matrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44);

List<Matrix> transforms;
if (instanceTransforms.TryGetValue(id, out transforms))
{
transforms.Add(transform);
}
else
{
instanceTransforms.Add(id, new List<Matrix> {transform});
}
}

/// <summary>
/// Set the transform as a double array, this transform is applied to all geometry in the renderPackage.
/// This matrix should be laid out as follows in row vector order:
/// [Xx,Xy,Xz, 0,
/// Yx, Yy, Yz, 0,
/// Zx, Zy, Zz, 0,
/// offsetX, offsetY, offsetZ, W]
/// NOTE: The caller of this method should transform the matrix from row vector order to whatever form is needed by the implementation.
/// When converting from ProtoGeometry CoordinateSystem form to input matrix, set the first row to the X axis of the CS,
/// the second row to the Y axis of the CS, the third row to the Z axis of the CS, and the last row to the CS origin, where W = 1.
/// </summary>
/// <param name="matrix"></param>
public void AddInstanceMatrix(float[] matrix, Guid id)
{
if (!ContainsTessellationId(id))
{
throw new Exception("The reference to the graphics range(mesh or line) for this ID does not exists.");
}

var transform = new Matrix(matrix);

List<Matrix> transforms;
if (instanceTransforms.TryGetValue(id, out transforms))
{
transforms.Add(transform);
}
else
{
instanceTransforms.Add(id, new List<Matrix> { transform });
}
}

/// <summary>
/// Checks if a base tessellation guid has already been registered with this <see cref="IInstancingRenderPackage"/>.
/// Both Line and Mesh ids are checked.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool ContainsTessellationId(Guid id)
{
return MeshVertexRangesAssociatedWithInstancing.ContainsKey(id) || LineVertexRangesAssociatedWithInstancing.ContainsKey(id);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mjkkirschner Hmmmmm.... Not sure but we may need to handle a transform that has both mesh and line type tessellation data. Maybe when edges are on. I can't remember how that case was supported.

Copy link
Contributor Author

@saintentropy saintentropy May 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a bit of a hack but the LIbG portion could actually handle this ^^^ I think. I always wondered why we didn't handle the ShowEdges in the base tessellation call. Maybe puts too much ownership on those implementing IGraphicItem. In this case we are still internal so it may work to let the instanceableItem handle the data. In the case of a cube, AddBaseTessellation(instancingPackage, factory.TessellationParameters) could inspect the TessellationParameters and add only a mesh or a mesh and line data if edges are needed. They would have to have separate GUIDs. The subsequent call to AddInstance(instancingPackage, factory.TessellationParameters, labelKey) could also add the correct instance references fo either mesh only or mesh and line data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may also be follow up task?

Copy link
Member

@mjkkirschner mjkkirschner May 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ I'll take a quick look but ,if nothing obvious and simple I think I'll have to file a followup as the allotted time for this task is running out.

I wonder if we can just push the line data to the baseTessellation from this method:

if (factory.TessellationParameters.ShowEdges)

or always generate the edge data in baseTessellation but only using it (extracting it from the render package) if show edges is on.

For the current state of the PR, I think we need to decide the following:

  1. When show_edges is on, we don't use instancing OR -
  2. When show_edges is on, we use instancing but don't draw edges... OR
  3. When show_edges is on, we use instancing for the meshes, but push a bunch of non instanced edge data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be also...
4. Edges added within instancing implementation.

}

public MeshGeometry3D Mesh
{
get { return mesh; }
Expand Down
Loading