115 KiB
PanoPainter Modernization Roadmap
Status: live Last updated: 2026-06-04
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.mdexists, 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:
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: pureBrush,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 usingpp_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, anddocs/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 mode start/stop, VR controller enablement, and canvas cursor mode;
the live tools/options menu and pano_cli plan-app-preferences consume those
contracts. Options-menu preference execution now dispatches through
AppPreferenceServices and src/legacy_app_preference_services.* before
legacy widgets, settings persistence, recording toggles, and canvas cursor
updates continue.
It also owns tested startup plans for run-counter increments, preference-save
intent, auto-timelapse startup, stored VR-controller state, and license-warning
visibility. App::init now plans those decisions before heavy initialization,
executes run-counter persistence through src/legacy_app_startup_services.*
before asset/layout setup, and executes runtime startup side effects after the
UI layout and main render target exist.
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.txtand shared CMake modules undercmake/. - Add
CMakePresets.jsonwith at least:windows-vs2026-x64windows-clangcl-asanlinux-clangandroid-arm64android-x64emscriptenmacosios-deviceios-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.pyonly if required to avoid unnecessary rewrites or missing-tag failures. - Add CMake options:
PP_BUILD_APPPP_BUILD_TESTSPP_BUILD_TOOLSPP_ENABLE_OPENGLPP_ENABLE_VULKAN_EXPERIMENTAL=OFFPP_ENABLE_VRPP_ENABLE_CLOUDPP_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, withC4100muted temporarily underDEBT-0019. - Optional MSVC analysis preset:
/analyze. - Clang/GCC:
-Wall -Wextra -Wpedantic -Wconversion -Wshadow -Wnull-dereference, with-Wunused-parametermuted temporarily underDEBT-0019.
- MSVC:
- 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-tidycppcheck- shader validation or compile checks
- CTest dashboard output
- Add
vcpkg.json. - Move reliable dependencies to vcpkg first:
fmtglmtinyxml2stbcurlsqlite3gladCatch2
- 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. Agent code navigation now includes
scripts/dev/clangd_nav.py with symbol/detail/path regex filters and a
panopainter_clangd_nav_regex_self_test CTest so broad symbol-family searches
can be validated before they guide refactors.
Implementation tasks:
- Add
tests/with one executable per component. - Register CTest labels:
foundationassetspaintdocumentrendereruiplatformintegrationfuzzslowgpu
- Add
tools/pano_clifor headless automation. pano_clishould 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 Debugruns 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. Recording lifecycle and MP4
export execution now dispatch through RecordingServices in
src/legacy_recording_services.* 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 in the shared app-shell layer bridge
src/legacy_document_layer_services.* 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
shared app-shell layer bridge continues legacy Canvas and UI layer 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 in src/legacy_document_layer_services.* continues execution. Layer
menu clear now routes through the shared DocumentCanvasClearServices executor
from that bridge 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
shared src/legacy_document_animation_services.* bridge continues legacy
Canvas/Layer/canvas-mode and animation-panel state execution.
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 shared src/legacy_brush_ui_services.* bridge mutates legacy Brush and
panel 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 in the shared brush bridge
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 in the shared brush bridge 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
in src/legacy_canvas_tool_services.* 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
shared bridge through its input-only path before legacy canvas mode execution
continues. Canvas mode pointer-tip visibility and Windows pressure remapping
now dispatch through PlatformServices, preserving iOS tip behavior and the
Windows pressure curve outside canvas_modes.cpp. 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 the shared bridge delegates to legacy
UI/canvas/history adapters.
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 in the shared app-shell bridge
src/legacy_app_shell_services.* 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 in the shared app-shell bridge
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. Grid execution now dispatches through
GridUiServices in src/legacy_grid_ui_services.* before legacy image loading,
OpenGL texture updates, nanort lightmap baking, and Canvas::draw_objects
execution continue.
The retained NodePanelGrid lightmap bake now uses the shared parallel_for
helper instead of platform-specific Win32 Concurrency Runtime and Apple
dispatch_apply branches, keeping the row-dispatch policy in common legacy
infrastructure while the bake itself remains debt-tracked.
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 src/legacy_app_shell_services.* 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 in src/legacy_quick_ui_services.* 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 in the shared app-shell bridge 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.
The live SonarPen menu action now asks the active PlatformServices instance
for availability and startup, removing the local iOS branch from the Tools menu
and shared Tools executor while preserving the retained iOS bridge in the
legacy platform adapter.
Options-menu preference callbacks now dispatch UI scale, viewport scale, RTL,
VR mode, VR-controller, auto-timelapse, and cursor-mode side effects through
AppPreferenceServices in src/legacy_app_preference_services.* before
retained settings writes, recording lifecycle calls, and legacy canvas/UI
adapters continue.
VR mode start/stop now enters App platform wrappers that dispatch through
PlatformServices; Windows keeps the retained OpenVR bridge in
WindowsPlatformServices, while the legacy fallback reports unsupported VR
startup on non-Windows platforms until their shells own the service.
pano_cli plan-about-menu exposes app-core planning for About menu help,
about, what's-new, crash-test, and performance-test commands, including
versioned what's-new labels, diagnostic gating, and no-canvas performance-test
blocking. pp_app_core now also owns an AboutMenuServices executor boundary,
so App::init_menu_about dispatches through src/legacy_app_shell_services.*
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, SonarPen availability/startup,
VR mode start/stop,
image/file/save-file pickers, and directory pickers.
Windows installs an injected WindowsPlatformServices implementation from
src/platform_windows/windows_platform_services.* in pp_platform_windows;
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.
Prepared-file writable target selection now also dispatches through
PlatformServices, preserving the existing iOS temporary background-write path
and Web data-path synchronous write path while removing those platform branches
from App::pick_file_save.
PPBR and MP4 export dialogs now ask PlatformServices whether prepared-file
writes are used, so those dialog flows no longer spell local __IOS__ || __WEB__ branches for mobile/Web export handoff.
Layer and animation-frame export dialogs now also ask PlatformServices
whether work-directory collection exports are used, then feed that into the
pure pp_app_core plan_document_export_collection_target policy. This
removes the local iOS branches from those dialogs while preserving iOS
work_path/doc_layers and work_path/doc_frames targets in the legacy
adapter until Apple platform services are injected.
App-owned curl helpers for download, upload, and license checks now ask
PlatformServices whether network TLS verification is disabled, removing the
local Android branches from those helpers while preserving Android's existing
TLS-verification bypass in the legacy adapter until a network/platform service
owns cloud transport.
The remaining legacy curl sites in Asset::open_url, LogRemote::net_init,
and NodeDialogCloud::load_thumbs_thread now consume the shared
pp_platform_api default TLS policy helper instead of spelling local Android
branches; this keeps the current Android behavior aligned with
PlatformServices while a dedicated network service is still pending.
The Tools menu SonarPen entry now asks PlatformServices whether SonarPen is
available and dispatches startup through the same service, preserving the
current iOS Objective-C bridge in the legacy adapter while removing iOS branches
from App::init_menu_tools and LegacyToolsMenuServices.
App VR lifecycle start/stop now asks PlatformServices, preserving the current
Windows OpenVR startup/shutdown bridge in WindowsPlatformServices while
non-Windows fallback adapters keep the existing unsupported/no-op behavior.
Canvas image export publishing and explicit persistent-storage flushes now
dispatch through PlatformServices too, preserving iOS photo-library export
publication and WebGL filesystem sync behavior in the legacy adapter while
removing those direct platform calls from Canvas and brush preset storage.
Document-browser search root selection now dispatches through
PlatformServices, preserving the iOS Inbox root in the legacy adapter while
removing the iOS-specific branch from App::dialog_browse.
Save, New Document, and Browse dialog working-directory picker availability and
display-path formatting now also dispatch through PlatformServices, removing
desktop-only branches and Win32/macOS path formatting from those UI nodes while
preserving Windows and macOS picker behavior in platform adapters.
Native UI/window state saving now dispatches through PlatformServices,
preserving Windows window placement persistence in WindowsPlatformServices
and macOS UI state persistence in the legacy adapter while removing platform
guards from App::ui_save.
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.
Canvas input tip-visibility and pressure-remap policies now also dispatch
through PlatformServices, removing the local iOS and Windows branches from
pen, line, and flood-fill canvas modes.
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.
Cloud upload, bulk upload, and browse/download live execution now flows through
the CloudServices app-core boundary and src/legacy_cloud_services.*, keeping
App::cloud_upload, App::cloud_upload_all, and App::cloud_browse as thin
planning adapters while legacy save, progress UI, network, dialog, canvas-open,
layer-refresh, and action-history work remains tracked under DEBT-0038.
The app-owned curl upload/download/license helpers now consume the platform TLS
verification policy through PlatformServices, and the retained Asset,
LogRemote, and cloud browse-dialog curl sites consume the same default platform
policy helper; retained cloud/network execution remains tracked under
DEBT-0038.
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.
Brush preset-list add/select/move/remove/clear decisions now consume
pp_app_core through NodePanelBrushPreset and
pano_cli plan-brush-preset-list, so preset UI callbacks share tested
headless index/selection planning. Live preset-list execution now also
dispatches through BrushPresetListServices and the shared app-core executor
before the retained legacy bridge mutates child nodes. The remaining direct
NodePanelBrushPreset child-node execution, legacy Brush cloning, friend
adapter, and preset save/reload behavior stay tracked under DEBT-0023.
App::open_document now routes through the app-core document-open executor and
src/legacy_document_open_services.*, preserving ABR/PPBR import prompts,
unsaved-project discard prompts, project open, layer refresh, title updates,
and history clearing while those live effects remain tracked under
DEBT-0039. Accepted ABR/PPBR import prompts now delegate import execution to
the app-core brush package import executor and
src/legacy_brush_package_import_services.*, preserving detached legacy preset
panel import threads while retained brush asset execution remains tracked under
DEBT-0048.
App::request_close, App::save_document, and
App::continue_document_workflow_after_optional_save now route through
app-core document-session executors and src/legacy_document_session_services.*,
preserving close prompts, save dialogs, save-version routing, existing-project
save execution, and dirty-workflow save-before-continue prompts while retained
legacy UI/canvas behavior remains tracked under DEBT-0040.
App::dialog_newdoc now routes accepted new-document plans through the
app-core new-document executor and src/legacy_document_session_services.*,
preserving target overwrite prompts, legacy canvas resize/layer setup, history
clearing, title updates, dirty/new-document flag mutation, and keyboard/dialog
cleanup while retained execution remains tracked under DEBT-0041.
App::dialog_save and App::dialog_save_ver now route accepted Save As and
Save Version plans through app-core document file/version save executors and
src/legacy_document_session_services.*, preserving overwrite prompts,
legacy Canvas::project_save, app document field updates, title updates, and
keyboard/dialog cleanup while retained execution remains tracked under
DEBT-0042.
App::dialog_export, App::dialog_export_layers,
App::dialog_export_anim_frames, App::dialog_export_depth, and
App::dialog_export_cube_faces now route accepted file/stem/collection and
named export work through app-core document export executors and
src/legacy_document_export_services.*; layer/frame collection export target
destination is now planned in pp_app_core and selected by PlatformServices,
preserving existing platform messages, directory creation, picker-selected
stems, Web prepared-file handoff, and legacy Canvas export calls while
retained execution remains tracked under DEBT-0043.
App::dialog_timelapse_export and App::dialog_export_mp4 now route
picker-selected MP4 export paths through the app-core document video export
executor and src/legacy_document_export_services.*, preserving mobile/Web
suggested-name save callbacks, desktop worker-thread timelapse export,
App::rec_export, animation Canvas::export_anim_mp4 dispatch, and existing
success messages while retained execution remains tracked under DEBT-0044.
App::dialog_ppbr_export now routes picker-selected PPBR brush package exports
through the app-core brush package export executor and
src/legacy_brush_package_export_services.*, preserving dialog metadata
collection, legacy Image header ownership, desktop worker-thread export,
mobile/Web save completion, NodePanelBrushPreset::export_ppbr, and existing
success messages while retained execution remains tracked under DEBT-0047.
PPBR package header validation and export target/data-directory planning now
live in pp_assets::brush_package and are exercised by
pp_assets_brush_package_tests plus pano_cli plan-brush-package-export.
The macOS-specific PPBR preview data-directory override now dispatches through
PlatformServices, so NodePanelBrushPreset::export_ppbr no longer spells a
local __OSX__ branch while the actual PPBR serialization path remains
legacy-owned.
The live PPBR import/export path consumes those helpers, while legacy
Serializer/Image payload reading, stroke preview generation, preset storage,
and the historical permissive version check remain tracked under DEBT-0047
and DEBT-0049.
ABR and PPBR import image target planning for brush tips and patterns also now
uses pp_assets::brush_package, so the legacy preset panel no longer owns the
data/brushes, data/brushes/thumbs, data/patterns, and
data/patterns/thumbs path construction rules. Actual ABR/PPBR parsing,
duplicate policy, preset creation, save/reload, and progress/UI refresh remain
legacy-owned under DEBT-0048.
Implementation tasks:
- Extract components in this order:
pp_foundationpp_assetspp_paintpp_documentpp_renderer_apipp_renderer_glpp_paint_rendererpp_ui_corepp_panopainter_uipp_platform_apipp_platform_*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, and its CPU lightmap row dispatch now uses the
shared legacy parallel_for helper rather than platform-specific worker APIs.
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:
IRenderDeviceITexture2DIRenderTargetIShaderProgramIMeshICommandContextIReadbackBufferIRenderTrace
- Port current renderer classes behind OpenGL backend types:
RTTTexture2DSamplerShaderManagerShape
- Keep OpenGL runtime capability decisions in
pp_renderer_glwith 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_labpp_renderer_metal_lab
- Use
D:\Dev\vkpaintas 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:
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_testspassed.pp_foundation_event_testspassed.pp_foundation_log_testspassed.pp_foundation_parse_testspassed.pp_foundation_task_queue_testspassed.pp_foundation_trace_testspassed.pp_assets_image_format_testspassed.pp_assets_image_metadata_testspassed.pp_assets_image_pixels_testspassed, including RGBA8 PNG decode and corrupt payload rejection.pp_assets_ppi_header_testspassed, 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_testspassed.pp_paint_brush_testspassed.pp_paint_blend_testspassed.pp_paint_stroke_testspassed.pp_paint_stroke_script_testspassed.pp_document_testspassed, 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_testspassed, including decoded PPI dirty-face payload attachment topp_documentlayer/frame storage and out-of-range payload rejection.pp_document_ppi_export_testspassed, 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_testspassed, 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_testspassed. 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_testspassed.pp_ui_core_layout_value_testspassed.pp_ui_core_layout_xml_testspassed.pano_cli_create_document_smokepassed.pano_cli_create_animation_document_smokepassed and reports animation duration JSON.pano_cli_simulate_document_edits_smokepassed and reports purepp_documentlayer metadata, frame order, active indices, and face-payload state as JSON.pano_cli_simulate_document_history_smokepassed and reports realpp_document::DocumentHistoryapply/undo/redo state as JSON.pano_cli_simulate_document_export_smokepassed and reports purepp_documentexport to PPI bytes, asset-level decode, and document reimport round-trip state as JSON.pano_cli_simulate_image_import_smokepassed and reports embedded PNG decode pluspp_documentface-payload attachment state as JSON.pano_cli_inspect_image_rejects_unsupportedpassed as an expected failure test.pano_cli_inspect_png_metadata_smokepassed and reports PNG metadata JSON for the tiny IHDR fixture.pano_cli_import_image_rejects_truncated_pngpassed 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_smokepassed and reports PPI thumbnail/body byte layout, body summary, layer/frame descriptors, and dirty-face PNG payload metadata JSON.pano_cli_load_project_metadata_smokepassed and reports app_documentprojection with per-layer frame counts, durations, and zero loaded face payloads for the minimal PPI fixture.pano_cli_save_project_roundtrip_smokepassed and proves the metadata-onlypp_assetsPPI writer can save a generated multi-frame PPI and reload it throughpano_cli load-project.pano_cli_save_project_payload_roundtrip_smokepassed and proves thepp_assetsPPI writer can save a compressed RGBA PNG dirty-face payload to an explicit layer/frame slot, inspect the serialized descriptor, and reload it as decodedpp_documentface-pixel data.pano_cli_save_document_project_roundtrip_smokepassed and proves a purepp_documentexport 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_smokepassed and proves a checked-in stroke script can be parsed, sampled, applied to a purepp_documentface 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_canvaspassed 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_smokepassed.pano_cli_simulate_stroke_smokepassed and reports deterministic stroke sample counts/distances.pano_cli_simulate_stroke_script_smokepassed and reports deterministic aggregate stroke-script counts/distances.pp_app_core_document_cloud_testspassed, 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, andpano_cli_plan_cloud_upload_no_canvas_smokepassed and expose those app-core cloud upload decisions as JSON.pano_cli_plan_cloud_upload_all_progress_smokeandpano_cli_plan_cloud_upload_all_headless_smokepassed and expose app-core bulk upload progress decisions as JSON.pano_cli_plan_cloud_browse_waiting_smoke,pano_cli_plan_cloud_browse_selected_smoke, andpano_cli_plan_cloud_browse_no_canvas_smokepassed and expose app-core cloud browse/download-selection decisions as JSON.PanoPainter,pp_app_core_document_cloud_tests, andpano_clibuilt after live cloud upload, bulk upload, and browse/download execution moved behind theCloudServicesboundary andsrc/legacy_cloud_services.*.- Focused cloud CTest coverage passed for
pp_app_core_document_cloud_testsand allpano_cli_plan_cloud_*smoke tests after the live bridge split. ctest --preset desktop-fast --build-config Debugpassed with 243 tests after the cloud bridge split.scripts/automation/package-smoke.ps1 -Preset windows-msvc-default -Configuration Debugpassed executable/data checks after the cloud bridge split; package target migration blockers remain underDEBT-0011.PanoPainter,pp_app_core_document_session_tests, andpano_clibuilt afterApp::open_documentmoved live execution behind the document-open services bridge. A clean rebuild was required once because MSVC reported the known Debug PDBLNK1103corruption, after which the build passed.- Focused document-open CTest coverage passed for
pp_app_core_document_route_tests,pp_app_core_document_session_tests, and thepano_cli_plan_open_route_*smoke tests after the live bridge split. PanoPainter,pp_app_core_document_session_tests, andpano_clibuilt after close request, document save, and dirty-workflow continuation execution moved behind document-session services. A clean rebuild was required once because MSVC reported the known Debug PDBLNK1103corruption, after which the build passed.- Focused document-session CTest coverage passed for
pp_app_core_document_session_tests,pano_cli_simulate_app_session_*, andpano_cli_plan_document_file/version_*smoke tests after the live bridge split. PanoPainter,pp_app_core_document_session_tests, andpano_clibuilt after accepted new-document execution moved behind the new-document services bridge. A clean rebuild was required once because MSVC reported the known Debug PDBLNK1103corruption, after which the build passed.- Focused new-document/session CTest coverage passed for
pp_app_core_document_session_tests,pano_cli_plan_new_document_*, andpano_cli_simulate_app_session_*smoke tests after the live bridge split. PanoPainter,pp_app_core_document_session_tests, andpano_clibuilt after accepted Save As and Save Version execution moved behind document file/version save services. A clean rebuild was required once because MSVC reported the known Debug PDBLNK1103corruption, after which the build passed.- Focused Save As/Version/session CTest coverage passed for
pp_app_core_document_session_tests,pano_cli_plan_document_file_*,pano_cli_plan_document_version_*, andpano_cli_simulate_app_session_*smoke tests after the live bridge split. PanoPainter,pp_app_core_document_export_tests, andpano_clibuilt after equirectangular, layers, animation-frame, depth, and cube-face export execution moved behind document export services. A clean rebuild was required once because MSVC reported the known Debug PDBLNK1103corruption, after which the build passed.- Focused export CTest coverage passed for
pp_app_core_document_export_tests,pano_cli_plan_export_start/menu/target_*, andpano_cli_simulate_document_export_smokeafter the live bridge split. PanoPainter,pp_app_core_document_export_tests, andpano_clibuilt after timelapse and animation MP4 export execution moved behind document video export services. A clean rebuild was required once because MSVC reported the known Debug PDBLNK1103corruption, after which the app, export tests, andpano_clitargets built cleanly.- Focused video export CTest coverage passed for
pp_app_core_document_export_tests,pano_cli_plan_export_menu_*,pano_cli_plan_export_target_name_smoke, andpano_cli_simulate_document_export_smoke. PanoPainter,pp_app_core_app_preferences_tests, andpano_clibuilt after options-menu preference execution moved behind app preference services.- Focused preference CTest coverage passed for
pp_app_core_app_preferences_testsand the app-preferences CLI smoke tests after the live bridge split, including VR mode failed-start status coverage. PanoPainter,pp_app_core_app_startup_tests, andpano_clibuilt after startup preference/runtime execution moved behind app startup services.- Focused startup CTest coverage passed for
pp_app_core_app_startup_tests,pano_cli_plan_app_startup_smoke, andpano_cli_plan_app_startup_rejects_negative_counter. PanoPainter,pp_app_core_brush_package_export_tests, andpano_clibuilt after PPBR brush package export request validation and dispatch moved behind app-core brush package services.- Focused PPBR export CTest coverage passed for
pp_app_core_brush_package_export_tests,pano_cli_plan_brush_package_export_smoke,pano_cli_plan_brush_package_export_rejects_empty_path, andpano_cli_plan_brush_package_export_dest_without_data_smoke. PanoPainter,pp_app_core_brush_package_import_tests, andpano_clibuilt after ABR/PPBR brush package import execution moved behind app-core brush import services.- Focused brush import CTest coverage passed for
pp_app_core_brush_package_import_tests,pano_cli_plan_brush_package_import_ppbr_smoke,pano_cli_plan_brush_package_import_abr_smoke,pano_cli_plan_brush_package_import_rejects_empty_path, andpano_cli_plan_brush_package_import_rejects_unknown_kind. PanoPainter,pp_assets_brush_package_tests,pp_app_core_brush_package_export_tests, andpano_clibuilt after PPBR header validation and export path/data-directory planning moved intopp_assets.- Focused PPBR asset CTest coverage passed for
pp_assets_brush_package_testsand the brush package export CLI tests, including path-without-directory rejection and legacy no-export-data data-directory planning. PanoPainter,pp_assets_brush_package_tests,pp_app_core_brush_package_import_tests, andpano_clibuilt after ABR and PPBR imported brush tip/pattern target paths moved intopp_assets.- Focused brush import storage CTest coverage passed for
pp_assets_brush_package_testsand the brush package import/export CLI smoke/failure tests. PanoPainter,pp_app_core_brush_ui_tests, andpano_clibuilt after brush preset-list add/select/move/remove/clear planning moved intopp_app_core.- Focused brush preset-list CTest coverage passed for
pp_app_core_brush_ui_testsandpano_cli_plan_brush_preset_list_*smoke tests. pp_app_core_document_recording_testspassed, 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, andpano_cli_plan_recording_session_platform_cleanup_smokepassed and expose app-core recording lifecycle/export decisions as JSON.pp_app_core_document_resize_testspassed, 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_smokeandpano_cli_plan_document_resize_rejects_invalid_selectionpassed and expose live document-resize planning as JSON automation.pp_app_core_document_layer_testspassed, 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, andpano_cli_plan_layer_rename_rejects_empty_namepassed 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, andpano_cli_plan_layer_menu_rejects_bad_statepassed and expose live Layer menu planning as JSON automation.pano_cli_plan_layer_merge_smokeandpano_cli_plan_layer_merge_animated_rejectedpassed 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, andpano_cli_plan_layer_operation_rejects_bad_opacitypassed and expose live layer-panel operation planning as JSON automation.pp_app_core_document_animation_testspassed, 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, andpano_cli_plan_animation_operation_rejects_bad_selectionpassed and expose live animation-panel planning as JSON automation.pp_app_core_brush_ui_testspassed, 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, andpano_cli_plan_brush_operation_rejects_empty_texturepassed 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, andpano_cli_plan_brush_texture_list_rejects_bad_sourcepassed 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, andpano_cli_plan_brush_stroke_control_rejects_bad_blendpassed and expose live stroke-panel slider/toggle/blend/reset planning as JSON automation.pp_app_core_grid_ui_testspassed, 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, andpano_cli_plan_grid_operation_rejects_bad_samplespassed and expose live grid/heightmap/lightmap planning as JSON automation.pp_app_core_canvas_tool_ui_testspassed, 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_testspassed, 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, andpano_cli_plan_canvas_hotkey_rejects_bad_countpassed 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, andpano_cli_plan_canvas_tool_rejects_unknownpassed 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, andpano_cli_plan_canvas_tool_state_rejects_unknownpassed and expose draw toolbar active-state refresh as JSON automation.pp_app_core_document_canvas_testspassed, 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, andpano_cli_plan_canvas_clear_rejects_bad_colorpassed and expose toolbar canvas clear planning as JSON automation.pp_app_core_document_import_testspassed, 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, andpano_cli_plan_image_import_rejects_invalid_dimensionspassed and expose FileImport route planning as JSON automation.
pp_app_core_file_menu_testspassed, 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, andpano_cli_plan_file_menu_rejects_unknownpassed and expose top-level File menu routing as JSON automation.pp_app_core_document_export_testspassed, 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, andpano_cli_plan_export_menu_rejects_unknownpassed and expose File menu export routing as JSON automation.pp_app_core_history_ui_testspassed, 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, andpano_cli_plan_history_operation_rejects_negative_countpassed and expose toolbar/canvas history planning as JSON automation.pp_app_core_quick_ui_testspassed, 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, andpano_cli_plan_quick_operation_rejects_bad_restorepassed and expose live quick-panel planning as JSON automation.pp_app_core_tools_menu_testspassed, 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, andpano_cli_plan_tools_panel_rejects_unknownpassed and expose live Tools menu/panel planning as JSON automation.pp_app_core_about_menu_testspassed, 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 theAboutMenuServicesexecutor 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, andpano_cli_plan_about_menu_rejects_unknownpassed and expose live About menu planning as JSON automation.pp_app_core_main_toolbar_testspassed, covering live toolbar/status direct dialog routing, undo/redo availability, clear-history availability, no-canvas clear blocking, negative history metric rejection, and dispatch through theMainToolbarServicesexecutor 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, andpano_cli_plan_main_toolbar_rejects_negative_countpassed and expose live toolbar/status planning as JSON automation.pp_app_core_document_sharing_testspassed, covering saved-path gating before platform share execution.pano_cli_plan_share_file_unsaved_smokeandpano_cli_plan_share_file_saved_smokepassed and expose app-core share decisions as JSON.pp_app_core_document_platform_io_testspassed, 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_smokeandpano_cli_plan_picked_path_selected_smokepassed and expose app-core picker selected-path decisions as JSON.pano_cli_plan_display_file_empty_smokeandpano_cli_plan_display_file_selected_smokepassed and expose app-core display-file decisions as JSON.pano_cli_plan_keyboard_visibility_hidden_smokeandpano_cli_plan_keyboard_visibility_visible_smokepassed and expose app-core virtual keyboard decisions as JSON.pano_cli_plan_cursor_visibility_hidden_smokeandpano_cli_plan_cursor_visibility_visible_smokepassed and expose app-core cursor visibility decisions as JSON.pano_cli_plan_clipboard_read_smoke,pano_cli_plan_clipboard_write_smoke, andpano_cli_plan_clipboard_write_empty_smokepassed and expose app-core clipboard decisions as JSON, including empty write text.pp_platform_api_testspassed, covering the SDK-freePlatformServicesinterface 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, exported-image publish dispatch, persistent storage flush dispatch, document browse-root dispatch, working-directory picker policy and display-path formatting dispatch, canvas input tip visibility and pressure remap dispatch, native UI/window state save dispatch, prepared-file writable target dispatch, prepared-file export-dialog policy dispatch, work-directory document export collection policy dispatch, network TLS verification policy dispatch, default network TLS policy coverage, PPBR export data-directory policy dispatch, SonarPen availability/startup dispatch, VR lifecycle dispatch, live asset/layout reload policy dispatch, diagnostic hook dispatch, per-frame platform hook dispatch, picker callback dispatch, and prepared-file save/download callback dispatch. The live Windows app now consumes this interface through an injectedWindowsPlatformServicesinstance isolated insrc/platform_windows/windows_platform_services.*; other platforms still use the legacy fallback adapter, now isolated insrc/platform_legacy/legacy_platform_services.*instead of being owned byapp_events.cpp.panopainter_validate_shaderspassed, validating 25 shader programs and 7 shader includes for stage markers and include graph integrity.pp_renderer_gl_capabilities_testspassed 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 byRTT::create. Sampler parameter validation covers wrap S/T/R plus min/mag filter ordering used by legacySampler::setandSampler::set_filter, plus the desktop border-color parameter name used bySampler::set_border. LegacyTextureCubeallocation/bind/delete andSamplercreate/configure/border/bind/unbind paths now execute through testedpp_renderer_gldispatch contracts, keeping cube-map and sampler resource lifecycle reachable without a live GL context. Shader attribute binding catalog validation covers the currentpos,uvs,uvs2,col, andnorbindings 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 byShader, preserves the legacy hash ids, and rejects empty, unnamed, null-name, mismatched-hash, and duplicate-name catalogs. LegacyShaderprogram use/delete, uniform writes, and attribute-location lookups now execute through testedpp_renderer_gldispatch contracts. Legacy shader source compilation, shader deletion, program attach/link, attribute rebinding, active-uniform count/enumeration, and uniform-location discovery also execute through testedpp_renderer_gldispatch contracts, leaving only thin GL adapter functions in the retainedShaderutility. LegacyShapemesh buffer/VAO creation, zero-byte dynamic-buffer creation, dynamic buffer uploads, indexed and non-indexed draws, and resource deletion now execute through testedpp_renderer_gldispatch contracts, leaving only thin GL adapter functions in the retained shape utility. LegacyFonttext 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_testscovers 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-headlessconfigured through the Visual Studio bundled vcpkg root, installed the manifest dependencies, built the headless component matrix, and passeddesktop-fast-vcpkg.pp_ui_corebuilt and tested against vcpkg tinyxml2 onwindows-msvc-vcpkg-headlessand against the vendored fallback onwindows-msvc-defaultandandroid-arm64.windows-clangcl-asanconfigures 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.exebuilt through CMake atout/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_apinow includes a headlessRecordingRenderDevicewith 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-renderexercises 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. Whenpp_renderer_glis available, it also emits anopenGlPlanJSON 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-clearmode 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-historyexercises pure document history apply/undo/redo behavior and emits JSON layer/frame/history state for agent automation.pano_cli simulate-document-editsexercises 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-importexercises embedded PNG decode throughpp_assetsandpp_documentface-payload attachment through JSON automation.pano_cli import-imageaccepts a PNG file path, decodes RGBA8 pixels throughpp_assets, attaches them to a purepp_documentface payload, and has checked-in decodable-PNG plus truncated-PNG rejection smoke tests.pano_cli export-imagewrites deterministic RGBA8 PNGs throughpp_assetsand has a save/import round-trip smoke test. Full legacy canvas export remains a futurepano_clitask.pano_cli save-projectexposes 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_projectexposes the underlying generated PPI writer for non-uniform layer metadata and frame-duration extraction work.pp_document::export_ppi_project_documentexposes pure document-to-PPI byte export through CTest coverage; legacy Canvas save integration remains tracked by DEBT-0010/DEBT-0013.pano_cli simulate-document-exportexposes the same export path through JSON automation for agents.pano_cli save-document-projectexposes file-writing document export automation for inspect/load round trips.pano_cli apply-stroke-scriptexposes 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.
NodeCanvaspanorama 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_rendererstroke composite planner for current layer and primary brush blend modes, while preserving legacy OpenGL compositing execution under DEBT-0036. NodeCanvaspanorama rendering now consumes the same testedpp_paint_renderercanvas blend-gate planner asCanvas::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
RenderDeviceFeaturesvalue instead of hand-built framebuffer-fetch/texture-copy flags. - Canvas draw-merge and
NodeCanvaspanorama 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_rendererstroke-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.cppno longer contains rawGL_*constants. - Windows desktop OpenGL context creation now consumes a tested
windows_wgl_core_context_3_3_config()catalog frompp_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,
LNK4099missing libyuv PDBs, andLNK4098runtime 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.