Own brush workers and thin preview/platform seams

This commit is contained in:
2026-06-16 06:54:14 +02:00
parent a76560e3df
commit 56c4743e66
11 changed files with 415 additions and 180 deletions

View File

@@ -1,7 +1,7 @@
# Modernization Debt Log # Modernization Debt Log
Status: live Status: live
Last updated: 2026-06-15 Last updated: 2026-06-16
Every shortcut, temporary adapter, retained vendored dependency, skipped Every shortcut, temporary adapter, retained vendored dependency, skipped
platform gate, compatibility shim, or incomplete automation path must be platform gate, compatibility shim, or incomplete automation path must be
@@ -18,6 +18,21 @@ agent or engineer to remove them without reconstructing context from chat.
## Reductions ## Reductions
- 2026-06-16: `DEBT-0048` was narrowed again. The retained ABR/PPBR import
bridge in `src/legacy_brush_package_import_services.cpp` no longer launches
detached worker threads; it now uses a service-owned `std::jthread` queue,
while retained preset ownership, progress UI, and storage mutation remain.
- 2026-06-16: `DEBT-0047` was narrowed again. The retained PPBR export bridge
in `src/legacy_brush_package_export_services.cpp` no longer launches a
detached desktop worker; it now uses a service-owned `std::jthread` queue
and returns dialog close plus success-message work to the UI thread, while
retained dialog data extraction, preset export ownership, and mobile/Web
completion remain.
- 2026-06-16: `DEBT-0036` was narrowed again. `NodeStrokePreview` final
composite plus preview-copy execution now routes through
`legacy_node_stroke_preview_execution_services.h` instead of living inline
in `draw_stroke_immediate()`; retained live-pass sequencing, GL resource
ownership, and node-owned draw execution remain.
- 2026-06-15: `DEBT-0042` was narrowed again. The retained Save Version dialog - 2026-06-15: `DEBT-0042` was narrowed again. The retained Save Version dialog
wiring in `src/app_dialogs.cpp` now routes through a focused helper instead wiring in `src/app_dialogs.cpp` now routes through a focused helper instead
of living inline in `App::dialog_save_ver()`; the remaining document-session of living inline in `App::dialog_save_ver()`; the remaining document-session
@@ -2128,8 +2143,8 @@ agent or engineer to remove them without reconstructing context from chat.
| DEBT-0044 | Open | Modernization | Timelapse and animation MP4 export execution dispatch now consumes pure `pp_app_core` through `App::dialog_timelapse_export`, `App::dialog_export_mp4`, `pano_cli plan-export-menu`, `pano_cli plan-export-target --kind name`, `pano_cli plan-export-message`, `pano_cli plan-export-report`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, and success/failure/license dialog metadata plus execution log labels now come from `pp_app_core`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, and owns mobile/Web save callbacks | Preserve current MP4/timelapse export behavior while video export moves toward app/document/renderer/video/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind animation-mp4`; `pano_cli plan-export-menu --kind timelapse`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -animation`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -timelapse`; `pano_cli plan-export-message --kind timelapse --destination success`; `pano_cli plan-export-report --kind animation-mp4 --message "video export path must not be empty"`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, and mobile/Web save callbacks are owned by injected app/document/renderer/video/platform/storage services with export dialogs acting only as UI adapters | | DEBT-0044 | Open | Modernization | Timelapse and animation MP4 export execution dispatch now consumes pure `pp_app_core` through `App::dialog_timelapse_export`, `App::dialog_export_mp4`, `pano_cli plan-export-menu`, `pano_cli plan-export-target --kind name`, `pano_cli plan-export-message`, `pano_cli plan-export-report`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, and success/failure/license dialog metadata plus execution log labels now come from `pp_app_core`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, and owns mobile/Web save callbacks | Preserve current MP4/timelapse export behavior while video export moves toward app/document/renderer/video/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind animation-mp4`; `pano_cli plan-export-menu --kind timelapse`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -animation`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -timelapse`; `pano_cli plan-export-message --kind timelapse --destination success`; `pano_cli plan-export-report --kind animation-mp4 --message "video export path must not be empty"`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, and mobile/Web save callbacks are owned by injected app/document/renderer/video/platform/storage services with export dialogs acting only as UI adapters |
| DEBT-0045 | Open | Modernization | Options-menu preference execution now consumes pure `pp_app_core` through UI scale, viewport scale, RTL direction, VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks plus `AppPreferenceServices` and `src/legacy_app_preference_services.*`; viewport-density and cursor-mode execution now delegate to `src/legacy_canvas_view_services.*`, and retained preference reads/writes for UI scale, UI-state/RTL, whats-new dialog state, viewport density, cursor mode, VR controllers, and auto-timelapse now route through `src/legacy_preference_storage.*` snapshots/helpers without direct `settings.h` includes or retained preference keys in the UI/dialog/canvas call sites, but the bridges still call legacy `App::set_ui_scale`, `App::set_ui_rtl`, `App::rec_start`, `App::rec_stop`, retained canvas view mutation, and retained `Settings` storage through that adapter; VR mode callbacks now call `App` VR wrappers that dispatch to `PlatformServices`, whose desktop runtime policy prefers OpenXR while the actual Windows OpenVR SDK bridge still lives in `WindowsPlatformServices` under DEBT-0061 | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_tests`; `pp_app_core_canvas_view_tests`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-canvas-view-density --density 1.5`; `pano_cli plan-canvas-view-cursor-mode --mode 3`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Preference persistence, UI/layout direction, viewport density, cursor mode, VR mode start/stop/failure handling, VR-controller state, and auto-timelapse recording side effects are owned by injected app/UI/platform/storage services with options-menu callbacks acting only as UI adapters | | DEBT-0045 | Open | Modernization | Options-menu preference execution now consumes pure `pp_app_core` through UI scale, viewport scale, RTL direction, VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks plus `AppPreferenceServices` and `src/legacy_app_preference_services.*`; viewport-density and cursor-mode execution now delegate to `src/legacy_canvas_view_services.*`, and retained preference reads/writes for UI scale, UI-state/RTL, whats-new dialog state, viewport density, cursor mode, VR controllers, and auto-timelapse now route through `src/legacy_preference_storage.*` snapshots/helpers without direct `settings.h` includes or retained preference keys in the UI/dialog/canvas call sites, but the bridges still call legacy `App::set_ui_scale`, `App::set_ui_rtl`, `App::rec_start`, `App::rec_stop`, retained canvas view mutation, and retained `Settings` storage through that adapter; VR mode callbacks now call `App` VR wrappers that dispatch to `PlatformServices`, whose desktop runtime policy prefers OpenXR while the actual Windows OpenVR SDK bridge still lives in `WindowsPlatformServices` under DEBT-0061 | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_tests`; `pp_app_core_canvas_view_tests`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-canvas-view-density --density 1.5`; `pano_cli plan-canvas-view-cursor-mode --mode 3`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Preference persistence, UI/layout direction, viewport density, cursor mode, VR mode start/stop/failure handling, VR-controller state, and auto-timelapse recording side effects are owned by injected app/UI/platform/storage services with options-menu callbacks acting only as UI adapters |
| DEBT-0046 | Open | Modernization | Startup preference/runtime execution and startup resource sequencing now consume pure `pp_app_core` through `App::init`, `pano_cli plan-app-startup`, `pano_cli plan-app-startup-resources`, `AppStartupServices`, `AppStartupResourceServices`, and `src/legacy_app_startup_services.*`, and startup preference load/read/write now routes through `src/legacy_preference_storage.*` with retained startup keys hidden behind `LegacyStartupPreferenceSnapshot`, but the bridge still calls legacy `Settings` storage through that adapter, `App::rec_start`, app VR-controller state mutation, message-box license warning execution, shader loading, asset initialization, layout creation, title updates, and UI render-target creation directly | Preserve current startup behavior while app startup moves toward app/preferences/storage/recording/UI/renderer services | `pp_app_core_app_startup_tests`; `pano_cli plan-app-startup --run-counter 7 --vr-controllers-disabled --license-invalid`; `pano_cli plan-app-startup --run-counter -1`; `pano_cli plan-app-startup-resources --width 1280 --height 720`; `pano_cli plan-app-startup-resources --bad-size`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Startup preference persistence, auto-timelapse startup, stored VR-controller state, license validation/warning, startup resource initialization, title updates, and UI render-target allocation are owned by injected app/preferences/storage/recording/UI/renderer services with `App::init` acting only as orchestration | | DEBT-0046 | Open | Modernization | Startup preference/runtime execution and startup resource sequencing now consume pure `pp_app_core` through `App::init`, `pano_cli plan-app-startup`, `pano_cli plan-app-startup-resources`, `AppStartupServices`, `AppStartupResourceServices`, and `src/legacy_app_startup_services.*`, and startup preference load/read/write now routes through `src/legacy_preference_storage.*` with retained startup keys hidden behind `LegacyStartupPreferenceSnapshot`, but the bridge still calls legacy `Settings` storage through that adapter, `App::rec_start`, app VR-controller state mutation, message-box license warning execution, shader loading, asset initialization, layout creation, title updates, and UI render-target creation directly | Preserve current startup behavior while app startup moves toward app/preferences/storage/recording/UI/renderer services | `pp_app_core_app_startup_tests`; `pano_cli plan-app-startup --run-counter 7 --vr-controllers-disabled --license-invalid`; `pano_cli plan-app-startup --run-counter -1`; `pano_cli plan-app-startup-resources --width 1280 --height 720`; `pano_cli plan-app-startup-resources --bad-size`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Startup preference persistence, auto-timelapse startup, stored VR-controller state, license validation/warning, startup resource initialization, title updates, and UI render-target allocation are owned by injected app/preferences/storage/recording/UI/renderer services with `App::init` acting only as orchestration |
| DEBT-0047 | Open | Modernization | PPBR brush package export request validation, success-dialog metadata, and execution dispatch now consume pure `pp_app_core` through `App::dialog_ppbr_export`, `pano_cli plan-brush-package-export`, `BrushPackageExportServices`, and `src/legacy_brush_package_export_services.*`; PPBR header/path planning now consumes `pp_assets::brush_package`, and the macOS data-directory override now routes through `PlatformServices`, but the bridge still reads `NodeDialogExportPPBR`, carries the legacy `Image` header object outside the pure request, converts to `NodePanelBrushPreset::PPBRInfo`, calls `NodePanelBrushPreset::export_ppbr`, owns desktop worker-thread dispatch, dialog destruction, and mobile/Web completion directly | Preserve current PPBR export behavior while brush assets, PPBR serialization, picker completion, and UI lifetime move toward asset/storage/UI/platform services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_export_tests`; `pp_platform_api_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --author Artist --dest-path D:/Paint/BrushPreviews --export-data --header-image`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `pano_cli plan-brush-package-export`; `pano_cli plan-brush-package-export --path clouds`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --dest-path D:/Paint/BrushPreviews --no-export-data`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | PPBR metadata collection, header-image ownership, serialization, picker-selected path execution, desktop threading, dialog lifetime, and mobile/Web completion are owned by injected brush asset/storage/UI/platform services with `App::dialog_ppbr_export` acting only as a UI adapter | | DEBT-0047 | Open | Modernization | PPBR brush package export request validation, success-dialog metadata, and execution dispatch now consume pure `pp_app_core` through `App::dialog_ppbr_export`, `pano_cli plan-brush-package-export`, `BrushPackageExportServices`, and `src/legacy_brush_package_export_services.*`; PPBR header/path planning now consumes `pp_assets::brush_package`, the macOS data-directory override now routes through `PlatformServices`, and the desktop async path now uses a service-owned `std::jthread` worker with UI-thread dialog close/message handoff, but the bridge still reads `NodeDialogExportPPBR`, carries the legacy `Image` header object outside the pure request, converts to `NodePanelBrushPreset::PPBRInfo`, calls `NodePanelBrushPreset::export_ppbr`, and handles mobile/Web completion directly | Preserve current PPBR export behavior while brush assets, PPBR serialization, picker completion, and UI lifetime move toward asset/storage/UI/platform services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_export_tests`; `pp_platform_api_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --author Artist --dest-path D:/Paint/BrushPreviews --export-data --header-image`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `pano_cli plan-brush-package-export`; `pano_cli plan-brush-package-export --path clouds`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --dest-path D:/Paint/BrushPreviews --no-export-data`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | PPBR metadata collection, header-image ownership, serialization, picker-selected path execution, desktop threading, dialog lifetime, and mobile/Web completion are owned by injected brush asset/storage/UI/platform services with `App::dialog_ppbr_export` acting only as a UI adapter |
| DEBT-0048 | Open | Modernization | ABR/PPBR brush package import execution now consumes pure `pp_app_core` through document-open confirmation callbacks, `pano_cli plan-brush-package-import`, `BrushPackageImportServices`, and `src/legacy_brush_package_import_services.*`; imported brush tip/pattern target paths now consume `pp_assets::brush_package`, but the bridge still launches detached legacy `NodePanelBrushPreset::import_abr`/`import_ppbr` worker threads and depends on the legacy preset panel as the importer/storage owner | Preserve current brush import behavior while brush package parsing, preset storage, progress/error reporting, and UI refresh move toward asset/paint/UI services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_import_tests`; `pano_cli plan-brush-package-import --kind ppbr --path D:/Paint/Brushes/clouds.ppbr`; `pano_cli plan-brush-package-import --kind abr --path D:/Paint/Brushes/clouds.abr`; `pano_cli plan-brush-package-import --kind ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | ABR/PPBR parsing, preset creation/storage, import threading/progress, duplicate asset policy, and UI refresh are owned by injected brush asset/paint/UI services with document-open callbacks only confirming user intent | | DEBT-0048 | Open | Modernization | ABR/PPBR brush package import execution now consumes pure `pp_app_core` through document-open confirmation callbacks, `pano_cli plan-brush-package-import`, `BrushPackageImportServices`, and `src/legacy_brush_package_import_services.*`; imported brush tip/pattern target paths now consume `pp_assets::brush_package`, and the retained bridge now uses a service-owned `std::jthread` worker with UI-thread completion handoff instead of detached `NodePanelBrushPreset::import_abr`/`import_ppbr` launches, but it still depends on the legacy preset panel as the importer/storage owner | Preserve current brush import behavior while brush package parsing, preset storage, progress/error reporting, and UI refresh move toward asset/paint/UI services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_import_tests`; `pano_cli plan-brush-package-import --kind ppbr --path D:/Paint/Brushes/clouds.ppbr`; `pano_cli plan-brush-package-import --kind abr --path D:/Paint/Brushes/clouds.abr`; `pano_cli plan-brush-package-import --kind ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | ABR/PPBR parsing, preset creation/storage, import threading/progress, duplicate asset policy, and UI refresh are owned by injected brush asset/paint/UI services with document-open callbacks only confirming user intent |
| DEBT-0049 | Open | Modernization | `pp_assets::validate_ppbr_header` intentionally preserves the legacy PPBR version check from `NodePanelBrushPreset::import_ppbr`, which accepts files when either major is `0` or minor is `1` instead of requiring exactly version `0.1` | Avoid rejecting existing brush packages before compatibility fixtures prove the stricter rule is safe | `pp_assets_brush_package_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Add PPBR compatibility fixtures for accepted/rejected historical package versions, then require canonical `0.1` or an explicit supported-version matrix and update live import accordingly | | DEBT-0049 | Open | Modernization | `pp_assets::validate_ppbr_header` intentionally preserves the legacy PPBR version check from `NodePanelBrushPreset::import_ppbr`, which accepts files when either major is `0` or minor is `1` instead of requiring exactly version `0.1` | Avoid rejecting existing brush packages before compatibility fixtures prove the stricter rule is safe | `pp_assets_brush_package_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Add PPBR compatibility fixtures for accepted/rejected historical package versions, then require canonical `0.1` or an explicit supported-version matrix and update live import accordingly |
| DEBT-0050 | Open | Modernization | iOS exported-image photo-library publishing and WebGL persistent-storage flushing now dispatch through platform service boundaries; the iOS/Web policy decision lives in tested `pp_platform_api::platform_policy`, and WebGL flushing now goes through injectable `pp::platform::WebPlatformServices`, but non-Windows execution still lives in retained fallback adapters and forwards to retained `save_image_library`/`webgl_sync` bridges | Preserve current iOS/Web export and save behavior while the Apple/Web platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; platform package smoke once Apple/Web root builds exist | Exported-image publishing and persistent-storage flushing are owned by injected Apple/Web `pp_platform_*` services with no legacy adapter branch | | DEBT-0050 | Open | Modernization | iOS exported-image photo-library publishing and WebGL persistent-storage flushing now dispatch through platform service boundaries; the iOS/Web policy decision lives in tested `pp_platform_api::platform_policy`, and WebGL flushing now goes through injectable `pp::platform::WebPlatformServices`, but non-Windows execution still lives in retained fallback adapters and forwards to retained `save_image_library`/`webgl_sync` bridges | Preserve current iOS/Web export and save behavior while the Apple/Web platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; platform package smoke once Apple/Web root builds exist | Exported-image publishing and persistent-storage flushing are owned by injected Apple/Web `pp_platform_*` services with no legacy adapter branch |
| DEBT-0051 | Open | Modernization | Document browser search roots, Apple file/image/save/directory picker dispatch, Browse dialog working-directory picker visibility/path formatting, iOS Inbox roots, macOS empty-selection filtering, and macOS display-path formatting now dispatch through the tested `src/platform_apple/apple_platform_services.*` boundary consumed by `PlatformServices`; retained `src/platform_legacy/legacy_platform_services.*` still creates the Apple bridge and owns other non-Apple fallback behavior | Preserve current iOS document import/browse and desktop browse picker behavior while Apple platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Apple package smoke once root Apple builds exist | Document browse roots and browse-directory picker/display formatting are owned by injected Apple and desktop `pp_platform_*` services with no legacy adapter branch | | DEBT-0051 | Open | Modernization | Document browser search roots, Apple file/image/save/directory picker dispatch, Browse dialog working-directory picker visibility/path formatting, iOS Inbox roots, macOS empty-selection filtering, and macOS display-path formatting now dispatch through the tested `src/platform_apple/apple_platform_services.*` boundary consumed by `PlatformServices`; retained `src/platform_legacy/legacy_platform_services.*` still creates the Apple bridge and owns other non-Apple fallback behavior | Preserve current iOS document import/browse and desktop browse picker behavior while Apple platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Apple package smoke once root Apple builds exist | Document browse roots and browse-directory picker/display formatting are owned by injected Apple and desktop `pp_platform_*` services with no legacy adapter branch |

View File

@@ -95,7 +95,9 @@ Current architecture mismatches that must be treated as real blockers:
- `pp_platform_api` still compiles Apple implementation files instead of only - `pp_platform_api` still compiles Apple implementation files instead of only
platform-neutral policy and interface code. platform-neutral policy and interface code.
- `src/platform_apple/apple_platform_services.cpp` and - `src/platform_apple/apple_platform_services.cpp` and
`src/platform_linux/linux_platform_services.cpp` still reach `App::I`. parts of the concrete platform layer still reach `App::I`; Linux FPS title
reporting now uses an injected callback, but Apple singleton reach and other
platform/app coupling remain.
- `src/platform_legacy/legacy_platform_services.*` is still part of the live - `src/platform_legacy/legacy_platform_services.*` is still part of the live
app shell. app shell.
- `pp_panopainter_ui` still depends on `pp_legacy_app`. - `pp_panopainter_ui` still depends on `pp_legacy_app`.
@@ -258,7 +260,8 @@ Required outcomes:
- document workflow bridges become thin adapters over `pp_app_core` - document workflow bridges become thin adapters over `pp_app_core`
- cloud transfer and cloud browser ownership move out of retained UI nodes - cloud transfer and cloud browser ownership move out of retained UI nodes
- brush package import/export ownership moves out of retained panel code - brush package import/export ownership moves out of retained panel code and
no longer depends on detached worker launch sites
### 7. Only Then Resume Future Backend Work ### 7. Only Then Resume Future Backend Work

View File

@@ -42,12 +42,13 @@ Completed, blocked, and superseded task history moved to
`src/node_canvas.cpp`, `src/app.cpp`, and `src/app_dialogs.cpp`. `src/node_canvas.cpp`, `src/app.cpp`, and `src/app_dialogs.cpp`.
- The platform boundary is not finished: - The platform boundary is not finished:
- `pp_platform_api` still compiles Apple implementation files - `pp_platform_api` still compiles Apple implementation files
- Apple and Linux platform services still reach `App::I` - Apple platform services still reach `App::I`
- Linux FPS title reporting now uses an injected callback, but broader
platform-to-app singleton reach is still open
- `platform_legacy` is still part of the live app shell - `platform_legacy` is still part of the live app shell
- The app runtime boundary is not finished: - The app runtime boundary is not finished:
- render/UI queues are static `App` state - render/UI queues are static `App` state
- detached workers still launch from canvas, cloud, brush, grid, preview, and - detached workers still launch from canvas, grid, preview, and event code
event code
- thread-affinity rules are enforced by convention and asserts instead of - thread-affinity rules are enforced by convention and asserts instead of
explicit runtime contracts explicit runtime contracts
- The UI ownership boundary is not finished: - The UI ownership boundary is not finished:
@@ -114,12 +115,17 @@ Mini-model packet:
#### ARC-RND-002 - Isolate Preview And Canvas View Render Execution #### ARC-RND-002 - Isolate Preview And Canvas View Render Execution
Status: Ready Status: In Progress
Why now: Why now:
`src/node_stroke_preview.cpp` and `src/node_canvas.cpp` still own a large amount `src/node_stroke_preview.cpp` and `src/node_canvas.cpp` still own a large amount
of live preview/canvas render sequencing around the renderer boundary. of live preview/canvas render sequencing around the renderer boundary.
Current slice:
- `NodeStrokePreview` final composite plus preview-texture copy now route
through `legacy_node_stroke_preview_execution_services.h`, but the preview
node still owns most live-pass and retained GL resource execution.
Write scope: Write scope:
- `src/node_stroke_preview.cpp` - `src/node_stroke_preview.cpp`
- `src/node_canvas.cpp` - `src/node_canvas.cpp`
@@ -330,13 +336,20 @@ Mini-model packet:
#### ARC-APP-005 - Replace Detached App Workers With Joinable Or Service-Owned Work #### ARC-APP-005 - Replace Detached App Workers With Joinable Or Service-Owned Work
Status: Ready Status: In Progress
Why now: Why now:
Canvas imports/exports/saves, cloud transfer, brush import/export, grid Canvas imports/exports/saves, cloud transfer, brush import/export, grid
lightmap work, stroke preview, and event persistence still launch detached lightmap work, stroke preview, and event persistence still launch detached
threads. That is not a safe modernization foundation. threads. That is not a safe modernization foundation.
Current slice:
- app-owned render/UI runtime queues and cloud worker ownership are already
moving behind owned runtime/service objects
- brush package import/export now use service-owned `std::jthread` workers and
UI-thread completion handoff
- canvas, grid, preview, and event-side detached work is still open
Write scope: Write scope:
- `src/canvas.cpp` - `src/canvas.cpp`
- `src/app_cloud.cpp` - `src/app_cloud.cpp`
@@ -525,12 +538,18 @@ Mini-model packet:
#### ARC-PLT-002 - Remove `App::I` Reach From Apple And Linux Services #### ARC-PLT-002 - Remove `App::I` Reach From Apple And Linux Services
Status: Ready Status: In Progress
Why now: Why now:
The current Apple and Linux service files still call into the app singleton, The current Apple and Linux service files still call into the app singleton,
which means the platform layer is not a platform layer yet. which means the platform layer is not a platform layer yet.
Current slice:
- Linux FPS title updates now route through an injected callback installed from
`App::set_platform_services()`
- Apple singleton reach and the remaining platform callback surface are still
open
Write scope: Write scope:
- `src/platform_apple/*` - `src/platform_apple/*`
- `src/platform_linux/*` - `src/platform_linux/*`

View File

@@ -5,6 +5,10 @@
#include "app_core/document_platform_io.h" #include "app_core/document_platform_io.h"
#include "app_core/document_sharing.h" #include "app_core/document_sharing.h"
#include "platform_api/platform_services.h" #include "platform_api/platform_services.h"
#ifdef __LINUX__
#include <GLFW/glfw3.h>
#include "platform_linux/linux_platform_services.h"
#endif
#include "platform_legacy/legacy_platform_services.h" #include "platform_legacy/legacy_platform_services.h"
#include "renderer_gl/opengl_capabilities.h" #include "renderer_gl/opengl_capabilities.h"
@@ -51,6 +55,21 @@ namespace {
void App::set_platform_services(pp::platform::PlatformServices* services) noexcept void App::set_platform_services(pp::platform::PlatformServices* services) noexcept
{ {
platform_services_ = services; platform_services_ = services;
#ifdef __LINUX__
if (services)
{
pp::platform::linux::set_fps_title_callback([this](std::string title) {
if (!glfw_window)
return;
glfwSetWindowTitle(glfw_window, title.c_str());
});
}
else
{
pp::platform::linux::set_fps_title_callback({});
}
#endif
} }
pp::platform::PlatformServices* App::platform_services() const noexcept pp::platform::PlatformServices* App::platform_services() const noexcept

View File

@@ -7,12 +7,90 @@
#include "node_dialog_export_ppbr.h" #include "node_dialog_export_ppbr.h"
#include "node_panel_brush.h" #include "node_panel_brush.h"
#include <condition_variable>
#include <deque>
#include <functional>
#include <string> #include <string>
#include <mutex>
#include <stop_token>
#include <thread> #include <thread>
namespace pp::panopainter { namespace pp::panopainter {
namespace { namespace {
class LegacyBrushPackageWorker final {
public:
LegacyBrushPackageWorker()
: worker_([this](std::stop_token stop_token) {
run(stop_token);
})
{
}
~LegacyBrushPackageWorker()
{
shutdown();
}
void post(std::function<void()> task)
{
{
std::lock_guard<std::mutex> lock(mutex_);
if (stopping_)
return;
tasks_.push_back(std::move(task));
}
cv_.notify_one();
}
private:
void shutdown()
{
{
std::lock_guard<std::mutex> lock(mutex_);
stopping_ = true;
}
cv_.notify_all();
}
void run(std::stop_token stop_token)
{
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [&] {
return stopping_ || stop_token.stop_requested() || !tasks_.empty();
});
if ((stopping_ || stop_token.stop_requested()) && tasks_.empty())
break;
task = std::move(tasks_.front());
tasks_.pop_front();
}
if (task) {
try {
task();
} catch (...) {
LOG("brush package export worker task failed");
}
}
}
}
std::mutex mutex_;
std::condition_variable cv_;
std::deque<std::function<void()>> tasks_;
bool stopping_ = false;
std::jthread worker_;
};
LegacyBrushPackageWorker& brush_package_worker()
{
static LegacyBrushPackageWorker worker;
return worker;
}
NodePanelBrushPreset::PPBRInfo to_legacy_ppbr_info( NodePanelBrushPreset::PPBRInfo to_legacy_ppbr_info(
const pp::app::BrushPackageExportRequest& request, const pp::app::BrushPackageExportRequest& request,
const NodeDialogExportPPBR& dialog) const NodeDialogExportPPBR& dialog)
@@ -44,20 +122,29 @@ public:
{ {
const auto path_string = std::string(path); const auto path_string = std::string(path);
const auto info = to_legacy_ppbr_info(request, dialog_); const auto info = to_legacy_ppbr_info(request, dialog_);
auto presets = app_.presets;
if (mode_ == LegacyBrushPackageExportMode::desktop_async_close_and_message) { if (mode_ == LegacyBrushPackageExportMode::desktop_async_close_and_message) {
auto* app = &app_; auto* app = &app_;
auto* dialog = &dialog_; auto dialog = std::static_pointer_cast<NodeDialogExportPPBR>(dialog_.shared_from_this());
std::thread([app, dialog, path_string, info] { brush_package_worker().post([app, presets, dialog, path_string, info] {
BT_SetTerminate(); BT_SetTerminate();
app->presets->export_ppbr(path_string, info); if (presets) {
pp::panopainter::close_legacy_dialog_node(*dialog); presets->export_ppbr(path_string, info);
}
const auto plan = pp::app::plan_brush_package_export_success_dialog(path_string); const auto plan = pp::app::plan_brush_package_export_success_dialog(path_string);
app->message_box(plan.title, plan.message, plan.show_cancel); app->ui_task([dialog, plan] {
}).detach(); if (dialog) {
pp::panopainter::close_legacy_dialog_node(*dialog);
}
App::I->message_box(plan.title, plan.message, plan.show_cancel);
});
});
return; return;
} }
app_.presets->export_ppbr(path_string, info); if (presets) {
presets->export_ppbr(path_string, info);
}
} }
private: private:

View File

@@ -5,12 +5,90 @@
#include "app.h" #include "app.h"
#include "node_panel_brush.h" #include "node_panel_brush.h"
#include <condition_variable>
#include <deque>
#include <functional>
#include <string> #include <string>
#include <mutex>
#include <stop_token>
#include <thread> #include <thread>
namespace pp::panopainter { namespace pp::panopainter {
namespace { namespace {
class LegacyBrushPackageWorker final {
public:
LegacyBrushPackageWorker()
: worker_([this](std::stop_token stop_token) {
run(stop_token);
})
{
}
~LegacyBrushPackageWorker()
{
shutdown();
}
void post(std::function<void()> task)
{
{
std::lock_guard<std::mutex> lock(mutex_);
if (stopping_)
return;
tasks_.push_back(std::move(task));
}
cv_.notify_one();
}
private:
void shutdown()
{
{
std::lock_guard<std::mutex> lock(mutex_);
stopping_ = true;
}
cv_.notify_all();
}
void run(std::stop_token stop_token)
{
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [&] {
return stopping_ || stop_token.stop_requested() || !tasks_.empty();
});
if ((stopping_ || stop_token.stop_requested()) && tasks_.empty())
break;
task = std::move(tasks_.front());
tasks_.pop_front();
}
if (task) {
try {
task();
} catch (...) {
LOG("brush package import worker task failed");
}
}
}
}
std::mutex mutex_;
std::condition_variable cv_;
std::deque<std::function<void()>> tasks_;
bool stopping_ = false;
std::jthread worker_;
};
LegacyBrushPackageWorker& brush_package_worker()
{
static LegacyBrushPackageWorker worker;
return worker;
}
class LegacyBrushPackageImportServices final : public pp::app::BrushPackageImportServices { class LegacyBrushPackageImportServices final : public pp::app::BrushPackageImportServices {
public: public:
explicit LegacyBrushPackageImportServices(App& app) noexcept explicit LegacyBrushPackageImportServices(App& app) noexcept
@@ -22,12 +100,18 @@ public:
{ {
auto presets = app_.presets; auto presets = app_.presets;
const auto path_string = std::string(path); const auto path_string = std::string(path);
brush_package_worker().post([presets, kind, path_string] {
BT_SetTerminate();
if (!presets) {
return;
}
if (kind == pp::app::BrushPackageImportKind::abr) { if (kind == pp::app::BrushPackageImportKind::abr) {
std::thread(&NodePanelBrushPreset::import_abr, presets, path_string).detach(); presets->import_abr(path_string);
return; return;
} }
std::thread(&NodePanelBrushPreset::import_ppbr, presets, path_string).detach(); presets->import_ppbr(path_string);
});
} }
private: private:

View File

@@ -4,14 +4,18 @@
#include "../libs/glm/glm/ext/matrix_clip_space.hpp" #include "../libs/glm/glm/ext/matrix_clip_space.hpp"
#include "legacy_canvas_stroke_shader_services.h" #include "legacy_canvas_stroke_shader_services.h"
#include "legacy_canvas_stroke_composite_services.h"
#include "legacy_canvas_stroke_preview_services.h" #include "legacy_canvas_stroke_preview_services.h"
#include "legacy_canvas_stroke_services.h" #include "legacy_canvas_stroke_services.h"
#include "legacy_ui_gl_dispatch.h"
#include "paint_renderer/compositor.h" #include "paint_renderer/compositor.h"
#include "texture.h" #include "texture.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstdint> #include <cstdint>
#include <functional>
#include <utility>
#include <vector> #include <vector>
namespace pp::panopainter { namespace pp::panopainter {
@@ -241,6 +245,91 @@ template <typename Frame>
return true; return true;
} }
[[nodiscard]] inline bool execute_legacy_node_stroke_preview_final_composite(
glm::vec2 size,
glm::vec2 pattern_scale,
const Brush& brush,
const pp::paint_renderer::CanvasStrokeCompositePassPlan& composite_pass,
Texture2D& background_texture,
Texture2D& stroke_texture,
Texture2D& dual_texture,
Texture2D& preview_texture,
Sampler& linear_sampler,
Sampler& repeat_sampler,
std::function<void()> bind_pattern_texture,
std::function<void()> draw_composite)
{
if (!bind_pattern_texture || !draw_composite) {
return false;
}
pp::panopainter::execute_legacy_stroke_preview_final_composite(
[&] {
pp::panopainter::setup_legacy_stroke_composite_shader(
pp::panopainter::LegacyStrokeCompositeUniforms {
.resolution = size,
.pattern = {
.scale = pattern_scale,
.invert = static_cast<float>(brush.m_pattern_invert),
.brightness = brush.m_pattern_brightness,
.contrast = brush.m_pattern_contrast,
.depth = brush.m_pattern_depth,
.blend_mode = composite_pass.pattern_blend_mode,
.offset = glm::vec2(brush.m_pattern_rand_offset ? 0.5f : 0.0f),
},
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
.layer_alpha = 1.0f,
.alpha_lock = false,
.mask_enabled = false,
.use_fragcoord = false,
.blend_mode = brush.m_blend_mode,
.use_dual = composite_pass.use_dual,
.dual_blend_mode = composite_pass.dual_blend_mode,
.dual_alpha = composite_pass.dual_alpha,
.use_pattern = composite_pass.use_pattern,
});
},
[&] {
linear_sampler.bind(0U);
linear_sampler.bind(1U);
linear_sampler.bind(2U);
linear_sampler.bind(3U);
repeat_sampler.bind(4U);
},
[&] {
pp::legacy::ui_gl::activate_texture_unit(0U, "NodeStrokePreview");
background_texture.bind();
pp::legacy::ui_gl::activate_texture_unit(1U, "NodeStrokePreview");
stroke_texture.bind();
pp::legacy::ui_gl::activate_texture_unit(3U, "NodeStrokePreview");
dual_texture.bind();
pp::legacy::ui_gl::activate_texture_unit(4U, "NodeStrokePreview");
bind_pattern_texture();
},
[&] {
draw_composite();
});
const auto copy_status = pp::paint_renderer::copy_stroke_preview_result_to_texture(
[&] {
preview_texture.bind();
},
[](
int src_x,
int src_y,
int dst_x,
int dst_y,
int width,
int height) {
copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height);
},
pp::paint_renderer::StrokePreviewCopySize {
.width = static_cast<int>(size.x),
.height = static_cast<int>(size.y),
});
return copy_status.ok();
}
struct LegacyNodeStrokePreviewPassOrchestrationRequest { struct LegacyNodeStrokePreviewPassOrchestrationRequest {
pp::renderer::RenderDeviceFeatures features {}; pp::renderer::RenderDeviceFeatures features {};
glm::vec2 preview_size {}; glm::vec2 preview_size {};

View File

@@ -4,6 +4,7 @@
#include "assets/brush_package.h" #include "assets/brush_package.h"
#include "app_core/brush_ui.h" #include "app_core/brush_ui.h"
#include "legacy_brush_ui_services.h" #include "legacy_brush_ui_services.h"
#include "legacy_brush_package_import_services.h"
#include "legacy_ui_overlay_services.h" #include "legacy_ui_overlay_services.h"
#include "asset.h" #include "asset.h"
#include "texture.h" #include "texture.h"
@@ -600,13 +601,8 @@ void NodePanelBrushPreset::init()
switch (index) switch (index)
{ {
case 0: // import file case 0: // import file
App::I->pick_file({"abr", "ppbr"}, [this] (std::string path) { App::I->pick_file({"abr", "ppbr"}, [presets = std::static_pointer_cast<NodePanelBrushPreset>(shared_from_this())] (std::string path) {
std::thread([this, path] { presets->import_brush(path);
BT_SetTerminate();
import_brush(path);
for (auto p : s_panels)
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
}).detach();
}); });
break; break;
case 1: // export file case 1: // export file
@@ -637,13 +633,8 @@ void NodePanelBrushPreset::init()
}; };
m_btn_import = find<NodeButton>("import"); m_btn_import = find<NodeButton>("import");
m_btn_import->on_click = [this] (Node*) { m_btn_import->on_click = [this] (Node*) {
App::I->pick_file({ "abr", "ppbr" }, [this](std::string path) { App::I->pick_file({ "abr", "ppbr" }, [presets = std::static_pointer_cast<NodePanelBrushPreset>(shared_from_this())](std::string path) {
std::thread([this, path] { presets->import_brush(path);
BT_SetTerminate();
import_brush(path);
for (auto p : s_panels)
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
}).detach();
}); });
}; };
m_btn_download = find<NodeButton>("download"); m_btn_download = find<NodeButton>("download");
@@ -1050,6 +1041,10 @@ bool NodePanelBrushPreset::import_ppbr(const std::string& path)
// brush settings // brush settings
auto brushes_count = sr.ru32(); auto brushes_count = sr.ru32();
std::vector<std::shared_ptr<Brush>> brushes_to_add;
if (brushes_count > 0) {
brushes_to_add.reserve(static_cast<std::size_t>(brushes_count));
}
for (int i = 0; i < brushes_count; i++) for (int i = 0; i < brushes_count; i++)
{ {
auto b = std::make_shared<Brush>(); auto b = std::make_shared<Brush>();
@@ -1058,16 +1053,23 @@ bool NodePanelBrushPreset::import_ppbr(const std::string& path)
LOG("import_ppbr brush name %s", b->m_name.c_str()); LOG("import_ppbr brush name %s", b->m_name.c_str());
if (b->valid()) if (b->valid())
{ {
for (auto p : s_panels) brushes_to_add.push_back(b);
p->add_brush(b);
} }
pb->increment(); pb->increment();
} }
save(); auto owner = std::static_pointer_cast<NodePanelBrushPreset>(shared_from_this());
App::I->stroke->m_brush_popup->reload(); App::I->ui_task([owner, brushes_to_add = std::move(brushes_to_add), pb]() mutable {
for (const auto& b : brushes_to_add)
{
for (auto p : s_panels)
p->add_brush(b);
}
owner->save();
App::I->stroke->m_brush_popup->reload();
pp::panopainter::close_legacy_dialog_node(*pb); pp::panopainter::close_legacy_dialog_node(*pb);
});
return true; return true;
} }
@@ -1148,7 +1150,8 @@ bool NodePanelBrushPreset::import_abr(const std::string& path)
}); });
auto brushes = abr.compute_brushes(App::I->data_path); auto brushes = abr.compute_brushes(App::I->data_path);
App::I->ui_task([&]{ auto owner = std::static_pointer_cast<NodePanelBrushPreset>(shared_from_this());
App::I->ui_task([owner, brushes = std::move(brushes), pb]() mutable {
for (const auto& b : brushes) for (const auto& b : brushes)
{ {
if (b->valid()) if (b->valid())
@@ -1159,11 +1162,10 @@ bool NodePanelBrushPreset::import_abr(const std::string& path)
} }
pb->increment(); pb->increment();
} }
}); owner->save();
save();
App::I->stroke->m_brush_popup->reload(); App::I->stroke->m_brush_popup->reload();
pp::panopainter::close_legacy_dialog_node(*pb); pp::panopainter::close_legacy_dialog_node(*pb);
});
return true; return true;
} }
@@ -1178,7 +1180,15 @@ bool NodePanelBrushPreset::import_brush(const std::string& path)
std::string name = m[2].str(); std::string name = m[2].str();
std::string ext = m[3].str(); std::string ext = m[3].str();
return ext == "ppbr" ? import_ppbr(path) : import_abr(path); const auto kind = ext == "ppbr"
? pp::app::BrushPackageImportKind::ppbr
: pp::app::BrushPackageImportKind::abr;
const auto status = pp::panopainter::execute_legacy_brush_package_import(*App::I, kind, path);
if (!status.ok()) {
LOG("Brush package import request failed: %s", status.message);
return false;
}
return true;
} }
void NodePanelBrushPreset::clear_brushes() void NodePanelBrushPreset::clear_brushes()

View File

@@ -82,43 +82,6 @@ constexpr std::uint32_t kMixer = 3U;
constexpr std::uint32_t kReservedLinear = 4U; constexpr std::uint32_t kReservedLinear = 4U;
} }
struct StrokePreviewCompositePassInputs {
glm::vec2 resolution;
glm::vec2 pattern_scale;
const Brush& brush;
const pp::paint_renderer::CanvasStrokeCompositePassPlan& composite_pass;
Texture2D& background_texture;
Texture2D& stroke_texture;
Texture2D& dual_texture;
Sampler& linear_sampler;
Sampler& repeat_sampler;
std::function<void()> draw_composite;
StrokePreviewCompositePassInputs(
glm::vec2 resolution_in,
glm::vec2 pattern_scale_in,
const Brush& brush_in,
const pp::paint_renderer::CanvasStrokeCompositePassPlan& composite_pass_in,
Texture2D& background_texture_in,
Texture2D& stroke_texture_in,
Texture2D& dual_texture_in,
Sampler& linear_sampler_in,
Sampler& repeat_sampler_in,
std::function<void()> draw_composite_in)
: resolution(resolution_in)
, pattern_scale(pattern_scale_in)
, brush(brush_in)
, composite_pass(composite_pass_in)
, background_texture(background_texture_in)
, stroke_texture(stroke_texture_in)
, dual_texture(dual_texture_in)
, linear_sampler(linear_sampler_in)
, repeat_sampler(repeat_sampler_in)
, draw_composite(std::move(draw_composite_in))
{
}
};
pp::panopainter::LegacyNodeStrokePreviewMixPassRequest make_stroke_preview_mix_pass_request( pp::panopainter::LegacyNodeStrokePreviewMixPassRequest make_stroke_preview_mix_pass_request(
const Brush& brush, const Brush& brush,
glm::vec2 resolution) noexcept glm::vec2 resolution) noexcept
@@ -235,64 +198,6 @@ pp::panopainter::LegacyCanvasStrokeMixPassRequest make_stroke_preview_mix_pass_e
}); });
} }
void copy_stroke_preview_result_to_texture(Texture2D& texture, glm::vec2 size);
void execute_stroke_preview_final_composite_and_copy(
const StrokePreviewCompositePassInputs& inputs,
Texture2D& preview_texture,
glm::vec2 size)
{
pp::panopainter::execute_legacy_stroke_preview_final_composite(
[&] {
pp::panopainter::setup_legacy_stroke_composite_shader(
pp::panopainter::LegacyStrokeCompositeUniforms {
.resolution = inputs.resolution,
.pattern = {
.scale = inputs.pattern_scale,
.invert = static_cast<float>(inputs.brush.m_pattern_invert),
.brightness = inputs.brush.m_pattern_brightness,
.contrast = inputs.brush.m_pattern_contrast,
.depth = inputs.brush.m_pattern_depth,
.blend_mode = inputs.composite_pass.pattern_blend_mode,
.offset = glm::vec2(inputs.brush.m_pattern_rand_offset ? 0.5f : 0.0f),
},
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
.layer_alpha = 1.0f,
.alpha_lock = false,
.mask_enabled = false,
.use_fragcoord = false,
.blend_mode = inputs.brush.m_blend_mode,
.use_dual = inputs.composite_pass.use_dual,
.dual_blend_mode = inputs.composite_pass.dual_blend_mode,
.dual_alpha = inputs.composite_pass.dual_alpha,
.use_pattern = inputs.composite_pass.use_pattern,
});
},
[&] {
inputs.linear_sampler.bind(stroke_preview_composite_slots::kBackground);
inputs.linear_sampler.bind(stroke_preview_composite_slots::kStroke);
inputs.linear_sampler.bind(2U);
inputs.linear_sampler.bind(stroke_preview_composite_slots::kDual);
inputs.repeat_sampler.bind(stroke_preview_composite_slots::kPattern);
},
[&] {
set_active_texture_unit(stroke_preview_composite_slots::kBackground);
inputs.background_texture.bind();
set_active_texture_unit(stroke_preview_composite_slots::kStroke);
inputs.stroke_texture.bind();
set_active_texture_unit(stroke_preview_composite_slots::kDual);
inputs.dual_texture.bind();
set_active_texture_unit(stroke_preview_composite_slots::kPattern);
inputs.brush.m_pattern_texture ?
inputs.brush.m_pattern_texture->bind() :
unbind_texture_2d();
},
[&] {
inputs.draw_composite();
});
copy_stroke_preview_result_to_texture(preview_texture, size);
}
void copy_stroke_preview_framebuffer_to_texture( void copy_stroke_preview_framebuffer_to_texture(
Texture2D& texture, Texture2D& texture,
glm::vec2 size, glm::vec2 size,
@@ -500,22 +405,6 @@ void execute_stroke_preview_background_capture_pass(
assert(copy_status.ok()); assert(copy_status.ok());
} }
void copy_stroke_preview_result_to_texture(Texture2D& preview_texture, glm::vec2 size)
{
const auto result = pp::paint_renderer::copy_stroke_preview_result_to_texture(
[&] {
preview_texture.bind();
},
[](int src_x, int src_y, int dst_x, int dst_y, int width, int height) {
copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height);
},
pp::paint_renderer::StrokePreviewCopySize {
.width = static_cast<int>(size.x),
.height = static_cast<int>(size.y),
});
assert(result.ok());
}
} }
std::atomic_int NodeStrokePreview::s_instances{ 0 }; std::atomic_int NodeStrokePreview::s_instances{ 0 };
@@ -841,8 +730,7 @@ void NodeStrokePreview::draw_stroke_immediate()
return false; return false;
} }
execute_stroke_preview_final_composite_and_copy( const bool final_composite_ok = pp::panopainter::execute_legacy_node_stroke_preview_final_composite(
StrokePreviewCompositePassInputs(
size, size,
glm::vec2(b->m_pattern_scale), glm::vec2(b->m_pattern_scale),
*b, *b,
@@ -850,13 +738,18 @@ void NodeStrokePreview::draw_stroke_immediate()
m_tex_background, m_tex_background,
m_tex, m_tex,
m_tex_dual, m_tex_dual,
m_tex_preview,
m_sampler_linear, m_sampler_linear,
m_sampler_linear_repeat, m_sampler_linear_repeat,
[&] {
b->m_pattern_texture ? b->m_pattern_texture->bind() : unbind_texture_2d();
},
[&] { [&] {
m_plane.draw_fill(); m_plane.draw_fill();
}), });
m_tex_preview, if (!final_composite_ok) {
size); return false;
}
return true; return true;
}(); }();
assert(sequence_ok); assert(sequence_ok);

View File

@@ -2,21 +2,28 @@
#ifdef __LINUX__ #ifdef __LINUX__
#include <string> #include <string>
#include <utility>
#include <GLFW/glfw3.h>
#include "app.h"
namespace pp::platform::linux { namespace pp::platform::linux {
namespace { namespace {
std::function<void(std::string)> g_fps_title_callback;
void linux_update_fps(int frames) void linux_update_fps(int frames)
{ {
App::I->title("PanoPainter - " + std::to_string(frames) + " FPS"); if (!g_fps_title_callback)
return;
g_fps_title_callback("PanoPainter - " + std::to_string(frames) + " FPS");
} }
} }
void set_fps_title_callback(std::function<void(std::string)> callback)
{
g_fps_title_callback = std::move(callback);
}
void report_rendered_frames(int frames) void report_rendered_frames(int frames)
{ {
linux_update_fps(frames); linux_update_fps(frames);
@@ -26,6 +33,11 @@ void report_rendered_frames(int frames)
#else #else
namespace pp::platform::linux { namespace pp::platform::linux {
void set_fps_title_callback(std::function<void(std::string)> callback)
{
(void)callback;
}
void report_rendered_frames(int frames) void report_rendered_frames(int frames)
{ {
(void)frames; (void)frames;

View File

@@ -1,7 +1,11 @@
#pragma once #pragma once
#include <functional>
#include <string>
namespace pp::platform::linux { namespace pp::platform::linux {
void set_fps_title_callback(std::function<void(std::string)> callback);
void report_rendered_frames(int frames); void report_rendered_frames(int frames);
} }