Extract dialog, VR, and canvas draw helpers

This commit is contained in:
2026-06-16 11:40:00 +02:00
parent 18665bdffc
commit d2a841f348
9 changed files with 566 additions and 377 deletions

View File

@@ -92,6 +92,7 @@ set(PP_PANOPAINTER_APP_SOURCES
src/app_cloud.cpp src/app_cloud.cpp
src/app_commands.cpp src/app_commands.cpp
src/app_dialogs.cpp src/app_dialogs.cpp
src/app_dialogs_info_openers.cpp
src/app_events.cpp src/app_events.cpp
src/app_layout.cpp src/app_layout.cpp
src/app_layout_about_layer_menu.cpp src/app_layout_about_layer_menu.cpp

View File

@@ -83,12 +83,12 @@ Current hotspot files:
- `src/app_layout.cpp`: 1249 lines - `src/app_layout.cpp`: 1249 lines
- `src/canvas_modes.cpp`: 1798 lines - `src/canvas_modes.cpp`: 1798 lines
- `src/node.cpp`: 1551 lines - `src/node.cpp`: 1551 lines
- `src/main.cpp`: 1218 lines - `src/main.cpp`: 1117 lines
- `src/node_panel_brush.cpp`: 1197 lines - `src/node_panel_brush.cpp`: 1197 lines
- `src/node_stroke_preview.cpp`: 933 lines - `src/node_stroke_preview.cpp`: 933 lines
- `src/node_canvas.cpp`: 968 lines - `src/node_canvas.cpp`: 953 lines
- `src/app.cpp`: 950 lines - `src/app.cpp`: 950 lines
- `src/app_dialogs.cpp`: 908 lines - `src/app_dialogs.cpp`: 789 lines
Current architecture mismatches that must be treated as real blockers: Current architecture mismatches that must be treated as real blockers:
@@ -124,8 +124,11 @@ Current architecture mismatches that must be treated as real blockers:
per-layer render-path orchestration now also route through retained per-layer render-path orchestration now also route through retained
draw-merge helpers even though the broader node draw loop is still inline, draw-merge helpers even though the broader node draw loop is still inline,
with the non-`draw_merged` outer layer/plane traversal now also routing with the non-`draw_merged` outer layer/plane traversal now also routing
through `execute_legacy_canvas_draw_layer_traversal(...)` while the heavier through `execute_legacy_canvas_draw_layer_traversal(...)`, while the heavier
per-layer GL setup and draw lambdas still live in `src/node_canvas.cpp`. per-layer GL setup now also routes through
`make_legacy_canvas_draw_merge_layer_path_gl_execution(...)` even though the
remaining draw lambdas and broader node draw loop still live in
`src/node_canvas.cpp`.
- `app_layout.cpp` and `app_dialogs.cpp` are still mixed shell/controller files - `app_layout.cpp` and `app_dialogs.cpp` are still mixed shell/controller files
rather than thin composition/binding surfaces, even though tools-menu binding rather than thin composition/binding surfaces, even though tools-menu binding
plus nested panels/options submenu wiring now live in plus nested panels/options submenu wiring now live in
@@ -134,7 +137,9 @@ Current architecture mismatches that must be treated as real blockers:
live in `src/app_layout_file_menu.cpp` and `App::init_menu_file()` is now a live in `src/app_layout_file_menu.cpp` and `App::init_menu_file()` is now a
thin call-through, while about-menu and layer-menu wiring now also live in thin call-through, while about-menu and layer-menu wiring now also live in
`src/app_layout_about_layer_menu.cpp` and `App::init_menu_about()` plus `src/app_layout_about_layer_menu.cpp` and `App::init_menu_about()` plus
`App::init_menu_layer()` are now thin call-throughs. `App::init_menu_layer()` are now thin call-throughs, while the informational
overlay opener family now also lives in `src/app_dialogs_info_openers.cpp`
and the corresponding `App::dialog_*` entrypoints are thinner.
- `App`, `Canvas`, `Node`, retained workers, and platform entrypoints still use - `App`, `Canvas`, `Node`, retained workers, and platform entrypoints still use
global singleton reach, raw observer pointers, retained static worker global singleton reach, raw observer pointers, retained static worker
ownership in several app families, and ad hoc mutex/condition-variable ownership in several app families, and ad hoc mutex/condition-variable
@@ -161,6 +166,9 @@ Current architecture mismatches that must be treated as real blockers:
`src/main.cpp`, while Win32 pointer API loading, stylus/ink timer decay, `src/main.cpp`, while Win32 pointer API loading, stylus/ink timer decay,
Wintab packet reset, and `WM_POINTERUPDATE` pen/touch handling now also live Wintab packet reset, and `WM_POINTERUPDATE` pen/touch handling now also live
in `src/platform_windows/windows_stylus_input.cpp` instead of `src/main.cpp`, in `src/platform_windows/windows_stylus_input.cpp` instead of `src/main.cpp`,
while the retained Win32 VR/HMD shell now also routes through
`src/platform_windows/windows_vr_shell.h` instead of staying inline in
`src/main.cpp`,
while `App::rec_loop()` now delegates worker-iteration orchestration into while `App::rec_loop()` now delegates worker-iteration orchestration into
the retained recording bridge, `App::update_rec_frames()` now delegates the retained recording bridge, `App::update_rec_frames()` now delegates
recording label refresh through that same retained recording path, and the recording label refresh through that same retained recording path, and the

View File

@@ -195,8 +195,11 @@ Current slice:
broader draw-loop and renderer-state shell sequencing. broader draw-loop and renderer-state shell sequencing.
- `NodeCanvas` outer non-`draw_merged` layer/plane traversal, onion-range - `NodeCanvas` outer non-`draw_merged` layer/plane traversal, onion-range
failure handling, and visit payload setup now also route through failure handling, and visit payload setup now also route through
`execute_legacy_canvas_draw_layer_traversal(...)`, but the node still owns `execute_legacy_canvas_draw_layer_traversal(...)`.
the heavier per-layer GL setup and draw lambdas. - `NodeCanvas` non-`draw_merged` per-layer temporary erase/paint, layer-texture,
and blend setup now also route through
`make_legacy_canvas_draw_merge_layer_path_gl_execution(...)`, but the node
still owns the remaining draw lambdas and broader renderer-state shell.
Write scope: Write scope:
- `src/node_stroke_preview.cpp` - `src/node_stroke_preview.cpp`
@@ -325,6 +328,13 @@ Why now:
`src/app_dialogs.cpp` still mixes document workflow decisions, export routing, `src/app_dialogs.cpp` still mixes document workflow decisions, export routing,
dialog construction, and overlay ownership. dialog construction, and overlay ownership.
Current slice:
- Informational overlay opener paths for user manual, changelog, about,
what's-new, and shortcuts now live in `src/app_dialogs_info_openers.cpp`,
and the corresponding `App::dialog_*` entrypoints are now thin call-throughs,
but document/export workflow and retained dialog execution are still inline in
`src/app_dialogs.cpp`.
Write scope: Write scope:
- `src/app_dialogs.cpp` - `src/app_dialogs.cpp`
- `src/legacy_app_dialog_services.*` - `src/legacy_app_dialog_services.*`
@@ -417,6 +427,10 @@ Current slice:
reset, and `WM_POINTERUPDATE` pen/touch handling now live in reset, and `WM_POINTERUPDATE` pen/touch handling now live in
`src/platform_windows/windows_stylus_input.cpp` instead of `src/main.cpp`, `src/platform_windows/windows_stylus_input.cpp` instead of `src/main.cpp`,
but broader retained Win32 shell state is still open but broader retained Win32 shell state is still open
- the retained Win32 VR/HMD shell, including worker start/stop and VR FPS
state, now routes through `src/platform_windows/windows_vr_shell.h` instead
of staying inline in `src/main.cpp`, but broader retained Win32 shell state
is still open
- prepared-file background work now runs through an `AppRuntime`-owned worker - prepared-file background work now runs through an `AppRuntime`-owned worker
queue instead of a retained static worker in `src/app_events.cpp` queue instead of a retained static worker in `src/app_events.cpp`
- canvas async import/export/save/open background work now also runs through an - canvas async import/export/save/open background work now also runs through an

View File

@@ -11,18 +11,12 @@
#include "legacy_document_export_services.h" #include "legacy_document_export_services.h"
#include "legacy_document_layer_services.h" #include "legacy_document_layer_services.h"
#include "legacy_document_session_services.h" #include "legacy_document_session_services.h"
#include "legacy_preference_storage.h"
#include "legacy_ui_overlay_services.h" #include "legacy_ui_overlay_services.h"
#include "node_dialog_open.h" #include "node_dialog_open.h"
#include "node_dialog_browse.h" #include "node_dialog_browse.h"
#include "node_dialog_resize.h" #include "node_dialog_resize.h"
#include "node_dialog_cloud.h" #include "node_dialog_cloud.h"
#include "node_about.h"
#include "node_changelog.h"
#include "node_usermanual.h"
#include "node_dialog_export_ppbr.h" #include "node_dialog_export_ppbr.h"
#include "node_remote_page.h"
#include "node_shorcuts.h"
#include <codec_api.h> #include <codec_api.h>
#define MP4V2_NO_STDINT_DEFS #define MP4V2_NO_STDINT_DEFS
@@ -34,6 +28,14 @@
#include "oculus_vr.h" #include "oculus_vr.h"
#endif #endif
namespace pp::panopainter {
void open_usermanual_dialog(App& app);
void open_changelog_dialog(App& app);
void open_about_dialog(App& app);
void open_whatsnew_dialog(App& app, bool force_show);
void open_shortcuts_dialog(App& app);
}
namespace { namespace {
[[nodiscard]] bool can_start_document_export(App& app, bool requires_license) [[nodiscard]] bool can_start_document_export(App& app, bool requires_license)
@@ -208,80 +210,17 @@ std::shared_ptr<NodeInputBox> App::input_box(const std::string& title,
void App::dialog_usermanual() void App::dialog_usermanual()
{ {
auto* overlay_anchor = layout[main_id]; pp::panopainter::open_usermanual_dialog(*this);
if (!overlay_anchor) {
LOG("User manual dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeUserManual>(*this);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("User manual dialog open failed: %s", overlay.status().message);
return;
}
const auto overlay_handle = overlay.value();
const auto close_dialog = [overlay_anchor, overlay_handle]() {
const auto close_status = pp::panopainter::close_legacy_overlay_node(*overlay_anchor, overlay_handle);
(void)close_status;
};
dialog->btn_ok->on_click = [close_dialog](Node*) {
close_dialog();
};
} }
void App::dialog_changelog() void App::dialog_changelog()
{ {
auto* overlay_anchor = layout[main_id]; pp::panopainter::open_changelog_dialog(*this);
if (!overlay_anchor) {
LOG("Changelog dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeChangelog>(*this);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("Changelog dialog open failed: %s", overlay.status().message);
return;
}
const auto overlay_handle = overlay.value();
const auto close_dialog = [overlay_anchor, overlay_handle]() {
const auto close_status = pp::panopainter::close_legacy_overlay_node(*overlay_anchor, overlay_handle);
(void)close_status;
};
dialog->btn_ok->on_click = [close_dialog](Node*) {
close_dialog();
};
} }
void App::dialog_about() void App::dialog_about()
{ {
auto* overlay_anchor = layout[main_id]; pp::panopainter::open_about_dialog(*this);
if (!overlay_anchor) {
LOG("About dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeAbout>(*this);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("About dialog open failed: %s", overlay.status().message);
return;
}
const auto overlay_handle = overlay.value();
const auto close_dialog = [overlay_anchor, overlay_handle]() {
const auto close_status = pp::panopainter::close_legacy_overlay_node(*overlay_anchor, overlay_handle);
(void)close_status;
};
dialog->btn_ok->on_click = [close_dialog](Node*) {
close_dialog();
};
} }
void App::continue_document_workflow_after_optional_save(std::function<void()> action) void App::continue_document_workflow_after_optional_save(std::function<void()> action)
@@ -841,68 +780,10 @@ void App::dialog_export_mp4()
void App::dialog_whatsnew(bool force_show) void App::dialog_whatsnew(bool force_show)
{ {
auto* overlay_anchor = layout[main_id]; pp::panopainter::open_whatsnew_dialog(*this, force_show);
if (!overlay_anchor) {
LOG("What's new dialog open failed: main layout anchor is missing");
return;
}
const auto overlay_handle = std::make_shared<pp::ui::NodeHandle>();
const auto open_overlay = [overlay_anchor, overlay_handle](const std::shared_ptr<NodeRemotePage>& page) {
if (overlay_handle->valid()) {
return;
}
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, page);
if (!overlay) {
return;
}
*overlay_handle = overlay.value();
};
const auto close_overlay = [overlay_anchor, overlay_handle]() {
if (!overlay_handle->valid()) {
return;
}
const auto close_status = pp::panopainter::close_legacy_overlay_node(*overlay_anchor, *overlay_handle);
(void)close_status;
*overlay_handle = {};
};
auto whatsnew = std::make_shared<NodeRemotePage>();
whatsnew->set_manager(&layout);
whatsnew->init();
std::string url = fmt::format("https://panopainter.com/app-content/whatsnew/?version={}", g_version_build);
whatsnew->load_url(url, [whatsnew, force_show, open_overlay](bool success) {
if (success)
{
int last_id = pp::panopainter::legacy_whatsnew_id_or(0);
if (force_show || (whatsnew->m_page_id <= g_version_build && whatsnew->m_page_id > last_id))
{
whatsnew->set_title(fmt::format("What's new in version {}", g_version_number));
if (!force_show)
open_overlay(whatsnew);
}
}
});
whatsnew->add_button("Reload", 120, [whatsnew](Node*) {
whatsnew->reload();
});
whatsnew->add_button("Read Later", 120, [whatsnew, close_overlay](Node*) {
pp::panopainter::clear_legacy_whatsnew_id();
pp::panopainter::save_legacy_preferences();
close_overlay();
});
whatsnew->add_button("Close", 100, [whatsnew, close_overlay](Node*) {
pp::panopainter::set_legacy_whatsnew_id(whatsnew->m_page_id);
pp::panopainter::save_legacy_preferences();
close_overlay();
});
if (force_show)
open_overlay(whatsnew);
} }
void App::dialog_shortcuts() void App::dialog_shortcuts()
{ {
(void)pp::panopainter::add_legacy_overlay_node<NodeShortcuts>(*this); pp::panopainter::open_shortcuts_dialog(*this);
} }

View File

@@ -0,0 +1,128 @@
#include "pch.h"
#include "app.h"
#include "legacy_preference_storage.h"
#include "legacy_ui_overlay_services.h"
#include "node_about.h"
#include "node_changelog.h"
#include "node_remote_page.h"
#include "node_shorcuts.h"
#include "node_usermanual.h"
#include "version.h"
namespace pp::panopainter {
namespace {
template <typename DialogNode>
void open_info_dialog(App& app, const char* log_name)
{
auto* overlay_anchor = app.layout[app.main_id];
if (!overlay_anchor) {
LOG("%s dialog open failed: main layout anchor is missing", log_name);
return;
}
auto dialog = make_legacy_overlay_node<DialogNode>(app);
const auto overlay = open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("%s dialog open failed: %s", log_name, overlay.status().message);
return;
}
const auto overlay_handle = overlay.value();
const auto close_dialog = [overlay_anchor, overlay_handle]() {
const auto close_status = close_legacy_overlay_node(*overlay_anchor, overlay_handle);
(void)close_status;
};
dialog->btn_ok->on_click = [close_dialog](Node*) {
close_dialog();
};
}
} // namespace
void open_usermanual_dialog(App& app)
{
open_info_dialog<NodeUserManual>(app, "User manual");
}
void open_changelog_dialog(App& app)
{
open_info_dialog<NodeChangelog>(app, "Changelog");
}
void open_about_dialog(App& app)
{
open_info_dialog<NodeAbout>(app, "About");
}
void open_whatsnew_dialog(App& app, bool force_show)
{
auto* overlay_anchor = app.layout[app.main_id];
if (!overlay_anchor) {
LOG("What's new dialog open failed: main layout anchor is missing");
return;
}
const auto overlay_handle = std::make_shared<pp::ui::NodeHandle>();
const auto open_overlay = [overlay_anchor, overlay_handle](const std::shared_ptr<NodeRemotePage>& page) {
if (overlay_handle->valid()) {
return;
}
const auto overlay = open_legacy_overlay_node_with_handle(*overlay_anchor, page);
if (!overlay) {
return;
}
*overlay_handle = overlay.value();
};
const auto close_overlay = [overlay_anchor, overlay_handle]() {
if (!overlay_handle->valid()) {
return;
}
const auto close_status = close_legacy_overlay_node(*overlay_anchor, *overlay_handle);
(void)close_status;
*overlay_handle = {};
};
auto whatsnew = std::make_shared<NodeRemotePage>();
whatsnew->set_manager(&app.layout);
whatsnew->init();
std::string url = fmt::format("https://panopainter.com/app-content/whatsnew/?version={}", g_version_build);
whatsnew->load_url(url, [whatsnew, force_show, open_overlay](bool success) {
if (success)
{
int last_id = legacy_whatsnew_id_or(0);
if (force_show || (whatsnew->m_page_id <= g_version_build && whatsnew->m_page_id > last_id))
{
whatsnew->set_title(fmt::format("What's new in version {}", g_version_number));
if (!force_show)
open_overlay(whatsnew);
}
}
});
whatsnew->add_button("Reload", 120, [whatsnew](Node*) {
whatsnew->reload();
});
whatsnew->add_button("Read Later", 120, [whatsnew, close_overlay](Node*) {
clear_legacy_whatsnew_id();
save_legacy_preferences();
close_overlay();
});
whatsnew->add_button("Close", 100, [whatsnew, close_overlay](Node*) {
set_legacy_whatsnew_id(whatsnew->m_page_id);
save_legacy_preferences();
close_overlay();
});
if (force_show)
open_overlay(whatsnew);
}
void open_shortcuts_dialog(App& app)
{
(void)add_legacy_overlay_node<NodeShortcuts>(app);
}
} // namespace pp::panopainter

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include "legacy_canvas_stroke_composite_services.h"
#include "legacy_canvas_stroke_erase_services.h"
#include "shader.h" #include "shader.h"
#include <array> #include <array>
@@ -130,6 +132,40 @@ struct LegacyCanvasDrawMergeLayerPathExecution {
std::function<void(int, float)> draw_frame; std::function<void(int, float)> draw_frame;
}; };
struct LegacyCanvasDrawMergeLayerPathGlUniforms {
LegacyStrokeEraseUniforms temporary_erase;
LegacyStrokeCompositeUniforms temporary_paint;
LegacyCanvasDrawMergeTextureAlphaUniforms layer_texture;
LegacyCanvasDrawMergeLayerBlendUniforms blend;
bool use_nearest_sampler = false;
bool use_dual_texture = false;
};
struct LegacyCanvasDrawMergeLayerPathGlExecution {
std::function<void()> bind_blender_framebuffer;
std::function<void()> clear_blender_framebuffer;
std::function<void()> unbind_blender_framebuffer;
std::function<void(int)> bind_sampler;
std::function<void(int)> bind_nearest_sampler;
std::function<void(int)> bind_stencil_sampler;
std::function<void(int)> set_active_texture_unit;
std::function<void()> bind_temporary_texture;
std::function<void()> unbind_temporary_texture;
std::function<void()> bind_smask_texture;
std::function<void()> unbind_smask_texture;
std::function<void()> bind_temporary_dual_texture;
std::function<void()> unbind_temporary_dual_texture;
std::function<void()> bind_pattern_texture;
std::function<void()> draw_face;
std::function<void()> bind_blender_texture;
std::function<void()> unbind_blender_texture;
std::function<void()> bind_destination_texture;
std::function<void()> unbind_destination_texture;
std::function<void()> copy_destination_framebuffer;
std::function<void()> draw_debug_outline;
std::function<void(int, float)> draw_frame;
};
struct LegacyCanvasDrawLayerVisit { struct LegacyCanvasDrawLayerVisit {
size_t layer_index = 0; size_t layer_index = 0;
int plane_index = 0; int plane_index = 0;
@@ -679,6 +715,102 @@ inline void execute_legacy_canvas_draw_merge_layer_path(
execution.draw_debug_outline(); execution.draw_debug_outline();
} }
[[nodiscard]] inline LegacyCanvasDrawMergeLayerPathExecution make_legacy_canvas_draw_merge_layer_path_gl_execution(
const LegacyCanvasDrawMergeLayerPathGlUniforms& uniforms,
const LegacyCanvasDrawMergeLayerPathGlExecution& execution)
{
return {
.bind_blender_framebuffer = execution.bind_blender_framebuffer,
.clear_blender_framebuffer = execution.clear_blender_framebuffer,
.unbind_blender_framebuffer = execution.unbind_blender_framebuffer,
.prepare_temporary_erase = [uniforms, execution] {
execution.bind_sampler(0);
execution.bind_sampler(1);
execution.bind_sampler(2);
setup_legacy_stroke_erase_shader(uniforms.temporary_erase);
execution.set_active_texture_unit(1);
execution.bind_temporary_texture();
execution.set_active_texture_unit(2);
execution.bind_smask_texture();
},
.cleanup_temporary_erase = [execution] {
execution.set_active_texture_unit(2);
execution.unbind_smask_texture();
execution.set_active_texture_unit(1);
execution.unbind_temporary_texture();
},
.prepare_temporary_paint = [uniforms, execution] {
execution.bind_sampler(0);
execution.bind_sampler(1);
execution.bind_sampler(2);
execution.bind_sampler(3);
execution.bind_stencil_sampler(4);
setup_legacy_stroke_composite_shader(uniforms.temporary_paint);
execution.set_active_texture_unit(1);
execution.bind_temporary_texture();
execution.set_active_texture_unit(2);
execution.bind_smask_texture();
execution.set_active_texture_unit(3);
if (uniforms.use_dual_texture) {
execution.bind_temporary_dual_texture();
}
execution.set_active_texture_unit(4);
execution.bind_pattern_texture();
},
.cleanup_temporary_paint = [uniforms, execution] {
execution.set_active_texture_unit(3);
if (uniforms.use_dual_texture) {
execution.unbind_temporary_dual_texture();
}
execution.set_active_texture_unit(2);
execution.unbind_smask_texture();
execution.set_active_texture_unit(1);
execution.unbind_temporary_texture();
},
.prepare_layer_texture = [uniforms, execution] {
if (uniforms.use_nearest_sampler) {
execution.bind_nearest_sampler(0);
} else {
execution.bind_sampler(0);
}
setup_legacy_canvas_draw_merge_texture_alpha_shader(uniforms.layer_texture);
},
.cleanup_layer_texture = [] {
},
.draw_blend = [uniforms, execution] {
execute_legacy_canvas_draw_merge_layer_blend(
uniforms.blend,
{
.unbind_merge_framebuffer = execution.unbind_blender_framebuffer,
.bind_samplers = [execution] {
execution.bind_sampler(0);
execution.bind_sampler(2);
},
.bind_merge_texture = [execution] {
execution.set_active_texture_unit(0);
execution.bind_blender_texture();
},
.bind_destination_texture = [execution] {
execution.set_active_texture_unit(2);
execution.bind_destination_texture();
},
.copy_destination_framebuffer = execution.copy_destination_framebuffer,
.draw = execution.draw_face,
.unbind_destination_texture = [execution] {
execution.set_active_texture_unit(2);
execution.unbind_destination_texture();
},
.unbind_merge_texture = [execution] {
execution.set_active_texture_unit(0);
execution.unbind_blender_texture();
},
});
},
.draw_debug_outline = execution.draw_debug_outline,
.draw_frame = execution.draw_frame,
};
}
template <typename PlanOnionRange, typename ShouldDrawPlane, typename VisitLayerPlane, typename LogOnionRangeFailure> template <typename PlanOnionRange, typename ShouldDrawPlane, typename VisitLayerPlane, typename LogOnionRangeFailure>
inline void execute_legacy_canvas_draw_layer_traversal( inline void execute_legacy_canvas_draw_layer_traversal(
size_t layer_count, size_t layer_count,

View File

@@ -7,20 +7,18 @@
#include "app.h" #include "app.h"
#include "canvas.h" #include "canvas.h"
#include "keymap.h" #include "keymap.h"
#include "hmd.h"
#include "legacy_gl_runtime_dispatch.h" #include "legacy_gl_runtime_dispatch.h"
#include "legacy_preference_storage.h" #include "legacy_preference_storage.h"
#include "renderer_gl/opengl_capabilities.h" #include "renderer_gl/opengl_capabilities.h"
#include "platform_windows/windows_platform_services.h" #include "platform_windows/windows_platform_services.h"
#include "platform_windows/windows_splash.h" #include "platform_windows/windows_splash.h"
#include "platform_windows/windows_stylus_input.h" #include "platform_windows/windows_stylus_input.h"
#include "platform_windows/windows_vr_shell.h"
#include "../resource.h" #include "../resource.h"
#include <shellscalingapi.h> #include <shellscalingapi.h>
#include <WbemCli.h> #include <WbemCli.h>
#include <deque> #include <deque>
#include <chrono>
#include "wacom.h" #include "wacom.h"
#include "abr.h" #include "abr.h"
@@ -32,9 +30,7 @@
#include <iomanip> #include <iomanip>
#include <ctime> #include <ctime>
#include <sstream> #include <sstream>
#include <memory>
#include <atomic> #include <atomic>
#include <stop_token>
#define WM_USER_CLOSE (WM_USER + 1) #define WM_USER_CLOSE (WM_USER + 1)
#define WM_USER_WAKEUP (WM_USER + 2) #define WM_USER_WAKEUP (WM_USER + 2)
@@ -55,11 +51,8 @@ struct RetainedState
bool keys[256]{}; bool keys[256]{};
std::map<kKey, int> vkey_map; std::map<kKey, int> vkey_map;
wchar_t window_title[512]{}; wchar_t window_title[512]{};
std::jthread hmd_renderer;
bool sandboxed = false; bool sandboxed = false;
std::mutex hmd_render_mutex; pp::platform::windows::VrShellState vr;
std::condition_variable hmd_render_cv;
std::unique_ptr<Vive> vive;
}; };
RetainedState& retained_state() RetainedState& retained_state()
@@ -125,9 +118,7 @@ void pp_windows_enqueue_main_task(std::packaged_task<void()> task)
enqueue_main_task_bridge(std::move(task)); enqueue_main_task_bridge(std::move(task));
} }
std::atomic<int> vr_frames{0};
std::atomic<int> running{-1}; std::atomic<int> running{-1};
std::atomic_bool vr_running{false};
#ifdef USE_RENDERDOC #ifdef USE_RENDERDOC
RENDERDOC_API_1_4_0* rdoc_api = NULL; RENDERDOC_API_1_4_0* rdoc_api = NULL;
@@ -230,8 +221,8 @@ void win32_update_stylus(float dt)
void win32_update_fps(int frames) void win32_update_fps(int frames)
{ {
static wchar_t title_fps[512]; static wchar_t title_fps[512];
const int vr_fps = vr_frames.load(std::memory_order_relaxed);
auto& state = retained_state(); auto& state = retained_state();
const int vr_fps = pp::platform::windows::current_vr_fps(state.vr);
if (App::I->vr_active) if (App::I->vr_active)
swprintf_s(title_fps, L"%s - %d fps - %d vr fps", state.window_title, frames, vr_fps); swprintf_s(title_fps, L"%s - %d fps - %d vr fps", state.window_title, frames, vr_fps);
else else
@@ -520,101 +511,13 @@ void init_vk_map()
bool win32_vr_start() bool win32_vr_start()
{ {
auto& state = retained_state(); auto& state = retained_state();
if (state.sandboxed) return pp::platform::windows::start_vr_shell(state.vr, state.sandboxed, running);
return false;
state.vive = std::make_unique<Vive>();
state.vive->on_draw = [](const glm::mat4& proj, const glm::mat4& view, const glm::mat4& pose) { App::I->vr_draw(proj, view, pose); };
if (!state.vive->Initialize())
{
state.vive.reset();
LOG("VR: failed to initialize vive");
return false;
}
if (state.hmd_renderer.joinable())
{
state.hmd_renderer.request_stop();
state.hmd_renderer.join();
}
state.hmd_renderer = std::jthread([&state](std::stop_token stop_token) {
if (!state.vive)
return;
BT_SetTerminate();
LOG("start hmd render thread");
App::I->has_vr = true;
vr_running.store(true, std::memory_order_relaxed);
state.vive->on_analog_button = std::bind(&App::vr_analog, App::I, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
state.vive->on_button = std::bind(&App::vr_digital, App::I, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
App::I->render_task([] {
App::I->vr_draw_ui();
});
const float target_tick_rate = 90;
auto t0 = GetTickCount64();
float one_sec_timer = 0;
int frames = 0;
while (!stop_token.stop_requested() && vr_running.load(std::memory_order_relaxed) && running.load(std::memory_order_relaxed) == 1 && state.vive->Valid())
{
std::unique_lock<std::mutex> lock(state.hmd_render_mutex);
auto t1 = GetTickCount64();
float dt = (float)(t1 - t0) / 1000.0f;
one_sec_timer += dt;
if (one_sec_timer >= 1.f)
{
one_sec_timer = 0;
vr_frames.store(frames, std::memory_order_relaxed);
frames = 0;
}
frames++;
state.vive->Update();
App::I->vr_active = state.vive->m_active;
App::I->vr_controllers[0] = state.vive->m_controllers[0];
App::I->vr_head = state.vive->m_pose;
App::I->vr_update(dt);
if (vr_running.load(std::memory_order_relaxed) && state.vive->m_active)
{
App::I->render_task([&state] {
state.vive->Draw();
});
}
const int framerate = (1.f / target_tick_rate) * 1000;
const int diff = framerate - (t1 - t0);
//state.hmd_render_cv.wait_for(lock, std::chrono::milliseconds(diff));
t0 = t1;
}
App::I->vr_active = false;
App::I->has_vr = false;
vr_running.store(false, std::memory_order_relaxed);
state.vive->Terminate();
LOG("hmd renderer terminated");
});
return true;
} }
void win32_vr_stop() void win32_vr_stop()
{ {
auto& state = retained_state(); auto& state = retained_state();
if (state.vive) pp::platform::windows::stop_vr_shell(state.vr);
{
vr_running.store(false, std::memory_order_relaxed);
if (state.hmd_renderer.joinable())
{
state.hmd_renderer.request_stop();
state.hmd_renderer.join();
}
state.vive->Terminate();
state.vive.reset();
}
} }
void win32_save_window_state() void win32_save_window_state()
@@ -947,11 +850,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{ {
case WM_USER_CLOSE: case WM_USER_CLOSE:
running.store(0, std::memory_order_relaxed); running.store(0, std::memory_order_relaxed);
if (state.hmd_renderer.joinable()) pp::platform::windows::request_stop_and_join_vr_thread(state.vr);
{
state.hmd_renderer.request_stop();
state.hmd_renderer.join();
}
App::I->runtime().ui_thread_stop(); App::I->runtime().ui_thread_stop();
App::I->runtime().render_thread_stop(); App::I->runtime().render_thread_stop();
App::I->terminate(); App::I->terminate();

View File

@@ -475,14 +475,62 @@ void NodeCanvas::draw()
plane_index, plane_index,
m_canvas->m_layers[layer_index]->m_opacity); m_canvas->m_layers[layer_index]->m_opacity);
pp::panopainter::execute_legacy_canvas_draw_merge_layer_path( glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale);
m_canvas->m_current_stroke && m_canvas->m_current_mode == kCanvasMode::Erase && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index, if (b->m_pattern_flipx)
m_canvas->m_current_stroke && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index, patt_scale.x *= -1.f;
use_blend, if (b->m_pattern_flipy)
visit.first_frame, patt_scale.y *= -1.f;
visit.last_frame,
[&](int frame) { const auto layer_path_execution = pp::panopainter::make_legacy_canvas_draw_merge_layer_path_gl_execution(
return pp::app::animation_onion_frame_alpha(onion_range, frame); {
.temporary_erase = {
.mvp = plane_mvp_z,
.texture_slot = 0,
.stroke_texture_slot = 1,
.mask_texture_slot = 2,
.mask_enabled = m_canvas->m_smask_active,
},
.temporary_paint = {
.resolution = Canvas::I->m_size,
.pattern = {
.scale = patt_scale,
.invert = static_cast<float>(b->m_pattern_invert),
.brightness = b->m_pattern_brightness,
.contrast = b->m_pattern_contrast,
.depth = b->m_pattern_depth,
.blend_mode = b->m_pattern_blend_mode,
.offset = Canvas::I->m_pattern_offset,
},
.mvp = plane_mvp_z,
.layer_alpha = 1.0f,
.alpha_lock = m_canvas->m_layers[layer_index]->m_alpha_locked,
.mask_enabled = m_canvas->m_smask_active,
.use_fragcoord = false,
.blend_mode = b->m_blend_mode,
.use_dual = b->m_dual_enabled,
.dual_blend_mode = b->m_dual_blend_mode,
.dual_alpha = b->m_dual_opacity,
.use_pattern = b->m_pattern_enabled && !b->m_pattern_eachsample,
},
.layer_texture = {
.mvp = plane_mvp_z,
.texture_slot = 0,
.alpha = 1.f,
.highlight = m_canvas->m_layers[layer_index]->m_hightlight,
},
.blend = {
.shader = {
.mvp = glm::ortho(-1, 1, -1, 1),
.texture_slot = 0,
.destination_texture_slot = 2,
.use_destination_texture = copy_blend_destination,
.blend_mode = m_canvas->m_layers[layer_index]->m_blend_mode,
.alpha = 1.f,
},
.copy_destination = copy_blend_destination,
},
.use_nearest_sampler = m_canvas->m_cam_fov < 20.f,
.use_dual_texture = b->m_dual_enabled,
}, },
{ {
.bind_blender_framebuffer = [&] { .bind_blender_framebuffer = [&] {
@@ -494,139 +542,65 @@ void NodeCanvas::draw()
.unbind_blender_framebuffer = [&] { .unbind_blender_framebuffer = [&] {
m_blender_rtt.unbindFramebuffer(); m_blender_rtt.unbindFramebuffer();
}, },
.prepare_temporary_erase = [&] { .bind_sampler = [&](int unit) {
m_sampler.bind(0); m_sampler.bind(unit);
m_sampler.bind(1);
m_sampler.bind(2);
//ShaderManager::u_vec2(kShaderUniform::Resolution, zw(m_canvas->m_box) / zoom);
//ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[layer_index]->m_alpha_locked);
pp::panopainter::setup_legacy_stroke_erase_shader(
pp::panopainter::LegacyStrokeEraseUniforms {
.mvp = plane_mvp_z,
.texture_slot = 0,
.stroke_texture_slot = 1,
.mask_texture_slot = 2,
.mask_enabled = m_canvas->m_smask_active,
});
set_active_texture_unit(1);
m_canvas->m_tmp[plane_index].bindTexture();
set_active_texture_unit(2);
m_canvas->m_smask.rtt(plane_index).bindTexture();
}, },
.cleanup_temporary_erase = [&] { .bind_nearest_sampler = [&](int unit) {
set_active_texture_unit(2); m_sampler_nearest.bind(unit);
m_canvas->m_smask.rtt(plane_index).unbindTexture(); },
set_active_texture_unit(1); .bind_stencil_sampler = [&](int unit) {
m_sampler_stencil.bind(unit);
},
.set_active_texture_unit = [&](int unit) {
set_active_texture_unit(unit);
},
.bind_temporary_texture = [&] {
m_canvas->m_tmp[plane_index].bindTexture();
},
.unbind_temporary_texture = [&] {
m_canvas->m_tmp[plane_index].unbindTexture(); m_canvas->m_tmp[plane_index].unbindTexture();
}, },
.prepare_temporary_paint = [&] { .bind_smask_texture = [&] {
m_sampler.bind(0);
m_sampler.bind(1);
m_sampler.bind(2);
m_sampler.bind(3);
m_sampler_stencil.bind(4);
glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale);
if (b->m_pattern_flipx) patt_scale.x *= -1.f;
if (b->m_pattern_flipy) patt_scale.y *= -1.f;
pp::panopainter::setup_legacy_stroke_composite_shader(
pp::panopainter::LegacyStrokeCompositeUniforms {
.resolution = Canvas::I->m_size,
.pattern = {
.scale = patt_scale,
.invert = static_cast<float>(b->m_pattern_invert),
.brightness = b->m_pattern_brightness,
.contrast = b->m_pattern_contrast,
.depth = b->m_pattern_depth,
.blend_mode = b->m_pattern_blend_mode,
.offset = Canvas::I->m_pattern_offset,
},
.mvp = plane_mvp_z,
.layer_alpha = 1.0f,
.alpha_lock = m_canvas->m_layers[layer_index]->m_alpha_locked,
.mask_enabled = m_canvas->m_smask_active,
.use_fragcoord = false,
.blend_mode = b->m_blend_mode,
.use_dual = b->m_dual_enabled,
.dual_blend_mode = b->m_dual_blend_mode,
.dual_alpha = b->m_dual_opacity,
.use_pattern = b->m_pattern_enabled && !b->m_pattern_eachsample,
});
set_active_texture_unit(1);
m_canvas->m_tmp[plane_index].bindTexture();
set_active_texture_unit(2);
m_canvas->m_smask.rtt(plane_index).bindTexture(); m_canvas->m_smask.rtt(plane_index).bindTexture();
set_active_texture_unit(3); },
if (b->m_dual_enabled) .unbind_smask_texture = [&] {
m_canvas->m_tmp_dual[plane_index].bindTexture(); m_canvas->m_smask.rtt(plane_index).unbindTexture();
set_active_texture_unit(4); },
.bind_temporary_dual_texture = [&] {
m_canvas->m_tmp_dual[plane_index].bindTexture();
},
.unbind_temporary_dual_texture = [&] {
m_canvas->m_tmp_dual[plane_index].unbindTexture();
},
.bind_pattern_texture = [&] {
b->m_pattern_texture ? b->m_pattern_texture ?
b->m_pattern_texture->bind() : b->m_pattern_texture->bind() :
unbind_texture_2d(); unbind_texture_2d();
}, },
.cleanup_temporary_paint = [&] { .draw_face = [&] {
set_active_texture_unit(3);
if (b->m_dual_enabled)
m_canvas->m_tmp_dual[plane_index].unbindTexture();
set_active_texture_unit(2);
m_canvas->m_smask.rtt(plane_index).unbindTexture();
set_active_texture_unit(1);
m_canvas->m_tmp[plane_index].unbindTexture();
},
.prepare_layer_texture = [&] {
m_canvas->m_cam_fov < 20.f ? m_sampler_nearest.bind(0) : m_sampler.bind(0);
pp::panopainter::setup_legacy_canvas_draw_merge_texture_alpha_shader(
pp::panopainter::LegacyCanvasDrawMergeTextureAlphaUniforms {
.mvp = plane_mvp_z,
.texture_slot = 0,
.alpha = 1.f,
.highlight = m_canvas->m_layers[layer_index]->m_hightlight,
});
},
.cleanup_layer_texture = [&] {
},
.draw_blend = [&] {
m_sampler.bind(0);
m_sampler.bind(2);
pp::panopainter::setup_legacy_canvas_draw_merge_texture_blend_shader(
pp::panopainter::LegacyCanvasDrawMergeTextureBlendUniforms {
.mvp = glm::ortho(-1, 1, -1, 1),
.texture_slot = 0,
.destination_texture_slot = 2,
.use_destination_texture = copy_blend_destination,
.blend_mode = m_canvas->m_layers[layer_index]->m_blend_mode,
.alpha = 1.f,
});
set_active_texture_unit(0);
m_blender_rtt.bindTexture();
if (copy_blend_destination)
{
set_active_texture_unit(2);
m_blender_bg.bind();
copy_framebuffer_to_texture_2d(
0,
0,
0,
0,
m_blender_bg.size().x,
m_blender_bg.size().y);
}
m_face_plane.draw_fill(); m_face_plane.draw_fill();
},
if (copy_blend_destination) .bind_blender_texture = [&] {
{ m_blender_rtt.bindTexture();
set_active_texture_unit(2); },
m_blender_bg.unbind(); .unbind_blender_texture = [&] {
}
set_active_texture_unit(0);
m_blender_rtt.unbindTexture(); m_blender_rtt.unbindTexture();
}, },
.bind_destination_texture = [&] {
m_blender_bg.bind();
},
.unbind_destination_texture = [&] {
m_blender_bg.unbind();
},
.copy_destination_framebuffer = [&] {
copy_framebuffer_to_texture_2d(
0,
0,
0,
0,
m_blender_bg.size().x,
m_blender_bg.size().y);
},
.draw_debug_outline = .draw_debug_outline =
#ifdef _DEBUG #ifdef _DEBUG
[&] { [&] {
@@ -648,6 +622,17 @@ void NodeCanvas::draw()
#endif #endif
.draw_frame = draw_layer_frame, .draw_frame = draw_layer_frame,
}); });
pp::panopainter::execute_legacy_canvas_draw_merge_layer_path(
m_canvas->m_current_stroke && m_canvas->m_current_mode == kCanvasMode::Erase && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index,
m_canvas->m_current_stroke && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index,
use_blend,
visit.first_frame,
visit.last_frame,
[&](int frame) {
return pp::app::animation_onion_frame_alpha(onion_range, frame);
},
layer_path_execution);
}, },
[&](const char* message) { [&](const char* message) {
LOG("NodeCanvas onion frame range failed: %s", message); LOG("NodeCanvas onion frame range failed: %s", message);

View File

@@ -0,0 +1,141 @@
#pragma once
#include "app.h"
#include "hmd.h"
#include "log.h"
#include <atomic>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
namespace pp::platform::windows {
struct VrShellState final {
std::jthread hmd_renderer;
std::mutex hmd_render_mutex;
std::condition_variable hmd_render_cv;
std::unique_ptr<Vive> vive;
std::atomic<int> vr_frames{0};
std::atomic_bool vr_running{false};
};
inline int current_vr_fps(const VrShellState& state)
{
return state.vr_frames.load(std::memory_order_relaxed);
}
inline void request_stop_and_join_vr_thread(VrShellState& state)
{
if (state.hmd_renderer.joinable())
{
state.hmd_renderer.request_stop();
state.hmd_renderer.join();
}
}
inline bool start_vr_shell(VrShellState& state, bool sandboxed, std::atomic<int>& running)
{
if (sandboxed)
return false;
state.vive = std::make_unique<Vive>();
state.vive->on_draw = [](const glm::mat4& proj, const glm::mat4& view, const glm::mat4& pose) {
App::I->vr_draw(proj, view, pose);
};
if (!state.vive->Initialize())
{
state.vive.reset();
LOG("VR: failed to initialize vive");
return false;
}
if (state.hmd_renderer.joinable())
{
state.hmd_renderer.request_stop();
state.hmd_renderer.join();
}
state.hmd_renderer = std::jthread([&state, &running](std::stop_token stop_token) {
if (!state.vive)
return;
BT_SetTerminate();
LOG("start hmd render thread");
App::I->has_vr = true;
state.vr_running.store(true, std::memory_order_relaxed);
state.vive->on_analog_button = std::bind(&App::vr_analog, App::I, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
state.vive->on_button = std::bind(&App::vr_digital, App::I, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
App::I->render_task([] {
App::I->vr_draw_ui();
});
const float target_tick_rate = 90;
auto t0 = GetTickCount64();
float one_sec_timer = 0;
int frames = 0;
while (!stop_token.stop_requested() &&
state.vr_running.load(std::memory_order_relaxed) &&
running.load(std::memory_order_relaxed) == 1 &&
state.vive->Valid())
{
std::unique_lock<std::mutex> lock(state.hmd_render_mutex);
auto t1 = GetTickCount64();
float dt = (float)(t1 - t0) / 1000.0f;
one_sec_timer += dt;
if (one_sec_timer >= 1.f)
{
one_sec_timer = 0;
state.vr_frames.store(frames, std::memory_order_relaxed);
frames = 0;
}
frames++;
state.vive->Update();
App::I->vr_active = state.vive->m_active;
App::I->vr_controllers[0] = state.vive->m_controllers[0];
App::I->vr_head = state.vive->m_pose;
App::I->vr_update(dt);
if (state.vr_running.load(std::memory_order_relaxed) && state.vive->m_active)
{
App::I->render_task([&state] {
state.vive->Draw();
});
}
const int framerate = (1.f / target_tick_rate) * 1000;
const int diff = framerate - (t1 - t0);
(void)diff;
//state.hmd_render_cv.wait_for(lock, std::chrono::milliseconds(diff));
t0 = t1;
}
App::I->vr_active = false;
App::I->has_vr = false;
state.vr_running.store(false, std::memory_order_relaxed);
state.vive->Terminate();
LOG("hmd renderer terminated");
});
return true;
}
inline void stop_vr_shell(VrShellState& state)
{
if (state.vive)
{
state.vr_running.store(false, std::memory_order_relaxed);
request_stop_and_join_vr_thread(state);
state.vive->Terminate();
state.vive.reset();
}
}
}