Trim main task queue, recording label, and canvas draw callbacks

This commit is contained in:
2026-06-16 08:57:15 +02:00
parent 667589f1f6
commit ad76aeb751
8 changed files with 130 additions and 50 deletions

View File

@@ -18,6 +18,22 @@ agent or engineer to remove them without reconstructing context from chat.
## Reductions
- 2026-06-16: `DEBT-0003` was narrowed again. `main.cpp` main-thread queued
task state now lives behind a narrow retained helper instead of
`RetainedState.main_tasklist` / `main_task_mutex` directly; broader Win32
bootstrap/runtime ownership remains.
- 2026-06-16: `DEBT-0037` was narrowed again. `App::update_rec_frames()` in
`src/app.cpp` now delegates recording label refresh through
`update_legacy_recording_frame_label(...)` in
`src/legacy_recording_services.cpp` instead of building the label directly;
retained recording label lookup, encoder-state reads, and MP4 execution
remain.
- 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` non-`draw_merged`
per-frame layer draw callback setup now routes through
`make_legacy_canvas_draw_merge_layer_frame_draw(...)` in
`src/legacy_canvas_draw_merge_services.h` instead of keeping that callback
body inline in `NodeCanvas::draw()`; broader canvas draw orchestration and
retained GL resource ownership remain.
- 2026-06-16: `DEBT-0003` was narrowed again. The Win32 async GL/context lock
state now lives in `src/platform_windows/windows_platform_services.cpp`
instead of `src/main.cpp` retained state, and `main.cpp` only seeds the

View File

@@ -117,9 +117,10 @@ Current architecture mismatches that must be treated as real blockers:
checkerboard background setup now route through retained draw-merge helpers,
with the cache-to-screen checkerboard-plane callback setup also reduced and
the merged-path checkerboard background-plane callback plus per-plane
merged-texture draw callback plus the smoothing-mask face shader/draw pass
plus heightmap, current-mode, and grid-mode callback setup now routed
through the same retained helper family.
merged-texture draw callback plus non-`draw_merged` per-frame layer draw
callback plus the smoothing-mask face shader/draw pass plus heightmap,
current-mode, and grid-mode callback setup now routed through the same
retained helper family.
- `app_layout.cpp` and `app_dialogs.cpp` are still mixed shell/controller files
rather than thin composition/binding surfaces.
- `App`, `Canvas`, `Node`, retained workers, and platform entrypoints still use
@@ -137,13 +138,17 @@ Current architecture mismatches that must be treated as real blockers:
retained local state object instead of separate process-wide globals, the
Win32 async GL/context lock state now lives under
`src/platform_windows/windows_platform_services.cpp` instead of `main.cpp`
retained state, the canvas async worker now sits behind a named retained
retained state, the main-thread queued task state now sits behind a narrow
retained helper instead of `RetainedState.main_tasklist` /
`main_task_mutex`, the canvas async worker now sits behind a named retained
local worker-state helper instead of a bare static accessor, the
prepared-file worker and the canvas async import/export/save/open worker now
live under `AppRuntime` instead of retained static app-events/canvas
workers, and `App::rec_loop()` now delegates worker-iteration orchestration
into the retained recording bridge even though that retained recording path
still owns the worker-side readback flow.
workers, `App::rec_loop()` now delegates worker-iteration orchestration into
the retained recording bridge, and `App::update_rec_frames()` now delegates
recording label refresh through that same retained recording path even though
the bridge still owns worker-side readback flow and encoder-state label
reads.
- Modern C++23 usage exists in extracted components, especially `std::span`,
explicit result/status objects, and a few concepts, but the live app still
does not consistently express ownership, thread affinity, or renderer

View File

@@ -159,6 +159,10 @@ Current slice:
also routes through `make_legacy_canvas_draw_merge_layer_texture_draw(...)`,
but the node still owns broader live layer traversal and renderer-state
sequencing.
- `NodeCanvas` non-`draw_merged` per-frame layer draw callback setup now also
routes through `make_legacy_canvas_draw_merge_layer_frame_draw(...)`, but
the node still owns broader live layer traversal and renderer-state
sequencing.
- `NodeCanvas` smoothing-mask face shader setup plus per-face draw execution
now also route through
`execute_legacy_canvas_draw_merge_smask_faces(...)`, but the node still owns
@@ -364,6 +368,8 @@ Current slice:
`src/platform_windows/windows_platform_services.cpp` instead of `main.cpp`
retained state, and `main.cpp` only seeds that platform-owned context handle
pair during initialization and context recreation
- `main.cpp` main-thread queued task state now sits behind a narrow retained
helper instead of `RetainedState.main_tasklist` / `main_task_mutex` directly
- 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
@@ -436,6 +442,9 @@ Current slice:
retained recording bridge in `src/legacy_recording_services.cpp`, while
`App::update()` no longer carries the dead update mutex residue; retained
recording loop control, readback ownership, and MP4 execution are still open
- `App::update_rec_frames()` now delegates recording label refresh through
`src/legacy_recording_services.cpp`, but retained recording label lookup,
encoder-state reads, and MP4 execution still stay on the legacy bridge
Write scope:
- `src/canvas.cpp`

View File

@@ -214,6 +214,7 @@ void App::initLog()
namespace pp::panopainter
{
bool process_legacy_recording_worker_iteration(App& app);
void update_legacy_recording_frame_label(App& app);
}
bool App::check_license()
@@ -566,14 +567,7 @@ void App::update_memory_usage(size_t bytes)
void App::update_rec_frames()
{
if (auto txt = layout[main_id]->find<NodeText>("txt-rec"))
{
const auto label = pp::app::make_recording_frame_label(
rec_running,
Canvas::I->m_encoder != nullptr,
Canvas::I->m_encoder ? Canvas::I->m_encoder->frames_count() : 0);
txt->set_text(label.text.c_str());
}
pp::panopainter::update_legacy_recording_frame_label(*this);
}
int App::res_from_index(int i)

View File

@@ -529,7 +529,25 @@ template <typename LayerMergeT, typename SamplerT, typename FacePlaneT, typename
.unbind_layer_texture = [layer_merge, plane_index] {
layer_merge->rtt(plane_index).unbindTexture();
},
});
});
};
}
template <typename LayerT, typename FacePlaneT, typename SetActiveTextureUnit>
[[nodiscard]] inline auto make_legacy_canvas_draw_merge_layer_frame_draw(
LayerT* layer,
FacePlaneT* face_plane,
SetActiveTextureUnit set_active_texture_unit,
int plane_index,
float layer_opacity)
{
return [layer, face_plane, set_active_texture_unit, plane_index, layer_opacity](int frame, float onion_alpha) {
ShaderManager::u_float(kShaderUniform::Alpha, layer_opacity * onion_alpha);
set_active_texture_unit(0);
layer->rtt(plane_index, frame).bindTexture();
face_plane->draw_fill();
set_active_texture_unit(0);
layer->rtt(plane_index, frame).unbindTexture();
};
}

View File

@@ -4,11 +4,14 @@
#include "app.h"
#include "canvas.h"
#include "app_core/app_status.h"
#include "legacy_app_dialog_services.h"
#include "legacy_ui_overlay_services.h"
#include "node_progress_bar.h"
namespace pp::panopainter {
void update_legacy_recording_frame_label(App& app);
namespace {
pp::app::RecordingWorkerIterationPlan make_recording_worker_iteration_plan(App& app)
@@ -42,9 +45,25 @@ void encode_recording_frame(
LOG("rec frame encoded");
if (plan.update_frame_label)
app.update_rec_frames();
update_legacy_recording_frame_label(app);
}
} // namespace
void update_legacy_recording_frame_label(App& app)
{
if (auto txt = app.layout[app.main_id]->find<NodeText>("txt-rec"))
{
const auto label = pp::app::make_recording_frame_label(
app.rec_running,
Canvas::I->m_encoder != nullptr,
Canvas::I->m_encoder ? Canvas::I->m_encoder->frames_count() : 0);
txt->set_text(label.text.c_str());
}
}
namespace {
class LegacyRecordingServices final : public pp::app::RecordingServices {
public:
explicit LegacyRecordingServices(App& app) noexcept
@@ -54,7 +73,7 @@ public:
void start_thread() override
{
app_.update_rec_frames();
update_legacy_recording_frame_label(app_);
app_.rec_thread = std::jthread(&App::rec_loop, &app_);
}
@@ -65,7 +84,7 @@ public:
app_.rec_cv.notify_all();
if (app_.rec_thread.joinable())
app_.rec_thread.join();
app_.update_rec_frames();
update_legacy_recording_frame_label(app_);
}
void delete_recorded_files() override
@@ -80,7 +99,7 @@ public:
void update_frame_label() override
{
app_.update_rec_frames();
update_legacy_recording_frame_label(app_);
}
void begin_export(int progress_total) override

View File

@@ -54,8 +54,6 @@ struct RetainedState
std::map<kKey, int> vkey_map;
wchar_t window_title[512]{};
std::jthread hmd_renderer;
std::deque<std::packaged_task<void()>> main_tasklist;
std::mutex main_task_mutex;
float timer_stylus = 0;
float timer_ink_touch = 0;
float timer_ink_pen = 0;
@@ -72,6 +70,46 @@ RetainedState& retained_state()
return state;
}
namespace {
struct RetainedMainTaskQueue final
{
std::deque<std::packaged_task<void()>> tasklist;
std::mutex task_mutex;
};
RetainedMainTaskQueue& retained_main_task_queue()
{
static RetainedMainTaskQueue queue;
return queue;
}
template <typename Callable>
void enqueue_main_task(Callable&& task)
{
auto& queue = retained_main_task_queue();
std::lock_guard<std::mutex> lock(queue.task_mutex);
queue.tasklist.emplace_back(std::forward<Callable>(task));
}
void drain_main_tasks()
{
std::deque<std::packaged_task<void()>> working_list;
{
auto& queue = retained_main_task_queue();
std::lock_guard<std::mutex> lock(queue.task_mutex);
working_list = std::move(queue.tasklist);
}
while (!working_list.empty())
{
working_list.front()();
working_list.pop_front();
}
}
}
std::atomic<int> vr_frames{0};
std::atomic<int> running{-1};
std::atomic_bool vr_running{false};
@@ -163,8 +201,7 @@ std::string GetLastErrorAsString()
void destroy_window()
{
auto& state = retained_state();
std::lock_guard<std::mutex> lock(state.main_task_mutex);
state.main_tasklist.emplace_back([hWnd = state.hWnd] {
enqueue_main_task([hWnd = state.hWnd] {
PostMessage(hWnd, WM_USER_CLOSE, 0, 0);
});
}
@@ -225,8 +262,7 @@ void win32_update_fps(int frames)
swprintf_s(title_fps, L"%s - %d fps", state.window_title, frames);
{
std::lock_guard<std::mutex> lock(state.main_task_mutex);
state.main_tasklist.emplace_back([hWnd = state.hWnd] {
enqueue_main_task([hWnd = state.hWnd] {
SetWindowText(hWnd, title_fps);
});
}
@@ -991,22 +1027,7 @@ int main(int argc, char** argv)
}
// list of tasks for the main thread
{
std::deque<std::packaged_task<void()>> working_list;
{
std::lock_guard<std::mutex> lock(state.main_task_mutex);
working_list = std::move(state.main_tasklist);
}
if (!working_list.empty())
{
while (!working_list.empty())
{
working_list.front()();
working_list.pop_front();
}
}
}
drain_main_tasks();
}
// Clean up
WacomTablet::I.terminate();

View File

@@ -469,14 +469,12 @@ void NodeCanvas::draw()
m_canvas->m_plane_transform[plane_index] *
glm::translate(glm::vec3(0, 0, -1));
const auto draw_layer_frame = [&](int frame, float onion_alpha) {
ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_layers[layer_index]->m_opacity * onion_alpha);
set_active_texture_unit(0);
m_canvas->m_layers[layer_index]->rtt(plane_index, frame).bindTexture();
m_face_plane.draw_fill();
set_active_texture_unit(0);
m_canvas->m_layers[layer_index]->rtt(plane_index, frame).unbindTexture();
};
const auto draw_layer_frame = pp::panopainter::make_legacy_canvas_draw_merge_layer_frame_draw(
m_canvas->m_layers[layer_index].get(),
&m_face_plane,
set_active_texture_unit,
plane_index,
m_canvas->m_layers[layer_index]->m_opacity);
pp::panopainter::execute_legacy_canvas_draw_merge_layer_plane(
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,