Skip to content

Add Vulkan rendering backend (new)#7553

Closed
notimaginative wants to merge 72 commits into
scp-fs2open:masterfrom
notimaginative:vulkan-pr-new
Closed

Add Vulkan rendering backend (new)#7553
notimaginative wants to merge 72 commits into
scp-fs2open:masterfrom
notimaginative:vulkan-pr-new

Conversation

@notimaginative

Copy link
Copy Markdown
Contributor

An updated version of #7233 that's current with master and includes all of the fixes from comments in that PR.

Original work by laanwj, with additions from SamuelCho, The-E, and myself.

laanwj added 30 commits June 30, 2026 11:21
Move the pure-math vertex/index generation out of `gropengldeferred.cpp`
into graphics/util/primitives so it can be reused by the Vulkan backend.
Modernize to use `SCP_vector` instead of `vm_malloc`/`vm_free` for automatic
memory management.
Replace direct `ImGui_ImplOpenGL3` calls in game code with
backend-agnostic `gr_imgui_new_frame` and `gr_imgui_render_draw_data`
function pointers, matching the pattern used by all other `gr_*`
functions. This makes it possible for the Vulkan backend
to provide its own ImGui implemantation.
`bm_close` calls `gf_bm_free_data` for each bitmap slot, which needs the
graphics backend (Vulkan texture manager, OpenGL context) to still be
alive. Move `bm_close` before the backend cleanup switch in `gr_close`.
`gr_flash_internal` used int vertices with `SCREEN_POS` (`VK_FORMAT_R32G32_SINT`)
but the default-material vertex shader expects vec4 float at location 0.
OpenGL silently converts via glVertexAttribPointer; Vulkan requires exact
type matching. Use float vertices with `POSITION2` format instead. There
should be no difference in behavior.
The `SCREEN_POS` vertex format is no longer used after the only use in
`gr_flash` was removed. Remove it entirely.
Deduplicate compressed texture block-size mapping and mip-size
calculation into two inline helpers in `ddsutils.h`, replacing
repeated inline formulas in `ddsutils.cpp` and `gropengltexture.cpp`.
Add a render system capability to indicate whether GPU timestamp query
handles can be immediately reused after reading.

When queries are not reusable, `free_query_object` returns handles to the
backend via `gr_delete_query_object` instead of the tracing free list,
letting the backend manage its own reset lifecycle. This greatly
simplifies query management for Vulkan.

Also change shutdown to discard gpu_events for backends where queries
aren't reusable (no more frames will be submitted to make them
available).
Move `output_uniform_debug_data` before `gr_reset_immediate_buffer` so
debug text is rendered while the immediate buffer still contains valid
data. The previous ordering read from a buffer that was already reset to
offset 0, which is logically wrong for any backend and a hard failure
for deferred-submission backends.
`gr_set_proj_matrix` already branches on rendering_to_texture to choose
top-left (RTT) vs bottom-left (screen) viewport origin. `gr_end_2d_matrix`
should match, but it unconditionally used the bottom-left formula. Add
the same `rendering_to_texture` branch so the viewport is restored
correctly when rendering to a texture.
Change `bool clipEnabled` to `uint clipEnabled` in the default-material
shader UBO. GLSL bool has implementation-defined std140 layout; uint is
portable and matches the SPIR-V decompiled output.

Add an else-branch writing `gl_ClipDistance[0] = 1.0` when clipping is
disabled. Without this, gl_ClipDistance is undefined and some drivers
cull geometry unexpectedly.
Memcpy from a `const void*` to `void*` is trivial enough. However, this
case was missing, resulting in a false positive compilation error.
Extract shader loading and preprocessing (include/predefine
expansion) into code/graphics/shader_preprocess.cpp, so it
can be shared with the Vulkan backend.
Bundle Vulkan headers (v1.4.309).
Bundle Vulkan Memory Allocator (v3.2.1).
ddsutils.cpp checked OpenGL-specific GLAD globals to decide whether to
decompress DXT textures. When the Vulkan backend was active these
variables were never set, so all DXT textures were decompressed to
32bpp RGBA.

Replace the GLAD checks with gr_is_capable() queries for the new
CAPABILITY_S3TC and existing CAPABILITY_BPTC, making ddsutils
backend-agnostic. Add the S3TC capability handler to the OpenGL backend.
Extract shader type tables (filenames, descriptions) and
variant tables (type, flag, define, description) into shared
code/graphics/shader_types.{h,cpp}.

Also move FXAA quality preset defines into shader_types so both
backends can share a single implementation.
Implement a Vulkan 1.1 renderer that replaces the previous stub with a
fully functional backend, mostly matching the OpenGL backend's rendering
capabilities.

Core rendering infrastructure:

- `VulkanMemory`: Custom allocator with sub-allocation from device-local and
  host-visible memory pools
- `VulkanBuffer`: Per-frame bump allocator for streaming uniform/vertex/index
  data (persistently mapped, double-buffered, auto-growing)
- `VulkanTexture`: Full texture management including 2D, 2D-array, 3D, and
  cubemap types with automatic mipmap generation and sampler caching
- `VulkanPipeline`: Lazy pipeline creation from hashed render state, with
  persistent VkPipelineCache
- `VulkanShader`: GLSL shader loading. Shader code and metadata are
  shared with OpenGL, with differences guarded by preprocessor
  conditions
- `VulkanDescriptorManager`: 3-set descriptor layout (Global/Material/PerDraw)
  with per-frame pool allocation, auto-grow, and batched updates
- `VulkanDeletionQueue`: Deferred resource destruction synchronized to
  frame-in-flight fences

Design choices:

- Two frames in flight with fence-based synchronization
- Asynchronous texture upload, no `waitIdle` in hot path
- Single command buffer per frame; render passes begun/ended as needed
  for the multi-pass deferred pipeline
- Per-frame descriptor pools
- All descriptor bindings pre-initialized with fallback resources (zero
  UBO + 1x1 white texture) so partial updates never leave undefined state
- Streaming data uses a bump allocator (one large VkBuffer per frame)
- Pipeline cache persisted to disk for fast startup on subsequent runs
- Use VMA (Vulkan Memory Allocator) for buffer management

Some notable Vulkan vs OpenGL differences are:

- Depth range is [0,1] not [-1,1]: shadow projection matrices adjusted,
  shaders that linearize depth need isinf/zero guards at depth boundaries
  where OpenGL gives finite values
- In Vulkan, all shader outputs must be initialized. Leaving them
  uninitialized can result in random corruptions, while
  OpenGL allows leaving them in some cases
- Swap chain is B8G8R8A8: screenshot/save_screen paths swizzle to RGBA
- Vulkan render target is "upside down", y-flip for render target is
  handled through negative viewport height, as is common
- Texture addressing for AABITMAP/INTERFACE/CUBEMAP forced to clamp
  (OpenGL's sampler state happens to do this implicitly)
- Render pass architecture requires explicit transitions between G-buffer,
  shadow, decal, light accumulation, fog, and post-processing passes
  (OpenGL just switches FBO bindings)
Include shadows.sdr unconditionally in main-v.sdr and main-f.sdr
(was guarded by #ifndef VULKAN / #ifdef OPENGL). Add shadowUV[4]
and shadowPos varyings to Vulkan's VertexOutput. Add shadow_map
sampler to Vulkan's fragment declarations. Remove #ifndef VULKAN
guard around forward shadow getShadowValue() call. Unify shadow
depth write to use VARIANCE_SHADOW_SCALE_INV for both backends.
Write the real shadow map texture to Global Set 0 Binding 2 during
model draw calls (was always fallback). Enables forward-pass shadows
for the Vulkan backend.
Cloak effect: declare sFramebuffer (scene color copy) at Set 1
Binding 5 in the Vulkan model fragment shader. The texture was
already bound by VulkanDraw.

Lightshaft cockpit mask: declare cockpit sampler at texture array
element 1 in Vulkan lightshaft shader. Currently samples a white
fallback (no cockpit depth isolation yet), matching existing Vulkan
behavior but unifying the shader code.
Cleaner, and avoids accidentally leaving holes
This is implemented differently in different places. And was missing in
others.
Replace the bare texture view getters with ready-made structures.
@notimaginative notimaginative added this to the Release 26.2 milestone Jun 30, 2026
@notimaginative notimaginative added graphics A feature or issue related to graphics (2d and 3d) Waiting for Stable Marks a pull request that is to be merged after the next stable release, due to a release cycle vulkan Issues and Features related to the vulkan render backend labels Jun 30, 2026
@notimaginative

notimaginative commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Known issues remaining:

  1. LOTS of debug spew. A graphics coder should probably go over all of the debug spew to determine what is actually useful going forward and either remove the rest or lock it behind a nprintf(("vulkan", ...))

  2. Lines render differently between the Vulkan and OpenGL backends. This affects briefings as well as the starfield

vk_bug_lines
  1. A texture flicker (z fighting??) can be seen when ships move, particularly noticeable when ships are rendered without a light source (Lab) Edit Lafiel: Fixed
vk_bug_tex_flicker.mp4
  1. Texture corruption can sometimes be seen in missions. This is most easily reproduced within the first minute or so playing massive_battle2.fs2
vk_bug_tex_corrupt.mp4
  1. Major rendering issues when MSAA is enabled. This has been seen on both Mac and Linux, though it presents a bit differently on each platform (WARNING: Bright flashing lights! DO NOT WATCH if you are sensitive!!) Edit Lafiel: Likely Fixed, needs testing on non-AMD
vk_bug_msaa.mp4
  1. Full nebula missions appear to lack of fog and poofs are colored strangely (limited testing so far) Edit Lafiel: Fog is Fixed, Poofs are likely fixed

@BMagnu BMagnu removed the Waiting for Stable Marks a pull request that is to be merged after the next stable release, due to a release cycle label Jul 5, 2026
The-E pushed a commit to The-E/fs2open.github.com that referenced this pull request Jul 5, 2026
Squashed snapshot of the Vulkan backend as vendored into this branch
(ce2d1e8..6ce5394, authored by Mara van der Laan with build/CI
fixes by Taylor Richards). This work has its own upstream PR scp-fs2open#7553
(notimaginative:vulkan-pr-new) and is not otherwise original to true-hdr.

@BMagnu BMagnu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the set of issues I found looking at all code that interfaces with existing code. I did not yet look at the fully new vulkan files in detail.
To start with, a rebase on master is likely required, as fixing the shadows issue for example is unfeasible otherwise.
A second round for looking at the vulkan code is coming soon.


vec3 coneDir;
bool dualCone;
int dualCone;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uniform_structs.h defines this one as float.
I agree with making them int here, but they should also be int in the uniform header then

Comment on lines +473 to +479
#ifdef VULKAN
// Vulkan has undefined values for unwritten outputs; zero-init G-buffer outputs
fragOut1 = vec4(0.0);
fragOut2 = vec4(0.0);
fragOut3 = vec4(0.0);
fragOut4 = vec4(0.0);
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that deferred is the expected use case, this should probably be in a

#prereplace ELSE_FLAG //MODEL_SDR_FLAG_DEFERRED

so that we don't double write when we don't need to.

Comment on lines +6 to +8
#if defined(VULKAN) && defined(MODEL_SDR_FLAG_SHADOW_MAP)
#extension GL_ARB_shader_viewport_layer_array : enable
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not necessary. The viewport_layer_array flag is exclusively for writing shadow maps, not reading, so it should be removed here.

Comment on lines +213 to 232
#ifdef VULKAN
#ifdef MODEL_SDR_FLAG_SHADOW_MAP
gl_Position = shadow_proj_matrix[gl_InstanceIndex] * position;
gl_Position.z = clamp(gl_Position.z, 0.0, gl_Position.w);
gl_Layer = gl_InstanceIndex;
#else
gl_Position = projMatrix * position;
#endif

// Clip invisible submodels by moving vertices off-screen
#prereplace IF_FLAG MODEL_SDR_FLAG_TRANSFORM
if (clipModel) {
gl_Position = vec4(-2.0, -2.0, -2.0, 1.0);
}
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_TRANSFORM
#else
#ifdef MODEL_SDR_FLAG_SHADOW_MAP
gl_Position = position;
#if !defined(GL_ARB_gpu_shader5)
#ifdef APPLE

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire section is incorrect and obsolete once rebased. MODEL_SDR_FLAG_SHADOW_MAP is no longer queried at all in main-* (and should probably be removed outright), so it also does not need a Vulkan path here. This must instead be handled in shadow_map-*.sdr, which seems like it hasn't been created on this branch yet (due to improper integration of the PCSS shadows PR).


layout (set = 2, binding = 2, std140) uniform NanoVGUniformData {
mat3 scissorMat;
mat3 paintMat;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not repeat the entire uniform and instead use the same pattern of just modifying the layout block like the other shaders

Comment on lines +4 to +14
#ifdef VULKAN
layout(location = 0) in vec4 fragTexCoord;
layout(location = 1) in vec4 fragColor;
layout(location = 0) out vec4 fragOut0;

layout(set = 1, binding = 1) uniform sampler2DArray baseMap;

layout(std140, set = 2, binding = 0) uniform genericData {
mat4 modelMatrix;
vec4 color;
vec4 clipEquation;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vulkan specific code in this shader is moot. This shader is only used by legacy components of the OpenGL pipeline, and thus should not even be compiled when running vulkan, let alone have custom code or an unused new struct

Comment on lines +1 to +15
#ifdef VULKAN
layout(location = 0) in vec4 vertPosition;
layout(location = 1) in vec4 vertColor;
layout(location = 2) in vec4 vertTexCoord;
layout(location = 0) out vec4 fragTexCoord;
layout(location = 1) out vec4 fragColor;

layout(std140, set = 2, binding = 0) uniform genericData {
mat4 modelMatrix;
vec4 color;
vec4 clipEquation;
int baseMapIndex;
int alphaTexture;
int noTexturing;
int srgb;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Comment thread lib/prebuilt.cmake
@@ -1,5 +1,5 @@

set(PREBUILT_VERSION_NAME "21d0b52")
set(PREBUILT_VERSION_NAME "4fbde4a")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to be modified and doubly verified with the Qt6 PR

The-E pushed a commit to The-E/fs2open.github.com that referenced this pull request Jul 7, 2026
Squashed snapshot of the Vulkan backend as vendored into this branch
(ce2d1e8..6ce5394, authored by Mara van der Laan with build/CI
fixes by Taylor Richards). This work has its own upstream PR scp-fs2open#7553
(notimaginative:vulkan-pr-new) and is not otherwise original to true-hdr.
@notimaginative

Copy link
Copy Markdown
Contributor Author

This PR has been superseded by #7554 which incorporates this PR amongst many other changes. Future work and comments should be directed there.

This vulkan-pr-new branch will continue to exist for the foreseeable to act as a reference for the Vulkan changes.

The-E pushed a commit to The-E/fs2open.github.com that referenced this pull request Jul 7, 2026
Squashed snapshot of the Vulkan backend as vendored into this branch
(ce2d1e8..6ce5394, authored by Mara van der Laan with build/CI
fixes by Taylor Richards). This work has its own upstream PR scp-fs2open#7553
(notimaginative:vulkan-pr-new) and is not otherwise original to true-hdr.
The-E pushed a commit to The-E/fs2open.github.com that referenced this pull request Jul 12, 2026
Squashed snapshot of the Vulkan backend as vendored into this branch
(ce2d1e8..6ce5394, authored by Mara van der Laan with build/CI
fixes by Taylor Richards). This work has its own upstream PR scp-fs2open#7553
(notimaginative:vulkan-pr-new) and is not otherwise original to true-hdr.
The-E pushed a commit to The-E/fs2open.github.com that referenced this pull request Jul 14, 2026
Squashed snapshot of the Vulkan backend as vendored into this branch
(ce2d1e8..6ce5394, authored by Mara van der Laan with build/CI
fixes by Taylor Richards). This work has its own upstream PR scp-fs2open#7553
(notimaginative:vulkan-pr-new) and is not otherwise original to true-hdr.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

graphics A feature or issue related to graphics (2d and 3d) vulkan Issues and Features related to the vulkan render backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants