#pragma once #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" #include #include #include #include namespace pp::panopainter { [[nodiscard]] inline pp::paint_renderer::CanvasStrokeFeedbackPlan plan_legacy_node_stroke_preview_feedback( pp::renderer::RenderDeviceFeatures features, int width, int height) noexcept { const auto plan = pp::paint_renderer::plan_canvas_stroke_feedback( features, pp::renderer::Extent2D { .width = static_cast(std::max(width, 0)), .height = static_cast(std::max(height, 0)), }); if (plan) { return plan.value(); } pp::paint_renderer::CanvasStrokeFeedbackPlan fallback; fallback.compatibility_fallback = true; fallback.path = pp::paint_renderer::StrokeCompositePath::ping_pong_textures; fallback.requires_auxiliary_texture = true; return fallback; } [[nodiscard]] inline pp::paint_renderer::StrokePreviewCompositePlan plan_legacy_node_stroke_preview_composite( bool uses_mixer, bool uses_dual, bool uses_pattern) noexcept { return pp::paint_renderer::plan_stroke_preview_composite( pp::paint_renderer::StrokePreviewCompositeRequest { .uses_mixer = uses_mixer, .uses_dual = uses_dual, .uses_pattern = uses_pattern, }); } struct LegacyNodeStrokePreviewMixPassPlan { pp::paint_renderer::CanvasStrokeMaterialPlan material {}; struct ShaderPlan { glm::vec2 resolution {}; glm::vec2 pattern_scale {}; float pattern_invert = 0.0f; float pattern_brightness = 0.0f; float pattern_contrast = 0.0f; float pattern_depth = 0.0f; int pattern_blend_mode = 0; glm::vec2 pattern_offset {}; int blend_mode = 0; bool use_dual = false; int dual_blend_mode = 0; float dual_alpha = 0.0f; bool use_pattern = false; } shader {}; }; struct LegacyNodeStrokePreviewMixPassRequest { glm::vec2 resolution {}; 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_wet = 0.0f; float tip_mix = 0.0f; float tip_noise = 0.0f; bool dual_enabled = false; int dual_blend_mode = 0; int pattern_blend_mode = 0; float dual_opacity = 0.0f; int blend_mode = 0; }; struct LegacyNodeStrokePreviewMixExecutionRequest { LegacyNodeStrokePreviewMixPassPlan::ShaderPlan shader {}; int mixer_width = 0; int mixer_height = 0; int scissor_x = 0; int scissor_y = 0; int scissor_width = 0; int scissor_height = 0; std::function save_state; std::function setup_mix_shader; std::function bind_mixer_framebuffer; std::function configure_mix_target_state; std::function bind_mix_inputs; std::function draw_mix; std::function unbind_mixer_framebuffer; std::function restore_state; }; [[nodiscard]] inline LegacyNodeStrokePreviewMixPassPlan plan_legacy_node_stroke_preview_mix_pass( const LegacyNodeStrokePreviewMixPassRequest& request) noexcept { glm::vec2 pattern_scale(request.pattern_scale); if (request.pattern_flipx) { pattern_scale.x *= -1.0f; } if (request.pattern_flipy) { pattern_scale.y *= -1.0f; } LegacyNodeStrokePreviewMixPassPlan plan; plan.material = plan_legacy_canvas_stroke_material( pp::paint_renderer::CanvasStrokeMaterialRequest { .destination_feedback_needed = false, .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.shader = LegacyNodeStrokePreviewMixPassPlan::ShaderPlan { .resolution = request.resolution, .pattern_scale = pattern_scale, .pattern_invert = static_cast(request.pattern_invert), .pattern_brightness = request.pattern_brightness, .pattern_contrast = request.pattern_contrast, .pattern_depth = request.pattern_depth, .pattern_blend_mode = plan.material.composite_pass.pattern_blend_mode, .pattern_offset = glm::vec2(request.pattern_rand_offset ? 0.5f : 0.0f), .blend_mode = request.blend_mode, .use_dual = plan.material.composite_pass.use_dual, .dual_blend_mode = plan.material.composite_pass.dual_blend_mode, .dual_alpha = plan.material.composite_pass.dual_alpha, .use_pattern = plan.material.composite_pass.use_pattern, }; return plan; } [[nodiscard]] inline bool execute_legacy_node_stroke_preview_mix_pass( const LegacyNodeStrokePreviewMixExecutionRequest& request) { if (request.mixer_width <= 0 || request.mixer_height <= 0 || !request.save_state || !request.setup_mix_shader || !request.bind_mixer_framebuffer || !request.configure_mix_target_state || !request.bind_mix_inputs || !request.draw_mix || !request.unbind_mixer_framebuffer || !request.restore_state) { return false; } request.save_state(); request.setup_mix_shader(request.shader); request.bind_mixer_framebuffer(); request.configure_mix_target_state( request.mixer_width, request.mixer_height, request.scissor_x, request.scissor_y, request.scissor_width, request.scissor_height); request.bind_mix_inputs(); request.draw_mix(); request.unbind_mixer_framebuffer(); request.restore_state(); return true; } struct LegacyNodeStrokePreviewPassSequenceRequest { bool dual_pass_enabled = false; std::function prepare_dual_pass; std::function execute_dual_pass; std::function capture_background; std::function prepare_main_pass; std::function execute_main_pass; std::function finish_main_pass; std::function execute_final_composite; std::function copy_preview_result; }; [[nodiscard]] inline bool execute_legacy_node_stroke_preview_pass_sequence( const LegacyNodeStrokePreviewPassSequenceRequest& request) { if (!request.capture_background || !request.prepare_main_pass || !request.execute_main_pass || !request.finish_main_pass || !request.execute_final_composite || !request.copy_preview_result) { return false; } if (request.dual_pass_enabled) { if (!request.prepare_dual_pass || !request.execute_dual_pass) { return false; } request.prepare_dual_pass(); request.execute_dual_pass(); } request.capture_background(); request.prepare_main_pass(); request.execute_main_pass(); request.finish_main_pass(); request.execute_final_composite(); request.copy_preview_result(); 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; }; struct LegacyNodeStrokePreviewStrokeSetupPlan { float stroke_max_size = 0.0f; float dual_stroke_max_size = 0.0f; bool dual_enabled = false; glm::vec2 pattern_scale {}; std::vector points; }; struct LegacyNodeStrokePreviewStrokeSetupRequest { glm::vec2 preview_size {}; float zoom = 1.0f; float brush_tip_size = 0.0f; float stroke_max_size_override = 0.0f; float pad_override = NAN; bool tip_size_pressure = false; bool dual_enabled = false; float dual_size = 1.0f; float pattern_scale = 0.0f; bool pattern_flipx = false; bool pattern_flipy = false; int preview_point_count = 100; }; [[nodiscard]] inline glm::vec2 evaluate_legacy_node_stroke_preview_bezier( std::vector control_points, float t) noexcept { if (control_points.empty()) { return {}; } for (std::size_t remaining = control_points.size(); remaining > 1; --remaining) { for (std::size_t i = 0; i + 1 < remaining; ++i) { control_points[i] = glm::mix(control_points[i], control_points[i + 1], t); } } return control_points.front(); } [[nodiscard]] inline LegacyNodeStrokePreviewStrokeSetupPlan plan_legacy_node_stroke_preview_stroke_setup( const LegacyNodeStrokePreviewStrokeSetupRequest& request) noexcept { LegacyNodeStrokePreviewStrokeSetupPlan plan; plan.stroke_max_size = request.stroke_max_size_override > 0.0f ? request.stroke_max_size_override : request.preview_size.y * 0.75f; plan.dual_enabled = request.dual_enabled; plan.dual_stroke_max_size = plan.stroke_max_size * request.dual_size; plan.pattern_scale = glm::vec2(request.pattern_scale); if (request.pattern_flipx) { plan.pattern_scale.x *= -1.0f; } if (request.pattern_flipy) { plan.pattern_scale.y *= -1.0f; } const float min_pad = request.preview_size.x * 0.05f; float pad = (5.0f + glm::max(glm::min(plan.stroke_max_size, request.brush_tip_size) / 2.0f, min_pad)) * request.zoom; if (request.tip_size_pressure) { pad = min_pad * request.zoom; } if (!std::isnan(request.pad_override)) { pad = request.pad_override; } const float width = request.preview_size.x * request.zoom; const float height = request.preview_size.y * request.zoom; const std::vector keypoints { { pad, height / 2.0f }, { width / 2.0f, 0.0f }, { width / 2.0f, height }, { width - pad, height / 2.0f }, }; const int point_count = std::max(request.preview_point_count, 0); plan.points.reserve(static_cast(point_count)); for (int i = 0; i < point_count; ++i) { const float t = static_cast(i) / static_cast(point_count); const float pressure = glm::clamp((1.0f - glm::abs(t * 2.0f - 1.0f)) * 1.1f, 0.0f, 1.0f); plan.points.push_back(LegacyNodeStrokePreviewStrokePoint { .position = glm::vec3(evaluate_legacy_node_stroke_preview_bezier(keypoints, t), 0.0f), .pressure = pressure, }); } return plan; } } // namespace pp::panopainter