Files
panopainter/docs/modernization/roadmap.md

1729 lines
96 KiB
Markdown

# PanoPainter Modernization Roadmap
Status: live
Last updated: 2026-06-03
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 | Started | OpenGL output matches golden readbacks |
| 6 | Platform Alignment | 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_api`: SDK-free service interfaces for clipboard, cursor,
virtual keyboard, file pickers, sharing, and future platform automation.
- `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. The root CMake Windows app graph now includes a
`panopainter_app` composition target and `pp_platform_windows` shell target so
`PanoPainter` is only the executable/resource wrapper; Windows and vendor link
dependencies now belong to the platform shell target, and Windows runtime
payload deployment lives behind `cmake/PanoPainterRuntime.cmake`.
`pp_legacy_vendor` now owns the retained third-party source bundle as an
interim containment boundary until vcpkg, SDK imports, or documented permanent
vendoring decisions replace each dependency. `pp_legacy_engine` now contains
the retained legacy tablet, video, and low-level runtime sources as an interim
containment boundary while pure replacement components take over.
`pp_legacy_assets_io` now owns retained ABR, asset/file, binary stream, image,
serializer, and settings implementations until `pp_assets` fully replaces those
paths in the app. `pp_legacy_paint_document` now owns retained action, bezier,
brush, canvas, canvas-layer, and event implementations until `pp_paint` and
`pp_document` fully replace those paths in the app.
`pp_legacy_renderer_gl` now owns the retained OpenGL runtime implementations
for `Font`, `RTT`, `Shader`, `Shape`, `Texture2D`, `TextureCube`, `Sampler`,
and `TextureManager` as an object-library boundary folded into the retained
engine until the renderer API inversion is complete. `pp_legacy_ui_core` now
owns retained base `Node`, layout, text, image, input, popup, slider, scroll,
and settings UI controls as an object-library boundary folded into the legacy
app adapter until those paths are replaced by `pp_ui_core` and app-specific UI
targets.
`pp_app_core` now owns tested app-level document-open routing for project
files, ABR imports, and PPBR imports without UI, filesystem, platform, or
renderer dependencies; `App::open_document` and `pano_cli classify-open`
consume this route contract. It also owns tested session decisions for
project-open, app-close, save, save-as, and save-version flows;
`App::open_document`, `App::request_close`, file-menu save actions,
`NodeCanvas` save hotkeys, and `pano_cli simulate-app-session` consume those
contracts while legacy canvas/project loading remains in place.
`pp_app_core` also owns tested app preference plans for UI scale/font scale,
scale option selection, viewport scale, RTL layout direction, timelapse
recording toggles, VR controller enablement, and canvas cursor mode;
the live tools/options menu and `pano_cli plan-app-preferences` consume those
contracts while legacy widgets and settings persistence execute them.
It also owns tested app status/display plans for document title text,
resolution mapping/labels, DPI text, history-memory text, and recording-frame
status text, plus renderer diagnostic indicator labels for framebuffer fetch
and floating-point render targets; `App::title_update`,
`App::update_memory_usage`, `App::update_rec_frames`, resolution helpers,
`App::initLayout`, and `pano_cli plan-app-status` consume those contracts while
legacy UI nodes still render the strings and status lights.
`panopainter_app` is now a real static target that owns app orchestration
sources, app version metadata, and version-header generation.
`pp_panopainter_ui` now owns app-specific modal, dialog, panel, canvas,
viewport, color-picker, stroke-preview, and tool UI workflow nodes outside
`pp_legacy_app`; base `Node` controls and layout plumbing remain in the legacy
target until the UI core/app UI boundary is tightened. Android arm64 now
configures and builds headless
foundation/tool targets through the root CMake/NDK path. Non-Windows platform
app/package 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, `vcpkg.json`,
a validated Windows headless vcpkg preset, `pp_ui_core` support for vcpkg
tinyxml2 on that preset, and a headless `panopainter_validate_shaders` target
exist. `windows-clangcl-asan` now configures as a headless Ninja/clang-cl ASan
preset and uses the release MSVC runtime required by clang-cl ASan, but local
ASan builds are blocked by DEBT-0014 until Clang and the selected MSVC STL are
compatible. Dependency migration is not complete until remaining component
dependencies and mobile/Apple 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`, with
`C4100` muted temporarily under `DEBT-0019`.
- Optional MSVC analysis preset: `/analyze`.
- Clang/GCC: `-Wall -Wextra -Wpedantic -Wconversion -Wshadow
-Wnull-dereference`, with `-Wunused-parameter` muted temporarily under
`DEBT-0019`.
- 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.
- Any global warning suppression must have an open debt entry, validation
command, and removal condition.
## 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`, `fuzz`, and `stress`
CTest presets run headlessly, and
PowerShell/bash wrappers exist for
configure/build/test/analyze/platform-build/package-smoke. `pano_cli` exists
with JSON automation commands for app document-open routing, app session
dirty-state and save decisions, creating a `pp_document` model, metadata-only
PPI project loading, and inspecting image signatures, PPI headers, and layout
XML; full document/app integration is debt-tracked as DEBT-0010 and full PPI
body parsing is debt-tracked as DEBT-0013.
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/overlapping-write tests. It also owns strict decimal `uint32` parsing used by
`pano_cli`, with rejection tests for empty, signed, mixed, and overflowing
input. A synchronous event dispatcher, structured logging facade, bounded FIFO
task queue, and deterministic `TraceRecorder` now record
component/name/thread/frame/stroke metadata with filtering, capacity, and
invalid-end tests. `pp_assets` has started with PNG/JPEG signature detection,
PNG IHDR metadata parsing, PPI header/project byte-layout/body-summary
recognition, layer/frame indexing, dirty-face PNG payload metadata validation,
asset-level RGBA PNG payload decoding, and a pure typed settings document
model, with
corrupt/truncated/unsupported, non-finite opacity, unsupported blend-mode,
extreme-dimension, and key/value limit tests.
`pp_paint` has started with pure brush parameter validation/stamp evaluation,
CPU reference math for the five current final RGBA shader blend modes plus the
shader-style stroke-alpha blend modes used by pattern/dual-brush mixing, and deterministic
stroke spacing/interpolation plus duplicate-segment, non-finite, sample-limit,
and 1001-sample stress coverage, plus a pure text stroke-script parser.
`pp_document` has
started with a pure canvas/layer/frame model, alpha-lock metadata, snapshot
construction, per-layer frame metadata, layer metadata operations, frame
move/duration queries, renderer-free RGBA8 cube-face payload storage,
renderer-free alpha8 selection-mask storage, PPI image import/export, and
layer/frame/undo-redo history invariant tests. Snapshot construction validates
embedded face-pixel payload bounds, byte counts, duplicate face payloads, and
duplicate selection masks.
`pp_renderer_api` has started with renderer-neutral
texture/readback descriptors and validation tests. `pp_paint_renderer` has
started with deterministic CPU layer compositing over renderer extents using
the paint blend reference. `pp_ui_core` has started with XML-layout-facing
length parsing, color parsing, tinyxml-backed layout XML parsing, and invalid
input tests.
`pano_cli inspect-image` exposes PNG IHDR metadata as JSON,
`pano_cli import-image` accepts a PNG path and imports decoded RGBA8 pixels
into a new pure `pp_document` face payload,
with checked-in decodable PNG and truncated PNG automation coverage,
`pano_cli export-image` writes a deterministic RGBA8 PNG through `pp_assets`
and round-trips it back through file import automation,
`pano_cli inspect-project` reports validated PPI thumbnail/body byte layout,
body summary, layer/frame descriptors, dirty-face PNG payload metadata, and
asset-level decode coverage, and
`pano_cli load-project` creates a `pp_document` projection with per-layer frame
counts, durations, and decoded face-pixel payload attachment when PPI image
payloads are present.
`pp_assets` can write generated PPI projects with explicit per-layer names,
visibility, opacity, blend mode, alpha lock, per-layer frame durations, and
dirty-face payloads targeted to layer/frame/face slots. `pano_cli save-project`
exposes the generated writer for metadata-only and test dirty-face-payload
round-trips through `load-project` and rejects non-finite automation float
inputs before writing files.
`pp_document::export_ppi_project_document` converts pure documents into PPI
bytes using that writer, including PNG-encoded layer/frame face payloads.
`pano_cli simulate-document-export` exercises that pure document export path,
decodes the generated PPI bytes, reimports them, and emits JSON round-trip
metadata.
`pano_cli save-document-project` writes the same pure document export to a PPI
file for inspect/load round-trip automation.
`pano_cli create-document` can create simple animation documents with explicit
frame count/duration. `pano_cli simulate-document-edits` exercises pure
layer metadata, frame reordering, active-index preservation, tiny face-payload
attachment, and selection-mask attachment. `pano_cli simulate-document-history` exercises the
pure `pp_document::DocumentHistory` apply/undo/redo path and emits JSON state
summaries. `pano_cli simulate-image-import` decodes an embedded tiny PNG
through `pp_assets` and attaches the resulting RGBA8 payload to `pp_document`.
`pano_cli simulate-document-export` exercises pure document-to-PPI export,
asset-level PPI image decode, and document reimport in one automation command.
`pano_cli save-document-project` writes a deterministic pure document export
PPI and verifies it through inspect/load smoke coverage.
`pano_cli simulate-blend` exposes deterministic final RGBA and stroke-alpha
blend reference vectors through JSON automation.
`pano_cli simulate-stroke` exercises the pure stroke sampler for
scripted-stroke automation. `pano_cli simulate-stroke-script`
loads stroke script fixtures, parses them through `pp_paint`, and samples every
stroke. `pano_cli apply-stroke-script` maps sampled script points into a
bounded `pp_document` RGBA8 face payload, writes a PPI file, and verifies that
the applied stroke payload survives inspect/load round-trip automation, with a
rejection smoke test for unsafe tiny canvas dimensions.
`pano_cli classify-open` exposes the pure `pp_app_core` document-open route
contract for project files, ABR imports, PPBR imports, and malformed path
rejection. `pano_cli plan-open-route` exposes the pure `pp_app_core`
document-open action plan for clean project open, dirty project prompt, and
brush-import prompt flows. `pano_cli simulate-app-session` exposes the pure
`pp_app_core` session decisions used by project-open, app-close, save, save-as,
and save-version flows, plus the save-before-continue workflow gate used by
new-document/open/browse dialogs. `pano_cli plan-new-document` exposes the
same app-core new-document target, legacy resolution-index mapping, and
overwrite decision used by the live new-document dialog, including invalid
resolution rejection. `pano_cli plan-document-file` exposes the same app-core
document-name validation, legacy `.ppi` path construction, and overwrite
prompt decision used by save-as dialogs through one combined save-file plan.
`pano_cli plan-document-version` exposes the save-version suffix parsing,
candidate generation, collision skipping, and no-slot failure behavior used by
the live save-version dialog.
`pano_cli plan-export-target` exposes app-core export target planning for
equirectangular image files, layer/frame collection stems, picked-directory
stems, and MP4 suggested names used by the live export dialogs.
`pano_cli plan-export-start` exposes the app-core export availability decision
used by live image, layer, animation-frame, depth, and cube-face export dialogs
plus MP4 animation and timelapse export dialogs before they call legacy
canvas/recording export execution.
`pano_cli plan-recording-session` exposes the app-core recording start, stop,
clear, platform recorded-file cleanup, frame reset, and export progress-total
decisions used by the live recording controls before legacy recording threads,
PBO readback, and MP4 encoder execution continue.
`pano_cli plan-share-file` exposes the app-core saved-path decision used by the
live platform share command before iOS/macOS sharing bridges or retained no-op
platform branches execute.
`pano_cli plan-picked-path` exposes the app-core selected-path decision used by
live image, file, save-file, and directory picker branches before retained
platform callbacks or legacy picker bridges continue.
`pano_cli plan-display-file` exposes the app-core external file presentation
decision used by live display-file requests before retained platform open-file
bridges continue.
`pano_cli plan-keyboard-visibility` exposes the app-core virtual keyboard
visibility decision used by live show/hide keyboard requests before retained
mobile platform keyboard bridges continue.
`pano_cli plan-cursor-visibility` exposes the app-core cursor visibility
decision used by live canvas cursor requests before retained desktop platform
cursor bridges continue.
`pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write` expose the
app-core clipboard text decisions used by live clipboard get/set requests
before retained platform clipboard bridges continue.
`pano_cli plan-document-resize` exposes the app-core resize dialog state and
selected-resolution commit plan used by the live document resize dialog, and
resize execution now dispatches through `DocumentResizeServices` before the
shared app-shell document-canvas bridge runs the legacy `Canvas` resize adapter
and history clearing.
`pano_cli plan-layer-rename` exposes the app-core layer rename decision used by
the live layer rename dialog, and rename execution now dispatches through
`DocumentLayerRenameServices` before legacy `Canvas`, `NodeLayer`, and
`ActionManager` undo adapters continue.
`pano_cli plan-layer-operation` exposes app-core planning for layer add,
duplicate, select, reorder, remove, opacity, visibility, alpha-lock, blend-mode,
and highlight actions used by the live layer panel. Direct layer-panel
operations now dispatch through `DocumentLayerOperationServices` before the
legacy `Canvas` and UI layer adapter continues execution.
`pano_cli plan-layer-menu` exposes app-core planning for Layer menu clear,
rename, and merge-down labels/actions, and direct Layer menu commands now
dispatch through `DocumentLayerMenuServices` before the legacy canvas/layer UI
adapter continues execution. Layer menu clear now routes through the shared
`DocumentCanvasClearServices` executor before the legacy canvas-clear adapter
continues, and Layer menu merge now validates and dispatches through
`DocumentLayerMergeServices` before the legacy layer-panel merge adapter
continues.
`pano_cli plan-animation-operation` exposes app-core planning for animation
frame add, duplicate, remove, duration adjustment, timeline moves, timeline
goto/next/previous, onion-size updates, frame selection, no-reload playback
stepping, and play-mode toggles used by the live animation panel.
`pano_cli plan-animation-panel-action` exposes the higher-level animation panel
state/action planner for goto, next, previous, playback-step, and play-toggle
automation without requiring the legacy UI or canvas.
Panel-control, timeline, selected-frame click, playback tick, and play-button
toggle execution now dispatch through `DocumentAnimationServices` before the
legacy `Canvas`/`Layer`/canvas-mode adapter continues.
`pano_cli plan-brush-operation` exposes app-core planning for brush color
changes, tip/pattern/dual texture changes, preset brush replacement, and stroke
settings refreshes used by the live brush, quick, color, and floating panel
callbacks. Brush UI execution now dispatches through `BrushUiServices` before
the legacy `Brush`/panel adapter mutates brush state or loads brush resources.
`pano_cli plan-brush-texture-list` exposes app-core planning for brush/pattern
texture add, remove, and reorder actions, and `NodePanelBrush` now dispatches
those actions through `BrushTextureListServices` before the legacy image
load/save and UI-list adapter continues.
`pano_cli plan-brush-stroke-control` exposes app-core planning for the live
stroke panel's slider, checkbox, blend-mode, tip-aspect reset, and default
brush reset commands. `NodePanelStroke` now dispatches those controls through
`BrushStrokeControlServices` before the legacy `Canvas::I`/`Brush`/stroke-panel
adapter continues.
`pano_cli plan-canvas-tool` exposes app-core planning for draw/erase/line,
camera, grid, copy, cut, fill, mask, flood-fill, pick, and touch-lock toolbar
commands. Canvas tool execution now dispatches through `CanvasToolServices`
before legacy toolbar selection, `Canvas` mode, pen picking, touch-lock, and
transform state adapters continue. `pano_cli plan-canvas-tool-state` exposes
the matching toolbar active-state refresh used by `App::update` before legacy
`Canvas` mode state remains the source of truth. `NodeCanvas` stylus eraser
mode switching consumes the same app-core executor before legacy canvas mode
execution continues. `NodeCanvas` keyboard and touch command handling now
consumes `pp_app_core` canvas-hotkey planning for E draw/erase, Ctrl+Z,
Ctrl+Shift+Z, Ctrl+S, Ctrl+Shift+S, Tab UI toggle, brush-size brackets,
Android back, Alt cursor reveal, and two-finger undo before legacy UI/canvas
adapters execute the command.
`pano_cli plan-canvas-clear` exposes app-core planning for the main toolbar
clear-current-layer command, including clear color validation, no-canvas
handling, undo recording intent, and dirty-state intent; live toolbar execution
and Layer menu clear now dispatch through the shared app-shell document-canvas
bridge before the legacy `Canvas::clear` adapter continues.
`pano_cli plan-image-import` exposes app-core planning for File > Import image
route decisions, including wide equirectangular images, legacy vertical cube
strips, regular transform-placement images, and invalid image dimensions; live
File > Import execution now dispatches through `DocumentImageImportServices`
before legacy image loading, `Canvas::import_equirectangular`, or import
transform-mode setup continues.
`pano_cli plan-file-menu` exposes app-core planning for the top-level File menu
commands, including new/open/import, save/save-as/save-version, share, resize,
cloud upload/browse, JPEG export, and export-submenu routing. Direct File menu
commands now dispatch through `FileMenuServices` before legacy dialogs, pickers,
platform services, cloud code, and canvas workflows continue.
`pano_cli plan-export-menu` exposes app-core planning for File menu export
choices, including image, layer, cube-face, depth, animation-frame, MP4, and
timelapse dialog routing plus license/canvas gating. Export menu commands now
dispatch through `DocumentExportMenuServices` before legacy export dialogs and
renderer/video execution continue.
`pano_cli plan-grid-operation` exposes app-core planning for grid heightmap
pick/load/reload/clear, lightmap render capability/limit checks, and heightmap
commit used by the live grid panel before legacy image loading, OpenGL texture
updates, nanort lightmap baking, and `Canvas::draw_objects` execution continue.
`pano_cli plan-history-operation` exposes app-core planning for undo, redo, and
clear-history availability used by toolbar buttons and canvas shortcuts; live
toolbar and canvas-hotkey execution now dispatch through a shared app-shell
legacy history bridge before the legacy `ActionManager` stack adapter
continues. The bridge also centralizes saturated history metrics so app-core
plans never receive wrapped negative counts from oversized legacy stacks.
`pano_cli plan-main-toolbar` exposes app-core planning for the live main
toolbar/status-bar shell, including open/save dialogs, undo/redo availability,
clear-history availability, clear-canvas no-canvas blocking, message-box
creation, and settings dialog routing. `pp_app_core` now also owns a
`MainToolbarServices` executor boundary, so `App::init_toolbar_main` dispatches
through a legacy adapter before legacy dialogs, history/canvas adapters, and
settings UI execution continue.
`pano_cli plan-quick-operation` exposes app-core planning for quick brush/color
slot selection versus popup opening, plus quick mini-state restore/reset
validation used by the live quick panel. Quick-panel execution now dispatches
through `QuickUiServices` before the legacy `Brush`, color picker, stroke
preview, and preset popup adapter continues.
`pano_cli plan-tools-menu` and `pano_cli plan-tools-panel` expose app-core
planning for top-level Tools commands and floating-panel requests, including
already-visible no-ops, panel chrome metadata, shortcuts, camera reset,
grid-clear, and platform-only SonarPen gating. Direct Tools commands now
dispatch through `ToolsMenuServices` before the legacy UI/panel/canvas/platform
adapters continue execution. The live animation panel route now also checks
animation panel visibility and applies animation panel layout state instead of
using the grid panel by mistake.
`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
blocking. `pp_app_core` now also owns an `AboutMenuServices` executor boundary,
so `App::init_menu_about` dispatches through a legacy adapter before legacy
dialogs, platform crash hooks, and Canvas performance strokes continue.
`pp_platform_api` now owns a headless `PlatformServices` interface for
startup storage path preparation, clipboard text, cursor visibility,
virtual-keyboard visibility, UI-thread lifecycle hooks, render-context
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, 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`;
other platforms still route through the debt-tracked legacy fallback adapter
now isolated in `src/platform_legacy/legacy_platform_services.*`, so behavior
is preserved while their platform shell implementations are extracted.
Prepared-file save/download handoff is now also part of the service contract,
so iOS/Web export completion routes through `PlatformServices` after the app
writes the temporary/exported payload.
`App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, and
`App::hideKeyboard` now dispatch through the active service without local
platform guards; unsupported platforms rely on their service no-op behavior.
The unsaved-document close prompt now requests native app/window close through
`PlatformServices`, with Windows implemented by `WindowsPlatformServices` and
macOS/Linux still handled by the legacy adapter until those platform shells
are injected.
The UI loop's per-frame platform hooks now dispatch through
`PlatformServices`: Windows stylus timeout polling and FPS-title updates live
in `WindowsPlatformServices`, while Linux FPS-title updates remain in the
legacy adapter pending Phase 6 platform shell extraction.
The UI thread's platform attach/detach hooks now also dispatch through
`PlatformServices`, preserving Android JNI attach/detach behavior in the
legacy adapter while removing direct Android lifecycle calls from the main app
loop.
The app's render context acquire/release/present path now dispatches through
`PlatformServices` as well. Windows owns WGL acquisition, default framebuffer
rebinding, and swap in `WindowsPlatformServices`; Apple, Android, Linux, and
WebGL behavior is preserved behind the legacy adapter until their platform
shells are injected.
Render-task default-target binding and visible main-target binding now dispatch
through `PlatformServices`, preserving the existing iOS drawable bind in the
legacy adapter while removing the iOS drawable branch from `App::draw`.
Initial render platform hints now also dispatch through `PlatformServices`,
preserving the previous Windows/macOS program-point-size and line-smoothing
enablement while removing the Windows/macOS branch from `App::init`.
Windows OpenGL debug callback setup now dispatches through `PlatformServices`,
moving Win32 console coloring, debug-output enablement, and debug-break callback
behavior into `WindowsPlatformServices` while keeping other platform adapters
as no-ops.
Initial PanoPainter OpenGL depth/blend startup state is now represented and
applied by tested `pp_renderer_gl` startup-state contracts; `App::init`
delegates to the backend dispatch path instead of hard-coding the policy or
operation order.
OpenGL runtime version/vendor/renderer/GLSL string queries now also use a
tested `pp_renderer_gl` dispatch contract, leaving `App::init` to log the
result while the backend owns the query set and order.
The default app clear color and color-buffer clear operation now dispatch
through `pp_renderer_gl` as well, moving another direct OpenGL operation out
of `App::clear` while preserving the current gray clear behavior.
Main app UI viewport and scissor execution now dispatch through tested
`pp_renderer_gl` viewport/scissor contracts, leaving `App::draw` and UI node
clipping to provide rectangles while the backend owns scissor-state tokens and
the live OpenGL call sequence.
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.
VR draw blend/depth state transitions and depth-buffer clears now use generic
tested `pp_renderer_gl` capability and clear dispatch contracts, reducing
direct OpenGL execution in the retained VR app path without changing state
restore behavior.
The retained `gl_state` save/restore utility now snapshots and restores through
tested `pp_renderer_gl` saved-state dispatch contracts, covering capability
state, viewport, clear color, framebuffer/program bindings, active texture,
2D texture slots, samplers, and cube-map binding without changing the legacy
utility's public fields.
Legacy `Texture2D` allocation, binding, deletion, mipmap generation, region
update, and framebuffer readback now execute through tested `pp_renderer_gl`
texture dispatch contracts. This keeps the app API stable while moving another
resource lifecycle path behind the renderer backend boundary.
Legacy `RTT` resize/copy blits and byte/float framebuffer readbacks now execute
through tested `pp_renderer_gl` framebuffer dispatch contracts with draw/read
framebuffer binding restore handled by the backend helper.
Legacy `RTT::bindFramebuffer` and `RTT::unbindFramebuffer` now use tested
`pp_renderer_gl` draw/read framebuffer binding snapshot and restore contracts,
moving render-target pass entry/exit state management behind the backend.
Windows RenderDoc frame capture hooks now also dispatch through
`PlatformServices`, keeping capture integration in the platform service while
leaving non-Windows adapters as no-ops.
Startup data/work/recording/temp path preparation now dispatches through
`PlatformServices`, with Windows creating the Documents/PanoPainter folder
tree in `WindowsPlatformServices` and Apple/Linux/Web behavior preserved in the
legacy adapter until platform shells are injected.
Recording clear now asks `PlatformServices` whether the platform owns recorded
file deletion and dispatches the cleanup through the service, preserving the
current Apple recorded-frame cleanup while removing Apple-specific file cleanup
guards from `App::rec_clear`.
The UI loop now asks `PlatformServices` whether live shader/layout reloading
should run, preserving the previous Windows/macOS reload behavior while removing
the direct `(_WIN32 || __OSX__)` guard from `App::ui_thread_main`.
`App::stacktrace` and `App::crash_test` now dispatch through `PlatformServices`,
with Windows retaining the debug-break crash hook and the legacy adapter
preserving Apple stacktrace/crash and Android crash-test behavior.
`pano_cli plan-cloud-upload` exposes the app-core cloud upload decision used by
the live cloud upload command for missing-canvas, new-document warning, publish
prompt, and dirty-document save-before-upload states before legacy UI, canvas,
and network execution continue.
`pano_cli plan-cloud-upload-all` exposes the app-core bulk upload file-count,
progress UI, and progress-total clamping decision used by the live upload-all
command before legacy asset listing, OpenGL context guard, progress UI, and
network upload execution continue.
`pano_cli plan-cloud-browse` exposes the app-core cloud browse and selected
download decisions used by the live cloud browse command before legacy dialog,
network download, canvas project-open, layer UI, and action-history execution
continue.
`pano_cli parse-layout` exercises the XML layout path. Continue expanding
document behavior toward legacy Canvas parity and then port OpenGL classes
behind the renderer boundary.
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_api`
11. `pp_platform_*`
12. `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.
Status: started. `pp_renderer_api` exists as a headless renderer-neutral target
with renderer feature flags, explicit texture usage flags, texture descriptor, byte-size, viewport,
mesh, readback bounds, command context, render device, shader program
descriptor, mesh, render target, readback byte-size helpers,
texture mip-level validation, resource debug-label validation,
texture-upload/readback command validation,
mipmap-generation command validation, texture-state transition validation, frame-capture byte-size helpers,
readback/copy/frame-capture/blit descriptor validation, frame-capture command validation, render-target blit validation, texture-slot
binding validation, blend-state validation, scissor-state validation,
depth-state validation, trace marker/scope validation, sampler-state validation,
texture/mesh/shader resource-label validation,
recording-device reuse/reset validation, and the canonical PanoPainter shader
catalog now consumed by the legacy OpenGL app initialization path.
`pp_renderer_gl` now exists as the first OpenGL backend library and owns pure
OpenGL capability detection for framebuffer fetch, map-buffer alignment, and
float texture support. It also owns the OpenGL texture upload-type mapping used
by legacy `Texture2D` and `RTT` creation, RGBA pixel-format mapping used by
`RTT` texture allocation, plus image channel-count to texture
format mapping for `Texture2D` image uploads and framebuffer status naming for
`RTT` and `Texture2D` diagnostics. It also owns renderer API texture-format to
OpenGL internal/pixel/component token mapping, including depth-stencil formats,
for future backend texture objects. `Texture2D` 2D texture binding, upload,
mipmap generation, framebuffer readback setup, and update component-type tokens
now delegate to `pp_renderer_gl`. `TextureCube` cube-map binding, allocation
face targets, RGBA allocation format, and unsigned-byte component type also
delegate to `pp_renderer_gl`. RGBA8/RGBA32F readback formats, checked byte-count math, and PBO
pixel-buffer target/usage/access tokens used by `RTT` and `PBO` readbacks now
live in `pp_renderer_gl`. The framebuffer blit color mask and linear/nearest
filter tokens used by `RTT::resize` and `RTT::copy`, renderer API blit-filter
to OpenGL token mapping, plus the default
render-target texture parameters, texture/renderbuffer targets, depth format,
framebuffer targets, binding queries, attachment points, and completion status
used by `RTT::create` and framebuffer bind/restore paths, also live in
`pp_renderer_gl`. RTT clear color/depth masks, renderer API render-pass
color/depth/stencil clear-mask and clear-value mapping, and color-write-mask query tokens also
live in `pp_renderer_gl`. `RTT` no longer spells GL enum names directly.
Renderer API primitive-topology to OpenGL draw-mode mapping, mesh index-type
and primitive-mode decisions used by legacy `Shape` drawing, plus Shape buffer
targets, static upload usage, and vertex attribute component/normalization
tokens, also live in `pp_renderer_gl`. Legacy `Shape` mesh buffer/VAO
creation, dynamic vertex/index uploads, fill/stroke draws, and buffer/VAO
deletion now execute through tested `pp_renderer_gl` dispatch contracts,
leaving the retained shape utility with thin GL adapter functions. The
PanoPainter cube-face to
OpenGL texture-target mapping used by `TextureCube` also lives in
`pp_renderer_gl`. The legacy app delegates extension, upload-format,
framebuffer diagnostic, framebuffer blit, render-target setup, clear-state,
2D/cube texture setup, mesh draw-mode, and cube-face texture-target
interpretation to that backend library. Sampler wrap, min/mag filter, and
desktop border-color parameter mapping for legacy `Sampler` also lives in
`pp_renderer_gl`. Renderer API sampler filter/address-mode to OpenGL token
mapping, including mirrored-repeat, and aggregate renderer API sampler-state to
OpenGL min/mag/wrap mapping are also tested there. The PanoPainter shader attribute
binding catalog, shader stage tokens, compile/link status queries, active-uniform
count query, and matrix-uniform transpose token also live in `pp_renderer_gl`
and are consumed by legacy `Shader` creation. Renderer API blend factor/op to
OpenGL token mapping also lives in `pp_renderer_gl`, with explicit support flags
so `GL_ZERO` remains distinguishable from unsupported enum values. Aggregate
renderer API blend-state to OpenGL enable/factor/equation/color-mask mapping,
depth compare-op to OpenGL depth-function mapping, and aggregate renderer API
depth-state to OpenGL enable/write/compare mapping also live in
`pp_renderer_gl`. Shader uniform hashing, catalog
validation, active-uniform mapping, and the legacy uniform uniqueness check now
delegate to `pp_renderer_gl` as well. `Shader` no longer spells GL enum names
directly. App OpenGL initialization debug severity, debug output, GL info
string, renderer API viewport/scissor rect conversion, default
depth/program-point/line-smooth state, blend factor/equation, and UI
render-target RGBA8 format tokens are now also cataloged and tested in
`pp_renderer_gl`; the legacy convert command and resize path consume the same
backend-owned mapping. App clear color-buffer masks, default framebuffer
binding, scissor state, and sampler filter/wrap tokens now share that backend
mapping too. OpenGL extension enumeration query tokens used before runtime
capability detection also live in `pp_renderer_gl`. Legacy font atlas texture
formats, text mesh buffer targets, attribute component/normalization, draw
primitive/index type, upload usage, and active texture unit selection also
delegate to `pp_renderer_gl`; text mesh buffer/VAO creation, deferred index
and vertex uploads, and indexed draw calls now execute through the same tested
mesh dispatch contracts used by `Shape`, leaving the retained `Font` utility
with thin GL adapter functions for mesh operations.
Canvas undo/redo dirty-region texture updates and readbacks now also delegate
their 2D texture target, RGBA pixel format, and unsigned-byte component type
mapping to `pp_renderer_gl`.
`NodeViewport` preview rendering now also delegates viewport query,
clear-color query, color-buffer clear mask, and blend-state tokens to
`pp_renderer_gl`.
`NodeImageTexture` preview drawing now delegates its fallback 2D texture bind
target and blend-state tokens to `pp_renderer_gl`.
`NodeImage` drawing and remote-image texture creation now delegate mipmapped
sampler filters, blend-state tokens, and RGBA8/RGBA texture format mapping to
`pp_renderer_gl`.
`NodeColorWheel` triangle-buffer setup and draw-state handling now delegate
array-buffer, static-upload, vertex-attribute, primitive-mode, and blend-state
tokens to `pp_renderer_gl`.
Simple UI text, text-input, border, scroll, and animation timeline draw paths
now also delegate blend-state tokens to `pp_renderer_gl`.
Canvas layer cube/equirect generation, clear, restore, and snapshot paths now
also delegate cube/2D texture targets, active texture units, blend/clear state,
and RGBA8 read/write pixel mapping to `pp_renderer_gl`.
`NodePanelGrid` heightmap preview and lightmap baking now delegate texture
readback formats, sampler filters, depth/blend state, depth clears, viewport
queries, color-mask booleans, active texture units, and float render-target
formats to `pp_renderer_gl`.
Legacy `util.cpp` OpenGL error naming and `gl_state` save/restore now delegate
error codes, state queries, framebuffer targets, texture binding targets, and
active texture units to `pp_renderer_gl`.
`NodeStrokePreview` brush preview rendering now delegates depth/scissor/blend
state, viewport/clear-color queries, active texture units, 2D texture targets,
copy targets, sampler filters/wraps, and destination-feedback copy/fetch
decisions to `pp_renderer_gl` and `pp_paint_renderer`.
Legacy `Texture2D`, `TextureManager`, `Sampler`, and `RTT` public headers no
longer expose raw OpenGL enum defaults; default texture formats, sampler
filters/wraps, and render-target formats are resolved through backend-owned
overloads.
The Windows entrypoint now delegates generic OpenGL error-code/info-string
tokens and WGL core-context/pixel-format attribute catalogs to `pp_renderer_gl`.
The headless OpenGL command planner now consumes `pp_renderer_api` recorded
commands and maps render-pass clear masks/values, viewport/scissor state,
blend/depth/sampler state, texture formats, primitive modes, draw counts, and
blit filters into GL-facing planned command data with explicit unsupported-token
rejection before a runtime GL context is needed. It also plans whole recorded
command streams, preserving per-command planned data while counting render
passes, draws, shader binds, shader uniforms, texture/sampler binds, texture
uploads, mipmap generation, texture transitions, texture copies, texture
readbacks, frame captures, passthrough commands, trace commands, unsupported
commands, and render-pass ordering errors such as state changes outside a pass,
nested passes, texture I/O or blits inside a pass, and unclosed passes. It
also validates executable command dependencies, including shader-before-uniform
and shader-plus-mesh before draw within each render pass, and rejects invalid
texture/sampler bind slots in malformed recorded streams.
The renderer-neutral API now also plans complex paint feedback strategies for
future stroke/layer compositing work: framebuffer-fetch-capable backends can
read destination color directly, while other backends must use ping-pong render
targets backed by texture copy or render-target blit support. This is exposed
through `pano_cli plan-paint-feedback` and tracked by DEBT-0036 until the live
paint renderer consumes the plan.
`pp_paint_renderer` now consumes that lower-level feedback planner through a
stroke composite plan that decides whether a stroke/layer blend can use
fixed-function blending or needs framebuffer-fetch/ping-pong destination
feedback. `pano_cli plan-stroke-composite` exposes the same decision for
automation, including layer blend, stroke blend, dual-brush, and pattern-blend
inputs. Live `Canvas::draw_merge` now uses this planner for its existing
shader-blend gate for layer and primary-brush blend modes while preserving the
legacy trigger policy; actual canvas stroke execution, dual-brush feedback, and
pattern feedback are still legacy OpenGL and remain tracked by DEBT-0036 until
the app calls through renderer services for the whole compositing path.
`pp_paint_renderer::plan_canvas_blend_gate` now also owns the compatibility
mapping from persisted layer and brush blend indices to that planner, including
fallback behavior for unknown nonzero indices. Both `Canvas::draw_merge` and
`NodeCanvas` panorama rendering consume that shared gate, so the live app no
longer has duplicate local blend-trigger logic or duplicate destination-copy
versus framebuffer-fetch decisions in those paths.
The OpenGL shader initialization path now stores a renderer-neutral
`RenderDeviceFeatures` snapshot converted by `pp_renderer_gl`, and those live
canvas gates consume that snapshot instead of rebuilding feature flags from
individual `ShaderManager` extension booleans.
`pp_paint_renderer::plan_canvas_stroke_feedback` now models the current stroke
shader's required destination feedback without changing the legacy shader math.
Live `Canvas::stroke_draw` consumes that plan for main-brush, dual-brush, and
stroke-pad destination-copy versus framebuffer-fetch decisions. Thumbnail layer
blending now consumes the same canvas destination-feedback decision for its
legacy `TextureBlend` path. `NodeStrokePreview` uses the same destination
feedback plan for its live brush-preview copy/fetch decision. The full
thumbnail and brush-preview compositing execution remains legacy OpenGL until a
fuller live paint-renderer boundary can take over.
The existing renderer classes are not yet fully
behind the renderer interfaces.
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`
- Keep OpenGL runtime capability decisions in `pp_renderer_gl` with headless
tests before moving GL object lifetimes behind backend types.
- 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 final RGBA and stroke-alpha 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.
Status: started. Root CMake configure presets now have matching build presets
for Windows VS 2026/default, Windows clang-cl ASan, Linux clang, Android
standard x64/arm64, Android Quest arm64, Android Focus/Wave arm64,
Emscripten/WebGL, macOS, iOS device, and iOS simulator. `platform-build`
automation now builds the current headless component matrix, including
`pp_platform_api`, `pp_app_core`, app-core tests, and platform API tests.
`package-smoke` now emits a structured package readiness matrix for Windows
AppX, Android standard/Quest/Focus APKs, Apple bundles, and WebGL output, with
blocked prerequisites tied to DEBT-0011. App/package entrypoints still need to
consume shared targets and remain covered by debt until package validation is
migrated from legacy package projects to root CMake.
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` | Deterministic parser/serializer edge corpus; future libFuzzer entrypoint | No GPU/window today |
| `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-06-02:
```powershell
cmake --preset windows-msvc-default
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_image_metadata_tests pp_assets_image_pixels_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_brush_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_document_ppi_import_tests pp_document_ppi_export_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_color_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests pano_cli PanoPainter
ctest --preset desktop-fast --build-config Debug
ctest --preset fuzz --build-config Debug
ctest --preset stress --build-config Debug
powershell -ExecutionPolicy Bypass -File scripts\automation\test.ps1 -Preset desktop-fast -Configuration Debug
powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli
cmake --build --preset windows-msvc-default --target panopainter_validate_shaders
powershell -ExecutionPolicy Bypass -File scripts\automation\analyze.ps1 -Preset windows-msvc-default -NoApp
set VCPKG_ROOT=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg
cmake --preset windows-msvc-vcpkg-headless
powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets windows-msvc-vcpkg-headless
ctest --preset desktop-fast-vcpkg --build-config Debug
cmake --preset android-arm64
powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64
powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug
cmake --fresh --preset windows-clangcl-asan
```
Results:
- `pp_foundation_binary_stream_tests` passed.
- `pp_foundation_event_tests` passed.
- `pp_foundation_log_tests` passed.
- `pp_foundation_parse_tests` passed.
- `pp_foundation_task_queue_tests` passed.
- `pp_foundation_trace_tests` passed.
- `pp_assets_image_format_tests` passed.
- `pp_assets_image_metadata_tests` passed.
- `pp_assets_image_pixels_tests` passed, including RGBA8 PNG decode and corrupt
payload rejection.
- `pp_assets_ppi_header_tests` passed, including PPI thumbnail/body layout,
body summary validation, layer/frame indexing, explicit per-layer metadata
and per-layer frame duration writing, dirty-face PNG payload metadata
validation, targeted layer/frame dirty-face writing, and decoded dirty-face
payload coverage.
- `pp_assets_settings_document_tests` passed.
- `pp_paint_brush_tests` passed.
- `pp_paint_blend_tests` passed.
- `pp_paint_stroke_tests` passed.
- `pp_paint_stroke_script_tests` passed.
- `pp_document_tests` passed, including snapshot construction, alpha-lock
metadata, per-layer frame metadata, frame move, duration, face-pixel payload
storage/replacement/rejection, snapshot-embedded face-payload rejection, and
history invariants.
- `pp_document_ppi_import_tests` passed, including decoded PPI dirty-face
payload attachment to `pp_document` layer/frame storage and out-of-range
payload rejection.
- `pp_document_ppi_export_tests` passed, including pure document metadata,
per-layer frame duration, and PNG-encoded face-payload export to PPI bytes,
plus malformed payload rejection at the export boundary.
- `pp_renderer_api_tests` passed, including shader descriptor validation,
PanoPainter shader catalog validation, explicit texture usage validation,
texture mip-level validation, resource debug-label validation, readback
byte-size and command-order validation, texture-upload byte-count validation,
mipmap-generation command validation, trace marker/scope validation,
frame-capture byte-size and command-order validation,
render-target blit validation, texture-slot binding validation, blend-state
validation, scissor-state validation, render-pass color/depth/stencil clear
validation, shader-uniform write validation, draw descriptor/range
validation, backend-neutral resource factory validation, texture-copy
validation, recording
render-pass clear/scissor/depth/blend/shader-uniform/texture/sampler-bind/
upload/texture-copy/readback/frame-capture/blit command capture, draw
mesh-input capture, explicit draw-range capture, and invalid catalog
rejection. The same suite now covers complex paint feedback planning for
framebuffer-fetch backends, ping-pong texture-copy/blit fallbacks, simple
no-feedback blends, invalid render-target usage, unsupported backends, and
depth-target rejection.
- `pp_paint_renderer_compositor_tests` passed.
The suite now covers fixed-function stroke composite planning,
framebuffer-fetch planning, ping-pong texture-copy/blit fallback planning,
dual/pattern blend feedback detection, invalid blend mode rejection,
unsupported backend rejection, and invalid render-target rejection.
- `pp_ui_core_color_tests` passed.
- `pp_ui_core_layout_value_tests` passed.
- `pp_ui_core_layout_xml_tests` passed.
- `pano_cli_create_document_smoke` passed.
- `pano_cli_create_animation_document_smoke` passed and reports animation
duration JSON.
- `pano_cli_simulate_document_edits_smoke` passed and reports pure
`pp_document` layer metadata, frame order, active indices, and face-payload
state as JSON.
- `pano_cli_simulate_document_history_smoke` passed and reports real
`pp_document::DocumentHistory` apply/undo/redo state as JSON.
- `pano_cli_simulate_document_export_smoke` passed and reports pure
`pp_document` export to PPI bytes, asset-level decode, and document reimport
round-trip state as JSON.
- `pano_cli_simulate_image_import_smoke` passed and reports embedded PNG decode
plus `pp_document` face-payload attachment state as JSON.
- `pano_cli_inspect_image_rejects_unsupported` passed as an expected failure
test.
- `pano_cli_inspect_png_metadata_smoke` passed and reports PNG metadata JSON
for the tiny IHDR fixture.
- `pano_cli_import_image_rejects_truncated_png` passed as an expected failure
test, proving the file-driven image import command rejects a metadata-valid
but undecodable PNG payload.
- `pano_cli_inspect_project_layout_smoke` passed and reports PPI
thumbnail/body byte layout, body summary, layer/frame descriptors, and
dirty-face PNG payload metadata JSON.
- `pano_cli_load_project_metadata_smoke` passed and reports a `pp_document`
projection with per-layer frame counts, durations, and zero loaded face
payloads for the minimal PPI fixture.
- `pano_cli_save_project_roundtrip_smoke` passed and proves the metadata-only
`pp_assets` PPI writer can save a generated multi-frame PPI and reload it
through `pano_cli load-project`.
- `pano_cli_save_project_payload_roundtrip_smoke` passed and proves the
`pp_assets` PPI writer can save a compressed RGBA PNG dirty-face payload to
an explicit layer/frame slot, inspect the serialized descriptor, and reload
it as decoded `pp_document` face-pixel data.
- `pano_cli_save_document_project_roundtrip_smoke` passed and proves a pure
`pp_document` export can be written to a PPI file, inspected for layer/frame
dirty-face descriptors, and loaded back through the PPI import path.
- `pano_cli_apply_stroke_script_roundtrip_smoke` passed and proves a checked-in
stroke script can be parsed, sampled, applied to a pure `pp_document` face
payload, written to PPI, inspected for the expected dirty-face box, and loaded
back as decoded document pixel data.
- `pano_cli_apply_stroke_script_rejects_tiny_canvas` passed as an expected
failure test, proving the stroke-script document command rejects dimensions
outside its bounded automation range before payload allocation.
- `pano_cli_parse_layout_smoke` passed.
- `pano_cli_simulate_stroke_smoke` passed and reports deterministic stroke
sample counts/distances.
- `pano_cli_simulate_stroke_script_smoke` passed and reports deterministic
aggregate stroke-script counts/distances.
- `pp_app_core_document_cloud_tests` passed, covering 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
decisions, plus bulk upload progress visibility, zero-file, and clamped
progress-total decisions.
- `pano_cli_plan_cloud_upload_clean_smoke`,
`pano_cli_plan_cloud_upload_unsaved_smoke`,
`pano_cli_plan_cloud_upload_new_document_smoke`, and
`pano_cli_plan_cloud_upload_no_canvas_smoke` passed and expose those app-core
cloud upload decisions as JSON.
- `pano_cli_plan_cloud_upload_all_progress_smoke` and
`pano_cli_plan_cloud_upload_all_headless_smoke` passed and expose app-core
bulk upload progress decisions as JSON.
- `pano_cli_plan_cloud_browse_waiting_smoke`,
`pano_cli_plan_cloud_browse_selected_smoke`, and
`pano_cli_plan_cloud_browse_no_canvas_smoke` passed and expose app-core cloud
browse/download-selection decisions as JSON.
- `pp_app_core_document_recording_tests` passed, covering recording start/stop,
clear, platform recorded-file cleanup, frame-count reset, export progress
totals, and oversized progress-total clamping.
- `pano_cli_plan_recording_session_stopped_smoke`,
`pano_cli_plan_recording_session_running_smoke`, and
`pano_cli_plan_recording_session_platform_cleanup_smoke` passed and expose
app-core recording lifecycle/export decisions as JSON.
- `pp_app_core_document_resize_tests` passed, covering resize dialog state,
unknown current-resolution labeling, selected-resolution mapping, square
canvas sizing, history-clearing intent, invalid selection rejection, service
dispatch order, optional history clearing, and invalid-dimension rejection.
- `pano_cli_plan_document_resize_smoke` and
`pano_cli_plan_document_resize_rejects_invalid_selection` passed and expose
live document-resize planning as JSON automation.
- `pp_app_core_document_layer_tests` passed, covering changed layer rename,
unchanged no-op rename, empty-name rejection, overlong-name rejection, rename
executor dispatch, no-op rename dialog finish, malformed rename-plan
rejection, layer add/duplicate/select/reorder/remove planning, metadata
planning, bad-index rejection, bad-opacity rejection, bad-blend-mode
rejection, transient highlight behavior, layer operation executor dispatch,
layer operation side-effect dispatch, no-op operation preservation,
malformed operation rejection, Layer menu labels/actions, merge-down routing,
animated merge blocking, missing selection handling, bad Layer menu state
rejection, Layer menu executor dispatch, no-op menu execution preservation,
merge-plan validation, unsupported animated merge rejection, merge executor
dispatch, and malformed merge-plan rejection.
- `pano_cli_plan_layer_rename_smoke`,
`pano_cli_plan_layer_rename_no_op_smoke`, and
`pano_cli_plan_layer_rename_rejects_empty_name` passed and expose live
layer-rename planning as JSON automation.
- `pano_cli_plan_layer_menu_merge_smoke`,
`pano_cli_plan_layer_menu_clear_smoke`,
`pano_cli_plan_layer_menu_merge_animated_blocked_smoke`,
`pano_cli_plan_layer_menu_missing_selection_smoke`, and
`pano_cli_plan_layer_menu_rejects_bad_state` passed and expose live Layer
menu planning as JSON automation.
- `pano_cli_plan_layer_merge_smoke` and
`pano_cli_plan_layer_merge_animated_rejected` passed and expose live
merge execution planning as JSON automation.
- `pano_cli_plan_layer_operation_add_smoke`,
`pano_cli_plan_layer_operation_reorder_no_op_smoke`,
`pano_cli_plan_layer_operation_highlight_smoke`, and
`pano_cli_plan_layer_operation_rejects_bad_opacity` passed and expose live
layer-panel operation planning as JSON automation.
- `pp_app_core_document_animation_tests` passed, covering animation frame
add/duplicate/remove planning, selected-frame rejection, last-frame remove
rejection, duration floor/overflow handling, timeline move edge behavior,
goto/next/previous wrapping, onion-size rejection, service dispatch ordering,
frame-click selection planning, no-reload playback step planning,
playback toggle start/stop planning, animation panel action planning,
invalid panel timeline state rejection, non-mutating duration no-ops, and
malformed execution payload rejection.
- `pano_cli_plan_animation_operation_add_smoke`,
`pano_cli_plan_animation_operation_duration_floor_smoke`,
`pano_cli_plan_animation_operation_next_wrap_smoke`,
`pano_cli_plan_animation_operation_select_smoke`,
`pano_cli_plan_animation_operation_playback_smoke`,
`pano_cli_plan_animation_operation_toggle_playback_start_smoke`,
`pano_cli_plan_animation_operation_toggle_playback_stop_smoke`,
`pano_cli_plan_animation_panel_action_next_smoke`,
`pano_cli_plan_animation_panel_action_toggle_stop_smoke`,
`pano_cli_plan_animation_panel_action_rejects_bad_timeline`,
`pano_cli_plan_animation_operation_rejects_remove_last_frame`, and
`pano_cli_plan_animation_operation_rejects_bad_selection` passed and expose
live animation-panel planning as JSON automation.
- `pp_app_core_brush_ui_tests` passed, covering brush color channel validation,
invalid color rejection, texture-path validation, preset-brush availability,
preserve-current-color intent, stroke-settings refresh intent, texture-list
add target path planning, user-texture removal intent, clamped reorder intent,
stroke-control slider/toggle/blend/reset planning, service dispatch ordering,
texture/preset/list/stroke-control execution payloads, execution failure
preservation, and invalid execution payload rejection.
- `pano_cli_plan_brush_operation_color_smoke`,
`pano_cli_plan_brush_operation_texture_smoke`,
`pano_cli_plan_brush_operation_preset_smoke`,
`pano_cli_plan_brush_operation_rejects_bad_color`, and
`pano_cli_plan_brush_operation_rejects_empty_texture` passed and expose live
brush/color/preset UI planning as JSON automation.
- `pano_cli_plan_brush_texture_list_add_smoke`,
`pano_cli_plan_brush_texture_list_remove_user_smoke`,
`pano_cli_plan_brush_texture_list_move_edge_smoke`, and
`pano_cli_plan_brush_texture_list_rejects_bad_source` passed and expose live
brush/pattern texture-list planning as JSON automation.
- `pano_cli_plan_brush_stroke_control_float_smoke`,
`pano_cli_plan_brush_stroke_control_toggle_smoke`,
`pano_cli_plan_brush_stroke_control_blend_smoke`,
`pano_cli_plan_brush_stroke_control_reset_smoke`,
`pano_cli_plan_brush_stroke_control_rejects_bad_setting`, and
`pano_cli_plan_brush_stroke_control_rejects_bad_blend` passed and expose live
stroke-panel slider/toggle/blend/reset planning as JSON automation.
- `pp_app_core_grid_ui_tests` passed, covering heightmap pick/load/reload/clear
planning, lightmap capability and limit checks, missing-heightmap no-op
behavior, and commit canvas gating.
- `pano_cli_plan_grid_operation_pick_smoke`,
`pano_cli_plan_grid_operation_load_smoke`,
`pano_cli_plan_grid_operation_render_supported_smoke`,
`pano_cli_plan_grid_operation_render_unsupported_smoke`,
`pano_cli_plan_grid_operation_rejects_empty_reload`, and
`pano_cli_plan_grid_operation_rejects_bad_samples` passed and expose live
grid/heightmap/lightmap planning as JSON automation.
- `pp_app_core_canvas_tool_ui_tests` passed, covering toolbar mode selection,
copy/cut transform action planning, pick no-op outside draw mode, and
touch-lock toggling, plus toolbar active-state derivation for draw, copy, and
bucket modes, service dispatch ordering, pick no-op execution, and malformed
execution payload rejection.
- `pp_app_core_canvas_hotkey_tests` passed, covering E draw/erase toggles,
Ctrl+Z/Ctrl+Shift+Z history planning, Ctrl+S/Ctrl+Shift+S document save
intents, Tab UI toggles, brush-size brackets, Android back and two-finger
undo, no-op Ctrl-less Z, bad-count rejection, executor dispatch, and
malformed brush-size execution rejection.
- `pano_cli_plan_canvas_hotkey_ctrl_z_smoke`,
`pano_cli_plan_canvas_hotkey_save_dirty_version_smoke`,
`pano_cli_plan_canvas_hotkey_erase_smoke`,
`pano_cli_plan_canvas_hotkey_two_finger_undo_smoke`, and
`pano_cli_plan_canvas_hotkey_rejects_bad_count` passed and expose live
canvas keyboard/touch command planning as JSON automation.
- `pano_cli_plan_canvas_tool_draw_smoke`,
`pano_cli_plan_canvas_tool_copy_smoke`,
`pano_cli_plan_canvas_tool_pick_noop_smoke`,
`pano_cli_plan_canvas_tool_touch_lock_smoke`, and
`pano_cli_plan_canvas_tool_rejects_unknown` passed and expose live draw
toolbar planning as JSON automation.
- `pano_cli_plan_canvas_tool_state_draw_smoke`,
`pano_cli_plan_canvas_tool_state_copy_smoke`, and
`pano_cli_plan_canvas_tool_state_rejects_unknown` passed and expose draw
toolbar active-state refresh as JSON automation.
- `pp_app_core_document_canvas_tests` passed, covering clear-current-layer
undo/dirty intent, no-canvas no-op behavior, and invalid clear color
rejection, service dispatch color forwarding, no-op execution preservation,
and invalid execution color rejection.
- `pano_cli_plan_canvas_clear_smoke`,
`pano_cli_plan_canvas_clear_no_canvas_smoke`, and
`pano_cli_plan_canvas_clear_rejects_bad_color` passed and expose toolbar
canvas clear planning as JSON automation.
- `pp_app_core_document_import_tests` passed, covering wide equirectangular,
legacy vertical cube strip, regular transform-placement, and invalid-dimension
import route decisions, equirectangular service dispatch, transform import
dispatch, empty-path rejection, and invalid execution dimension rejection.
- `pano_cli_plan_image_import_wide_equirect_smoke`,
`pano_cli_plan_image_import_transform_smoke`, and
`pano_cli_plan_image_import_rejects_invalid_dimensions` passed and expose File
> Import route planning as JSON automation.
- `pp_app_core_file_menu_tests` passed, covering top-level File menu routing for
creation/open/import, save intents, export/submenu/cloud actions, and unknown
command rejection, plus executor dispatch for dialog, picker, save, export,
share, resize, and cloud actions.
- `pano_cli_plan_file_menu_import_smoke`,
`pano_cli_plan_file_menu_save_as_smoke`,
`pano_cli_plan_file_menu_export_smoke`,
`pano_cli_plan_file_menu_cloud_upload_smoke`, and
`pano_cli_plan_file_menu_rejects_unknown` passed and expose top-level File
menu routing as JSON automation.
- `pp_app_core_document_export_tests` passed, now also covering export menu
dialog routing, demo-mode MP4/timelapse license gating, and missing-canvas
handling, plus export menu executor dispatch for all dialog, blocked, and
unavailable actions before legacy export dialogs continue.
- `pano_cli_plan_export_menu_png_smoke`,
`pano_cli_plan_export_menu_mp4_demo_blocked_smoke`,
`pano_cli_plan_export_menu_no_canvas_smoke`, and
`pano_cli_plan_export_menu_rejects_unknown` passed and expose File menu export
routing as JSON automation.
- `pp_app_core_history_ui_tests` passed, covering undo/redo availability,
no-op history commands, clear-history stack/memory state, memory-only clear,
negative metric rejection, service dispatch order, empty-history no-op
execution, and invalid execution metric rejection.
- `pano_cli_plan_history_operation_undo_smoke`,
`pano_cli_plan_history_operation_redo_empty_smoke`,
`pano_cli_plan_history_operation_clear_smoke`, and
`pano_cli_plan_history_operation_rejects_negative_count` passed and expose
toolbar/canvas history planning as JSON automation.
- `pp_app_core_quick_ui_tests` passed, covering quick brush/color slot
selection, active-slot popup decisions, invalid slot rejection, restore-state
validation, reset-state validation, service dispatch order, explicit
brush/color restore indices, and malformed execution payload rejection.
- `pano_cli_plan_quick_operation_select_brush_smoke`,
`pano_cli_plan_quick_operation_open_color_smoke`,
`pano_cli_plan_quick_operation_restore_smoke`,
`pano_cli_plan_quick_operation_reset_smoke`,
`pano_cli_plan_quick_operation_rejects_bad_slot`, and
`pano_cli_plan_quick_operation_rejects_bad_restore` passed and expose live
quick-panel planning as JSON automation.
- `pp_app_core_tools_menu_tests` passed, covering Tools submenu routing,
root-closing commands, platform-only SonarPen gating, executor dispatch,
unavailable no-op actions, floating panel chrome metadata, already-visible
panel no-ops, and animation panel non-droppable state.
- `pano_cli_plan_tools_menu_shortcuts_smoke`,
`pano_cli_plan_tools_menu_sonarpen_unavailable_smoke`,
`pano_cli_plan_tools_panel_layers_smoke`,
`pano_cli_plan_tools_panel_visible_noop_smoke`, and
`pano_cli_plan_tools_panel_rejects_unknown` passed and expose live Tools
menu/panel planning as JSON automation.
- `pp_app_core_about_menu_tests` passed, covering About/help/what's-new dialog
routing, versioned what's-new labels, crash diagnostic gating, performance
workload metadata, no-canvas performance-test blocking, dispatch through the
`AboutMenuServices` executor boundary, and no-op unavailable actions.
- `pano_cli_plan_about_menu_news_smoke`,
`pano_cli_plan_about_menu_performance_no_canvas_smoke`,
`pano_cli_plan_about_menu_crash_disabled_smoke`, and
`pano_cli_plan_about_menu_rejects_unknown` passed and expose live About menu
planning as JSON automation.
- `pp_app_core_main_toolbar_tests` passed, covering live toolbar/status direct
dialog routing, undo/redo availability, clear-history availability, no-canvas
clear blocking, negative history metric rejection, and dispatch through the
`MainToolbarServices` executor boundary without invoking no-op actions.
- `pano_cli_plan_main_toolbar_undo_smoke`,
`pano_cli_plan_main_toolbar_redo_empty_smoke`,
`pano_cli_plan_main_toolbar_clear_canvas_no_canvas_smoke`, and
`pano_cli_plan_main_toolbar_rejects_negative_count` passed and expose live
toolbar/status planning as JSON automation.
- `pp_app_core_document_sharing_tests` passed, covering saved-path gating before
platform share execution.
- `pano_cli_plan_share_file_unsaved_smoke` and
`pano_cli_plan_share_file_saved_smoke` passed and expose app-core share
decisions as JSON.
- `pp_app_core_document_platform_io_tests` passed, covering empty selected-path
filtering and non-empty picked-path callback planning before platform picker
callbacks, plus empty/non-empty display-file planning before platform
display callbacks, plus virtual keyboard show/hide planning before platform
keyboard callbacks, plus cursor visibility planning before platform cursor
callbacks, plus clipboard read/write planning before platform clipboard
callbacks.
- `pano_cli_plan_picked_path_empty_smoke` and
`pano_cli_plan_picked_path_selected_smoke` passed and expose app-core picker
selected-path decisions as JSON.
- `pano_cli_plan_display_file_empty_smoke` and
`pano_cli_plan_display_file_selected_smoke` passed and expose app-core
display-file decisions as JSON.
- `pano_cli_plan_keyboard_visibility_hidden_smoke` and
`pano_cli_plan_keyboard_visibility_visible_smoke` passed and expose app-core
virtual keyboard decisions as JSON.
- `pano_cli_plan_cursor_visibility_hidden_smoke` and
`pano_cli_plan_cursor_visibility_visible_smoke` passed and expose app-core
cursor visibility decisions as JSON.
- `pano_cli_plan_clipboard_read_smoke`,
`pano_cli_plan_clipboard_write_smoke`, and
`pano_cli_plan_clipboard_write_empty_smoke` passed and expose app-core
clipboard decisions as JSON, including empty write text.
- `pp_platform_api_tests` passed, covering the SDK-free `PlatformServices`
interface for startup storage path preparation, clipboard read/write, empty
clipboard writes, cursor visibility dispatch, virtual-keyboard visibility
dispatch, external file display dispatch, file sharing dispatch, native
app/window close dispatch, UI-thread lifecycle dispatch, render-context
lifecycle dispatch, render-target binding dispatch, render platform hint
dispatch, render debug callback dispatch, render-capture frame hook dispatch,
recording cleanup 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
app now
consumes this interface through an injected
`WindowsPlatformServices` instance isolated in
`src/platform_windows/windows_platform_services.*`; other platforms still
use the legacy fallback adapter, now isolated in
`src/platform_legacy/legacy_platform_services.*` instead of being owned by
`app_events.cpp`.
- `panopainter_validate_shaders` passed, validating 25 shader programs and 7
shader includes for stage markers and include graph integrity.
- `pp_renderer_gl_capabilities_tests` passed on default MSVC, vcpkg-headless,
and Android arm64 configure/build, covering framebuffer fetch, map-buffer
alignment, desktop GL core float support, GLES float/half-float extensions,
WebGL exclusion behavior, upload types for RGBA8/RGBA16F/RGBA32F internal
formats, image channel-count format mapping including invalid counts, and
RGBA8/RGBA32F readback format and byte-count mapping, PBO pixel-buffer
target/usage/access mapping, framebuffer status names, framebuffer blit color
mask and linear/nearest filters, plus Shape index-type and fill/stroke primitive mode mapping,
PanoPainter cube-face texture-target order, and the linear clamp-to-edge
render-target texture parameter set used by `RTT::create`.
Sampler parameter validation covers wrap S/T/R plus min/mag filter ordering
used by legacy `Sampler::set` and `Sampler::set_filter`, plus the desktop
border-color parameter name used by `Sampler::set_border`.
Legacy `TextureCube` allocation/bind/delete and `Sampler`
create/configure/border/bind/unbind paths now execute through tested
`pp_renderer_gl` dispatch contracts, keeping cube-map and sampler resource
lifecycle reachable without a live GL context.
Shader attribute binding catalog validation covers the current `pos`, `uvs`,
`uvs2`, `col`, and `nor` bindings and rejects empty, unnamed, null-name, and
duplicate-name catalogs while preserving legacy shared locations. Shader
uniform catalog validation covers the 43 legacy uniform
names used by `Shader`, preserves the legacy hash ids, and rejects empty,
unnamed, null-name, mismatched-hash, and duplicate-name catalogs.
Legacy `Shader` program use/delete, uniform writes, and attribute-location
lookups now execute through tested `pp_renderer_gl` dispatch contracts.
Legacy shader source compilation, shader deletion, program attach/link,
attribute rebinding, active-uniform count/enumeration, and uniform-location
discovery also execute through tested `pp_renderer_gl` dispatch contracts,
leaving only thin GL adapter functions in the retained `Shader` utility.
Legacy `Shape` mesh buffer/VAO creation, zero-byte dynamic-buffer creation,
dynamic buffer uploads, indexed and non-indexed draws, and resource deletion
now execute through tested `pp_renderer_gl` dispatch contracts, leaving only
thin GL adapter functions in the retained shape utility.
Legacy `Font` text mesh creation now covers the one-VAO/deferred-upload case,
and its dynamic index/vertex uploads and indexed draw calls execute through
the same tested dispatch contracts.
- `pp_renderer_gl_command_plan_tests` covers the headless OpenGL command
planner for recorded render-pass clear masks/values, viewport/scissor state,
blend/depth/sampler state, texture format mapping, mesh/draw primitive modes,
draw counts, shader bind/uniform names and byte counts, texture
upload/mipmap/transition/copy/readback/capture metadata, blit filters and
byte totals, planned command names, unsupported enum/state rejection, whole
recorded stream planning, valid trace/render/shader/draw/blit ordering, typed
texture-command counts, broken render-pass order detection, and executable
draw/uniform dependency failures.
- PowerShell analyze automation returns JSON summaries and includes the shader
validation target and renderer-boundary guard.
- `windows-msvc-vcpkg-headless` configured through the Visual Studio bundled
vcpkg root, installed the manifest dependencies, built the headless component
matrix, and passed `desktop-fast-vcpkg`.
- `pp_ui_core` built and tested against vcpkg tinyxml2 on
`windows-msvc-vcpkg-headless` and against the vendored fallback on
`windows-msvc-default` and `android-arm64`.
- `windows-clangcl-asan` configures headlessly with clang-cl 18.1.8 and
release MSVC runtime selection; build remains blocked and debt-tracked in
DEBT-0014 because the selected VS 2026-preview STL requires Clang 20 or
newer.
- `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.
- Renderer-boundary automation fails if active non-backend source code
reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL
implementation files.
- `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict
renderer feature flags, renderer-owned resource factory and
command-order/render-pass-clear/scissor-state/depth-state/blend-state/
texture-usage/texture-bind/sampler-bind/shader-uniform/texture-upload/
mipmap-generation/texture-transition/readback/frame-capture/blit validation plus explicit draw
descriptor and texture-copy validation; it creates validated textures,
render targets, shaders, meshes, and readback buffers with validated debug
labels, then records commands, trace markers/scopes, render-pass
color/depth/stencil clear intent, scissor state, depth state, blend state,
shader uniform writes, texture/sampler binds, draw mesh inputs, explicit draw
ranges, texture uploads/mipmap generations/state transitions/copies/readbacks, frame captures,
and render-target blits, giving automation a backend-neutral render path that
does not require a window or GL context. Clearing the recording device now
resets active render-pass and trace-scope state so interrupted automation can
reuse a recorder without carrying stale frame state forward.
- `pano_cli record-render` exercises that headless recording renderer and emits
JSON command counts, backend feature flags, resource creation counts, target dimensions, backend
name, trace marker/scope and draw summary, labeled descriptor counts,
render-pass/depth-clear counts, and draw
descriptor vertex/index totals, scissor/depth/blend-state plus
shader-uniform/texture/sampler-bind/upload/mipmap-generation/texture-transition/texture-copy/readback/
frame-capture/blit command/byte totals for agent automation. When
`pp_renderer_gl` is available, it also emits an `openGlPlan` JSON object with
planned command count, support status, render-pass/draw/shader-bind/uniform/
texture-upload/mipmap/transition/copy/readback/capture/passthrough/trace
counts, unsupported command count, render-pass order error count, dependency
error count, and unclosed-pass state. The
`--exercise-clear` mode deliberately clears an interrupted trace/render pass,
verifies stale trace-scope state is rejected, verifies the render context can
be reused, and then emits that reset status in JSON. It also has an
expected-failure smoke for oversized render/readback targets.
- `pano_cli simulate-document-history` exercises pure document history
apply/undo/redo behavior and emits JSON layer/frame/history state for agent
automation.
- `pano_cli simulate-document-edits` exercises pure document layer/frame edit
operations and emits JSON metadata, frame order, face-payload state, and
selection-mask state for agent automation.
- `pano_cli simulate-image-import` exercises embedded PNG decode through
`pp_assets` and `pp_document` face-payload attachment through JSON
automation.
- `pano_cli import-image` accepts a PNG file path, decodes RGBA8 pixels through
`pp_assets`, attaches them to a pure `pp_document` face payload, and has
checked-in decodable-PNG plus truncated-PNG rejection smoke tests.
- `pano_cli export-image` writes deterministic RGBA8 PNGs through `pp_assets`
and has a save/import round-trip smoke test. Full legacy canvas export
remains a future `pano_cli` task.
- `pano_cli save-project` exposes generated multi-layer, multi-frame PPI
writing with layer metadata and targeted dirty-face layer/frame payloads
through JSON automation and is covered by metadata-only and
dirty-face-payload save/load round-trip smoke tests. Full legacy canvas save
parity remains tracked by DEBT-0013.
- `pp_assets::create_ppi_project` exposes the underlying generated PPI writer
for non-uniform layer metadata and frame-duration extraction work.
- `pp_document::export_ppi_project_document` exposes pure document-to-PPI byte
export through CTest coverage; legacy Canvas save integration remains tracked
by DEBT-0010/DEBT-0013.
- `pano_cli simulate-document-export` exposes the same export path through JSON
automation for agents.
- `pano_cli save-document-project` exposes file-writing document export
automation for inspect/load round trips.
- `pano_cli apply-stroke-script` exposes file-driven stroke-script application
to a pure document face payload and writes a PPI artifact for inspect/load
round-trip automation.
- Snapshot creation now rejects invalid embedded RGBA8 face payloads before
document export or history can persist malformed state.
- Package-smoke wrappers validate the Windows CMake app executable/runtime
`data/` copy and report structured package readiness for AppX, Android
standard/Quest/Focus APKs, Apple bundles, and WebGL outputs. Actual package
building remains blocked by DEBT-0011 until those targets are migrated to
root CMake.
- Android arm64 configured with NDK 29.0.14206865 through the platform-build
wrapper and compiled headless foundation/tool/test targets.
- Desktop VR drawing now routes generic OpenGL scissor/depth/blend state,
depth clears, active texture units, and fallback 2D texture unbinds through
the renderer GL backend mapping; platform VR SDK bridges remain isolated for
later platform-shell extraction.
- Canvas mode overlay, mask, and transform paths now route generic OpenGL
blend/depth state, active texture units, 2D copy targets, and RGBA8
readback formats through the renderer GL backend mapping.
- `NodeCanvas` panorama UI rendering now routes sampler defaults, saved
viewport/clear/blend/depth/scissor state, color clears, active texture units,
fallback 2D texture unbinds, copy targets, and RGBA8 render-target formats
through the renderer GL backend mapping.
- Canvas resource setup now routes stroke-buffer RGBA8/RGBA16F/RGBA32F
formats, flood-fill texture upload format/type, brush/stencil/mix sampler
filters and wraps, and cube-strip import channel formats through the renderer
GL backend mapping. The clamp-to-border sampler wrap is now cataloged and
tested in `pp_renderer_gl`.
- Early canvas draw helpers now route pick readbacks, stroke mixer depth/scissor
and blend state, saved viewport/clear-state queries, active texture units,
fallback 2D texture unbinds, and stroke background copy targets through the
renderer GL backend mapping.
- Canvas stroke commit now routes saved viewport/clear/blend state, history
readbacks, active texture units, fallback 2D texture unbinds, and layer
compositing copy targets through the renderer GL backend mapping.
- Canvas layer merge rendering and explicit layer-merge compositing now route
depth/blend state, active texture units, fallback 2D texture unbinds, and
merge framebuffer copy targets through the renderer GL backend mapping.
- Canvas draw-merge shader-blend selection now consumes the extracted
`pp_paint_renderer` stroke composite planner for current layer and primary
brush blend modes, while preserving legacy OpenGL compositing execution under
DEBT-0036.
- `NodeCanvas` panorama rendering now consumes the same tested
`pp_paint_renderer` canvas blend-gate planner as `Canvas::draw_merge`, so
layer and primary-brush blend-trigger compatibility is centralized.
- Shader initialization now publishes the OpenGL backend's renderer-neutral
feature snapshot through the legacy shader manager, and live canvas blend
gates consume that `RenderDeviceFeatures` value instead of hand-built
framebuffer-fetch/texture-copy flags.
- Canvas draw-merge and `NodeCanvas` panorama shader-blend paths now use the
shared canvas blend-gate plan to decide whether they can read destination
color through framebuffer fetch or must copy the destination texture before
the legacy OpenGL blend draw.
- Canvas main-brush, dual-brush, and stroke-pad draw paths now use the tested
`pp_paint_renderer` stroke-feedback plan to decide whether framebuffer fetch
supplies destination color or the legacy OpenGL path must copy the target
texture before drawing.
- Canvas thumbnail layer blending now uses the same canvas destination-feedback
plan for framebuffer-fetch versus texture-copy decisions; the thumbnail draw
itself still executes through retained OpenGL canvas code under DEBT-0036.
- Canvas equirectangular import drawing and depth export rendering now route
depth/blend state and active texture units through the renderer GL backend
mapping.
- Canvas thumbnail generation and object-drawing helpers now route saved
viewport/clear/blend state, active texture units, readback format/type,
framebuffer copy targets, and renderbuffer/depth attachment parameters through
the renderer GL backend mapping; `src/canvas.cpp` no longer contains raw
`GL_*` constants.
- Windows desktop OpenGL context creation now consumes a tested
`windows_wgl_core_context_3_3_config()` catalog from `pp_renderer_gl`, moving
the active WGL context/pixel-format attribute literals out of the platform
entrypoint.
- 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, cursor visibility.
- 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.