1
\$\begingroup\$

So I am trying to multithread a Voxel engine in C# made with Sharpdx and I am having a few issues:

This is the class I have :

#region

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using HyroVoxelEngine.Voxels.Chunks;
using HyroVoxelEngine.Voxels.Comparators;
using HyroVoxelEngine.Voxels.Meshing;
using HyroVoxelEngine.World.Cameras;
using HyroVoxelEngine.World.Procedural.Terrain;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Toolkit;

#endregion

namespace HyroVoxelEngine.Voxels
{
    public class ChunkManager
    {
        private GreedyMeshing Mesher = new GreedyMeshing();
        private static ConcurrentDictionary<ChunkIndex, Chunk> Cache;
        private static ConcurrentQueue<Thread> BuildingThreads = new ConcurrentQueue<Thread>();
        private readonly List<Chunk> LoadList;
        private readonly int MaxChunkLoadPerFrame = 1;
        private readonly List<Chunk> RebuildList;
        private readonly List<Chunk> VisibilityList;
        public FPSCamera Camera;

        private bool ForceVisibilityUpdate = true;
        public List<Chunk> RenderList;
        private ChunkIndex lastIndex;
        private Matrix lastView;
        private volatile int taskCount = 10;
        private volatile int buildCount = 1;
        #region Loading and Building
        private void Load(Chunk c)
        {
            if (c.State != Chunk.ChunkState.New)
                return;

            new Thread(() =>
            {
                c.State = Chunk.ChunkState.Generating;
                taskCount--;
                PlanetaryGenerator.GeneratePlain(c);
                taskCount++;
                c.State = Chunk.ChunkState.Complete;

            }).Start();




        }
        private void Rebuild(Chunk c)
        {
            if (c.State != Chunk.ChunkState.NeedBuilding)
                return;
            if (buildCount > 0)
                Task.Run(() =>
                {
                    c.State = Chunk.ChunkState.Generating;
                    Mesher.Mesh(c);
                    c.State = Chunk.ChunkState.Complete;
                });



        }
        #endregion

        public ChunkManager()
        {
            LoadList = new List<Chunk>();
            RenderList = new List<Chunk>();
            VisibilityList = new List<Chunk>();
            RebuildList = new List<Chunk>();
            Cache = new ConcurrentDictionary<ChunkIndex, Chunk>(2, 500, new ChunkIndexComparator());
        }
        public void Update(GameTime gt)
        {
            UpdateLoadList();
            UpdateRebuildList();
            UpdateVisibilityList();
            UpdateRenderList();
            if (lastView == Camera.ViewMatrix)
                return;
            lastView = Camera.ViewMatrix;
            UpdateRenderList();
        }
        private void UpdateVisibilityList()
        {
            if (!ForceVisibilityUpdate && Camera.ChunkIndex == lastIndex && VisibilityList.Count != 0)
                for (int i = 0; i < VisibilityList.Count; i++)
                {
                    LoadList.Add(VisibilityList[i]);
                }
            else
            {
            Chunk c;
            lastIndex = Camera.ChunkIndex;
            LoadList.Clear();
            VisibilityList.Clear();
            int VisibilityRange = Configs.Configs.ViewDistance;
            for (var x = -VisibilityRange; x < VisibilityRange; x++)
            {
                for (var z = -VisibilityRange; z < VisibilityRange; z++)
                {
                    c = GetChunk(Camera.ChunkIndex.X + x , 0, Camera.ChunkIndex.Z +z );
                    VisibilityList.Add(c);
                    switch (c.State)
                    {
                        case Chunk.ChunkState.New:
                            LoadList.Add(c);
                            break;
                        case Chunk.ChunkState.NeedBuilding:
                            RebuildList.Add(c);
                            break;
                        case Chunk.ChunkState.Complete:
                            break;
                    }
                }
            }
        }
        VisibilityList.Sort((x, y) => x.DistanceTo(Camera.Position).CompareTo(y.DistanceTo(Camera.Position)));
        RebuildList.Sort((x, y) => x.DistanceTo(Camera.Position).CompareTo(y.DistanceTo(Camera.Position)));
        LoadList.Sort((x, y) => x.DistanceTo(Camera.Position).CompareTo(y.DistanceTo(Camera.Position)));
        ForceVisibilityUpdate = false;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private Chunk GetChunk(long x, int y, long z)
        {
            var ind = new ChunkIndex(x, z, y);
            Chunk c;
            if (Cache.TryGetValue(ind, out c))
                return c;
            c = new Chunk(ind);

            if (Cache.TryAdd(ind, c))
            {
                return c;
            }
            return null;
        }

        private void UpdateRenderList()
        {

            RenderList.Clear();
            Chunk c;
            for (int i = 0; i < VisibilityList.Count; i++)
            {
                c = VisibilityList[i];
                if (c.Empty)
                    return;
                BoundingBox b = new BoundingBox();
                BoundingFrustum f = new BoundingFrustum(Camera.ViewMatrix*Camera.Projection);
                if (!f.Intersects(ref c.BoundingBox))
                    continue;
                if ( c.State == Chunk.ChunkState.Complete)
                    RenderList.Add(c); 
            }

        }
        private void UpdateRebuildList()
        {
            int loadedChunks = 0;
            Chunk c;
            for (int i = 0; i < RebuildList.Count; i++)
            {
                //if (i > MaxChunkLoadPerFrame)
                //    break;
                c = RebuildList[i];
                if (c.State == Chunk.ChunkState.NeedBuilding)
                {
                    Rebuild(c);
                    if (c.State == Chunk.ChunkState.Complete)
                    {
                        //Rebuild the neighbours

                        //TODO:: REBUILD THE NEIGHBOURS
                        loadedChunks++;
                        ForceVisibilityUpdate = true;
                    }
                }
            }
            RebuildList.Clear();
        }

        private void UpdateLoadList()
        {
            int loadedChunks = 0;
            Chunk c;
            for (int i = 0; i < LoadList.Count; i++)
            {
                //if (i > MaxChunkLoadPerFrame)
                //    break;
                c = LoadList[i];
                if (c.State != Chunk.ChunkState.Complete)
                {
                    Load(c);
                    if (c.State == Chunk.ChunkState.NeedBuilding)
                    {
                        loadedChunks++;
                        ForceVisibilityUpdate = true;
                    }
                }
            }
            LoadList.Clear();
        }
    }
}

The issue being that some of the chunks get "broken"

enter image description here

Just like that image

My Second issue is that whenever I move the camera, I get "hiccups" and my framerate drops to under 10.

I have profileed my code and obviously my issue with performance is the Meshing, but my CPU is not even maxed out. and since I am running in anothe rthread it should drop my FPS that much. So how and what could I possibly do to fix this issues?

EDIT

private void Load(Chunk c)
{

    if (c.State != Chunk.ChunkState.New)
        return;
    Stopwatch t = new Stopwatch();
    t.Start();
    if (taskCount > 0)
    {
        new Thread(() =>
        {
            GreedyMeshing Mesher = new GreedyMeshing();


            c.State = Chunk.ChunkState.Generating;
            taskCount--;
            PlanetaryGenerator.GeneratePlain(c);
            Mesher.Mesh(c);
            taskCount++;
            c.State = Chunk.ChunkState.Complete;

        }).Start();
    }
    t.Stop();




}

So I have found that this code is actually the one behind the stutters, probably I should Implement a Pool Mechanism for the threads, but still it should be that lenghty to create a thread I think. Any ideas on how to fix this?

\$\endgroup\$
3
  • \$\begingroup\$ Is your problem the performance or the "chunk brokenness"? How have you tried narrowing the problem down? What's a minimal example that still does what you expect? \$\endgroup\$
    – Anko
    Commented Jun 16, 2014 at 9:49
  • \$\begingroup\$ My issue here is when I multithread the chunks appear just like that, if I don't my chunks appear correctly. \$\endgroup\$
    – Morphex
    Commented Jun 16, 2014 at 18:38
  • \$\begingroup\$ In general, you need more synchronization and atomicity than what just concurrent collections provides. At a quick glance your mass of code seems to have threads working with smeared state, consider either synchronizing more or working with copies and doing atomic updates of the whole state. \$\endgroup\$ Commented Jun 17, 2014 at 7:49

1 Answer 1

2
\$\begingroup\$
  1. In UpdateVisibilityList(), Why are you sorting the lists? Doesn't seem to need it, since every voxel will be calculated on each frame

  2. In UpdateRenderList(), You probably shouldn't clear the list, and proceed to fill it with 90% of the same voxels. Calculate the new frustum, then add the new blocks, and remove blocks that don't intersect. Also, why is UpdateRenderList() being called twice per updated?

Can you also explain what all of the 4 lists are supposed to do, specifically the load and rebuild lists.

\$\endgroup\$
7
  • \$\begingroup\$ My building list is to rebuild the mesh only, while the load list is used to Load from disk/create the chunk from the noise functions. The double UpdateRenderList is actually a mistake. What I am thinking when sorting, is that all the closest chunks have priority over the further away, so I generate/draw the closest ones first. \$\endgroup\$
    – Morphex
    Commented Jun 16, 2014 at 18:40
  • \$\begingroup\$ When you render the voxels as shown in the screenshot, is it the same missing/corrupted voxels every time? If not, you may have some sort of race condition. Another question, how many voxels are in each chunk? And try logging what happens when you move the camera, it may be doing something unexpected, like loading incorrect chunks. Hard to tell without all of the code. \$\endgroup\$
    – Steve
    Commented Jun 16, 2014 at 19:33
  • \$\begingroup\$ you were right about about the race condition, I was sharing the same mesher instance on all the chunks so that happened, still I getting stuttering when moving across borders. \$\endgroup\$
    – Morphex
    Commented Jun 16, 2014 at 21:04
  • \$\begingroup\$ With regard to the stuttering, you will have to add some logging, to see how many times all the functions are being called, and figuring out whats going wrong. I can't find anything from looking at the code currently. \$\endgroup\$
    – Steve
    Commented Jun 16, 2014 at 22:43
  • \$\begingroup\$ So multithreading wise is it ok the way it is? because thats what I think could be the issue. It stutters only when new chunks are loaded. \$\endgroup\$
    – Morphex
    Commented Jun 17, 2014 at 4:26

Not the answer you're looking for? Browse other questions tagged .