Geometry Instancing is not working correctly

Started by
18 comments, last by MoruganKodi 10 years, 8 months ago

Hello Guys.

It really bugs me when my first post in any community I join has to be a request for help, so I have tried to hold off asking for assistance - but now this is holding up my development.

I have been having a headache over this for 2 weeks. There are plenty of examples and topics on the internet demonstrating this using primitives, and plenty that imply you want to use a content processor, but hardly any that ( none that I could find ) that shows how to implement this correctly when you want to copy vertices and indices from and existing model... and being fairly new to XNA ( specifically 3D programming ) , i have been losing sleep over only one and one particular issue: Geometry Instancing.

To begin - I started my implementation using the examples Here and Here, but with two notable differences:

- A: I did not need the "atlas texture" thing.
- B: I dont want to use generated geometry prrimitives, but instead copy the vertices from an existing model.

The MSDN Instanced Geometry Example doesnt help because the so called "instanced mesh" class mentioned in the article cannot be found.

In my particular project I need to check individual sub meshes ( + transforms ) to the camera's view frustum - so as a result - I want to maintain each individual mesh part in their own seperate vertexbuffers ( only examples I found that copy existing meshdata combines all meshparts into a single vertex buffer, making it impossible to manage individual mesh parts seperatly ).

In my project I have created the following classes:

  • OSModel
  • OSModelMesh
  • OSModelMeshPart

I want OSModelMeshPart to contain my VertexBuffer / InstanceBuffer, as this would be initialized from an existing ModelMeshPart, and this is how I have written it.

At runtime, my entire draw routine passes through (during step through debugging) without raising any exceptions - however nothing displays. One of those issues where theres is nothing telling me what isnt working. VertexBuffers appear full ( and all copied data matches lengths and types of the original sources ). Its driving me nuts.

So I tried the following:

  • Test: Forcing all instanced positions into the view frustum:
    Result: FPS drops as if all positions in my instance buffer are being drawn, but they seem completly invisible.
  • Test: Alternate positioning by sending only a matrix to use as position transform
    Result: Failed
  • Test: Tried using Generated Geometry ( manually typed in cube ).. as in the examples:
    Result: It worked - which is why I believe Im copying existing model data incorrectly...
  • Test: A none instanced version of my shader:
    Result: Passed - and renders correctly

In the attached images - all None-Instanced tests - all my positions are working - and my shaders are working, frustum checks are working, but I need this same result with Instancing, because I can maintain 20000+ ships in the game world, but I cannot put more than a few hundred of them inside my view frustum because I hit my batch limit ( too many draw calls ). I need to be able to put 2000 ships in my view frustum, in which some of these would be huge titans, I intend to LOD and Frustum Occlude each individual mesh part.


But when initializing these same models ( Skybox, Planets, and ships ) using the below instancing code, they do not render.



After banging my head against the wall a few times I figured the problem must be originating from the areas that differ from the examples:

  • Either I am copying Initializing my VertexBuffer, and IndexBuffer, for each OSModelMeshPart when copying from a ModelMeshPart incorrectly... or....
  • Maybe the extra elements ( currently only a vector 4, I removed others ) are not being passes to the shader correctly ( which I doubt since It appears the positions are working correctly ? )

My goal:

  • I want to maintain seperate draw lists for every mesh part in the end, therefore seperate instancebuffers for each meshpart - each meshpart is tested against the cameras view frustum on update - and the final instance buffer would contain only those visible in the view frustum.
  • DrawStates are controlled by OSModelMesh, and are ordered by OSModel (Opaque meshes are stored in a seperate lists from Alpha and additive meshes )
  • For it to actually work.

My entire instancing takes place in OSModelMeshPart - which I wanted to attach instead of making my post too long, but it appears .VB files are not permitted....

This is the curent state of my OSModelMeshPart object in my framework: RenderHelper contains StateManagement and Frustum Checks, etc...


Imports OS.Graphics.RenderHelper

Namespace Graphics

    Public Class OSModelMeshPart
        Inherits OS.iGameObject



        Friend m_VertexDeclaration As VertexDeclaration, _
 _
               m_GeometryBuffer As VertexBuffer,
               m_IndexBuffer As IndexBuffer,
 _
               m_InstanceBuffer As VertexBuffer

        Friend m_Bindings As VertexBufferBinding()


        Friend m_VertexStreamElements As VertexElement()

        Private m_Owner As OSModelMesh


        Friend m_Vertices As VertexPositionTexture()

        Friend m_VertexCount As Integer,
               m_VertexOffset As Integer,
               m_IndexCount As Integer,
               m_PrimitiveCount As Integer,
 _
               m_Last_InstanceCount As Integer = 0


        Private m_BoundingSphere As BoundingSphere,
                m_BoundingBox_FromSphere As BoundingBox,
                m_BoundingBox_FromVertices As BoundingBox

        Private m_Cache_LastPositions As ModelPositionInfo()

        ' instance list ( we TRIM/ ADD per cycle - do not CLEAR  - optimization )
        Private m_Instances As New List(Of InstanceInfo)

        Private m_ListIDCap As Integer = -1

        ' weather we have anything to draw:
        Private m_CanDraw As Boolean = False

        ' bounds for each instance per cycle:
        Friend m_Cache_Instances_BoundingSpheres As BoundingSphere() = {}
        Friend m_Cache_Instances_BoundingBoxes As BoundingBox() = {}




        ''' <summary>
        ''' Returns Bounding Spheres for this mesh part for all previous instances passed. 
        ''' Bounding spheres are returned for all instanced regardless of View Frustum visibility.
        ''' EG: Length of array will always metch the positioninfo list length.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        ReadOnly Property Get_Last_Instance_BoundingSpheres As BoundingSphere()
            Get
                Return m_Cache_Instances_BoundingSpheres
            End Get
        End Property

        ''' <summary>
        ''' Returns all instance boundingboxes for this meshpart from the last instance positions list passed .
        ''' Bounding spheres are returned for all instanced regardless of View Frustum visibility.
        ''' EG: Length of array will always metch the positioninfo list length.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        ReadOnly Property Get_Last_Instance_BoundingBoxes As BoundingBox()
            Get
                Return m_Cache_Instances_BoundingBoxes
            End Get
        End Property
         
        ReadOnly Property Owner_Mesh As OSModelMesh
            Get
                Return m_Owner
            End Get
        End Property

        ReadOnly Property Owner_Model As OSModel
            Get
                Return m_Owner.Owner_Model
            End Get
        End Property

        'Private Structure InstanceInfo
        '    Public World As Vector4

        '    Public TeamColor_1 As Vector4 ' for meshes that support TeamColor on RED   regionmap channel
        '    Public TeamColor_2 As Vector4 ' for meshes that support TeamColor on GREEN region map channel
        '    Public TeamColor_3 As Vector4 ' for meshes that support TeamColor on BLUE  region map channel
        'End Structure

        Private Structure InstanceInfo
            Public World As Vector4
        End Structure


        Friend Sub New(Owner As OSModelMesh, Part As ModelMeshPart)
            MyBase.New(Owner.Game)
  
            m_Owner = Owner

            _Initialize_VertexDeclaration(Part)


            _Initialize_Geometry(Part)

            _Initialize_BoundingSphere()
            _Initialize_BoundingBox_From_BoundingSphere()
            _Initialize_BoundingBox_From_Vertices()

#If DEBUG Then
            'TODO: log stats containing information about this meshpart:
#End If
        End Sub

        Private Sub _Initialize_VertexDeclaration(Part As ModelMeshPart)

            '0 = POSITION1  : Model Transforms per instance

            '1 = COLOR1     : Per Instance Team Color1
            '2 = COLOR2     : Per Instance Team Color2
            '3 = COLOR3     : Per Instance Team Color3

            '           m_VertexStreamElements =
            '               {
            '                   New VertexElement(0, VertexElementFormat.Vector4, VertexElementUsage.Position, 1)
            '_
            '                   New VertexElement(SizeOfVector4 * 4, VertexElementFormat.Vector4, VertexElementUsage.Color, 1),
            '                   New VertexElement(SizeOfVector4 * 5, VertexElementFormat.Vector4, VertexElementUsage.Color, 2),
            '                   New VertexElement(SizeOfVector4 * 6, VertexElementFormat.Vector4, VertexElementUsage.Color, 3)
            '_
            '               }

            m_VertexStreamElements =
            {
                New VertexElement(0, VertexElementFormat.Vector4, VertexElementUsage.Position, 1)
            }

            m_VertexDeclaration = New VertexDeclaration(m_VertexStreamElements)
        End Sub

 
        Private Sub _Initialize_Geometry(Part As ModelMeshPart)


            Dim Result_Vertex As VertexPositionTexture() = {}


            With Part
                
                ' Initialize Vertices
                m_VertexCount = .PrimitiveCount * 3
                m_VertexOffset = .VertexOffset

                m_PrimitiveCount = .PrimitiveCount

                Array.Resize(Result_Vertex, m_VertexCount)

                .VertexBuffer.GetData(Of VertexPositionTexture)(Result_Vertex)

                ' execute the original vertexbuffer
                .VertexBuffer.Dispose()

                ' Bake Bone transforms into vertices: 
                ' NOTE: if animated / dynamic bones are needed - then this should be removed - and bone transforms would need to be supplied somehow per instance
                g_ApplyBoneTransforms(Result_Vertex, Owner_Mesh.m_ParentBone)

                m_GeometryBuffer = New VertexBuffer(Owner_Model.GraphicsDevice, VertexPositionTexture.VertexDeclaration, Result_Vertex.Length, BufferUsage.None)

                ' apply:
                m_GeometryBuffer.SetData(Of VertexPositionTexture)(Result_Vertex)

                ' Indices:
                m_IndexCount = .IndexBuffer.IndexCount



                    ' ----------------------------------------
                    Dim Result_Indices As Short() = {}
                    Array.Resize(Result_Indices, m_IndexCount)

                    .IndexBuffer.GetData(Of Short)(Result_Indices, 0, Result_Indices.Length)

                    m_IndexBuffer = New IndexBuffer(Owner_Model.GraphicsDevice, Part.IndexBuffer.IndexElementSize, .IndexBuffer.IndexCount, BufferUsage.None)

                    ' apply
                    m_IndexBuffer.SetData(Of Short)(Result_Indices)

                    ' ----------------------------------------
                   


            End With

            ' a copy of vertices remains in memory because we create bounds from them for thsi meshpart:
            m_Vertices = Result_Vertex

        End Sub

        ' we dont have dynamic bones in our model structure ( we dont need them in the current game ) - so we will instead bake the bone transforms into the vertices
        Private Sub g_ApplyBoneTransforms(ByRef Vertices As VertexPositionTexture(), Bone As ModelBone)
            If Bone Is Nothing Then Return
            For I = 0 To Vertices.Length - 1
                Vertices(I).Position = Vector3.Transform(Vertices(I).Position, GetAbsoluteBoneTransform(Bone))
            Next
        End Sub

        ' get absolute bone transforms: ( which will be hardbaked into our model )
        Private Function GetAbsoluteBoneTransform(Bone As ModelBone) As Matrix
            If Bone Is Nothing Then Return Matrix.Identity
            Return Bone.Transform * GetAbsoluteBoneTransform(Bone.Parent)
        End Function

        Private Sub _Initialize_BoundingSphere()
            Dim R As Single = 0

            ' use bone transform 
            ' vertices already have bone transformations applied...
            Dim BoundingSpherePosition As Vector3 = Vector3.Transform(Vector3.Zero, GetAbsoluteBoneTransform(Owner_Mesh.m_ParentBone))

            For I = 0 To m_Vertices.Length - 1

                Dim D = System.Math.Abs(Vector3.Distance(BoundingSpherePosition, m_Vertices(I).Position))

                If D > R Then R = D
            Next

            m_BoundingSphere = New BoundingSphere(BoundingSpherePosition, R)
        End Sub

        Private Sub _Initialize_BoundingBox_From_BoundingSphere()
            m_BoundingBox_FromSphere = BoundingBox.CreateFromSphere(m_BoundingSphere)
        End Sub

        ' vertices at this point only have bone transforms applied: bounding box represents all verticies with bone transforms
        ' - so world transforms would have to be applied seperatly ( in a cached instance list )
        Private Sub _Initialize_BoundingBox_From_Vertices()
            Dim MIN As Vector3 = Vector3.Zero
            Dim MAX As Vector3 = Vector3.Zero

            For I = 0 To m_Vertices.Length - 1
                MIN = Vector3.Min(MIN, m_Vertices(I).Position)
                MAX = Vector3.Max(MAX, m_Vertices(I).Position)
            Next

            m_BoundingBox_FromVertices = New BoundingBox(MIN, MAX)
        End Sub

        ' Update cache list of boundingboxes for all instances (sphere too )
        Private Sub Update_Instance_BoundingBox(Position As ModelPositionInfo, Index As Integer)
            m_Cache_Instances_BoundingBoxes(Index) = New BoundingBox(
                        Vector3.Transform(m_BoundingBox_FromVertices.Min, Position.GetTransform),
                        Vector3.Transform(m_BoundingBox_FromVertices.Max, Position.GetTransform)
            )
            m_Cache_Instances_BoundingSpheres(Index) = New BoundingSphere(
                        Vector3.Transform(m_BoundingSphere.Center, Position.GetTransform),
                        m_BoundingSphere.Radius * Position.Scale
            )
        End Sub

        Friend Sub Update_Instance_Information(Positions As ModelPositionInfo())



            If Not m_Cache_LastPositions Is Nothing And Not Positions Is Nothing Then
                If m_Cache_LastPositions.GetHashCode = Positions.GetHashCode Then
                    'OPTIMIZATION:  nothing has changed - use previous instance information: 
                    If Not m_InstanceBuffer Is Nothing Then Return
                End If
            End If

            m_CanDraw = False
            m_Last_InstanceCount = 0


            If Positions Is Nothing Then Return
            If Positions.Count < 1 Then Return

            ' bounding boxes lengths:
            If m_Cache_Instances_BoundingSpheres.Length <> Positions.Length Then Array.Resize(m_Cache_Instances_BoundingSpheres, Positions.Length)
            If m_Cache_Instances_BoundingBoxes.Length <> Positions.Length Then Array.Resize(m_Cache_Instances_BoundingBoxes, Positions.Length)

            ' make sure we are not repeatedly growing the list in the loop per instance:
            If m_Instances.Capacity < Positions.Length Then m_Instances.Capacity = Positions.Length


            Dim Visible As Integer = 0

            Dim Index As Integer = 0 ' so we may scale our list:
            Dim TrueIndex As Integer = 0
 
            For Each P As ModelPositionInfo In Positions
                If P.Enabled Then
                    ' we only check with wour bounding sphere because the method has extra checks in case of bad accuracy:
                    If g_IsInViewOfCamera(P.Position, m_BoundingSphere, Owner_Model.GameState.Camera) Then

                        ' TODO: check LOD and determine weather to / or how to draw this meshpart  Per Instance

 
                        Dim INSTANCE As New InstanceInfo With
                        {
                            .World = Vector4.Transform(Vector3.Zero, P.GetTransform)
                        }

                        If Owner_Mesh.IsBillboard Then
                            ' TODO: Billboard Tranformation for this model instance ( must face camera )

                        End If

                        If m_ListIDCap < Index Then
                            ' index does not exist in list yet:
                            m_Instances.Add(INSTANCE)

                            m_ListIDCap = Index
                        Else
                            ' index exists : replace... ( avoid clearing list every cycle )
                            m_Instances(Index) = INSTANCE
                        End If

                        ' done:
                        Index += 1
                    End If
                End If

                ' finally - ensure we have a bounding box and bounding sphere for each instance:
                ' bounds are cached for ALL positions regardless of weather they are enabled, or in the view frustum
                Update_Instance_BoundingBox(P, TrueIndex)

                TrueIndex += 1
            Next

            ' remove excess positions that are not drawn this time around 
            ' ( extra instance information from a previous draw cycle ( eg: an instance is no longer in the view frustum) )
            If m_Instances.Count >= Index Then
                For I = Index To m_Instances.Count - 1
                    m_Instances.RemoveAt(I)
                Next
            End If

            ' check that we actually have vertices to send:
            If m_Instances.Count < 1 Then
                ' nothing to draw:
                m_InstanceBuffer = Nothing
                m_CanDraw = False
                m_Last_InstanceCount = 0
            End If

            ' if not created:
            If m_InstanceBuffer Is Nothing Then
                m_InstanceBuffer = New VertexBuffer(Owner_Model.GraphicsDevice, m_VertexDeclaration, m_Instances.Count, BufferUsage.WriteOnly)
            End If

            ' if count is extactly the same as previous cycle - then do not redeclare - simply replace the data:
            'If m_InstanceBuffer.VertexCount <> m_Instances.Count Then
            m_InstanceBuffer = New VertexBuffer(Owner_Model.GraphicsDevice, m_VertexDeclaration, m_Instances.Count, BufferUsage.WriteOnly)
            ' End If

            ' apply:
            m_InstanceBuffer.SetData(m_Instances.ToArray)

            ' set draw information states:
            m_CanDraw = True
            m_Last_InstanceCount = m_Instances.Count

            ' hold a copy of previous positions list for hash comparison next cycle:
            m_Cache_LastPositions = Positions

        End Sub

        ' must be called from OSModelMesh.Draw() AFTER 'Update_Instance_Information()' is called
        ' returns total instances drawn:
        Friend Function Draw() As Integer
            ' check is flag is set notifying that we have nothing to draw this time round:
            If m_CanDraw = False Then Return 0

            m_Bindings = Nothing

            ' prepare bindings:
            m_Bindings = {
                New VertexBufferBinding(m_GeometryBuffer),
                New VertexBufferBinding(m_InstanceBuffer, 0, 1)
            }



            With Owner_Model.GraphicsDevice
                .Indices = m_IndexBuffer

                If Owner_Model.Effect.Shader.CurrentTechnique.Passes.Count = 1 Then
                    ' single pass
                    Owner_Model.Effect.Shader.CurrentTechnique.Passes(0).Apply()

                    .SetVertexBuffers(m_Bindings)
                    .DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, m_VertexCount, 0, m_PrimitiveCount, m_Last_InstanceCount)
                Else
                    ' shader have multiple passes:
                    For Each P As EffectPass In Owner_Model.Effect.Shader.CurrentTechnique.Passes
                        P.Apply()

                        .SetVertexBuffers(m_Bindings)
 
                        .DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, m_VertexCount, 0, m_PrimitiveCount, m_Last_InstanceCount)


                    Next
                End If

                '.SetVertexBuffers(Nothing)
            End With
            Return m_Last_InstanceCount
        End Function
 
    End Class

End Namespace

I think my problem is here - were I initialize my geometry:


 
        Private Sub _Initialize_Geometry(Part As ModelMeshPart)


            Dim Result_Vertex As VertexPositionTexture() = {}


            With Part
                
                ' Initialize Vertices
                m_VertexCount = .PrimitiveCount * 3
                m_VertexOffset = .VertexOffset

                m_PrimitiveCount = .PrimitiveCount

                Array.Resize(Result_Vertex, m_VertexCount)

                .VertexBuffer.GetData(Of VertexPositionTexture)(Result_Vertex)

                .VertexBuffer.Dispose()

                ' Bake Bone transforms into vertices: 
                ' NOTE: if animated / dynamic bones are needed - then this should be removed - and bone transforms would need to be supplied somehow per instance
                g_ApplyBoneTransforms(Result_Vertex, Owner_Mesh.m_ParentBone)

                m_GeometryBuffer = New VertexBuffer(Owner_Model.GraphicsDevice, VertexPositionTexture.VertexDeclaration, Result_Vertex.Length, BufferUsage.None)

                ' apply:
                m_GeometryBuffer.SetData(Of VertexPositionTexture)(Result_Vertex)

                ' Indices:
                m_IndexCount = .IndexBuffer.IndexCount



                    ' ----------------------------------------
                    Dim Result_Indices As Short() = {}
                    Array.Resize(Result_Indices, m_IndexCount)

                    .IndexBuffer.GetData(Of Short)(Result_Indices, 0, Result_Indices.Length)

                    m_IndexBuffer = New IndexBuffer(Owner_Model.GraphicsDevice, Part.IndexBuffer.IndexElementSize, .IndexBuffer.IndexCount, BufferUsage.None)

                    ' apply
                    m_IndexBuffer.SetData(Of Short)(Result_Indices)

                    ' ----------------------------------------
                   


            End With

            ' a copy of vertices remains in memory because we create bounds from them:
            m_Vertices = Result_Vertex

        End Sub

Notes:

- Update_Instance_Information() would be called at the end of every update cycle once - and is required before draw ( at the moment it is called below draw ):

- OSModel.Draw is called - wich contains each mesh sorted by Blendstate (opaque first, Additive second, Alpha Last ) , which calls OSModelMesh.Draw - and finally OSModelMeshPart.Draw().

- Shader effect parameters are set before calling OSModelMeshPart.Draw.

I am also supplying the shader I am trying to test my instancing with - which should should only be returning white for now:



// #include "functions.hlsl"

float4x4 View;
float4x4 Projection;
 
texture InputTexture; 
sampler InputTextureSampler = sampler_state
{
    texture = <InputTexture>;
    mipfilter = LINEAR;
    minfilter = LINEAR;
    magfilter = LINEAR;
};

struct INSTANCED_VSI{
	float4 Position				: POSITION0; 
};

struct INSTANCED_VSO{
	float4 Position				: POSITION0; 
};

INSTANCED_VSO VS_MAIN(INSTANCED_VSI input, float4 InstanceTransform : POSITION1)
{
	INSTANCED_VSO output;
	// -->
	
	float4x4 WVP = View * Projection;

	float4 WorldPosition = input.Position + InstanceTransform; 
	output.Position		 = mul(WorldPosition, WVP); 
 
	return output;
}
 
float4 PS_MAIN(INSTANCED_VSO input) : COLOR0
{
	float4 result = float4(1,1,1,1);
        // we are ignoring the input texture for now....
	return (result) ;
}
 

technique RENDER_WITH_PS
{
    pass p0
    {
		//ZWriteEnable = false;
		//AlphaTestEnable = false;
		//AlphaBlendEnable = true;
		//SrcBlend = SRCALPHA;
		//DestBlend = ONE;
        VertexShader = compile vs_3_0 VS_MAIN();
        PixelShader = compile ps_3_0 PS_MAIN();
    }
}


//technique RENDER_WITHOUT_PS
//{
//    pass p0
//    {
//		//ZWriteEnable = false;
//		//AlphaTestEnable = false;
//		//AlphaBlendEnable = true;
//		//SrcBlend = SRCALPHA;
//		//DestBlend = ONE;
//        VertexShader = compile vs_3_0 VertexShaderFunction(); 
//    }
//}

Any help and explanation would be much appreciated. I can read both C# and VB , so any examples can be described in either language. I am writing in VB because of personal preference.

Advertisement
This quite a lot of code to inspect, especially since you use a "unpopular" language (no offence intended. On the contrary: I consider it quite nice. It's probably the first time I see someone using XNA with VB). Maybe translate it to C# with reflector or ILSpy, so more people can read it.

What you're trying to do is surely possible. Can't easily read the VB part but the shader caught my eye. I've seen troubles with using the POSITION1 semantic in some thread around here. Maybe you can't use it for instancing. Use a texcoord for a change (and make sure it doesn't collide with the geometry part).

Such troubles are best investigated with PIX (needs the DirectX June 2010 SDK) or a similar tool though. Problem is, with XNA you're probably not familiar with the underlying D3D9 calls, so not sure this will help.

Edit: Are you familiar with the Direct3D debug runtimes ?. I suspect another problem with your vertex declaration (I'm not sure one can use a COLOR2 semantic).

This quite a lot of code to inspect, especially since you use a "unpopular" language (no offence intended. On the contrary: I consider it quite nice. It's probably the first time I see someone using XNA with VB). Maybe translate it to C# with reflector or ILSpy, so more people can read it.

What you're trying to do is surely possible. Can't easily read the VB part but the shader caught my eye. I've seen troubles with using the POSITION1 semantic in some thread around here. Maybe you can't use it for instancing. Use a texcoord for a change (and make sure it doesn't collide with the geometry part).

Such troubles are best investigated with PIX (needs the DirectX June 2010 SDK) or a similar tool though. Problem is, with XNA you're probably not familiar with the underlying D3D9 calls, so not sure this will help.

Edit: Are you familiar with the Direct3D debug runtimes ?. I suspect another problem with your vertex declaration (I'm not sure one can use a COLOR2 semantic).

Teamcolor lines were commented out as well as all related code. I wanted to be able to pass instance spefic colors as well ( teamcolors,) Im looking into the debug runtimes now.

Your also right that im not familiar with underling direct3D calls.

Im tried replacing use of Position1 with Texcoord. After reading what you said - while looking at http://roecode.wordpress.com/2008/03/17/xna-framework-gameengine-development-part-19-hardware-instancing-pc-only/ , it hit me that Texcoord is used their as well ( to encode a matrix in 4 texture coordinates , were I thought this would have been done in 4 positions) - so in my case ( were I wanted just one world position ) - so I rewrote it to send the entire matrix instead.

However. The instances are still not drawing ( and DirectXSDK is still downloading, so i cant try the utilities in there yet ) All the results I have again - are exactly as they were before. And no exceptions are being raised.


Changes:

I changed my Vertexshader function as follows:


INSTANCED_VSO VS_MAIN(INSTANCED_VSI input, float4x4 InstanceTransform : TEXCOORD1)
{
	INSTANCED_VSO output;
 
	float4x4 WVP = View * Projection;

	float4 WorldPosition = mul(input.Position, InstanceTransform); 
	output.Position		 = mul(WorldPosition , WVP); 
 
	return output;
}

My InstanceInfo structure's World Position field is now a Matrix instead of a Vector4:


 
        Private Structure InstanceInfo

            Public World As Matrix

        End Structure


Which led to my VertexDeclaration changing to :



        Private Sub _Initialize_VertexDeclaration(Part As ModelMeshPart)
 
            m_VertexStreamElements =
            {
                New VertexElement(0                , VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1),
                New VertexElement(SizeOfVector4 * 1, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 2),
                New VertexElement(SizeOfVector4 * 2, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 3),
                New VertexElement(SizeOfVector4 * 3, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 4)
            }

            m_VertexDeclaration = New VertexDeclaration(m_VertexStreamElements)
        End Sub

 

And instead of transforming a Vector4 by a matrix before sending it - I am just sending the instance specific Matrix:


            For Each P As ModelPositionInfo In Positions
                If P.Enabled Then 

                    If g_IsInViewOfCamera(P.Position, m_BoundingSphere, Owner_Model.GameState.Camera) Then
 
                        Dim INSTANCE As New InstanceInfo With
                        {
                            .World = P.GetTransform
                        } 

                        If m_ListIDCap < Index Then
                            ' index does not exist in list yet:
                            m_Instances.Add(INSTANCE)

                            m_ListIDCap = Index
                        Else
                            ' index exists : replace... ( avoid clearing list every cycle )
                            m_Instances(Index) = INSTANCE
                        End If

                        ' done:
                        Index += 1
                    End If
                End If
 
                Update_Instance_BoundingBox(P, TrueIndex)

                TrueIndex += 1
            Next

Basically it's all doing the same thing in a different way, and the only major difference now from examples being the routine used to initialize the Vertex and Index buffers.

Step-Through debugging also passes through once again - without error, but still not displaying.

I still think Im not copying existing model data correctly for use with instancing.

--------------

BRB 20 minutes ( oops - this is a forum , not IRC )

---------------

I decided to try another fix. I think the only vertex elements I was sending were my own?

I commented out the original Initialize subroutine for my vertex declaration and tried using this replacement:



        Private Sub _Initialize_VertexDeclaration2(Part As ModelMeshPart)
            Dim OriginalVertexElements = Part.VertexBuffer.VertexDeclaration.GetVertexElements

            Dim Offset As Integer = 0

            Offset = OriginalVertexElements(OriginalVertexElements.Length - 1).Offset

            Dim ExtraElements As VertexElement() =
            {
                New VertexElement(Offset + (SizeOfVector4 * 1), VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 2),
                New VertexElement(Offset + (SizeOfVector4 * 2), VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 3),
                New VertexElement(Offset + (SizeOfVector4 * 3), VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 4),
                New VertexElement(Offset + (SizeOfVector4 * 4), VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 5)
            }

            ' destroy original vertexdeclaration:
            Part.VertexBuffer.VertexDeclaration.Dispose()

            Dim Length = OriginalVertexElements.Length + ExtraElements.Length

            Dim FinalElements As VertexElement() = {}
            Array.Resize(FinalElements, Length)

            OriginalVertexElements.CopyTo(FinalElements, 0)

            ExtraElements.CopyTo(FinalElements, OriginalVertexElements.Length)

            m_VertexDeclaration = New VertexDeclaration(FinalElements)
 
        End Sub
    End Class

However I made a new discovery while writing and testing it:

  • A TEXCOORD1 ( elementusage 1) already existed in the vertexdeclaration supplied by the original model, so I decided to leave it as it is by not replacing it ( just in case). This resulted in me having to begin my matrix elements at ElementUsage 2 ( TEXCOORD2 ).

In the attached images:

  • 1: The original vertex elements array supplied by the original vertexdeclaration supplied by the model - see the second TextureCoordinate supplied by the original model.
  • 2: My vertex elements, but offset so they are just added to the eisting elements without replacing or conflicting with existing texturecoordinate elements.
  • 3: My completed stream elements

As a result I made a version of my shader that expects a TEXCOORD2 instead of TEXCOORD1 - because I am not replacing the original - but instead just adding more:


INSTANCED_VSO VS_MAIN(INSTANCED_VSI input, float4x4 InstanceTransform : TEXCOORD2)
{
	INSTANCED_VSO output;
 
	float4x4 WVP = View * Projection;

	float4 WorldPosition = mul(input.Position, InstanceTransform); 
	output.Position		 = mul(WorldPosition , WVP); 
 
	return output;
}


Results:
- still renders nothing, absolutly no change in rendering results. FPS still drops when reacting to 10000 positions forced into view frustum - whil still rendering as invisible.

- still assuming I am initializing my geometry incorrectly when copying from the original model.


- A dont know why the original model's VertexDeclaration Stream Elements define a second Texture Coordinate VertexElement , I could probably use some kind of enlightenment on this.

3DSMax FBX Exporter ( 2012 )

Look at the example you linked to more closely. If you send a full world matrix you need to transpose it, either beforehand when filling the instance stream or - as in the example - by transposing in the shader. This is because the mul(float4, matrix) needs the matrix layed out in column-major (For shader parameters like View/Projection this is happening automatically with the effect framework).

Why there's a second texcoord ? Well, if that mesh needs/has one, so be it. Could be for e.g. multitexturing. Anyway you made it right by offsetting so the semantics don't collide.

I had a look at the reflected XNA code. You should get exceptions if the drawing fails. Still, the debug runtimes gives more clues about the why.

Hmmm, what is FinalElements for ? I don't think you need to setup the combined declaration manually, at least not for XNA 4.0 since the VertexBufferBinding/VertexBuffers will take care of that for you (see here).

OK - even though I figured PIX would be difficult to use - it gave me the verification I needed .... My geometry is getting messed up, and now I am convinced I am initializing my geometry incorrectly:


Heres what I did:

First - I set my test scene to pass only one Instance position to my draw call - so that it should draw only one fighter model using the instanced draw routines.

Next - I had my shader ignore the matrix transformation but simply using Output.Position = Input.Position:


INSTANCED_VSO VS_MAIN(INSTANCED_VSI input, float4x4 InstanceTransform : TEXCOORD2)
{
	INSTANCED_VSO output;
 
	//float4x4 WVP = View * Projection;

	//float4 WorldPosition = mul(input.Position, InstanceTransform); 
	//output.Position		 = mul(WorldPosition , WVP); 
 
	output.Position = input.Position;
	return output;
}

Then - when I ran it - I got the first image result attached. Holy hell, now we see something. But the model mesh is now broken in this geometry buffer obviously ( see the Fighter Model in the previously attached screenshots from the Non-Instanced draws ).

I managed to isolate the Draw call in pix after running it again to test using F12 capture.

I also tested this WITH my matrix transformation - which appears to be working ( the broken mesh data is now simply transformed... )

The results - . I dont know how to fix this, but now im convinced that I am definetly not copying my model's data corectly.

(Skybox is not drawn as instanced, only the fighter model)
[attachment=17183:4.jpg]

[attachment=17185:Clipboard03.jpg]


The model is supposed to look like this:

[attachment=17186:6.jpg]


Look at the example you linked to more closely. If you send a full world matrix you need to transpose it, either beforehand when filling the instance stream or - as in the example - by transposing in the shader. This is because the mul(float4, matrix) needs the matrix layed out in column-major (For shader parameters like View/Projection this is happening automatically with the effect framework).

Why there's a second texcoord ? Well, if that mesh needs/has one, so be it. Could be for e.g. multitexturing. Anyway you made it right by offsetting so the semantics don't collide.

I had a look at the reflected XNA code. You should get exceptions if the drawing fails. Still, the debug runtimes gives more clues about the why.

Hmmm, what is FinalElements for ? I don't think you need to setup the combined declaration manually, at least not for XNA 4.0 since the VertexBufferBinding/VertexBuffers will take care of that for you (see here).

Sorry - I responded and didnt notice your response before I posted. In my own project I have corrected my shader now. However my geometry is now obviously broken before I send it to the GPU.

The reason I tried a combined Elementstream - was because the ROE example seems to be doing the same thing.

After reading your post I tested both by original elemenstream and the finalstream now that I can actually see something ( the broken geometry ) . Both results are exactly the same on display

I'm excited not that things are actually telling me what's wrong ( specifically - how pix showed me what is happening to my geometry ) ...

The problem now Is figuring out why my geometry is so broken.



Excuse my typos - I didnt sleep last night.

No problem. Congrats you got familiar with PIX so fast.

That really helps. You said the original mesh has a second texcoord. Make sure you use an appropriate vertex struct ( VertexPositionTexture only has one texcoord). XNA probably does not come with a suitable struct, so you need to define one yourself.

Actually I wonder if you need to copy at all. You might as well bind the original buffers from the mesh part.

Edit: Nah, scratch that. I just realized you apply the bone transforms, not just copy.

- I wish I had known about PIX before. The only problem is what I "guessed" was my problem before, was only confirmed by pix... regarding my geometry.

Yeah - because I don't need animated bones, I applied them directly to the mesh when trying to copy.

Note - I was using the wrong ( simpler) model with the instanced test, but the results are the same regardless of what model I load into my OSModel heirachy.

I also tested with bone transforms call commented out - mesh is broken either way ( and I dont think I applied the transforms wrong anyhow ).

The following screenshots from pix are with the shader applying color from TEXCOORD0, in addition to using the InstanceTransform.

Also - see what Texcoord1 looks like in pix? , it's just a single float always evaluating to zero. D:? I can ignore this, but who knows when I may actually need that extra TEXCOORD.... ( this is when I am only sending my extra stream elements, however still with the offsets assuming that TEXCOORD1 is populated, which I will change later - I'm now just worried about the geometry problem )

And just to be safe - my VertexShader function looks like this:



INSTANCED_VSO VS_MAIN(INSTANCED_VSI input, float4x4 InstanceTransform : TEXCOORD2)
{
	INSTANCED_VSO output;
 
	output.Position = mul(input.Position, transpose(InstanceTransform));
	output.Position = mul(output.Position, WorldViewProjection);
	output.TextureCoordinate = input.TextureCoordinate;
	// output.Position = input.Position;
	return output;
}

The results:

Wireframe output from pix:

[attachment=17187:1.jpg]

And

[attachment=17188:2.jpg]

Render:
[attachment=17189:3.jpg]

Alot of vertices appear to be converging on approximatly 0,0,0....


This is frustrating. I cant figure out why my geometry is broken.

The instancing streams looks bad, too (TEXCOORD5). I'd recommend inspecting the data you send manually from the VB side (log it in tabular form, so you can compare it easily with the PIX table). Also check if the stride (last paramter of SetStreamSource) matches the vertex struct size (in C# Marshal.SizeOf(typeof(VertexType))).

Also, in PIX you can inspect all states and resources by clicking on the "hyperlinks" (device, textures, buffers, declarations). Especially the buffers and the declarations are of interest. For the buffers you can first provide a HLSL-like definition to get it into the type-correct tabular form.

This topic is closed to new replies.

Advertisement