From bdd7a32ff5d885b175614242675899fe9bae63b3 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Fri, 5 Jun 2026 16:06:52 +0200 Subject: [PATCH] Prefer OpenXR for desktop XR policy --- docs/modernization/build-inventory.md | 16 +++++--- docs/modernization/capability-map.md | 2 +- docs/modernization/debt.md | 10 ++++- docs/modernization/roadmap.md | 23 +++++++----- src/platform_api/platform_policy.cpp | 28 ++++++++++++++ src/platform_api/platform_policy.h | 17 +++++++++ .../windows_platform_services.cpp | 37 ++++++++++++++++++- .../platform_api/platform_services_tests.cpp | 36 ++++++++++++++++++ 8 files changed, 149 insertions(+), 20 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index f1fb52e..c671e35 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -44,7 +44,8 @@ dependencies until each platform triplet is proven. | 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 | +| OpenXR | Not wired yet | Target desktop XR backend; add SDK/package behind `pp_platform_vr` | +| OpenVR | `libs/openvr` | Retain only as a temporary desktop compatibility fallback; remove under `DEBT-0061` | | OVR Platform/Mobile | `libs/ovr_platform`, `libs/ovr_mobile` | Retain initially | | Wave SDK | `libs/wave_sdk` | Retain initially | | Wacom WinTab | `libs/wacom` | Retain initially | @@ -323,8 +324,10 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p the `pp_app_core` `AppPreferenceServices` contract while retained settings persistence, recording lifecycle, and legacy canvas/UI execution remain tracked by `DEBT-0045`; the VR mode callbacks now call `App` wrappers that - dispatch through `PlatformServices` before reaching the retained platform VR - bridge. + dispatch through `PlatformServices` before reaching the selected platform XR + runtime. `pp_platform_api` now prefers OpenXR in its tested desktop runtime + selection policy and marks the retained Windows OpenVR SDK path as a legacy + fallback under `DEBT-0061`. - `src/legacy_app_startup_services.*` is the current app-shell bridge for startup preference/runtime execution. It keeps run-counter persistence, startup preference save, auto-timelapse startup, stored VR-controller state, @@ -623,9 +626,10 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p context. Desktop VR drawing also consumes backend-owned scissor/depth/blend state, blend/depth state query-restore, depth clear masks, active texture unit - dispatch, and fallback 2D texture unbind dispatch; VR SDK start/stop now dispatches - through `PlatformServices` while retaining the existing Windows OpenVR bridge - shape. Its retained callback endpoints now share `legacy_ui_gl_dispatch` + dispatch, and fallback 2D texture unbind dispatch; XR SDK start/stop now + dispatches through `PlatformServices` and the tested desktop runtime policy + prefers OpenXR before falling back to the existing Windows OpenVR bridge. Its + retained callback endpoints now share `legacy_ui_gl_dispatch` with app startup, app clear, app UI viewport/scissor, and command-convert renderer state callbacks, so those files no longer duplicate local raw GL adapter clusters for capability, blend, clear, viewport, scissor, active diff --git a/docs/modernization/capability-map.md b/docs/modernization/capability-map.md index 6652767..a64eef3 100644 --- a/docs/modernization/capability-map.md +++ b/docs/modernization/capability-map.md @@ -70,7 +70,7 @@ and validation command. | Wacom pressure | `WacomTablet` | `pp_platform_windows` | Adapter smoke with fallback | | Clipboard/file picker/share/display | `App` platform methods | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Clipboard read/write, share saved-path, picked-path, and display-file decision tests, platform service display/share/picker dispatch tests, platform smoke or mocked service | | Virtual keyboard | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Keyboard visibility decision tests, platform service dispatch tests, platform smoke | -| OpenVR desktop | `HMD`, `Vive`, `app_vr` | `pp_platform_vr`, app | Compile gate and mocked pose tests | +| Desktop XR | `HMD`, `Vive`, `app_vr`, retained OpenVR bridge | `pp_platform_vr`, app with OpenXR backend | Runtime-selection policy tests, 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 | diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index b0f91ad..a510f32 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -417,13 +417,18 @@ agent or engineer to remove them without reconstructing context from chat. `src/main.cpp`, and `src/app_shaders.cpp` no longer carry duplicated raw runtime-query callback clusters. Renderer services still need to own runtime and capability probing before this bridge can be removed. +- 2026-06-05: DEBT-0061 was opened. `pp_platform_api` now owns a tested desktop + XR runtime-selection policy that prefers OpenXR and labels OpenVR as a + legacy fallback; `WindowsPlatformServices` consumes that policy before + calling the retained OpenVR bridge. The actual OpenXR SDK/backend wiring + remains open. ## 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, including the generated Android package `nanort` compatibility overlay tracked by DEBT-0060 | 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-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially, including the generated Android package `nanort` compatibility overlay tracked by DEBT-0060 and the retained OpenVR SDK fallback tracked by DEBT-0061 | 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; `App::open_document`, `App::request_close`, `App::share_file`, `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `App::rec_loop`, `App::update_ui_observer`, `App::render_task*`, `App::ui_task*`, `App::render_thread_*`, `App::ui_thread_*`, file-menu save actions, `NodeCanvas` canvas hotkeys, new/open/browse dirty-document workflow prompts, new-document target/resolution/overwrite decisions, save-as document file naming and overwrite decisions, save-version target decisions, export start/menu/target naming/path/message/report decisions, share-file saved-path decisions, file/image/save/directory picker selected-path decisions, display-file external-open decisions, virtual-keyboard visibility decisions, recording lifecycle/export progress/worker decisions, cloud-upload prompt/save-before-upload decisions, cloud-browse availability and selected-download decisions, bulk cloud-upload progress decisions, tools/options app preference decisions, app status/display and renderer diagnostic decisions, app dialog metadata decisions, app frame/UI-observer decisions, app thread/task orchestration decisions, document resize decisions, layer rename/menu decisions, Tools menu/panel decisions, About menu/diagnostic decisions, main toolbar/status decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-file-menu`, `pano_cli plan-new-document`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-start`, `pano_cli plan-export-menu`, `pano_cli plan-export-target`, `pano_cli plan-export-message`, `pano_cli plan-export-report`, `pano_cli plan-recording-session`, `pano_cli plan-app-preferences`, `pano_cli plan-app-status`, `pano_cli plan-app-dialog`, `pano_cli plan-app-thread`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, `pano_cli plan-about-menu`, `pano_cli plan-main-toolbar`, `pano_cli plan-document-resize`, `pano_cli plan-layer-rename`, `pano_cli plan-layer-menu`, `pano_cli plan-canvas-hotkey`, `pano_cli plan-share-file`, `pano_cli plan-picked-path`, `pano_cli plan-display-file`, `pano_cli plan-keyboard-visibility`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-upload-all`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export/recording/preferences/status/dialog/thread/share/platform-I/O/display/keyboard/cloud/resize/layer/tools/about/toolbar/canvas-command contracts, but document creation/loading, brush import execution, saving, export execution, tools/options UI execution, Tools panel creation/execution, About dialog/diagnostic execution, toolbar/status dialog/history/canvas execution, app dialog node creation, status/display UI rendering, renderer diagnostic capability adaptation, app task/thread execution, UI observer parent walking/callback execution, document resize execution, layer rename/menu execution, settings persistence, platform share service execution, picker service execution, display-file service execution, keyboard service execution, recording/MP4/PBO execution, cloud upload execution, and cloud browse/download execution still reach legacy `Canvas::I`/UI/network/video/platform singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_file_menu_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_recording_tests`; `pp_app_core_app_frame_tests`; `pp_app_core_app_preferences_tests`; `pp_app_core_app_status_tests`; `pp_app_core_app_dialog_tests`; `pp_app_core_app_thread_tests`; `pp_app_core_tools_menu_tests`; `pp_app_core_about_menu_tests`; `pp_app_core_main_toolbar_tests`; `pp_app_core_document_resize_tests`; `pp_app_core_document_layer_tests`; `pp_app_core_document_sharing_tests`; `pp_app_core_document_platform_io_tests`; `pp_app_core_document_cloud_tests`; `pp_app_core_document_session_tests`; `pp_app_core_canvas_hotkey_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-file-menu --command save-as`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind animation-mp4 --demo`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli plan-export-message --kind timelapse --destination success`; `pano_cli plan-export-report --kind license-disabled`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --running --no-encoder`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-app-status --doc-name demo --unsaved --resolution 2048 --resolution-index 3 --zoom 1.25 --history-bytes 1572864 --recording-running --encoder-available --encoded-frames 12 --framebuffer-fetch --float32 --float32-linear --float16`; `pano_cli plan-app-dialog --kind message --cancel`; `pano_cli plan-app-frame`; `pano_cli plan-app-thread --kind ui-loop --live-reload`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-layer-menu --command merge --current-index 2 --lower-name Paint`; `pano_cli plan-canvas-hotkey --event key-up --key z --ctrl --undo-count 2`; `pano_cli plan-share-file --path D:/Paint/demo.ppi`; `pano_cli plan-picked-path --path D:/Paint/demo.ppi`; `pano_cli plan-display-file --path D:/Paint/export.png`; `pano_cli plan-keyboard-visibility --visible`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries | | DEBT-0004 | Open | Modernization | Android, Linux, and WebGL retained CMake entrypoints now use the CMake 3.10/C++23 modernization baseline, but Android Gradle/APK, Linux app, WebGL app/package, 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`; `python scripts/dev/check_retained_platform_cmake.py`; 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 | @@ -464,7 +469,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0042 | Open | Modernization | Accepted Save As and Save Version planning/execution dispatch plus Save As overwrite prompt metadata now consumes pure `pp_app_core` through `App::dialog_save`, `App::dialog_save_ver`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-document-session-prompt`, `DocumentFileSaveServices`, `DocumentVersionSaveServices`, and `src/legacy_document_session_services.*`; Save As overwrite prompt creation now uses `src/legacy_app_dialog_services.*`, but the bridge still wires overwrite callbacks directly, calls legacy `Canvas::project_save`, mutates app document name/path/directory fields, marks version saves dirty before saving, updates the title, and handles keyboard/dialog cleanup directly | Preserve current Save As and Save Version behavior while document persistence moves toward app/document/storage/UI services | `pp_app_core_document_session_tests`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-session-prompt --kind file-overwrite --name demo`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli simulate-app-session --save-intent save-as`; `pano_cli simulate-app-session --save-intent save-version`; `ctest --preset desktop-fast --build-config Debug` | Save As overwrite prompting, project-save execution, app document metadata updates, title updates, version-save dirty-state handling, and keyboard/dialog cleanup are owned by injected app/document/storage/UI services with `App::dialog_save` and `App::dialog_save_ver` acting only as UI adapters | | DEBT-0043 | Open | Modernization | Equirectangular, layer, animation-frame, depth, and cube-face export planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_export`, `App::dialog_export_layers`, `App::dialog_export_anim_frames`, `App::dialog_export_depth`, `App::dialog_export_cube_faces`, `pano_cli plan-export-*`, `DocumentExportServices`, and `src/legacy_document_export_services.*`; layer/frame dialogs also consume `plan_document_export_collection_target` plus `PlatformServices::uses_work_directory_document_export_collections()` instead of spelling local iOS branches, and export success/failure/license dialog metadata plus execution log labels now come from `pp_app_core`, but the bridge still calls legacy `Canvas` export methods, creates export directories, handles picker-selected stems, and performs Web prepared-file handoff directly | Preserve current image/collection/depth/cube export behavior while export execution moves toward document/renderer/platform/storage services | `pp_app_core_document_export_tests`; `pp_platform_api_tests`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind layers`; `pano_cli plan-export-target --kind collection --work-dir D:/Paint --doc-name demo --suffix _layers`; `pano_cli plan-export-message --kind equirectangular --destination work --detail D:/Paint`; `pano_cli plan-export-report --kind license-disabled`; `pano_cli simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and cube export execution, export-directory creation, Web file handoff, picker-selected stem handling, and legacy canvas export calls are owned by injected document/renderer/platform/storage services with export dialogs acting only as UI adapters | | DEBT-0044 | Open | Modernization | Timelapse and animation MP4 export execution dispatch now consumes pure `pp_app_core` through `App::dialog_timelapse_export`, `App::dialog_export_mp4`, `pano_cli plan-export-menu`, `pano_cli plan-export-target --kind name`, `pano_cli plan-export-message`, `pano_cli plan-export-report`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, and success/failure/license dialog metadata plus execution log labels now come from `pp_app_core`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, and owns mobile/Web save callbacks | Preserve current MP4/timelapse export behavior while video export moves toward app/document/renderer/video/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind animation-mp4`; `pano_cli plan-export-menu --kind timelapse`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -animation`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -timelapse`; `pano_cli plan-export-message --kind timelapse --destination success`; `pano_cli plan-export-report --kind animation-mp4 --message "video export path must not be empty"`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, and mobile/Web save callbacks are owned by injected app/document/renderer/video/platform/storage services with export dialogs acting only as UI adapters | -| DEBT-0045 | Open | Modernization | Options-menu preference execution now consumes pure `pp_app_core` through UI scale, viewport scale, RTL direction, VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks plus `AppPreferenceServices` and `src/legacy_app_preference_services.*`; viewport-density and cursor-mode execution now delegate to `src/legacy_canvas_view_services.*`, but the bridges still call legacy `App::set_ui_scale`, `App::set_ui_rtl`, `App::rec_start`, `App::rec_stop`, retained canvas view mutation, and `Settings::save` directly; VR mode callbacks now call `App` VR wrappers that dispatch to `PlatformServices`, while the actual Windows OpenVR SDK bridge still lives in `WindowsPlatformServices` | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_tests`; `pp_app_core_canvas_view_tests`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-canvas-view-density --density 1.5`; `pano_cli plan-canvas-view-cursor-mode --mode 3`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Preference persistence, UI/layout direction, viewport density, cursor mode, VR mode start/stop/failure handling, VR-controller state, and auto-timelapse recording side effects are owned by injected app/UI/platform/storage services with options-menu callbacks acting only as UI adapters | +| DEBT-0045 | Open | Modernization | Options-menu preference execution now consumes pure `pp_app_core` through UI scale, viewport scale, RTL direction, VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks plus `AppPreferenceServices` and `src/legacy_app_preference_services.*`; viewport-density and cursor-mode execution now delegate to `src/legacy_canvas_view_services.*`, but the bridges still call legacy `App::set_ui_scale`, `App::set_ui_rtl`, `App::rec_start`, `App::rec_stop`, retained canvas view mutation, and `Settings::save` directly; VR mode callbacks now call `App` VR wrappers that dispatch to `PlatformServices`, whose desktop runtime policy prefers OpenXR while the actual Windows OpenVR SDK bridge still lives in `WindowsPlatformServices` under DEBT-0061 | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_tests`; `pp_app_core_canvas_view_tests`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-canvas-view-density --density 1.5`; `pano_cli plan-canvas-view-cursor-mode --mode 3`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Preference persistence, UI/layout direction, viewport density, cursor mode, VR mode start/stop/failure handling, VR-controller state, and auto-timelapse recording side effects are owned by injected app/UI/platform/storage services with options-menu callbacks acting only as UI adapters | | DEBT-0046 | Open | Modernization | Startup preference/runtime execution and startup resource sequencing now consume pure `pp_app_core` through `App::init`, `pano_cli plan-app-startup`, `pano_cli plan-app-startup-resources`, `AppStartupServices`, `AppStartupResourceServices`, and `src/legacy_app_startup_services.*`, but the bridge still calls legacy `Settings::set`, `Settings::save`, `App::rec_start`, app VR-controller state mutation, message-box license warning execution, shader loading, asset initialization, layout creation, title updates, and UI render-target creation directly | Preserve current startup behavior while app startup moves toward app/preferences/storage/recording/UI/renderer services | `pp_app_core_app_startup_tests`; `pano_cli plan-app-startup --run-counter 7 --vr-controllers-disabled --license-invalid`; `pano_cli plan-app-startup --run-counter -1`; `pano_cli plan-app-startup-resources --width 1280 --height 720`; `pano_cli plan-app-startup-resources --bad-size`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Startup preference persistence, auto-timelapse startup, stored VR-controller state, license validation/warning, startup resource initialization, title updates, and UI render-target allocation are owned by injected app/preferences/storage/recording/UI/renderer services with `App::init` acting only as orchestration | | DEBT-0047 | Open | Modernization | PPBR brush package export request validation, success-dialog metadata, and execution dispatch now consume pure `pp_app_core` through `App::dialog_ppbr_export`, `pano_cli plan-brush-package-export`, `BrushPackageExportServices`, and `src/legacy_brush_package_export_services.*`; PPBR header/path planning now consumes `pp_assets::brush_package`, and the macOS data-directory override now routes through `PlatformServices`, but the bridge still reads `NodeDialogExportPPBR`, carries the legacy `Image` header object outside the pure request, converts to `NodePanelBrushPreset::PPBRInfo`, calls `NodePanelBrushPreset::export_ppbr`, owns desktop worker-thread dispatch, dialog destruction, and mobile/Web completion directly | Preserve current PPBR export behavior while brush assets, PPBR serialization, picker completion, and UI lifetime move toward asset/storage/UI/platform services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_export_tests`; `pp_platform_api_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --author Artist --dest-path D:/Paint/BrushPreviews --export-data --header-image`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `pano_cli plan-brush-package-export`; `pano_cli plan-brush-package-export --path clouds`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --dest-path D:/Paint/BrushPreviews --no-export-data`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | PPBR metadata collection, header-image ownership, serialization, picker-selected path execution, desktop threading, dialog lifetime, and mobile/Web completion are owned by injected brush asset/storage/UI/platform services with `App::dialog_ppbr_export` acting only as a UI adapter | | DEBT-0048 | Open | Modernization | ABR/PPBR brush package import execution now consumes pure `pp_app_core` through document-open confirmation callbacks, `pano_cli plan-brush-package-import`, `BrushPackageImportServices`, and `src/legacy_brush_package_import_services.*`; imported brush tip/pattern target paths now consume `pp_assets::brush_package`, but the bridge still launches detached legacy `NodePanelBrushPreset::import_abr`/`import_ppbr` worker threads and depends on the legacy preset panel as the importer/storage owner | Preserve current brush import behavior while brush package parsing, preset storage, progress/error reporting, and UI refresh move toward asset/paint/UI services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_import_tests`; `pano_cli plan-brush-package-import --kind ppbr --path D:/Paint/Brushes/clouds.ppbr`; `pano_cli plan-brush-package-import --kind abr --path D:/Paint/Brushes/clouds.abr`; `pano_cli plan-brush-package-import --kind ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | ABR/PPBR parsing, preset creation/storage, import threading/progress, duplicate asset policy, and UI refresh are owned by injected brush asset/paint/UI services with document-open callbacks only confirming user intent | @@ -477,6 +482,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0055 | Open | Modernization | `src/app.h` now forward-declares retained iOS/macOS/Android/Linux/Web platform handles instead of including platform SDK headers, and full SDK includes are isolated in `src/platform_legacy/legacy_platform_services.cpp`, but the `App` singleton still stores those platform handles directly | Reduce central header platform coupling incrementally without rewriting non-Windows platform entrypoints before Phase 6 | Windows app build; Apple/Android/Linux/Web package smoke once platform root builds are active | Platform handles are owned by injected `pp_platform_*` shell state or services, and `App` has no platform SDK handle fields or platform conditional members | | DEBT-0056 | Open | Modernization | `src/asset.h` is now Android-SDK-free and uses opaque Android asset handles behind `Asset::set_android_asset_manager`, but retained `Asset` still owns a static Android asset-manager bridge and `src/asset.cpp` still performs Android `AAssetManager` reads directly; the current `android-arm64` root preset is headless and does not expose `pp_legacy_assets_io`, though the retained Android standard package `native-lib` now builds through its refreshed C++23 CMake path | Reduce legacy asset I/O header coupling without rewriting Android asset loading before the asset manager/storage boundary exists | Windows app build; `powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64 -Targets pp_assets`; `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard` | Android asset loading is owned by injected asset storage/platform services or `pp_assets` file providers, with no static Android asset manager on `Asset` | | DEBT-0060 | Open | Modernization | Retained Android package CMake generates a patched `nanort.h` overlay in the build tree for `native-lib` instead of modifying the `libs/nanort` submodule | Current SDK Manager NDK/Clang rejects `TriangleSAHPred::operator=` assigning to a `const size_t` member, but the retained grid/lightmap path still includes `nanort` before that dependency is replaced or updated | `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard`; Quest/Focus retained package configure checks; Windows app build | Update/replace `nanort`, move grid/lightmap baking behind a component that owns its dependency, or retire the retained Android package CMake path so no generated vendor overlay is required | +| DEBT-0061 | Open | Modernization | Desktop XR runtime selection now lives in tested `pp_platform_api` policy and prefers OpenXR, but `WindowsPlatformServices` still reports OpenXR unavailable and reaches the retained OpenVR SDK bridge as a legacy fallback | Preserve current desktop VR behavior while replacing OpenVR with OpenXR behind the platform/renderer boundary | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Add an OpenXR SDK/package target, implement desktop OpenXR startup/shutdown/pose/controller submission behind `pp_platform_vr` or `PlatformServices`, validate parity with mocked/runtime smoke coverage, and remove `libs/openvr` plus the OpenVR link/include paths from root CMake | | DEBT-0057 | Open | Modernization | Default canvas allocation size now dispatches through `PlatformServices::default_canvas_resolution`, removing the `CANVAS_RES` platform macro from `src/canvas.h`; WebGL's retained 512 default now lives in tested `pp_platform_api::platform_policy`, but the Web shell still reaches it through the legacy platform fallback until injected Web services own the policy | Preserve WebGL memory behavior while moving canvas creation policy out of shared canvas headers and into the platform boundary | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests`; Windows app build; WebGL package smoke once root Web build exists | Default canvas resolution is owned by injected `pp_platform_*` services for every supported platform, with no WebGL branch in the legacy fallback | | DEBT-0058 | Open | Modernization | App-level progress/message/input dialog metadata, including message-dialog OK/cancel captions, now consumes pure `pp_app_core` through `App::show_progress`, `App::message_box`, `App::input_box`, `pano_cli plan-app-dialog`, and `pp_app_core_app_dialog_tests`; live execution is centralized in `src/legacy_app_dialog_services.*`, but the bridge still creates retained `NodeProgressBar`, `NodeMessageBox`, and `NodeInputBox` instances and inserts them into the legacy layout tree | Preserve current app-shell dialog behavior while moving shared dialog policy toward UI/app services | `pp_app_core_app_dialog_tests`; `pano_cli plan-app-dialog --kind progress --total -4`; `pano_cli plan-app-dialog --kind message --cancel`; `pano_cli plan-app-dialog --kind input --ok-caption Save`; `ctest --preset desktop-fast --build-config Debug`; Windows app build | Progress/message/input dialog creation, callback wiring, layout insertion, lifetime ownership, and headless automation are owned by injected app/UI services with `App` methods acting only as adapters | | DEBT-0059 | Open | Modernization | iOS root CMake headless builds assign generated bundle identifiers and disable code signing for executable test/tool targets | The current Apple gate is compile validation for shared component targets; signed iOS app/package validation is not migrated to root CMake yet | `powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.ps1 -Presets macos,ios-simulator,ios-device`; `sh scripts/automation/platform-build.sh "ios-device"` on `panopainter-mac` | Root CMake owns the signed Apple app/package targets, package-smoke validates Apple bundles where signing material is available, and headless iOS test/tool targets are either excluded from signed package builds or use explicit test-runner signing policy | diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index b91dc58..584eccd 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -336,7 +336,8 @@ Implementation tasks: - `glad` - `Catch2` - Keep vendored until proven: - - OpenVR + - OpenVR only as the temporary desktop compatibility fallback while OpenXR is + introduced behind `pp_platform_vr` - OVR/Wave SDKs - Wacom WinTab - AppCenter @@ -735,9 +736,11 @@ VR mode, VR-controller, auto-timelapse, and cursor-mode side effects through retained settings writes, recording lifecycle calls, and legacy canvas/UI adapters continue. VR mode start/stop now enters `App` platform wrappers that dispatch through -`PlatformServices`; Windows keeps the retained OpenVR bridge in -`WindowsPlatformServices`, while the legacy fallback reports unsupported VR -startup on non-Windows platforms until their shells own the service. +`PlatformServices`; the desktop runtime-selection policy in `pp_platform_api` +prefers OpenXR and marks OpenVR as a legacy fallback. Windows still reaches the +retained OpenVR bridge in `WindowsPlatformServices` until the OpenXR backend is +wired, while the legacy fallback reports unsupported VR startup on non-Windows +platforms until their shells own the service. `pano_cli plan-about-menu` exposes app-core planning for About menu help, about, what's-new, crash-test, and performance-test commands, including versioned what's-new labels, diagnostic gating, and no-canvas performance-test @@ -823,8 +826,9 @@ available and dispatches startup through the same service, preserving the current iOS Objective-C bridge in the legacy adapter while removing iOS branches from `App::init_menu_tools` and `LegacyToolsMenuServices`. App VR lifecycle start/stop now asks `PlatformServices`, preserving the current -Windows OpenVR startup/shutdown bridge in `WindowsPlatformServices` while -non-Windows fallback adapters keep the existing unsupported/no-op behavior. +Windows OpenVR startup/shutdown bridge as the selected legacy fallback in +`WindowsPlatformServices` while non-Windows fallback adapters keep the existing +unsupported/no-op behavior. Canvas image export publishing and explicit persistent-storage flushes now dispatch through `PlatformServices` too, preserving iOS photo-library export publication and WebGL filesystem sync behavior in the legacy adapter while @@ -903,8 +907,8 @@ the live OpenGL call sequence. The retained viewport/scissor callback endpoints now share `legacy_ui_gl_dispatch`. VR UI framebuffer viewport and scissor-test setup now also consumes those `pp_renderer_gl` contracts, keeping desktop and VR UI rendering aligned while -the retained OpenVR app path is split incrementally; its retained callback -endpoints now reuse the shared UI GL bridge. +the desktop XR path moves from the retained OpenVR app path toward OpenXR; its +retained callback endpoints now reuse the shared UI GL bridge. VR draw blend/depth state snapshots, transitions, restore, and depth-buffer clears, active texture unit switches, and fallback 2D texture unbinds now use generic tested `pp_renderer_gl` capability query/apply, clear, active-texture, @@ -2699,7 +2703,8 @@ Use this as the starting checklist for Phase 0 inventory. VR controllers. - Platform services: clipboard, file picker, save picker, directory picker, share/display file, keyboard show/hide, cursor visibility. -- VR/platform variants: OpenVR desktop, Quest, Focus/Wave, Android standard, +- VR/platform variants: OpenXR desktop target with retained OpenVR fallback, + 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/src/platform_api/platform_policy.cpp b/src/platform_api/platform_policy.cpp index 32a5158..1525a64 100644 --- a/src/platform_api/platform_policy.cpp +++ b/src/platform_api/platform_policy.cpp @@ -23,6 +23,34 @@ PlatformFamily current_platform_family() noexcept #endif } +const char* xr_runtime_backend_name(XrRuntimeBackend backend) noexcept +{ + switch (backend) + { + case XrRuntimeBackend::openxr: + return "openxr"; + case XrRuntimeBackend::openvr: + return "openvr"; + case XrRuntimeBackend::none: + default: + return "none"; + } +} + +XrRuntimeSelection select_desktop_xr_runtime( + bool openxr_available, + bool openvr_available, + bool allow_legacy_openvr_fallback) noexcept +{ + if (openxr_available) + return { XrRuntimeBackend::openxr, false }; + + if (allow_legacy_openvr_fallback && openvr_available) + return { XrRuntimeBackend::openvr, true }; + + return {}; +} + bool platform_deletes_recorded_files_on_clear(PlatformFamily family) noexcept { return family == PlatformFamily::ios || family == PlatformFamily::macos; diff --git a/src/platform_api/platform_policy.h b/src/platform_api/platform_policy.h index 719ae88..9adfd11 100644 --- a/src/platform_api/platform_policy.h +++ b/src/platform_api/platform_policy.h @@ -17,8 +17,25 @@ enum class PlatformFamily { webgl, }; +enum class XrRuntimeBackend { + none, + openxr, + openvr, +}; + +struct XrRuntimeSelection { + XrRuntimeBackend backend = XrRuntimeBackend::none; + bool uses_legacy_openvr_fallback = false; +}; + [[nodiscard]] PlatformFamily current_platform_family() noexcept; +[[nodiscard]] const char* xr_runtime_backend_name(XrRuntimeBackend backend) noexcept; +[[nodiscard]] XrRuntimeSelection select_desktop_xr_runtime( + bool openxr_available, + bool openvr_available, + bool allow_legacy_openvr_fallback) noexcept; + [[nodiscard]] bool platform_deletes_recorded_files_on_clear(PlatformFamily family) noexcept; [[nodiscard]] bool platform_publishes_exported_images(PlatformFamily family) noexcept; [[nodiscard]] bool platform_flushes_persistent_storage(PlatformFamily family) noexcept; diff --git a/src/platform_windows/windows_platform_services.cpp b/src/platform_windows/windows_platform_services.cpp index 0cdda9a..36a7a8c 100644 --- a/src/platform_windows/windows_platform_services.cpp +++ b/src/platform_windows/windows_platform_services.cpp @@ -209,6 +209,15 @@ void ensure_directory(const std::string& path) CreateDirectoryA(path.c_str(), NULL); } +[[nodiscard]] pp::platform::XrRuntimeSelection select_windows_xr_runtime() noexcept +{ + // DEBT-0061: OpenXR is the target backend; Windows only exposes the retained OpenVR bridge today. + return pp::platform::select_desktop_xr_runtime( + false, + true, + true); +} + std::string build_supported_files_filter(const std::vector& types) { std::string filter = "Supported Files ("; @@ -442,12 +451,33 @@ public: [[nodiscard]] bool start_vr_mode() override { - return win32_vr_start(); + const auto runtime = select_windows_xr_runtime(); + if (runtime.backend == pp::platform::XrRuntimeBackend::openvr) + { + active_xr_runtime_backend_ = pp::platform::XrRuntimeBackend::none; + if (win32_vr_start()) + active_xr_runtime_backend_ = runtime.backend; + return active_xr_runtime_backend_ == runtime.backend; + } + + if (runtime.backend == pp::platform::XrRuntimeBackend::openxr) + LOG("OpenXR runtime selected but the Windows OpenXR backend is not wired yet"); + + return false; } void stop_vr_mode() override { - win32_vr_stop(); + auto runtime = active_xr_runtime_backend_; + if (runtime == pp::platform::XrRuntimeBackend::none) + runtime = select_windows_xr_runtime().backend; + + if (runtime == pp::platform::XrRuntimeBackend::openvr) + win32_vr_stop(); + else if (runtime == pp::platform::XrRuntimeBackend::openxr) + LOG("OpenXR runtime selected but the Windows OpenXR stop path is not wired yet"); + + active_xr_runtime_backend_ = pp::platform::XrRuntimeBackend::none; } void pick_image(pp::platform::PickedPathCallback callback) override @@ -572,6 +602,9 @@ public: (void)suggested_name; callback(std::string(path), false); } + +private: + pp::platform::XrRuntimeBackend active_xr_runtime_backend_ = pp::platform::XrRuntimeBackend::none; }; } diff --git a/tests/platform_api/platform_services_tests.cpp b/tests/platform_api/platform_services_tests.cpp index c89c823..91bd69e 100644 --- a/tests/platform_api/platform_services_tests.cpp +++ b/tests/platform_api/platform_services_tests.cpp @@ -913,6 +913,39 @@ void platform_policy_preserves_recording_and_export_storage_rules(pp::tests::Har PP_EXPECT(harness, !pp::platform::platform_flushes_persistent_storage(pp::platform::PlatformFamily::windows)); } +void platform_policy_prefers_openxr_and_marks_openvr_fallback(pp::tests::Harness& harness) +{ + const auto openxr = pp::platform::select_desktop_xr_runtime( + true, + true, + true); + PP_EXPECT(harness, openxr.backend == pp::platform::XrRuntimeBackend::openxr); + PP_EXPECT(harness, !openxr.uses_legacy_openvr_fallback); + PP_EXPECT( + harness, + std::string_view(pp::platform::xr_runtime_backend_name(openxr.backend)) == "openxr"); + + const auto retained_openvr = pp::platform::select_desktop_xr_runtime( + false, + true, + true); + PP_EXPECT(harness, retained_openvr.backend == pp::platform::XrRuntimeBackend::openvr); + PP_EXPECT(harness, retained_openvr.uses_legacy_openvr_fallback); + PP_EXPECT( + harness, + std::string_view(pp::platform::xr_runtime_backend_name(retained_openvr.backend)) == "openvr"); + + const auto unsupported = pp::platform::select_desktop_xr_runtime( + false, + true, + false); + PP_EXPECT(harness, unsupported.backend == pp::platform::XrRuntimeBackend::none); + PP_EXPECT(harness, !unsupported.uses_legacy_openvr_fallback); + PP_EXPECT( + harness, + std::string_view(pp::platform::xr_runtime_backend_name(unsupported.backend)) == "none"); +} + void platform_policy_preserves_document_browse_roots(pp::tests::Harness& harness) { const auto ios_roots = pp::platform::platform_document_browse_roots( @@ -1053,6 +1086,9 @@ int main() harness.run( "platform policy preserves recording and export storage rules", platform_policy_preserves_recording_and_export_storage_rules); + harness.run( + "platform policy prefers OpenXR and marks OpenVR fallback", + platform_policy_prefers_openxr_and_marks_openvr_fallback); harness.run("platform policy preserves document browse roots", platform_policy_preserves_document_browse_roots); harness.run( "platform policy preserves picker and prepared file rules",