diff --git a/CMakeLists.txt b/CMakeLists.txt index 3385614..8dde4af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,12 @@ add_custom_target(panopainter_modernization_status COMMAND "${CMAKE_COMMAND}" -E echo "Debt log: docs/modernization/debt.md" VERBATIM) +add_custom_target(panopainter_validate_shaders + COMMAND "${CMAKE_COMMAND}" + "-DPP_SHADER_DIR=${CMAKE_CURRENT_SOURCE_DIR}/data/shaders" + -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ValidatePanoPainterShaders.cmake" + VERBATIM) + add_library(pp_foundation STATIC src/foundation/binary_stream.cpp src/foundation/parse.cpp diff --git a/cmake/ValidatePanoPainterShaders.cmake b/cmake/ValidatePanoPainterShaders.cmake new file mode 100644 index 0000000..5df22f6 --- /dev/null +++ b/cmake/ValidatePanoPainterShaders.cmake @@ -0,0 +1,75 @@ +if(NOT DEFINED PP_SHADER_DIR) + message(FATAL_ERROR "PP_SHADER_DIR is required") +endif() + +file(REAL_PATH "${PP_SHADER_DIR}" pp_shader_dir) +if(NOT IS_DIRECTORY "${pp_shader_dir}") + message(FATAL_ERROR "Shader directory does not exist: ${pp_shader_dir}") +endif() + +file(GLOB_RECURSE pp_shader_files + "${pp_shader_dir}/*.glsl") + +if(NOT pp_shader_files) + message(FATAL_ERROR "No shader files found under: ${pp_shader_dir}") +endif() + +set(pp_shader_errors "") +set(pp_top_level_count 0) +set(pp_include_count 0) + +foreach(pp_shader_file IN LISTS pp_shader_files) + file(RELATIVE_PATH pp_shader_rel "${pp_shader_dir}" "${pp_shader_file}") + file(READ "${pp_shader_file}" pp_shader_contents) + + string(REGEX MATCHALL "#[ \t]*include[ \t]+\"[^\"]+\"" pp_include_lines "${pp_shader_contents}") + foreach(pp_include_line IN LISTS pp_include_lines) + string(REGEX REPLACE ".*\"([^\"]+)\".*" "\\1" pp_include_path "${pp_include_line}") + if(pp_include_path MATCHES "^/") + list(APPEND pp_shader_errors "${pp_shader_rel}: include path must be relative: ${pp_include_path}") + endif() + if(pp_include_path MATCHES "^[A-Za-z]:") + list(APPEND pp_shader_errors "${pp_shader_rel}: include path must not be drive-absolute: ${pp_include_path}") + endif() + if(pp_include_path MATCHES "\\.\\.") + list(APPEND pp_shader_errors "${pp_shader_rel}: include path must not traverse parent directories: ${pp_include_path}") + endif() + if(NOT EXISTS "${pp_shader_dir}/${pp_include_path}") + list(APPEND pp_shader_errors "${pp_shader_rel}: missing include: ${pp_include_path}") + endif() + endforeach() + + if(pp_shader_rel MATCHES "^include/") + math(EXPR pp_include_count "${pp_include_count} + 1") + if(pp_shader_contents MATCHES "\\[\\[(vertex|fragment)\\]\\]") + list(APPEND pp_shader_errors "${pp_shader_rel}: include shaders must not declare stage markers") + endif() + else() + math(EXPR pp_top_level_count "${pp_top_level_count} + 1") + + string(REGEX MATCHALL "\\[\\[vertex\\]\\]" pp_vertex_markers "${pp_shader_contents}") + string(REGEX MATCHALL "\\[\\[fragment\\]\\]" pp_fragment_markers "${pp_shader_contents}") + list(LENGTH pp_vertex_markers pp_vertex_count) + list(LENGTH pp_fragment_markers pp_fragment_count) + + if(NOT pp_vertex_count EQUAL 1) + list(APPEND pp_shader_errors "${pp_shader_rel}: expected exactly one [[vertex]] marker") + endif() + if(NOT pp_fragment_count EQUAL 1) + list(APPEND pp_shader_errors "${pp_shader_rel}: expected exactly one [[fragment]] marker") + endif() + + string(FIND "${pp_shader_contents}" "[[vertex]]" pp_vertex_pos) + string(FIND "${pp_shader_contents}" "[[fragment]]" pp_fragment_pos) + if(pp_vertex_pos GREATER_EQUAL 0 AND pp_fragment_pos GREATER_EQUAL 0 AND NOT pp_vertex_pos LESS pp_fragment_pos) + list(APPEND pp_shader_errors "${pp_shader_rel}: [[vertex]] marker must appear before [[fragment]]") + endif() + endif() +endforeach() + +if(pp_shader_errors) + list(JOIN pp_shader_errors "\n" pp_shader_error_text) + message(FATAL_ERROR "Shader validation failed:\n${pp_shader_error_text}") +endif() + +message(STATUS "Validated ${pp_top_level_count} shader programs and ${pp_include_count} shader includes under ${pp_shader_dir}") diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 9898b4d..74347f5 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -63,6 +63,8 @@ cmake --build --preset windows-msvc-default --config Debug --target PanoPainter ctest --preset desktop-fast --build-config Debug powershell -ExecutionPolicy Bypass -File scripts\automation\test.ps1 -Preset desktop-fast -Configuration Debug powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli +cmake --build --preset windows-msvc-default --target panopainter_validate_shaders +powershell -ExecutionPolicy Bypass -File scripts\automation\analyze.ps1 -Preset windows-msvc-default -NoApp cmake --preset android-arm64 powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64 powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug @@ -79,6 +81,9 @@ Known local toolchain state: `pp_paint`, `pp_document`, `pp_renderer_api`, `pp_paint_renderer`, `pp_ui_core`, `pano_cli`, and their current headless test binaries, including PPI header and layout XML parse coverage. +- `panopainter_validate_shaders` validates the current combined GLSL shader + files for one vertex stage marker, one fragment stage marker, valid marker + order, and existing relative includes. - `vcpkg` is not on PATH yet; see DEBT-0007. Known warnings after the current CMake app build: diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 648a8d9..b3a0f77 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -188,9 +188,10 @@ Gate: Goal: turn the build into an error-finding system before deep refactors. -Status: in progress. Initial warning/sanitizer option targets and `vcpkg.json` -exist. Dependency migration is not complete until component targets consume -vcpkg packages and platform triplets are validated. +Status: in progress. Initial warning/sanitizer option targets, `vcpkg.json`, +and a headless `panopainter_validate_shaders` target exist. Dependency +migration is not complete until component targets consume vcpkg packages and +platform triplets are validated. Implementation tasks: @@ -524,6 +525,8 @@ cmake --build --preset windows-msvc-default --config Debug --target pp_foundatio ctest --preset desktop-fast --build-config Debug powershell -ExecutionPolicy Bypass -File scripts\automation\test.ps1 -Preset desktop-fast -Configuration Debug powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli +cmake --build --preset windows-msvc-default --target panopainter_validate_shaders +powershell -ExecutionPolicy Bypass -File scripts\automation\analyze.ps1 -Preset windows-msvc-default -NoApp cmake --preset android-arm64 powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64 powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug @@ -546,6 +549,10 @@ Results: - `pano_cli_inspect_image_rejects_unsupported` passed as an expected failure test. - `pano_cli_parse_layout_smoke` passed. +- `panopainter_validate_shaders` passed, validating 25 shader programs and 7 + shader includes for stage markers and include graph integrity. +- PowerShell analyze automation returns JSON summaries and includes the shader + validation target. - `PanoPainter.exe` built through CMake at `out/build/windows-msvc-default/Debug/PanoPainter.exe`. - PowerShell build/test automation wrappers return JSON summaries and passed diff --git a/scripts/automation/analyze.ps1 b/scripts/automation/analyze.ps1 index bb34a97..01551fb 100644 --- a/scripts/automation/analyze.ps1 +++ b/scripts/automation/analyze.ps1 @@ -17,14 +17,36 @@ if ($NoApp) { } & cmake @argsList -$exitCode = $LASTEXITCODE +$configureExitCode = $LASTEXITCODE +$shaderExitCode = 0 + +if ($configureExitCode -eq 0) { + & cmake --build --preset $Preset --target panopainter_validate_shaders + $shaderExitCode = $LASTEXITCODE +} + +$exitCode = $configureExitCode +if ($exitCode -eq 0 -and $shaderExitCode -ne 0) { + $exitCode = $shaderExitCode +} + $elapsed = [int]((Get-Date) - $started).TotalMilliseconds [ordered]@{ - command = "analyze-configure" + command = "analyze" preset = $Preset exitCode = $exitCode + checks = @( + [ordered]@{ + name = "configure" + exitCode = $configureExitCode + }, + [ordered]@{ + name = "shader-validation" + exitCode = $shaderExitCode + } + ) elapsedMs = $elapsed -} | ConvertTo-Json -Compress +} | ConvertTo-Json -Compress -Depth 4 exit $exitCode diff --git a/scripts/automation/analyze.sh b/scripts/automation/analyze.sh index d3098bc..d81b811 100644 --- a/scripts/automation/analyze.sh +++ b/scripts/automation/analyze.sh @@ -4,8 +4,17 @@ set -u preset="${1:-linux-clang}" start="$(date +%s)" cmake --preset "$preset" -DPP_ENABLE_CLANG_TIDY=ON -DPP_ENABLE_CPPCHECK=ON -exit_code="$?" +configure_exit_code="$?" +shader_exit_code="0" +if [ "$configure_exit_code" -eq 0 ]; then + cmake --build --preset "$preset" --target panopainter_validate_shaders + shader_exit_code="$?" +fi +exit_code="$configure_exit_code" +if [ "$exit_code" -eq 0 ] && [ "$shader_exit_code" -ne 0 ]; then + exit_code="$shader_exit_code" +fi end="$(date +%s)" elapsed_ms="$(( (end - start) * 1000 ))" -printf '{"command":"analyze-configure","preset":"%s","exitCode":%s,"elapsedMs":%s}\n' "$preset" "$exit_code" "$elapsed_ms" +printf '{"command":"analyze","preset":"%s","exitCode":%s,"checks":[{"name":"configure","exitCode":%s},{"name":"shader-validation","exitCode":%s}],"elapsedMs":%s}\n' "$preset" "$exit_code" "$configure_exit_code" "$shader_exit_code" "$elapsed_ms" exit "$exit_code"