feat: Add support for additional shader types and enhance shader path handling

This commit is contained in:
2026-01-05 21:38:56 +00:00
parent 748864b656
commit 3f1684ef03
5 changed files with 146 additions and 56 deletions

View File

@@ -37,6 +37,8 @@ class SDL3CppConan(ConanFile):
self.requires("vulkan-headers/1.3.243.0")
self.requires("vulkan-memory-allocator/3.3.0")
self.requires("vulkan-validationlayers/1.3.243.0")
self.requires("spirv-tools/1.4.313.0")
self.requires("spirv-headers/1.4.313.0")
self.requires("cpptrace/1.0.4")
self.requires("ogg/1.3.5")
self.requires("theora/1.1.1")

View File

@@ -284,7 +284,7 @@ def msvc_quick(args: argparse.Namespace) -> None:
def _compile_shaders(dry_run: bool) -> None:
"""
Compile GLSL shaders to SPIR-V format using glslangValidator.
Compiles all .vert and .frag files in the shaders directory.
Compiles .vert, .frag, .geom, .tesc, .tese, and .comp files in the shaders directory.
"""
shaders_dir = Path("shaders")
if not shaders_dir.exists():
@@ -309,7 +309,14 @@ def _compile_shaders(dry_run: bool) -> None:
return
print("\n=== Compiling Shaders ===")
shader_files = list(shaders_dir.glob("*.vert")) + list(shaders_dir.glob("*.frag"))
shader_files = (
list(shaders_dir.glob("*.vert"))
+ list(shaders_dir.glob("*.frag"))
+ list(shaders_dir.glob("*.geom"))
+ list(shaders_dir.glob("*.tesc"))
+ list(shaders_dir.glob("*.tese"))
+ list(shaders_dir.glob("*.comp"))
)
for shader_file in shader_files:
output_file = shader_file.with_suffix(shader_file.suffix + ".spv")

View File

@@ -4,6 +4,7 @@
#include <filesystem>
#include <fstream>
#include <stdexcept>
#include <vector>
namespace sdl3cpp::services::impl {
@@ -248,42 +249,96 @@ void PipelineService::CreatePipelinesInternal(VkRenderPass renderPass, VkExtent2
"\n\nPlease ensure shader files are compiled and present in the shaders directory.");
}
auto vertShaderCode = ReadShaderFile(paths.vertex);
auto fragShaderCode = ReadShaderFile(paths.fragment);
bool hasGeometry = !paths.geometry.empty();
bool hasTessControl = !paths.tessControl.empty();
bool hasTessEval = !paths.tessEval.empty();
VkShaderModule vertShaderModule = CreateShaderModule(vertShaderCode);
VkShaderModule fragShaderModule = CreateShaderModule(fragShaderCode);
VkPipelineShaderStageCreateInfo vertStageInfo{};
vertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertStageInfo.module = vertShaderModule;
vertStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragStageInfo{};
fragStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragStageInfo.module = fragShaderModule;
fragStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = {vertStageInfo, fragStageInfo};
VkGraphicsPipelineCreateInfo pipelineCreateInfo = pipelineInfo;
pipelineCreateInfo.stageCount = 2;
pipelineCreateInfo.pStages = shaderStages;
VkPipeline pipeline;
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr,
&pipeline) != VK_SUCCESS) {
vkDestroyShaderModule(device, fragShaderModule, nullptr);
vkDestroyShaderModule(device, vertShaderModule, nullptr);
throw std::runtime_error("Failed to create graphics pipeline for shader: " + key);
if (hasGeometry && !std::filesystem::exists(paths.geometry)) {
throw std::runtime_error(
"Geometry shader not found: " + paths.geometry +
"\n\nShader key: " + key +
"\n\nPlease ensure shader files are compiled and present in the shaders directory.");
}
if (hasTessControl != hasTessEval) {
throw std::runtime_error(
"Tessellation shaders require both 'tesc' and 'tese' paths. Shader key: " + key);
}
if (hasTessControl && !std::filesystem::exists(paths.tessControl)) {
throw std::runtime_error(
"Tessellation control shader not found: " + paths.tessControl +
"\n\nShader key: " + key +
"\n\nPlease ensure shader files are compiled and present in the shaders directory.");
}
if (hasTessEval && !std::filesystem::exists(paths.tessEval)) {
throw std::runtime_error(
"Tessellation evaluation shader not found: " + paths.tessEval +
"\n\nShader key: " + key +
"\n\nPlease ensure shader files are compiled and present in the shaders directory.");
}
pipelines_[key] = pipeline;
std::vector<VkShaderModule> shaderModules;
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
shaderStages.reserve(2 + (hasGeometry ? 1 : 0) + (hasTessControl ? 2 : 0));
vkDestroyShaderModule(device, fragShaderModule, nullptr);
vkDestroyShaderModule(device, vertShaderModule, nullptr);
auto destroyShaderModules = [&]() {
for (VkShaderModule module : shaderModules) {
vkDestroyShaderModule(device, module, nullptr);
}
shaderModules.clear();
};
auto addStage = [&](VkShaderStageFlagBits stage, const std::string& path) {
auto shaderCode = ReadShaderFile(path);
VkShaderModule shaderModule = CreateShaderModule(shaderCode);
shaderModules.push_back(shaderModule);
VkPipelineShaderStageCreateInfo stageInfo{};
stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stageInfo.stage = stage;
stageInfo.module = shaderModule;
stageInfo.pName = "main";
shaderStages.push_back(stageInfo);
};
try {
addStage(VK_SHADER_STAGE_VERTEX_BIT, paths.vertex);
if (hasTessControl) {
addStage(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, paths.tessControl);
addStage(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, paths.tessEval);
}
if (hasGeometry) {
addStage(VK_SHADER_STAGE_GEOMETRY_BIT, paths.geometry);
}
addStage(VK_SHADER_STAGE_FRAGMENT_BIT, paths.fragment);
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = inputAssembly;
VkPipelineTessellationStateCreateInfo tessellationState{};
bool useTessellation = hasTessControl && hasTessEval;
if (useTessellation) {
inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
tessellationState.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
tessellationState.patchControlPoints = 3;
}
VkGraphicsPipelineCreateInfo pipelineCreateInfo = pipelineInfo;
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pTessellationState = useTessellation ? &tessellationState : nullptr;
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCreateInfo.pStages = shaderStages.data();
VkPipeline pipeline;
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr,
&pipeline) != VK_SUCCESS) {
destroyShaderModules();
throw std::runtime_error("Failed to create graphics pipeline for shader: " + key);
}
pipelines_[key] = pipeline;
destroyShaderModules();
} catch (...) {
destroyShaderModules();
throw;
}
logger_->Debug("Created pipeline: " + key);
}

View File

@@ -82,30 +82,52 @@ ShaderPaths ShaderScriptService::ReadShaderPathsTable(lua_State* L, int index) c
ShaderPaths paths;
int absIndex = lua_absindex(L, index);
lua_getfield(L, absIndex, "vertex");
if (!lua_isstring(L, -1)) {
lua_pop(L, 1);
if (logger_) {
logger_->Error("Shader path 'vertex' must be a string");
auto readRequiredPath = [&](const char* field, std::string& target) {
lua_getfield(L, absIndex, field);
if (!lua_isstring(L, -1)) {
lua_pop(L, 1);
if (logger_) {
logger_->Error("Shader path '" + std::string(field) + "' must be a string");
}
throw std::runtime_error("Shader path '" + std::string(field) + "' must be a string");
}
throw std::runtime_error("Shader path 'vertex' must be a string");
}
paths.vertex = lua_tostring(L, -1);
lua_pop(L, 1);
lua_getfield(L, absIndex, "fragment");
if (!lua_isstring(L, -1)) {
target = lua_tostring(L, -1);
lua_pop(L, 1);
if (logger_) {
logger_->Error("Shader path 'fragment' must be a string");
}
throw std::runtime_error("Shader path 'fragment' must be a string");
}
paths.fragment = lua_tostring(L, -1);
lua_pop(L, 1);
};
paths.vertex = ResolveShaderPath(paths.vertex);
paths.fragment = ResolveShaderPath(paths.fragment);
auto readOptionalPath = [&](const char* field, std::string& target) {
lua_getfield(L, absIndex, field);
if (lua_isstring(L, -1)) {
target = lua_tostring(L, -1);
} else if (!lua_isnil(L, -1)) {
lua_pop(L, 1);
if (logger_) {
logger_->Error("Shader path '" + std::string(field) + "' must be a string when provided");
}
throw std::runtime_error("Shader path '" + std::string(field) + "' must be a string when provided");
}
lua_pop(L, 1);
};
readRequiredPath("vertex", paths.vertex);
readRequiredPath("fragment", paths.fragment);
readOptionalPath("geometry", paths.geometry);
readOptionalPath("tesc", paths.tessControl);
readOptionalPath("tese", paths.tessEval);
readOptionalPath("compute", paths.compute);
auto resolveIfPresent = [&](std::string& value) {
if (!value.empty()) {
value = ResolveShaderPath(value);
}
};
resolveIfPresent(paths.vertex);
resolveIfPresent(paths.fragment);
resolveIfPresent(paths.geometry);
resolveIfPresent(paths.tessControl);
resolveIfPresent(paths.tessEval);
resolveIfPresent(paths.compute);
return paths;
}

View File

@@ -22,6 +22,10 @@ struct GraphicsConfig {
struct ShaderPaths {
std::string vertex;
std::string fragment;
std::string geometry;
std::string tessControl;
std::string tessEval;
std::string compute;
};
/**
@@ -35,4 +39,4 @@ struct RenderCommand {
std::array<float, 16> modelMatrix;
};
} // namespace sdl3cpp::services
} // namespace sdl3cpp::services