diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 334546f..4700ac1 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,14 @@ agent or engineer to remove them without reconstructing context from chat. ## Recent Reductions +- 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` + now routes retained preview feedback/material/composite planning plus stroke + shader uniform assembly through + `plan_legacy_node_stroke_preview_pass_orchestration(...)`; focused compositor + coverage now locks the retained destination-feedback fallback, composite-slot + intent, and pattern/dual shader-uniform handoff while brush mutation, retained + `Stroke` population, and live GL callback execution remain in the preview + node. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes retained preview stroke max-size fallback, dual-preview max-size derivation, pattern-scale flips, and Bezier preview-point generation through diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 693cefd..9f0a456 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -62,6 +62,12 @@ queue, blocked queue, validation commands, and completion rules. Do not move the percentage for a narrowed adapter or added planner unless a task in that file is marked `Done` with validation and a debt/roadmap update. +Recent 2026-06-13 retained preview reductions continue to narrow DEBT-0036: +`NodeStrokePreview::draw_stroke_immediate()` now also routes +feedback/material/composite planning and stroke-shader uniform assembly through +`plan_legacy_node_stroke_preview_pass_orchestration(...)`, leaving the preview +node with a smaller live-GL callback surface around pass execution. + ## Target Component Architecture The refactor should move toward one-way dependencies: diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 085aa02..92b4af0 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -509,6 +509,15 @@ Done Checks: Progress Notes: +- 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` now routes retained + preview feedback/material/composite planning plus stroke-shader uniform + assembly through `plan_legacy_node_stroke_preview_pass_orchestration(...)`; + compositor coverage now locks destination-feedback fallback, composite-slot + intent, and the retained pattern/dual shader-uniform handoff. The preview + node still owns brush object mutation, retained `Stroke` population, and the + concrete GL pass callbacks. Next slice should target another narrow preview + execution seam without reopening the landed preview setup, mix, pass-sequence, + or final-composite helpers. - 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` now routes preview stroke max-size fallback, dual-preview max-size derivation, pattern-scale flips, and Bezier preview-point generation through diff --git a/src/legacy_canvas_stroke_shader_services.h b/src/legacy_canvas_stroke_shader_services.h index c2d8902..0f955ba 100644 --- a/src/legacy_canvas_stroke_shader_services.h +++ b/src/legacy_canvas_stroke_shader_services.h @@ -1,7 +1,6 @@ #pragma once #include "shader.h" -#include "util.h" #include diff --git a/src/legacy_node_stroke_preview_execution_services.h b/src/legacy_node_stroke_preview_execution_services.h index 8a72443..53d058c 100644 --- a/src/legacy_node_stroke_preview_execution_services.h +++ b/src/legacy_node_stroke_preview_execution_services.h @@ -3,6 +3,7 @@ #include "../libs/glm/glm/glm.hpp" #include "../libs/glm/glm/ext/matrix_clip_space.hpp" +#include "legacy_canvas_stroke_shader_services.h" #include "legacy_canvas_stroke_services.h" #include "paint_renderer/compositor.h" @@ -224,6 +225,98 @@ struct LegacyNodeStrokePreviewPassSequenceRequest { return true; } +struct LegacyNodeStrokePreviewPassOrchestrationPlan { + pp::paint_renderer::CanvasStrokeFeedbackPlan feedback {}; + pp::paint_renderer::CanvasStrokeMaterialPlan material {}; + pp::paint_renderer::StrokePreviewCompositePlan composite {}; + LegacyStrokeShaderSetupUniforms stroke_shader {}; + bool copy_stroke_destination = false; + bool background_colorize = false; +}; + +struct LegacyNodeStrokePreviewPassOrchestrationRequest { + pp::renderer::RenderDeviceFeatures features {}; + glm::vec2 preview_size {}; + float pattern_scale = 0.0f; + bool pattern_flipx = false; + bool pattern_flipy = false; + bool pattern_invert = false; + float pattern_brightness = 0.0f; + float pattern_contrast = 0.0f; + float pattern_depth = 0.0f; + bool pattern_rand_offset = false; + bool pattern_enabled = false; + bool pattern_eachsample = false; + float tip_mix = 0.0f; + float tip_wet = 0.0f; + float tip_noise = 0.0f; + bool dual_enabled = false; + int dual_blend_mode = 0; + float dual_opacity = 0.0f; + int pattern_blend_mode = 0; + int blend_mode = 0; + glm::mat4 mvp { 1.0f }; +}; + +[[nodiscard]] inline LegacyNodeStrokePreviewPassOrchestrationPlan +plan_legacy_node_stroke_preview_pass_orchestration( + const LegacyNodeStrokePreviewPassOrchestrationRequest& request) noexcept +{ + LegacyNodeStrokePreviewPassOrchestrationPlan plan; + plan.feedback = plan_legacy_node_stroke_preview_feedback( + request.features, + static_cast(request.preview_size.x), + static_cast(request.preview_size.y)); + plan.copy_stroke_destination = !plan.feedback.reads_destination_color; + plan.material = plan_legacy_canvas_stroke_material( + pp::paint_renderer::CanvasStrokeMaterialRequest { + .destination_feedback_needed = plan.copy_stroke_destination, + .pattern_enabled = request.pattern_enabled, + .pattern_eachsample = request.pattern_eachsample, + .wet_blend = request.tip_wet > 0.0f, + .mix_blend = request.tip_mix > 0.0f, + .noise_enabled = request.tip_noise > 0.0f, + .dual_brush_enabled = request.dual_enabled, + .dual_blend_mode = request.dual_blend_mode, + .pattern_blend_mode = request.pattern_blend_mode, + .dual_alpha = request.dual_opacity, + }); + plan.composite = plan_legacy_node_stroke_preview_composite( + request.tip_mix > 0.0f, + plan.material.composite_pass.use_dual, + plan.material.composite_pass.use_pattern); + + glm::vec2 preview_pattern_scale(request.pattern_scale); + if (request.pattern_flipx) { + preview_pattern_scale.x *= -1.0f; + } + if (request.pattern_flipy) { + preview_pattern_scale.y *= -1.0f; + } + + plan.stroke_shader = LegacyStrokeShaderSetupUniforms { + .resolution = request.preview_size, + .pattern = { + .scale = preview_pattern_scale, + .invert = static_cast(request.pattern_invert), + .brightness = request.pattern_brightness, + .contrast = request.pattern_contrast, + .depth = request.pattern_depth, + .blend_mode = request.pattern_blend_mode, + .offset = glm::vec2(request.pattern_rand_offset ? 0.5f : 0.0f), + }, + .mvp = request.mvp, + .uses_destination_feedback = plan.copy_stroke_destination, + .uses_pattern = false, + .mix_alpha = 0.0f, + .wet = 0.0f, + .noise = 0.0f, + .set_opacity = false, + }; + plan.background_colorize = request.tip_mix > 0.0f || request.blend_mode != 0; + return plan; +} + struct LegacyNodeStrokePreviewStrokePoint { glm::vec3 position {}; float pressure = 0.0f; diff --git a/src/node_stroke_preview.cpp b/src/node_stroke_preview.cpp index a83794a..24201bd 100644 --- a/src/node_stroke_preview.cpp +++ b/src/node_stroke_preview.cpp @@ -27,35 +27,6 @@ pp::renderer::RenderDeviceFeatures stroke_preview_render_device_features() noexc return ShaderManager::render_device_features(); } -pp::paint_renderer::CanvasStrokeFeedbackPlan stroke_preview_destination_feedback_plan( - int width, - int height) noexcept -{ - return pp::panopainter::plan_legacy_node_stroke_preview_feedback( - stroke_preview_render_device_features(), - width, - height); -} - -pp::paint_renderer::CanvasStrokeMaterialPlan stroke_preview_material_plan( - const Brush& brush, - bool destination_feedback_needed) noexcept -{ - return pp::panopainter::plan_legacy_canvas_stroke_material( - pp::paint_renderer::CanvasStrokeMaterialRequest { - .destination_feedback_needed = destination_feedback_needed, - .pattern_enabled = brush.m_pattern_enabled, - .pattern_eachsample = brush.m_pattern_eachsample, - .wet_blend = brush.m_tip_wet > 0.F, - .mix_blend = brush.m_tip_mix > 0.F, - .noise_enabled = brush.m_tip_noise > 0.F, - .dual_brush_enabled = brush.m_dual_enabled, - .dual_blend_mode = brush.m_dual_blend_mode, - .pattern_blend_mode = brush.m_pattern_blend_mode, - .dual_alpha = brush.m_dual_opacity, - }); -} - void set_active_texture_unit(std::uint32_t unit_index) { pp::legacy::ui_gl::activate_texture_unit(unit_index, "NodeStrokePreview"); @@ -761,33 +732,33 @@ void NodeStrokePreview::draw_stroke_immediate() const glm::vec2 patt_scale = stroke_setup.pattern_scale; apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false); - const auto stroke_feedback = stroke_preview_destination_feedback_plan(m_rtt.getWidth(), m_rtt.getHeight()); - const bool copy_stroke_destination = !stroke_feedback.reads_destination_color; - const auto material = stroke_preview_material_plan(*b, copy_stroke_destination); - const auto preview_composite_plan = pp::panopainter::plan_legacy_node_stroke_preview_composite( - b->m_tip_mix > 0.0f, - material.composite_pass.use_dual, - material.composite_pass.use_pattern); - pp::panopainter::setup_legacy_stroke_shader( - pp::panopainter::LegacyStrokeShaderSetupUniforms { - .resolution = size, - .pattern = { - .scale = patt_scale, - .invert = static_cast(b->m_pattern_invert), - .brightness = b->m_pattern_brightness, - .contrast = b->m_pattern_contrast, - .depth = b->m_pattern_depth, - .blend_mode = b->m_pattern_blend_mode, - .offset = glm::vec2(b->m_pattern_rand_offset ? 0.5f : 0.0f), - }, + const auto pass_orchestration = pp::panopainter::plan_legacy_node_stroke_preview_pass_orchestration( + pp::panopainter::LegacyNodeStrokePreviewPassOrchestrationRequest { + .features = stroke_preview_render_device_features(), + .preview_size = size, + .pattern_scale = b->m_pattern_scale, + .pattern_flipx = b->m_pattern_flipx, + .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, - .uses_destination_feedback = copy_stroke_destination, - .uses_pattern = false, - .mix_alpha = 0.0f, - .wet = 0.0f, - .noise = 0.0f, - .set_opacity = false, }); + const bool copy_stroke_destination = pass_orchestration.copy_stroke_destination; + const auto& material = pass_orchestration.material; + pp::panopainter::setup_legacy_stroke_shader(pass_orchestration.stroke_shader); const bool sequence_ok = pp::panopainter::execute_legacy_node_stroke_preview_pass_sequence( pp::panopainter::LegacyNodeStrokePreviewPassSequenceRequest { @@ -817,7 +788,7 @@ void NodeStrokePreview::draw_stroke_immediate() .capture_background = [&] { execute_stroke_preview_background_capture_pass( size, - b->m_tip_mix > 0.f || b->m_blend_mode != 0, + pass_orchestration.background_colorize, m_tex_background, [&] { m_plane.draw_fill(); @@ -836,7 +807,7 @@ void NodeStrokePreview::draw_stroke_immediate() m_tex, m_rtt_mixer, copy_stroke_destination, - preview_composite_plan.uses_mixer); + pass_orchestration.composite.uses_mixer); }, .execute_main_pass = [&] { execute_stroke_preview_live_pass( diff --git a/src/shader.h b/src/shader.h index 619bd12..aef0db0 100644 --- a/src/shader.h +++ b/src/shader.h @@ -1,91 +1,104 @@ #pragma once #include "renderer_api/renderer_api.h" -#include "util.h" + +#include "../libs/glm/glm/glm.hpp" + +#include +#include +#include +#include + +uint16_t constexpr shader_const_hash(const char* input) +{ + return *input ? + static_cast(*input) + 33 * shader_const_hash(input + 1) : + 5381; +} bool check_uniform_uniqueness(); enum class kShaderUniform : uint16_t { - MVP = const_hash("mvp"), - Tex = const_hash("tex"), - TexFG = const_hash("tex_fg"), - TexBG = const_hash("tex_bg"), - TexMix = const_hash("tex_mix"), - TexMixA = const_hash("tex_mix_alpha"), - TexMask = const_hash("tex_mask"), - TexDual = const_hash("tex_dual"), - TexStroke = const_hash("tex_stroke"), - TexPattern = const_hash("tex_pattern"), - PatternOffset = const_hash("pattern_offset"), - PatternAlpha = const_hash("pattern_alpha"), - MixAlpha = const_hash("mix_alpha"), - Opacity = const_hash("opacity"), - Wet = const_hash("wet"), - Lock = const_hash("lock"), - Col = const_hash("col"), - Tof = const_hash("tof"), - Tsz = const_hash("tsz"), - Alpha = const_hash("alpha"), - Mask = const_hash("mask"), - Resolution = const_hash("resolution"), - Highlight = const_hash("highlight"), - BlendMode = const_hash("blend_mode"), - DualBlendMode = const_hash("dual_blend_mode"), - Noise = const_hash("noise"), - Direction = const_hash("dir"), - UseDual = const_hash("use_dual"), - UsePattern = const_hash("use_pattern"), - LightDir = const_hash("light_dir"), - Mode = const_hash("mode"), - Ambient = const_hash("ambient"), - PatternInvert = const_hash("pattern_invert"), - PatternScale = const_hash("pattern_scale"), - PatternBright = const_hash("pattern_bright"), - PatternContrast = const_hash("pattern_contr"), - PatternDepth = const_hash("pattern_depth"), - PatternBlendMode = const_hash("patt_blend_mode"), - Colorize = const_hash("colorize"), - DualAlpha = const_hash("dual_alpha"), - UseFragcoord = const_hash("use_fragcoord"), - DrawOutline = const_hash("draw_outline"), + MVP = shader_const_hash("mvp"), + Tex = shader_const_hash("tex"), + TexFG = shader_const_hash("tex_fg"), + TexBG = shader_const_hash("tex_bg"), + TexMix = shader_const_hash("tex_mix"), + TexMixA = shader_const_hash("tex_mix_alpha"), + TexMask = shader_const_hash("tex_mask"), + TexDual = shader_const_hash("tex_dual"), + TexStroke = shader_const_hash("tex_stroke"), + TexPattern = shader_const_hash("tex_pattern"), + PatternOffset = shader_const_hash("pattern_offset"), + PatternAlpha = shader_const_hash("pattern_alpha"), + MixAlpha = shader_const_hash("mix_alpha"), + Opacity = shader_const_hash("opacity"), + Wet = shader_const_hash("wet"), + Lock = shader_const_hash("lock"), + Col = shader_const_hash("col"), + Tof = shader_const_hash("tof"), + Tsz = shader_const_hash("tsz"), + Alpha = shader_const_hash("alpha"), + Mask = shader_const_hash("mask"), + Resolution = shader_const_hash("resolution"), + Highlight = shader_const_hash("highlight"), + BlendMode = shader_const_hash("blend_mode"), + DualBlendMode = shader_const_hash("dual_blend_mode"), + Noise = shader_const_hash("noise"), + Direction = shader_const_hash("dir"), + UseDual = shader_const_hash("use_dual"), + UsePattern = shader_const_hash("use_pattern"), + LightDir = shader_const_hash("light_dir"), + Mode = shader_const_hash("mode"), + Ambient = shader_const_hash("ambient"), + PatternInvert = shader_const_hash("pattern_invert"), + PatternScale = shader_const_hash("pattern_scale"), + PatternBright = shader_const_hash("pattern_bright"), + PatternContrast = shader_const_hash("pattern_contr"), + PatternDepth = shader_const_hash("pattern_depth"), + PatternBlendMode = shader_const_hash("patt_blend_mode"), + Colorize = shader_const_hash("colorize"), + DualAlpha = shader_const_hash("dual_alpha"), + UseFragcoord = shader_const_hash("use_fragcoord"), + DrawOutline = shader_const_hash("draw_outline"), }; enum class kShader : uint16_t { - Color = const_hash("color"), - ColorQuad = const_hash("color-quad"), - ColorTri = const_hash("color-tri"), - ColorHue = const_hash("color-hue"), - Texture = const_hash("texture"), - TextureMask = const_hash("texture-mask"), - TextureColorize = const_hash("texture-colorize"), - TextureAlpha= const_hash("texture-alpha"), - TextureBlend= const_hash("texture-blend"), - CompErase = const_hash("comp-erase"), - CompDraw = const_hash("comp-draw"), - UVs = const_hash("uvs"), - UVs_2 = const_hash("uvs2"), - Font = const_hash("font"), - Atlas = const_hash("atlas"), - Stroke = const_hash("stroke"), - StrokePad = const_hash("stroke-pad"), - StrokeDilate= const_hash("stroke-dilate"), - StrokePreview = const_hash("stroke-preview"), - Checkerboard= const_hash("checkerboard"), - Equirect = const_hash("equirect"), - BrushStroke = const_hash("brush-stroke"), - VertexColor = const_hash("vertex-color"), - Lambert = const_hash("lambert"), - LambertLightmap = const_hash("lambert-lightmap"), - BakeUV = const_hash("bakeuv"), + Color = shader_const_hash("color"), + ColorQuad = shader_const_hash("color-quad"), + ColorTri = shader_const_hash("color-tri"), + ColorHue = shader_const_hash("color-hue"), + Texture = shader_const_hash("texture"), + TextureMask = shader_const_hash("texture-mask"), + TextureColorize = shader_const_hash("texture-colorize"), + TextureAlpha= shader_const_hash("texture-alpha"), + TextureBlend= shader_const_hash("texture-blend"), + CompErase = shader_const_hash("comp-erase"), + CompDraw = shader_const_hash("comp-draw"), + UVs = shader_const_hash("uvs"), + UVs_2 = shader_const_hash("uvs2"), + Font = shader_const_hash("font"), + Atlas = shader_const_hash("atlas"), + Stroke = shader_const_hash("stroke"), + StrokePad = shader_const_hash("stroke-pad"), + StrokeDilate= shader_const_hash("stroke-dilate"), + StrokePreview = shader_const_hash("stroke-preview"), + Checkerboard= shader_const_hash("checkerboard"), + Equirect = shader_const_hash("equirect"), + BrushStroke = shader_const_hash("brush-stroke"), + VertexColor = shader_const_hash("vertex-color"), + Lambert = shader_const_hash("lambert"), + LambertLightmap = shader_const_hash("lambert-lightmap"), + BakeUV = shader_const_hash("bakeuv"), }; class Shader { std::map m_deps; std::string m_path; - std::map m_umap; - GLuint prog; + std::map m_umap; + unsigned int prog; std::string read(const std::string& path); public: kShader name; @@ -102,7 +115,7 @@ public: void u_int(kShaderUniform id, int i); void u_int(const char* uniform_name, int i); void u_float(kShaderUniform id, float f); - GLint GetAttribLocation(const char* attribute_name); + int GetAttribLocation(const char* attribute_name); }; class ShaderManager diff --git a/src/util.h b/src/util.h index e1baa0f..f255a4f 100644 --- a/src/util.h +++ b/src/util.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + #ifdef _DEBUG #define GL(stmt) stmt; check_OpenGLError(#stmt, __FILE__, __LINE__); #else diff --git a/tests/paint_renderer/compositor_tests.cpp b/tests/paint_renderer/compositor_tests.cpp index 4257196..1134080 100644 --- a/tests/paint_renderer/compositor_tests.cpp +++ b/tests/paint_renderer/compositor_tests.cpp @@ -2514,6 +2514,77 @@ void legacy_node_stroke_preview_stroke_setup_plan_preserves_curve_and_dual_input PP_EXPECT(h, pressure_fallback.points.empty()); } +void legacy_node_stroke_preview_pass_orchestration_plan_preserves_feedback_material_and_composite_inputs( + pp::tests::Harness& h) +{ + const auto plan = pp::panopainter::plan_legacy_node_stroke_preview_pass_orchestration( + pp::panopainter::LegacyNodeStrokePreviewPassOrchestrationRequest { + .features = RenderDeviceFeatures { .framebuffer_fetch = false }, + .preview_size = glm::vec2(96.0F, 48.0F), + .pattern_scale = 0.75F, + .pattern_flipx = true, + .pattern_flipy = false, + .pattern_invert = true, + .pattern_brightness = 0.2F, + .pattern_contrast = 0.3F, + .pattern_depth = 0.4F, + .pattern_rand_offset = true, + .pattern_enabled = true, + .pattern_eachsample = false, + .tip_mix = 0.5F, + .tip_wet = 0.25F, + .tip_noise = 0.1F, + .dual_enabled = true, + .dual_blend_mode = 2, + .dual_opacity = 0.6F, + .pattern_blend_mode = 3, + .blend_mode = 4, + .mvp = glm::ortho(-2.0F, 2.0F, -1.0F, 1.0F), + }); + + PP_EXPECT(h, plan.feedback.compatibility_fallback); + PP_EXPECT(h, !plan.feedback.reads_destination_color); + PP_EXPECT(h, plan.copy_stroke_destination); + PP_EXPECT(h, plan.background_colorize); + + PP_EXPECT(h, plan.material.stroke_pass.uses_destination_feedback); + PP_EXPECT(h, plan.material.composite_pass.use_dual); + PP_EXPECT(h, plan.material.composite_pass.use_pattern); + PP_EXPECT(h, plan.material.composite_pass.pattern_blend_mode == 3); + PP_EXPECT(h, plan.material.composite_pass.dual_blend_mode == 2); + PP_EXPECT(h, near(plan.material.composite_pass.dual_alpha, 0.6F)); + + PP_EXPECT(h, plan.composite.uses_mixer); + PP_EXPECT(h, plan.composite.uses_dual == plan.material.composite_pass.use_dual); + PP_EXPECT(h, plan.composite.uses_pattern == plan.material.composite_pass.use_pattern); + const std::size_t expected_texture_slots = + 2U + + (plan.composite.uses_dual ? 1U : 0U) + + (plan.composite.uses_pattern ? 1U : 0U) + + (plan.composite.uses_mixer ? 1U : 0U); + PP_EXPECT(h, plan.composite.texture_slot_count == expected_texture_slots); + PP_EXPECT(h, has_preview_texture_slot(plan.composite, StrokePreviewTextureRole::background, 0)); + PP_EXPECT(h, has_preview_texture_slot(plan.composite, StrokePreviewTextureRole::stroke, 1)); + PP_EXPECT(h, has_preview_texture_slot(plan.composite, StrokePreviewTextureRole::dual, 3)); + PP_EXPECT(h, has_preview_texture_slot(plan.composite, StrokePreviewTextureRole::mixer, 3)); + PP_EXPECT(h, has_preview_texture_slot(plan.composite, StrokePreviewTextureRole::pattern, 4)); + + PP_EXPECT(h, near(plan.stroke_shader.resolution, glm::vec2(96.0F, 48.0F))); + PP_EXPECT(h, near(plan.stroke_shader.pattern.scale, glm::vec2(-0.75F, 0.75F))); + PP_EXPECT(h, near(plan.stroke_shader.pattern.invert, 1.0F)); + PP_EXPECT(h, near(plan.stroke_shader.pattern.brightness, 0.2F)); + PP_EXPECT(h, near(plan.stroke_shader.pattern.contrast, 0.3F)); + PP_EXPECT(h, near(plan.stroke_shader.pattern.depth, 0.4F)); + PP_EXPECT(h, plan.stroke_shader.pattern.blend_mode == 3); + PP_EXPECT(h, near(plan.stroke_shader.pattern.offset, glm::vec2(0.5F, 0.5F))); + PP_EXPECT(h, plan.stroke_shader.uses_destination_feedback); + PP_EXPECT(h, !plan.stroke_shader.uses_pattern); + PP_EXPECT(h, !plan.stroke_shader.set_opacity); + PP_EXPECT(h, near(plan.stroke_shader.mix_alpha, 0.0F)); + PP_EXPECT(h, near(plan.stroke_shader.wet, 0.0F)); + PP_EXPECT(h, near(plan.stroke_shader.noise, 0.0F)); +} + void plans_canvas_blend_gate_from_persisted_indices(pp::tests::Harness& h) { const std::vector normal_layers { 0, 0, 0 }; @@ -2977,6 +3048,9 @@ int main() harness.run( "legacy_node_stroke_preview_stroke_setup_plan_preserves_curve_and_dual_inputs", legacy_node_stroke_preview_stroke_setup_plan_preserves_curve_and_dual_inputs); + harness.run( + "legacy_node_stroke_preview_pass_orchestration_plan_preserves_feedback_material_and_composite_inputs", + legacy_node_stroke_preview_pass_orchestration_plan_preserves_feedback_material_and_composite_inputs); harness.run("plans_canvas_blend_gate_from_persisted_indices", plans_canvas_blend_gate_from_persisted_indices); harness.run("canvas_blend_gate_preserves_legacy_fallbacks", canvas_blend_gate_preserves_legacy_fallbacks); harness.run("plans_canvas_stroke_feedback_paths", plans_canvas_stroke_feedback_paths);