diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 032802f..d54c778 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,12 @@ agent or engineer to remove them without reconstructing context from chat. ## Recent Reductions +- 2026-06-12: DEBT-0036 was narrowed again. Live `Canvas::stroke_draw`, + stroke commit, and draw-merge paths now consume `CanvasStrokeMaterialPlan` + through `plan_legacy_canvas_stroke_material` for destination feedback, + each-sample pattern, dual-brush, and final composite material decisions. + Retained OpenGL draw calls and `NodeStrokePreview` still duplicate execution + and preview material wiring. - 2026-06-12: DEBT-0036 was narrowed again. Live `Canvas::stroke_draw` destination feedback now consumes a named `CanvasStrokeRasterizationPlan` through `plan_legacy_canvas_stroke_rasterization`, and `pp_paint_renderer` diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index f73b50e..07d076c 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -1339,8 +1339,10 @@ services: `Canvas::stroke_draw` now consumes a named `CanvasStrokeRasterizationPlan` through a legacy adapter boundary for destination feedback/copy decisions, and `pp_paint_renderer` owns pure stroke material/pass planning for pattern, mixer, dual-brush, and final -composite texture/uniform intent. Actual retained OpenGL draw execution remains -in `Canvas` under `DEBT-0036`. +composite texture/uniform intent. Live `Canvas::stroke_draw`, stroke commit, +and draw-merge paths consume that material plan for destination feedback, +each-sample pattern, dual-brush, and final composite decisions. Actual retained +OpenGL draw execution and preview material wiring remain under `DEBT-0036`. It also owns renderer API texture-format to OpenGL internal/pixel/component token mapping, including depth-stencil formats, for future backend texture objects. `Texture2D` 2D texture binding, upload, diff --git a/src/canvas.cpp b/src/canvas.cpp index bec4bbc..7a2a37f 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -61,6 +61,25 @@ pp::paint_renderer::CanvasStrokeFeedbackPlan canvas_destination_feedback_plan( return canvas_stroke_rasterization_plan(width, height).feedback; } +pp::paint_renderer::CanvasStrokeMaterialPlan canvas_stroke_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, + }); +} + pp::paint_renderer::CanvasBlendGatePlan draw_merge_blend_gate_plan( int width, int height, @@ -666,11 +685,12 @@ void Canvas::stroke_draw() const auto stroke_rasterization = canvas_stroke_rasterization_plan(m_width, m_height); const bool copy_stroke_destination = stroke_rasterization.copy_stroke_destination; + const auto stroke_material = canvas_stroke_material_plan(*brush, copy_stroke_destination); apply_canvas_capability(blend_state(), false); ShaderManager::use(kShader::Stroke); ShaderManager::u_int(kShaderUniform::Tex, 0); // brush - if (copy_stroke_destination) + if (stroke_material.stroke_pass.uses_destination_feedback) ShaderManager::u_int(kShaderUniform::TexBG, 1); // bg ShaderManager::u_int(kShaderUniform::TexPattern, 2); // pattern ShaderManager::u_int(kShaderUniform::TexMix, 3); // mixer @@ -684,7 +704,7 @@ void Canvas::stroke_draw() ShaderManager::u_float(kShaderUniform::PatternDepth, brush->m_pattern_depth); ShaderManager::u_int(kShaderUniform::PatternBlendMode, brush->m_pattern_blend_mode); ShaderManager::u_vec2(kShaderUniform::PatternOffset, m_pattern_offset); - ShaderManager::u_int(kShaderUniform::UsePattern, brush->m_pattern_enabled && brush->m_pattern_eachsample); + ShaderManager::u_int(kShaderUniform::UsePattern, stroke_material.stroke_pass.uses_pattern); ShaderManager::u_float(kShaderUniform::MixAlpha, brush->m_tip_mix); ShaderManager::u_float(kShaderUniform::Wet, brush->m_tip_wet); ShaderManager::u_float(kShaderUniform::Noise, brush->m_tip_noise); @@ -803,10 +823,10 @@ void Canvas::stroke_draw() // DRAW DUAL BRUSH - if (brush->m_dual_enabled) + if (stroke_material.dual_pass.enabled) { ShaderManager::use(kShader::Stroke); - ShaderManager::u_int(kShaderUniform::UsePattern, false); + ShaderManager::u_int(kShaderUniform::UsePattern, stroke_material.dual_pass.uses_pattern); ShaderManager::u_float(kShaderUniform::MixAlpha, 0); ShaderManager::u_float(kShaderUniform::Wet, 0); ShaderManager::u_float(kShaderUniform::Noise, 0); @@ -831,7 +851,7 @@ void Canvas::stroke_draw() m_tmp_dual[i].unbindFramebuffer(); // this mode overflows the main brush boundries - if (brush->m_dual_blend_mode == 0) + if (stroke_material.composite_pass.dual_blend_mode == 0) m_dirty_box[i] = glm::clamp(box_union(m_dirty_box[i], box_sample), glm::vec4(0), glm::vec4(m_width)); } } @@ -1040,6 +1060,7 @@ void Canvas::stroke_commit() } else { + const auto stroke_material = canvas_stroke_material_plan(*b, false); 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; @@ -1057,10 +1078,10 @@ void Canvas::stroke_commit() ShaderManager::u_int(kShaderUniform::UseFragcoord, false); ShaderManager::u_int(kShaderUniform::BlendMode, b->m_blend_mode); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); - ShaderManager::u_int(kShaderUniform::UseDual, b->m_dual_enabled); - ShaderManager::u_int(kShaderUniform::DualBlendMode, b->m_dual_blend_mode); - ShaderManager::u_float(kShaderUniform::DualAlpha, b->m_dual_opacity); - ShaderManager::u_int(kShaderUniform::UsePattern, b->m_pattern_enabled && !b->m_pattern_eachsample); + ShaderManager::u_int(kShaderUniform::UseDual, stroke_material.composite_pass.use_dual); + ShaderManager::u_int(kShaderUniform::DualBlendMode, stroke_material.composite_pass.dual_blend_mode); + ShaderManager::u_float(kShaderUniform::DualAlpha, stroke_material.composite_pass.dual_alpha); + ShaderManager::u_int(kShaderUniform::UsePattern, stroke_material.composite_pass.use_pattern); ShaderManager::u_vec2(kShaderUniform::PatternScale, patt_scale); ShaderManager::u_float(kShaderUniform::PatternInvert, b->m_pattern_invert); ShaderManager::u_float(kShaderUniform::PatternBright, b->m_pattern_brightness); @@ -1076,7 +1097,7 @@ void Canvas::stroke_commit() set_active_texture_unit(2); m_smask.rtt(i).bindTexture(); set_active_texture_unit(3); - if (b->m_dual_enabled) + if (stroke_material.composite_pass.use_dual) m_tmp_dual[i].bindTexture(); set_active_texture_unit(4); b->m_pattern_texture ? @@ -1084,7 +1105,7 @@ void Canvas::stroke_commit() unbind_texture_2d(); m_plane.draw_fill(); set_active_texture_unit(3); - if (b->m_dual_enabled) + if (stroke_material.composite_pass.use_dual) m_tmp_dual[i].unbindTexture(); set_active_texture_unit(2); m_smask.rtt(i).unbindTexture(); @@ -1248,6 +1269,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array faces /*= SI m_sampler.bind(3); m_sampler_stencil.bind(4); + const auto stroke_material = canvas_stroke_material_plan(*b, false); 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; @@ -1265,9 +1287,9 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array faces /*= SI ShaderManager::u_int(kShaderUniform::UseFragcoord, false); ShaderManager::u_int(kShaderUniform::BlendMode, b->m_blend_mode); ShaderManager::u_mat4(kShaderUniform::MVP, ortho); - ShaderManager::u_int(kShaderUniform::UseDual, b->m_dual_enabled); - ShaderManager::u_int(kShaderUniform::DualBlendMode, b->m_dual_blend_mode); - ShaderManager::u_int(kShaderUniform::UsePattern, b->m_pattern_enabled && !b->m_pattern_eachsample); + ShaderManager::u_int(kShaderUniform::UseDual, stroke_material.composite_pass.use_dual); + ShaderManager::u_int(kShaderUniform::DualBlendMode, stroke_material.composite_pass.dual_blend_mode); + ShaderManager::u_int(kShaderUniform::UsePattern, stroke_material.composite_pass.use_pattern); ShaderManager::u_vec2(kShaderUniform::PatternScale, patt_scale); ShaderManager::u_float(kShaderUniform::PatternInvert, b->m_pattern_invert); ShaderManager::u_float(kShaderUniform::PatternBright, b->m_pattern_brightness); @@ -1275,7 +1297,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array faces /*= SI ShaderManager::u_float(kShaderUniform::PatternDepth, b->m_pattern_depth); ShaderManager::u_int(kShaderUniform::PatternBlendMode, b->m_pattern_blend_mode); ShaderManager::u_vec2(kShaderUniform::PatternOffset, Canvas::I->m_pattern_offset); - ShaderManager::u_float(kShaderUniform::DualAlpha, b->m_dual_opacity); + ShaderManager::u_float(kShaderUniform::DualAlpha, stroke_material.composite_pass.dual_alpha); set_active_texture_unit(0); m_layers[layer_index]->rtt(plane_index).bindTexture(); @@ -1284,7 +1306,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array faces /*= SI set_active_texture_unit(2); m_smask.rtt(plane_index).bindTexture(); set_active_texture_unit(3); - if (b->m_dual_enabled) + if (stroke_material.composite_pass.use_dual) m_tmp_dual[plane_index].bindTexture(); set_active_texture_unit(4); b->m_pattern_texture ? @@ -1292,7 +1314,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array faces /*= SI unbind_texture_2d(); m_plane.draw_fill(); set_active_texture_unit(3); - if (b->m_dual_enabled) + if (stroke_material.composite_pass.use_dual) m_tmp_dual[plane_index].unbindTexture(); set_active_texture_unit(2); m_smask.rtt(plane_index).unbindTexture(); diff --git a/src/legacy_canvas_stroke_services.h b/src/legacy_canvas_stroke_services.h index ead7e74..c4507fd 100644 --- a/src/legacy_canvas_stroke_services.h +++ b/src/legacy_canvas_stroke_services.h @@ -32,4 +32,10 @@ namespace pp::panopainter { return fallback; } +[[nodiscard]] inline pp::paint_renderer::CanvasStrokeMaterialPlan plan_legacy_canvas_stroke_material( + pp::paint_renderer::CanvasStrokeMaterialRequest request) noexcept +{ + return pp::paint_renderer::plan_canvas_stroke_material(request); +} + } // namespace pp::panopainter