stephen-hqxu / superterrainplus

SuperTerrain+: A real-time procedural 3D infinite terrain engine with geographical features and photorealistic rendering.
MIT License
12 stars 1 forks source link

It's time to move from OpenGL on to Vulkan #60

Open stephen-hqxu opened 1 year ago

stephen-hqxu commented 1 year ago

Although I have been working with OpenGL for a couple of years and am pretty familiar with it, there are many aspects that I would consider that GL is outdated and awkward to use with a modern programming language like C++20, even with the aid of vendor-specific extensions. The effort that takes to port the whole project to a new API is considerable, which might take half a year or more based on my current workload.

I am deciding if I should port the project to VK along with upgrading it to C++20, so they can both get released in version 1.0 together.

The downside of GL

The most unsatisfying experience with GL is the concept of global state. Despite there are extensions adding supports for bindless texture and buffer, many settings such as depth and stencil buffering are still bound to the context rather than an individual drawing program, which beings extra works as a developer to keep track of the current context state and to ensure there is no state leakage, specially when working with an OOP language. Noticed that there is an extension to bind these states to a rendering pipeline, but this extension is quite old and doing so requires use of compatibility profile, which I want to avoid.

In addition, the interoperation between GL and CUDA isn't that good either. CUDA can only map GL texture to a cudaArray_t, which can only be used via cudaTextureObject_t. As there is no way to force GL to allocate a pinned device memory, the memory address might change every time a GL texture is mapped, which is inconvenient since I need to create/destroy cudaTextureObject_t every frame, plus the implicit synchronisation bottleneck before destruction to wait for the CUDA kernel to finish. There is a way to import external memory into GL via virtual shareable device memory, but however GL organises the texture memory internally is completely unknown, so CUDA doesn't know the alignment and memory structure requirement when allocating memory, therefore an intermediate graphics API must be used^gl_cuda_interop.

Nevertheless, despite I have no performance issue working with GL, it is too high-level to add support for multithreading, memory and context manipulation, which makes writing a game engine very tedious.

Switch to Vulkan

The reasoning is pretty obvious to use VK, all aforementioned downsides are solved with a low-level graphics API. As a side note, I am not choosing DX12 mainly because I want the engine to be portable, say on Linux.

Additionally, VK SDK provides an object wrapper for C++, for which I can simplify my engine by a huge amount and remove all custom GL object wrappers.

More advanced features

There are a few features that can be used when using VK:

First, I am thinking to transfer the current ray tracing code from OptiX, as planned and described in #41, to VKR. Although OptiX is more powerful, I don't feel like I need that much power, and VKR suffices for a game engine. It is also easier to pass resources around (like buffer and texture memory) between rasterisation and ray tracing shaders, without going through CUDA.

Second, debugging shader code on VK is much easier than on GL. There are built-in tools in VK SDK for debugging.

Last but not least, I am planning to integrate DLSS^dlss into the engine, specially for the ray tracing part of the engine. (Why not? It is fancy!)