Files
fs2open.github.com/code/graphics/vulkan/VulkanShader.cpp
T
BMagnu 4a35713992 FSR Support (#8019)
* FSR4 libs

* Motion Vectors

* FSR AA

* FSR upscaling

* Cleanup

* Clang Tidy

* Address Feedback

* Improve canonical_prev handling

* Clang Tidy again

* Clean up prev_ submodel motion handling

* Address Feedback
2026-11-13 10:16:24 +09:00

284 lines
8.4 KiB
C++

#include "VulkanShader.h"
#include "VulkanShaderCompiler.h"
#include "VulkanRenderer.h"
#include "gr_vulkan.h"
namespace graphics::vulkan {
// Global shader manager pointer
static VulkanShaderManager* g_shaderManager = nullptr;
static uint32_t vertex_input_mask(const ShaderTypeInfo* typeInfo)
{
uint32_t mask = shader_vertex_input_mask(*typeInfo);
// Batched effects also read their motion, which the attribute lists shared with OpenGL can't hold
if (typeInfo->type_id == SDR_TYPE_BATCHED_BITMAP || typeInfo->type_id == SDR_TYPE_EFFECT_PARTICLE) {
mask |= (1u << VATTRIB_EFFECT_MOTION);
}
return mask;
}
VulkanShaderManager* getShaderManager()
{
Assertion(g_shaderManager != nullptr, "Vulkan ShaderManager not initialized!");
return g_shaderManager;
}
void setShaderManager(VulkanShaderManager* manager)
{
g_shaderManager = manager;
}
// ========== gr_screen function pointer implementations ==========
int vulkan_maybe_create_shader(shader_type shader_t, unsigned int flags)
{
auto* shaderManager = getShaderManager();
return shaderManager->maybeCreateShader(shader_t, flags);
}
void vulkan_recompile_all_shaders(const std::function<void(size_t, size_t)>& progressCallback)
{
auto* shaderManager = getShaderManager();
shaderManager->recompileAllShaders(progressCallback);
getRendererInstance()->requestPipelineRebuild();
}
VulkanShaderManager::VulkanShaderManager() = default;
VulkanShaderManager::~VulkanShaderManager() = default;
bool VulkanShaderManager::init(vk::Device device, bool hwClipDistance)
{
if (m_initialized) {
return true;
}
m_device = device;
// Initialize runtime shader compiler
m_compiler = std::make_unique<VulkanShaderCompiler>();
if (!m_compiler->init(hwClipDistance)) {
nprintf(("vulkan", "VulkanShaderManager: Failed to initialize shader compiler!\n"));
return false;
}
VulkanShaderCompiler::purgeOldCache();
m_initialized = true;
nprintf(("vulkan", "VulkanShaderManager: Initialized with runtime shader compilation\n"));
return true;
}
void VulkanShaderManager::shutdown()
{
if (!m_initialized) {
return;
}
// Clear all shaders (unique_ptrs will clean up)
m_shaders.clear();
m_shaderMap.clear();
m_freeSlots.clear();
if (m_compiler) {
m_compiler->shutdown();
m_compiler.reset();
}
m_initialized = false;
nprintf(("vulkan", "VulkanShaderManager: Shutdown complete\n"));
}
int VulkanShaderManager::maybeCreateShader(shader_type type, unsigned int flags)
{
if (!m_initialized) {
return -1;
}
shader_key_t key(static_cast<int>(type), flags);
auto it = m_shaderMap.find(key);
if (it != m_shaderMap.end()) {
return static_cast<int>(it->second);
}
return loadShader(type, flags);
}
void VulkanShaderManager::recompileAllShaders(const std::function<void(size_t, size_t)>& progressCallback)
{
if (!m_initialized || !m_compiler) {
return;
}
size_t total = m_shaders.size();
size_t current = 0;
for (auto& shader : m_shaders) {
if (shader.valid) {
shader_type type = shader.type;
unsigned int flags = shader.flags;
// Release old modules
shader.vertexModule.reset();
shader.fragmentModule.reset();
shader.valid = false;
const ShaderTypeInfo* typeInfo = shader_get_type_info(type);
if (typeInfo) {
bool requiresRaytracing = shader_variant_requires_raytracing(type, flags);
SCP_string vertFile = typeInfo->vert;
auto vertSpirv = m_compiler->compile(vertFile, vk::ShaderStageFlagBits::eVertex, type, flags, requiresRaytracing);
if (!vertSpirv.empty()) {
vk::ShaderModuleCreateInfo createInfo;
createInfo.codeSize = vertSpirv.size() * sizeof(uint32_t);
createInfo.pCode = vertSpirv.data();
try {
shader.vertexModule = m_device.createShaderModuleUnique(createInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanShaderManager: Failed to create vertex module: %s\n", e.what()));
}
}
shader.vertexInputMask = vertex_input_mask(typeInfo);
SCP_string fragFile = typeInfo->frag;
auto fragSpirv = m_compiler->compile(fragFile, vk::ShaderStageFlagBits::eFragment, type, flags, requiresRaytracing);
if (!fragSpirv.empty()) {
vk::ShaderModuleCreateInfo createInfo;
createInfo.codeSize = fragSpirv.size() * sizeof(uint32_t);
createInfo.pCode = fragSpirv.data();
try {
shader.fragmentModule = m_device.createShaderModuleUnique(createInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanShaderManager: Failed to create fragment module: %s\n", e.what()));
}
}
shader.valid = shader.vertexModule && shader.fragmentModule;
// the inactive anti-aliasing family cannot compile under the current mode, so build it again on next use
if (!shader.valid) {
m_shaderMap.erase(shader_key_t(static_cast<int>(type), flags));
m_freeSlots.push_back(static_cast<size_t>(&shader - m_shaders.data()));
}
}
}
// callers expect 0..total-1, like OpenGL; the retail options popup waits for current + 1 == total
if (progressCallback) {
progressCallback(current, total);
}
++current;
}
nprintf(("vulkan", "VulkanShaderManager: Recompiled %zu shaders\n", total));
}
const VulkanShaderModule* VulkanShaderManager::getShader(int handle) const
{
if (handle < 0 || static_cast<size_t>(handle) >= m_shaders.size()) {
return nullptr;
}
const VulkanShaderModule& shader = m_shaders[handle];
return shader.valid ? &shader : nullptr;
}
const VulkanShaderModule* VulkanShaderManager::getShaderByType(shader_type type) const
{
// Find the first shader of this type (any flags).
// Used for vertex input mask queries where the specific variant doesn't matter.
for (const auto& pair : m_shaderMap) {
if (pair.first.first == static_cast<int>(type)) {
return getShader(static_cast<int>(pair.second));
}
}
return nullptr;
}
int VulkanShaderManager::loadShader(shader_type type, unsigned int flags)
{
const ShaderTypeInfo* typeInfo = shader_get_type_info(type);
if (!typeInfo) {
nprintf(("vulkan", "VulkanShaderManager: Unknown shader type: %d\n", static_cast<int>(type)));
return -1;
}
VulkanShaderModule shader;
shader.type = type;
shader.flags = flags;
shader.description = typeInfo->description;
if (flags != 0) {
shader.description += " (flags=0x";
char buf[16];
snprintf(buf, sizeof(buf), "%x", flags);
shader.description += buf;
shader.description += ")";
}
bool requiresRaytracing = shader_variant_requires_raytracing(type, flags);
// Compile vertex shader
SCP_string vertFile = typeInfo->vert;
auto vertSpirv = m_compiler->compile(vertFile, vk::ShaderStageFlagBits::eVertex, type, flags, requiresRaytracing);
if (!vertSpirv.empty()) {
vk::ShaderModuleCreateInfo createInfo;
createInfo.codeSize = vertSpirv.size() * sizeof(uint32_t);
createInfo.pCode = vertSpirv.data();
try {
shader.vertexModule = m_device.createShaderModuleUnique(createInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanShaderManager: Failed to create vertex module for %s: %s\n",
vertFile.c_str(), e.what()));
}
}
shader.vertexInputMask = vertex_input_mask(typeInfo);
// Compile fragment shader
SCP_string fragFile = typeInfo->frag;
auto fragSpirv = m_compiler->compile(fragFile, vk::ShaderStageFlagBits::eFragment, type, flags, requiresRaytracing);
if (!fragSpirv.empty()) {
vk::ShaderModuleCreateInfo createInfo;
createInfo.codeSize = fragSpirv.size() * sizeof(uint32_t);
createInfo.pCode = fragSpirv.data();
try {
shader.fragmentModule = m_device.createShaderModuleUnique(createInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanShaderManager: Failed to create fragment module for %s: %s\n",
fragFile.c_str(), e.what()));
}
}
// Check if essential modules loaded
shader.valid = shader.vertexModule && shader.fragmentModule;
if (!shader.valid) {
nprintf(("vulkan", "VulkanShaderManager: Failed to load shader type %d (flags=0x%x)\n",
static_cast<int>(type), flags));
}
// Find or allocate slot
size_t index;
if (!m_freeSlots.empty()) {
index = m_freeSlots.back();
m_freeSlots.pop_back();
m_shaders[index] = std::move(shader);
} else {
index = m_shaders.size();
m_shaders.push_back(std::move(shader));
}
// Add to lookup map
shader_key_t key(static_cast<int>(type), flags);
m_shaderMap[key] = index;
if (m_shaders[index].valid) {
nprintf(("Vulkan", "VulkanShaderManager: Created shader %zu: %s\n",
index, m_shaders[index].description.c_str()));
}
return static_cast<int>(index);
}
} // namespace graphics::vulkan