From 7aadd1041a9643808d05f2ea618ad59c06f709c4 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Thu, 4 Jun 2026 18:39:22 +0200 Subject: [PATCH] Route VR lifecycle through platform services --- docs/modernization/build-inventory.md | 16 ++++++---- docs/modernization/debt.md | 4 +-- docs/modernization/roadmap.md | 10 ++++++- src/app.h | 2 ++ src/app_events.cpp | 10 +++++++ src/app_vr.cpp | 15 ++-------- src/platform_api/platform_services.h | 2 ++ .../legacy_platform_services.cpp | 9 ++++++ .../windows_platform_services.cpp | 12 ++++++++ .../platform_api/platform_services_tests.cpp | 29 +++++++++++++++++++ 10 files changed, 87 insertions(+), 22 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 04c00e9..fe4c1d4 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -197,8 +197,10 @@ Known local toolchain state: options-menu preference execution. It keeps UI scale, viewport scale, RTL, VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks on the `pp_app_core` `AppPreferenceServices` contract while retained settings - persistence, VR start/stop, recording lifecycle, and legacy canvas/UI - execution remain tracked by `DEBT-0045`. + 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. - `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, @@ -387,7 +389,8 @@ Known local toolchain state: context. Desktop VR drawing also consumes backend-owned scissor/depth/blend state, depth clear masks, active texture units, and fallback 2D texture unbind - targets while retaining the existing VR SDK/platform bridge shape. + targets; VR SDK start/stop now dispatches through `PlatformServices` while + retaining the existing Windows OpenVR bridge shape. Canvas mode overlay, mask, and transform paths also consume backend-owned blend/depth state, active texture units, 2D texture copy targets, and RGBA8 readback format tokens. @@ -559,7 +562,8 @@ Known local toolchain state: display-path formatting, canvas input tip visibility and pressure remapping, native UI/window state saving, live asset/layout reload policy, diagnostic stacktrace/crash hooks, - SonarPen availability/startup, PPBR export data-directory policy, + SonarPen availability/startup, VR mode lifecycle, + PPBR export data-directory policy, prepared-file writable target selection, network TLS verification policy, and prepared-file save/download handoff; PPBR and MP4 export dialogs consume @@ -656,8 +660,8 @@ Known local toolchain state: split persistence/runtime dispatch, and malformed startup-plan rejection. - `pp_platform_api_tests` covers service dispatch for clipboard read/write, empty clipboard writes, cursor visibility, virtual-keyboard visibility, - external file display, file sharing, and picker callbacks without platform - SDK headers or a window. + external file display, file sharing, VR lifecycle, and picker callbacks + without platform SDK headers or a window. - `pp_app_core_document_cloud_tests` covers cloud upload no-canvas, new-document warning, clean publish prompt, and dirty save-before-upload decisions, plus cloud browse no-canvas/show-browser and selected-download diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 62eb512..3dfa780 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -35,7 +35,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0014 | Open | Modernization | `windows-clangcl-asan` now configures as a headless Ninja/clang-cl preset and uses the release MSVC runtime required by ASan, but local builds still fail because installed clang-cl 18.1.8 is paired with VS 2026-preview STL headers that require Clang 20 or newer | Sanitizer validation should be local and repeatable, but this machine's compiler/header pairing is incompatible | `cmake --fresh --preset windows-clangcl-asan`; `cmake --build --preset windows-clangcl-asan --target pp_foundation` | Install/use Clang 20+ with the VS 2026 STL, or point the preset at a compatible VS 2022 toolchain, then make `platform-build.ps1 -Presets windows-clangcl-asan` pass for the headless matrix | | DEBT-0015 | Open | Modernization | Cursor visibility requests now consume pure `pp_app_core` planning through `pano_cli plan-cursor-visibility`, `App::show_cursor`/`App::hide_cursor` dispatch through `PlatformServices` without platform guards, and Windows live execution uses injected `WindowsPlatformServices`, but macOS cursor execution still reaches the retained fallback adapter | Keep canvas cursor behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-cursor-visibility --visible`; `ctest --preset desktop-fast --build-config Debug` | Cursor visibility execution is owned by injected `pp_platform_*` services for every supported platform | | DEBT-0016 | Open | Modernization | Clipboard get/set requests now consume pure `pp_app_core` planning through `pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write`, and Windows live execution uses injected `WindowsPlatformServices`, but Apple/Android clipboard execution still reaches retained fallback adapter branches from `App::clipboard_get_text` and `App::clipboard_set_text` | Keep picker/color text clipboard behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-clipboard-write --text #ff00aa`; `ctest --preset desktop-fast --build-config Debug` | Clipboard execution is owned by injected `pp_platform_*` services for every supported platform | -| DEBT-0017 | Open | Modernization | Startup storage path preparation, `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, `App::hideKeyboard`, `App::display_file`, `App::share_file`, native app/window close, UI-thread lifecycle hooks, render-context acquire/release/present hooks, render-target binding hooks, render platform hint hooks, render debug callback hooks, render-capture frame hooks, recording cleanup, live asset/layout reload policy, diagnostic stacktrace/crash hooks, per-frame platform hooks, `App::pick_image`, `App::pick_file`, the non-writer `App::pick_file_save`, `App::pick_dir`, working-directory picker/display-path policy, canvas input tip/pressure policy, prepared-file save/download handoff, work-directory document export collection policy, app network TLS verification policy, PPBR export data-directory policy, and SonarPen availability/startup now call the SDK-free `pp::platform::PlatformServices` interface, and Windows injects `WindowsPlatformServices` from `src/platform_windows/windows_platform_services.*`; non-Windows live implementations still use `src/platform_legacy/legacy_platform_services.*`, a named fallback adapter that forwards to retained Apple/Android/Linux/Web bridge functions and retained no-op branches, including retained iOS canvas tip behavior, retained macOS directory picker/display-path behavior, retained iOS SonarPen bridge, and retained macOS PPBR export directory override; `pp_platform_api` also owns the default network TLS policy helper consumed by retained curl sites that cannot yet depend on injected services | Preserve behavior while moving platform execution behind a testable service boundary before platform shell implementations are injected | `pp_platform_api_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_platform_io_tests`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Replace `src/platform_legacy/legacy_platform_services.*` with injected `pp_platform_*` service implementations owned by each non-Windows platform shell | +| DEBT-0017 | Open | Modernization | Startup storage path preparation, `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, `App::hideKeyboard`, `App::display_file`, `App::share_file`, native app/window close, UI-thread lifecycle hooks, render-context acquire/release/present hooks, render-target binding hooks, render platform hint hooks, render debug callback hooks, render-capture frame hooks, recording cleanup, live asset/layout reload policy, diagnostic stacktrace/crash hooks, per-frame platform hooks, `App::pick_image`, `App::pick_file`, the non-writer `App::pick_file_save`, `App::pick_dir`, working-directory picker/display-path policy, canvas input tip/pressure policy, prepared-file save/download handoff, work-directory document export collection policy, app network TLS verification policy, PPBR export data-directory policy, SonarPen availability/startup, and VR mode start/stop now call the SDK-free `pp::platform::PlatformServices` interface, and Windows injects `WindowsPlatformServices` from `src/platform_windows/windows_platform_services.*`; non-Windows live implementations still use `src/platform_legacy/legacy_platform_services.*`, a named fallback adapter that forwards to retained Apple/Android/Linux/Web bridge functions and retained no-op branches, including retained iOS canvas tip behavior, retained macOS directory picker/display-path behavior, retained iOS SonarPen bridge, retained non-Windows VR unsupported/no-op behavior, and retained macOS PPBR export directory override; `pp_platform_api` also owns the default network TLS policy helper consumed by retained curl sites that cannot yet depend on injected services | Preserve behavior while moving platform execution behind a testable service boundary before platform shell implementations are injected | `pp_platform_api_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_platform_io_tests`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Replace `src/platform_legacy/legacy_platform_services.*` with injected `pp_platform_*` service implementations owned by each non-Windows platform shell | | DEBT-0019 | Open | Modernization | Unreferenced-parameter warnings are muted globally through `pp_project_warnings` with MSVC `/wd4100` and Clang/GCC `-Wno-unused-parameter` | Legacy callbacks, virtual hooks, serializer methods, and platform/API compatibility functions carry many intentionally unused parameters during the component split; muting this keeps stricter warning builds focused on higher-signal migration issues | `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset linux-clang --target pp_foundation` | Remove `/wd4100` and `-Wno-unused-parameter`, mark intentionally unused parameters with names/comments or `[[maybe_unused]]`, and make the Windows app plus headless Clang/GCC tests pass without unreferenced-parameter warnings | | DEBT-0020 | Open | Modernization | Document resize dialog state, selected-resolution planning, and execution dispatch now consume pure `pp_app_core` through `NodeDialogResize`, `App::dialog_resize`, `pano_cli plan-document-resize`, and the `DocumentResizeServices` boundary, and live resize shares `src/legacy_document_canvas_services.*` with canvas clear commands, but the shared live bridge still calls legacy `Canvas::resize`, updates the legacy app title, and clears legacy `ActionManager` history through the history bridge | Preserve existing layer/frame GPU resize behavior while the document model and canvas execution boundary are extracted incrementally | `pp_app_core_document_resize_tests`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `ctest --preset desktop-fast --build-config Debug` | Document resize execution is owned by injected document/app services with no legacy resize adapter, title shim, or direct `ActionManager` history clearing | | DEBT-0021 | Open | Modernization | Layer rename planning/execution dispatch and layer panel operation planning/execution dispatch now consume pure `pp_app_core` through `App::dialog_layer_rename`, `App::init_sidebar` layer callbacks, `pano_cli plan-layer-rename`, `pano_cli plan-layer-operation`, `DocumentLayerRenameServices`, and `DocumentLayerOperationServices`, and the live execution adapters are centralized in `src/legacy_document_layer_services.*`, but that shared bridge still mutates legacy `Canvas` layer state, `NodeLayer`/`NodePanelLayer`, and `ActionManager` undo entries | Preserve existing UI/canvas behavior while document layer commands and undo history are extracted incrementally | `pp_app_core_document_layer_tests`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-layer-operation --kind add --layer-count 2 --index 1 --name Paint`; `ctest --preset desktop-fast --build-config Debug` | Layer command execution is owned by the document/app command boundary with legacy `Canvas`/UI nodes acting only as adapters or removed entirely | @@ -62,7 +62,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 now consumes pure `pp_app_core` through `App::dialog_save`, `App::dialog_save_ver`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `DocumentFileSaveServices`, `DocumentVersionSaveServices`, and `src/legacy_document_session_services.*`, but the bridge still opens legacy overwrite prompts, 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-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, but the bridge still calls legacy `Canvas` export methods, owns platform-specific export success messages, 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 simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and cube export execution, export-directory creation, platform success reporting, 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`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, owns mobile/Web save callbacks, and emits success messages directly | 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`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, mobile/Web save callbacks, and success reporting 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.*`, but the bridge still calls legacy `App::set_ui_scale`, `App::set_ui_rtl`, `App::vr_start`, `App::vr_stop`, `NodeCanvas::set_density`, `NodeCanvas::set_cursor_visibility`, `App::rec_start`, `App::rec_stop`, and `Settings::save` directly | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_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`; `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.*`, but the bridge still calls legacy `App::set_ui_scale`, `App::set_ui_rtl`, `NodeCanvas::set_density`, `NodeCanvas::set_cursor_visibility`, `App::rec_start`, `App::rec_stop`, and `Settings::save` directly; its 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`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `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 now consumes pure `pp_app_core` through `App::init`, `pano_cli plan-app-startup`, `AppStartupServices`, and `src/legacy_app_startup_services.*`, but the bridge still calls legacy `Settings::set`, `Settings::save`, `App::rec_start`, app VR-controller state mutation, and message-box license warning execution directly | Preserve current startup behavior while app startup moves toward app/preferences/storage/recording/UI 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`; `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, and startup UI/runtime side effects are owned by injected app/preferences/storage/recording/UI services with `App::init` acting only as orchestration | | DEBT-0047 | Open | Modernization | PPBR brush package export request validation 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, mobile/Web completion, and success-message behavior 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`; `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 success UI 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 | diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index d5a0570..4dd6e5c 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -630,6 +630,10 @@ VR mode, VR-controller, auto-timelapse, and cursor-mode side effects through `AppPreferenceServices` in `src/legacy_app_preference_services.*` before 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. `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 @@ -644,6 +648,7 @@ acquire/release/present hooks, render-target binding hooks, render-capture frame hooks, render platform hint hooks, render debug callback hooks, external file display, file sharing, recording file cleanup, live asset/layout reload policy, diagnostic stacktrace/crash hooks, SonarPen availability/startup, +VR mode start/stop, image/file/save-file pickers, and directory pickers. Windows installs an injected `WindowsPlatformServices` implementation from `src/platform_windows/windows_platform_services.*` in `pp_platform_windows`; @@ -680,6 +685,9 @@ The Tools menu SonarPen entry now asks `PlatformServices` whether SonarPen is 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. 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 @@ -1757,7 +1765,7 @@ Results: prepared-file export-dialog policy dispatch, work-directory document export collection policy dispatch, network TLS verification policy dispatch, default network TLS policy coverage, PPBR export data-directory policy - dispatch, SonarPen availability/startup dispatch, + dispatch, SonarPen availability/startup dispatch, VR lifecycle dispatch, live asset/layout reload policy dispatch, diagnostic hook dispatch, per-frame platform hook dispatch, picker callback dispatch, and prepared-file save/download callback dispatch. The live Windows diff --git a/src/app.h b/src/app.h index 4a0ccc9..4410d07 100644 --- a/src/app.h +++ b/src/app.h @@ -198,6 +198,8 @@ public: void display_file(std::string path); void share_file(std::string path); void request_app_close(); + [[nodiscard]] bool start_platform_vr_mode(); + void stop_platform_vr_mode(); void attach_ui_thread(); void detach_ui_thread(); void acquire_render_context(); diff --git a/src/app_events.cpp b/src/app_events.cpp index 43cfde0..2875ba4 100644 --- a/src/app_events.cpp +++ b/src/app_events.cpp @@ -264,6 +264,16 @@ void App::request_app_close() active_platform_services().request_app_close(); } +bool App::start_platform_vr_mode() +{ + return active_platform_services().start_vr_mode(); +} + +void App::stop_platform_vr_mode() +{ + active_platform_services().stop_vr_mode(); +} + void App::attach_ui_thread() { active_platform_services().attach_ui_thread(); diff --git a/src/app_vr.cpp b/src/app_vr.cpp index cb5f0ba..7c832b7 100644 --- a/src/app_vr.cpp +++ b/src/app_vr.cpp @@ -7,11 +7,6 @@ #include "shape.h" #include "renderer_gl/opengl_capabilities.h" -#ifdef _WIN32 -bool win32_vr_start(); -void win32_vr_stop(); -#endif - namespace { void set_active_texture_unit(std::uint32_t unit_index) @@ -104,18 +99,12 @@ Sphere controller_ray; bool App::vr_start() { -#ifdef _WIN32 - return win32_vr_start(); -#else - return false; -#endif + return start_platform_vr_mode(); } void App::vr_stop() { -#ifdef _WIN32 - win32_vr_stop(); -#endif + stop_platform_vr_mode(); } void App::vr_draw_ui() diff --git a/src/platform_api/platform_services.h b/src/platform_api/platform_services.h index 9dd0c5e..8c6f24c 100644 --- a/src/platform_api/platform_services.h +++ b/src/platform_api/platform_services.h @@ -59,6 +59,8 @@ public: virtual void display_file(std::string_view path) = 0; virtual void share_file(std::string_view path) = 0; virtual void request_app_close() = 0; + [[nodiscard]] virtual bool start_vr_mode() = 0; + virtual void stop_vr_mode() = 0; virtual void pick_image(PickedPathCallback callback) = 0; virtual void pick_file(std::vector file_types, PickedPathCallback callback) = 0; virtual void pick_save_file(std::vector file_types, PickedPathCallback callback) = 0; diff --git a/src/platform_legacy/legacy_platform_services.cpp b/src/platform_legacy/legacy_platform_services.cpp index d0c143e..f57654e 100644 --- a/src/platform_legacy/legacy_platform_services.cpp +++ b/src/platform_legacy/legacy_platform_services.cpp @@ -587,6 +587,15 @@ public: #endif } + [[nodiscard]] bool start_vr_mode() override + { + return false; + } + + void stop_vr_mode() override + { + } + void save_prepared_file( std::string_view path, std::string_view suggested_name, diff --git a/src/platform_windows/windows_platform_services.cpp b/src/platform_windows/windows_platform_services.cpp index 62d5e60..423a279 100644 --- a/src/platform_windows/windows_platform_services.cpp +++ b/src/platform_windows/windows_platform_services.cpp @@ -21,6 +21,8 @@ void win32_renderdoc_frame_end(); void win32_update_fps(int frames); void win32_update_stylus(float dt); void win32_save_window_state(); +bool win32_vr_start(); +void win32_vr_stop(); namespace { @@ -423,6 +425,16 @@ public: destroy_window(); } + [[nodiscard]] bool start_vr_mode() override + { + return win32_vr_start(); + } + + void stop_vr_mode() override + { + win32_vr_stop(); + } + void pick_image(pp::platform::PickedPathCallback callback) override { const std::string path = open_file("Image Files (*.jpg, *.png)\0*.jpg;*.png"); diff --git a/tests/platform_api/platform_services_tests.cpp b/tests/platform_api/platform_services_tests.cpp index 9e6c8c9..eab19bd 100644 --- a/tests/platform_api/platform_services_tests.cpp +++ b/tests/platform_api/platform_services_tests.cpp @@ -186,6 +186,17 @@ public: ++app_close_requests; } + [[nodiscard]] bool start_vr_mode() override + { + ++vr_starts; + return vr_start_success; + } + + void stop_vr_mode() override + { + ++vr_stops; + } + void pick_image(pp::platform::PickedPathCallback callback) override { ++pick_image_requests; @@ -334,6 +345,8 @@ public: int display_file_requests = 0; int share_file_requests = 0; int app_close_requests = 0; + int vr_starts = 0; + int vr_stops = 0; int pick_image_requests = 0; int pick_file_requests = 0; int pick_save_file_requests = 0; @@ -353,6 +366,7 @@ public: bool cursor_visible = false; bool keyboard_visible = false; bool prepared_file_saved = true; + bool vr_start_success = false; bool prepared_file_writes = true; bool working_directory_picker_supported = false; bool work_directory_document_export_collections = false; @@ -611,6 +625,20 @@ void platform_services_dispatch_file_actions(pp::tests::Harness& harness) PP_EXPECT(harness, fake.shared_path == "D:/Paint/demo.ppi"); } +void platform_services_dispatch_vr_lifecycle(pp::tests::Harness& harness) +{ + FakePlatformServices fake("unused"); + pp::platform::PlatformServices& services = fake; + + PP_EXPECT(harness, !services.start_vr_mode()); + fake.vr_start_success = true; + PP_EXPECT(harness, services.start_vr_mode()); + services.stop_vr_mode(); + + PP_EXPECT(harness, fake.vr_starts == 2); + PP_EXPECT(harness, fake.vr_stops == 1); +} + void platform_services_dispatch_picker_callbacks(pp::tests::Harness& harness) { FakePlatformServices fake("unused"); @@ -793,6 +821,7 @@ int main() harness.run("platform services dispatch live asset reload policy", platform_services_dispatch_live_asset_reload_policy); harness.run("platform services dispatch frame hooks", platform_services_dispatch_frame_hooks); harness.run("platform services dispatch file actions", platform_services_dispatch_file_actions); + harness.run("platform services dispatch VR lifecycle", platform_services_dispatch_vr_lifecycle); harness.run("platform services dispatch picker callbacks", platform_services_dispatch_picker_callbacks); harness.run( "platform services dispatch working directory picker policy",