Skip to content

Commit

Permalink
DYN-3896 Add support for geometry instancing in render pipeline (#11914)
Browse files Browse the repository at this point in the history
* Add instancing support to DynamoMesh sharder

* Add instancing interfaces to GraphicsDataInterfaces for instance support

* Add IRenderInstances implementation to HelixRenderPackage

* Add support to HelixWatch3DViewModel to add instance data when pressent

* Handle adding instance data when object is IInstanceableItem

* Update interface to add tessellation params

* add line instancing to render package

* Support rendering of line instances

* add todo for labels

* Add bool to control instance availablity

* Add parameter to support labeling within instance set

* add simple renderpackage tests
add viewmodel test that renders instances
add test classes
add test file

* add instancing image comparison test 1

* add image comparison test for instance class with no data

* review comments to HelixWatch3dViewModel and GraphicsDataInterface files

* builds and moved to value tuple

* it builds

* fix some comments
try making interfaces internal

* initialize internal collection properties
small fixes
internals visible for test
update tests

* new test for transformable and instancing

* control instancing with feature flag

* update

* test mode should enable instancing flag

* update shaders

* fix test, add 1 more

* handle show edges
add test that will fail until libG changes are merged

* TODO

Co-authored-by: Craig Long <[email protected]>
Co-authored-by: michael kirschner <[email protected]>
Co-authored-by: mjk.kirschner <[email protected]>
  • Loading branch information
4 people authored May 18, 2022
1 parent cb428b7 commit 581b2ef
Show file tree
Hide file tree
Showing 28 changed files with 4,399 additions and 128 deletions.
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;
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.";
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);
}

if(MeshVertexRangesAssociatedWithInstancing.ContainsKey(id))
{
throw new Exception("The reference to the mesh range for this ID already exists.");
}

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.");
}

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);
}

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

0 comments on commit 581b2ef

Please sign in to comment.