-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add vulkan renderer to Craft #222
base: master
Are you sure you want to change the base?
Conversation
The versions of glfw and glew included in the deps directory are out of date and unable to handle vulkan and spir-v. By using the system versions, we can leverage these newer features.
This enables all warnings as they can be very helpful. To avoid existing warnings muddling the results, they are fixed. Most were unlikely to produce real problems. The exception is the lack of a parameter in db_worker_start() a strange "feature" of C.
To facilitate replacing GL calls with Vulkan equivalents, functions that call GL code or are closely related to those that do are moved into glrenderer files. A few functions had their prototypes changed to remove depdendencies on types that are exclusive to main.c. Attrib is closely related to the renderer, so glrenderer takes ownership of it. GL calls in functions that are not to be moved have been left for now, but will be moved in a later change.
I have attempted to maintain the implicit style included in this project. However, I don't feel that a complete lack of comments is a style. Since I intend to document my later contributions to the renderer with extensive comments, I think it would be more consistent if the previous code was similarly documented. I'm not going to document the entire project, but this adds guidance for the portions relevant to rendering
To allow the same controlling code in main to perform the same rendering functions by passing around buffer objects, this abstracts the representation of the buffer object away as a pointer to a partially defined struct. In the case of GL, this just contains an integer identifier. For Vulkan, it will carry a lot more.
UBOs are required for SPIR-V. This converts the list of uniforms in each shader to a uniform block that is backed by a buffer object that is updated accordingly. This requires a version bump in the shaders. Samplers are an exception and they continue to be separate. Since UBO representation differs by API, the UBO is wrapped in a Uniform interface object that will eventually include the samplers as well. The uniform interface object is bound alongside the attrib object.
First a disclaimer. I did this project for my own benefit to better familiarize myself with Vulkan and porting to it. That said, I would be perfectly content for it to be accepted into the main branch, but I am not as insistent as the volume of the changes might suggest. If any part of this is appealing, please feel free to take what you want and leave the rest. I'd be happy to continue collaborating as well. Uploaded at @chaoticbob's suggestion. |
Collects texture information into an Image object and sampler information into a Uniform object. Both are bound through a bind_pipeline call that takes the pre-existing Attrib object as well. Shortly, the samplers in the uniform interface object will be joined by UBOs to encompass the other uniforms. Additionally, texture samplers are now bound explicitly and tightly within the shaders themselves. To use this feature, the version had to be bumped to 420. This is among the alterations needed to compile them to Spir-V
Spir-v requires explicit locations. Additionally, this allows avoiding the location and binding queries for shader attributes and uniforms that were used before. To use the explicit binding feature, version 420 of the shading language is required. Also rename attribute and varying keywords to the more modern in/out qualifiers and eliminate the builtin color output variable. As a side effect, the attrib object is not needed for many drawing functions. So it's removed from them.
Since all that's left in what was the Attrib object is a program, it really doesn't make sense to call it that anymore. It will be used as a program/pipeline object going forward. So it's named Pipeline. Additionally, the struct has been rendered undefined outside the renderer code so it's contents are API depdenent.
A few OpenGL calls remained outside glrenderer.c. This creates a renderer_init function to perform the context initialization not covered by glfw. Clears and viewport sets are also abstracted and line-related gl calls are moved into the draw_lines function. A small number of GL types were converted to their standard alternatives too Where possible, glfw and glew headers are removed.
Because blending state in Vulkan is immutable and text rendering and sign rendering use different blending states, they will need to be separated. Its ubo was needlessly distinct due to a single boolean that split the behavior of the fragment shader in almost every respect. It makes more sense for them to be split.
Load spir-v precompiled shaders instead of glsl. Add rules to cmake file to generate spir-v output when glsl changes. A few more changes to the shaders were required. All locations had to be explicit and some deprecated functionality was removed.
Most buffers are created and destroyed immediately before and after they are used for rendering. This is functional, though inefficient for OpenGL, but it causes more serious problems in vulkan due to its greater asynchronicity. To accomplish this, the wireframe vertex buffer lost its position information in favor of adding it to the model matrix at render time. For crosshairs, the dimensions of the frame are extracted a little earlier and used to determine its location. Instead of allocating buffers for item rendering on demand, since the location and geometry doesn't change, the buffers can be precreated and reused as many times as needed
It's a lot easier to deal with per frame text buffers when they are all together. All it takes is a little text manipulation and deferring the final buffer creation.
Rather than delete each buffer after its done and recreate it, we can create dynamic buffers for things with attributes that change rapidly. With them, some update functions that preserve the backing store of the VBO and just update the contents. This describes what is done currently, with vulkan, they will consist of multiple objects, one for each frame in flight, but the interface will be the same.
The only 2D lines were the crosshair, which is trivial to make 3D. Doing so helps resource reuse in vulkan so crosshairs and wireframe boxes can use the same pipeline.
It's all led up to this! This adds the vulkan rendererer that implments the renderer interface previously implemented by OpenGL. It introduces a few tweaks to that interface as well. Additional entry points for the beginning and end of the frame are added and calls to finalize rendering and destroy the renderer state. The prototypes of a few existing calls have had information added because it's needed by vulkan. The cmake file now has a VULKANIZE flag that links appropriate libraries as well as the appropriate *renderer object and sets defines that change a few lines of code in source files. It would be extremely difficult to give much detail about the vkrenderer added here. As for broader design principles, there are a number of cases where flexibility is consciously limited based on the requirements of the application. This includes formats and layouts being explicitly specified and also the limitations of what extensions and features can be enabled. In general, the interface is still based on GL and so there is some sense of a minor GL on vulkan vibe. The preceding changes to the renderer interface were made to allow for this kind of abstraction. In general, the established practice of naming creation functions gen_* and destruction functions del_* is maintained. Internally, create_* is used to create internal representations and anything that is not an API function is made static and thrown inline. I considered hiving off a lot of the internal code to another module and making vkrenderer a translation layer, but I didn't feel like designing another API. To a large degree, the renderer centers around the usual standard objects that any vulkan app might have, instance, device, queue, render pass, swapchain, and various per-frame objects that amount to framebuffers and command buffers along with sync objects to keep it all in line. The user-controlled objects are exactly as they were for GL, Images (textures), Buffers, Uniforms, and Pipelines. The one with the greatest difference is of course the pipeline, which contains a lot more than GL programs do. I expect that's pretty standard though. Image is a bit broader than just textures, but externally, that's its only use. Buffers have the most gotchas. The app previously created and deleted them a lot and that's a problem if you don't have OpenGL around to keep track of when its safe to actually delete them. Uniforms are just per-frame descriptor sets and UBOs.
The first part of this is to implement setting the viewport, which was deferred before. This means that the viewport must be dynamic for every pipeline. Okay. not every one, but it's easier to make them the same. Much of this involves adding uniforms for the portions of the scene that are rendered in the split screen. Since it is derived from a different player, the values are entirely independent and need their own uniforms, but the pipelines can be reused. The text buffer is considerably lesser on the split screen, but it's simpler and not terribly inefficient to use the same mechanism. Like so many others, player vertex buffers were getting created and deleted on a per-frame basis. This converts them to dynamic buffers and updates them instead of recreating them. A side effect of this is that the CPU finally caught up with the v-synch and revealed that I left out a fence wait at start_frame.
There are a few kinds of vertex buffers that have been treated differently. Some are the same throughout and they are created at startup and deleted only at termination. Some are more volatile, but have constant size or have a reasonable upper bound. So those are made dynamic. Chunk buffers aren't quite like either. They stay the same for the most unless you change the landscape. Then they are completely destroyed and recreated, as is this program's wont. They have to be allowed to change, but can't be made dynamic because of the size changes and large size. So I gave up and reimplemented the GL driver approach. Whenever any buffer is deleted, it's added to a frame-specific queue. It is only deleted when that frame index is encountered again, ensuring that the command buffer also associated with that frame will have completed so any buffers that were last used in that frame can safely be destroyed.
Abstract the rendering interface of Craft and add an OpenGL and then a Vulkan implementation. This also converts the shaders to spir-v that is used for both. The Vulkan port required a later version of GL and thus a later version of glfw and glew, which was acquired by adding a system dependency on them.