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_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

View File

@@ -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

View File

@@ -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

View File

@@ -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 <codec_api.h>
#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<NodeInputBox> 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<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();
};
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<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();
};
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<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();
};
pp::panopainter::open_about_dialog(*this);
}
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)
{
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<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);
pp::panopainter::open_whatsnew_dialog(*this, force_show);
}
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
#include "legacy_canvas_stroke_composite_services.h"
#include "legacy_canvas_stroke_erase_services.h"
#include "shader.h"
#include <array>
@@ -130,6 +132,40 @@ struct LegacyCanvasDrawMergeLayerPathExecution {
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 {
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 <typename PlanOnionRange, typename ShouldDrawPlane, typename VisitLayerPlane, typename LogOnionRangeFailure>
inline void execute_legacy_canvas_draw_layer_traversal(
size_t layer_count,

View File

@@ -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 <shellscalingapi.h>
#include <WbemCli.h>
#include <deque>
#include <chrono>
#include "wacom.h"
#include "abr.h"
@@ -32,9 +30,7 @@
#include <iomanip>
#include <ctime>
#include <sstream>
#include <memory>
#include <atomic>
#include <stop_token>
#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<kKey, int> 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> vive;
pp::platform::windows::VrShellState vr;
};
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));
}
std::atomic<int> vr_frames{0};
std::atomic<int> 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<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;
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();

View File

@@ -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<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 = [&] {
@@ -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<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);
.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);

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();
}
}
}