From c38ff8209b3d0038a1c75aefcf6093ebfa2e8e58 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sun, 31 May 2026 23:40:43 +0200 Subject: [PATCH] Start CMake modernization scaffold --- .gitignore | 2 + CMakeLists.txt | 164 +++++ CMakePresets.json | 174 +++++ PanoPainter.sln | 57 -- PanoPainter.vcxproj | 634 ---------------- PanoPainter.vcxproj.filters | 854 ---------------------- cmake/PanoPainterOptions.cmake | 16 + cmake/PanoPainterSources.cmake | 143 ++++ cmake/PanoPainterVersion.cmake | 17 + cmake/PanoPainterWarnings.cmake | 40 + docs/adr/0001-modernization-boundaries.md | 49 ++ docs/modernization/build-inventory.md | 83 +++ docs/modernization/capability-map.md | 83 +++ docs/modernization/debt.md | 34 + docs/modernization/roadmap.md | 553 ++++++++++++++ libs/wacom/WinTab/Utils.cpp | 2 +- scripts/automation/analyze.ps1 | 30 + scripts/automation/analyze.sh | 11 + scripts/automation/build.ps1 | 28 + scripts/automation/build.sh | 17 + scripts/automation/configure.ps1 | 25 + scripts/automation/configure.sh | 11 + scripts/automation/test.ps1 | 22 + scripts/automation/test.sh | 12 + src/foundation/binary_stream.cpp | 142 ++++ src/foundation/binary_stream.h | 46 ++ src/foundation/result.h | 80 ++ src/main.cpp | 22 +- src/pch.h | 7 +- src/version.cpp | 8 +- tests/CMakeLists.txt | 23 + tests/foundation/binary_stream_tests.cpp | 100 +++ tests/test_harness.h | 50 ++ tools/pano_cli/CMakeLists.txt | 6 + tools/pano_cli/main.cpp | 113 +++ vcpkg.json | 16 + 36 files changed, 2118 insertions(+), 1556 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 CMakePresets.json delete mode 100644 PanoPainter.sln delete mode 100644 PanoPainter.vcxproj delete mode 100644 PanoPainter.vcxproj.filters create mode 100644 cmake/PanoPainterOptions.cmake create mode 100644 cmake/PanoPainterSources.cmake create mode 100644 cmake/PanoPainterVersion.cmake create mode 100644 cmake/PanoPainterWarnings.cmake create mode 100644 docs/adr/0001-modernization-boundaries.md create mode 100644 docs/modernization/build-inventory.md create mode 100644 docs/modernization/capability-map.md create mode 100644 docs/modernization/debt.md create mode 100644 docs/modernization/roadmap.md create mode 100644 scripts/automation/analyze.ps1 create mode 100644 scripts/automation/analyze.sh create mode 100644 scripts/automation/build.ps1 create mode 100644 scripts/automation/build.sh create mode 100644 scripts/automation/configure.ps1 create mode 100644 scripts/automation/configure.sh create mode 100644 scripts/automation/test.ps1 create mode 100644 scripts/automation/test.sh create mode 100644 src/foundation/binary_stream.cpp create mode 100644 src/foundation/binary_stream.h create mode 100644 src/foundation/result.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/foundation/binary_stream_tests.cpp create mode 100644 tests/test_harness.h create mode 100644 tools/pano_cli/CMakeLists.txt create mode 100644 tools/pano_cli/main.cpp create mode 100644 vcpkg.json diff --git a/.gitignore b/.gitignore index fc82edf..be919ea 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ linux/Makefile webgl/build webgl/.vscode + +out/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8b2ac24 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,164 @@ +cmake_minimum_required(VERSION 3.29) + +project(PanoPainter + VERSION 0.0.0 + DESCRIPTION "Panoramic painting and animation application" + LANGUAGES C CXX) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() + +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +include(PanoPainterOptions) +include(PanoPainterWarnings) +include(PanoPainterSources) +include(PanoPainterVersion) + +if(PP_ENABLE_CLANG_TIDY) + find_program(PP_CLANG_TIDY_EXE NAMES clang-tidy) + if(PP_CLANG_TIDY_EXE) + set(CMAKE_CXX_CLANG_TIDY "${PP_CLANG_TIDY_EXE}") + else() + message(WARNING "PP_ENABLE_CLANG_TIDY is ON but clang-tidy was not found.") + endif() +endif() + +if(PP_ENABLE_CPPCHECK) + find_program(PP_CPPCHECK_EXE NAMES cppcheck) + if(PP_CPPCHECK_EXE) + set(CMAKE_CXX_CPPCHECK + "${PP_CPPCHECK_EXE}" + "--enable=warning,style,performance,portability" + "--inline-suppr" + "--suppress=missingIncludeSystem") + else() + message(WARNING "PP_ENABLE_CPPCHECK is ON but cppcheck was not found.") + endif() +endif() + +add_library(pp_project_options INTERFACE) +target_compile_features(pp_project_options INTERFACE cxx_std_23) + +add_library(pp_project_warnings INTERFACE) +pp_configure_project_warnings(pp_project_warnings) + +add_custom_target(panopainter_modernization_status + COMMAND "${CMAKE_COMMAND}" -E echo "PanoPainter modernization scaffold configured." + COMMAND "${CMAKE_COMMAND}" -E echo "Roadmap: docs/modernization/roadmap.md" + COMMAND "${CMAKE_COMMAND}" -E echo "Debt log: docs/modernization/debt.md" + VERBATIM) + +add_library(pp_foundation STATIC + src/foundation/binary_stream.cpp) +target_include_directories(pp_foundation + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/src") +target_link_libraries(pp_foundation + PUBLIC + pp_project_options + PRIVATE + pp_project_warnings) + +if(PP_BUILD_TOOLS) + add_subdirectory(tools/pano_cli) +endif() + +if(PP_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + +if(PP_BUILD_APP) + if(WIN32) + add_library(pp_legacy_app STATIC + ${PP_LEGACY_APP_SOURCES} + ${PP_VENDOR_SOURCES}) + + target_link_libraries(pp_legacy_app + PUBLIC + pp_project_options + PRIVATE + pp_project_warnings) + + target_include_directories(pp_legacy_app + PUBLIC + ${PP_LEGACY_INCLUDE_DIRS}) + + target_compile_definitions(pp_legacy_app + PUBLIC + ENUM_BITFIELDS_NOT_SUPPORTED + UNICODE + _UNICODE + _CRT_SECURE_NO_WARNINGS + _SCL_SECURE_NO_WARNINGS + _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING + _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING + _CONSOLE + WITH_CURL=1) + set_target_properties(pp_legacy_app PROPERTIES + VS_GLOBAL_CharacterSet "Unicode") + + target_precompile_headers(pp_legacy_app PRIVATE src/pch.h) + set_source_files_properties(${PP_VENDOR_SOURCES} + PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + set_source_files_properties(src/version.cpp + PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + + add_executable(PanoPainter WIN32 + ${PP_WINDOWS_APP_SOURCES}) + + target_link_libraries(PanoPainter + PRIVATE + pp_project_options + pp_project_warnings + pp_legacy_app + "${CMAKE_CURRENT_SOURCE_DIR}/libs/bugtrap-client/lib/BugTrapU-x64.lib" + "$<$:${CMAKE_CURRENT_SOURCE_DIR}/libs/curl-win/lib/dll-debug-x64/libcurl_debug.lib>" + "$<$>:${CMAKE_CURRENT_SOURCE_DIR}/libs/curl-win/lib/dll-release-x64/libcurl.lib>" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/libyuv/lib/win/yuv.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/mp4v2/lib/win/libmp4v2.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/openh264/lib/openh264-2.0.0-win64.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/openvr/lib/win64/openvr_api.lib" + comdlg32 + gdi32 + opengl32 + ole32 + shell32 + shlwapi + user32 + wbemuuid) + + target_precompile_headers(PanoPainter REUSE_FROM pp_legacy_app) + set_target_properties(PanoPainter PROPERTIES + VS_GLOBAL_CharacterSet "Unicode") + + pp_add_version_generation(PanoPainter "$,debug,release>") + + add_custom_command(TARGET PanoPainter POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/data" + "$/data" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/libs/bugtrap-client/lib/BugTrapU-x64.dll" + "$/BugTrapU-x64.dll" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "$<$:${CMAKE_CURRENT_SOURCE_DIR}/libs/curl-win/lib/dll-debug-x64/libcurl_debug.dll>$<$>:${CMAKE_CURRENT_SOURCE_DIR}/libs/curl-win/lib/dll-release-x64/libcurl.dll>" + "$/" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/libs/libyuv/lib/win/libyuv.dll" + "$/libyuv.dll" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/libs/mp4v2/lib/win/libmp4v2.dll" + "$/libmp4v2.dll" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/libs/openh264/lib/openh264-2.0.0-win64.dll" + "$/openh264-2.0.0-win64.dll" + VERBATIM) + else() + message(WARNING "PP_BUILD_APP is enabled, but the root CMake app target is currently Windows-only. Platform alignment is tracked in Phase 6.") + endif() +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..e86aafa --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,174 @@ +{ + "version": 8, + "cmakeMinimumRequired": { + "major": 3, + "minor": 29, + "patch": 0 + }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "PP_BUILD_APP": "ON", + "PP_BUILD_TESTS": "ON", + "PP_BUILD_TOOLS": "ON", + "PP_ENABLE_OPENGL": "ON", + "PP_ENABLE_VULKAN_EXPERIMENTAL": "OFF", + "PP_ENABLE_VR": "ON", + "PP_ENABLE_CLOUD": "ON", + "PP_ENABLE_VIDEO": "ON" + } + }, + { + "name": "windows-vs2026-x64", + "inherits": "base", + "displayName": "Windows VS 2026 x64", + "generator": "Visual Studio 18 2026", + "architecture": "x64" + }, + { + "name": "windows-msvc-default", + "inherits": "base", + "displayName": "Windows MSVC default generator", + "architecture": "x64" + }, + { + "name": "windows-clangcl-asan", + "inherits": "base", + "displayName": "Windows clang-cl ASan", + "generator": "Ninja", + "toolset": "ClangCL", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "PP_ENABLE_ASAN": "ON", + "PP_ENABLE_UBSAN": "OFF" + } + }, + { + "name": "linux-clang", + "inherits": "base", + "displayName": "Linux clang", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "android-arm64", + "inherits": "base", + "displayName": "Android arm64-v8a", + "generator": "Ninja", + "cacheVariables": { + "ANDROID_ABI": "arm64-v8a", + "ANDROID_PLATFORM": "android-26" + } + }, + { + "name": "android-x64", + "inherits": "base", + "displayName": "Android x86_64", + "generator": "Ninja", + "cacheVariables": { + "ANDROID_ABI": "x86_64", + "ANDROID_PLATFORM": "android-26" + } + }, + { + "name": "emscripten", + "inherits": "base", + "displayName": "Emscripten WebGL", + "generator": "Ninja", + "cacheVariables": { + "PP_ENABLE_VR": "OFF", + "PP_ENABLE_VIDEO": "OFF" + } + }, + { + "name": "macos", + "inherits": "base", + "displayName": "macOS", + "generator": "Ninja" + }, + { + "name": "ios-device", + "inherits": "base", + "displayName": "iOS device", + "generator": "Xcode", + "cacheVariables": { + "CMAKE_SYSTEM_NAME": "iOS", + "CMAKE_OSX_SYSROOT": "iphoneos" + } + }, + { + "name": "ios-simulator", + "inherits": "base", + "displayName": "iOS simulator", + "generator": "Xcode", + "cacheVariables": { + "CMAKE_SYSTEM_NAME": "iOS", + "CMAKE_OSX_SYSROOT": "iphonesimulator" + } + } + ], + "buildPresets": [ + { + "name": "windows-vs2026-x64", + "configurePreset": "windows-vs2026-x64" + }, + { + "name": "windows-msvc-default", + "configurePreset": "windows-msvc-default" + }, + { + "name": "windows-clangcl-asan", + "configurePreset": "windows-clangcl-asan" + }, + { + "name": "linux-clang", + "configurePreset": "linux-clang" + } + ], + "testPresets": [ + { + "name": "desktop-fast", + "configurePreset": "windows-msvc-default", + "output": { + "outputOnFailure": true + }, + "filter": { + "exclude": { + "label": "gpu|slow|platform" + } + } + }, + { + "name": "desktop-fast-vs2026", + "configurePreset": "windows-vs2026-x64", + "output": { + "outputOnFailure": true + }, + "filter": { + "exclude": { + "label": "gpu|slow|platform" + } + } + }, + { + "name": "desktop-gpu", + "configurePreset": "windows-msvc-default", + "output": { + "outputOnFailure": true + }, + "filter": { + "include": { + "label": "gpu" + } + } + } + ] +} diff --git a/PanoPainter.sln b/PanoPainter.sln deleted file mode 100644 index 53e579d..0000000 --- a/PanoPainter.sln +++ /dev/null @@ -1,57 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28010.2026 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PanoPainter", "PanoPainter.vcxproj", "{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}" -EndProject -Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "PanoPainterPackage", "PanoPainterPackage\PanoPainterPackage.wapproj", "{3A716FB6-DE62-439F-83B6-3C40915D6678}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|x64.ActiveCfg = Debug|x64 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|x64.Build.0 = Debug|x64 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|x64.Deploy.0 = Debug|x64 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|x86.ActiveCfg = Debug|Win32 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|x86.Build.0 = Debug|Win32 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|Any CPU.ActiveCfg = Release|Win32 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|x64.ActiveCfg = Release|x64 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|x64.Build.0 = Release|x64 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|x64.Deploy.0 = Release|x64 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|x86.ActiveCfg = Release|Win32 - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|x86.Build.0 = Release|Win32 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x64.ActiveCfg = Debug|x64 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x64.Build.0 = Debug|x64 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x64.Deploy.0 = Debug|x64 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x86.ActiveCfg = Debug|x86 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x86.Build.0 = Debug|x86 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x86.Deploy.0 = Debug|x86 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|Any CPU.Build.0 = Release|Any CPU - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|Any CPU.Deploy.0 = Release|Any CPU - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x64.ActiveCfg = Release|x64 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x64.Build.0 = Release|x64 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x64.Deploy.0 = Release|x64 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x86.ActiveCfg = Release|x86 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x86.Build.0 = Release|x86 - {3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x86.Deploy.0 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3E8EFC4B-CEA1-4408-8628-7D2C0F6C43C8} - EndGlobalSection -EndGlobal diff --git a/PanoPainter.vcxproj b/PanoPainter.vcxproj deleted file mode 100644 index dd1727f..0000000 --- a/PanoPainter.vcxproj +++ /dev/null @@ -1,634 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {6D5028CE-4D76-4B6A-A7C2-DE5A3268D433} - Win32Proj - PanoPainter - 8.1 - PanoPainter - - - - Application - true - v141 - Unicode - - - Application - false - v141 - true - Unicode - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - libs\glm;libs\glew-2.0.0\include;libs\stb;libs\tinyxml2;libs\yoga;libs\curl-win\include;libs\jpeg;libs\wacom;C:\Users\omar\Downloads\BugTrap-master\BugTrap-master\source\Client;$(IncludePath) - libs\curl-win\lib\dll-$(Configuration)-$(PlatformShortName);libs\glew-2.0.0\lib\Release\$(Platform);C:\Users\omar\Downloads\BugTrap-master\BugTrap-master\bin;$(LibraryPath) - - - true - libs\glm;libs\glew-2.0.0\include;libs\stb;libs\tinyxml2;libs\yoga;libs\curl-win\include;libs\jpeg;libs\wacom;libs\bugtrap-client\include;libs\poly2tri\poly2tri;libs\base64;libs\sqlite3;libs\openvr\headers;libs\nanort;libs\hash-library;libs\fmt\include;libs\glad\include;libs\openh264\include;libs\mp4v2\include;libs\libyuv\include;C:\Program Files\RenderDoc;$(IncludePath) - libs\curl-win\lib\dll-$(Configuration)-$(PlatformShortName);libs\glew-2.0.0\lib\Release\$(Platform);libs\bugtrap-client\lib;libs\openvr\lib\win64;libs\openh264\lib;libs\mp4v2\lib\win;libs\libyuv\lib\win;$(LibraryPath) - - - false - libs\glm;libs\glew-2.0.0\include;libs\stb;libs\tinyxml2;libs\yoga;libs\curl-win\include;libs\jpeg;libs\wacom;C:\Users\omar\Downloads\BugTrap-master\BugTrap-master\source\Client;$(IncludePath) - libs\curl-win\lib\dll-$(Configuration)-$(PlatformShortName);libs\glew-2.0.0\lib\Release\$(Platform);C:\Users\omar\Downloads\BugTrap-master\BugTrap-master\bin;$(LibraryPath) - - - false - libs\glm;libs\glew-2.0.0\include;libs\stb;libs\tinyxml2;libs\yoga;libs\curl-win\include;libs\jpeg;libs\wacom;libs\bugtrap-client\include;libs\poly2tri\poly2tri;libs\base64;libs\sqlite3;libs\openvr\headers;libs\nanort;libs\hash-library;libs\fmt\include;libs\glad\include;libs\openh264\include;libs\mp4v2\include;libs\libyuv\include;C:\Program Files\RenderDoc;$(IncludePath) - libs\curl-win\lib\dll-$(Configuration)-$(PlatformShortName);libs\glew-2.0.0\lib\Release\$(Platform);libs\bugtrap-client\lib;libs\openvr\lib\win64;libs\openh264\lib;libs\mp4v2\lib\win;libs\libyuv\lib\win;$(LibraryPath) - - - - Use - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - pch.h - - - Console - true - - - - - - - - - Use - Level3 - Disabled - ENUM_BITFIELDS_NOT_SUPPORTED;DEBUG;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - pch.h - false - - - Console - true - - - python .\scripts\pre-build.py debug - - - - - Level3 - Use - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - pch.h - MultiThreadedDLL - - - Console - true - true - true - - - - - - - - - Level3 - Use - MaxSpeed - true - true - ENUM_BITFIELDS_NOT_SUPPORTED;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - pch.h - MultiThreadedDLL - false - - - Windows - true - true - true - - - python .\scripts\pre-build.py release - - - - - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - NotUsing - NotUsing - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - $(IntDir)yoga\ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - Create - Create - Create - - - - - - - - - - NotUsing - NotUsing - - - - NotUsing - NotUsing - NotUsing - NotUsing - - - NotUsing - NotUsing - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - - - NotUsing - NotUsing - NotUsing - NotUsing - - - Use - Use - Use - Use - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - true - true - true - - - - - - \ No newline at end of file diff --git a/PanoPainter.vcxproj.filters b/PanoPainter.vcxproj.filters deleted file mode 100644 index e17b38b..0000000 --- a/PanoPainter.vcxproj.filters +++ /dev/null @@ -1,854 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {600b8daa-4234-4c37-b4ba-c22cad7d1dc3} - - - {6d64b115-02d1-43e0-86c8-c8212f51162d} - - - {dc178d53-6a6d-4a18-a93c-d4994340515f} - - - {54dc9f46-d2e0-466c-90d2-eb5d72d5799d} - - - {a4a12057-835e-47ff-be4d-ce58b36cecf5} - - - {6fe315aa-e2b9-4f01-8291-683a5fda123b} - - - {bda6fa93-a186-41ca-9bd9-49b7e0fd1ca4} - - - {e631ac80-1b9b-424f-8adf-e2bab71a566d} - - - {ef44d179-f28b-458c-b3df-be2895553149} - - - {be0c0053-abd8-4e2d-a294-7c54511b05a6} - - - {2a784067-6741-47a3-b668-cc45f2224286} - - - {7b4f5b47-7a8b-4e4c-9e82-399bb5047ffc} - - - {b55fb692-a845-4ef2-9b0e-5b2dd8bd125f} - - - {a2cacb13-2854-44ee-9511-6cb8ac587428} - - - {ca37521b-213f-4bcf-acfd-eda1483a30b2} - - - {5ecb54ed-7c3d-46fd-9b5d-227abdbc5954} - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files - - - Source Files - - - Source Files - - - libs\jpeg - - - libs\jpeg - - - libs\tinyxml2 - - - Source Files - - - libs\WinTab - - - Source Files\ui - - - Source Files - - - libs\poly2tri - - - libs\poly2tri - - - libs\poly2tri - - - libs\poly2tri - - - libs\poly2tri - - - Source Files\ui - - - Source Files\ui - - - Source Files - - - Source Files\ui - - - Source Files - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - libs\sqlite3 - - - Source Files - - - Source Files - - - libs\nanort - - - libs\hash - - - Source Files - - - libs\fmt - - - libs\fmt - - - Source Files\ui - - - Source Files - - - Source Files - - - libs\yoga - - - libs\yoga - - - libs\yoga - - - libs\yoga - - - libs\yoga - - - libs\yoga - - - libs\yoga - - - libs\yoga - - - libs\yoga - - - libs\yoga - - - Source Files\ui - - - Source Files - - - Source Files - - - Source Files - - - Source Files\ui - - - libs\glad - - - libs\glad - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - libs\yoga - - - libs\yoga - - - Source Files - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - - - libs\jpeg - - - libs\jpeg - - - libs\tinyxml2 - - - libs\WinTab - - - libs\WinTab - - - libs\WinTab - - - libs\WinTab - - - libs\sqlite3 - - - libs\sqlite3 - - - libs\nanort - - - libs\hash - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files\ui - - - Source Files - - - - - Resource Files - - - - - extras - - - - - extras - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - extras\dialogs - - - - - shaders - - - shaders\include - - - shaders\include - - - shaders\include - - - shaders\include - - - shaders\include - - - shaders\include - - - shaders\include - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - shaders - - - - - Resource Files - - - - - extras - - - \ No newline at end of file diff --git a/cmake/PanoPainterOptions.cmake b/cmake/PanoPainterOptions.cmake new file mode 100644 index 0000000..861c68b --- /dev/null +++ b/cmake/PanoPainterOptions.cmake @@ -0,0 +1,16 @@ +option(PP_BUILD_APP "Build the PanoPainter application target from root CMake." ON) +option(PP_BUILD_TESTS "Build PanoPainter tests." ON) +option(PP_BUILD_TOOLS "Build PanoPainter automation tools." ON) + +option(PP_ENABLE_OPENGL "Enable the OpenGL renderer backend." ON) +option(PP_ENABLE_VULKAN_EXPERIMENTAL "Enable non-production Vulkan experiments." OFF) +option(PP_ENABLE_VR "Enable VR support." ON) +option(PP_ENABLE_CLOUD "Enable cloud/network features." ON) +option(PP_ENABLE_VIDEO "Enable MP4/timelapse video features." ON) + +option(PP_ENABLE_ASAN "Enable AddressSanitizer where supported." OFF) +option(PP_ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer where supported." OFF) +option(PP_ENABLE_TSAN "Enable ThreadSanitizer for headless targets where supported." OFF) +option(PP_ENABLE_MSVC_ANALYZE "Enable MSVC static analysis." OFF) +option(PP_ENABLE_CLANG_TIDY "Enable clang-tidy integration." OFF) +option(PP_ENABLE_CPPCHECK "Enable cppcheck integration." OFF) diff --git a/cmake/PanoPainterSources.cmake b/cmake/PanoPainterSources.cmake new file mode 100644 index 0000000..026f46b --- /dev/null +++ b/cmake/PanoPainterSources.cmake @@ -0,0 +1,143 @@ +set(PP_LEGACY_APP_SOURCES + src/abr.cpp + src/action.cpp + src/app.cpp + src/app_cloud.cpp + src/app_commands.cpp + src/app_dialogs.cpp + src/app_events.cpp + src/app_layout.cpp + src/app_shaders.cpp + src/app_vr.cpp + src/asset.cpp + src/bezier.cpp + src/binary_stream.cpp + src/brush.cpp + src/canvas.cpp + src/canvas_actions.cpp + src/canvas_layer.cpp + src/canvas_modes.cpp + src/event.cpp + src/font.cpp + src/hmd.cpp + src/image.cpp + src/layout.cpp + src/log.cpp + src/mp4enc.cpp + src/node.cpp + src/node_about.cpp + src/node_border.cpp + src/node_button.cpp + src/node_button_custom.cpp + src/node_canvas.cpp + src/node_changelog.cpp + src/node_checkbox.cpp + src/node_color_quad.cpp + src/node_colorwheel.cpp + src/node_combobox.cpp + src/node_dialog_browse.cpp + src/node_dialog_cloud.cpp + src/node_dialog_export_ppbr.cpp + src/node_dialog_layer_rename.cpp + src/node_dialog_open.cpp + src/node_dialog_picker.cpp + src/node_dialog_resize.cpp + src/node_icon.cpp + src/node_image.cpp + src/node_image_texture.cpp + src/node_input_box.cpp + src/node_message_box.cpp + src/node_metadata.cpp + src/node_panel_animation.cpp + src/node_panel_brush.cpp + src/node_panel_color.cpp + src/node_panel_floating.cpp + src/node_panel_grid.cpp + src/node_panel_layer.cpp + src/node_panel_quick.cpp + src/node_panel_stroke.cpp + src/node_popup_menu.cpp + src/node_progress_bar.cpp + src/node_remote_page.cpp + src/node_scroll.cpp + src/node_settings.cpp + src/node_shorcuts.cpp + src/node_slider.cpp + src/node_stroke_preview.cpp + src/node_text.cpp + src/node_text_input.cpp + src/node_tool_bucket.cpp + src/node_usermanual.cpp + src/node_viewport.cpp + src/pch.cpp + src/rtt.cpp + src/serializer.cpp + src/settings.cpp + src/shader.cpp + src/shape.cpp + src/texture.cpp + src/util.cpp + src/version.cpp + src/wacom.cpp +) + +set(PP_WINDOWS_APP_SOURCES + src/main.cpp + PanoPainter.rc +) + +set(PP_VENDOR_SOURCES + libs/fmt/src/format.cc + libs/fmt/src/posix.cc + libs/glad/src/glad.c + libs/glad/src/glad_wgl.c + libs/hash-library/md5.cpp + libs/jpeg/jpgd.cpp + libs/jpeg/jpge.cpp + libs/nanort/nanort.cc + libs/poly2tri/poly2tri/common/shapes.cc + libs/poly2tri/poly2tri/sweep/advancing_front.cc + libs/poly2tri/poly2tri/sweep/cdt.cc + libs/poly2tri/poly2tri/sweep/sweep.cc + libs/poly2tri/poly2tri/sweep/sweep_context.cc + libs/sqlite3/sqlite3.c + libs/tinyxml2/tinyxml2.cpp + libs/wacom/WinTab/Utils.cpp + libs/yoga/yoga/event/event.cpp + libs/yoga/yoga/internal/experiments.cpp + libs/yoga/yoga/log.cpp + libs/yoga/yoga/Utils.cpp + libs/yoga/yoga/YGConfig.cpp + libs/yoga/yoga/YGEnums.cpp + libs/yoga/yoga/YGLayout.cpp + libs/yoga/yoga/YGNode.cpp + libs/yoga/yoga/YGNodePrint.cpp + libs/yoga/yoga/YGStyle.cpp + libs/yoga/yoga/YGValue.cpp + libs/yoga/yoga/Yoga.cpp +) + +set(PP_LEGACY_INCLUDE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/base64" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/bugtrap-client/include" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/curl-win/include" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/fmt/include" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/glad/include" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/glm" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/hash-library" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/jpeg" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/libyuv/include" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/mp4v2/include" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/nanort" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/openh264/include" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/openvr/headers" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/poly2tri/poly2tri" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/sqlite3" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/stb" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/tinyxml2" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/wacom" + "${CMAKE_CURRENT_SOURCE_DIR}/libs/yoga" +) + diff --git a/cmake/PanoPainterVersion.cmake b/cmake/PanoPainterVersion.cmake new file mode 100644 index 0000000..6f9257c --- /dev/null +++ b/cmake/PanoPainterVersion.cmake @@ -0,0 +1,17 @@ +function(pp_add_version_generation target config_name) + find_package(Python3 COMPONENTS Interpreter REQUIRED) + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/version.gen.h" + COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/pre-build.py" "${config_name}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/scripts/pre-build.py" + COMMENT "Generating src/version.gen.h" + VERBATIM) + + add_custom_target(pp_generate_version + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/version.gen.h") + + add_dependencies(${target} pp_generate_version) +endfunction() + diff --git a/cmake/PanoPainterWarnings.cmake b/cmake/PanoPainterWarnings.cmake new file mode 100644 index 0000000..e628265 --- /dev/null +++ b/cmake/PanoPainterWarnings.cmake @@ -0,0 +1,40 @@ +function(pp_configure_project_warnings target) + if(MSVC) + target_compile_options(${target} INTERFACE + /W4 + /permissive- + /Zc:__cplusplus + /Zc:preprocessor) + if(PP_ENABLE_MSVC_ANALYZE) + target_compile_options(${target} INTERFACE /analyze) + endif() + else() + target_compile_options(${target} INTERFACE + -Wall + -Wextra + -Wpedantic + -Wconversion + -Wshadow + -Wnull-dereference) + endif() + + if(PP_ENABLE_ASAN) + if(MSVC) + target_compile_options(${target} INTERFACE /fsanitize=address) + target_link_options(${target} INTERFACE /fsanitize=address) + else() + target_compile_options(${target} INTERFACE -fsanitize=address) + target_link_options(${target} INTERFACE -fsanitize=address) + endif() + endif() + + if(PP_ENABLE_UBSAN AND NOT MSVC) + target_compile_options(${target} INTERFACE -fsanitize=undefined) + target_link_options(${target} INTERFACE -fsanitize=undefined) + endif() + + if(PP_ENABLE_TSAN AND NOT MSVC) + target_compile_options(${target} INTERFACE -fsanitize=thread) + target_link_options(${target} INTERFACE -fsanitize=thread) + endif() +endfunction() diff --git a/docs/adr/0001-modernization-boundaries.md b/docs/adr/0001-modernization-boundaries.md new file mode 100644 index 0000000..88dab30 --- /dev/null +++ b/docs/adr/0001-modernization-boundaries.md @@ -0,0 +1,49 @@ +# ADR 0001: Incremental Component Boundaries + +Status: accepted +Date: 2026-05-31 + +## Context + +PanoPainter currently has a flat `src/` layout with broad dependencies through +`pch.h`, global singletons such as `App::I` and `Canvas::I`, OpenGL types in +high-level painting/document headers, and duplicated platform source lists. +The modernization work must retain existing behavior across Windows desktop +and AppX, macOS, iOS, Android standard, Quest, Focus/Wave, Linux, and WebGL. + +## Decision + +Modernization will proceed incrementally. OpenGL remains the production +renderer while component boundaries and tests are introduced. Vulkan, Metal, +and WebGPU-related work must stay out of the production path until OpenGL +parity tests exist. + +The target dependency direction is: + +```text +pp_foundation + -> pp_assets + -> pp_paint + -> pp_document + -> pp_renderer_api + -> pp_renderer_gl + -> pp_paint_renderer + -> pp_ui_core + -> pp_panopainter_ui + -> pp_platform_* + -> panopainter_app +``` + +Pure component headers must not include platform SDK headers or graphics API +headers. Temporary shims are allowed only when recorded in +`docs/modernization/debt.md`. + +## Consequences + +- The first implementation steps are documentation, inventory, CMake skeleton, + diagnostics, and tests, not a renderer rewrite. +- Existing project files remain until the shared CMake targets are validated. +- Refactors should prefer additive compatibility layers before moving behavior. +- Every extracted component must gain its own tests before the next component + boundary is extracted. + diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md new file mode 100644 index 0000000..62de6df --- /dev/null +++ b/docs/modernization/build-inventory.md @@ -0,0 +1,83 @@ +# Build And Platform Inventory + +Status: live +Last updated: 2026-05-31 + +This inventory records the known build surfaces during the CMake migration. +Keep it updated as platform paths move to shared CMake targets. + +## Existing Build Entrypoints + +| Platform/Target | Current Entrypoint | Notes | +| --- | --- | --- | +| Windows desktop | Root `CMakeLists.txt`, preset `windows-msvc-default`; target preset `windows-vs2026-x64` retained for VS 2026 | Raw `.sln/.vcxproj` files removed on 2026-05-31; local machine currently uses Visual Studio 17 2022 | +| Windows AppX | `PanoPainterPackage/Package.appxmanifest`, `.wapproj` referenced by solution | Distribution packaging | +| macOS | `PanoPainter-OSX/` project files and `Info.plist` | Uses `NSOpenGLView` today | +| iOS | `PanoPainter/Info.plist`, related Apple sources | Uses OpenGL ES today | +| Android standard | `android/android/build.gradle`, `android/android/CMakeLists.txt` | Native library target `native-lib` | +| Android Quest | `android/quest/build.gradle`, `android/quest/CMakeLists.txt` | OVR SDK imported libraries | +| Android Focus/Wave | `android/focus/build.gradle`, `android/focus/CMakeLists.txt` | Wave SDK imported libraries | +| Linux | `linux/CMakeLists.txt` | Old CMake 3.4, C++14 flag | +| WebGL/Emscripten | `webgl/CMakeLists.txt` | Old CMake 3.4, WebGL2 flags | + +## Existing Version Generation + +- Script: `scripts/pre-build.py` +- Output: `src/version.gen.h` +- Current behavior: derives version from git branch, latest tag, short hash, + commit count, and configuration argument. +- Migration requirement: root CMake should call this script through a custom + command and avoid unnecessary tracked-file churn where possible. + +## Existing Dependency Sources + +Hybrid policy: migrate reliable packages to vcpkg and retain SDK/patched +dependencies until each platform triplet is proven. + +| Dependency | Current Source | Initial Policy | +| --- | --- | --- | +| fmt | `libs/fmt` | Move to vcpkg | +| GLM | `libs/glm` | Move to vcpkg | +| tinyxml2 | `libs/tinyxml2` | Move to vcpkg | +| stb | `libs/stb` | Move to vcpkg or interface target if package friction | +| CURL | `libs/curl-win`, `libs/curl-android-ios` | Move to vcpkg where triplets work | +| SQLite | `libs/sqlite3` | Move to vcpkg | +| GLAD | `libs/glad` | Move to vcpkg or generated backend target | +| Catch2 | none yet | Add through vcpkg | +| OpenVR | `libs/openvr` | Retain initially | +| OVR Platform/Mobile | `libs/ovr_platform`, `libs/ovr_mobile` | Retain initially | +| Wave SDK | `libs/wave_sdk` | Retain initially | +| Wacom WinTab | `libs/wacom` | Retain initially | +| AppCenter Apple | `libs/appcenter-apple` | Retain initially | +| openh264/mp4v2/libyuv | `libs/openh264`, `libs/mp4v2`, `libs/libyuv` | Retain initially | +| jpeg helpers | `libs/jpeg` | Evaluate after image tests exist | +| poly2tri/nanort/base64/hash-library | `libs/*` | Evaluate after component split | + +## Current Validation Commands + +These commands are the current local baseline. + +```powershell +cmake --preset windows-msvc-default +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 +``` + +Known local toolchain state: + +- CMake: 4.0.0-rc4 +- Local Visual Studio generator selected by CMake: Visual Studio 17 2022 +- Android SDK: `C:\Users\omara\AppData\Local\Android\Sdk` +- Android NDK: `C:\Users\omara\AppData\Local\Android\Sdk\ndk\29.0.14206865` +- `vcpkg` is not on PATH yet; see DEBT-0007. + +Known warnings after the current CMake app build: + +- Legacy code/vendor warnings under `/W4`. +- Visual Studio vcpkg manifest warning because manifest mode is not enabled. +- `LNK4099` missing `yuv.pdb` for retained libyuv binaries. +- `LNK4098` runtime library conflict from retained vendor binaries. + +Platform-specific commands should be added here when verified locally. diff --git a/docs/modernization/capability-map.md b/docs/modernization/capability-map.md new file mode 100644 index 0000000..ffd0118 --- /dev/null +++ b/docs/modernization/capability-map.md @@ -0,0 +1,83 @@ +# PanoPainter Capability Map + +Status: live +Last updated: 2026-05-31 + +This map is the preservation checklist for the modernization. When a component +is extracted, update the relevant rows with the owning component, test label, +and validation command. + +## Project And Documents + +| Capability | Current Area | Target Owner | Required Tests | +| --- | --- | --- | --- | +| PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture | +| Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior | +| Thumbnail generation/read | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden thumbnail, corrupt input | +| Save-as, overwrite prompts | App/dialogs | `pp_panopainter_ui`, `pp_platform_*` | UI automation and platform smoke | + +## Image And Export + +| Capability | Current Area | Target Owner | Required Tests | +| --- | --- | --- | --- | +| PNG/JPEG import | `Image`, `Canvas` import paths | `pp_assets`, `pp_document` | Fixture import, malformed file | +| PNG/JPEG export | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden output tolerance | +| Equirectangular import/export | `Canvas`, shaders, RTT | `pp_paint_renderer` | Tiny cube/equirect golden | +| Cube face export | `Canvas` | `pp_paint_renderer` | Six-face golden set | +| Depth export | `Canvas`, grid tools | `pp_paint_renderer` | Float/readback validation | + +## Brush And Painting + +| Capability | Current Area | Target Owner | Required Tests | +| --- | --- | --- | --- | +| Brush settings serialization | `Brush`, `Serializer` | `pp_paint`, `pp_assets` | Round-trip and boundary values | +| ABR import | `ABR`, `Brush` | `pp_assets`, `pp_paint` | Sample ABR and malformed ABR | +| PPBR import/export | brush panel/dialog | `pp_assets`, `pp_panopainter_ui` | Round-trip fixture | +| Stroke sampling | `Stroke`, `Canvas` | `pp_paint` | Property tests for spacing, pressure, jitter | +| Dual brush/pattern behavior | `Brush`, shaders | `pp_paint`, `pp_paint_renderer` | CPU reference and GPU golden | +| Blend modes | GLSL include files, layer rendering | `pp_paint`, `pp_paint_renderer` | CPU reference vectors and GPU parity | +| Erase/flood fill/masks | `Canvas`, modes, shaders | `pp_document`, `pp_paint_renderer` | Edge masks, alpha lock, dirty rects | + +## Layers And Animation + +| Capability | Current Area | Target Owner | Required Tests | +| --- | --- | --- | --- | +| Layer add/remove/move/merge | `Canvas`, `Layer`, actions | `pp_document` | Undo/redo invariant tests | +| Blend/opacity/visibility/alpha lock | `Layer`, UI panels, shaders | `pp_document`, `pp_paint_renderer` | CPU model and render golden | +| Selection mask | `Canvas` mask layer | `pp_document`, `pp_paint_renderer` | Mask apply/clear edge cases | +| Animation frames | `LayerFrame`, animation panel | `pp_document`, `pp_panopainter_ui` | Duration, duplicate, remove, seek | +| MP4/timelapse export | `MP4Encoder`, recording thread | `pp_assets`, `pp_paint_renderer`, app | Smoke export and cancellation | + +## UI And Workflow + +| Capability | Current Area | Target Owner | Required Tests | +| --- | --- | --- | --- | +| XML layout parsing | `LayoutManager`, `Node` | `pp_ui_core` | Layout fixtures and malformed XML | +| Yoga layout | `Node` | `pp_ui_core` | Deterministic geometry fixtures | +| Generic controls | `NodeButton`, sliders, text, images | `pp_ui_core` | Event dispatch and layout tests | +| PanoPainter panels/dialogs | `NodePanel*`, `NodeDialog*` | `pp_panopainter_ui` | UI automation scripts | +| Canvas viewport UI | `NodeCanvas` | `pp_panopainter_ui`, `pp_paint_renderer` | Input-to-command automation | +| Settings UI | `Settings`, `NodeSettings` | `pp_assets`, `pp_panopainter_ui` | Round-trip settings | + +## Input, Platform, And Devices + +| Capability | Current Area | Target Owner | Required Tests | +| --- | --- | --- | --- | +| Mouse/keyboard/touch/gestures | `App`, platform entrypoints | `pp_platform_*`, app | Synthetic event playback | +| Wacom pressure | `WacomTablet` | `pp_platform_windows` | Adapter smoke with fallback | +| Clipboard/file picker/share | `App` platform methods | `pp_platform_*` | Platform smoke or mocked service | +| Virtual keyboard | platform entrypoints | `pp_platform_*` | Platform smoke | +| OpenVR desktop | `HMD`, `Vive`, `app_vr` | `pp_platform_vr`, app | Compile gate and mocked pose tests | +| Quest/OVR | Android Quest files | `pp_platform_android_quest` | Compile/package gate | +| Focus/Wave | Android Focus files | `pp_platform_android_wave` | Compile/package gate | + +## Cloud, Logging, And Automation + +| Capability | Current Area | Target Owner | Required Tests | +| --- | --- | --- | --- | +| Upload/download/browse | `app_cloud`, CURL helpers | app service, `pp_platform_*` | Mocked HTTP and timeout tests | +| License/check flows | app/cloud code | app service | Mocked response tests | +| Logging/crash reporting | `log`, BugTrap/AppCenter | `pp_foundation`, platform wrappers | Log formatting and platform compile | +| Headless automation | none yet | `tools/pano_cli` | JSON command fixtures | +| Tracing | none yet | `pp_foundation` | Span nesting/timing tests | + diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md new file mode 100644 index 0000000..2787d32 --- /dev/null +++ b/docs/modernization/debt.md @@ -0,0 +1,34 @@ +# Modernization Debt Log + +Status: live +Last updated: 2026-05-31 + +Every shortcut, temporary adapter, retained vendored dependency, skipped +platform gate, compatibility shim, or incomplete automation path must be +recorded here before it lands. Entries must be specific enough for a future +agent or engineer to remove them without reconstructing context from chat. + +## Entry Rules + +- Add an entry before merging the shortcut. +- Reference the debt id in code comments, TODOs, ADRs, or roadmap notes. +- Include an owner, reason, validation command, and removal condition. +- Do not close an entry until the removal condition is met and validated. +- Prefer deleting shortcuts over expanding this log. + +## Open Debt + +| ID | Status | Owner | Item | Reason | Validation | Removal Condition | +| --- | --- | --- | --- | --- | --- | --- | +| DEBT-0001 | Open | Modernization | Existing platform build files remain alongside new CMake | Required for incremental migration without losing platform coverage | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets | +| DEBT-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only, patched, or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace with vcpkg packages or document permanent vendored status after triplet evaluation | +| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split | Avoid behavior changes while introducing component boundaries | App launch and component tests | Replace singleton reaches with context/service injection at component boundaries | +| DEBT-0004 | Open | Modernization | Android, Linux, WebGL, Apple, and AppX build files remain platform-specific until root CMake alignment reaches them | Prevent platform regressions during incremental migration; raw Windows `.sln/.vcxproj` files were removed on 2026-05-31 by user decision | `cmake --preset windows-msvc-default`; platform-specific configure/build smoke checks as each platform is migrated | Root CMake owns every platform source list and package path | +| DEBT-0005 | Open | Modernization | Temporary local CTest harness is used before Catch2 is wired through vcpkg | `vcpkg` is not currently on PATH, but headless tests need to run now | `ctest --preset desktop-fast --build-config Debug` | Replace `tests/test_harness.h` tests with Catch2 tests once vcpkg toolchain/presets are validated | +| DEBT-0006 | Open | Modernization | `pano_cli create-document` validates and emits JSON command contracts but does not yet invoke the legacy document/app model | The document model has not been extracted from `Canvas`/`App` yet | `pano_cli create-document --width 64 --height 32 --layers 2`; CTest `pano_cli_create_document_smoke` | Replace command contract implementation with real `pp_document` creation once Phase 4 extracts the document model | +| DEBT-0007 | Open | Modernization | `vcpkg.json` exists but CMake is not yet using a validated vcpkg toolchain on this machine | `vcpkg` is not available on PATH and Visual Studio reports manifest mode is disabled | `cmake --preset windows-msvc-default` currently configures with vendored dependencies | Add validated vcpkg toolchain/preset integration for desktop, Android, and Apple triplets | +| DEBT-0008 | Open | Modernization | `windows-msvc-default` preset is used for local validation because the VS 2026 generator is not installed here | The target VS 2026 preset must remain, but this machine configures with Visual Studio 17 2022 | `cmake --preset windows-msvc-default`; `ctest --preset desktop-fast --build-config Debug` | Validate `windows-vs2026-x64` on a machine with Visual Studio 2026 installed and make it the default Windows validation preset | + +## Closed Debt + +None yet. diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md new file mode 100644 index 0000000..366b571 --- /dev/null +++ b/docs/modernization/roadmap.md @@ -0,0 +1,553 @@ +# PanoPainter Modernization Roadmap + +Status: live +Last updated: 2026-05-31 + +This is the living roadmap for modernizing PanoPainter into independently +testable C++23 components while retaining all existing functionality. Keep this +file current as phases are implemented. Do not let shortcuts, skipped platforms, +or temporary adapters live only in chat history. + +## How To Keep This Roadmap Live + +- Update the phase status before and after each implementation pass. +- When a shortcut is introduced, add it to the debt log section in this file + until `docs/modernization/debt.md` exists, then move debt entries there. +- When a major architectural decision is made, add an ADR under `docs/adr/` + once that directory exists. +- Every phase must preserve old behavior unless the roadmap explicitly says + otherwise. +- Each phase must leave the repo in a buildable and testable state. +- Do not add stubs without a debt entry, validation command, and removal + condition. + +## Locked Decisions + +- Graphics path: keep OpenGL working first; add Vulkan and Metal after the + renderer boundary exists. +- Required platforms at phase gates: Windows desktop/AppX, macOS, iOS, + Android standard, Quest, Focus/Wave, Linux, and WebGL. +- Dependency policy: use vcpkg where reliable; keep SDK, patched, or + vendor-only dependencies with documented reasons. +- Test stack: Catch2, golden/approval tests, and fuzz/property tests where + useful. +- Automation: local reproducible matrix first; hosted CI can be added later. +- Documentation: ADRs, debt log, and this living roadmap. +- "vkpkg" in older notes means `vcpkg`. +- Target C++ standard: C++23. +- Initial Windows CMake generator target: Visual Studio 2026 when available. + +## Phase Status + +| Phase | Name | Status | Gate | +| --- | --- | --- | --- | +| 0 | Inventory, Safety Rails, And Memory | Complete | No behavior changes; old builds still work | +| 1 | Unified CMake Skeleton | In progress | Root CMake builds the Windows app and owns the source list | +| 2 | Toolchain, Diagnostics, And Dependencies | In progress | Strict desktop library builds compile cleanly | +| 3 | Test Harness And Agent-Ready Automation | In progress | `ctest --preset desktop-fast` runs headlessly | +| 4 | Component Split Without Behavior Change | Started | Each extracted target builds and tests | +| 5 | Renderer Boundary And OpenGL Parity | Not started | OpenGL output matches golden readbacks | +| 6 | Platform Alignment | Not started | Every supported platform has named validation | +| 7 | Hardening, Coverage, And Breaking-Point Tests | Not started | Each component has edge/failure tests | +| 8 | Future Backend Readiness | Not started | Vulkan/Metal lab targets remain non-default | + +## Target Component Architecture + +The refactor should move toward one-way dependencies: + +```text +pp_foundation + -> pp_assets + -> pp_paint + -> pp_document + -> pp_renderer_api + -> pp_renderer_gl + -> pp_paint_renderer + -> pp_ui_core + -> pp_panopainter_ui + -> pp_platform_* + -> panopainter_app +``` + +Intended responsibilities: + +- `pp_foundation`: logging facade, math/util helpers, events, task queues, + binary streams. +- `pp_assets`: `Asset`, `Image`, `Settings`, serialization, ABR, PPBR, and PPI + helpers. +- `pp_paint`: pure `Brush`, `Stroke`, stroke sampling, and CPU reference blend + math. +- `pp_document`: canvas document model, layers, animation frames, and undo/redo + model. +- `pp_renderer_api`: renderer-neutral interfaces for textures, render targets, + shaders, meshes, readback, frame capture, and tracing. +- `pp_renderer_gl`: current OpenGL implementation behind renderer interfaces. +- `pp_paint_renderer`: stroke rasterization, layer compositing, cube/equirect + export using `pp_renderer_api`. +- `pp_ui_core`: `Node`, layout, generic controls, text/image primitives. +- `pp_panopainter_ui`: panels, dialogs, `NodeCanvas`, and app-specific + workflows. +- `pp_platform_*`: Windows, macOS/iOS, Android, Linux, and WebGL shells. +- `panopainter_app`: composition root only. + +Rules: + +- Component headers must not include platform SDK or graphics API headers unless + the component name includes that backend or platform. +- Pure libraries must build and test without a window, GL context, network, + tablet, VR headset, or filesystem outside test temp directories. +- Public APIs should return explicit status/result objects. PanoPainter app + code keeps exceptions disabled unless isolated SDK wrappers require them. +- Singleton access should be replaced at component boundaries with context or + service objects. Temporary facade shims require debt entries. + +## Phase 0: Inventory, Safety Rails, And Memory + +Status: complete on 2026-05-31. Created this roadmap, +`docs/modernization/debt.md`, `docs/modernization/capability-map.md`, +`docs/modernization/build-inventory.md`, and ADR 0001. + +Goal: create durable project memory and prevent silent shortcuts before large +refactors begin. + +Implementation tasks: + +- Add `docs/modernization/roadmap.md`, `docs/modernization/debt.md`, and + `docs/adr/`. +- Add a shortcut rule: every temporary adapter, fallback, skipped platform, or + retained vendored dependency must have owner, reason, validation command, and + removal condition. +- Generate a current capability map covering: + - project open/save and PPI compatibility + - image import/export and thumbnails + - brush presets, ABR import, PPBR export/import + - layers, blend modes, alpha lock, selection mask + - animation frames and MP4/timelapse recording + - VR, tablet, touch, mouse, keyboard, gestures + - cloud upload/download/browse + - UI dialogs, panels, layout XML, settings + - Windows/AppX, macOS, iOS, Android standard, Quest, Focus/Wave, Linux, WebGL +- Record current build commands and known platform prerequisites. + +Gate: + +- No behavior changes. +- Existing Visual Studio, platform CMake, Gradle, Apple, Linux, and WebGL paths + are not removed. + +## Phase 1: Unified CMake Skeleton + +Goal: make CMake the canonical source list without breaking existing projects. + +Status: in progress. Root `CMakeLists.txt`, `CMakePresets.json`, and project +option targets exist. The Windows desktop app builds through CMake as +`PanoPainter`; the raw Visual Studio solution/project files were removed on +2026-05-31 by user decision. Non-Windows platform build files remain during +Phase 6 alignment. + +Implementation tasks: + +- Add root `CMakeLists.txt` and shared CMake modules under `cmake/`. +- Add `CMakePresets.json` with at least: + - `windows-vs2026-x64` + - `windows-clangcl-asan` + - `linux-clang` + - `android-arm64` + - `android-x64` + - `emscripten` + - `macos` + - `ios-device` + - `ios-simulator` +- Keep Android CMake, Linux CMake, WebGL CMake, Apple project files, and AppX + packaging during the transition until each consumes shared component targets. +- Move version generation into a CMake custom command using + `scripts/pre-build.py`. +- Fix `scripts/pre-build.py` only if required to avoid unnecessary rewrites or + missing-tag failures. +- Add CMake options: + - `PP_BUILD_APP` + - `PP_BUILD_TESTS` + - `PP_BUILD_TOOLS` + - `PP_ENABLE_OPENGL` + - `PP_ENABLE_VULKAN_EXPERIMENTAL=OFF` + - `PP_ENABLE_VR` + - `PP_ENABLE_CLOUD` + - `PP_ENABLE_VIDEO` +- Define source-list helper targets so per-platform source duplication can be + reduced incrementally. + +Gate: + +- Windows desktop app builds through CMake. +- New CMake can configure on Windows. +- Source list differences are understood and documented. +- Non-Windows platform migration is debt-tracked until Phase 6. + +## Phase 2: Toolchain, Diagnostics, And Dependencies + +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. + +Implementation tasks: + +- Set C++23 through target features, not raw compiler flags. +- Add warning profiles: + - MSVC: `/W4 /permissive- /Zc:__cplusplus /Zc:preprocessor`. + - Optional MSVC analysis preset: `/analyze`. + - Clang/GCC: `-Wall -Wextra -Wpedantic -Wconversion -Wshadow + -Wnull-dereference`. +- Keep exceptions disabled for PanoPainter targets, except isolated SDK wrapper + targets when unavoidable. +- Add sanitizer presets: + - Clang/GCC ASan and UBSan for headless libraries. + - MSVC ASan where supported. + - TSan only for pure/headless targets. +- Add tooling hooks: + - `clang-tidy` + - `cppcheck` + - shader validation or compile checks + - CTest dashboard output +- Add `vcpkg.json`. +- Move reliable dependencies to vcpkg first: + - `fmt` + - `glm` + - `tinyxml2` + - `stb` + - `curl` + - `sqlite3` + - `glad` + - `Catch2` +- Keep vendored until proven: + - OpenVR + - OVR/Wave SDKs + - Wacom WinTab + - AppCenter + - openh264 + - mp4v2 + - libyuv + - patched or SDK-specific libraries + +Gate: + +- Desktop library targets compile with strict diagnostics. +- New warnings caused by refactor are fixed or locally justified. +- No global blanket warning suppression for project code. + +## Phase 3: Test Harness And Agent-Ready Automation + +Goal: make each component reachable by automated tools and future agents. + +Status: in progress. `tests/` exists, `desktop-fast` runs headlessly, and +PowerShell/bash wrappers exist for configure/build/test/analyze. `pano_cli` +exists with a first JSON automation command for validating create-document +inputs; full document/app integration is debt-tracked as DEBT-0006. + +Implementation tasks: + +- Add `tests/` with one executable per component. +- Register CTest labels: + - `foundation` + - `assets` + - `paint` + - `document` + - `renderer` + - `ui` + - `platform` + - `integration` + - `fuzz` + - `slow` + - `gpu` +- Add `tools/pano_cli` for headless automation. +- `pano_cli` should support: + - create document + - load project + - save project + - apply scripted strokes + - import/export images + - inspect layers + - run layout parse + - emit JSON results +- Add local automation wrappers under `scripts/automation/`: + - configure + - build + - test + - analyze + - package smoke +- All wrappers must return machine-readable logs or summaries. +- Establish `tests/data/` fixtures: + - tiny PPI files + - corrupt/truncated PPI cases + - PNG/JPEG fixtures + - ABR/PPBR samples + - layout XML + - shader snippets + - brush stroke scripts + +Gate: + +- `ctest --preset desktop-fast --build-config Debug` runs without a GL + context. +- Non-render components can be tested on a headless machine. + +## Phase 4: Component Split Without Behavior Change + +Goal: split libraries while keeping current app behavior. + +Status: started. `pp_foundation` exists with binary stream utilities and +boundary/overread tests. Continue extracting legacy-safe utilities before +moving assets, paint, or document behavior. + +Implementation tasks: + +- Extract components in this order: + 1. `pp_foundation` + 2. `pp_assets` + 3. `pp_paint` + 4. `pp_document` + 5. `pp_renderer_api` + 6. `pp_renderer_gl` + 7. `pp_paint_renderer` + 8. `pp_ui_core` + 9. `pp_panopainter_ui` + 10. `pp_platform_*` + 11. `panopainter_app` +- Remove renderer/platform dependencies from pure headers first, especially: + - `Brush` + - document/layer model + - serializer + - UI core headers +- Keep facade shims where needed, but debt-track every shim. +- Avoid large behavioral rewrites during extraction. +- Each extracted component gets a focused test suite before moving to the next. + +Gate: + +- Old app still launches. +- Component tests pass after every extraction. +- No undocumented stubs or shortcuts. + +## Phase 5: Renderer Boundary And OpenGL Parity + +Goal: make OpenGL an implementation detail and establish parity tests before +adding new backends. + +Implementation tasks: + +- Introduce renderer interfaces: + - `IRenderDevice` + - `ITexture2D` + - `IRenderTarget` + - `IShaderProgram` + - `IMesh` + - `ICommandContext` + - `IReadbackBuffer` + - `IRenderTrace` +- Port current renderer classes behind OpenGL backend types: + - `RTT` + - `Texture2D` + - `Sampler` + - `ShaderManager` + - `Shape` +- Preserve current shader behavior and asset paths. +- Add deterministic GPU tests: + - clear + - blit + - texture upload/download + - stroke composite + - erase + - layer blend + - equirect export + - readback bounds +- Add CPU reference tests for blend modes. +- Compare GPU output to golden/reference data with explicit tolerances. + +Gate: + +- OpenGL readbacks match golden data on Windows and Linux. +- Mobile/WebGL compile gates remain green. + +## Phase 6: Platform Alignment + +Goal: every supported platform consumes the same component targets. + +Implementation tasks: + +- Convert these builds to shared component targets: + - Windows desktop + - Windows AppX + - macOS + - iOS + - Android standard + - Android Quest + - Android Focus/Wave + - Linux + - WebGL/Emscripten +- Keep platform entrypoints thin: + - window lifecycle + - input dispatch + - clipboard + - file picker/share + - GL context creation + - VR SDK bridge + - packaging only +- Add or refine CMake toolchain/preset support for: + - Android NDK ABIs + - iOS device + - iOS simulator + - macOS + - Emscripten +- Keep SDK-only imported libraries documented until vcpkg triplets are proven. + +Gate: + +- Every platform has a named configure/build command. +- Missing local prerequisites are documented. +- Each platform has at least compile or package validation. + +## Phase 7: Hardening, Coverage, And Breaking-Point Tests + +Goal: tests should try to break components, not only confirm current happy +paths. + +Implementation tasks: + +- Add property/fuzz tests for: + - binary streams + - serializers + - PPI parsing + - ABR parsing + - layout XML parsing + - image metadata parsing + - brush parameter extremes + - layer/frame operations + - undo/redo invariants +- Add stress tests for: + - thousands of stroke samples + - extreme resolutions guarded by memory limits + - rapid layer/frame edits + - corrupt assets + - cancellation during export + - concurrent render/UI task scheduling +- Add coverage for headless libraries on Clang/GCC. +- Require coverage reports for changed components first; do not set a global + threshold until the baseline is meaningful. +- Add tracing spans around: + - project load/save + - render passes + - stroke commit + - readback + - export + - UI layout + - platform I/O +- Logs must include component, thread, frame/stroke id, and timing. + +Gate: + +- No shortcut remains undocumented. +- Every component has unit tests and at least one failure or edge test. + +## Phase 8: Future Backend Readiness + +Goal: prepare Vulkan and Metal without destabilizing the OpenGL parity path. + +Implementation tasks: + +- Create non-default targets only after OpenGL backend parity: + - `pp_renderer_vulkan_lab` + - `pp_renderer_metal_lab` +- Use `D:\Dev\vkpaint` as reference material for Vulkan painting experiments, + not as direct production code. +- Before integration, prove: + - ping-pong compositing path + - input-attachment/subpass path where applicable + - feedback-loop or framebuffer-fetch-style path where supported + - synchronization and layout correctness under validation layers +- Keep WebGPU as an optional future portability backend, not the core renderer + contract. + +Gate: + +- Vulkan/Metal lab targets are opt-in. +- OpenGL production backend remains stable. + +## Test Matrix + +| Preset/Label | Purpose | Requires | +| --- | --- | --- | +| `desktop-fast` | Pure component unit tests | No GPU/window | +| `desktop-gpu` | OpenGL backend golden/readback tests | GPU/GL context | +| `fuzz` | Parser and serializer fuzzing | Fuzzer-capable compiler | +| `stress` | Large and adversarial scenarios | Longer runtime | +| `platform-build` | Configure/build each supported platform | Local toolchains | +| `package-smoke` | AppX/APK/Apple/WebGL package smoke | Platform SDKs | + +Acceptance for each phase: + +- Previous phase tests still pass. +- New component has its own tests. +- No undocumented stubs. +- No skipped platform without a debt entry. +- Automation command is recorded in this roadmap or linked docs. + +## Verified Commands + +Last verified on 2026-05-31: + +```powershell +cmake --preset windows-msvc-default +cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_tests pano_cli 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 +``` + +Results: + +- `pp_foundation_tests` passed. +- `pano_cli_create_document_smoke` passed. +- `PanoPainter.exe` built through CMake at + `out/build/windows-msvc-default/Debug/PanoPainter.exe`. +- PowerShell build/test automation wrappers return JSON summaries and passed + local smoke checks. +- Known remaining warnings: legacy project/vendor diagnostics, Visual Studio + vcpkg-manifest warning, `LNK4099` missing libyuv PDBs, and `LNK4098` runtime + library conflict from retained vendor binaries. + +## Current Debt Log + +The canonical debt log is now `docs/modernization/debt.md`. Keep this section +as a reminder only; do not add new debt entries here. + +| ID | Status | Owner | Item | Reason | Validation | Removal Condition | +| --- | --- | --- | --- | --- | --- | --- | +| DEBT-0001 | Open | TBD | Existing platform build files remain alongside new CMake | Required for incremental migration | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets | +| DEBT-0002 | Open | TBD | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace or document permanent vendored status after vcpkg triplet evaluation | +| DEBT-0003 | Open | TBD | Existing singletons remain during initial split | Avoid behavior changes while introducing boundaries | App launch and component tests | Replace singleton reaches with context/service injection at component boundaries | + +## Current Capability Map Seed + +Use this as the starting checklist for Phase 0 inventory. + +- Project I/O: PPI open/save, thumbnails, version metadata, autosave/save-as + flows. +- Image I/O: JPEG/PNG import/export, cube faces, equirectangular export, + depth export. +- Brush system: ABR import, PPBR import/export, presets, tip/pattern/dual brush, + pressure, jitter, blend modes. +- Painting: six cube faces, temporary stroke buffers, erase, flood fill, masks, + alpha lock, layer compositing. +- Layers and animation: layer add/remove/move/merge, blend/opacity/visibility, + frame add/remove/duplicate/duration, MP4/timelapse export. +- UI: XML layout, Yoga layout, panels, dialogs, color tools, brush tools, + layers, animation timeline, settings, shortcuts, manual/changelog/about. +- Input: mouse, keyboard, touch, gestures, Wacom tablet, stylus pressure, + VR controllers. +- Platform services: clipboard, file picker, save picker, directory picker, + share/display file, keyboard show/hide. +- VR/platform variants: OpenVR desktop, Quest, Focus/Wave, Android standard, + iOS/macOS, Linux, WebGL. +- Cloud/network: upload, download, browse, license/check flows. +- Recording/export: PBO readbacks, MP4 encoder, timelapse frames. diff --git a/libs/wacom/WinTab/Utils.cpp b/libs/wacom/WinTab/Utils.cpp index ed15601..b98a21c 100644 --- a/libs/wacom/WinTab/Utils.cpp +++ b/libs/wacom/WinTab/Utils.cpp @@ -62,7 +62,7 @@ BOOL LoadWintab( void ) // ghWintab = LoadLibraryA( "C:\\dev\\mainline\\Wacom\\Win\\Win32\\Debug\\Wacom_Tablet.dll" ); // ghWintab = LoadLibraryA( "C:\\dev\\mainline\\Wacom\\Win\\Win32\\Debug\\Wintab32.dll" ); LOG("calling LoadLibrary"); - ghWintab = LoadLibrary(L"Wintab32.dll"); + ghWintab = LoadLibraryW(L"Wintab32.dll"); LOG("LoadLibrary called"); if ( !ghWintab ) diff --git a/scripts/automation/analyze.ps1 b/scripts/automation/analyze.ps1 new file mode 100644 index 0000000..bb34a97 --- /dev/null +++ b/scripts/automation/analyze.ps1 @@ -0,0 +1,30 @@ +[CmdletBinding()] +param( + [string]$Preset = "windows-msvc-default", + [switch]$NoApp +) + +$ErrorActionPreference = "Stop" +$started = Get-Date +$argsList = @( + "--preset", $Preset, + "-DPP_ENABLE_MSVC_ANALYZE=ON", + "-DPP_ENABLE_CLANG_TIDY=ON", + "-DPP_ENABLE_CPPCHECK=ON" +) +if ($NoApp) { + $argsList += "-DPP_BUILD_APP=OFF" +} + +& cmake @argsList +$exitCode = $LASTEXITCODE +$elapsed = [int]((Get-Date) - $started).TotalMilliseconds + +[ordered]@{ + command = "analyze-configure" + preset = $Preset + exitCode = $exitCode + elapsedMs = $elapsed +} | ConvertTo-Json -Compress + +exit $exitCode diff --git a/scripts/automation/analyze.sh b/scripts/automation/analyze.sh new file mode 100644 index 0000000..d3098bc --- /dev/null +++ b/scripts/automation/analyze.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh +set -u + +preset="${1:-linux-clang}" +start="$(date +%s)" +cmake --preset "$preset" -DPP_ENABLE_CLANG_TIDY=ON -DPP_ENABLE_CPPCHECK=ON +exit_code="$?" +end="$(date +%s)" +elapsed_ms="$(( (end - start) * 1000 ))" +printf '{"command":"analyze-configure","preset":"%s","exitCode":%s,"elapsedMs":%s}\n' "$preset" "$exit_code" "$elapsed_ms" +exit "$exit_code" diff --git a/scripts/automation/build.ps1 b/scripts/automation/build.ps1 new file mode 100644 index 0000000..14854fa --- /dev/null +++ b/scripts/automation/build.ps1 @@ -0,0 +1,28 @@ +[CmdletBinding()] +param( + [string]$Preset = "windows-msvc-default", + [string]$Configuration = "Debug", + [string]$Target = "" +) + +$ErrorActionPreference = "Stop" +$started = Get-Date +$argsList = @("--build", "--preset", $Preset, "--config", $Configuration) +if ($Target.Length -gt 0) { + $argsList += @("--target", $Target) +} + +& cmake @argsList +$exitCode = $LASTEXITCODE +$elapsed = [int]((Get-Date) - $started).TotalMilliseconds + +[ordered]@{ + command = "build" + preset = $Preset + configuration = $Configuration + target = $Target + exitCode = $exitCode + elapsedMs = $elapsed +} | ConvertTo-Json -Compress + +exit $exitCode diff --git a/scripts/automation/build.sh b/scripts/automation/build.sh new file mode 100644 index 0000000..13654c2 --- /dev/null +++ b/scripts/automation/build.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh +set -u + +preset="${1:-linux-clang}" +configuration="${2:-Debug}" +target="${3:-}" +start="$(date +%s)" +if [ -n "$target" ]; then + cmake --build --preset "$preset" --config "$configuration" --target "$target" +else + cmake --build --preset "$preset" --config "$configuration" +fi +exit_code="$?" +end="$(date +%s)" +elapsed_ms="$(( (end - start) * 1000 ))" +printf '{"command":"build","preset":"%s","configuration":"%s","target":"%s","exitCode":%s,"elapsedMs":%s}\n' "$preset" "$configuration" "$target" "$exit_code" "$elapsed_ms" +exit "$exit_code" diff --git a/scripts/automation/configure.ps1 b/scripts/automation/configure.ps1 new file mode 100644 index 0000000..a240ebe --- /dev/null +++ b/scripts/automation/configure.ps1 @@ -0,0 +1,25 @@ +[CmdletBinding()] +param( + [string]$Preset = "windows-msvc-default", + [switch]$NoApp +) + +$ErrorActionPreference = "Stop" +$started = Get-Date +$argsList = @("--preset", $Preset) +if ($NoApp) { + $argsList += "-DPP_BUILD_APP=OFF" +} + +& cmake @argsList +$exitCode = $LASTEXITCODE +$elapsed = [int]((Get-Date) - $started).TotalMilliseconds + +[ordered]@{ + command = "configure" + preset = $Preset + exitCode = $exitCode + elapsedMs = $elapsed +} | ConvertTo-Json -Compress + +exit $exitCode diff --git a/scripts/automation/configure.sh b/scripts/automation/configure.sh new file mode 100644 index 0000000..5a77205 --- /dev/null +++ b/scripts/automation/configure.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh +set -u + +preset="${1:-linux-clang}" +start="$(date +%s)" +cmake --preset "$preset" +exit_code="$?" +end="$(date +%s)" +elapsed_ms="$(( (end - start) * 1000 ))" +printf '{"command":"configure","preset":"%s","exitCode":%s,"elapsedMs":%s}\n' "$preset" "$exit_code" "$elapsed_ms" +exit "$exit_code" diff --git a/scripts/automation/test.ps1 b/scripts/automation/test.ps1 new file mode 100644 index 0000000..202617b --- /dev/null +++ b/scripts/automation/test.ps1 @@ -0,0 +1,22 @@ +[CmdletBinding()] +param( + [string]$Preset = "desktop-fast", + [string]$Configuration = "Debug" +) + +$ErrorActionPreference = "Stop" +$started = Get-Date + +& ctest --preset $Preset --build-config $Configuration +$exitCode = $LASTEXITCODE +$elapsed = [int]((Get-Date) - $started).TotalMilliseconds + +[ordered]@{ + command = "test" + preset = $Preset + configuration = $Configuration + exitCode = $exitCode + elapsedMs = $elapsed +} | ConvertTo-Json -Compress + +exit $exitCode diff --git a/scripts/automation/test.sh b/scripts/automation/test.sh new file mode 100644 index 0000000..6cdadb8 --- /dev/null +++ b/scripts/automation/test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env sh +set -u + +preset="${1:-desktop-fast}" +configuration="${2:-Debug}" +start="$(date +%s)" +ctest --preset "$preset" --build-config "$configuration" +exit_code="$?" +end="$(date +%s)" +elapsed_ms="$(( (end - start) * 1000 ))" +printf '{"command":"test","preset":"%s","configuration":"%s","exitCode":%s,"elapsedMs":%s}\n' "$preset" "$configuration" "$exit_code" "$elapsed_ms" +exit "$exit_code" diff --git a/src/foundation/binary_stream.cpp b/src/foundation/binary_stream.cpp new file mode 100644 index 0000000..2dc44d8 --- /dev/null +++ b/src/foundation/binary_stream.cpp @@ -0,0 +1,142 @@ +#include "foundation/binary_stream.h" + +namespace pp::foundation { + +ByteReader::ByteReader(std::span bytes) noexcept + : bytes_(bytes) +{ +} + +std::size_t ByteReader::position() const noexcept +{ + return position_; +} + +std::size_t ByteReader::size() const noexcept +{ + return bytes_.size(); +} + +std::size_t ByteReader::remaining() const noexcept +{ + return bytes_.size() - position_; +} + +bool ByteReader::empty() const noexcept +{ + return remaining() == 0; +} + +Status ByteReader::seek(std::size_t position) noexcept +{ + if (position > bytes_.size()) { + return Status::out_of_range("seek position is outside the stream"); + } + + position_ = position; + return Status::success(); +} + +Result ByteReader::read_u8() noexcept +{ + const auto bytes = read_bytes(1); + if (!bytes) { + return Result::failure(bytes.status()); + } + + return Result::success(static_cast(bytes.value()[0])); +} + +Result ByteReader::read_u16_le() noexcept +{ + const auto bytes = read_bytes(2); + if (!bytes) { + return Result::failure(bytes.status()); + } + + const auto b0 = static_cast(bytes.value()[0]); + const auto b1 = static_cast(bytes.value()[1]); + return Result::success(static_cast(b0 | (b1 << 8U))); +} + +Result ByteReader::read_u32_le() noexcept +{ + const auto bytes = read_bytes(4); + if (!bytes) { + return Result::failure(bytes.status()); + } + + const auto b0 = static_cast(bytes.value()[0]); + const auto b1 = static_cast(bytes.value()[1]); + const auto b2 = static_cast(bytes.value()[2]); + const auto b3 = static_cast(bytes.value()[3]); + return Result::success(b0 | (b1 << 8U) | (b2 << 16U) | (b3 << 24U)); +} + +Result> ByteReader::read_bytes(std::size_t count) noexcept +{ + if (count > remaining()) { + return Result>::failure( + Status::out_of_range("read would move beyond the end of the stream")); + } + + const auto start = position_; + position_ += count; + return Result>::success(bytes_.subspan(start, count)); +} + +ByteWriter::ByteWriter(std::vector& bytes) noexcept + : bytes_(&bytes) +{ +} + +std::size_t ByteWriter::size() const noexcept +{ + return bytes_ == nullptr ? 0 : bytes_->size(); +} + +Status ByteWriter::write_u8(std::uint8_t value) +{ + if (bytes_ == nullptr) { + return Status::invalid_argument("writer has no backing storage"); + } + + bytes_->push_back(static_cast(value)); + return Status::success(); +} + +Status ByteWriter::write_u16_le(std::uint16_t value) +{ + if (bytes_ == nullptr) { + return Status::invalid_argument("writer has no backing storage"); + } + + bytes_->push_back(static_cast(value & 0xffU)); + bytes_->push_back(static_cast((value >> 8U) & 0xffU)); + return Status::success(); +} + +Status ByteWriter::write_u32_le(std::uint32_t value) +{ + if (bytes_ == nullptr) { + return Status::invalid_argument("writer has no backing storage"); + } + + bytes_->push_back(static_cast(value & 0xffU)); + bytes_->push_back(static_cast((value >> 8U) & 0xffU)); + bytes_->push_back(static_cast((value >> 16U) & 0xffU)); + bytes_->push_back(static_cast((value >> 24U) & 0xffU)); + return Status::success(); +} + +Status ByteWriter::write_bytes(std::span bytes) +{ + if (bytes_ == nullptr) { + return Status::invalid_argument("writer has no backing storage"); + } + + bytes_->insert(bytes_->end(), bytes.begin(), bytes.end()); + return Status::success(); +} + +} diff --git a/src/foundation/binary_stream.h b/src/foundation/binary_stream.h new file mode 100644 index 0000000..1e583e9 --- /dev/null +++ b/src/foundation/binary_stream.h @@ -0,0 +1,46 @@ +#pragma once + +#include "foundation/result.h" + +#include +#include +#include +#include + +namespace pp::foundation { + +class ByteReader { +public: + explicit ByteReader(std::span bytes) noexcept; + + [[nodiscard]] std::size_t position() const noexcept; + [[nodiscard]] std::size_t size() const noexcept; + [[nodiscard]] std::size_t remaining() const noexcept; + [[nodiscard]] bool empty() const noexcept; + + [[nodiscard]] Status seek(std::size_t position) noexcept; + [[nodiscard]] Result read_u8() noexcept; + [[nodiscard]] Result read_u16_le() noexcept; + [[nodiscard]] Result read_u32_le() noexcept; + [[nodiscard]] Result> read_bytes(std::size_t count) noexcept; + +private: + std::span bytes_; + std::size_t position_ = 0; +}; + +class ByteWriter { +public: + explicit ByteWriter(std::vector& bytes) noexcept; + + [[nodiscard]] std::size_t size() const noexcept; + [[nodiscard]] Status write_u8(std::uint8_t value); + [[nodiscard]] Status write_u16_le(std::uint16_t value); + [[nodiscard]] Status write_u32_le(std::uint32_t value); + [[nodiscard]] Status write_bytes(std::span bytes); + +private: + std::vector* bytes_ = nullptr; +}; + +} diff --git a/src/foundation/result.h b/src/foundation/result.h new file mode 100644 index 0000000..1d059b0 --- /dev/null +++ b/src/foundation/result.h @@ -0,0 +1,80 @@ +#pragma once + +namespace pp::foundation { + +enum class StatusCode { + ok, + invalid_argument, + out_of_range, +}; + +struct Status { + StatusCode code = StatusCode::ok; + const char* message = "ok"; + + [[nodiscard]] constexpr bool ok() const noexcept + { + return code == StatusCode::ok; + } + + [[nodiscard]] static constexpr Status success() noexcept + { + return {}; + } + + [[nodiscard]] static constexpr Status invalid_argument(const char* message) noexcept + { + return { StatusCode::invalid_argument, message }; + } + + [[nodiscard]] static constexpr Status out_of_range(const char* message) noexcept + { + return { StatusCode::out_of_range, message }; + } +}; + +template +class Result { +public: + [[nodiscard]] static constexpr Result success(T value) noexcept + { + return Result(value, Status::success()); + } + + [[nodiscard]] static constexpr Result failure(Status status) noexcept + { + return Result(T{}, status); + } + + [[nodiscard]] constexpr bool ok() const noexcept + { + return status_.ok(); + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return ok(); + } + + [[nodiscard]] constexpr const T& value() const noexcept + { + return value_; + } + + [[nodiscard]] constexpr Status status() const noexcept + { + return status_; + } + +private: + constexpr Result(T value, Status status) noexcept + : value_(value) + , status_(status) + { + } + + T value_{}; + Status status_{}; +}; + +} diff --git a/src/main.cpp b/src/main.cpp index 62baa99..8b086cd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,10 @@ #include "abr.h" #include "settings.h" +#if __has_include() #include +#define USE_RENDERDOC +#endif #include #include @@ -33,7 +36,7 @@ HINSTANCE hInst; HWND hWnd; HDC hDC; HGLRC hRC; -wchar_t* className; +const wchar_t* className; bool keys[256]; std::mutex gl_mutex; std::mutex async_mutex; @@ -54,6 +57,7 @@ float timer_ink_touch = 0; float timer_ink_pen = 0; bool sandboxed = false; +#ifdef USE_RENDERDOC RENDERDOC_API_1_4_0* rdoc_api = NULL; bool win32_renderdoc_init() { @@ -78,6 +82,10 @@ void win32_renderdoc_frame_end() if (rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL); } +#else +void win32_renderdoc_frame_start() { } +void win32_renderdoc_frame_end() { } +#endif HRESULT(*GetDpiForMonitor_fn)(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT* dpiX, UINT* dpiY); HRESULT(*SetProcessDpiAwareness_fn)(PROCESS_DPI_AWARENESS value); @@ -367,7 +375,7 @@ int read_WMI_info() } IWbemServices* pService = NULL; - if (FAILED(hRes = pLocator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService))) + if (FAILED(hRes = pLocator->ConnectServer(BSTR(L"root\\CIMV2"), NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService))) { pLocator->Release(); LOG("Unable to connect to \"CIMV2\": %x", hRes); @@ -411,7 +419,7 @@ int read_WMI_info() // GET DEVICE INFO { IEnumWbemClassObject* pEnumerator = NULL; - if (FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_ComputerSystem", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) + if (FAILED(hRes = pService->ExecQuery(BSTR(L"WQL"), BSTR(L"SELECT * FROM Win32_ComputerSystem"), WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) { pLocator->Release(); pService->Release(); @@ -438,7 +446,7 @@ int read_WMI_info() // GET OS INFO { IEnumWbemClassObject* pEnumerator = NULL; - if (FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_OperatingSystem", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) + if (FAILED(hRes = pService->ExecQuery(BSTR(L"WQL"), BSTR(L"SELECT * FROM Win32_OperatingSystem"), WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) { pLocator->Release(); pService->Release(); @@ -468,7 +476,7 @@ int read_WMI_info() pService->Release(); pService = NULL; - if (FAILED(hRes = pLocator->ConnectServer(L"root\\Microsoft\\Windows\\DeviceGuard", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService))) + if (FAILED(hRes = pLocator->ConnectServer(BSTR(L"root\\Microsoft\\Windows\\DeviceGuard"), NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService))) { pLocator->Release(); LOG("Unable to connect to \"DeviceGuard\": %x", hRes); @@ -478,7 +486,7 @@ int read_WMI_info() // GET DEVICE GUARD { IEnumWbemClassObject* pEnumerator = NULL; - if (FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_DeviceGuard", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) + if (FAILED(hRes = pService->ExecQuery(BSTR(L"WQL"), BSTR(L"SELECT * FROM Win32_DeviceGuard"), WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) { pLocator->Release(); pService->Release(); @@ -954,8 +962,10 @@ int main(int argc, char** argv) LOG("GL vendor: %s", glGetString(GL_VENDOR)); LOG("GL renderer: %s", glGetString(GL_RENDERER)); +#ifdef USE_RENDERDOC if (!win32_renderdoc_init()) LOG("Renderdoc not started"); +#endif // USE_RENDERDOC swprintf_s(window_title, L"PanoPainter %s (%s)", g_version_number_w, str2wstr((char*)glGetString(GL_RENDERER)).c_str()); diff --git a/src/pch.h b/src/pch.h index 70d0a33..47837c7 100644 --- a/src/pch.h +++ b/src/pch.h @@ -64,8 +64,12 @@ #elif _WIN32 #define _USE_MATH_DEFINES + #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _SCL_SECURE_NO_WARNINGS #define _SCL_SECURE_NO_WARNINGS + #endif #include #include #include @@ -138,6 +142,7 @@ #include #include #include +#include #include #include #include @@ -181,4 +186,4 @@ #ifndef EMSCRIPTEN #include -#endif \ No newline at end of file +#endif diff --git a/src/version.cpp b/src/version.cpp index d7a20f7..ac368f7 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -11,7 +11,9 @@ const int g_version_build = PP_VERSION_BUILD; #ifdef _WIN32 #include -const wchar_t* g_version_w = TEXT(PP_VERSION_STRING); -const wchar_t* g_version_number_w = TEXT(PP_VERSION_NUMBER_STRING); -const wchar_t* g_window_title_w = L"PanoPainter " TEXT(PP_VERSION_NUMBER_STRING); +#define PP_WIDEN2(x) L##x +#define PP_WIDEN(x) PP_WIDEN2(x) +const wchar_t* g_version_w = PP_WIDEN(PP_VERSION_STRING); +const wchar_t* g_version_number_w = PP_WIDEN(PP_VERSION_NUMBER_STRING); +const wchar_t* g_window_title_w = L"PanoPainter " PP_WIDEN(PP_VERSION_NUMBER_STRING); #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..b7e8ce4 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(pp_test_harness INTERFACE) +target_include_directories(pp_test_harness INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(pp_test_harness INTERFACE + pp_project_options + pp_project_warnings) + +add_executable(pp_foundation_tests + foundation/binary_stream_tests.cpp) +target_link_libraries(pp_foundation_tests PRIVATE + pp_foundation + pp_test_harness) + +add_test(NAME pp_foundation_tests COMMAND pp_foundation_tests) +set_tests_properties(pp_foundation_tests PROPERTIES + LABELS "foundation;desktop-fast") + +if(TARGET pano_cli) + add_test(NAME pano_cli_create_document_smoke + COMMAND pano_cli create-document --width 64 --height 32 --layers 2) + set_tests_properties(pano_cli_create_document_smoke PROPERTIES + LABELS "integration;desktop-fast") +endif() diff --git a/tests/foundation/binary_stream_tests.cpp b/tests/foundation/binary_stream_tests.cpp new file mode 100644 index 0000000..38c578e --- /dev/null +++ b/tests/foundation/binary_stream_tests.cpp @@ -0,0 +1,100 @@ +#include "foundation/binary_stream.h" +#include "test_harness.h" + +#include +#include +#include +#include + +using pp::foundation::ByteReader; +using pp::foundation::ByteWriter; + +namespace { + +void round_trips_little_endian_values(pp::tests::Harness& h) +{ + std::vector bytes; + ByteWriter writer(bytes); + + PP_EXPECT(h, writer.write_u8(0x12U).ok()); + PP_EXPECT(h, writer.write_u16_le(0x3456U).ok()); + PP_EXPECT(h, writer.write_u32_le(0x789abcdeU).ok()); + PP_EXPECT(h, writer.size() == 7U); + + ByteReader reader(bytes); + const auto u8 = reader.read_u8(); + const auto u16 = reader.read_u16_le(); + const auto u32 = reader.read_u32_le(); + + PP_EXPECT(h, u8.ok()); + PP_EXPECT(h, u8.value() == 0x12U); + PP_EXPECT(h, u16.ok()); + PP_EXPECT(h, u16.value() == 0x3456U); + PP_EXPECT(h, u32.ok()); + PP_EXPECT(h, u32.value() == 0x789abcdeU); + PP_EXPECT(h, reader.empty()); +} + +void rejects_overread_without_moving_cursor(pp::tests::Harness& h) +{ + const std::array bytes { + std::byte { 0x01 }, + std::byte { 0x02 }, + std::byte { 0x03 }, + }; + ByteReader reader(bytes); + + PP_EXPECT(h, reader.seek(2).ok()); + const auto before = reader.position(); + const auto value = reader.read_u32_le(); + + PP_EXPECT(h, !value.ok()); + PP_EXPECT(h, reader.position() == before); + PP_EXPECT(h, reader.remaining() == 1U); +} + +void rejects_out_of_range_seek(pp::tests::Harness& h) +{ + const std::array bytes { + std::byte { 0x01 }, + std::byte { 0x02 }, + }; + ByteReader reader(bytes); + + PP_EXPECT(h, !reader.seek(3).ok()); + PP_EXPECT(h, reader.position() == 0U); + PP_EXPECT(h, reader.seek(2).ok()); + PP_EXPECT(h, reader.empty()); +} + +void boundary_reads_are_consistent(pp::tests::Harness& h) +{ + std::array bytes {}; + for (std::size_t i = 0; i < bytes.size(); ++i) { + bytes[i] = static_cast(i); + } + + for (std::size_t length = 0; length <= bytes.size(); ++length) { + ByteReader reader(std::span(bytes.data(), length)); + const auto exact = reader.read_bytes(length); + PP_EXPECT(h, exact.ok()); + PP_EXPECT(h, exact.value().size() == length); + PP_EXPECT(h, reader.empty()); + + const auto too_much = reader.read_u8(); + PP_EXPECT(h, !too_much.ok()); + PP_EXPECT(h, reader.position() == length); + } +} + +} + +int main() +{ + pp::tests::Harness harness; + harness.run("round_trips_little_endian_values", round_trips_little_endian_values); + harness.run("rejects_overread_without_moving_cursor", rejects_overread_without_moving_cursor); + harness.run("rejects_out_of_range_seek", rejects_out_of_range_seek); + harness.run("boundary_reads_are_consistent", boundary_reads_are_consistent); + return harness.finish(); +} diff --git a/tests/test_harness.h b/tests/test_harness.h new file mode 100644 index 0000000..12df4f6 --- /dev/null +++ b/tests/test_harness.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +namespace pp::tests { + +class Harness { +public: + void expect(bool condition, std::string_view expression, std::string_view file, int line) + { + ++assertions_; + if (!condition) { + ++failures_; + std::cerr << file << ":" << line << ": expectation failed: " << expression << "\n"; + } + } + + template + void run(std::string_view name, Callback callback) + { + const auto failures_before = failures_; + callback(*this); + ++tests_; + if (failures_ == failures_before) { + std::cout << "[pass] " << name << "\n"; + } else { + std::cout << "[fail] " << name << "\n"; + } + } + + [[nodiscard]] int finish() const + { + std::cout << "{\"tests\":" << tests_ + << ",\"assertions\":" << assertions_ + << ",\"failures\":" << failures_ << "}\n"; + return failures_ == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + } + +private: + int tests_ = 0; + int assertions_ = 0; + int failures_ = 0; +}; + +} + +#define PP_EXPECT(harness, expression) \ + (harness).expect(static_cast(expression), #expression, __FILE__, __LINE__) diff --git a/tools/pano_cli/CMakeLists.txt b/tools/pano_cli/CMakeLists.txt new file mode 100644 index 0000000..895c5ec --- /dev/null +++ b/tools/pano_cli/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(pano_cli + main.cpp) +target_link_libraries(pano_cli PRIVATE + pp_project_options + pp_project_warnings + pp_foundation) diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp new file mode 100644 index 0000000..10ad764 --- /dev/null +++ b/tools/pano_cli/main.cpp @@ -0,0 +1,113 @@ +#include "foundation/result.h" + +#include +#include +#include +#include + +namespace { + +struct DocumentArgs { + std::uint32_t width = 0; + std::uint32_t height = 0; + std::uint32_t layers = 1; +}; + +bool parse_u32(std::string_view text, std::uint32_t& value) +{ + const auto* begin = text.data(); + const auto* end = text.data() + text.size(); + const auto [ptr, ec] = std::from_chars(begin, end, value); + return ec == std::errc {} && ptr == end; +} + +void print_error(std::string_view command, std::string_view message) +{ + std::cout << "{\"ok\":false,\"command\":\"" << command + << "\",\"error\":\"" << message << "\"}\n"; +} + +void print_help() +{ + std::cout + << "pano_cli commands:\n" + << " create-document --width N --height N [--layers N]\n" + << " --help\n"; +} + +pp::foundation::Status parse_document_args(int argc, char** argv, DocumentArgs& args) +{ + for (int i = 2; i < argc; ++i) { + const std::string_view key(argv[i]); + if (key == "--width" || key == "--height" || key == "--layers") { + if (i + 1 >= argc) { + return pp::foundation::Status::invalid_argument("missing value for option"); + } + + std::uint32_t value = 0; + if (!parse_u32(argv[++i], value)) { + return pp::foundation::Status::invalid_argument("option value must be an unsigned integer"); + } + + if (key == "--width") { + args.width = value; + } else if (key == "--height") { + args.height = value; + } else { + args.layers = value; + } + } else { + return pp::foundation::Status::invalid_argument("unknown option"); + } + } + + if (args.width == 0 || args.height == 0) { + return pp::foundation::Status::invalid_argument("width and height must be greater than zero"); + } + + if (args.layers == 0) { + return pp::foundation::Status::invalid_argument("layer count must be greater than zero"); + } + + return pp::foundation::Status::success(); +} + +int create_document(int argc, char** argv) +{ + DocumentArgs args; + const auto status = parse_document_args(argc, argv, args); + if (!status.ok()) { + print_error("create-document", status.message); + return 2; + } + + std::cout << "{\"ok\":true,\"command\":\"create-document\",\"document\":{" + << "\"width\":" << args.width + << ",\"height\":" << args.height + << ",\"layers\":" << args.layers + << "}}\n"; + return 0; +} + +} + +int main(int argc, char** argv) +{ + if (argc < 2) { + print_help(); + return 1; + } + + const std::string_view command(argv[1]); + if (command == "--help" || command == "-h") { + print_help(); + return 0; + } + + if (command == "create-document") { + return create_document(argc, argv); + } + + print_error(command, "unknown command"); + return 2; +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..cb3b8d3 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,16 @@ +{ + "name": "panopainter", + "version-string": "0.0.0-modernization", + "description": "PanoPainter modernization dependency manifest.", + "dependencies": [ + "catch2", + "curl", + "fmt", + "glad", + "glm", + "sqlite3", + "stb", + "tinyxml2" + ] +} +