From 945b724838d5c0d5cb935eb84d73ada3b621774f Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Mon, 5 Jan 2026 23:34:48 +0000 Subject: [PATCH] Refactor shader management and remove unused shaders - Introduced a shader toolkit for dynamic shader generation in soundboard.lua. - Removed obsolete shader files: ceiling.frag, cube.frag, cube.vert, floor.frag, pbr.frag, pbr.vert, solid.frag, solid.vert, wall.frag. - Updated pipeline_service to handle inline shader sources and paths, improving shader registration and validation. - Enhanced shader_script_service to support optional shader source paths alongside traditional paths. - Modified ShaderPaths structure to include source fields for vertex, fragment, geometry, tessellation control, tessellation evaluation, and compute shaders. - Updated test_gxm_backend to reflect changes in shader paths and ensure successful pipeline creation. --- docs/test_error_handling.md | 8 +- scripts/cube_logic.lua | 71 +++++++-- scripts/shader_toolkit.lua | 33 +++- scripts/soundboard.lua | 27 +++- shaders/ceiling.frag | 71 --------- shaders/cube.frag | 67 -------- shaders/cube.vert | 35 ----- shaders/floor.frag | 72 --------- shaders/pbr.frag | 163 -------------------- shaders/pbr.vert | 41 ----- shaders/solid.frag | 55 ------- shaders/solid.vert | 35 ----- shaders/wall.frag | 73 --------- src/services/impl/pipeline_service.cpp | 156 ++++++++++++------- src/services/impl/pipeline_service.hpp | 9 +- src/services/impl/shader_script_service.cpp | 36 +++-- src/services/interfaces/graphics_types.hpp | 6 + tests/test_gxm_backend.cpp | 6 +- 18 files changed, 247 insertions(+), 717 deletions(-) delete mode 100644 shaders/ceiling.frag delete mode 100644 shaders/cube.frag delete mode 100644 shaders/cube.vert delete mode 100644 shaders/floor.frag delete mode 100644 shaders/pbr.frag delete mode 100644 shaders/pbr.vert delete mode 100644 shaders/solid.frag delete mode 100644 shaders/solid.vert delete mode 100644 shaders/wall.frag diff --git a/docs/test_error_handling.md b/docs/test_error_handling.md index 27c82c1..146f245 100644 --- a/docs/test_error_handling.md +++ b/docs/test_error_handling.md @@ -83,7 +83,7 @@ The file exists but cannot be opened. Check file permissions. **Example error message**: ``` -Vertex shader not found: shaders/cube.vert +Vertex shader not found: shaders/gui_2d.vert Shader key: default @@ -160,14 +160,14 @@ Plus a message box with the same error. ### Test Case 2: Missing Shader File ```bash cd build/Release -mv shaders/cube.vert shaders/cube.vert.backup -./sdl3_app --json-file-in ./config/seed_runtime.json +mv shaders/gui_2d.vert shaders/gui_2d.vert.backup +./sdl3_app --json-file-in ./config/gui_runtime.json ``` **Expected Result**: Message box showing: ``` -Vertex shader not found: shaders/cube.vert +Vertex shader not found: shaders/gui_2d.vert Shader key: default diff --git a/scripts/cube_logic.lua b/scripts/cube_logic.lua index 31f7a8f..c6cd8b8 100644 --- a/scripts/cube_logic.lua +++ b/scripts/cube_logic.lua @@ -200,30 +200,74 @@ if cube_mesh_info.loaded then end local function build_static_shader_variants() + local fallback_vertex_source = [[ +#version 450 + +layout(location = 0) in vec3 inPos; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 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; + gl_Position = pushConstants.viewProj * pushConstants.model * vec4(inPos, 1.0); +} +]] + + local fallback_fragment_source = [[ +#version 450 + +layout(location = 0) in vec3 fragColor; +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} +]] + return { default = { - vertex = "shaders/cube.vert", - fragment = "shaders/cube.frag", + vertex_source = fallback_vertex_source, + fragment_source = fallback_fragment_source, }, solid = { - vertex = "shaders/solid.vert", - fragment = "shaders/solid.frag", + vertex_source = fallback_vertex_source, + fragment_source = fallback_fragment_source, }, floor = { - vertex = "shaders/solid.vert", - fragment = "shaders/floor.frag", + vertex_source = fallback_vertex_source, + fragment_source = fallback_fragment_source, }, wall = { - vertex = "shaders/solid.vert", - fragment = "shaders/wall.frag", + vertex_source = fallback_vertex_source, + fragment_source = fallback_fragment_source, }, ceiling = { - vertex = "shaders/solid.vert", - fragment = "shaders/ceiling.frag", + vertex_source = fallback_vertex_source, + fragment_source = fallback_fragment_source, }, pbr = { - vertex = "shaders/pbr.vert", - fragment = "shaders/pbr.frag", + vertex_source = fallback_vertex_source, + fragment_source = fallback_fragment_source, }, } end @@ -243,7 +287,8 @@ local function build_shader_variants() return build_static_shader_variants() end - local ok_generate, generated = pcall(toolkit.generate_cube_demo_variants, {compile = false}) + local ok_generate, generated = pcall(toolkit.generate_cube_demo_variants, + {compile = false, output_mode = "source"}) if not ok_generate then log_debug("Shader generation failed: %s", tostring(generated)) return build_static_shader_variants() diff --git a/scripts/shader_toolkit.lua b/scripts/shader_toolkit.lua index 763be65..e6d591a 100644 --- a/scripts/shader_toolkit.lua +++ b/scripts/shader_toolkit.lua @@ -281,6 +281,7 @@ function ShaderVariant:new(options) template = options.template or options.template_name, output_name = options.output_name or options.outputName, output_dir = options.output_dir or options.outputDir, + output_mode = options.output_mode or options.outputMode, compile = options.compile, compiler = options.compiler, skip_if_present = options.skip_if_present, @@ -1177,10 +1178,30 @@ function shader_toolkit.generate_variant(options) if not template then error("Unknown shader template: " .. tostring(variant.template)) end + local output_mode = variant.output_mode or "source" local output_name = normalize_output_name(variant.output_name or variant.key or template.name) if not output_name then error("Shader variant requires output_name or key") end + + local sources = template:Generate(variant.parameters) + if type(sources) ~= "table" or not sources.vertex or not sources.fragment then + error("Shader template did not return vertex and fragment source") + end + + if output_mode ~= "files" then + if variant.compile == true then + error("Inline shader output does not support external compilation") + end + local result = {} + for stage, source in pairs(sources) do + if type(source) == "string" then + result[stage .. "_source"] = source + end + end + return result + end + local output_dir = resolve_output_dir(variant.output_dir) if not ensure_directory(output_dir) then error("Failed to create shader output directory: " .. output_dir) @@ -1192,11 +1213,6 @@ function shader_toolkit.generate_variant(options) local vertex_spv = vertex_source .. ".spv" local fragment_spv = fragment_source .. ".spv" - local sources = template:Generate(variant.parameters) - if type(sources) ~= "table" or not sources.vertex or not sources.fragment then - error("Shader template did not return vertex and fragment source") - end - write_text_file_if_changed(vertex_source, sources.vertex) write_text_file_if_changed(fragment_source, sources.fragment) @@ -1243,6 +1259,7 @@ end function shader_toolkit.generate_cube_demo_variants(options) local settings = options or {} local parameters = settings.parameters or {} + local output_mode = settings.output_mode or "source" local compile = settings.compile if compile == nil then compile = false @@ -1254,6 +1271,7 @@ function shader_toolkit.generate_cube_demo_variants(options) key = "default", template = "cube_rainbow", output_name = "cube", + output_mode = output_mode, compile = compile, output_dir = output_dir, parameters = parameters.default, @@ -1261,6 +1279,7 @@ function shader_toolkit.generate_cube_demo_variants(options) shader_toolkit.create_variant({ key = "solid", template = "solid_lit", + output_mode = output_mode, compile = compile, output_dir = output_dir, parameters = parameters.solid, @@ -1268,6 +1287,7 @@ function shader_toolkit.generate_cube_demo_variants(options) shader_toolkit.create_variant({ key = "floor", template = "room_floor", + output_mode = output_mode, compile = compile, output_dir = output_dir, parameters = parameters.floor, @@ -1275,6 +1295,7 @@ function shader_toolkit.generate_cube_demo_variants(options) shader_toolkit.create_variant({ key = "wall", template = "room_wall", + output_mode = output_mode, compile = compile, output_dir = output_dir, parameters = parameters.wall, @@ -1282,6 +1303,7 @@ function shader_toolkit.generate_cube_demo_variants(options) shader_toolkit.create_variant({ key = "ceiling", template = "room_ceiling", + output_mode = output_mode, compile = compile, output_dir = output_dir, parameters = parameters.ceiling, @@ -1289,6 +1311,7 @@ function shader_toolkit.generate_cube_demo_variants(options) shader_toolkit.create_variant({ key = "pbr", template = "pbr", + output_mode = output_mode, compile = compile, output_dir = output_dir, parameters = parameters.pbr, diff --git a/scripts/soundboard.lua b/scripts/soundboard.lua index 6d2cc90..84a6a0b 100644 --- a/scripts/soundboard.lua +++ b/scripts/soundboard.lua @@ -224,12 +224,27 @@ local cubeIndices = { 1, 2, 6, 6, 5, 1, } -local shaderVariants = { - default = { - vertex = "shaders/cube.vert", - fragment = "shaders/cube.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 = "cube_rainbow", + output_name = "cube", + output_mode = "source", + compile = false, + }) + if not ok_generate then + error("Shader generation failed: " .. tostring(variant)) + end + + return {default = variant} +end + +local shaderVariants = build_shader_variants() local camera = { eye = { 2.0, 2.0, 3.0 }, diff --git a/shaders/ceiling.frag b/shaders/ceiling.frag deleted file mode 100644 index 2123bdd..0000000 --- a/shaders/ceiling.frag +++ /dev/null @@ -1,71 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 fragColor; -layout(location = 1) in vec3 fragWorldPos; -layout(location = 0) out vec4 outColor; - -float hash(vec2 p) { - return fract(sin(dot(p, vec2(63.1, 157.9))) * 43758.5453123); -} - -const vec3 SURFACE_BASE = vec3(0.98, 0.96, 0.1); - -vec3 ComputeNormal() { - vec3 dx = dFdx(fragWorldPos); - vec3 dy = dFdy(fragWorldPos); - return normalize(cross(dx, dy)); -} - -const vec3 LIGHT_POSITIONS[8] = vec3[8]( - vec3(13.0, 4.5, 13.0), - vec3(-13.0, 4.5, 13.0), - vec3(13.0, 4.5, -13.0), - vec3(-13.0, 4.5, -13.0), - vec3(0.0, 4.5, 13.0), - vec3(0.0, 4.5, -13.0), - vec3(13.0, 4.5, 0.0), - vec3(-13.0, 4.5, 0.0) -); - -const vec3 LIGHT_COLOR = vec3(0.92, 0.96, 1.0); -const float LIGHT_INTENSITY = 0.85; -const float AMBIENT_STRENGTH = 0.38; - -float calculateAttenuation(float distance) { - const float kConstant = 1.0; - const float kLinear = 0.09; - const float kQuadratic = 0.032; - return 1.0 / (kConstant + kLinear * distance + kQuadratic * distance * distance); -} - -void main() { - vec3 baseColor = SURFACE_BASE; - vec2 gridUv = fragWorldPos.xz * 0.45; - vec2 grid = abs(fract(gridUv) - 0.5); - float gridLine = step(0.48, max(grid.x, grid.y)); - float speckle = hash(floor(fragWorldPos.xz * 3.0)); - baseColor *= mix(0.94, 1.04, speckle); - baseColor *= mix(1.0, 0.84, gridLine); - - vec3 normal = ComputeNormal(); - vec3 ambient = AMBIENT_STRENGTH * baseColor; - vec3 lighting = vec3(0.0); - - for (int i = 0; i < 8; i++) { - vec3 lightDir = LIGHT_POSITIONS[i] - fragWorldPos; - float distance = length(lightDir); - lightDir = normalize(lightDir); - float attenuation = calculateAttenuation(distance); - float ndotl = abs(dot(normal, lightDir)); - lighting += LIGHT_COLOR * LIGHT_INTENSITY * attenuation * ndotl; - } - - vec3 keyDir = normalize(vec3(-0.15, 1.0, 0.25)); - float keyNdotL = abs(dot(normal, keyDir)); - lighting += vec3(0.85, 0.95, 1.0) * keyNdotL * 0.28; - - vec3 finalColor = ambient + baseColor * lighting; - finalColor = clamp(finalColor, 0.0, 1.0); - - outColor = vec4(finalColor, 1.0); -} diff --git a/shaders/cube.frag b/shaders/cube.frag deleted file mode 100644 index 7dd61a4..0000000 --- a/shaders/cube.frag +++ /dev/null @@ -1,67 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 fragColor; -layout(location = 1) in vec3 fragWorldPos; -layout(location = 0) out vec4 outColor; - -vec3 ComputeNormal() { - vec3 dx = dFdx(fragWorldPos); - vec3 dy = dFdy(fragWorldPos); - return normalize(cross(dx, dy)); -} - -vec3 RainbowBand(float t) { - t = fract(t); - float scaled = t * 5.0; - int index = int(floor(scaled)); - float blend = fract(scaled); - - vec3 red = vec3(0.91, 0.19, 0.21); - vec3 orange = vec3(0.99, 0.49, 0.09); - vec3 yellow = vec3(0.99, 0.86, 0.22); - vec3 green = vec3(0.16, 0.74, 0.39); - vec3 blue = vec3(0.24, 0.48, 0.88); - vec3 purple = vec3(0.56, 0.25, 0.75); - - vec3 a = red; - vec3 b = orange; - if (index == 1) { - a = orange; - b = yellow; - } else if (index == 2) { - a = yellow; - b = green; - } else if (index == 3) { - a = green; - b = blue; - } else if (index == 4) { - a = blue; - b = purple; - } else if (index >= 5) { - a = purple; - b = purple; - } - - return mix(a, b, blend); -} - -void main() { - float bandPos = fragWorldPos.y * 0.35; - float diagonal = (fragWorldPos.x + fragWorldPos.z) * 0.25; - vec3 rainbow = RainbowBand(bandPos + diagonal); - vec3 baseColor = mix(rainbow, fragColor, 0.08); - - vec3 normal = ComputeNormal(); - vec3 lighting = vec3(0.0); - - vec3 keyDir = normalize(vec3(-0.35, 1.0, -0.25)); - vec3 fillDir = normalize(vec3(0.45, 0.6, 0.2)); - float keyNdotL = abs(dot(normal, keyDir)); - float fillNdotL = abs(dot(normal, fillDir)); - - lighting += vec3(1.0, 0.92, 0.85) * keyNdotL * 0.9; - lighting += vec3(0.35, 0.45, 0.65) * fillNdotL * 0.4; - - vec3 finalColor = baseColor * (0.28 + lighting); - outColor = vec4(clamp(finalColor, 0.0, 1.0), 1.0); -} diff --git a/shaders/cube.vert b/shaders/cube.vert deleted file mode 100644 index fc1df5c..0000000 --- a/shaders/cube.vert +++ /dev/null @@ -1,35 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 inPos; -layout(location = 1) in vec3 inColor; - -layout(location = 0) out vec3 fragColor; -layout(location = 1) out vec3 fragWorldPos; - -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); - fragWorldPos = worldPos.xyz; - gl_Position = pushConstants.viewProj * worldPos; -} diff --git a/shaders/floor.frag b/shaders/floor.frag deleted file mode 100644 index 7c74dd4..0000000 --- a/shaders/floor.frag +++ /dev/null @@ -1,72 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 fragColor; -layout(location = 1) in vec3 fragWorldPos; -layout(location = 0) out vec4 outColor; - -float hash(vec2 p) { - return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); -} - -const vec3 SURFACE_BASE = vec3(0.02, 0.95, 0.72); - -vec3 ComputeNormal() { - vec3 dx = dFdx(fragWorldPos); - vec3 dy = dFdy(fragWorldPos); - return normalize(cross(dx, dy)); -} - -const vec3 LIGHT_POSITIONS[8] = vec3[8]( - vec3(13.0, 4.5, 13.0), - vec3(-13.0, 4.5, 13.0), - vec3(13.0, 4.5, -13.0), - vec3(-13.0, 4.5, -13.0), - vec3(0.0, 4.5, 13.0), - vec3(0.0, 4.5, -13.0), - vec3(13.0, 4.5, 0.0), - vec3(-13.0, 4.5, 0.0) -); - -const vec3 LIGHT_COLOR = vec3(1.0, 0.92, 0.7); -const float LIGHT_INTENSITY = 1.0; -const float AMBIENT_STRENGTH = 0.3; - -float calculateAttenuation(float distance) { - const float kConstant = 1.0; - const float kLinear = 0.09; - const float kQuadratic = 0.032; - return 1.0 / (kConstant + kLinear * distance + kQuadratic * distance * distance); -} - -void main() { - vec3 baseColor = SURFACE_BASE; - float checkerScale = 0.55; - float cx = step(0.5, fract(fragWorldPos.x * checkerScale)); - float cz = step(0.5, fract(fragWorldPos.z * checkerScale)); - float checker = abs(cx - cz); - float grit = hash(floor(fragWorldPos.xz * 2.2)); - float pattern = mix(0.82, 1.08, checker) * mix(0.96, 1.04, grit); - baseColor *= pattern; - - vec3 normal = ComputeNormal(); - vec3 ambient = AMBIENT_STRENGTH * baseColor; - vec3 lighting = vec3(0.0); - - for (int i = 0; i < 8; i++) { - vec3 lightDir = LIGHT_POSITIONS[i] - fragWorldPos; - float distance = length(lightDir); - lightDir = normalize(lightDir); - float attenuation = calculateAttenuation(distance); - float ndotl = abs(dot(normal, lightDir)); - lighting += LIGHT_COLOR * LIGHT_INTENSITY * attenuation * ndotl; - } - - vec3 keyDir = normalize(vec3(-0.3, 1.0, -0.4)); - float keyNdotL = abs(dot(normal, keyDir)); - lighting += vec3(0.9, 0.95, 1.0) * keyNdotL * 0.25; - - vec3 finalColor = ambient + baseColor * lighting; - finalColor = clamp(finalColor, 0.0, 1.0); - - outColor = vec4(finalColor, 1.0); -} diff --git a/shaders/pbr.frag b/shaders/pbr.frag deleted file mode 100644 index 446a67a..0000000 --- a/shaders/pbr.frag +++ /dev/null @@ -1,163 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 fragColor; -layout(location = 1) in vec3 fragWorldPos; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec2 fragTexCoord; - -layout(location = 0) out vec4 outColor; - -// Material properties -layout(push_constant) uniform PushConstants { - mat4 model; - 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; - -// Lighting uniforms -// layout(set = 0, binding = 0) uniform sampler2D shadowMap; - -// Material parameters (can be extended to use textures) -const vec3 MATERIAL_ALBEDO = vec3(0.8, 0.8, 0.8); -const float MATERIAL_ROUGHNESS = 0.3; -const float MATERIAL_METALLIC = 0.1; - -// Atmospheric parameters -// const vec3 FOG_COLOR = vec3(0.05, 0.05, 0.08); -// const float FOG_DENSITY = 0.003; -// const float AMBIENT_STRENGTH = 0.01; // Much darker ambient - -// Light properties -const vec3 LIGHT_COLOR = vec3(1.0, 0.9, 0.6); -const float LIGHT_INTENSITY = 1.2; -const vec3 LIGHT_POSITIONS[8] = vec3[8]( - vec3(13.0, 4.5, 13.0), - vec3(-13.0, 4.5, 13.0), - vec3(13.0, 4.5, -13.0), - vec3(-13.0, 4.5, -13.0), - vec3(0.0, 4.5, 13.0), - vec3(0.0, 4.5, -13.0), - vec3(13.0, 4.5, 0.0), - vec3(-13.0, 4.5, 0.0) -); - -// PBR functions -vec3 fresnelSchlick(float cosTheta, vec3 F0) { - return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); -} - -float DistributionGGX(vec3 N, vec3 H, float roughness) { - float a = roughness * roughness; - float a2 = a * a; - float NdotH = max(dot(N, H), 0.0); - float NdotH2 = NdotH * NdotH; - - float num = a2; - float denom = (NdotH2 * (a2 - 1.0) + 1.0); - denom = 3.14159 * denom * denom; - - return num / denom; -} - -float GeometrySchlickGGX(float NdotV, float roughness) { - float r = (roughness + 1.0); - float k = (r * r) / 8.0; - - float num = NdotV; - float denom = NdotV * (1.0 - k) + k; - - return num / denom; -} - -float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = GeometrySchlickGGX(NdotV, roughness); - float ggx1 = GeometrySchlickGGX(NdotL, roughness); - - return ggx1 * ggx2; -} - -float calculateShadow(vec3 worldPos) { - // TODO: Implement shadow mapping - return 0.0; // No shadow for now -} - -vec3 calculateLighting(vec3 worldPos, vec3 normal, vec3 viewDir) { - vec3 F0 = vec3(0.04); - F0 = mix(F0, MATERIAL_ALBEDO, MATERIAL_METALLIC); - - vec3 Lo = vec3(0.0); - - for (int i = 0; i < 8; i++) { - vec3 lightDir = normalize(LIGHT_POSITIONS[i] - worldPos); - vec3 halfway = normalize(viewDir + lightDir); - - float distance = length(LIGHT_POSITIONS[i] - worldPos); - float attenuation = 1.0 / (distance * distance); - vec3 radiance = LIGHT_COLOR * LIGHT_INTENSITY * attenuation; - - float NDF = DistributionGGX(normal, halfway, MATERIAL_ROUGHNESS); - float G = GeometrySmith(normal, viewDir, lightDir, MATERIAL_ROUGHNESS); - vec3 F = fresnelSchlick(max(dot(halfway, viewDir), 0.0), F0); - - vec3 kS = F; - vec3 kD = vec3(1.0) - kS; - kD *= 1.0 - MATERIAL_METALLIC; - - float NdotL = max(dot(normal, lightDir), 0.0); - - vec3 numerator = NDF * G * F; - float denominator = 4.0 * max(dot(normal, viewDir), 0.0) * NdotL + 0.0001; - vec3 specular = numerator / denominator; - - Lo += (kD * MATERIAL_ALBEDO / 3.14159 + specular) * radiance * NdotL; - } - - return Lo; -} - -void main() { - vec3 normal = normalize(fragNormal); - vec3 viewDir = normalize(pc.cameraPos - fragWorldPos); - - // Ambient lighting - vec3 ambient = pc.ambientStrength * MATERIAL_ALBEDO; - - // Direct lighting with PBR - vec3 lighting = calculateLighting(fragWorldPos, normal, viewDir); - - // Shadow calculation - float shadow = calculateShadow(fragWorldPos); - lighting *= (1.0 - shadow * 0.7); // Soften shadows - - // Combine lighting - vec3 finalColor = ambient + lighting; - - // Fog - if (pc.enableFog != 0) { - float fogFactor = 1.0 - exp(-pc.fogDensity * length(pc.cameraPos - fragWorldPos)); - finalColor = mix(finalColor, pc.fogColor, fogFactor); - } - - // Simple tone mapping (Reinhard) - finalColor = finalColor / (finalColor + vec3(1.0)); - - // Gamma correction - finalColor = pow(finalColor, vec3(1.0 / pc.gamma)); - - outColor = vec4(finalColor, 1.0); -} \ No newline at end of file diff --git a/shaders/pbr.vert b/shaders/pbr.vert deleted file mode 100644 index eca2f28..0000000 --- a/shaders/pbr.vert +++ /dev/null @@ -1,41 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inColor; // Color instead of normal -layout(location = 2) in vec2 inTexCoord; // Not used for now - -layout(location = 0) out vec3 fragColor; -layout(location = 1) out vec3 fragWorldPos; -layout(location = 2) out vec3 fragNormal; -layout(location = 3) out vec2 fragTexCoord; - -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() { - vec4 worldPos = pc.model * vec4(inPosition, 1.0); - gl_Position = pc.proj * pc.view * worldPos; - - fragWorldPos = worldPos.xyz; - fragNormal = normalize(mat3(pc.model) * vec3(0.0, 0.0, 1.0)); // Simple normal for flat shading - fragTexCoord = vec2(0.0, 0.0); // Not used - fragColor = inColor; // Use vertex color -} \ No newline at end of file diff --git a/shaders/solid.frag b/shaders/solid.frag deleted file mode 100644 index 5e503b7..0000000 --- a/shaders/solid.frag +++ /dev/null @@ -1,55 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 fragColor; -layout(location = 1) in vec3 fragWorldPos; -layout(location = 0) out vec4 outColor; - -// Lantern positions (8 lights) -const vec3 LIGHT_POSITIONS[8] = vec3[8]( - vec3(13.0, 4.5, 13.0), // Corner - vec3(-13.0, 4.5, 13.0), // Corner - vec3(13.0, 4.5, -13.0), // Corner - vec3(-13.0, 4.5, -13.0), // Corner - vec3(0.0, 4.5, 13.0), // Wall midpoint - vec3(0.0, 4.5, -13.0), // Wall midpoint - vec3(13.0, 4.5, 0.0), // Wall midpoint - vec3(-13.0, 4.5, 0.0) // Wall midpoint -); - -const vec3 LIGHT_COLOR = vec3(1.0, 0.9, 0.6); // Warm lantern color -const float LIGHT_INTENSITY = 0.9; -const float AMBIENT_STRENGTH = 0.22; // Boost ambient to preserve surface color - -float calculateAttenuation(float distance) { - // Quadratic attenuation: 1 / (constant + linear*d + quadratic*d^2) - const float kConstant = 1.0; - const float kLinear = 0.09; - const float kQuadratic = 0.032; - return 1.0 / (kConstant + kLinear * distance + kQuadratic * distance * distance); -} - -void main() { - vec3 ambient = AMBIENT_STRENGTH * fragColor; - vec3 lighting = vec3(0.0); - - // Calculate contribution from each lantern - for (int i = 0; i < 8; i++) { - vec3 lightDir = LIGHT_POSITIONS[i] - fragWorldPos; - float distance = length(lightDir); - lightDir = normalize(lightDir); - - // Distance attenuation - float attenuation = calculateAttenuation(distance); - - // Add light contribution - lighting += LIGHT_COLOR * LIGHT_INTENSITY * attenuation; - } - - // Combine ambient and dynamic lighting with surface color - vec3 finalColor = ambient + fragColor * lighting; - - // Clamp to prevent over-bright areas - finalColor = clamp(finalColor, 0.0, 1.0); - - outColor = vec4(finalColor, 1.0); -} diff --git a/shaders/solid.vert b/shaders/solid.vert deleted file mode 100644 index fc1df5c..0000000 --- a/shaders/solid.vert +++ /dev/null @@ -1,35 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 inPos; -layout(location = 1) in vec3 inColor; - -layout(location = 0) out vec3 fragColor; -layout(location = 1) out vec3 fragWorldPos; - -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); - fragWorldPos = worldPos.xyz; - gl_Position = pushConstants.viewProj * worldPos; -} diff --git a/shaders/wall.frag b/shaders/wall.frag deleted file mode 100644 index 92a9e0b..0000000 --- a/shaders/wall.frag +++ /dev/null @@ -1,73 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 fragColor; -layout(location = 1) in vec3 fragWorldPos; -layout(location = 0) out vec4 outColor; - -float hash(vec2 p) { - return fract(sin(dot(p, vec2(91.7, 127.3))) * 43758.5453123); -} - -const vec3 SURFACE_BASE = vec3(0.98, 0.18, 0.08); - -vec3 ComputeNormal() { - vec3 dx = dFdx(fragWorldPos); - vec3 dy = dFdy(fragWorldPos); - return normalize(cross(dx, dy)); -} - -const vec3 LIGHT_POSITIONS[8] = vec3[8]( - vec3(13.0, 4.5, 13.0), - vec3(-13.0, 4.5, 13.0), - vec3(13.0, 4.5, -13.0), - vec3(-13.0, 4.5, -13.0), - vec3(0.0, 4.5, 13.0), - vec3(0.0, 4.5, -13.0), - vec3(13.0, 4.5, 0.0), - vec3(-13.0, 4.5, 0.0) -); - -const vec3 LIGHT_COLOR = vec3(1.0, 0.88, 0.6); -const float LIGHT_INTENSITY = 0.95; -const float AMBIENT_STRENGTH = 0.24; - -float calculateAttenuation(float distance) { - const float kConstant = 1.0; - const float kLinear = 0.09; - const float kQuadratic = 0.032; - return 1.0 / (kConstant + kLinear * distance + kQuadratic * distance * distance); -} - -void main() { - vec3 baseColor = SURFACE_BASE; - float axisSelector = step(abs(fragWorldPos.z), abs(fragWorldPos.x)); - float coord = mix(fragWorldPos.z, fragWorldPos.x, axisSelector); - float plankScale = 0.4; - float plank = abs(fract(coord * plankScale) - 0.5); - float groove = smoothstep(0.46, 0.5, plank); - float grain = hash(floor(vec2(coord * 1.2, fragWorldPos.y * 2.0))); - baseColor *= mix(0.92, 1.05, grain); - baseColor *= mix(1.0, 0.78, groove); - - vec3 normal = ComputeNormal(); - vec3 ambient = AMBIENT_STRENGTH * baseColor; - vec3 lighting = vec3(0.0); - - for (int i = 0; i < 8; i++) { - vec3 lightDir = LIGHT_POSITIONS[i] - fragWorldPos; - float distance = length(lightDir); - lightDir = normalize(lightDir); - float attenuation = calculateAttenuation(distance); - float ndotl = abs(dot(normal, lightDir)); - lighting += LIGHT_COLOR * LIGHT_INTENSITY * attenuation * ndotl; - } - - vec3 keyDir = normalize(vec3(0.35, 0.9, 0.2)); - float keyNdotL = abs(dot(normal, keyDir)); - lighting += vec3(1.0, 0.9, 0.8) * keyNdotL * 0.2; - - vec3 finalColor = ambient + baseColor * lighting; - finalColor = clamp(finalColor, 0.0, 1.0); - - outColor = vec4(finalColor, 1.0); -} diff --git a/src/services/impl/pipeline_service.cpp b/src/services/impl/pipeline_service.cpp index 1364c0d..ee5b38a 100644 --- a/src/services/impl/pipeline_service.cpp +++ b/src/services/impl/pipeline_service.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -50,10 +51,16 @@ PipelineService::~PipelineService() { } void PipelineService::RegisterShader(const std::string& key, const ShaderPaths& paths) { + std::string vertexLabel = paths.vertex.empty() + ? (paths.vertexSource.empty() ? "" : "") + : paths.vertex; + std::string fragmentLabel = paths.fragment.empty() + ? (paths.fragmentSource.empty() ? "" : "") + : paths.fragment; logger_->Trace("PipelineService", "RegisterShader", "key=" + key + - ", vertex=" + paths.vertex + - ", fragment=" + paths.fragment); + ", vertex=" + vertexLabel + + ", fragment=" + fragmentLabel); shaderPathMap_[key] = paths; logger_->Debug("Registered shader: " + key); } @@ -260,35 +267,38 @@ void PipelineService::CreatePipelinesInternal(VkRenderPass renderPass, VkExtent2 // Create pipeline for each registered shader for (const auto& [key, paths] : shaderPathMap_) { - auto requireShader = [&](const std::string& label, const std::string& path) { - if (!HasShaderSource(path)) { + auto requireShader = [&](const std::string& label, + const std::string& path, + const std::string& source) { + if (!HasShaderSource(path, source)) { + std::string labelPath = path.empty() ? "" : path; throw std::runtime_error( - label + " shader not found: " + path + + label + " shader not found: " + labelPath + "\n\nShader key: " + key + "\n\nPlease ensure the shader source (.vert/.frag/etc.) exists."); } }; // Validate shader files exist - requireShader("Vertex", paths.vertex); - requireShader("Fragment", paths.fragment); + requireShader("Vertex", paths.vertex, paths.vertexSource); + requireShader("Fragment", paths.fragment, paths.fragmentSource); - bool hasGeometry = !paths.geometry.empty(); - bool hasTessControl = !paths.tessControl.empty(); - bool hasTessEval = !paths.tessEval.empty(); + bool hasGeometry = !paths.geometry.empty() || !paths.geometrySource.empty(); + bool hasTessControl = !paths.tessControl.empty() || !paths.tessControlSource.empty(); + bool hasTessEval = !paths.tessEval.empty() || !paths.tessEvalSource.empty(); if (hasGeometry) { - requireShader("Geometry", paths.geometry); + requireShader("Geometry", paths.geometry, paths.geometrySource); } if (hasTessControl != hasTessEval) { throw std::runtime_error( "Tessellation shaders require both 'tesc' and 'tese' paths. Shader key: " + key); } if (hasTessControl) { - requireShader("Tessellation control", paths.tessControl); + requireShader("Tessellation control", paths.tessControl, paths.tessControlSource); } if (hasTessEval) { - requireShader("Tessellation evaluation", paths.tessEval); + requireShader("Tessellation evaluation", paths.tessEval, paths.tessEvalSource); } std::vector shaderModules; @@ -302,8 +312,10 @@ void PipelineService::CreatePipelinesInternal(VkRenderPass renderPass, VkExtent2 shaderModules.clear(); }; - auto addStage = [&](VkShaderStageFlagBits stage, const std::string& path) { - const auto& shaderCode = ReadShaderFile(path, stage); + auto addStage = [&](VkShaderStageFlagBits stage, + const std::string& path, + const std::string& source) { + const auto& shaderCode = ReadShaderSource(path, source, stage); VkShaderModule shaderModule = CreateShaderModule(shaderCode); shaderModules.push_back(shaderModule); @@ -316,15 +328,17 @@ void PipelineService::CreatePipelinesInternal(VkRenderPass renderPass, VkExtent2 }; try { - addStage(VK_SHADER_STAGE_VERTEX_BIT, paths.vertex); + addStage(VK_SHADER_STAGE_VERTEX_BIT, paths.vertex, paths.vertexSource); if (hasTessControl) { - addStage(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, paths.tessControl); - addStage(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, paths.tessEval); + addStage(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, paths.tessControl, + paths.tessControlSource); + addStage(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, paths.tessEval, + paths.tessEvalSource); } if (hasGeometry) { - addStage(VK_SHADER_STAGE_GEOMETRY_BIT, paths.geometry); + addStage(VK_SHADER_STAGE_GEOMETRY_BIT, paths.geometry, paths.geometrySource); } - addStage(VK_SHADER_STAGE_FRAGMENT_BIT, paths.fragment); + addStage(VK_SHADER_STAGE_FRAGMENT_BIT, paths.fragment, paths.fragmentSource); VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = inputAssembly; VkPipelineTessellationStateCreateInfo tessellationState{}; @@ -388,7 +402,26 @@ VkShaderModule PipelineService::CreateShaderModule(const std::vector& code return shaderModule; } -bool PipelineService::HasShaderSource(const std::string& path) const { +std::string PipelineService::BuildShaderCacheKey(const std::string& path, + const std::string& source, + VkShaderStageFlagBits stage) const { + if (!source.empty()) { + size_t hash = std::hash{}(source); + return "inline:" + std::to_string(hash) + "|" + + std::to_string(static_cast(stage)); + } + std::filesystem::path shaderPath(path); + if (shaderPath.extension() == ".spv") { + shaderPath.replace_extension(); + } + return shaderPath.string() + "|" + + std::to_string(static_cast(stage)); +} + +bool PipelineService::HasShaderSource(const std::string& path, const std::string& source) const { + if (!source.empty()) { + return true; + } if (path.empty()) { return false; } @@ -399,59 +432,66 @@ bool PipelineService::HasShaderSource(const std::string& path) const { return std::filesystem::exists(shaderPath); } -const std::vector& PipelineService::ReadShaderFile(const std::string& path, VkShaderStageFlagBits stage) { - logger_->Trace("PipelineService", "ReadShaderFile", - "path=" + path + ", stage=" + std::to_string(static_cast(stage))); +const std::vector& PipelineService::ReadShaderSource(const std::string& path, + const std::string& source, + VkShaderStageFlagBits stage) { + logger_->Trace("PipelineService", "ReadShaderSource", + "path=" + path + ", stage=" + std::to_string(static_cast(stage)) + + ", source=" + std::string(source.empty() ? "path" : "inline")); - if (path.empty()) { - throw std::runtime_error("Shader path is empty"); + if (path.empty() && source.empty()) { + throw std::runtime_error("Shader path and source are empty"); } - std::filesystem::path shaderPath(path); - if (shaderPath.extension() == ".spv") { - std::filesystem::path sourcePath = shaderPath; - sourcePath.replace_extension(); - logger_->Trace("PipelineService", "ReadShaderFile", - "usingSource=" + sourcePath.string()); - shaderPath = sourcePath; - } - - if (!std::filesystem::exists(shaderPath)) { - throw std::runtime_error("Shader file not found: " + shaderPath.string() + - "\n\nPlease ensure the shader source (.vert/.frag/etc.) exists."); - } - - if (!std::filesystem::is_regular_file(shaderPath)) { - throw std::runtime_error("Path is not a regular file: " + shaderPath.string()); - } - - const std::string cacheKey = shaderPath.string() + "|" + - std::to_string(static_cast(stage)); + const std::string cacheKey = BuildShaderCacheKey(path, source, stage); auto cached = shaderSpirvCache_.find(cacheKey); if (cached != shaderSpirvCache_.end()) { - logger_->Trace("PipelineService", "ReadShaderFile", + logger_->Trace("PipelineService", "ReadShaderSource", "cacheHit=true, bytes=" + std::to_string(cached->second.size())); return cached->second; } - std::ifstream sourceFile(shaderPath); - if (!sourceFile) { - throw std::runtime_error("Failed to open shader source: " + shaderPath.string()); + std::string shaderLabel = path.empty() ? "" : path; + std::string shaderSource = source; + if (shaderSource.empty()) { + std::filesystem::path shaderPath(path); + if (shaderPath.extension() == ".spv") { + std::filesystem::path sourcePath = shaderPath; + sourcePath.replace_extension(); + logger_->Trace("PipelineService", "ReadShaderSource", + "usingSource=" + sourcePath.string()); + shaderPath = sourcePath; + } + + if (!std::filesystem::exists(shaderPath)) { + throw std::runtime_error("Shader file not found: " + shaderPath.string() + + "\n\nPlease ensure the shader source (.vert/.frag/etc.) exists."); + } + + if (!std::filesystem::is_regular_file(shaderPath)) { + throw std::runtime_error("Path is not a regular file: " + shaderPath.string()); + } + + std::ifstream sourceFile(shaderPath); + if (!sourceFile) { + throw std::runtime_error("Failed to open shader source: " + shaderPath.string()); + } + shaderSource.assign((std::istreambuf_iterator(sourceFile)), + std::istreambuf_iterator()); + sourceFile.close(); + shaderLabel = shaderPath.string(); } - std::string source((std::istreambuf_iterator(sourceFile)), - std::istreambuf_iterator()); - sourceFile.close(); 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, shaderPath.string().c_str(), options); + auto result = compiler.CompileGlslToSpv(shaderSource, kind, shaderLabel.c_str(), options); if (result.GetCompilationStatus() != shaderc_compilation_status_success) { std::string error = result.GetErrorMessage(); - logger_->Error("Shader compilation failed: " + shaderPath.string() + "\n" + error); - throw std::runtime_error("Shader compilation failed: " + shaderPath.string() + "\n" + error); + logger_->Error("Shader compilation failed: " + shaderLabel + "\n" + error); + throw std::runtime_error("Shader compilation failed: " + shaderLabel + "\n" + error); } std::vector spirv(result.cbegin(), result.cend()); @@ -460,11 +500,11 @@ const std::vector& PipelineService::ReadShaderFile(const std::string& path std::memcpy(buffer.data(), spirv.data(), buffer.size()); } - logger_->Debug("Compiled shader: " + shaderPath.string() + + logger_->Debug("Compiled shader: " + shaderLabel + " (" + std::to_string(buffer.size()) + " bytes)"); auto inserted = shaderSpirvCache_.emplace(cacheKey, std::move(buffer)); - logger_->Trace("PipelineService", "ReadShaderFile", + logger_->Trace("PipelineService", "ReadShaderSource", "cacheHit=false, bytes=" + std::to_string(inserted.first->second.size())); return inserted.first->second; } diff --git a/src/services/impl/pipeline_service.hpp b/src/services/impl/pipeline_service.hpp index 6f297e9..9ec1760 100644 --- a/src/services/impl/pipeline_service.hpp +++ b/src/services/impl/pipeline_service.hpp @@ -52,8 +52,13 @@ private: // Helper methods VkShaderModule CreateShaderModule(const std::vector& code); - const std::vector& ReadShaderFile(const std::string& path, VkShaderStageFlagBits stage); - bool HasShaderSource(const std::string& path) const; + const std::vector& ReadShaderSource(const std::string& path, + const std::string& source, + VkShaderStageFlagBits stage); + bool HasShaderSource(const std::string& path, const std::string& source) const; + std::string BuildShaderCacheKey(const std::string& path, + const std::string& source, + VkShaderStageFlagBits stage) const; void CreatePipelineLayout(); void CreatePipelinesInternal(VkRenderPass renderPass, VkExtent2D extent); void CleanupPipelines(); diff --git a/src/services/impl/shader_script_service.cpp b/src/services/impl/shader_script_service.cpp index 76f7ccb..96ec548 100644 --- a/src/services/impl/shader_script_service.cpp +++ b/src/services/impl/shader_script_service.cpp @@ -82,19 +82,6 @@ ShaderPaths ShaderScriptService::ReadShaderPathsTable(lua_State* L, int index) c ShaderPaths paths; int absIndex = lua_absindex(L, index); - 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"); - } - target = lua_tostring(L, -1); - lua_pop(L, 1); - }; - auto readOptionalPath = [&](const char* field, std::string& target) { lua_getfield(L, absIndex, field); if (lua_isstring(L, -1)) { @@ -109,12 +96,31 @@ ShaderPaths ShaderScriptService::ReadShaderPathsTable(lua_State* L, int index) c lua_pop(L, 1); }; - readRequiredPath("vertex", paths.vertex); - readRequiredPath("fragment", paths.fragment); + readOptionalPath("vertex", paths.vertex); + readOptionalPath("vertex_source", paths.vertexSource); + readOptionalPath("fragment", paths.fragment); + readOptionalPath("fragment_source", paths.fragmentSource); readOptionalPath("geometry", paths.geometry); + readOptionalPath("geometry_source", paths.geometrySource); readOptionalPath("tesc", paths.tessControl); + readOptionalPath("tesc_source", paths.tessControlSource); readOptionalPath("tese", paths.tessEval); + readOptionalPath("tese_source", paths.tessEvalSource); readOptionalPath("compute", paths.compute); + readOptionalPath("compute_source", paths.computeSource); + + auto requirePathOrSource = [&](const char* field, const std::string& path, const std::string& source) { + if (!path.empty() || !source.empty()) { + return; + } + if (logger_) { + logger_->Error("Shader stage '" + std::string(field) + "' must provide a path or source"); + } + throw std::runtime_error("Shader stage '" + std::string(field) + "' must provide a path or source"); + }; + + requirePathOrSource("vertex", paths.vertex, paths.vertexSource); + requirePathOrSource("fragment", paths.fragment, paths.fragmentSource); auto resolveIfPresent = [&](std::string& value) { if (!value.empty()) { diff --git a/src/services/interfaces/graphics_types.hpp b/src/services/interfaces/graphics_types.hpp index 7eee500..d5ffd52 100644 --- a/src/services/interfaces/graphics_types.hpp +++ b/src/services/interfaces/graphics_types.hpp @@ -21,11 +21,17 @@ struct GraphicsConfig { */ struct ShaderPaths { std::string vertex; + std::string vertexSource; std::string fragment; + std::string fragmentSource; std::string geometry; + std::string geometrySource; std::string tessControl; + std::string tessControlSource; std::string tessEval; + std::string tessEvalSource; std::string compute; + std::string computeSource; }; /** diff --git a/tests/test_gxm_backend.cpp b/tests/test_gxm_backend.cpp index 5169026..9fa16d9 100644 --- a/tests/test_gxm_backend.cpp +++ b/tests/test_gxm_backend.cpp @@ -45,7 +45,9 @@ int main() { std::cout << "GXM device created successfully\n"; // Test pipeline creation - sdl3cpp::services::ShaderPaths shaderPaths{"shaders/cube.vert", "shaders/cube.frag"}; + sdl3cpp::services::ShaderPaths shaderPaths; + shaderPaths.vertex = "shaders/gui_2d.vert"; + shaderPaths.fragment = "shaders/gui_2d.frag"; auto pipeline = backend->CreatePipeline(device, "test_shader", shaderPaths); Assert(pipeline != nullptr, "pipeline creation failed", failures); std::cout << "GXM pipeline created successfully\n"; @@ -100,4 +102,4 @@ int main() { } return failures; -} \ No newline at end of file +}