Nova

Nova Progress Update - June to September 2018

In June I stopped writing about Nova so I could write Nova… but now I’m writing about Nova again. Sorry for the delay, and for the very…dense nature of this blog. If you’re more into pretty pictures and less into a lot of technical talk, you might want to skip this one.

With that said, let’s get into it!

June

In June, I continued fixing any bugs I could find and continued building out more of the renderer. This included setting up the data for UBOs and handling the Minecraft lightmap properly. I also started on a CPU profiler to get a general idea of how well Nova runs.

Janrupf joined at the beginning of June and started working on moving Nova from raw MCP to Forge. Most of this effort was converting Nova's changes to Minecraft from a source code patch to Forge Mixins.

July

Barteks2x joined at the beginning of July and jumped into helping Janrupf convert Nova to Forge. He also updated Nova to run in Minecraft 1.12.2 instead of 1.10. That brought us into a better position to update to 1.13 when it became available.

Barteks2x also got chunk rendering working. I hadn't quite gotten that feature working, but Barteks2x was able to solve the issues and get the chunks drawing. Unfortunately, this introduced a bug where, on my computer, the GPU would freeze up after a second of rendering a world. Solving that would take me many weeks.

4D Klein Manifold (hereafter referred to as Klein) came in around the middle of July. He began working on getting the main menu panorama working.

We merged the Forge version of Nova into the Vulkan branch - essentially declaring that Nova was completely moved to Forge. During that merge we had to resolve a number of compatibility issues. Barteks2x and Janrupf develop on Linux, while I'm on Windows and Klein switches between the two. Unfortunately they had done a few Linux-specific things that needed to be resolved.

Janrupf then started setting Nova up for Continuous Integration (CI). CI lets us automatically compile Nova whenever the code changes. This ensures that the code changes we make won't horribly break Nova, and removes the need to manually compile Nova. I had wanted to set this up for a while, but it would have been much more difficult before we converted Nova to Forge.

Then came the task of merging the Vulkan branch into the Master branch. This was more tricky than one might expect because back in February, Klein had made some changes that made chunk loading much faster and more like vanilla - but made them when I was having doubts about Vulkan, so we ended up with two branches with different commit histories. Barteks2x took on the task of resolving all those changes, and did so very well.

With CI conquered, Janrupf began working on a standalone shader editor for Nova. Nova is much more complex and more involved than Optifine shaders or Bedrock shaders. Developers coming from those platforms will likely need a lot of help to become proficient with Nova, and a standalone shader editor can go a long way to providing that help. You can see and follow his work at here.

Another developer, Strum, also joined in July. He began working on the shader marketplace. The vision here is that shaderpack developers can upload their shaderpacks to a central location, then the shaderpacks can be browsed and downloaded through Nova, without leaving Minecraft or moving things to different folders. This will provide a lot of discoverability for shaderpacks, especially smaller packs that needs a popularity boost. You can see and follow his work here.

August

To help solve some of the issues that arose when merging Vulkan into Master, Barteks2x created a tool to capture the parameters to native Nova functions, then replay them in a JVM-free environment. The JVM is cool and all, but it does a lot of things that interfere with debugging C++ code. In the first week of its existence, his tool proved to be well worth it, time and time again.

But that prosperity was not to last. While Barteks and Janrupf were making wonderful progress, I did not have any such luck. A crash began rearing its ugly head. On Windows, after Nova was rendering 50+ chunks, it just…died.

I first noticed the problem as a segfault when updating the lightmap. I removed the offending code, and got a crash when updating a different resource. Removed that code, and now the crash happened when reading the GPU time queries for each frame.

The behavior was dumb. Were there multiple crashes? One bad one? I added a new thread that would poll the GPU to find out when each VkFence got signaled. It polled, and… things got weirder. I could see each command buffer be submitted, could see the fence get signaled… then a segfault somewhere inside vkWaitForFences.

This was dumb.

I dug into this for a week. I tried this and that, poked here and there…nothing. The GPU was just like “nah bro I’m out” in the middle of rendering a frame.

Vulkan isn’t the first-party graphics API on Windows 10, DirectX 12 is. On August 14 I made the decision to add in a DirectX 12 rendering backend for Windows 10. This was the first-party API on Windows 10, surely it would work better?

The work began. I would write the DirectX 12 backend for Windows 10, Janrupf would make the Vulkan backend. By this point Barteks was back to focusing on CubicChunks and unfortunately couldn’t give more time to Nova

Janrupf did a lot for the architecture of Nova in the last half of August. He realized that Nova shouldn’t have its own logger or load its own config file, that all should be provided by whatever application Nova was embedded into. Additionally, he pushed for “add a DirectX 12 backend” to become “add a DirectX 12 backend and also rewrite the Nova backend”, which I agreed to. The Vulkan code would have to be mostly replaced anyways; I was trying to get things done quickly and made far too many compromises


 
 

September

During September, I continued working on the DirectX 12 code, and Janrupf continued working on the Vulkan code. At one point I had the bright idea to make a command buffer-based abstraction over both DX12 and Vulkan. That was a horrible idea, because while DX12 and Vulkan are similar, they aren’t similar enough to have a single interface. Bleh.

In mid-September I decided to convert Nova to a job-based threading model built on top of fibers. This meant that Nova could take advantage of all the cores in your CPU, and they it could do so very efficiently. There were a number of growing pains as we adapted to this new style of programming, but ultimately we learned.

We started working on loading shaderpacks in both Vulkan in DirectX 12. We made a good bit of progress, but Janrupf had to go back to school and my girlfriend came to visit for three weeks (one week in October) so not much happened in the second half of September.

October

I added all kinds of validation and debugging information to the shaderpack loading code in October. Shaderpack developers are going to have to re-learn a lot of things to take full advantage of Nova, and the better the error messages I can give them are, the easier the transition will be. I also spent a lot of time fleshing out the shaderpack loading code for both Vulkan and DirectX 12. Janrupf was busy with school, which meant that the Vulkan code fell on me. The work to load shaderpacks and create the API-specific resources that each one needed was straightforward, if a little tedious.

What was not straightforward was transpiling shaders. Nova accepts shaders in GLSL (which is compiled to SPIR-V) or in SPIR-V. DirectX 12 accepts shaders in HLSL (which is compiled to HLSL bytecode), or HLSL bytecode. Additionally, a few concepts that are crucial to Vulkan shaders have no equivalent in DirectX 12 shaders. This meant a pretty significant amount of work on my part to perform the translation.

Additionally, on October 22 I started a new job. This was pretty cool, but meant that I’d have less time for Nova.

Looking Ahead

Rewriting the Vulkan code and adding in DirectX 12 code will take plenty of time, but it’ll be time well spent. Nova will end up with a well-organized code base that can be easily extended as more and more features get added to the code.

Nova won't be updating to 1.13 until Forge does, and Forge won't work with 1.13 for a while - they want to take some time to improve their code quality. This gives us a lot of time to make Nova amazing before we have to handle all the changes that Minecraft made for 1.13. We're excited to be starting to realize the vision of Nova, and we hope that you're excited, too!


 
 

Nova Update: April 2018

Oops, this one was a little late. We're all going to have to find a way to deal with that.

What happened in April? Fun things! The render graph, which I've been working on for a couple months, is coming together. Get hype!


Materials

As the render graph became more complete, it became obvious that Nova needed a material system of some sort. It needed a way to specify which resources (textures and other data) were bound to each shader.

I ended up going with a simple dictionary-like thing. You use the uniform names from your shader and assign the name of a resource to each uniform. I wrote up a wiki page that gives you an overview of this system.


Putting it all together

With the render graph in place, and the material system in place, Nova should finally run!

Except there were a number of bugs and errors, which is to be expected when one writes a large block of code without running it. A few days of bug-fixing, and we finally have some images, including one very nice image of the GUI!

That tweet was also retweeted by the offical Vulkan Twitter acount, which isn't a huge achievement but it did give us some nice publicity.


Textures

The GUI is pink! Oh no! I hadn't made the code to get textures working yet - so that's what I worked on next. It was kinda painful; since the way that Vulkan handles textures is very different from how OpenGL handles them, but after a couple days I got them sorted. Behold!

That's cool. The render graph is now mostly working!

You'll notice that there's no text on the GUI. Nova is issuing the draw call for the text, but nothing renders because the font is using the GUI texture atlas, not the font texture atlas. This issue will be solved when I get virtual textures in, but for now I have to make a new material for GUI text. I was hoping I wouldn't have to do that, but here we are.


Swapchain Management

In OpenGL, everything relating to the swapchain is handled by the driver. You write to framebuffer 0 from a shader, tell the windowing system to swap the backbuffer with the frontbuffer, and there you go. In Vulkan, however...

If you want to write directly to the swapchain, you have to create a framebuffer for each swapchain image. A framebuffer needs to be created with a reference to the renderpass that uses it, so I would have had to create (and manage) one framebuffer per swapchain image per renderpass that outputs to the swapchain directly.

That doesn't appeal to me. Instead, I'm adding a step after rendering is completed to copy the data from the texture named "Backbuffer (which is created by the render graph) into the current swapchain image. No more need to deal with a ton of framebuffers! Yay!

Unfortunately, I had originally set up the code to assume that there was only one swapchain image...which is in no way correct. Changing to the way I want to do this has proved less than trivial, as a quick glance at Nova's commit log will tell you. Still, it's happening.


The Future

Where does Nova go from here?

The render graph still needs more testing. I've only seen the GUI rendered with it - will it handle chunks? It better! There's definitely more work to be done with the render graph to ensure that it's robust enough for usage by shader devs at large. I know a few Minecraft shader devs - they will take whatever system I give them and push it far past any reasonable limits. It's my job to make sure pushing it that far doesn't break it.

As part of that, I need to get the scripting system set up. The scripting will tie in with the render graph - you'll be able to write Lua scripts that are executed before, during, and after each render pass. This will let shader devs perform work on the CPU, such as calculating custom uniform values or only running certain passes every few ticks. The scripting system will be what really pushes Nova past other Minecraft shader implementations.

I'm not going to stop there, however. After scripting comes virtual textures, which is essentially a way to load a texture in at runtime in a memory-efficient way. This technology was originally developed by id Software for Rage, then improved for Wolfenstein: The New Order and DOOM 2016. It's also shown up in Far Cry 4, Battlefield 3, and associated sequels. This will vastly change how textures work in Minecraft. Rather than loading all textures into a single atlas texture, they'll be loaded as needed. If you only have one type of block on screen, only one block texture will be loaded. This will vastly decrease memory usage, allowing for larger resource packs than are currently feasible. Combined with texture compression, this may make 2k resource packs usable by consumer graphics cards

This is all great, but Nova will still be running on a single core. Sometime around virtual textures, I'll be implementing a task-based threading system. This will allow Nova to scale with the available CPU - buying a processor with more cores will make Nova faster. It should also vastly reduce frame time, further improving what will already be a very solid renderer.

I haven't forgotten about Forge either. The plan is still that, once Forge for Minecraft: Java Edition 1.13 becomes available, Nova will move from modifying the Minecraft client directly to injecting through Forge. I fully expect that to be absolute hell, given how many changes Forge makes to Minecraft. There's a possibility it won't be worth the effort - although, considering the immense value the Forge provides to Minecraft modding, that's pretty unlikely.

And that's just what's on my plate! In the last couple of weeks Nova has had a couple people start working on setting Nova up for CI/CD (Continuous Integration/Continuous Delivery). There's a number of benefits to CI/CD, including that Nova could be compiled on a server somewhere and not on an end-user's computer like it is now. Licensing with the Minecraft client prevents us from distributing more than the C++ part of Nova in a per-compiled format, but once Nova moves to Forge we should be able to distribute the whole thing as a compiled Forge mod, which Forge will then inject into the Minecraft client. That's where the fun begins.


In closing...

That's where Nova is now, and that's the next few major tasks for Nova. As always everything is subject to how much time I have and how many other developers are working on Nova at any given point in time - you'll notice that I haven't given any estimates for when things might be done - but all those big tasks are probably a few months of work each for a single developer. If you're willing to help, we could deliver features much faster!