Extract canvas projection helpers and thin preview and node loading

This commit is contained in:
2026-06-16 18:16:04 +02:00
parent 1442c13dd7
commit 8906756d12
11 changed files with 565 additions and 341 deletions

View File

@@ -23,6 +23,8 @@ set(PP_LEGACY_PAINT_DOCUMENT_SOURCES
src/canvas_actions.cpp src/canvas_actions.cpp
src/canvas_layer.cpp src/canvas_layer.cpp
src/legacy_canvas_document_io_services.cpp src/legacy_canvas_document_io_services.cpp
src/legacy_canvas_projection_services.cpp
src/legacy_canvas_projection_services.h
src/legacy_canvas_state_services.cpp src/legacy_canvas_state_services.cpp
src/event.cpp src/event.cpp
) )
@@ -36,6 +38,8 @@ set(PP_LEGACY_RENDERER_GL_SOURCES
) )
set(PP_LEGACY_UI_CORE_SOURCES set(PP_LEGACY_UI_CORE_SOURCES
src/legacy_ui_node_loader.cpp
src/legacy_ui_node_loader.h
src/layout.cpp src/layout.cpp
src/node.cpp src/node.cpp
src/node_border.cpp src/node_border.cpp

View File

@@ -79,16 +79,16 @@ What is still carrying too much live ownership:
Current hotspot files: Current hotspot files:
- `src/canvas.cpp`: 2645 lines - `src/canvas.cpp`: 2728 lines
- `src/app_layout.cpp`: 125 lines - `src/app_layout.cpp`: 125 lines
- `src/canvas_modes.cpp`: 1798 lines - `src/canvas_modes.cpp`: 1798 lines
- `src/node.cpp`: 1594 lines - `src/node.cpp`: 1465 lines
- `src/main.cpp`: 271 lines - `src/main.cpp`: 271 lines
- `src/node_panel_brush.cpp`: 1197 lines - `src/node_panel_brush.cpp`: 1207 lines
- `src/node_stroke_preview.cpp`: 890 lines - `src/node_stroke_preview.cpp`: 910 lines
- `src/node_canvas.cpp`: 852 lines - `src/node_canvas.cpp`: 852 lines
- `src/app.cpp`: 502 lines - `src/app.cpp`: 575 lines
- `src/app_dialogs.cpp`: 142 lines - `src/app_dialogs.cpp`: 168 lines
Current architecture mismatches that must be treated as real blockers: Current architecture mismatches that must be treated as real blockers:
@@ -240,10 +240,17 @@ Current architecture mismatches that must be treated as real blockers:
`src/node_stroke_preview.cpp`, while the immediate preview pass-sequencing `src/node_stroke_preview.cpp`, while the immediate preview pass-sequencing
family inside `draw_stroke_immediate()` now also routes through family inside `draw_stroke_immediate()` now also routes through
`NodeStrokePreview::execute_stroke_draw_immediate_pass_sequence(...)`, while `NodeStrokePreview::execute_stroke_draw_immediate_pass_sequence(...)`, while
`Node` child attach/detach/reorder operations now route through named local preview stroke preparation, dual-brush setup, and live pass-orchestration
helpers in `src/node.cpp`, which makes the scene-graph mutation paths easier request assembly now also route through retained preview execution helpers,
to reason about without yet reducing the file or moving ownership into while `Node` child attach/detach/reorder operations now route through named
`pp_ui_core`. local helpers in `src/node.cpp`, and `Node::load_internal(...)` child XML
loading now also routes through `src/legacy_ui_node_loader.*`, which makes
the scene-graph mutation and child-instantiation paths easier to reason
about without yet reducing the file or moving ownership into `pp_ui_core`,
while `Canvas` point-trace/unproject/project/camera push-pop-get-set and
face-to-shape helpers now also route through
`src/legacy_canvas_projection_services.*` instead of staying inline in
`src/canvas.cpp`.
- Modern C++23 usage exists in extracted components, especially `std::span`, - Modern C++23 usage exists in extracted components, especially `std::span`,
explicit result/status objects, and a few concepts, but the live app still explicit result/status objects, and a few concepts, but the live app still
does not consistently express ownership, thread affinity, or renderer does not consistently express ownership, thread affinity, or renderer

View File

@@ -91,7 +91,7 @@ Status: In Progress
Why now: Why now:
`src/canvas.cpp` is still the biggest single architectural blocker at about `src/canvas.cpp` is still the biggest single architectural blocker at about
2645 lines. 2728 lines.
Current slice: Current slice:
- Canvas state-management helpers for picking, clear/clear-all, layer - Canvas state-management helpers for picking, clear/clear-all, layer
@@ -103,6 +103,11 @@ Current slice:
`src/legacy_canvas_document_io_services.cpp` instead of staying inline in `src/legacy_canvas_document_io_services.cpp` instead of staying inline in
`src/canvas.cpp`, which materially reduces document IO ownership in the live `src/canvas.cpp`, which materially reduces document IO ownership in the live
render shell. render shell.
- Canvas point-trace, unproject, project2D, face-to-shape, and camera
push/pop/get/set helpers now also live in
`src/legacy_canvas_projection_services.*` instead of staying inline in
`src/canvas.cpp`, which trims another coherent non-UI state/query pocket
from the live canvas shell.
Write scope: Write scope:
- `src/canvas.cpp` - `src/canvas.cpp`
@@ -158,6 +163,11 @@ Current slice:
`execute_stroke_draw_immediate_pass_sequence(...)` helper, which removes `execute_stroke_draw_immediate_pass_sequence(...)` helper, which removes
another live orchestration block from the node even though worker/readback another live orchestration block from the node even though worker/readback
flow still remains in the file. flow still remains in the file.
- `NodeStrokePreview` stroke preparation, dual-brush setup, and live-pass
request assembly now also route through retained preview execution helpers,
which trims another coherent setup pocket from
`src/node_stroke_preview.cpp` even though worker/readback ownership and
broader preview flow still remain inline.
- `NodeCanvas` merged-path per-plane merged-texture draw execution now also - `NodeCanvas` merged-path per-plane merged-texture draw execution now also
routes through `execute_legacy_canvas_draw_merge_layer_texture(...)`. routes through `execute_legacy_canvas_draw_merge_layer_texture(...)`.
- `NodeCanvas` merged-path and non-blend checkerboard background setup now also - `NodeCanvas` merged-path and non-blend checkerboard background setup now also
@@ -390,7 +400,7 @@ Status: In Progress
Why now: Why now:
`src/app_dialogs.cpp` still mixes document workflow decisions, export routing, `src/app_dialogs.cpp` still mixes document workflow decisions, export routing,
dialog construction, and overlay ownership. dialog construction, and overlay ownership in one 168-line shell.
Current slice: Current slice:
- Informational overlay opener paths for user manual, changelog, about, - Informational overlay opener paths for user manual, changelog, about,
@@ -439,7 +449,7 @@ Status: In Progress
Why now: Why now:
`src/app.cpp` still carries startup, frame flow, queue draining, recording, `src/app.cpp` still carries startup, frame flow, queue draining, recording,
and composition logic in one 502-line file. and composition logic in one 575-line file.
Current slice: Current slice:
- UI observer math now routes through `src/legacy_app_frame_services.cpp` - UI observer math now routes through `src/legacy_app_frame_services.cpp`
@@ -651,12 +661,19 @@ attached to it.
#### ARC-UI-001 - Move Generic Node And Control Code Out Of `pp_legacy_ui_core` #### ARC-UI-001 - Move Generic Node And Control Code Out Of `pp_legacy_ui_core`
Status: Ready Status: In Progress
Why now: Why now:
`pp_ui_core` has layout, color, node lifetime, and overlay lifetime, but the `pp_ui_core` has layout, color, node lifetime, and overlay lifetime, but the
generic widget layer still sits in `pp_legacy_ui_core`. generic widget layer still sits in `pp_legacy_ui_core`.
Current slice:
- `Node::load_internal(...)` child XML loading now routes through
`src/legacy_ui_node_loader.*` instead of staying inline in `src/node.cpp`.
That trims another coherent generic node-instantiation pocket and makes the
remaining scene-graph load path easier to isolate, even though ownership has
not yet moved into `pp_ui_core`.
Write scope: Write scope:
- `src/node.cpp` - `src/node.cpp`
- `src/layout.cpp` - `src/layout.cpp`

View File

@@ -10,6 +10,7 @@
#include "legacy_canvas_stroke_edge_services.h" #include "legacy_canvas_stroke_edge_services.h"
#include "legacy_canvas_stroke_execution_services.h" #include "legacy_canvas_stroke_execution_services.h"
#include "legacy_canvas_stroke_shader_services.h" #include "legacy_canvas_stroke_shader_services.h"
#include "legacy_canvas_projection_services.h"
#include "legacy_canvas_stroke_services.h" #include "legacy_canvas_stroke_services.h"
#include "legacy_ui_gl_dispatch.h" #include "legacy_ui_gl_dispatch.h"
#include "legacy_ui_overlay_services.h" #include "legacy_ui_overlay_services.h"
@@ -2028,29 +2029,7 @@ void Canvas::stroke_draw()
bool Canvas::point_trace(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir, bool Canvas::point_trace(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
glm::vec3& hit_pos, glm::vec2& fb_pos, glm::vec3& hit_normal, int& out_plane_id) glm::vec3& hit_pos, glm::vec2& fb_pos, glm::vec3& hit_normal, int& out_plane_id)
{ {
point_unproject(loc, { 0, 0, zw(m_box) }, m_mv, m_proj, ray_origin, ray_dir); return pp::panopainter::legacy_canvas_point_trace(*this, loc, ray_origin, ray_dir, hit_pos, fb_pos, hit_normal, out_plane_id);
glm::vec3 hit;
float hit_t;
for (int i = 0; i < 6; i++)
{
if (ray_intersect(ray_origin, ray_dir, m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i], hit, hit_t))
{
glm::mat4 plane_camera = glm::lookAt(m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i]);
glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1);
if (glm::abs(plane_local.x) < 1.f && glm::abs(plane_local.y) < 1.f)
{
fb_pos.x = -(plane_local.x * 0.5f - 0.5f) * m_width;
fb_pos.y = (plane_local.y * 0.5f + 0.5f) * m_height;
hit_pos = hit;
hit_normal = m_plane_normal[i];
out_plane_id = i;
return true;
}
else continue;
}
else continue;
}
return false;
} }
/* /*
bool Canvas::point_trace_plane(glm::vec2 loc, glm::vec3& hit_pos, glm::vec2& hit_fb_pos, int plane_id) bool Canvas::point_trace_plane(glm::vec2 loc, glm::vec3& hit_pos, glm::vec2& hit_fb_pos, int plane_id)
@@ -2068,52 +2047,20 @@ bool Canvas::point_trace_plane(glm::vec2 loc, glm::vec3& hit_pos, glm::vec2& hit
bool Canvas::point_trace_plane(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir, bool Canvas::point_trace_plane(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
glm::vec3& hit_pos, glm::vec3& hit_normal, glm::vec2& hit_fb_pos, int plane_id) glm::vec3& hit_pos, glm::vec3& hit_normal, glm::vec2& hit_fb_pos, int plane_id)
{ {
point_unproject(loc, { 0, 0, zw(m_box) }, m_mv, m_proj, ray_origin, ray_dir); return pp::panopainter::legacy_canvas_point_trace_plane(*this, loc, ray_origin, ray_dir, hit_pos, hit_normal, hit_fb_pos, plane_id);
glm::vec3 hit;
float hit_t;
if (ray_intersect(ray_origin, ray_dir, m_plane_origin[plane_id],
m_plane_normal[plane_id], m_plane_tangent[plane_id], hit, hit_t))
{
glm::mat4 plane_camera = glm::lookAt(m_plane_origin[plane_id], m_plane_normal[plane_id], m_plane_tangent[plane_id]);
glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1);
hit_pos = hit;
hit_normal = m_plane_normal[plane_id];
hit_fb_pos.x = -(plane_local.x * 0.5f - 0.5f);
hit_fb_pos.y = (plane_local.y * 0.5f + 0.5f);
return true;
}
return false;
} }
void Canvas::point_unproject(glm::vec2 loc, glm::vec4 vp, glm::mat4 camera, glm::mat4 proj, void Canvas::point_unproject(glm::vec2 loc, glm::vec4 vp, glm::mat4 camera, glm::mat4 proj,
glm::vec3& out_origin, glm::vec3& out_dir) glm::vec3& out_origin, glm::vec3& out_dir)
{ {
auto clip_space = glm::vec2(loc.x, vp.w - loc.y - 1.f) / zw(vp) * 2.f - 1.f; pp::panopainter::legacy_canvas_point_unproject(loc, vp, camera, proj, out_origin, out_dir);
auto inv = glm::inverse(proj * camera); }
auto wp0 = inv * glm::vec4(clip_space, 0, 1);
auto wp1 = inv * glm::vec4(clip_space, .5, 1);
out_origin = xyz(wp0 / wp0.w);
out_dir = glm::normalize(xyz(wp1 / wp1.w) - out_origin);
};
void Canvas::point_unproject(glm::vec2 loc, glm::vec3& out_origin, glm::vec3& out_dir) void Canvas::point_unproject(glm::vec2 loc, glm::vec3& out_origin, glm::vec3& out_dir)
{ {
auto clip_space = glm::vec2(loc.x, m_vp.w - loc.y - 1.f) / zw(m_vp) * 2.f - 1.f; pp::panopainter::legacy_canvas_point_unproject(*this, loc, out_origin, out_dir);
auto inv = glm::inverse(m_proj * m_mv);
auto wp0 = inv * glm::vec4(clip_space, 0, 1);
auto wp1 = inv * glm::vec4(clip_space, .5, 1);
out_origin = xyz(wp0 / wp0.w);
out_dir = glm::normalize(xyz(wp1 / wp1.w) - out_origin);
} }
glm::vec3 Canvas::point_trace(glm::vec2 loc) glm::vec3 Canvas::point_trace(glm::vec2 loc)
{ {
glm::vec3 ray_origin; return pp::panopainter::legacy_canvas_point_trace(*this, loc);
glm::vec3 ray_dir;
glm::vec3 hit_pos;
glm::vec3 hit_normal;
glm::vec2 fb_pos;
int plane_id;
if (point_trace(loc, ray_origin, ray_dir, hit_pos, fb_pos, hit_normal, plane_id))
return hit_pos;
return glm::vec3(0);
} }
void Canvas::stroke_commit() void Canvas::stroke_commit()
{ {
@@ -2736,24 +2683,12 @@ void Canvas::draw_objects(std::function<void(const glm::mat4& camera, const glm:
void Canvas::project2Dpoints(std::vector<vertex_t>& vertices) void Canvas::project2Dpoints(std::vector<vertex_t>& vertices)
{ {
for (auto& p : vertices) pp::panopainter::legacy_canvas_project_2d_points(*this, vertices);
{
glm::vec3 ro, rd, hit_o, hit_d;
glm::vec2 hit_fb;
int plane_id;
if (point_trace(p.pos, ro, rd, hit_o, hit_fb, hit_d, plane_id))
p.pos = glm::vec4(hit_o, 1);
}
} }
glm::vec3 Canvas::project2Dpoint(glm::vec2 pt) glm::vec3 Canvas::project2Dpoint(glm::vec2 pt)
{ {
glm::vec3 ro, rd, hit_o, hit_d; return pp::panopainter::legacy_canvas_project_2d_point(*this, pt);
glm::vec2 hit_fb;
int plane_id;
if (point_trace(pt, ro, rd, hit_o, hit_fb, hit_d, plane_id))
return glm::vec4(hit_o, 1);
return glm::vec3(0);
} }
@@ -2762,73 +2697,27 @@ glm::vec3 Canvas::project2Dpoint(glm::vec2 pt)
// this can be used for screen space shapes clipping // this can be used for screen space shapes clipping
std::vector<glm::vec2> Canvas::face_to_shape2D(int plane_index) std::vector<glm::vec2> Canvas::face_to_shape2D(int plane_index)
{ {
static std::array<glm::vec4, 4> corners{ return pp::panopainter::legacy_canvas_face_to_shape_2d(*this, plane_index);
glm::vec4(-1.f, +1.f, -1.f, 1.f), // A top-left
glm::vec4(+1.f, +1.f, -1.f, 1.f), // B top-right
glm::vec4(+1.f, -1.f, -1.f, 1.f), // C bottom-right
glm::vec4(-1.f, -1.f, -1.f, 1.f), // D bottom-left
};
// compute points in camera space
std::vector<glm::vec3> pt_cam;
for (auto c : corners)
{
auto pt_world = m_plane_transform[plane_index] * c;
pt_cam.push_back(m_mv * pt_world);
}
// clip at near plane
pt_cam = poly_clip_near(pt_cam, 0.01);
// compute windows space
std::vector<glm::vec2> points;
for (auto p : pt_cam)
{
auto pt_clip = m_proj * glm::vec4(p, 1);
pt_clip = pt_clip / pt_clip.w;
glm::vec2 pt_screen = (glm::vec2(pt_clip) * 0.5f + 0.5f) * zw(m_vp);
pt_screen.y = m_vp.w - pt_screen.y - 1;
points.push_back(pt_screen);
}
return points;
} }
void Canvas::push_camera() void Canvas::push_camera()
{ {
m_camera_stack.push(get_camera()); pp::panopainter::legacy_canvas_push_camera(*this);
} }
void Canvas::pop_camera() void Canvas::pop_camera()
{ {
if (!m_camera_stack.empty()) pp::panopainter::legacy_canvas_pop_camera(*this);
{
set_camera(m_camera_stack.top());
m_camera_stack.pop();
}
} }
CameraData Canvas::get_camera() CameraData Canvas::get_camera()
{ {
CameraData c; return pp::panopainter::legacy_canvas_get_camera(*this);
c.m_box = m_box;
c.m_mv = m_mv;
c.m_pan = m_pan;
std::copy_n(m_plane_dir, 6, c.m_plane_dir);
std::copy_n(m_plane_unproject, 6, c.m_plane_unproject);
c.m_proj = m_proj;
c.m_vp = m_vp;
return c;
} }
void Canvas::set_camera(const CameraData& c) void Canvas::set_camera(const CameraData& c)
{ {
m_box = c.m_box; pp::panopainter::legacy_canvas_set_camera(*this, c);
m_mv = c.m_mv;
m_pan = c.m_pan;
std::copy_n(c.m_plane_dir, 6, m_plane_dir);
std::copy_n(c.m_plane_unproject, 6, m_plane_unproject);
m_proj = c.m_proj;
m_vp = c.m_vp;
} }
void Canvas::timelapse_reset_encoder() noexcept void Canvas::timelapse_reset_encoder() noexcept

View File

@@ -0,0 +1,179 @@
#include "pch.h"
#include "legacy_canvas_projection_services.h"
#include "canvas.h"
namespace pp::panopainter {
bool legacy_canvas_point_trace(Canvas& canvas, glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
glm::vec3& hit_pos, glm::vec2& fb_pos, glm::vec3& hit_normal, int& out_plane_id)
{
legacy_canvas_point_unproject(loc, { 0, 0, zw(canvas.m_box) }, canvas.m_mv, canvas.m_proj, ray_origin, ray_dir);
glm::vec3 hit;
float hit_t;
for (int i = 0; i < 6; i++)
{
if (ray_intersect(ray_origin, ray_dir, canvas.m_plane_origin[i], canvas.m_plane_normal[i], canvas.m_plane_tangent[i], hit, hit_t))
{
glm::mat4 plane_camera = glm::lookAt(canvas.m_plane_origin[i], canvas.m_plane_normal[i], canvas.m_plane_tangent[i]);
glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1);
if (glm::abs(plane_local.x) < 1.f && glm::abs(plane_local.y) < 1.f)
{
fb_pos.x = -(plane_local.x * 0.5f - 0.5f) * canvas.m_width;
fb_pos.y = (plane_local.y * 0.5f + 0.5f) * canvas.m_height;
hit_pos = hit;
hit_normal = canvas.m_plane_normal[i];
out_plane_id = i;
return true;
}
}
}
return false;
}
bool legacy_canvas_point_trace_plane(Canvas& canvas, glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
glm::vec3& hit_pos, glm::vec3& hit_normal, glm::vec2& hit_fb_pos, int plane_id)
{
legacy_canvas_point_unproject(loc, { 0, 0, zw(canvas.m_box) }, canvas.m_mv, canvas.m_proj, ray_origin, ray_dir);
glm::vec3 hit;
float hit_t;
if (ray_intersect(ray_origin, ray_dir, canvas.m_plane_origin[plane_id],
canvas.m_plane_normal[plane_id], canvas.m_plane_tangent[plane_id], hit, hit_t))
{
glm::mat4 plane_camera = glm::lookAt(canvas.m_plane_origin[plane_id], canvas.m_plane_normal[plane_id], canvas.m_plane_tangent[plane_id]);
glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1);
hit_pos = hit;
hit_normal = canvas.m_plane_normal[plane_id];
hit_fb_pos.x = -(plane_local.x * 0.5f - 0.5f);
hit_fb_pos.y = (plane_local.y * 0.5f + 0.5f);
return true;
}
return false;
}
void legacy_canvas_point_unproject(glm::vec2 loc, glm::vec4 vp, glm::mat4 camera, glm::mat4 proj,
glm::vec3& out_origin, glm::vec3& out_dir)
{
auto clip_space = glm::vec2(loc.x, vp.w - loc.y - 1.f) / zw(vp) * 2.f - 1.f;
auto inv = glm::inverse(proj * camera);
auto wp0 = inv * glm::vec4(clip_space, 0, 1);
auto wp1 = inv * glm::vec4(clip_space, .5, 1);
out_origin = xyz(wp0 / wp0.w);
out_dir = glm::normalize(xyz(wp1 / wp1.w) - out_origin);
}
void legacy_canvas_point_unproject(Canvas& canvas, glm::vec2 loc, glm::vec3& out_origin, glm::vec3& out_dir)
{
auto clip_space = glm::vec2(loc.x, canvas.m_vp.w - loc.y - 1.f) / zw(canvas.m_vp) * 2.f - 1.f;
auto inv = glm::inverse(canvas.m_proj * canvas.m_mv);
auto wp0 = inv * glm::vec4(clip_space, 0, 1);
auto wp1 = inv * glm::vec4(clip_space, .5, 1);
out_origin = xyz(wp0 / wp0.w);
out_dir = glm::normalize(xyz(wp1 / wp1.w) - out_origin);
}
glm::vec3 legacy_canvas_point_trace(Canvas& canvas, glm::vec2 loc)
{
glm::vec3 ray_origin;
glm::vec3 ray_dir;
glm::vec3 hit_pos;
glm::vec3 hit_normal;
glm::vec2 fb_pos;
int plane_id;
if (legacy_canvas_point_trace(canvas, loc, ray_origin, ray_dir, hit_pos, fb_pos, hit_normal, plane_id))
return hit_pos;
return glm::vec3(0);
}
void legacy_canvas_project_2d_points(Canvas& canvas, std::vector<vertex_t>& vertices)
{
for (auto& p : vertices)
{
glm::vec3 ro, rd, hit_o, hit_d;
glm::vec2 hit_fb;
int plane_id;
if (legacy_canvas_point_trace(canvas, p.pos, ro, rd, hit_o, hit_fb, hit_d, plane_id))
p.pos = glm::vec4(hit_o, 1);
}
}
glm::vec3 legacy_canvas_project_2d_point(Canvas& canvas, glm::vec2 pt)
{
glm::vec3 ro, rd, hit_o, hit_d;
glm::vec2 hit_fb;
int plane_id;
if (legacy_canvas_point_trace(canvas, pt, ro, rd, hit_o, hit_fb, hit_d, plane_id))
return glm::vec4(hit_o, 1);
return glm::vec3(0);
}
std::vector<glm::vec2> legacy_canvas_face_to_shape_2d(const Canvas& canvas, int plane_index)
{
static std::array<glm::vec4, 4> corners{
glm::vec4(-1.f, +1.f, -1.f, 1.f),
glm::vec4(+1.f, +1.f, -1.f, 1.f),
glm::vec4(+1.f, -1.f, -1.f, 1.f),
glm::vec4(-1.f, -1.f, -1.f, 1.f),
};
std::vector<glm::vec3> pt_cam;
for (auto c : corners)
{
auto pt_world = canvas.m_plane_transform[plane_index] * c;
pt_cam.push_back(canvas.m_mv * pt_world);
}
pt_cam = poly_clip_near(pt_cam, 0.01f);
std::vector<glm::vec2> points;
for (auto p : pt_cam)
{
auto pt_clip = canvas.m_proj * glm::vec4(p, 1);
pt_clip = pt_clip / pt_clip.w;
glm::vec2 pt_screen = (glm::vec2(pt_clip) * 0.5f + 0.5f) * zw(canvas.m_vp);
pt_screen.y = canvas.m_vp.w - pt_screen.y - 1;
points.push_back(pt_screen);
}
return points;
}
void legacy_canvas_push_camera(Canvas& canvas)
{
canvas.m_camera_stack.push(legacy_canvas_get_camera(canvas));
}
void legacy_canvas_pop_camera(Canvas& canvas)
{
if (!canvas.m_camera_stack.empty())
{
legacy_canvas_set_camera(canvas, canvas.m_camera_stack.top());
canvas.m_camera_stack.pop();
}
}
CameraData legacy_canvas_get_camera(const Canvas& canvas)
{
CameraData camera;
camera.m_box = canvas.m_box;
camera.m_mv = canvas.m_mv;
camera.m_pan = canvas.m_pan;
std::copy_n(canvas.m_plane_dir, 6, camera.m_plane_dir);
std::copy_n(canvas.m_plane_unproject, 6, camera.m_plane_unproject);
camera.m_proj = canvas.m_proj;
camera.m_vp = canvas.m_vp;
return camera;
}
void legacy_canvas_set_camera(Canvas& canvas, const CameraData& camera)
{
canvas.m_box = camera.m_box;
canvas.m_mv = camera.m_mv;
canvas.m_pan = camera.m_pan;
std::copy_n(camera.m_plane_dir, 6, canvas.m_plane_dir);
std::copy_n(camera.m_plane_unproject, 6, canvas.m_plane_unproject);
canvas.m_proj = camera.m_proj;
canvas.m_vp = camera.m_vp;
}
} // namespace pp::panopainter

View File

@@ -0,0 +1,26 @@
#pragma once
#include "canvas_modes.h"
#include "util.h"
class Canvas;
namespace pp::panopainter {
bool legacy_canvas_point_trace(Canvas& canvas, glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
glm::vec3& hit_pos, glm::vec2& fb_pos, glm::vec3& hit_normal, int& out_plane_id);
bool legacy_canvas_point_trace_plane(Canvas& canvas, glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir,
glm::vec3& hit_pos, glm::vec3& hit_normal, glm::vec2& hit_fb_pos, int plane_id);
void legacy_canvas_point_unproject(glm::vec2 loc, glm::vec4 vp, glm::mat4 camera, glm::mat4 proj,
glm::vec3& out_origin, glm::vec3& out_dir);
void legacy_canvas_point_unproject(Canvas& canvas, glm::vec2 loc, glm::vec3& out_origin, glm::vec3& out_dir);
glm::vec3 legacy_canvas_point_trace(Canvas& canvas, glm::vec2 loc);
void legacy_canvas_project_2d_points(Canvas& canvas, std::vector<vertex_t>& vertices);
glm::vec3 legacy_canvas_project_2d_point(Canvas& canvas, glm::vec2 pt);
std::vector<glm::vec2> legacy_canvas_face_to_shape_2d(const Canvas& canvas, int plane_index);
void legacy_canvas_push_camera(Canvas& canvas);
void legacy_canvas_pop_camera(Canvas& canvas);
CameraData legacy_canvas_get_camera(const Canvas& canvas);
void legacy_canvas_set_camera(Canvas& canvas, const CameraData& camera);
} // namespace pp::panopainter

View File

@@ -9,6 +9,7 @@
#include "legacy_canvas_stroke_services.h" #include "legacy_canvas_stroke_services.h"
#include "legacy_ui_gl_dispatch.h" #include "legacy_ui_gl_dispatch.h"
#include "paint_renderer/compositor.h" #include "paint_renderer/compositor.h"
#include "brush.h"
#include "texture.h" #include "texture.h"
#include "util.h" #include "util.h"
@@ -508,6 +509,38 @@ struct LegacyNodeStrokePreviewPassOrchestrationRequest {
glm::mat4 mvp { 1.0f }; glm::mat4 mvp { 1.0f };
}; };
[[nodiscard]] inline LegacyNodeStrokePreviewPassOrchestrationRequest
make_legacy_node_stroke_preview_pass_orchestration_request(
pp::renderer::RenderDeviceFeatures features,
glm::vec2 preview_size,
const Brush& brush,
const glm::mat4& mvp) noexcept
{
return LegacyNodeStrokePreviewPassOrchestrationRequest {
.features = features,
.preview_size = preview_size,
.pattern_scale = brush.m_pattern_scale,
.pattern_flipx = brush.m_pattern_flipx,
.pattern_flipy = brush.m_pattern_flipy,
.pattern_invert = brush.m_pattern_invert,
.pattern_brightness = brush.m_pattern_brightness,
.pattern_contrast = brush.m_pattern_contrast,
.pattern_depth = brush.m_pattern_depth,
.pattern_rand_offset = brush.m_pattern_rand_offset,
.pattern_enabled = brush.m_pattern_enabled,
.pattern_eachsample = brush.m_pattern_eachsample,
.tip_mix = brush.m_tip_mix,
.tip_wet = brush.m_tip_wet,
.tip_noise = brush.m_tip_noise,
.dual_enabled = brush.m_dual_enabled,
.dual_blend_mode = brush.m_dual_blend_mode,
.dual_opacity = brush.m_dual_opacity,
.pattern_blend_mode = brush.m_pattern_blend_mode,
.blend_mode = brush.m_blend_mode,
.mvp = mvp,
};
}
[[nodiscard]] inline LegacyNodeStrokePreviewPassOrchestrationPlan [[nodiscard]] inline LegacyNodeStrokePreviewPassOrchestrationPlan
plan_legacy_node_stroke_preview_pass_orchestration( plan_legacy_node_stroke_preview_pass_orchestration(
const LegacyNodeStrokePreviewPassOrchestrationRequest& request) noexcept const LegacyNodeStrokePreviewPassOrchestrationRequest& request) noexcept
@@ -595,6 +628,69 @@ struct LegacyNodeStrokePreviewStrokeSetupRequest {
int preview_point_count = 100; int preview_point_count = 100;
}; };
struct LegacyNodeStrokePreviewPreparedStrokes {
Stroke stroke;
Stroke dual_stroke;
std::shared_ptr<Brush> dual_brush;
};
[[nodiscard]] inline std::shared_ptr<Brush> make_legacy_node_stroke_preview_dual_brush(const Brush& brush)
{
auto dual_brush = std::make_shared<Brush>();
dual_brush->m_tip_scale = brush.m_dual_scale;
dual_brush->m_tip_angle = brush.m_dual_angle;
dual_brush->m_tip_flow = brush.m_dual_flow;
dual_brush->m_tip_opacity = brush.m_dual_opacity;
dual_brush->m_tip_flipx = brush.m_dual_flipx;
dual_brush->m_tip_flipy = brush.m_dual_flipy;
dual_brush->m_tip_invert = brush.m_dual_invert;
dual_brush->m_blend_mode = brush.m_dual_blend_mode;
dual_brush->m_tip_randflipx = brush.m_dual_randflip;
dual_brush->m_tip_randflipy = brush.m_dual_randflip;
dual_brush->m_tip_size = brush.m_dual_size * brush.m_tip_size;
dual_brush->m_tip_spacing = brush.m_dual_spacing;
dual_brush->m_jitter_scatter = brush.m_dual_scatter;
dual_brush->m_jitter_scatter_bothaxis = brush.m_dual_scatter_bothaxis;
dual_brush->m_jitter_angle = brush.m_dual_rotate;
dual_brush->m_tip_texture = brush.m_dual_texture;
dual_brush->m_tip_aspect = brush.m_dual_aspect;
return dual_brush;
}
[[nodiscard]] inline LegacyNodeStrokePreviewPreparedStrokes prepare_legacy_node_stroke_preview_strokes(
const std::shared_ptr<Brush>& brush,
const LegacyNodeStrokePreviewStrokeSetupPlan& stroke_setup,
float camera_fov,
const glm::mat4& camera_rot)
{
LegacyNodeStrokePreviewPreparedStrokes prepared;
prepared.stroke.m_filter_points = false;
prepared.stroke.m_max_size = stroke_setup.stroke_max_size;
prepared.stroke.m_camera.fov = camera_fov;
prepared.stroke.m_camera.rot = camera_rot;
prepared.stroke.reset(true);
prepared.stroke.start(brush);
prepared.dual_brush = make_legacy_node_stroke_preview_dual_brush(*brush);
if (stroke_setup.dual_enabled) {
prepared.dual_stroke.m_filter_points = false;
prepared.dual_stroke.m_max_size = stroke_setup.dual_stroke_max_size;
prepared.dual_stroke.m_camera.fov = camera_fov;
prepared.dual_stroke.m_camera.rot = camera_rot;
prepared.dual_stroke.reset(true);
prepared.dual_stroke.start(prepared.dual_brush);
}
for (const auto& point : stroke_setup.points) {
prepared.stroke.add_point(point.position, point.pressure);
if (stroke_setup.dual_enabled) {
prepared.dual_stroke.add_point(point.position, point.pressure);
}
}
return prepared;
}
[[nodiscard]] inline glm::vec2 evaluate_legacy_node_stroke_preview_bezier( [[nodiscard]] inline glm::vec2 evaluate_legacy_node_stroke_preview_bezier(
std::vector<glm::vec2> control_points, std::vector<glm::vec2> control_points,
float t) noexcept float t) noexcept

View File

@@ -0,0 +1,180 @@
#include "pch.h"
#include "legacy_ui_node_loader.h"
#include "log.h"
#include "node.h"
#include "layout.h"
#include "node_about.h"
#include "node_border.h"
#include "node_button.h"
#include "node_button_custom.h"
#include "node_canvas.h"
#include "node_changelog.h"
#include "node_checkbox.h"
#include "node_color_quad.h"
#include "node_colorwheel.h"
#include "node_combobox.h"
#include "node_dialog_browse.h"
#include "node_dialog_cloud.h"
#include "node_dialog_picker.h"
#include "node_icon.h"
#include "node_image.h"
#include "node_image_texture.h"
#include "node_metadata.h"
#include "node_panel_animation.h"
#include "node_panel_brush.h"
#include "node_panel_color.h"
#include "node_panel_grid.h"
#include "node_panel_layer.h"
#include "node_panel_quick.h"
#include "node_panel_stroke.h"
#include "node_popup_menu.h"
#include "node_scroll.h"
#include "node_slider.h"
#include "node_stroke_preview.h"
#include "node_text.h"
#include "node_text_input.h"
#include "node_tool_bucket.h"
#include "node_usermanual.h"
#include "node_viewport.h"
#include "util.h"
namespace pp::panopainter {
namespace {
bool should_skip_child_for_os(const tinyxml2::XMLElement& x_child)
{
if (auto os = x_child.Attribute("os"))
{
auto osv = split(os, ',');
if (std::find(osv.begin(), osv.end(), PP_OS) == osv.end())
{
LOG("Element %s not for this os(%s), skipping", x_child.Name(), PP_OS)
return true;
}
}
return false;
}
void load_ref_child(Node& parent, const tinyxml2::XMLElement& x_child)
{
auto ids = x_child.Attribute("id");
auto id = const_hash(ids);
auto& ref = (*parent.m_manager)[id]->m_children[0];
auto n = ref->clone();
n->m_nodeID_s = ids;
n->m_nodeID = id;
parent.add_child(n);
}
void load_text_child(Node& parent, const tinyxml2::XMLElement& x_child)
{
auto n = new NodeText();
parent.add_child(n);
n->load_internal(&x_child, true);
std::string text;
auto node = x_child.FirstChild();
while (node)
{
if (auto e = node->ToElement())
{
if (strcmp(e->Name(), "br") == 0)
text.append("\n");
}
else if (auto t = node->ToText())
{
text.append(t->Value());
}
node = node->NextSibling();
}
if (!text.empty())
n->set_text(text);
}
Node* instantiate_child_node(std::string_view node_name)
{
if (node_name == "node") return new Node();
if (node_name == "border") return new NodeBorder();
if (node_name == "image") return new NodeImage();
if (node_name == "image-texture") return new NodeImageTexture();
if (node_name == "icon") return new NodeIcon();
if (node_name == "text-input") return new NodeTextInput();
if (node_name == "button") return new NodeButton();
if (node_name == "button-custom") return new NodeButtonCustom();
if (node_name == "combobox") return new NodeComboBox();
if (node_name == "slider-h") return new NodeSliderH();
if (node_name == "slider-v") return new NodeSliderV();
if (node_name == "slider-hue") return new NodeSliderHue();
if (node_name == "popup-menu") return new NodePopupMenu();
if (node_name == "viewport") return new NodeViewport();
if (node_name == "checkbox") return new NodeCheckBox();
if (node_name == "layer") return new NodeLayer();
if (node_name == "panel-layer") return new NodePanelLayer();
if (node_name == "panel-brush") return new NodePanelBrush();
if (node_name == "panel-color") return new NodePanelColor();
if (node_name == "panel-stroke") return new NodePanelStroke();
if (node_name == "panel-grid") return new NodePanelGrid();
if (node_name == "panel-quick") return new NodePanelQuick();
if (node_name == "dialog-browse") return new NodeDialogBrowse();
if (node_name == "dialog-browse-item") return new NodeDialogBrowseItem();
if (node_name == "dialog-cloud") return new NodeDialogCloud();
if (node_name == "dialog-cloud-item") return new NodeDialogCloudItem();
if (node_name == "color-picker") return new NodeColorPicker();
if (node_name == "about") return new NodeAbout();
if (node_name == "changelog") return new NodeChangelog();
if (node_name == "usermanual") return new NodeUserManual();
if (node_name == "tool-bucket") return new NodeToolBucket();
if (node_name == "timeline") return new NodeAnimationTimeline();
if (node_name == "stroke-preview") return new NodeStrokePreview();
if (node_name == "canvas") return new NodeCanvas();
if (node_name == "scroll") return new NodeScroll();
if (node_name == "metadata") return new NodeMetadata();
if (node_name == "colorwheel") return new NodeColorWheel();
if (node_name == "color-quad") return new NodeColorQuad();
return nullptr;
}
void load_typed_child(Node& parent, const tinyxml2::XMLElement& x_child, std::string_view node_name)
{
if (auto* n = instantiate_child_node(node_name))
{
parent.add_child(n);
n->load_internal(&x_child);
return;
}
LOG("instancing UNKNOWN node: %s", x_child.Name());
auto* n = new Node();
parent.add_child(n);
n->load_internal(&x_child);
}
} // namespace
void load_legacy_ui_children(Node& parent, const tinyxml2::XMLElement& x_node)
{
auto x_child = x_node.FirstChildElement();
while (x_child)
{
if (should_skip_child_for_os(*x_child))
{
x_child = x_child->NextSiblingElement();
continue;
}
std::string_view node_name = x_child->Name();
if (node_name == "ref")
load_ref_child(parent, *x_child);
else if (node_name == "text")
load_text_child(parent, *x_child);
else
load_typed_child(parent, *x_child, node_name);
x_child = x_child->NextSiblingElement();
}
}
} // namespace pp::panopainter

View File

@@ -0,0 +1,13 @@
#pragma once
namespace tinyxml2 {
class XMLElement;
}
class Node;
namespace pp::panopainter {
void load_legacy_ui_children(Node& parent, const tinyxml2::XMLElement& x_node);
} // namespace pp::panopainter

View File

@@ -1,43 +1,11 @@
#include "pch.h" #include "pch.h"
#include "app.h" #include "app.h"
#include "log.h" #include "log.h"
#include "legacy_ui_node_loader.h"
#include "node.h" #include "node.h"
#include "layout.h" #include "layout.h"
#include "util.h" #include "util.h"
#include "asset.h" #include "asset.h"
#include "node_border.h"
#include "node_image.h"
#include "node_image_texture.h"
#include "node_icon.h"
#include "node_text.h"
#include "node_text_input.h"
#include "node_button.h"
#include "node_button_custom.h"
#include "node_slider.h"
#include "node_popup_menu.h"
#include "node_viewport.h"
#include "node_checkbox.h"
#include "node_panel_layer.h"
#include "node_panel_brush.h"
#include "node_panel_color.h"
#include "node_panel_stroke.h"
#include "node_color_quad.h"
#include "node_stroke_preview.h"
#include "node_canvas.h"
#include "node_scroll.h"
#include "node_dialog_browse.h"
#include "node_dialog_cloud.h"
#include "node_combobox.h"
#include "node_colorwheel.h"
#include "node_dialog_picker.h"
#include "node_panel_grid.h"
#include "node_about.h"
#include "node_changelog.h"
#include "node_usermanual.h"
#include "node_panel_quick.h"
#include "node_tool_bucket.h"
#include "node_panel_animation.h"
#include "node_metadata.h"
namespace namespace
{ {
@@ -1430,104 +1398,7 @@ void Node::load_internal(const tinyxml2::XMLElement* x_node, bool skip_children
return; return;
} }
auto x_child = x_node->FirstChildElement(); pp::panopainter::load_legacy_ui_children(*this, *x_node);
while (x_child)
{
if (auto os = x_child->Attribute("os"))
{
auto osv = split(os, ',');
if (std::find(osv.begin(), osv.end(), PP_OS) == osv.end())
{
LOG("Element %s not for this os(%s), skipping", x_child->Name(), PP_OS)
x_child = x_child->NextSiblingElement();
continue;
}
}
std::string node_name = x_child->Name();
if (node_name == "ref")
{
auto ids = x_child->Attribute("id");
auto id = const_hash(ids);
auto& ref = (*m_manager)[id]->m_children[0];
auto n = ref->clone();
n->m_nodeID_s = ids;
n->m_nodeID = id;
add_child(n);
}
else if (node_name == "text")
{
auto n = new NodeText();
add_child(n);
n->load_internal(x_child, true);
std::string text;
auto node = x_child->FirstChild();
while (node)
{
if (auto e = node->ToElement())
{
if (strcmp(e->Name(), "br") == 0)
text.append("\n");
}
else if (auto t = node->ToText())
{
text.append(t->Value());
}
node = node->NextSibling();
}
if (!text.empty())
n->set_text(text);
}
#define CASE(W,C) else if (node_name == W) { auto n = new C(); add_child(n); n->load_internal(x_child); }
CASE("node", Node)
CASE("border", NodeBorder)
CASE("image", NodeImage)
CASE("image-texture", NodeImageTexture)
CASE("icon", NodeIcon)
CASE("text-input", NodeTextInput)
CASE("button", NodeButton)
CASE("button-custom", NodeButtonCustom)
CASE("combobox", NodeComboBox)
CASE("slider-h", NodeSliderH)
CASE("slider-v", NodeSliderV)
CASE("slider-hue", NodeSliderHue)
CASE("popup-menu", NodePopupMenu)
CASE("viewport", NodeViewport)
CASE("checkbox", NodeCheckBox)
CASE("layer", NodeLayer)
CASE("panel-layer", NodePanelLayer)
CASE("panel-brush", NodePanelBrush)
CASE("panel-color", NodePanelColor)
CASE("panel-stroke", NodePanelStroke)
CASE("panel-grid", NodePanelGrid)
CASE("panel-quick", NodePanelQuick)
CASE("dialog-browse", NodeDialogBrowse)
CASE("dialog-browse-item", NodeDialogBrowseItem)
CASE("dialog-cloud", NodeDialogCloud)
CASE("dialog-cloud-item", NodeDialogCloudItem)
CASE("color-picker", NodeColorPicker)
CASE("about", NodeAbout)
CASE("changelog", NodeChangelog)
CASE("usermanual", NodeUserManual)
CASE("tool-bucket", NodeToolBucket)
CASE("timeline", NodeAnimationTimeline)
CASE("stroke-preview", NodeStrokePreview)
CASE("canvas", NodeCanvas)
CASE("scroll", NodeScroll)
CASE("metadata", NodeMetadata)
CASE("panel-quick", NodePanelQuick)
CASE("colorwheel", NodeColorWheel)
CASE("color-quad", NodeColorQuad)
#undef CASE
else
{
LOG("instancing UNKNOWN node: %s", x_child->Name());
auto n = new Node();
add_child(n);
n->load_internal(x_child);
}
x_child = x_child->NextSiblingElement();
}
loaded(); loaded();
} }

View File

@@ -584,84 +584,26 @@ void NodeStrokePreview::draw_stroke_immediate()
const auto& b = m_brush; const auto& b = m_brush;
Stroke m_stroke; auto prepared_strokes = pp::panopainter::prepare_legacy_node_stroke_preview_strokes(
Stroke m_dual_stroke; b,
stroke_setup,
m_stroke.m_filter_points = false; Canvas::I->m_cam_fov,
m_stroke.m_max_size = stroke_setup.stroke_max_size; Canvas::I->m_cam_rot);
m_stroke.m_camera.fov = Canvas::I->m_cam_fov;
m_stroke.m_camera.rot = Canvas::I->m_cam_rot;
m_stroke.reset(true);
m_stroke.start(b);
auto dual_brush = std::make_shared<Brush>();
dual_brush->m_tip_scale = b->m_dual_scale;
dual_brush->m_tip_angle = b->m_dual_angle;
dual_brush->m_tip_flow = b->m_dual_flow;
dual_brush->m_tip_opacity = b->m_dual_opacity;
dual_brush->m_tip_flipx = b->m_dual_flipx;
dual_brush->m_tip_flipy = b->m_dual_flipy;
dual_brush->m_tip_invert = b->m_dual_invert;
dual_brush->m_blend_mode = b->m_dual_blend_mode;
dual_brush->m_tip_randflipx = b->m_dual_randflip;
dual_brush->m_tip_randflipy = b->m_dual_randflip;
dual_brush->m_tip_size = b->m_dual_size * b->m_tip_size;
dual_brush->m_tip_spacing = b->m_dual_spacing;
dual_brush->m_jitter_scatter = b->m_dual_scatter;
dual_brush->m_jitter_scatter_bothaxis = b->m_dual_scatter_bothaxis;
dual_brush->m_jitter_angle = b->m_dual_rotate;
dual_brush->m_tip_texture = b->m_dual_texture;
dual_brush->m_tip_aspect = b->m_dual_aspect;
if (stroke_setup.dual_enabled)
{
m_dual_stroke.m_filter_points = false;
m_dual_stroke.m_max_size = stroke_setup.dual_stroke_max_size;
m_dual_stroke.m_camera.fov = Canvas::I->m_cam_fov;
m_dual_stroke.m_camera.rot = Canvas::I->m_cam_rot;
m_dual_stroke.reset(true);
m_dual_stroke.start(dual_brush);
}
for (const auto& point : stroke_setup.points)
{
m_stroke.add_point(point.position, point.pressure);
if (stroke_setup.dual_enabled)
m_dual_stroke.add_point(point.position, point.pressure);
}
apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false); apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false);
const auto pass_orchestration = pp::panopainter::plan_legacy_node_stroke_preview_pass_orchestration( const auto pass_orchestration = pp::panopainter::plan_legacy_node_stroke_preview_pass_orchestration(
pp::panopainter::LegacyNodeStrokePreviewPassOrchestrationRequest { pp::panopainter::make_legacy_node_stroke_preview_pass_orchestration_request(
.features = stroke_preview_render_device_features(), stroke_preview_render_device_features(),
.preview_size = size, size,
.pattern_scale = b->m_pattern_scale, *b,
.pattern_flipx = b->m_pattern_flipx, ortho_proj));
.pattern_flipy = b->m_pattern_flipy,
.pattern_invert = b->m_pattern_invert,
.pattern_brightness = b->m_pattern_brightness,
.pattern_contrast = b->m_pattern_contrast,
.pattern_depth = b->m_pattern_depth,
.pattern_rand_offset = b->m_pattern_rand_offset,
.pattern_enabled = b->m_pattern_enabled,
.pattern_eachsample = b->m_pattern_eachsample,
.tip_mix = b->m_tip_mix,
.tip_wet = b->m_tip_wet,
.tip_noise = b->m_tip_noise,
.dual_enabled = b->m_dual_enabled,
.dual_blend_mode = b->m_dual_blend_mode,
.dual_opacity = b->m_dual_opacity,
.pattern_blend_mode = b->m_pattern_blend_mode,
.blend_mode = b->m_blend_mode,
.mvp = ortho_proj,
});
const bool copy_stroke_destination = pass_orchestration.copy_stroke_destination; const bool copy_stroke_destination = pass_orchestration.copy_stroke_destination;
pp::panopainter::setup_legacy_stroke_shader(pass_orchestration.stroke_shader); pp::panopainter::setup_legacy_stroke_shader(pass_orchestration.stroke_shader);
execute_stroke_draw_immediate_pass_sequence( execute_stroke_draw_immediate_pass_sequence(
m_stroke, prepared_strokes.stroke,
m_dual_stroke, prepared_strokes.dual_stroke,
*b, *b,
std::move(dual_brush), std::move(prepared_strokes.dual_brush),
pass_orchestration, pass_orchestration,
copy_stroke_destination, copy_stroke_destination,
zoom, zoom,