Own brush workers and thin preview/platform seams
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Modernization Debt Log
|
||||
|
||||
Status: live
|
||||
Last updated: 2026-06-15
|
||||
Last updated: 2026-06-16
|
||||
|
||||
Every shortcut, temporary adapter, retained vendored dependency, skipped
|
||||
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
|
||||
|
||||
- 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
|
||||
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
|
||||
@@ -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-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-0047 | Open | Modernization | PPBR brush package export request validation, success-dialog metadata, and execution dispatch now consume pure `pp_app_core` through `App::dialog_ppbr_export`, `pano_cli plan-brush-package-export`, `BrushPackageExportServices`, and `src/legacy_brush_package_export_services.*`; PPBR header/path planning now consumes `pp_assets::brush_package`, and the macOS data-directory override now routes through `PlatformServices`, but the bridge still reads `NodeDialogExportPPBR`, carries the legacy `Image` header object outside the pure request, converts to `NodePanelBrushPreset::PPBRInfo`, calls `NodePanelBrushPreset::export_ppbr`, owns desktop worker-thread dispatch, dialog destruction, and mobile/Web completion directly | Preserve current PPBR export behavior while brush assets, PPBR serialization, picker completion, and UI lifetime move toward asset/storage/UI/platform services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_export_tests`; `pp_platform_api_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --author Artist --dest-path D:/Paint/BrushPreviews --export-data --header-image`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `pano_cli plan-brush-package-export`; `pano_cli plan-brush-package-export --path clouds`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --dest-path D:/Paint/BrushPreviews --no-export-data`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | PPBR metadata collection, header-image ownership, serialization, picker-selected path execution, desktop threading, dialog lifetime, and mobile/Web completion are owned by injected brush asset/storage/UI/platform services with `App::dialog_ppbr_export` acting only as a UI adapter |
|
||||
| DEBT-0048 | Open | Modernization | ABR/PPBR brush package import execution now consumes pure `pp_app_core` through document-open confirmation callbacks, `pano_cli plan-brush-package-import`, `BrushPackageImportServices`, and `src/legacy_brush_package_import_services.*`; imported brush tip/pattern target paths now consume `pp_assets::brush_package`, but the bridge still launches detached legacy `NodePanelBrushPreset::import_abr`/`import_ppbr` worker threads and depends on the legacy preset panel as the importer/storage owner | Preserve current brush import behavior while brush package parsing, preset storage, progress/error reporting, and UI refresh move toward asset/paint/UI services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_import_tests`; `pano_cli plan-brush-package-import --kind ppbr --path D:/Paint/Brushes/clouds.ppbr`; `pano_cli plan-brush-package-import --kind abr --path D:/Paint/Brushes/clouds.abr`; `pano_cli plan-brush-package-import --kind ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | ABR/PPBR parsing, preset creation/storage, import threading/progress, duplicate asset policy, and UI refresh are owned by injected brush asset/paint/UI services with document-open callbacks only confirming user intent |
|
||||
| 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`, 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-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 |
|
||||
|
||||
@@ -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
|
||||
platform-neutral policy and interface code.
|
||||
- `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
|
||||
app shell.
|
||||
- `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`
|
||||
- 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
|
||||
|
||||
|
||||
@@ -42,12 +42,13 @@ Completed, blocked, and superseded task history moved to
|
||||
`src/node_canvas.cpp`, `src/app.cpp`, and `src/app_dialogs.cpp`.
|
||||
- The platform boundary is not finished:
|
||||
- `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
|
||||
- The app runtime boundary is not finished:
|
||||
- render/UI queues are static `App` state
|
||||
- detached workers still launch from canvas, cloud, brush, grid, preview, and
|
||||
event code
|
||||
- detached workers still launch from canvas, grid, preview, and event code
|
||||
- thread-affinity rules are enforced by convention and asserts instead of
|
||||
explicit runtime contracts
|
||||
- The UI ownership boundary is not finished:
|
||||
@@ -114,12 +115,17 @@ Mini-model packet:
|
||||
|
||||
#### ARC-RND-002 - Isolate Preview And Canvas View Render Execution
|
||||
|
||||
Status: Ready
|
||||
Status: In Progress
|
||||
|
||||
Why now:
|
||||
`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.
|
||||
|
||||
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:
|
||||
- `src/node_stroke_preview.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
|
||||
|
||||
Status: Ready
|
||||
Status: In Progress
|
||||
|
||||
Why now:
|
||||
Canvas imports/exports/saves, cloud transfer, brush import/export, grid
|
||||
lightmap work, stroke preview, and event persistence still launch detached
|
||||
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:
|
||||
- `src/canvas.cpp`
|
||||
- `src/app_cloud.cpp`
|
||||
@@ -525,12 +538,18 @@ Mini-model packet:
|
||||
|
||||
#### ARC-PLT-002 - Remove `App::I` Reach From Apple And Linux Services
|
||||
|
||||
Status: Ready
|
||||
Status: In Progress
|
||||
|
||||
Why now:
|
||||
The current Apple and Linux service files still call into the app singleton,
|
||||
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:
|
||||
- `src/platform_apple/*`
|
||||
- `src/platform_linux/*`
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#include "app_core/document_platform_io.h"
|
||||
#include "app_core/document_sharing.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 "renderer_gl/opengl_capabilities.h"
|
||||
|
||||
@@ -51,6 +55,21 @@ namespace {
|
||||
void App::set_platform_services(pp::platform::PlatformServices* services) noexcept
|
||||
{
|
||||
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
|
||||
|
||||
@@ -7,12 +7,90 @@
|
||||
#include "node_dialog_export_ppbr.h"
|
||||
#include "node_panel_brush.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
namespace pp::panopainter {
|
||||
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(
|
||||
const pp::app::BrushPackageExportRequest& request,
|
||||
const NodeDialogExportPPBR& dialog)
|
||||
@@ -44,20 +122,29 @@ public:
|
||||
{
|
||||
const auto path_string = std::string(path);
|
||||
const auto info = to_legacy_ppbr_info(request, dialog_);
|
||||
auto presets = app_.presets;
|
||||
if (mode_ == LegacyBrushPackageExportMode::desktop_async_close_and_message) {
|
||||
auto* app = &app_;
|
||||
auto* dialog = &dialog_;
|
||||
std::thread([app, dialog, path_string, info] {
|
||||
auto dialog = std::static_pointer_cast<NodeDialogExportPPBR>(dialog_.shared_from_this());
|
||||
brush_package_worker().post([app, presets, dialog, path_string, info] {
|
||||
BT_SetTerminate();
|
||||
app->presets->export_ppbr(path_string, info);
|
||||
pp::panopainter::close_legacy_dialog_node(*dialog);
|
||||
if (presets) {
|
||||
presets->export_ppbr(path_string, info);
|
||||
}
|
||||
const auto plan = pp::app::plan_brush_package_export_success_dialog(path_string);
|
||||
app->message_box(plan.title, plan.message, plan.show_cancel);
|
||||
}).detach();
|
||||
app->ui_task([dialog, plan] {
|
||||
if (dialog) {
|
||||
pp::panopainter::close_legacy_dialog_node(*dialog);
|
||||
}
|
||||
App::I->message_box(plan.title, plan.message, plan.show_cancel);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
app_.presets->export_ppbr(path_string, info);
|
||||
if (presets) {
|
||||
presets->export_ppbr(path_string, info);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -5,12 +5,90 @@
|
||||
#include "app.h"
|
||||
#include "node_panel_brush.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
namespace pp::panopainter {
|
||||
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 {
|
||||
public:
|
||||
explicit LegacyBrushPackageImportServices(App& app) noexcept
|
||||
@@ -22,12 +100,18 @@ public:
|
||||
{
|
||||
auto presets = app_.presets;
|
||||
const auto path_string = std::string(path);
|
||||
if (kind == pp::app::BrushPackageImportKind::abr) {
|
||||
std::thread(&NodePanelBrushPreset::import_abr, presets, path_string).detach();
|
||||
return;
|
||||
}
|
||||
brush_package_worker().post([presets, kind, path_string] {
|
||||
BT_SetTerminate();
|
||||
if (!presets) {
|
||||
return;
|
||||
}
|
||||
if (kind == pp::app::BrushPackageImportKind::abr) {
|
||||
presets->import_abr(path_string);
|
||||
return;
|
||||
}
|
||||
|
||||
std::thread(&NodePanelBrushPreset::import_ppbr, presets, path_string).detach();
|
||||
presets->import_ppbr(path_string);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
#include "../libs/glm/glm/ext/matrix_clip_space.hpp"
|
||||
|
||||
#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_services.h"
|
||||
#include "legacy_ui_gl_dispatch.h"
|
||||
#include "paint_renderer/compositor.h"
|
||||
#include "texture.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::panopainter {
|
||||
@@ -241,6 +245,91 @@ template <typename Frame>
|
||||
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 {
|
||||
pp::renderer::RenderDeviceFeatures features {};
|
||||
glm::vec2 preview_size {};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "assets/brush_package.h"
|
||||
#include "app_core/brush_ui.h"
|
||||
#include "legacy_brush_ui_services.h"
|
||||
#include "legacy_brush_package_import_services.h"
|
||||
#include "legacy_ui_overlay_services.h"
|
||||
#include "asset.h"
|
||||
#include "texture.h"
|
||||
@@ -600,13 +601,8 @@ void NodePanelBrushPreset::init()
|
||||
switch (index)
|
||||
{
|
||||
case 0: // import file
|
||||
App::I->pick_file({"abr", "ppbr"}, [this] (std::string path) {
|
||||
std::thread([this, path] {
|
||||
BT_SetTerminate();
|
||||
import_brush(path);
|
||||
for (auto p : s_panels)
|
||||
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
|
||||
}).detach();
|
||||
App::I->pick_file({"abr", "ppbr"}, [presets = std::static_pointer_cast<NodePanelBrushPreset>(shared_from_this())] (std::string path) {
|
||||
presets->import_brush(path);
|
||||
});
|
||||
break;
|
||||
case 1: // export file
|
||||
@@ -637,13 +633,8 @@ void NodePanelBrushPreset::init()
|
||||
};
|
||||
m_btn_import = find<NodeButton>("import");
|
||||
m_btn_import->on_click = [this] (Node*) {
|
||||
App::I->pick_file({ "abr", "ppbr" }, [this](std::string path) {
|
||||
std::thread([this, path] {
|
||||
BT_SetTerminate();
|
||||
import_brush(path);
|
||||
for (auto p : s_panels)
|
||||
p->m_notification->SetVisibility(p->m_container->m_children.size() == 0);
|
||||
}).detach();
|
||||
App::I->pick_file({ "abr", "ppbr" }, [presets = std::static_pointer_cast<NodePanelBrushPreset>(shared_from_this())](std::string path) {
|
||||
presets->import_brush(path);
|
||||
});
|
||||
};
|
||||
m_btn_download = find<NodeButton>("download");
|
||||
@@ -1050,6 +1041,10 @@ bool NodePanelBrushPreset::import_ppbr(const std::string& path)
|
||||
|
||||
// brush settings
|
||||
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++)
|
||||
{
|
||||
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());
|
||||
if (b->valid())
|
||||
{
|
||||
for (auto p : s_panels)
|
||||
p->add_brush(b);
|
||||
brushes_to_add.push_back(b);
|
||||
}
|
||||
pb->increment();
|
||||
}
|
||||
|
||||
save();
|
||||
App::I->stroke->m_brush_popup->reload();
|
||||
auto owner = std::static_pointer_cast<NodePanelBrushPreset>(shared_from_this());
|
||||
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);
|
||||
}
|
||||
|
||||
pp::panopainter::close_legacy_dialog_node(*pb);
|
||||
owner->save();
|
||||
App::I->stroke->m_brush_popup->reload();
|
||||
pp::panopainter::close_legacy_dialog_node(*pb);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1148,7 +1150,8 @@ bool NodePanelBrushPreset::import_abr(const std::string& 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)
|
||||
{
|
||||
if (b->valid())
|
||||
@@ -1159,12 +1162,11 @@ bool NodePanelBrushPreset::import_abr(const std::string& path)
|
||||
}
|
||||
pb->increment();
|
||||
}
|
||||
owner->save();
|
||||
App::I->stroke->m_brush_popup->reload();
|
||||
pp::panopainter::close_legacy_dialog_node(*pb);
|
||||
});
|
||||
|
||||
save();
|
||||
App::I->stroke->m_brush_popup->reload();
|
||||
pp::panopainter::close_legacy_dialog_node(*pb);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1178,7 +1180,15 @@ bool NodePanelBrushPreset::import_brush(const std::string& path)
|
||||
std::string name = m[2].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()
|
||||
|
||||
@@ -82,43 +82,6 @@ constexpr std::uint32_t kMixer = 3U;
|
||||
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(
|
||||
const Brush& brush,
|
||||
glm::vec2 resolution) noexcept
|
||||
@@ -232,65 +195,7 @@ pp::panopainter::LegacyCanvasStrokeMixPassRequest make_stroke_preview_mix_pass_e
|
||||
[&](int) {
|
||||
set_active_texture_unit(stroke_preview_composite_slots::kBackground);
|
||||
background_texture.unbind();
|
||||
});
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -500,22 +405,6 @@ void execute_stroke_preview_background_capture_pass(
|
||||
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 };
|
||||
@@ -841,22 +730,26 @@ void NodeStrokePreview::draw_stroke_immediate()
|
||||
return false;
|
||||
}
|
||||
|
||||
execute_stroke_preview_final_composite_and_copy(
|
||||
StrokePreviewCompositePassInputs(
|
||||
size,
|
||||
glm::vec2(b->m_pattern_scale),
|
||||
*b,
|
||||
material.composite_pass,
|
||||
m_tex_background,
|
||||
m_tex,
|
||||
m_tex_dual,
|
||||
m_sampler_linear,
|
||||
m_sampler_linear_repeat,
|
||||
[&] {
|
||||
m_plane.draw_fill();
|
||||
}),
|
||||
const bool final_composite_ok = pp::panopainter::execute_legacy_node_stroke_preview_final_composite(
|
||||
size,
|
||||
glm::vec2(b->m_pattern_scale),
|
||||
*b,
|
||||
material.composite_pass,
|
||||
m_tex_background,
|
||||
m_tex,
|
||||
m_tex_dual,
|
||||
m_tex_preview,
|
||||
size);
|
||||
m_sampler_linear,
|
||||
m_sampler_linear_repeat,
|
||||
[&] {
|
||||
b->m_pattern_texture ? b->m_pattern_texture->bind() : unbind_texture_2d();
|
||||
},
|
||||
[&] {
|
||||
m_plane.draw_fill();
|
||||
});
|
||||
if (!final_composite_ok) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
assert(sequence_ok);
|
||||
|
||||
@@ -2,21 +2,28 @@
|
||||
|
||||
#ifdef __LINUX__
|
||||
#include <string>
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "app.h"
|
||||
#include <utility>
|
||||
|
||||
namespace pp::platform::linux {
|
||||
namespace {
|
||||
|
||||
std::function<void(std::string)> g_fps_title_callback;
|
||||
|
||||
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)
|
||||
{
|
||||
linux_update_fps(frames);
|
||||
@@ -26,6 +33,11 @@ void report_rendered_frames(int frames)
|
||||
#else
|
||||
namespace pp::platform::linux {
|
||||
|
||||
void set_fps_title_callback(std::function<void(std::string)> callback)
|
||||
{
|
||||
(void)callback;
|
||||
}
|
||||
|
||||
void report_rendered_frames(int frames)
|
||||
{
|
||||
(void)frames;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace pp::platform::linux {
|
||||
|
||||
void set_fps_title_callback(std::function<void(std::string)> callback);
|
||||
void report_rendered_frames(int frames);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user