Skip to content
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

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open

Conversation

pow2clk
Copy link

@pow2clk pow2clk commented Jun 19, 2019

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.

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.
@pow2clk
Copy link
Author

pow2clk commented Jun 19, 2019

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant