HoloLens 2 + DrawMeshInstancedIndirect + Single Pass Instanced Rendering

Hello,
I have project where I need to render a large collection of objects on the hololens 2. For this purpose I experimented with Graphics.DrawMeshInstanced and Graphics.DrawMeshInstancedIndirect. Due to performance I chose the latter.

My project is configured with the “old” XR-Settings (Settings → Player → XR-Settings, Windows Mixed Reality).
When setting the stereo rendering mode to “Single Pass”, everything looks fine, but when I switch to “Single Pass Instanced” the data rendered via DrawMeshInstancedIndirect does not align. Otherwise everything else looks correct. It only affects the meshes rendered by DrawMeshInstancedIndirect.

I assume it has something to do with an incorrect setup for the right and left eye in my shader, but I have no clue what I need to do to fix this issue. I am using Unity 2019.4.8 + URP + MRTK Toolkit.

Custom shade code (partially):

[...]
struct appdata
{
   float4 vertex : POSITION;
   float4 normal : NORMAL;

   UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct v2f
{
   float4 vertex : SV_POSITION;
   fixed4 color : COLOR0;

   UNITY_VERTEX_OUTPUT_STEREO
};

struct MeshProperties
{
   float4x4 mat; 
   float4 color;
};

StructuredBuffer<MeshProperties> _Properties;

v2f vert(appdata v, uint instanceID: SV_InstanceID)
{
   v2f o;

   UNITY_SETUP_INSTANCE_ID(v);
   UNITY_INITIALIZE_OUTPUT(v2f, o);
   UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

   // position
   float4 position =  mul(_Properties[instanceID].mat, v.vertex);
   o.vertex  = mul(UNITY_MATRIX_VP, position); 
// I used UnityObjectToClipPos(position); before, but that gave the same result

   // color
[...] // code for the color, basicly I adjust the color from the properties array to add a simple lighting effect

   return o;
}

fixed4 frag(v2f i) : SV_Target
{
   return i.color;
}

The MeshProperties Struct correspondeds to the one I use in C# Code and contains the TRS Data of the mesh.

internal struct MeshProperties
        {
            public Matrix4x4 trsData;
            public Vector4 color;

            public static int Size()
            {
                return
                    sizeof(float) * 4 * 4 + // matrix;
                    sizeof(float) * 4;      // color;
            }
        }

https://docs.unity3d.com/Manual/SinglePassStereoRenderingHoloLens.html states that “… you will have to manually double the instance count contained in your compute buffers.” if using Graphics.DrawProceduralIndirect().
I do not really know how this function is used or what it does, but maybe it also applies to DrawMeshInstancedIndirect, as this approach uses compute buffers, too?

Hey, I ran into this same issue, and that blurb from the documentation is sparse and confused me too. What I found out is that they are expecting interleaved values, so I had to double the size of my computebuffer and then double the entries one by one so they are in memory like:
start with 5 instances like so
1,2,3,4,5
change to:
1,1, 2,2,3,3,4,4,5,5

EDIT: In fact when I ran into this problem this post was the first thing I came across in my searches, so wanted to share the answer.

4 Likes

Thank you very much for your help!

@ferrous @lady_magnetic any chance either of you have code samples you could share?

I just came across this problem today and would like to provide a code example for future reference:

as mentioned in the docs and explained by @ferrous , an easy solution is to duplicate your data in the buffer. In my code I introduced a boolean to declare if the data should be duplicated to support stereo rendering:

// Here, you may want to check if stereo rendering is enabled
protected bool UseStereoBuffer => true;

// Use x2 factor for stereo rendering
protected int StereoBufferSizeFactor => (UseStereoBuffer ? 2 : 1);

When it comes to building the buffer I duplicate the data accordingly:

// Double your buffer size if stereo rendering is enabled
var size = bufferSize * StereoBufferSizeFactor;
meshPropertiesBuffer = new ComputeBuffer(size, MeshProperties.Size());
MeshProperties[] meshProperties = new MeshProperties[size];

// Set up the args buffer
uint[] args = new uint[5] {0, 0, 0, 0, 0};
args[0] = (uint) mesh.GetIndexCount(0);
args[1] = (uint) size;
args[2] = (uint) mesh.GetIndexStart(0);
args[3] = (uint) mesh.GetBaseVertex(0);
argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
argsBuffer.SetData(args);

// Set up the mesh data buffer
for(int i = 0; i < size; i += StereoBufferSizeFactor)
{
    meshProperties[i] = GetMyBufferData(i);
    // Duplicate data for stereo rendering
    if (UseStereoBuffer) meshProperties[i + 1] = meshProperties[i];
}

There may be better ways to solve this that do not rely on actually duplicating the data in memory but it works and is relatively easy to implement.

3 Likes