SlimDX low performance with many vertices

721 views Asked by At

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 ?

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

Edit: I know, that using an index buffer would be more efficient, as well as I know that CullingMode.None isn't really a FPS-Saver, but in the OpenGL test I've even used two sided lighting and a bit of smoothing, which should be as hard as creating (in the worst case) 3 times as many points as necessary.

Edit: Out of curiosity I modified the code to include an indexBuffer, and it really did have some impact on the FPS, I am validating this right now

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

1

There are 1 answers

0
user3062148 On BEST ANSWER

I solved it.
The issue was based on an inperformant approach to adding the vertices.
For further information, checkout this nice paper I've found