feat: Refactor shader handling by implementing inline shader sources and enhancing error handling

This commit is contained in:
2026-01-05 23:44:00 +00:00
parent 945b724838
commit d7d4411251
12 changed files with 473 additions and 239 deletions

View File

@@ -83,7 +83,7 @@ The file exists but cannot be opened. Check file permissions.
**Example error message**:
```
Vertex shader not found: shaders/gui_2d.vert
Vertex shader not found: shaders/missing.vert
Shader key: default
@@ -160,19 +160,20 @@ Plus a message box with the same error.
### Test Case 2: Missing Shader File
```bash
cd build/Release
mv shaders/gui_2d.vert shaders/gui_2d.vert.backup
sed -i 's/return {default = variant}/return {default = {vertex = "shaders\/missing.vert", fragment = "shaders\/missing.frag"}}/' ../scripts/gui_demo.lua
./sdl3_app --json-file-in ./config/gui_runtime.json
```
**Expected Result**:
Message box showing:
```
Vertex shader not found: shaders/gui_2d.vert
Vertex shader not found: shaders/missing.vert
Shader key: default
Please ensure shader files are compiled and present in the shaders directory.
```
Restore `scripts/gui_demo.lua` after the test run.
### Test Case 3: Normal Operation
```bash

View File

@@ -66,12 +66,26 @@ local function updateFpsModeToggle()
end
end
local shader_variants = {
default = {
vertex = "shaders/gui_2d.vert",
fragment = "shaders/gui_2d.frag",
},
}
local function build_shader_variants()
local ok, toolkit = pcall(require, "shader_toolkit")
if not ok then
error("Shader toolkit unavailable: " .. tostring(toolkit))
end
local ok_generate, variant = pcall(toolkit.generate_variant, {
key = "default",
template = "gui_2d",
output_mode = "source",
compile = false,
})
if not ok_generate then
error("Shader generation failed: " .. tostring(variant))
end
return {default = variant}
end
local shader_variants = build_shader_variants()
local function drawTestButtons()
-- Background panel
@@ -177,9 +191,12 @@ function get_scene_objects()
end
function get_shader_paths()
local default_variant = shader_variants.default or {}
local vertex_label = default_variant.vertex or "inline"
local fragment_label = default_variant.fragment or "inline"
log_trace("GUI demo shader variants: default vertex=%s fragment=%s",
shader_variants.default.vertex,
shader_variants.default.fragment)
vertex_label,
fragment_label)
return shader_variants
end

View File

@@ -338,6 +338,258 @@ void main() {
}
]]
local gui_2d_vertex_source = [[
#version 450
layout(location = 0) in vec3 inPos;
layout(location = 1) in vec4 inColor;
layout(location = 0) out vec4 fragColor;
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 viewProj;
// Extended fields for PBR/atmospherics (ignored by basic shaders)
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
// Atmospherics parameters
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
vec3 fogColor;
float gamma;
float exposure;
int enableShadows;
int enableFog;
} pushConstants;
void main() {
fragColor = inColor;
vec4 worldPos = pushConstants.model * vec4(inPos, 1.0);
gl_Position = pushConstants.viewProj * worldPos;
}
]]
local gui_2d_fragment_source = [[
#version 450
layout(location = 0) in vec4 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = fragColor;
}
]]
local shadow_vertex_source = [[
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
// layout(location = 2) in vec2 inTexCoord; // Not used
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 viewProj;
// Extended fields for PBR/atmospherics
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
// Atmospherics parameters
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
vec3 fogColor;
float gamma;
float exposure;
int enableShadows;
int enableFog;
} pc;
void main() {
gl_Position = pc.lightViewProj * pc.model * vec4(inPosition, 1.0);
}
]]
local shadow_fragment_source = [[
#version 450
void main() {
// Empty fragment shader for shadow mapping
// Depth is automatically written
}
]]
local fullscreen_vertex_source = [[
#version 450
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec2 inTexCoord;
layout(location = 0) out vec2 fragPosition;
layout(location = 1) out vec2 fragTexCoord;
void main() {
fragPosition = inPosition;
fragTexCoord = inTexCoord;
gl_Position = vec4(inPosition, 0.0, 1.0);
}
]]
local ssgi_fragment_source = [[
#version 450
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec2 inTexCoord;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform sampler2D sceneColor;
layout(set = 0, binding = 1) uniform sampler2D normalBuffer;
layout(set = 0, binding = 2) uniform sampler2D depthBuffer;
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 viewProj;
// Extended fields for PBR/atmospherics
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
// Atmospherics parameters
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
vec3 fogColor;
float gamma;
float exposure;
int enableShadows;
int enableFog;
} pc;
const int NUM_SAMPLES = 16;
const float SAMPLE_RADIUS = 0.5;
// Reconstruct world position from depth
vec3 worldPosFromDepth(float depth, vec2 texCoord) {
vec4 clipSpace = vec4(texCoord * 2.0 - 1.0, depth, 1.0);
vec4 viewSpace = pc.proj * clipSpace; // Use proj as invProj for now (dummy)
viewSpace /= viewSpace.w;
vec4 worldSpace = pc.view * viewSpace; // Use view as invView for now (dummy)
return worldSpace.xyz;
}
vec3 ssao(vec2 texCoord) {
float depth = texture(depthBuffer, texCoord).r;
if (depth >= 1.0) return vec3(1.0); // Skybox
vec3 worldPos = worldPosFromDepth(depth, texCoord);
vec3 normal = normalize(texture(normalBuffer, texCoord).rgb * 2.0 - 1.0);
float occlusion = 0.0;
for (int i = 0; i < NUM_SAMPLES; i++) {
// Generate sample position in hemisphere around normal
float angle = (float(i) / float(NUM_SAMPLES)) * 6.283185;
vec2 offset = vec2(cos(angle), sin(angle)) * SAMPLE_RADIUS;
vec2 sampleTexCoord = texCoord + offset / textureSize(depthBuffer, 0);
float sampleDepth = texture(depthBuffer, sampleTexCoord).r;
if (sampleDepth < depth - 0.01) { // Occluded
occlusion += 1.0;
}
}
occlusion = 1.0 - (occlusion / float(NUM_SAMPLES));
return vec3(occlusion);
}
void main() {
vec3 sceneColor = texture(sceneColor, inTexCoord).rgb;
vec3 ao = ssao(inTexCoord);
// Apply ambient occlusion
vec3 finalColor = sceneColor * (0.3 + 0.7 * ao); // Mix AO with direct lighting
outColor = vec4(finalColor, 1.0);
}
]]
local volumetric_fragment_source = [[
#version 450
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec2 inTexCoord;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform sampler2D sceneColor;
layout(set = 0, binding = 1) uniform sampler2D depthBuffer;
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 viewProj;
// Extended fields for PBR/atmospherics
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
// Atmospherics parameters
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
vec3 fogColor;
float gamma;
float exposure;
int enableShadows;
int enableFog;
} pc;
const int NUM_SAMPLES = 100;
const float DECAY_BASE = 0.96815;
const float WEIGHT_BASE = 0.58767;
const float EXPOSURE = 0.2;
void main() {
vec2 texCoord = inTexCoord;
vec2 lightScreenPos = vec2(0.5, 0.5); // Dummy light position
vec2 deltaTexCoord = (texCoord - lightScreenPos);
deltaTexCoord *= 1.0 / float(NUM_SAMPLES) * 0.5; // Scale for effect
vec3 color = texture(sceneColor, texCoord).rgb;
// Only apply god rays if we're looking towards the light
float centerDistance = length(lightScreenPos - vec2(0.5, 0.5));
if (centerDistance < 0.8) { // Light is visible on screen
vec3 godRayColor = vec3(0.0);
float weight = WEIGHT_BASE;
for (int i = 0; i < NUM_SAMPLES; i++) {
texCoord -= deltaTexCoord;
vec3 sampleColor = texture(sceneColor, texCoord).rgb;
godRayColor += sampleColor * weight;
weight *= DECAY_BASE;
}
color += godRayColor * EXPOSURE * 1.0; // Dummy intensity
}
outColor = vec4(color, 1.0);
}
]]
local vertex_world_color_source = [[
#version 450
@@ -996,6 +1248,13 @@ local function build_vertex_color_sources()
}
end
local function build_gui_2d_sources()
return {
vertex = gui_2d_vertex_source,
fragment = gui_2d_fragment_source,
}
end
local function build_solid_color_sources(options)
local color = normalize_color(options.color)
local fragment = string.format([[
@@ -1013,6 +1272,13 @@ void main() {
}
end
local function build_shadow_sources()
return {
vertex = shadow_vertex_source,
fragment = shadow_fragment_source,
}
end
local function build_cube_rainbow_sources(options)
return {
vertex = vertex_world_color_source,
@@ -1055,6 +1321,20 @@ local function build_pbr_sources(options)
}
end
local function build_ssgi_sources()
return {
vertex = fullscreen_vertex_source,
fragment = ssgi_fragment_source,
}
end
local function build_volumetric_sources()
return {
vertex = fullscreen_vertex_source,
fragment = volumetric_fragment_source,
}
end
local templates = {}
local function register_template_object(template)
@@ -1068,9 +1348,11 @@ local function register_template_object(template)
end
register_template_object(ShaderTemplate:new("vertex_color", build_vertex_color_sources))
register_template_object(ShaderTemplate:new("gui_2d", build_gui_2d_sources))
register_template_object(ShaderTemplate:new("solid_color", build_solid_color_sources, {
color = {1.0, 1.0, 1.0, 1.0},
}))
register_template_object(ShaderTemplate:new("shadow", build_shadow_sources))
register_template_object(ShaderTemplate:new("cube_rainbow", build_cube_rainbow_sources, {
band_scale = 0.35,
diagonal_scale = 0.25,
@@ -1114,6 +1396,8 @@ register_template_object(ShaderTemplate:new("pbr", build_pbr_sources, {
light_color = {1.0, 0.9, 0.6},
light_intensity = 1.2,
}))
register_template_object(ShaderTemplate:new("ssgi", build_ssgi_sources))
register_template_object(ShaderTemplate:new("volumetric", build_volumetric_sources))
shader_toolkit.templates = templates

View File

@@ -1,8 +0,0 @@
#version 450
layout(location = 0) in vec4 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = fragColor;
}

View File

@@ -1,33 +0,0 @@
#version 450
layout(location = 0) in vec3 inPos;
layout(location = 1) in vec4 inColor;
layout(location = 0) out vec4 fragColor;
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 viewProj;
// Extended fields for PBR/atmospherics (ignored by basic shaders)
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
// Atmospherics parameters
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
vec3 fogColor;
float gamma;
float exposure;
int enableShadows;
int enableFog;
} pushConstants;
void main() {
fragColor = inColor;
vec4 worldPos = pushConstants.model * vec4(inPos, 1.0);
gl_Position = pushConstants.viewProj * worldPos;
}

View File

@@ -1,6 +0,0 @@
#version 450
void main() {
// Empty fragment shader for shadow mapping
// Depth is automatically written
}

View File

@@ -1,30 +0,0 @@
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
// layout(location = 2) in vec2 inTexCoord; // Not used
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 viewProj;
// Extended fields for PBR/atmospherics
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
// Atmospherics parameters
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
vec3 fogColor;
float gamma;
float exposure;
int enableShadows;
int enableFog;
} pc;
void main() {
gl_Position = pc.lightViewProj * pc.model * vec4(inPosition, 1.0);
}

View File

@@ -1,79 +0,0 @@
#version 450
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec2 inTexCoord;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform sampler2D sceneColor;
layout(set = 0, binding = 1) uniform sampler2D normalBuffer;
layout(set = 0, binding = 2) uniform sampler2D depthBuffer;
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 viewProj;
// Extended fields for PBR/atmospherics
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
// Atmospherics parameters
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
vec3 fogColor;
float gamma;
float exposure;
int enableShadows;
int enableFog;
} pc;
const int NUM_SAMPLES = 16;
const float SAMPLE_RADIUS = 0.5;
// Reconstruct world position from depth
vec3 worldPosFromDepth(float depth, vec2 texCoord) {
vec4 clipSpace = vec4(texCoord * 2.0 - 1.0, depth, 1.0);
vec4 viewSpace = pc.proj * clipSpace; // Use proj as invProj for now (dummy)
viewSpace /= viewSpace.w;
vec4 worldSpace = pc.view * viewSpace; // Use view as invView for now (dummy)
return worldSpace.xyz;
}
vec3 ssao(vec2 texCoord) {
float depth = texture(depthBuffer, texCoord).r;
if (depth >= 1.0) return vec3(1.0); // Skybox
vec3 worldPos = worldPosFromDepth(depth, texCoord);
vec3 normal = normalize(texture(normalBuffer, texCoord).rgb * 2.0 - 1.0);
float occlusion = 0.0;
for (int i = 0; i < NUM_SAMPLES; i++) {
// Generate sample position in hemisphere around normal
float angle = (float(i) / float(NUM_SAMPLES)) * 6.283185;
vec2 offset = vec2(cos(angle), sin(angle)) * SAMPLE_RADIUS;
vec2 sampleTexCoord = texCoord + offset / textureSize(depthBuffer, 0);
float sampleDepth = texture(depthBuffer, sampleTexCoord).r;
if (sampleDepth < depth - 0.01) { // Occluded
occlusion += 1.0;
}
}
occlusion = 1.0 - (occlusion / float(NUM_SAMPLES));
return vec3(occlusion);
}
void main() {
vec3 sceneColor = texture(sceneColor, inTexCoord).rgb;
vec3 ao = ssao(inTexCoord);
// Apply ambient occlusion
vec3 finalColor = sceneColor * (0.3 + 0.7 * ao); // Mix AO with direct lighting
outColor = vec4(finalColor, 1.0);
}

View File

@@ -1,62 +0,0 @@
#version 450
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec2 inTexCoord;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform sampler2D sceneColor;
layout(set = 0, binding = 1) uniform sampler2D depthBuffer;
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 viewProj;
// Extended fields for PBR/atmospherics
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
// Atmospherics parameters
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
vec3 fogColor;
float gamma;
float exposure;
int enableShadows;
int enableFog;
} pc;
const int NUM_SAMPLES = 100;
const float DECAY_BASE = 0.96815;
const float WEIGHT_BASE = 0.58767;
const float EXPOSURE = 0.2;
void main() {
vec2 texCoord = inTexCoord;
vec2 lightScreenPos = vec2(0.5, 0.5); // Dummy light position
vec2 deltaTexCoord = (texCoord - lightScreenPos);
deltaTexCoord *= 1.0 / float(NUM_SAMPLES) * 0.5; // Scale for effect
vec3 color = texture(sceneColor, texCoord).rgb;
// Only apply god rays if we're looking towards the light
float centerDistance = length(lightScreenPos - vec2(0.5, 0.5));
if (centerDistance < 0.8) { // Light is visible on screen
vec3 godRayColor = vec3(0.0);
float weight = WEIGHT_BASE;
for (int i = 0; i < NUM_SAMPLES; i++) {
texCoord -= deltaTexCoord;
vec3 sampleColor = texture(sceneColor, texCoord).rgb;
godRayColor += sampleColor * weight;
weight *= DECAY_BASE;
}
color += godRayColor * EXPOSURE * 1.0; // Dummy intensity
}
outColor = vec4(color, 1.0);
}

View File

@@ -316,6 +316,93 @@ std::vector<uint8_t> ReadShaderFile(const std::filesystem::path& path,
return buffer;
}
std::vector<uint8_t> ReadShaderSource(const std::string& source,
VkShaderStageFlagBits stage,
const std::string& label,
ILogger* logger) {
if (logger) {
logger->Trace("GuiRenderer", "ReadShaderSource",
"label=" + label + ", stage=" +
std::to_string(static_cast<int>(stage)));
}
if (source.empty()) {
throw std::runtime_error("Shader source is empty");
}
shaderc::Compiler compiler;
shaderc::CompileOptions options;
options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2);
shaderc_shader_kind kind = ShadercKindFromStage(stage);
auto result = compiler.CompileGlslToSpv(source, kind, label.c_str(), options);
if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
std::string error = result.GetErrorMessage();
if (logger) {
logger->Error("GuiRenderer shader compilation failed: " + label + "\n" + error);
}
throw std::runtime_error("Shader compilation failed: " + label + "\n" + error);
}
std::vector<uint32_t> spirv(result.cbegin(), result.cend());
std::vector<uint8_t> buffer(spirv.size() * sizeof(uint32_t));
if (!buffer.empty()) {
std::memcpy(buffer.data(), spirv.data(), buffer.size());
}
if (logger) {
logger->Trace("GuiRenderer", "ReadShaderSource",
"compiledBytes=" + std::to_string(buffer.size()));
}
return buffer;
}
const char* kGuiVertexSource = R"(
#version 450
layout(location = 0) in vec3 inPos;
layout(location = 1) in vec4 inColor;
layout(location = 0) out vec4 fragColor;
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 viewProj;
// Extended fields for PBR/atmospherics (ignored by basic shaders)
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
// Atmospherics parameters
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
vec3 fogColor;
float gamma;
float exposure;
int enableShadows;
int enableFog;
} pushConstants;
void main() {
fragColor = inColor;
vec4 worldPos = pushConstants.model * vec4(inPos, 1.0);
gl_Position = pushConstants.viewProj * worldPos;
}
)";
const char* kGuiFragmentSource = R"(
#version 450
layout(location = 0) in vec4 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = fragColor;
}
)";
} // namespace
GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFormat swapchainFormat,
@@ -663,22 +750,45 @@ const std::vector<uint8_t>& GuiRenderer::LoadShaderBytes(const std::filesystem::
return inserted.first->second;
}
const std::vector<uint8_t>& GuiRenderer::LoadShaderBytes(const std::string& cacheKey,
const std::string& source,
VkShaderStageFlagBits stage) {
auto cached = shaderSpirvCache_.find(cacheKey);
if (cached != shaderSpirvCache_.end()) {
if (logger_) {
logger_->Trace("GuiRenderer", "LoadShaderBytes",
"cacheHit=true, key=" + cacheKey +
", bytes=" + std::to_string(cached->second.size()));
}
return cached->second;
}
std::vector<uint8_t> shaderBytes = ReadShaderSource(source, stage, cacheKey, logger_.get());
auto inserted = shaderSpirvCache_.emplace(cacheKey, std::move(shaderBytes));
if (logger_) {
logger_->Trace("GuiRenderer", "LoadShaderBytes",
"cacheHit=false, key=" + cacheKey +
", bytes=" + std::to_string(inserted.first->second.size()));
}
return inserted.first->second;
}
void GuiRenderer::CreatePipeline(VkRenderPass renderPass, VkExtent2D extent) {
// Load shader modules
const std::filesystem::path vertexShaderPath =
scriptDirectory_.parent_path() / "shaders" / "gui_2d.vert";
const std::filesystem::path fragmentShaderPath =
scriptDirectory_.parent_path() / "shaders" / "gui_2d.frag";
const std::string vertexLabel = "inline:gui_2d.vert";
const std::string fragmentLabel = "inline:gui_2d.frag";
if (logger_) {
logger_->Trace("GuiRenderer", "CreatePipeline",
"renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false") +
", extent=" + std::to_string(extent.width) + "x" + std::to_string(extent.height) +
", vertexShader=" + vertexShaderPath.string() +
", fragmentShader=" + fragmentShaderPath.string());
", vertexShader=" + vertexLabel +
", fragmentShader=" + fragmentLabel);
}
const auto& vertShaderCode = LoadShaderBytes(vertexShaderPath, VK_SHADER_STAGE_VERTEX_BIT);
const auto& fragShaderCode = LoadShaderBytes(fragmentShaderPath, VK_SHADER_STAGE_FRAGMENT_BIT);
const auto& vertShaderCode = LoadShaderBytes(vertexLabel, kGuiVertexSource,
VK_SHADER_STAGE_VERTEX_BIT);
const auto& fragShaderCode = LoadShaderBytes(fragmentLabel, kGuiFragmentSource,
VK_SHADER_STAGE_FRAGMENT_BIT);
VkShaderModuleCreateInfo vertModuleInfo{};
vertModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;

View File

@@ -59,6 +59,9 @@ private:
void GenerateGuiGeometry(const std::vector<GuiCommand>& commands, uint32_t width, uint32_t height);
const std::vector<uint8_t>& LoadShaderBytes(const std::filesystem::path& path,
VkShaderStageFlagBits stage);
const std::vector<uint8_t>& LoadShaderBytes(const std::string& cacheKey,
const std::string& source,
VkShaderStageFlagBits stage);
VkDevice device_;
VkPhysicalDevice physicalDevice_;

View File

@@ -46,8 +46,45 @@ int main() {
// Test pipeline creation
sdl3cpp::services::ShaderPaths shaderPaths;
shaderPaths.vertex = "shaders/gui_2d.vert";
shaderPaths.fragment = "shaders/gui_2d.frag";
shaderPaths.vertex = "inline:gui_2d.vert";
shaderPaths.fragment = "inline:gui_2d.frag";
shaderPaths.vertexSource = R"(
#version 450
layout(location = 0) in vec3 inPos;
layout(location = 1) in vec4 inColor;
layout(location = 0) out vec4 fragColor;
layout(push_constant) uniform PushConstants {
mat4 model;
mat4 viewProj;
mat4 view;
mat4 proj;
mat4 lightViewProj;
vec3 cameraPos;
float time;
float ambientStrength;
float fogDensity;
float fogStart;
float fogEnd;
vec3 fogColor;
float gamma;
float exposure;
int enableShadows;
int enableFog;
} pushConstants;
void main() {
fragColor = inColor;
vec4 worldPos = pushConstants.model * vec4(inPos, 1.0);
gl_Position = pushConstants.viewProj * worldPos;
}
)";
shaderPaths.fragmentSource = R"(
#version 450
layout(location = 0) in vec4 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = fragColor;
}
)";
auto pipeline = backend->CreatePipeline(device, "test_shader", shaderPaths);
Assert(pipeline != nullptr, "pipeline creation failed", failures);
std::cout << "GXM pipeline created successfully\n";