Skip to content

Huge meshing speed boost! Crazy performance!

Compare
Choose a tag to compare
@serg06 serg06 released this 28 May 18:00
· 122 commits to master since this release

New features:

  • Meshing is now insanely fast. Try holding +, the world will expand at supersonic speeds.

Technical details:

  • Read about ZeroMQ and replaced my mutexes with a rough messaging system.
  • Create a unique_queue which maps keys to elements, and given a key, can move the corresponding element to the front or back of the queue in O(1).
  • Came up with a really cool algorithm for sharing world data with other threads without worrying about it being edited. (See extra notes at the bottom.)

Controls:

  • F11 fullscreen
  • R toggle mouse raw input
  • T toggle t-junction fixing
  • Mouse look around
  • WASD move around
  • Shift/space move up/down
  • ESC quit
  • N toggle noclip
  • P cycle polygon mode
  • [+ or numpad+] (press once or hold) increase render distance
  • [- or numpad-] (press once or hold) decrease render distance (without unloading existing chunks)
  • C toggle face culling
  • Left-click destroy block
  • Right-click place flowing water
  • F3 enable debug overlay
  • Scroll change block type (active block type visible in debug menu)

Extra notes:

When you hear "meshing speed boost" you may think that my meshing algorithm has been improved, but no; it's the inter-thread communication that has improved. My main thread and meshing thread (only one for now) communicate much more efficiently.

Old way:

  • Main thread creates a "mesh generation request" containing A COPY of a chunk of world data that needs meshing.

  • Main thread locks the "request queue" and sticks the request in there.

  • Meshing thread picks it up, creates the "mesh generation response", and sticks in in the "response queue."

  • Main thread picks it up and updates its list of meshes.

New way:

  • Main thread creates a "mesh generation request" but now with A CONST REFERENCE to the block data.

  • Main thread asynchronously sends the request message through zeromq (basically a super-fast socket) to the meshing thread.

  • Meshing thread receives the request, removes duplicate requests using an O(1) unique_queue data structure, generates the mesh, and sends it back through zeromq.

  • Main thread asynchronously receives mesh and updates its list of meshes.

Without locks and conditional variables, everything is much smoother!

Now you may be thinking "How can you send a reference to the world data? What if someone else modifies that data while you're meshing it?" I came up with a clever way to overcome that problem:

  • The world stores data as shared pointers to chunks of blocks.

  • When creating the "mesh generation request", the world simply duplicates the shared pointers to the data, increasing the reference count of the shared pointers until the meshing thread discards it.

  • When placing or breaking a block into a chunk of data, the world checks the reference count of the shared pointer - if it's 1 (only the world has a copy), the world simply edits the existing data. If it's more than 1 (at least one meshing request is somewhere in the request queue), the world creates a copy of the data and discards the old shared pointer.

Now you may be thinking "What are the downsides of this strategy compared to the other one?"

  • So far, none. Using the old method, every time we placed/destroyed a block we would create a copy of the block's chunk data. Now, we only create a copy if the chunk is currently being meshed.