Home > Software engineering >  WPF 3D graphic loop takes too long
WPF 3D graphic loop takes too long

Time:01-28

I'm trying to create goldbergs polyhedra, but the code that should draw it on my screen works too slow (about 22 seconds to draw 6th lvl of detalization)

            Stopwatch sw = new Stopwatch();

        var hexes = sphere.hexes.ToArray();

        sw.Start();

        for (int j = 0; j < hexes.Length; j  )
        {
            MeshGeometry3D myMeshGeometry3D = new MeshGeometry3D();

            Vector3DCollection myNormalCollection = new Vector3DCollection();

            foreach (var verts in hexes[j].Normals)
            {
                myNormalCollection.Add(verts);
            }

            myMeshGeometry3D.Normals = myNormalCollection;

            Point3DCollection myPositionCollection = new Point3DCollection();

            foreach (var verts in hexes[j].Normals)
            {
                myPositionCollection.Add(new Point3D(verts.X, verts.Y, verts.Z));
            }

            myMeshGeometry3D.Positions = myPositionCollection;

            Int32Collection myTriangleIndicesCollection = new Int32Collection();

            foreach (var triangle in hexes[j].Tris)
            {
                myTriangleIndicesCollection.Add(triangle);
            }

            myMeshGeometry3D.TriangleIndices = myTriangleIndicesCollection;                

            Material material = new DiffuseMaterial(
                            new SolidColorBrush(Colors.Black)); ;

            if (switcher)
            {
                material = new DiffuseMaterial(
                            new SolidColorBrush(Colors.BlueViolet)); 
            }

            switcher = !switcher;

            GeometryModel3D model = new GeometryModel3D(
                myMeshGeometry3D, material);

            myGeometryModel.Geometry = myMeshGeometry3D;

            myModel3DGroup.Children.Add(model);

            myModel3DGroup.Children.Add(myGeometryModel);                
        }

        sw.Stop();

I've tried to make my loop parallel, but myGeometryModel and myModel3DGroup are in the main thread so i can't modify them.

CodePudding user response:

Edit:

Experiment with code.

I added the following code to the end of the generation:

        myViewport3D.Children.Add(myModelVisual3D);

        XmlWriterSettings settings = new XmlWriterSettings();
        settings.Indent = true;
        XmlWriter writer = XmlWriter.Create(@"e:\temp\3dg.xaml", settings);
        XamlWriter.Save(myViewport3D, writer);

This results in a file roughly 79 meg and of course does not speed the generation up at all.

I then load that file instead of generating it:

    private void CreateHexaSphere(object sender, RoutedEventArgs e)
    {
        using (FileStream fs = new FileStream(@"e:\temp\3dg.xaml", FileMode.Open))
        {
            myViewport3D = (Viewport3D)XamlReader.Load(fs);
        }
        this.Content = myViewport3D;
        return;

That takes roughly 7 seconds to show the sphere. Yep. Bit of a difference there.

If you are happy to just load that file from disk then 7 seconds seems way better than 52 seconds.

If you need to generate this thing on the fly then I would find a way to work with strings and build a similar string to what you get in that file.

Unsuccessful theory:

I think all the things that matter there are freezables.

.Freeze() a freezable and you increase performance but can also pass it from one thread to another.

You could do most of this on multiple parallel background threads.

EG each hex could be built on a set of parallel worker threads. Each building their own myMeshGeometry3D which was frozen and returned to a caller thread.

Loosely, you'd have a Task DoMeshGeom3D(int j) which returns one of your 3d geometries.

You could use plinq or task.whenall or parallel.foreach

Roughly

 var taskList = new List<Task<MeshGeometry3D>>();

 for (int j = 0; j < hexes.Length; j  )
 {
    taskList.Add(DoMeshGeom3D(j));
 }

 var result = await Task.WhenAll(taskList.ToList());

Grab your list of numerous MeshGeometry3D and assemble them all together.

And you could be awaiting that on a background thread in another task.

You can also try constructing your 3dgroup using a list or ienumerable of these things constructed in the separate threads rather than adding one at a time.

https://briancaos.wordpress.com/2020/09/11/run-tasks-in-parallel-using-net-core-c-and-async-coding/

https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.whenall?view=netcore-3.1

CodePudding user response:

            foreach (var figure in hexes)
        {
            var mesh = new MeshGeometry3D();
            mesh.Positions = new Point3DCollection(figure.Vericies);
            mesh.TriangleIndices = new Int32Collection(figure.Tris);
            mesh.Normals = new Vector3DCollection(figure.Normals);


            Material material = new DiffuseMaterial(
                            new SolidColorBrush(Colors.Black)); ;

            if (switcher)
            {
                material = new DiffuseMaterial(
                            new SolidColorBrush(Colors.BlueViolet));
            }

            switcher = !switcher;

            //var material = new DiffuseMaterial(new SolidColorBrush(Colors.Blue));
            var model = new GeometryModel3D(mesh, material);
            myModel3DGroup.Children.Add(model);
        }

I tried to ask chat GPT and got this solution (render time 191ms)

CodePudding user response:

Your code isn't the problem.

I tried it out (thanks for posting a link) and based on what I was seeing in the Visual Studio performance tools tried using the Model3DCollection constructor that takes an IEnumerable<Model3D>, instead of adding them one by one, which I see Andy also suggested. (During the build loop I added all the models to my own List<Model3D> - which took almost no time - and sourced the Model3DCollection with this list - which is where everything ground to a halt.)

While this approximately halved the time, it was still 20 seconds on my souped up machine in debug mode. The Model3DCollection constructor with the IEnumerable - one line of code - took up nearly all the time.

For what it's worth - and I assume you know this, but for everyone else's benefit - detail level 5 rendered about 4x as fast for me, with 20,485 elements in the collection vs. 81,925 at level 6, while level 4 was essentially instantaneous at 5125 elements. So apparently every detail level quadruples your model count and in turn the time to construct the Model3DCollection. Point being, if you can get away with a lower detail level the render times improve dramatically.

But if you require that detail level, you really need to be looking at another platform IMO. C# itself is not the issue, but the bottleneck is in WPF, so you're rather stuck. If you need to stick with WPF, you might try looking into D3DImage and SharpDX. No C is required. Of course it would be a substantial re-write, but you're virtually certain to get the performance you're looking for.

  • Related