nova

Nova Update - November 2018

Hello everyone, please join me for the November 2018 update for Nova!

As mentioned in the last blog, I started a new job in late October. Much of my time in November has been taken up by that job, which is just how things go sometimes.

The first work I did in November was getting the DirectX 12 render backend’s shader loading working nicely. Nova converts all shaders to SPIR-V before sending them to any render backends, so I needed to convert from SPIR-V to DirectX 12 HLSL. Doable, but incredibly non-trivial - I spent most of a week on the conversion code, even though I was able to use SPIRV-Cross to handle most of the translation.

I then started working on loading and rendering meshes - the core part of Nova. Nova will use indirect rendering to draw meshes, which means that I needed to put meshes into as few buffers as I could. I spent a couple weeks making a mesh allocator. I actually tried a few different ideas before settling on a block allocator that can compact its allocations if needed - if there’s enough total free space in the buffer for a new allocation, but no blocks are large enough, the allocator will move data towards the front of the buffer until all free space has been filled.

Once mesh memory was allocated, it was time to render meshes. This is the crux of Nova, and I expect it to take a long time to get right. One of the major challenges is how to make rendering things multithreaded. I definitely want Nova to be able to take full advantage of all cores, but how to do that while staying sane is…difficult. At the time of writing this blog post I’m making a command buffer for each VkPipeline and synchronizing them…somehow. There’s a couple of unknowns in there.

Additionally, near the middle of November I decided to leave Continuum Graphics. For a while now I have been responsible for maintaining the server infrastructure, and unfortunately that took far too much time away from Nova, especially with working a full-time job. Nova needs all my free time, and now it can have it. There are no hard feelings between me and the Continuum team, and we are all still friends. This is just a time management thing.

My plans for Nova have not changed in the slightest. I’ll still be working tirelessly to bring everyone the best possible general-purpose renderer, and to integrate it into Minecraft and eventually other games. Leaving Continuum Graphics has given me the time to make that happen.

I won’t be making blog posts about Nova any more. The best way to keep up with development of Nova is to join the Nova Discord server, where I hang out pretty regularly. There’s also a Twitter account, but I mostly use that for big announcements. I’ll still be hanging out in the Continuum Discord as well.

That brings us to the end of the final Nova progress update on the Continuum Graphics blog. Everyone’s support for my project has been incredible, and I only hope that I can deliver something that meets your expectations - Dethraid



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: February 2018

segfault at vkResetFences

That's the error message that's greeted me for the last few weeks. There was a segfault, which is a portmanteau of segmentation fault - a program has tried to access a segment of memory that it shouldn't have. Usually your debugger can take you to the line where the segfault happens and you'll see what you did wrong - Are you accessing an array element that doesn't exist? Did you forget to initialize something? Segfaults are annoying, but usually they can be squashed in development.

Except for now.

The debugger gave me a location where the error was happening - deep within the Vulkan loader. It said that the error happened in the function vkResetFences, but I didn't call that from anywhere within Nova. Something strange was happening.

I tried a lot of things to learn more about this error. I tried compiling the Vulkan loader myself, tried compiling Nova with Visual Studio, tried commenting out large swaths of code until the error went away so isolate the offending line - many hours were spent cursing at my compiler. I learned a few interesting things, such as that the Vulkan loader doesn't support being compiled with MinGW, and that Nova doesn't like being compiled with Visual Studio, and that the error wasn't necessarily happening in the function vkResetFences, but since I had a release build of the Vulkan loader then gdb wasn't reporting the offending stack frame accurately. This was all rather interesting and would probably be useful, but was not useful now.

I cast a wide net online, reaching out for any help I could get. Some people suggested I was smashing my stack, but that didn't seem likely - stack smashing seems to be super rare and difficult to actually do. I looked into debugging a Vulkan application, and realized that I had never gotten a Vulkan debug callback working.

I'm using vulkan.hpp, a C++ wrapper around Vulkan. It's a thin wrapper around the Vulkan API - sometimes too thin. Vulkan debug callbacks, in which you give Vulkan a callback to call when it has something to say about how badly you're misusing the API, is a Vulkan extension, and it isn't loaded very well by vulkan.hpp. Google gave some example code to load the debug callback extension, I gave Vulkan a callback to print errors, and... the callback printed out many errors. Whoops.

In OpenGL, I never had to clean up objects when my program shut down - that was all handled by the API. In Vulkan, however, you do have to clean up after yourself. This information had slipped by me. I went through all my code and added the statements to destroy things I wasn't using, and Nova stopped crashing. When you use the API correctly, your program won't break. Who knew?

...but now it's time for OpenGL

I spent four months working on converting Nova to Vulkan. I got probably 2/3 of the way there, but I kept running into more and more problems. The tale above is just one of them: turns out learning a new graphics API all at once is pretty challenging. I took a step back and decided that I couldn't justify spending another four months getting Vulkan Nova working, not with Bedrock shaders right around the corner and not with all the features Optifine keeps adding. If I had any hope of being competitive, I needed to get a working renderer together as soon as possible... and I already had a working renderer, one that used OpenGL.

The first task was bringing in all the improvements made in the conversion to Vulkan. I made a number of tweaks to the build scripts and wrote a loader for Bedrock materials, and those changes had to be brought in.

Data-Driven Render Passes

During the time I spent working on Vulkan, I came across an article titled Render Graphs and Vulkan: A Deep Dive. The article talked about a data-driven renderer that gives a great deal of flexibility with regards to how a renderer processes a scene, and I realized that it was exactly what I wanted. The author of that article had solved a number of problems I was struggling with, so why not use their solutions?

I decided to upgrade Bedrock's material system to use render passes while bringing in the Bedrock material file loader. The render passes would form the backbone of Nova's data processing algorithms, so I definitely wanted to have that system in place as early as possible.

Bugfixes and Improvements

We haven't only been developing new tech, however. Coler706 has been working on bugfixes and improvements to the existing tech. He discovered that the place I had initially hooked into Minecraft for chunk updates wasn't very good, and found a better place so that Nova was able to handle the removal of block and loaded chunks from the player outward, like in vanilla. He's also been expanding the set of GUI screens that Nova can handle, paying special attention to the in-game GUI. The work he's doing has fixed many issues that I would have taken much longer to get to, so I very much appreciate his help.

Thi brings us to today. We have a strong modern OpenGL base to work off of. We're moving ahead strong with implementing the render pass system, constantly improving the data format to be more expressive and easier to use. We're fixing bugs and making improvements that are highly visible to the end user. Nova development in 2018 is moving along very well!