diff --git a/cmake/PanoPainterSources.cmake b/cmake/PanoPainterSources.cmake index c485ee93..e266dbb4 100644 --- a/cmake/PanoPainterSources.cmake +++ b/cmake/PanoPainterSources.cmake @@ -92,6 +92,7 @@ set(PP_PANOPAINTER_APP_SOURCES src/app_cloud.cpp src/app_commands.cpp src/app_dialogs.cpp + src/app_dialogs_info_openers.cpp src/app_events.cpp src/app_layout.cpp src/app_layout_about_layer_menu.cpp diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 77cba2b0..69076a2e 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -83,12 +83,12 @@ Current hotspot files: - `src/app_layout.cpp`: 1249 lines - `src/canvas_modes.cpp`: 1798 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_stroke_preview.cpp`: 933 lines -- `src/node_canvas.cpp`: 968 lines +- `src/node_canvas.cpp`: 953 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: @@ -124,8 +124,11 @@ Current architecture mismatches that must be treated as real blockers: per-layer render-path orchestration now also route through retained 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 - through `execute_legacy_canvas_draw_layer_traversal(...)` while the heavier - per-layer GL setup and draw lambdas still live in `src/node_canvas.cpp`. + through `execute_legacy_canvas_draw_layer_traversal(...)`, while the heavier + 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 rather than thin composition/binding surfaces, even though tools-menu binding 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 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 - `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 global singleton reach, raw observer pointers, retained static worker 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, 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`, + 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 the retained recording bridge, `App::update_rec_frames()` now delegates recording label refresh through that same retained recording path, and the diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 3f2069af..397263d6 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -195,8 +195,11 @@ Current slice: broader draw-loop and renderer-state shell sequencing. - `NodeCanvas` outer non-`draw_merged` layer/plane traversal, onion-range failure handling, and visit payload setup now also route through - `execute_legacy_canvas_draw_layer_traversal(...)`, but the node still owns - the heavier per-layer GL setup and draw lambdas. + `execute_legacy_canvas_draw_layer_traversal(...)`. +- `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: - `src/node_stroke_preview.cpp` @@ -325,6 +328,13 @@ Why now: `src/app_dialogs.cpp` still mixes document workflow decisions, export routing, 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: - `src/app_dialogs.cpp` - `src/legacy_app_dialog_services.*` @@ -417,6 +427,10 @@ Current slice: reset, and `WM_POINTERUPDATE` pen/touch handling now live in `src/platform_windows/windows_stylus_input.cpp` instead of `src/main.cpp`, 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 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 diff --git a/src/app_dialogs.cpp b/src/app_dialogs.cpp index 0325e9a6..a19badbf 100644 --- a/src/app_dialogs.cpp +++ b/src/app_dialogs.cpp @@ -11,18 +11,12 @@ #include "legacy_document_export_services.h" #include "legacy_document_layer_services.h" #include "legacy_document_session_services.h" -#include "legacy_preference_storage.h" #include "legacy_ui_overlay_services.h" #include "node_dialog_open.h" #include "node_dialog_browse.h" #include "node_dialog_resize.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_remote_page.h" -#include "node_shorcuts.h" #include #define MP4V2_NO_STDINT_DEFS @@ -34,6 +28,14 @@ #include "oculus_vr.h" #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 { [[nodiscard]] bool can_start_document_export(App& app, bool requires_license) @@ -208,80 +210,17 @@ std::shared_ptr App::input_box(const std::string& title, void App::dialog_usermanual() { - auto* overlay_anchor = layout[main_id]; - if (!overlay_anchor) { - LOG("User manual dialog open failed: main layout anchor is missing"); - return; - } - - auto dialog = pp::panopainter::make_legacy_overlay_node(*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(); - }; + pp::panopainter::open_usermanual_dialog(*this); } void App::dialog_changelog() { - auto* overlay_anchor = layout[main_id]; - if (!overlay_anchor) { - LOG("Changelog dialog open failed: main layout anchor is missing"); - return; - } - - auto dialog = pp::panopainter::make_legacy_overlay_node(*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(); - }; + pp::panopainter::open_changelog_dialog(*this); } void App::dialog_about() { - auto* overlay_anchor = layout[main_id]; - if (!overlay_anchor) { - LOG("About dialog open failed: main layout anchor is missing"); - return; - } - - auto dialog = pp::panopainter::make_legacy_overlay_node(*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(); - }; + pp::panopainter::open_about_dialog(*this); } void App::continue_document_workflow_after_optional_save(std::function action) @@ -841,68 +780,10 @@ void App::dialog_export_mp4() void App::dialog_whatsnew(bool force_show) { - auto* overlay_anchor = layout[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(); - const auto open_overlay = [overlay_anchor, overlay_handle](const std::shared_ptr& 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(); - 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); + pp::panopainter::open_whatsnew_dialog(*this, force_show); } void App::dialog_shortcuts() { - (void)pp::panopainter::add_legacy_overlay_node(*this); + pp::panopainter::open_shortcuts_dialog(*this); } diff --git a/src/app_dialogs_info_openers.cpp b/src/app_dialogs_info_openers.cpp new file mode 100644 index 00000000..2f67d685 --- /dev/null +++ b/src/app_dialogs_info_openers.cpp @@ -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 +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(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(app, "User manual"); +} + +void open_changelog_dialog(App& app) +{ + open_info_dialog(app, "Changelog"); +} + +void open_about_dialog(App& app) +{ + open_info_dialog(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(); + const auto open_overlay = [overlay_anchor, overlay_handle](const std::shared_ptr& 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(); + 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(app); +} + +} // namespace pp::panopainter diff --git a/src/legacy_canvas_draw_merge_services.h b/src/legacy_canvas_draw_merge_services.h index 8d8a879c..67be87d8 100644 --- a/src/legacy_canvas_draw_merge_services.h +++ b/src/legacy_canvas_draw_merge_services.h @@ -1,5 +1,7 @@ #pragma once +#include "legacy_canvas_stroke_composite_services.h" +#include "legacy_canvas_stroke_erase_services.h" #include "shader.h" #include @@ -130,6 +132,40 @@ struct LegacyCanvasDrawMergeLayerPathExecution { std::function 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 bind_blender_framebuffer; + std::function clear_blender_framebuffer; + std::function unbind_blender_framebuffer; + std::function bind_sampler; + std::function bind_nearest_sampler; + std::function bind_stencil_sampler; + std::function set_active_texture_unit; + std::function bind_temporary_texture; + std::function unbind_temporary_texture; + std::function bind_smask_texture; + std::function unbind_smask_texture; + std::function bind_temporary_dual_texture; + std::function unbind_temporary_dual_texture; + std::function bind_pattern_texture; + std::function draw_face; + std::function bind_blender_texture; + std::function unbind_blender_texture; + std::function bind_destination_texture; + std::function unbind_destination_texture; + std::function copy_destination_framebuffer; + std::function draw_debug_outline; + std::function draw_frame; +}; + struct LegacyCanvasDrawLayerVisit { size_t layer_index = 0; int plane_index = 0; @@ -679,6 +715,102 @@ inline void execute_legacy_canvas_draw_merge_layer_path( 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 inline void execute_legacy_canvas_draw_layer_traversal( size_t layer_count, diff --git a/src/main.cpp b/src/main.cpp index 4a2bfe88..eae473a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,20 +7,18 @@ #include "app.h" #include "canvas.h" #include "keymap.h" -#include "hmd.h" #include "legacy_gl_runtime_dispatch.h" #include "legacy_preference_storage.h" #include "renderer_gl/opengl_capabilities.h" #include "platform_windows/windows_platform_services.h" #include "platform_windows/windows_splash.h" #include "platform_windows/windows_stylus_input.h" +#include "platform_windows/windows_vr_shell.h" #include "../resource.h" #include #include #include -#include - #include "wacom.h" #include "abr.h" @@ -32,9 +30,7 @@ #include #include #include -#include #include -#include #define WM_USER_CLOSE (WM_USER + 1) #define WM_USER_WAKEUP (WM_USER + 2) @@ -55,11 +51,8 @@ struct RetainedState bool keys[256]{}; std::map vkey_map; wchar_t window_title[512]{}; - std::jthread hmd_renderer; bool sandboxed = false; - std::mutex hmd_render_mutex; - std::condition_variable hmd_render_cv; - std::unique_ptr vive; + pp::platform::windows::VrShellState vr; }; RetainedState& retained_state() @@ -125,9 +118,7 @@ void pp_windows_enqueue_main_task(std::packaged_task task) enqueue_main_task_bridge(std::move(task)); } -std::atomic vr_frames{0}; std::atomic running{-1}; -std::atomic_bool vr_running{false}; #ifdef USE_RENDERDOC RENDERDOC_API_1_4_0* rdoc_api = NULL; @@ -230,8 +221,8 @@ void win32_update_stylus(float dt) void win32_update_fps(int frames) { static wchar_t title_fps[512]; - const int vr_fps = vr_frames.load(std::memory_order_relaxed); auto& state = retained_state(); + const int vr_fps = pp::platform::windows::current_vr_fps(state.vr); if (App::I->vr_active) swprintf_s(title_fps, L"%s - %d fps - %d vr fps", state.window_title, frames, vr_fps); else @@ -520,101 +511,13 @@ void init_vk_map() bool win32_vr_start() { auto& state = retained_state(); - if (state.sandboxed) - return false; - - state.vive = std::make_unique(); - 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 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; + return pp::platform::windows::start_vr_shell(state.vr, state.sandboxed, running); } void win32_vr_stop() { auto& state = retained_state(); - if (state.vive) - { - 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(); - } + pp::platform::windows::stop_vr_shell(state.vr); } void win32_save_window_state() @@ -947,11 +850,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { case WM_USER_CLOSE: running.store(0, std::memory_order_relaxed); - if (state.hmd_renderer.joinable()) - { - state.hmd_renderer.request_stop(); - state.hmd_renderer.join(); - } + pp::platform::windows::request_stop_and_join_vr_thread(state.vr); App::I->runtime().ui_thread_stop(); App::I->runtime().render_thread_stop(); App::I->terminate(); diff --git a/src/node_canvas.cpp b/src/node_canvas.cpp index 21e4d252..2e6bff3f 100644 --- a/src/node_canvas.cpp +++ b/src/node_canvas.cpp @@ -475,14 +475,62 @@ void NodeCanvas::draw() plane_index, m_canvas->m_layers[layer_index]->m_opacity); - 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); + 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; + + const auto layer_path_execution = pp::panopainter::make_legacy_canvas_draw_merge_layer_path_gl_execution( + { + .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(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 = [&] { @@ -494,139 +542,65 @@ void NodeCanvas::draw() .unbind_blender_framebuffer = [&] { m_blender_rtt.unbindFramebuffer(); }, - .prepare_temporary_erase = [&] { - m_sampler.bind(0); - 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(); + .bind_sampler = [&](int unit) { + m_sampler.bind(unit); }, - .cleanup_temporary_erase = [&] { - set_active_texture_unit(2); - m_canvas->m_smask.rtt(plane_index).unbindTexture(); - set_active_texture_unit(1); + .bind_nearest_sampler = [&](int unit) { + m_sampler_nearest.bind(unit); + }, + .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(); }, - .prepare_temporary_paint = [&] { - 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(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); + .bind_smask_texture = [&] { m_canvas->m_smask.rtt(plane_index).bindTexture(); - set_active_texture_unit(3); - if (b->m_dual_enabled) - m_canvas->m_tmp_dual[plane_index].bindTexture(); - set_active_texture_unit(4); + }, + .unbind_smask_texture = [&] { + m_canvas->m_smask.rtt(plane_index).unbindTexture(); + }, + .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->bind() : unbind_texture_2d(); }, - .cleanup_temporary_paint = [&] { - 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); - } - + .draw_face = [&] { m_face_plane.draw_fill(); - - if (copy_blend_destination) - { - set_active_texture_unit(2); - m_blender_bg.unbind(); - } - set_active_texture_unit(0); + }, + .bind_blender_texture = [&] { + m_blender_rtt.bindTexture(); + }, + .unbind_blender_texture = [&] { 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 = #ifdef _DEBUG [&] { @@ -648,6 +622,17 @@ void NodeCanvas::draw() #endif .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) { LOG("NodeCanvas onion frame range failed: %s", message); diff --git a/src/platform_windows/windows_vr_shell.h b/src/platform_windows/windows_vr_shell.h new file mode 100644 index 00000000..f8d2cfaa --- /dev/null +++ b/src/platform_windows/windows_vr_shell.h @@ -0,0 +1,141 @@ +#pragma once + +#include "app.h" +#include "hmd.h" +#include "log.h" + +#include +#include +#include +#include +#include +#include + +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; + std::atomic 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& running) +{ + if (sandboxed) + return false; + + state.vive = std::make_unique(); + 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 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(); + } +} + +}