SlimDX low performance with many vertices

Started by
4 comments, last by Richardp 10 years, 4 months ago

Hello,

So I've been playing around with SlimDX for quite a while now,
and experienced some issues with big STL files.

While on OpenGL they load without flinching I get down to 1-2 FPS an soon as I load files of about 100mb (same issues with multiple files) in SharpGL. Did I miss anything, or is there anything I am simply doing not right at all ?

Just to specify my question: is performance with SlimDX on 1.000.000+ vertices always that poor ?

BasicFramework.cs





    #region Using Statements
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Text;


    using SlimDX;
    using SlimDX.Direct3D11;
    using SlimDX.DXGI;
    using SlimDX.Windows;


    using Device = SlimDX.Direct3D11.Device;
    using Texture2D = SlimDX.Direct3D11.Texture2D;


    #endregion


    namespace SlimDX_Evaluation
    {
        public abstract class BasicFramework : IDisposable
        {
            #region Membervariables


            //Objects
            private RenderForm renderForm;
            private SwapChain swapChain;
            private Factory factory;
            private Device device;
            private DeviceContext deviceContext;
            private Texture2D backBufffer;
            private Texture2D depthBuffer;
            private RenderTargetView renderTargetView;
            private DepthStencilView depthStencilView;
            private TimeSpan lastFrameTime;
            private Stopwatch clock;


            //Variables
            private bool userResized;
            private bool isResizing;


            #endregion
            #region Constructors
            /**
             * The Constructor initializes the default behavior of the Framework.
             * It is not supposed to be replaced, the customization should be done in the Constructor
             */
            public BasicFramework() : this("My Title") { }


            public BasicFramework(string title)
            {
                //Create the winForm
                renderForm = new RenderForm(title);                                                
                renderForm.ClientSize = new System.Drawing.Size(800, 480);                          
                renderForm.MaximizeBox = true;                                                     
                renderForm.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;         


                //Hook into Windows.Forms Event 
                renderForm.ClientSizeChanged += HandleClientSizeChanged;


                //Generate SwapChain
                var desc = new SwapChainDescription()
                {
                    BufferCount = 1,
                    ModeDescription = new ModeDescription(renderForm.ClientSize.Width,
                        renderForm.ClientSize.Height,
                        new Rational(60, 1),
                        Format.B8G8R8A8_UNorm),
                    IsWindowed = true,
                    OutputHandle = renderForm.Handle,
                    SampleDescription = new SampleDescription(1, 0),
                    SwapEffect = SwapEffect.Discard,
                    Usage = Usage.RenderTargetOutput,                                              
                };


                Device.CreateWithSwapChain(
                    DriverType.Hardware,                                                             
                    DeviceCreationFlags.None,                                                        
                    desc,                                                                            
                    out device,                                                                      
                    out swapChain                                                                    
                );


                //Set DeviceContext
                deviceContext = device.ImmediateContext;                                             


                // prevent DXGI handling of alt+enter,prt scrn, etc which doesn't work properly with Winforms
                using (var factory = swapChain.GetParent<Factory>())
                    factory.SetWindowAssociation(renderForm.Handle, WindowAssociationFlags.IgnoreAll);


                //Generate Backbuffer
                backBufffer = Texture2D.FromSwapChain<Texture2D>(swapChain, 0);
                renderTargetView = new RenderTargetView(device, backBufffer);                       


                //Generate Depthbuffer and DepthBufferView
                depthBuffer = new Texture2D(device, new Texture2DDescription()
                {
                    Format = Format.D16_UNorm,                                                   
                    ArraySize = 1,                                                               
                    MipLevels = 1,                                                               
                    Width = renderForm.ClientSize.Width,                                         
                    Height = renderForm.ClientSize.Height,                                       
                    SampleDescription = new SampleDescription(1, 0),                             
                    Usage = ResourceUsage.Default,                                               
                    BindFlags = BindFlags.DepthStencil,                                          
                    CpuAccessFlags = CpuAccessFlags.None,                                        
                    OptionFlags = ResourceOptionFlags.None,                                      
                });


                depthStencilView = new DepthStencilView(device, depthBuffer);


                //Define Rasterizer
                RasterizerStateDescription rasterizerDescription = new RasterizerStateDescription()
                {
                    CullMode = CullMode.None,
                    FillMode = FillMode.Solid,
                    IsAntialiasedLineEnabled = true,
                    IsFrontCounterclockwise = true,
                    IsMultisampleEnabled = true,
                    IsDepthClipEnabled = true,
                    IsScissorEnabled = false
                };


                deviceContext.Rasterizer.State = 
                           RasterizerState.FromDescription(device, rasterizerDescription);


                //Set ViewPort
                deviceContext.Rasterizer.SetViewports(new Viewport(
                    0,                                                                                 
                    0,                                                                              
                    renderForm.Width,                                                               
                    renderForm.Height));                                                            


                deviceContext.OutputMerger.SetTargets(depthStencilView, renderTargetView);


                //Force recalibration on first load
                userResized = true;
            }


            #endregion
            #region Run
            public void Run()
            {
                clock = new Stopwatch();                                                          
                clock.Start();                                                                    
                this.lastFrameTime = clock.Elapsed;                                               


                Initialize();                                                                     
                LoadContent();                                                                    


                MessagePump.Run(renderForm, () =>                                                 
                {
                     if (userResized)                                                             
                     {
                         backBufffer.Dispose();                                                   
                         RenderTargetView.Dispose();                                              
                         depthBuffer.Dispose();                                                   
                         depthStencilView.Dispose();                                              


                         //Resize the buffers
                         swapChain.ResizeBuffers(
                             0,                                                                     
                             renderForm.ClientSize.Width,                                           
                             renderForm.ClientSize.Height,                                          
                             Format.Unknown,                                                        
                             SwapChainFlags.None                                                    
                             );


                         //Get the new Backbuffer
                         backBufffer = Texture2D.FromSwapChain<Texture2D>(swapChain, 0);             


                         //Renew RenderTargetView
                         renderTargetView = new RenderTargetView(device, backBufffer);              


                         //Create the new DepthBuffer
                         depthBuffer = new Texture2D(device, new Texture2DDescription()
                         {
                             Format = Format.D32_Float_S8X24_UInt,
                             ArraySize = 1,
                             MipLevels = 1,
                             Width = renderForm.ClientSize.Width,
                             Height = renderForm.ClientSize.Height,
                             SampleDescription = new SampleDescription(1, 0),
                             Usage = ResourceUsage.Default,
                             BindFlags = BindFlags.DepthStencil,
                             CpuAccessFlags = CpuAccessFlags.None,
                             OptionFlags = ResourceOptionFlags.None
                         });


                         //Create DepthBufferView
                         depthStencilView = new DepthStencilView(device, depthBuffer);


                         //SetUp Targets and Viewports for Rendering
                         deviceContext.Rasterizer.SetViewports(
                                      new Viewport(0, 0, renderForm.Width, renderForm.Height));
                         deviceContext.OutputMerger.SetTargets(depthStencilView, renderTargetView);   


                         //finished resizing
                         isResizing = userResized = false;
                     }


                    TimeSpan timeSinceLastFrame = clock.Elapsed - this.lastFrameTime;              
                    this.lastFrameTime = clock.Elapsed;                                            


                    Update(clock.Elapsed, timeSinceLastFrame);                                     
                    BeginFrame();                                                                  
                    Draw(clock.Elapsed, timeSinceLastFrame);                                       
                    EndFrame();                                                                    
                });


                UnloadContent();
            }
            #endregion


            #region MethodsToOverride
            public virtual void Update(TimeSpan totalRunTime, TimeSpan timeSinceLastFrame)
            {


            }


            public virtual void Draw(TimeSpan totalRunTime, TimeSpan timeSinceLastFrame)
            {


            }


            public virtual void BeginFrame()
            {


            }


            public void EndFrame()
            {
                swapChain.Present(0, PresentFlags.None);        //Presents the image to the user
            }


            public virtual void Initialize()
            {


            }


            public virtual void LoadContent()
            {


            }


            public virtual void UnloadContent()
            {


            }


            public virtual void Dispose()
            {
                renderForm.Dispose();
                backBufffer.Dispose();
                deviceContext.ClearState();
                deviceContext.Flush();
                device.Dispose();
                deviceContext.Dispose();
                depthBuffer.Dispose();
                depthStencilView.Dispose();
                swapChain.Dispose();
            }


            #endregion


            #region Handlers


            private void HandleResize(object sender, EventArgs e)
            {
                backBufffer.Dispose();
                RenderTargetView.Dispose();
                depthBuffer.Dispose();
                depthStencilView.Dispose();


                //Resize the buffers
                swapChain.ResizeBuffers(
                    0,
                    renderForm.ClientSize.Width,
                    renderForm.ClientSize.Height,
                    Format.Unknown,
                    SwapChainFlags.None
                    );


                //Get the new Backbuffer
                backBufffer = Texture2D.FromSwapChain<Texture2D>(swapChain, 0);


                //Renew RenderTargetView
                renderTargetView = new RenderTargetView(device, backBufffer);


                //Create the new DepthBuffer
                depthBuffer = new Texture2D(device, new Texture2DDescription()
                {
                    Format = Format.D32_Float_S8X24_UInt,
                    ArraySize = 1,
                    MipLevels = 1,
                    Width = renderForm.ClientSize.Width,
                    Height = renderForm.ClientSize.Height,
                    SampleDescription = new SampleDescription(1, 0),
                    Usage = ResourceUsage.Default,
                    BindFlags = BindFlags.DepthStencil,
                    CpuAccessFlags = CpuAccessFlags.None,
                    OptionFlags = ResourceOptionFlags.None
                });


                //Create DepthBufferView
                depthStencilView = new DepthStencilView(device, depthBuffer);


                //SetUp Targets and Viewports for Rendering
                deviceContext.Rasterizer.SetViewports(
                              new Viewport(0, 0, renderForm.Width, renderForm.Height));
                deviceContext.OutputMerger.SetTargets(depthStencilView, renderTargetView);


                //finished resizing
                isResizing = userResized = false;


                TimeSpan timeSinceLastFrame = clock.Elapsed - this.lastFrameTime;
                this.lastFrameTime = clock.Elapsed;


                Update(clock.Elapsed, timeSinceLastFrame);
                BeginFrame();
                Draw(clock.Elapsed, timeSinceLastFrame);
                EndFrame();   
            }


            private void HandleClientSizeChanged(object sender, EventArgs e)
            {
                userResized = true;
            }
            #endregion


            #region GetAndSet
            public Device Device
            {
                get
                {
                    return this.device;
                }
            }


            public DeviceContext DeviceContext
            {
                get
                {
                    return this.deviceContext;
                }
            }


            public RenderTargetView RenderTargetView
            {
                get
                {
                    return this.renderTargetView;
                }
            }


            public RenderForm RenderForm
            {
                get
                {
                    return this.renderForm;
                }
            }


            public DepthStencilView DepthStencilView
            {
                get
                {
                    return this.depthStencilView;
                }
            }
            #endregion
        }
    }
SimpleIntegration.cs


    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Drawing;


    using SlimDX;
    using SlimDX.D3DCompiler;
    using SlimDX.Direct3D11;


    using Buffer = SlimDX.Direct3D11.Buffer;
    using System.Diagnostics;


    namespace SlimDX_Evaluation
    {
        class SampleIntegration : BasicFramework
        {
            #region Members
            private VertexShader vertexShader;
            private PixelShader pixelShader;


            private Buffer constantBuffer;
        
            private VertexBufferBinding vertexBufferBinding_model;
            private int vertCount;


            private Stopwatch timer;
            private long lastFrame;
            private int frameCount;


            private Matrix view;
            private Matrix proj;
            private Matrix viewProj;
            Matrix worldViewProj;




            #endregion


            public override void Draw(TimeSpan totalRunTime, TimeSpan timeSinceLastFrame)
            {
                //Output FPS
                frameCount++;
                if (timer.ElapsedMilliseconds - lastFrame >= 1000)
                {
                    Console.WriteLine("FPS: " + frameCount);
                    lastFrame = timer.ElapsedMilliseconds;
                    frameCount = 0;
                }


                worldViewProj = Matrix.Multiply(Matrix.RotationAxis(Vector3.UnitY, timer.ElapsedMilliseconds / 1000.0f), viewProj);


                //Update ConstantBuffer
                var buffer = new MyConstantBuffer();
                buffer.worldViewProj = worldViewProj;


                var data = new DataStream(System.Runtime.InteropServices.Marshal.SizeOf(new MyConstantBuffer()), true, true);
                data.Write(buffer);
                data.Position = 0;


                DeviceContext.UpdateSubresource(new DataBox(0, 0, data),constantBuffer,0);


                //Clear
                Device.ImmediateContext.ClearRenderTargetView(RenderTargetView, Color.WhiteSmoke);
                Device.ImmediateContext.ClearDepthStencilView(DepthStencilView, DepthStencilClearFlags.Depth, 1.0f, 0);


                //Draw
                DeviceContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;              


                DeviceContext.InputAssembler.SetVertexBuffers(0, vertexBufferBinding_model);             
                Device.ImmediateContext.Draw(vertCount, 0);                                       


                base.Draw(totalRunTime, timeSinceLastFrame);
            }


            public override void LoadContent()
            {
                //Initialize the timer
                timer = new Stopwatch();
                timer.Start();


                //Initialize Matrices
                view = Matrix.LookAtLH(new Vector3(0, 100, -500), new Vector3(0, 0, 0), Vector3.UnitY);
                proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, RenderForm.ClientSize.Width / RenderForm.ClientSize.Height, 0.1f, 10000.0f);
                viewProj = Matrix.Multiply(view, proj);


                //Load Shaders
                ShaderBytecode vertexShaderByteCode;
                ShaderBytecode pixelShaderByteCode;


                try
                {
                    vertexShaderByteCode = ShaderBytecode.CompileFromFile("Shaders/shader.hlsl", "VShader", "vs_4_0",ShaderFlags.None,EffectFlags.None);
                    pixelShaderByteCode = ShaderBytecode.CompileFromFile("Shaders/shader.hlsl", "PShader", "ps_4_0",ShaderFlags.None,EffectFlags.None);
                }
                catch (System.Exception ex)
                {
                    throw ex;
                }


                vertexShader = new VertexShader(Device, vertexShaderByteCode);
                pixelShader = new PixelShader(Device, pixelShaderByteCode);


                DeviceContext.VertexShader.Set(vertexShader);
                DeviceContext.PixelShader.Set(pixelShader);


                var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode);


                //Define first 16 floats as Position, next 16 as Color, next 12 normal (4 cords, 4 Color, 3 normal parts)
                InputElement[] elements = new InputElement[] 
                {
                    new InputElement("POSITION", 0, SlimDX.DXGI.Format.R32G32B32A32_Float, 0, 0),
                    new InputElement("COLOR"   , 0, SlimDX.DXGI.Format.R32G32B32A32_Float, 16, 0),
                    new InputElement("NORMAL"  , 0, SlimDX.DXGI.Format.R32G32B32_Float, 32, 0),
                };


                //Define Layout for the InputAssembler
                DeviceContext.InputAssembler.InputLayout = new InputLayout(Device, signature, elements);


                //Generate and link constant buffers
                constantBuffer = new Buffer(Device, System.Runtime.InteropServices.Marshal.SizeOf(new Matrix()), ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
                DeviceContext.VertexShader.SetConstantBuffer(constantBuffer,0);


                //load STL and generate Vertices from it


                ModuleWorks.Meshf meshf = ModuleWorks.MeshHelper.ReadSTLf(@"C:\ModuleWorks\STL\Homer.stl", ModuleWorks.Unit.Metric);


                try
                {


                    vertCount = meshf.TriangleCount * 3;


                    var vertices_model = new DataStream(vertCount * System.Runtime.InteropServices.Marshal.SizeOf(typeof(Vertex)), true, true);
                    var stopWatch = new Stopwatch();
                    stopWatch.Start();
                    for (int x = 0; x < meshf.TriangleCount; x++)
                    {
                        var triangle = meshf.GetTriangle(x);
                        var normal = triangle.Normal;
                        vertices_model.Write(new Vertex(meshf.GetPoint(triangle.Idx1).X, meshf.GetPoint(triangle.Idx1).Y, meshf.GetPoint(triangle.Idx1).Z, 1.0f, 0.0f, 0.0f, 1.0f, normal.X, normal.Y, normal.Z));
                        vertices_model.Write(new Vertex(meshf.GetPoint(triangle.Idx2).X, meshf.GetPoint(triangle.Idx2).Y, meshf.GetPoint(triangle.Idx2).Z, 1.0f, 0.0f, 0.0f, 1.0f, normal.X, normal.Y, normal.Z));
                        vertices_model.Write(new Vertex(meshf.GetPoint(triangle.Idx3).X, meshf.GetPoint(triangle.Idx3).Y, meshf.GetPoint(triangle.Idx3).Z, 1.0f, 0.0f, 0.0f, 1.0f, normal.X, normal.Y, normal.Z));
                    }
                    vertices_model.Position = 0;


                    //Generate VertexBufferBinding
                    var sizeInBytes = vertCount * 
                                System.Runtime.InteropServices.Marshal.SizeOf(typeof(Vertex));
                    var stride = 
                                System.Runtime.InteropServices.Marshal.SizeOf(typeof(Vector4)) * 2 
                                + System.Runtime.InteropServices.Marshal.SizeOf(typeof(Vector3));


                    var vertexBuffer_model = new Buffer(
                                       Device, vertices_model, sizeInBytes, ResourceUsage.Default,
                                       BindFlags.VertexBuffer, CpuAccessFlags.None, 
                                       ResourceOptionFlags.None, 0);
                    vertexBufferBinding_model = new VertexBufferBinding(vertexBuffer_model, stride, 0);
                    vertices_model.Close();
                }
                catch (System.Exception ex)
                {
                    Console.WriteLine(ex);
                    return;
                }
            }


            public override void Dispose()
            {
                vertexShader.Dispose();
                pixelShader.Dispose();
                constantBuffer.Dispose();
                base.Dispose();
            }
        }
    }
shader.hlsl

cbuffer matrixBuffer : register(b0)
{
float4x4 worldViewProj;
};


struct VOut
{
float4 position : SV_POSITION;
float4 color    : COLOR;
float3 normal : NORMAL;
};


VOut VShader(float4 position : POSITION, float4 color : COLOR, float3 normal : NORMAL)
{
VOut output = (VOut)0;


output.position = mul(worldViewProj, position);


output.normal = normalize(mul((float3x3)worldViewProj,normal));


output.color = color;


return output;
}


float4 PShader(VOut vout) : SV_TARGET
{
return vout.color;
}
Thanks in advance
Advertisement

One obvious thing I spotted with that code is that you're not creating an index buffer. That will be far more efficient than manually duplicating vertices - I wouldn't be surprised if it goes more than three times quicker with one (assuming you're vertex bound). It should also save lots of memory.

You're also using CullMode.None which on an average scene will double the quantity of per-pixel work you're doing compared to enabling back face culling.

You also have what is currently a completely redundant line of code in your vertex shader that transforms the normal, and then renormalizes it. Firstly unless you need it in the pixel shader you should not output it from the vertex shader (or store it in the vertex buffer). Secondly a renormalize will need to happen in the pixel shader anyway as interpolation will change the length of the normal, so the renormalize in the vertex shader isn't usually needed, even if you are using it in the pixel shader.

By the way what graphics card are you getting this 1-2 FPS on?


you're not creating an index buffer

I will check this, but in the OpenGL implementation, another shader is beeing used, adding a bit of smoothing and two sided lighting, which I thought would take about the same amount of processing power. (Two sided lighting should even be a little bit harder than drawing both faces, shouldn't it ?)


You also have what is currently a completely redundant line of code in your vertex shader

Thanks for the hint with the shader, I will fix this as well, but it don't really believe that it will have a huge impact on performance.


By the way what graphics card are you getting this 1-2 FPS on?

I am using a GeForce GT 330M, but it shouldn't be the laptop, as performance in OpenGL is just nice.

[Intel Core i7 Q740 @1.73GhZ, 6GB Ram, Win 7 Prof]

Oh, and thanks for checking :)

So I am currently getting some changes done and using an index buffer in fact affected the FPS a lot! :)

The main reason the index buffer helps is that the GPU has a post-transform vertex cache, which means triangles that re-use recently used vertices are much cheaper.

There's a more detailed explanation, along with an algorithm for optimizing the way the vertices and indices are stored at: http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html

The benefits from that mostly depend on how well ordered the triangles in your source data are.

Well this seems to be the solution, thanks a lot for the help, as well as the in-depth information !

With thanks, Richard

This topic is closed to new replies.

Advertisement