Own runtime threads and thin platform/canvas seams
This commit is contained in:
@@ -18,6 +18,24 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
|
||||
## Reductions
|
||||
|
||||
- 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` non-`draw_merged`
|
||||
per-layer/per-plane retained draw execution now routes through
|
||||
`execute_legacy_canvas_draw_merge_layer_plane(...)` in
|
||||
`src/legacy_canvas_draw_merge_services.h` instead of keeping the erase,
|
||||
temporary paint/composite, regular texture, and optional blend path fully
|
||||
inline in `NodeCanvas::draw()`; retained layer traversal and broader canvas
|
||||
renderer orchestration remain.
|
||||
- 2026-06-16: `DEBT-0003` was narrowed again. `AppRuntime` now owns its
|
||||
render/UI worker threads as `std::jthread` with explicit stop requests in
|
||||
`src/app_runtime.cpp` and `src/app_runtime.h` instead of raw `std::thread`
|
||||
ownership; retained app task call sites, singleton composition, and broader
|
||||
runtime/platform coupling remain.
|
||||
- 2026-06-16: `DEBT-0017`/`DEBT-0053` were narrowed again. iOS virtual-keyboard
|
||||
visibility and prepared-file save handoff now route through explicit Apple
|
||||
bridge callbacks in `src/platform_apple/apple_platform_services.*` consumed
|
||||
by `src/platform_legacy/legacy_platform_services.cpp` instead of direct
|
||||
`App::I` calls in those legacy platform methods; the retained Apple fallback
|
||||
adapter and broader platform singleton reach remain.
|
||||
- 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` smoothing-mask
|
||||
overlay draw, smoothing-mask face pass, grid keepalive draw, heightmap draw,
|
||||
and current-mode draw now route through
|
||||
|
||||
@@ -97,21 +97,25 @@ Current architecture mismatches that must be treated as real blockers:
|
||||
- `src/platform_apple/apple_platform_services.cpp` no longer reaches `App::I`
|
||||
directly, and Linux FPS title reporting now uses an injected callback, but
|
||||
retained Apple bridging in `platform_legacy` and other platform/app coupling
|
||||
remain.
|
||||
remain, even though iOS keyboard visibility and prepared-file save handoff
|
||||
now also route through explicit Apple bridge callbacks.
|
||||
- `src/platform_legacy/legacy_platform_services.*` is still part of the live
|
||||
app shell.
|
||||
- `pp_panopainter_ui` still depends on `pp_legacy_app`.
|
||||
- `Canvas`, `NodeCanvas`, and `NodeStrokePreview` still own too much live
|
||||
OpenGL execution around the renderer boundary, even though `NodeCanvas`
|
||||
display resolve, cache-to-screen composite, and post-draw mask/grid/current-
|
||||
mode sequencing now route through retained draw-merge helpers.
|
||||
display resolve, cache-to-screen composite, post-draw mask/grid/current-mode
|
||||
sequencing, and per-layer/per-plane retained draw execution now route
|
||||
through retained draw-merge helpers.
|
||||
- `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
|
||||
global singleton reach, raw observer pointers, retained static worker
|
||||
ownership in several app families, and ad hoc mutex/condition-variable
|
||||
ownership, even though most previously detached or raw app-facing worker
|
||||
launches now use owned `std::jthread` or service-owned worker queues.
|
||||
launches now use owned `std::jthread` or service-owned worker queues and
|
||||
`AppRuntime` now owns render/UI workers with explicit `std::jthread`
|
||||
shutdown semantics.
|
||||
- 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
|
||||
|
||||
@@ -137,6 +137,10 @@ Current slice:
|
||||
draw, heightmap draw, and current-mode draw now also route through
|
||||
`execute_legacy_canvas_draw_merge_post_draw(...)`, but broader canvas draw
|
||||
orchestration is still inline.
|
||||
- `NodeCanvas` non-`draw_merged` per-layer/per-plane retained draw execution
|
||||
now also routes through `execute_legacy_canvas_draw_merge_layer_plane(...)`,
|
||||
but the node still owns substantial live layer traversal and renderer state
|
||||
orchestration.
|
||||
|
||||
Write scope:
|
||||
- `src/node_stroke_preview.cpp`
|
||||
@@ -313,13 +317,21 @@ Mini-model packet:
|
||||
|
||||
#### ARC-APP-004 - Move Render/UI Queues Into An Owned App Runtime Service
|
||||
|
||||
Status: Ready
|
||||
Status: In Progress
|
||||
|
||||
Why now:
|
||||
`App` still owns static render/UI queues, mutexes, condition variables, and
|
||||
thread ids. That makes thread safety hard to reason about and keeps platform
|
||||
entrypoints coupled to the singleton.
|
||||
|
||||
Current slice:
|
||||
- render/UI queues, mutexes, condition variables, and thread identity already
|
||||
live in `AppRuntime`
|
||||
- `AppRuntime` render/UI worker ownership now also uses `std::jthread` plus
|
||||
explicit stop requests instead of raw `std::thread`
|
||||
- retained `App` composition, task call sites, and platform/runtime entrypoint
|
||||
coupling are still not fully reduced behind the runtime contract
|
||||
|
||||
Write scope:
|
||||
- `src/app.h`
|
||||
- `src/app.cpp`
|
||||
@@ -570,6 +582,9 @@ Current slice:
|
||||
- `LegacyPlatformServices::prepare_storage_paths()` now routes Apple path
|
||||
preparation through a narrow local helper instead of reading `App::I`
|
||||
directly in that method body
|
||||
- iOS virtual-keyboard visibility and prepared-file save handoff now also route
|
||||
through explicit Apple bridge callbacks instead of direct `App::I` calls in
|
||||
`LegacyPlatformServices`
|
||||
- retained Apple callback injection and broader `platform_legacy` singleton
|
||||
reach are still open
|
||||
|
||||
|
||||
@@ -52,20 +52,23 @@ void AppRuntime::render_thread_tick(App& app)
|
||||
}
|
||||
}
|
||||
|
||||
void AppRuntime::render_thread_main(App& app)
|
||||
void AppRuntime::render_thread_main(App& app, std::stop_token stop_token)
|
||||
{
|
||||
BT_SetTerminate();
|
||||
|
||||
render_thread_id_ = std::this_thread::get_id();
|
||||
render_running_ = pp::app::plan_app_thread_start().mark_running;
|
||||
while (render_running_)
|
||||
while (render_running_ && !stop_token.stop_requested())
|
||||
{
|
||||
std::deque<AppTask> working_list;
|
||||
pp::app::AppQueueDrainPlan drain_plan;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(render_task_mutex_);
|
||||
render_cv_.wait(lock, [this] { return render_tasklist_.empty() && render_running_ ? false : true; });
|
||||
render_cv_.wait(lock, [this, &stop_token]
|
||||
{
|
||||
return stop_token.stop_requested() || !render_running_ || !render_tasklist_.empty();
|
||||
});
|
||||
drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist_.size());
|
||||
working_list = std::move(render_tasklist_);
|
||||
}
|
||||
@@ -124,7 +127,7 @@ void AppRuntime::ui_thread_tick(App& app)
|
||||
}
|
||||
}
|
||||
|
||||
void AppRuntime::ui_thread_main(App& app)
|
||||
void AppRuntime::ui_thread_main(App& app, std::stop_token stop_token)
|
||||
{
|
||||
BT_SetTerminate();
|
||||
|
||||
@@ -141,14 +144,16 @@ void AppRuntime::ui_thread_main(App& app)
|
||||
float t_fps_counter = 0;
|
||||
float t_reloader = 0;
|
||||
int rendered_frames = 0;
|
||||
while (ui_running_)
|
||||
while (ui_running_ && !stop_token.stop_requested())
|
||||
{
|
||||
std::deque<AppTask> working_list;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(ui_task_mutex_);
|
||||
ui_cv_.wait_for(lock, std::chrono::milliseconds(app.idle_ms),
|
||||
[this] { return ui_tasklist_.empty() && ui_running_ ? false : true; });
|
||||
ui_cv_.wait_for(lock, std::chrono::milliseconds(app.idle_ms), [this, &stop_token]
|
||||
{
|
||||
return stop_token.stop_requested() || !ui_running_ || !ui_tasklist_.empty();
|
||||
});
|
||||
working_list = std::move(ui_tasklist_);
|
||||
}
|
||||
|
||||
@@ -223,7 +228,10 @@ void AppRuntime::render_thread_start(App& app)
|
||||
{
|
||||
const auto plan = pp::app::plan_app_thread_start();
|
||||
if (plan.start_thread)
|
||||
render_thread_ = std::thread(&AppRuntime::render_thread_main, this, std::ref(app));
|
||||
render_thread_ = std::jthread([this, &app](std::stop_token stop_token)
|
||||
{
|
||||
render_thread_main(app, stop_token);
|
||||
});
|
||||
render_running_ = plan.mark_running;
|
||||
}
|
||||
|
||||
@@ -232,6 +240,8 @@ void AppRuntime::render_thread_stop()
|
||||
const auto plan = pp::app::plan_app_thread_stop(render_thread_.joinable());
|
||||
if (plan.mark_not_running)
|
||||
render_running_ = false;
|
||||
if (plan.join_thread)
|
||||
render_thread_.request_stop();
|
||||
if (plan.notify_worker)
|
||||
render_cv_.notify_all();
|
||||
if (plan.join_thread)
|
||||
@@ -242,7 +252,10 @@ void AppRuntime::ui_thread_start(App& app)
|
||||
{
|
||||
const auto plan = pp::app::plan_app_thread_start();
|
||||
if (plan.start_thread)
|
||||
ui_thread_ = std::thread(&AppRuntime::ui_thread_main, this, std::ref(app));
|
||||
ui_thread_ = std::jthread([this, &app](std::stop_token stop_token)
|
||||
{
|
||||
ui_thread_main(app, stop_token);
|
||||
});
|
||||
ui_running_ = plan.mark_running;
|
||||
}
|
||||
|
||||
@@ -251,6 +264,8 @@ void AppRuntime::ui_thread_stop()
|
||||
const auto plan = pp::app::plan_app_thread_stop(ui_thread_.joinable());
|
||||
if (plan.mark_not_running)
|
||||
ui_running_ = false;
|
||||
if (plan.join_thread)
|
||||
ui_thread_.request_stop();
|
||||
if (plan.notify_worker)
|
||||
ui_cv_.notify_all();
|
||||
if (plan.join_thread)
|
||||
|
||||
@@ -43,12 +43,12 @@ public:
|
||||
void notify_ui_worker() noexcept;
|
||||
|
||||
void render_thread_tick(App& app);
|
||||
void render_thread_main(App& app);
|
||||
void render_thread_main(App& app, std::stop_token stop_token);
|
||||
void render_thread_start(App& app);
|
||||
void render_thread_stop();
|
||||
|
||||
void ui_thread_tick(App& app);
|
||||
void ui_thread_main(App& app);
|
||||
void ui_thread_main(App& app, std::stop_token stop_token);
|
||||
void ui_thread_start(App& app);
|
||||
void ui_thread_stop();
|
||||
|
||||
@@ -202,14 +202,14 @@ private:
|
||||
std::deque<AppTask> render_tasklist_;
|
||||
std::mutex render_task_mutex_;
|
||||
std::condition_variable render_cv_;
|
||||
std::thread render_thread_;
|
||||
std::jthread render_thread_;
|
||||
std::thread::id render_thread_id_;
|
||||
bool render_running_ = false;
|
||||
|
||||
std::deque<AppTask> ui_tasklist_;
|
||||
std::mutex ui_task_mutex_;
|
||||
std::condition_variable ui_cv_;
|
||||
std::thread ui_thread_;
|
||||
std::jthread ui_thread_;
|
||||
std::thread::id ui_thread_id_;
|
||||
bool ui_running_ = false;
|
||||
bool request_redraw_ = false;
|
||||
|
||||
@@ -94,6 +94,22 @@ struct LegacyCanvasDrawMergeLayerCompositeExecution {
|
||||
std::function<void()> execute_layer_blend;
|
||||
};
|
||||
|
||||
struct LegacyCanvasDrawMergeLayerPlaneExecution {
|
||||
std::function<void()> bind_blender_framebuffer;
|
||||
std::function<void()> clear_blender_framebuffer;
|
||||
std::function<void()> unbind_blender_framebuffer;
|
||||
std::function<void()> prepare_temporary_erase;
|
||||
std::function<void(int, float)> draw_temporary_erase_frame;
|
||||
std::function<void()> cleanup_temporary_erase;
|
||||
std::function<void()> prepare_temporary_paint;
|
||||
std::function<void(int, float)> draw_temporary_paint_frame;
|
||||
std::function<void()> cleanup_temporary_paint;
|
||||
std::function<void()> prepare_layer_texture;
|
||||
std::function<void(int, float)> draw_layer_texture_frame;
|
||||
std::function<void()> cleanup_layer_texture;
|
||||
std::function<void()> draw_blend;
|
||||
};
|
||||
|
||||
struct LegacyCanvasDrawMergeTemporaryCompositeExecution {
|
||||
std::function<void()> setup;
|
||||
std::function<void()> bind_samplers;
|
||||
@@ -419,6 +435,46 @@ inline void execute_legacy_canvas_draw_merge_layer_composite(
|
||||
}
|
||||
}
|
||||
|
||||
inline void execute_legacy_canvas_draw_merge_layer_plane(
|
||||
bool is_temporary_erase,
|
||||
bool is_temporary_paint,
|
||||
bool use_blend,
|
||||
int first_frame,
|
||||
int last_frame,
|
||||
const std::function<float(int)>& frame_alpha,
|
||||
const LegacyCanvasDrawMergeLayerPlaneExecution& execution)
|
||||
{
|
||||
if (use_blend) {
|
||||
execution.bind_blender_framebuffer();
|
||||
execution.clear_blender_framebuffer();
|
||||
}
|
||||
|
||||
if (is_temporary_erase) {
|
||||
execution.prepare_temporary_erase();
|
||||
for (int frame = first_frame; frame <= last_frame; frame++) {
|
||||
execution.draw_temporary_erase_frame(frame, frame_alpha(frame));
|
||||
}
|
||||
execution.cleanup_temporary_erase();
|
||||
} else if (is_temporary_paint) {
|
||||
execution.prepare_temporary_paint();
|
||||
for (int frame = first_frame; frame <= last_frame; frame++) {
|
||||
execution.draw_temporary_paint_frame(frame, frame_alpha(frame));
|
||||
}
|
||||
execution.cleanup_temporary_paint();
|
||||
} else {
|
||||
execution.prepare_layer_texture();
|
||||
for (int frame = first_frame; frame <= last_frame; frame++) {
|
||||
execution.draw_layer_texture_frame(frame, frame_alpha(frame));
|
||||
}
|
||||
execution.cleanup_layer_texture();
|
||||
}
|
||||
|
||||
if (use_blend) {
|
||||
execution.unbind_blender_framebuffer();
|
||||
execution.draw_blend();
|
||||
}
|
||||
}
|
||||
|
||||
inline void execute_legacy_canvas_draw_merge_temporary_composite(
|
||||
const LegacyCanvasDrawMergeTemporaryCompositeExecution& execution)
|
||||
{
|
||||
|
||||
@@ -468,12 +468,6 @@ void NodeCanvas::draw()
|
||||
m_canvas->m_layers[layer_index]->m_opacity == .0f || !faces))
|
||||
continue;
|
||||
|
||||
if (use_blend)
|
||||
{
|
||||
m_blender_rtt.bindFramebuffer();
|
||||
m_blender_rtt.clear();
|
||||
}
|
||||
|
||||
int z = (int)(m_canvas->m_layers.size() - i);
|
||||
auto plane_mvp_z = proj * camera *
|
||||
glm::scale(glm::vec3(z + 1)) *
|
||||
@@ -481,174 +475,177 @@ void NodeCanvas::draw()
|
||||
m_canvas->m_plane_transform[plane_index] *
|
||||
glm::translate(glm::vec3(0, 0, -1));
|
||||
|
||||
if (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_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();
|
||||
for (int frame = onion_range.first_frame; frame <= onion_range.last_frame; frame++)
|
||||
{
|
||||
const float onion_alpha = pp::app::animation_onion_frame_alpha(onion_range, frame);
|
||||
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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
else if(m_canvas->m_current_stroke && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index)
|
||||
{
|
||||
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();
|
||||
set_active_texture_unit(3);
|
||||
if (b->m_dual_enabled)
|
||||
m_canvas->m_tmp_dual[plane_index].bindTexture();
|
||||
set_active_texture_unit(4);
|
||||
b->m_pattern_texture ?
|
||||
b->m_pattern_texture->bind() :
|
||||
unbind_texture_2d();
|
||||
for (int frame = onion_range.first_frame; frame <= onion_range.last_frame; frame++)
|
||||
{
|
||||
const float onion_alpha = pp::app::animation_onion_frame_alpha(onion_range, frame);
|
||||
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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
else
|
||||
{
|
||||
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,
|
||||
});
|
||||
|
||||
for (int frame = onion_range.first_frame; frame <= onion_range.last_frame; frame++)
|
||||
{
|
||||
const float onion_alpha = pp::app::animation_onion_frame_alpha(onion_range, frame);
|
||||
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();
|
||||
m_canvas->m_layers[layer_index]->rtt(plane_index, frame).unbindTexture();
|
||||
}
|
||||
}
|
||||
|
||||
if (use_blend)
|
||||
{
|
||||
m_blender_rtt.unbindFramebuffer();
|
||||
}
|
||||
|
||||
// draw the blended
|
||||
if (use_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,
|
||||
});
|
||||
|
||||
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_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_canvas->m_layers[layer_index]->rtt(plane_index, frame).bindTexture();
|
||||
m_face_plane.draw_fill();
|
||||
|
||||
if (copy_blend_destination)
|
||||
{
|
||||
set_active_texture_unit(2);
|
||||
m_blender_bg.unbind();
|
||||
}
|
||||
set_active_texture_unit(0);
|
||||
m_blender_rtt.unbindTexture();
|
||||
}
|
||||
m_canvas->m_layers[layer_index]->rtt(plane_index, frame).unbindTexture();
|
||||
};
|
||||
|
||||
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,
|
||||
m_canvas->m_current_stroke && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index,
|
||||
use_blend,
|
||||
onion_range.first_frame,
|
||||
onion_range.last_frame,
|
||||
[&](int frame) {
|
||||
return pp::app::animation_onion_frame_alpha(onion_range, frame);
|
||||
},
|
||||
{
|
||||
.bind_blender_framebuffer = [&] {
|
||||
m_blender_rtt.bindFramebuffer();
|
||||
},
|
||||
.clear_blender_framebuffer = [&] {
|
||||
m_blender_rtt.clear();
|
||||
},
|
||||
.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();
|
||||
},
|
||||
.draw_temporary_erase_frame = [&](int frame, float onion_alpha) {
|
||||
draw_layer_frame(frame, onion_alpha);
|
||||
},
|
||||
.cleanup_temporary_erase = [&] {
|
||||
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_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);
|
||||
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);
|
||||
b->m_pattern_texture ?
|
||||
b->m_pattern_texture->bind() :
|
||||
unbind_texture_2d();
|
||||
},
|
||||
.draw_temporary_paint_frame = [&](int frame, float onion_alpha) {
|
||||
draw_layer_frame(frame, onion_alpha);
|
||||
},
|
||||
.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,
|
||||
});
|
||||
},
|
||||
.draw_layer_texture_frame = [&](int frame, float onion_alpha) {
|
||||
draw_layer_frame(frame, onion_alpha);
|
||||
},
|
||||
.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();
|
||||
|
||||
if (copy_blend_destination)
|
||||
{
|
||||
set_active_texture_unit(2);
|
||||
m_blender_bg.unbind();
|
||||
}
|
||||
set_active_texture_unit(0);
|
||||
m_blender_rtt.unbindTexture();
|
||||
},
|
||||
});
|
||||
|
||||
#ifdef _DEBUG
|
||||
// draw dirty area
|
||||
|
||||
@@ -61,6 +61,16 @@ bool AppleDocumentPlatformServices::set_clipboard_text(std::string_view text) co
|
||||
return false;
|
||||
}
|
||||
|
||||
void AppleDocumentPlatformServices::set_virtual_keyboard_visible(bool visible) const
|
||||
{
|
||||
#if defined(__IOS__)
|
||||
if (bridge_.set_virtual_keyboard_visible)
|
||||
bridge_.set_virtual_keyboard_visible(visible);
|
||||
#else
|
||||
(void)visible;
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppleDocumentPlatformServices::pick_image(PickedPathCallback callback) const
|
||||
{
|
||||
if (family_ == PlatformFamily::ios)
|
||||
@@ -173,6 +183,26 @@ void AppleDocumentPlatformServices::set_cursor_visible(bool visible) const
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppleDocumentPlatformServices::save_prepared_file(
|
||||
std::string_view path,
|
||||
std::string_view suggested_name,
|
||||
PreparedFileCallback callback) const
|
||||
{
|
||||
#if defined(__IOS__)
|
||||
if (bridge_.save_prepared_file)
|
||||
{
|
||||
bridge_.save_prepared_file(
|
||||
std::string(path),
|
||||
std::string(suggested_name),
|
||||
std::move(callback));
|
||||
}
|
||||
#else
|
||||
(void)path;
|
||||
(void)suggested_name;
|
||||
(void)callback;
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppleDocumentPlatformServices::save_ui_state() const
|
||||
{
|
||||
#if defined(__OSX__)
|
||||
|
||||
@@ -18,9 +18,11 @@ struct AppleDocumentPickerBridge {
|
||||
std::function<std::string(std::string_view path)> format_working_directory_path;
|
||||
std::function<std::string()> clipboard_text;
|
||||
std::function<bool(std::string_view text)> set_clipboard_text;
|
||||
std::function<void(bool visible)> set_virtual_keyboard_visible;
|
||||
std::function<void(std::string path)> display_file;
|
||||
std::function<void(std::string path)> share_file;
|
||||
std::function<void(bool visible)> set_cursor_visible;
|
||||
std::function<void(std::string path, std::string suggested_name, PreparedFileCallback callback)> save_prepared_file;
|
||||
std::function<void()> save_ui_state;
|
||||
};
|
||||
|
||||
@@ -43,9 +45,14 @@ public:
|
||||
[[nodiscard]] std::string format_working_directory_path(std::string_view path) const;
|
||||
[[nodiscard]] std::string clipboard_text() const;
|
||||
[[nodiscard]] bool set_clipboard_text(std::string_view text) const;
|
||||
void set_virtual_keyboard_visible(bool visible) const;
|
||||
void display_file(std::string_view path) const;
|
||||
void share_file(std::string_view path) const;
|
||||
void set_cursor_visible(bool visible) const;
|
||||
void save_prepared_file(
|
||||
std::string_view path,
|
||||
std::string_view suggested_name,
|
||||
PreparedFileCallback callback) const;
|
||||
void save_ui_state() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -135,6 +135,14 @@ public:
|
||||
const std::string value(text);
|
||||
return [ios_view clipboard_set_string:value];
|
||||
};
|
||||
bridge.set_virtual_keyboard_visible = [ios_view](bool visible) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (visible)
|
||||
[ios_view show_keyboard];
|
||||
else
|
||||
[ios_view hide_keyboard];
|
||||
});
|
||||
};
|
||||
bridge.display_file = [ios_view](std::string path) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[ios_view display_file:path];
|
||||
@@ -157,6 +165,16 @@ public:
|
||||
[ios_view pick_file:apple_file_types_array(file_types) then:callback];
|
||||
});
|
||||
};
|
||||
bridge.save_prepared_file = [ios_view](
|
||||
std::string path,
|
||||
std::string suggested_name,
|
||||
pp::platform::PreparedFileCallback callback) {
|
||||
(void)suggested_name;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[ios_view pick_file_save:path];
|
||||
});
|
||||
callback(path, true);
|
||||
};
|
||||
return bridge;
|
||||
}());
|
||||
return services;
|
||||
@@ -330,12 +348,7 @@ public:
|
||||
void set_virtual_keyboard_visible(bool visible) override
|
||||
{
|
||||
#ifdef __IOS__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (visible)
|
||||
[App::I->ios_view show_keyboard];
|
||||
else
|
||||
[App::I->ios_view hide_keyboard];
|
||||
});
|
||||
active_apple_document_platform_services().set_virtual_keyboard_visible(visible);
|
||||
#elif __ANDROID__
|
||||
displayKeyboard(visible);
|
||||
#else
|
||||
@@ -734,11 +747,10 @@ public:
|
||||
const std::string value(path);
|
||||
const std::string name(suggested_name);
|
||||
#ifdef __IOS__
|
||||
(void)name;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[App::I->ios_view pick_file_save:value];
|
||||
});
|
||||
callback(value, true);
|
||||
active_apple_document_platform_services().save_prepared_file(
|
||||
value,
|
||||
name,
|
||||
std::move(callback));
|
||||
#else
|
||||
(void)name;
|
||||
callback(value, false);
|
||||
|
||||
Reference in New Issue
Block a user